Annotate with intermediate model in Django - python

I have a two models Group and User.
class User(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
users = models.ManyToManyField(User)
A user can be in multiple groups and a group can have multiple users so I have made a ManyToManyField in Group.
I have made an intermediate model to store the date when the user was added to the group
class User(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
users = models.ManyToManyField(User, through='GroupUser')
class GroupUser(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group)
It works as it should.
But I have a UserListView in which I want to annotate the number of groups each user belongs to.
I have overriden get_queryset in ListView
def get_queryset(self):
return super(UserListView, self).get_queryset().annotate(num_groups=Count(?))
but I don't know how to count correctly.

I believe this should work:
def get_queryset(self):
return super(UserListView, self).get_queryset().annotate(num_groups=Count('groupuser'))

Related

Through model in Django

I am getting errors while I am building the following database. Idea is that you create a Team object. Create Student Objects and link them to Teams. And then give the Students points through PointEntry objects. I want to relate the team given to each Student object in each PointEntry Object. But for some reason, Django gives the error:
score_board.Student: (fields.E336) The model is used as an intermediate model by 'score_board.PointEntry.team', but it does not have a foreign key to 'PointEntry' or 'Team'.
Modeladmin Class
class StudentAdmin(admin.ModelAdmin):
list_display = ['name', 'team']
list_filter = ['team']
class PointEntryAdmin(admin.ModelAdmin):
list_display = ['student', 'points']
list_filter = ['student']
Below are the models
class Team(models.Model):
# Team/group model
name = models.CharField(max_length=64)
class Student(models.Model):
# Student model
name = models.CharField(max_length=64)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
def __str__(self):
return f"{self.name}"
class PointEntry(models.Model):
# Point entry's made by users appointed to a student in a group
student = models.OneToOneField(Student, on_delete=models.CASCADE)
points = models.IntegerField()
team = models.ManyToManyField(Team, through='Student')
Your through model needs a ForeignKey to both models. Since the model that defines the ManyToManyField is defined lower, you can not reference to the class. But in Django, you can also use a string literal:
class Student(models.Model):
name = models.CharField(max_length=64)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
pointentry = models.ForeignKey('PointEntry', on_delete=models.CASCADE)
def __str__(self):
return f'{self.name}'
class PointEntry(models.Model):
# Point entry's made by users appointed to a student in a group
student = models.OneToOneField(Student, on_delete=models.CASCADE)
points = models.IntegerField()
team = models.ManyToManyField(Team, through='Student')
Django will then replace the string with a reference to the class.
class Team(models.Model):
# Team/group model
name = models.CharField(max_length=64)
student = models. models.ManyToManyField(Student,through='PointEntry ')
class Student(models.Model):
# Student model
name = models.CharField(max_length=64)
team = models. models.ManyToManyField(Team,through='PointEntry')
def __str__(self):
return f"{self.name}"
class PointEntry(models.Model):
# Point entry's made by users appointed to a student in a group
student = models. ForeignKey(Student,on_delete=models.CASCADE)
team = models. ForeignKey(Team, on_delete=models.CASCADE)
points = models.IntegerField()
I think a model like this might work for what you’re trying to achieve.

How can I filter related field in django?

Say I have the model User which has a many-to-many relation with the model Company; and the model UserType, which is connected to both User and Company. Like this:
class User(models.Model):
name = models.TextField()
class Company(models.Model):
name = models.TextField()
users = models.ManyToManyField(User, related_name="companies")
class UserType(models.Model):
name = models.TextField()
company = models.ForeignKey(Company, related_name="user_types")
users = models.ManyToManyField(User, related_name="user_types")
I wanna find all Users in a Company, which is simple enough: User.objects.filter(companies=some_company). However, I also wanna filter the user_types field on the returned users objects, so that only UserType objects connected to the given Company is returned. To explain it with code, this should return true:
def check_user_types(users, company):
for user in users:
for user_type in user.user_types:
if user_type.company != company:
return false
return true
How would I do this?
I figured it out. For anyone facing the same problem, this solved it:
from django.db.models import Prefetch
users = User.objects.filter(companies=company).prefetch_related(
Prefetch(
"user_types",
queryset=UserType.objects.filter(company=company),
)
)

CreateView doesn't save related objects

I have two models: Student and Group. A group consists of multiple students and a student can be in only one group. Here are the models:
class Group(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Student(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
section = models.PositiveSmallIntegerField(default=1)
group = models.ForeignKey(
Group, on_delete=models.SET_NULL, null=True, blank=True
)
I am trying to build a form where I can select multiple students to create a group.
class CreateGroupForm(forms.ModelForm):
students = forms.ModelMultipleChoiceField(
required=True,
queryset=Student.objects.all()
)
class Meta:
model = Group
fields = ('students', )
I am using the following view for the form:
class SelectGroup(CreateView):
model = Group
form_class = CreateGroupForm
template_name = 'selection.html'
success_url = reverse_lazy('test')
When I submit the form, it creates a group but the group's student_set is empty. I am guessing this is because I cannot add students to a group without saving the group first. Is there a way for me to modify this view to save the students or should I use something else?
Since students is not a field of the group model, the model form's save does not know what to do with it. You have to override the save method and handle the students field manually:
class CreateGroupForm(forms.ModelForm):
# ...
def save(self, commit=True):
# you have to commit, else the reverse fk has nothing to point to
group = super(CreateGroupForm, self).save(commit=True)
group.student_set.add(*self.cleaned_data['students'])
return group
If you prefer not to remove the non-commit save option on the form, you can also override the form_valid method of the view:
class SelectGroup(CreateView):
# ...
def form_valid(self, form):
self.object.student_set.add(*self.form.cleaned_data['students'])
return super(SelectGroup, self).form_valid(form)

Does the 'through' argument in ManyToManyField in Django includes all fields?

Does the 'through' argument in ManyToManyField in Django includes all fields in the related tables? For example will Group contain all Person and Membership fileds? And also how many levels deep can 'through' relationships can be?
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Yes when you use a through field the associated models' fields are all accessible from the related table. Levels can be deep as you can but it gets complicated better just to create separate tables.

Models in Python Django not working for Many to Many relationships

I am trying to create the proper Django model that could fit the following reqs:
Person Class has 1 to many relations with the Address Class
Person Class has many to many relations with the Group Class
Book Class contains the collections of the Persons and the Groups
This is my code:
class Person(models.Model):
first_name = models.CharField(max_length=15)
last_name = models.CharField(max_length=20)
def __str__(self):
return self.first_name+ ' - ' + self.last_name
class Address(models.Model):
person = models.ForeignKey(Person)
address_line = models.CharField(max_length=200)
def __str__(self):
return self.address_line
class Group(models.Model):
group_name = models.CharField(max_length=12)
persons = models.ManyToManyField(Person)
def __str__(self):
return self.group_name
class Book(models.Model):
record_name = models.CharField(max_length=12)
person = models.ForeignKey(Person )
group = models.ForeignKey(Group )
def __str__(self):
return self.record_name
However it's not correct:
1) A Group can now contain multiple Persons but the Persons do not contain any Group.
I am not sure if I should add to the Person class the following code:
groups = models.ManyToManyField(Group)
2) The Book class now contains only 1 record of Person & Group per Book record.
3) When I added the Foreign Keys to the models, I removed
on_delete tag:
person = models.ForeignKey(Person, on_delete=models.CASCADE())
because it does not compile it, asking for some params.
I know how to make all this for C#, but I am a kinda stucked with this simple task in Python/Django.
1) The ManyToMany field should appear only in one of the models, and by looks of things you probably want it in the Person model.
Its important to understand that the data about the ManyToMany field is saved in a differant table. Django only allows this field to be visable through buth models (so basiclly, choose where it is move convinient).
2)By the look of your structure I will suggest you use a ManyToMany field through a different table. here is an example:
class Activity(models.Model):
name = models.CharField(max_length=140)
description = models.TextField(blank=True, null=True)
class Route(models.Model):
title = models.CharField(max_length=140)
description = models.TextField()
activities_meta = models.ManyToManyField(Activity, through = 'RouteOrdering')
class RouteOrdering(models.Model):
route = models.ForeignKey(Route, on_delete=models.CASCADE)
activity = models.ForeignKey(Activity, on_delete=models.CASCADE, related_name='activita')
day = models.IntegerField()
order = models.IntegerField(default=0)
that way the data is binded to the ManyToMany field

Categories