Django queries on properties of related object sets - python

Say I have the following models.py:
from django.db import models
class Teacher(models.Model):
name = models.CharField()
class Student(models.Model):
name = models.CharField()
absent = models.BooleanField(default=False)
teacher = models.ForeignKey('Teacher',related_name='students')
If I wanted to look up a list of teachers who have at least one absent student, I can do the following:
Teacher.objects.filter(students__absent=True)
My question is, how would I come up with a list of teachers who, have all of their students absent, excluding the teachers who have no students at all? Is this even possible through the ORM without dropping into SQL?

You can do this:
Teacher.objects.exclude(Q(students__absent=False) | Q(students=None))
This will exclude all teachers who have at least one non-absent student, or have no students at all.

Related

Add to m2m field on seperate model

Problem:
I have 3 models in 3 separate apps
School
Student
Classroom
class School(models.Model):
Name = models.CharField(max_length=100)
Students = models.ManyToManyField(Student)
class Classroom(models.Model):
School = models.ForeignKey(School, on_delete=models.CASCADE)
Students = models.ManyToManyField(Student)
What I want:
When a new classroom is saved the students that are in that classroom instance are automatically added to the students many-to-many field of the school model.
Then in the school page template I want to display the students in order of the date they were added to the school model, I think this part needs a separate through model but just can't get my head around it.
When a new classroom is saved the students that are in that classroom instance are automatically added to the students many-to-many field of the school model.
There's no such behavior, I can suggest this:
Remove School.Students, then any time you need students of a specific school, you do something along the lines
Students.objects.filter(classroom__School__Name="Hogwarts")
If you want to keep both (for whatever reason) you can create your custom functions that add items into both. Or you can create custom managers to modify default behavior.
Then in the school page template I want to display the students in order of the date they were added to the school model - I think this part needs a separate through model but just can't get my head around it.
You're right, if you want to add any field to a many-to-many you need to define your custom model. Then you can create a many-to-many out of it with through option.
First, I'd like to stick with some pep8 fixes
class School(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student)
class Classroom(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE)
students = models.ManyToManyField(Student)
Second, unless you've a reason to set name.max_length to 100, set it to 255.
name = models.CharField(max_length=255)
I'd also set a related name to make things easier
class Classroom(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='classrooms')
students = models.ManyToManyField(Student)
Now, to your problem.
Your logic is saying that a school can have many students and a student can enroll in many schools (weird but I'll cope), a classroom can exist inside a school and a school can have multiple classrooms, a classroom HAS MANY STUDENTS. This is a key here, you don't need to save the students in the school itself, you can just use something like
class School(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student)
#property
def students(self):
# iterate the class room, return a QS with all students from them
# there're operators for union, but I'm using the function itself
qs = Student.objects.none()
for classroom in self.classrooms: # related name in action
qs = qs.union(classroom.students)
return qs
I'll add a note, the behavior you want can be done in a signal # DON'T DO THING, JUST KNOW IT
More info

Proper way to access nested foreignkey objects?

I have models with hierarchical relationships:
class School(models.Model):
name = models.CharField()
class Class(models.Model):
school = models.ForeignKey(School)
class Student(models.Model):
class = models.ForeignKey(Class)
And I'd like to retrieve every Student objects related to certain School.
I usually did it like below:
the_school = School.objects.get(name='Springfield Elementary School')
students = Student.objects.filter(class__school=the_school)
But this method needs two models to look up (School and Student) which seems somewhat unnecessary... For me.
So I tried to make a single line query, using Django's built in related manager:
students = School.objects.get(name='...').class_set.student_set.all()
# or
students = School.objects.get(name='...').class_set.all().student_set.all()
...which did not work.
How can I make a query referencing down from given School object?
Or is there any better solution?
You can use class__school__name as filter argument:
students = Student.objects.filter(class__school__name='Springfield Elementary School''Springfield Elementary School')

could not understand foreign key and manytomany field in django

I know this is a very basic question. I am learning django and i see the most important part is ForeignKey field and ManyToManyField. They are used ubiquitously. Without understanding those two, a proper model cannot be designed. If i have to design a model with FK relation, i always have to see the example first and try to come with the solution. I cannot confidently design a model cause i have not understand this well. It would be great if someone make me understand so that the picture comes to my head what is FKField, how FKField and MTMField are generated in table with simple english(Language is one of the barrier for me to understand from the documentation).
Here is the model for foreign key
class Category(models.Model):
name = models.CharField()
class Product(models.Model):
name = models.CharField()
category = models.ForeignKeyField(Category, related_name="product")
In django, you can add one instance of a "variable" as a part of a table: That is a ForeignKey.
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=30)
category = models.ForeignKey(Category)
class Category(models.Model):
name = models.CharField(max_length=30)
Here, you will have a SQL table named "[NAME OF YOUR APP]_product" that will have two columns: "name" and "category_id".
You will have an other table named "[NAME OF YOUR APP]_category" that will contain one column "name".
Django will know that when you load a Product, it will have to get its category_id, and then get that element from the category table.
This is because you use a foreignkey: it is one "variable". And it is "Many to One" because you can have many Products having the same Category.
Then you have "Many to Many". Here you can have more than one "variable"
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=30)
category = models.ManyToManyField(Category)
class Category(models.Model):
name = models.CharField(max_length=30)
Here, the difference is that you will get a table named "[NAME OF YOUR APP]_product" with only one column: "name".
Next to that, you will have a table "[NAME OF YOUR APP]_product_category", that will have the columns "product_id" and "category_id".
And one last table that will be "[NAME OF YOUR APP]_category" that will have one column: "name".
The way it works is that Django will get the Product, and see that it have a ManyToMany field of Category.
It will go to "[NAME OF YOUR APP]_product_category" and get the list of ids for the product_id you need, and get them from "[NAME OF YOUR APP]_category".
This is Many to Many because you can have a lot of Products that have each lots of different Category.
If you still don't understand, I will edit this post to add a SQL example of what the database looks like.
(Sorry, this is not really pleasant to read and a really broad way to explain how Django handle things, but I tried to do short and simple statements.)

Django still stuck (Many to Many)

the idea is that students can send a ping to their teacher if they do not understand something, but first I am trying to get the student, teacher and whatever class they are in relationship
so in my models after a few attempts I have come up with this
Models
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=50)
class Teacher(models.Model):
name = models.CharField(max_length = 70)
class Lecture(models.Model):
name = models.CharField(max_length = 70)
members = models.ManyToManyField(
Student,
through = 'part_of_class',
through_fields = ('lecture', 'student'),
)
lecturers = models.ManyToManyField(
Teacher,
through = 'Teacher_of_class',
through_fields = ('lecture', 'teacher'),
)
class Teacher_of_class(models.Model):
lecture = models.ForeignKey(Lecture)
teacher = models.ForeignKey(Teacher)
class part_of_class(models.Model):
lecture = models.ForeignKey(Lecture)
student = models.ForeignKey(Student)
Question is adding a student field messes up, and Im not even sure this is the right way, it sounds right in my head but I am sure I am missing something, any clues?
I'm not entirely sure that you need explicit intermediary relationships (Teacher_of_class and part_of_class). Instead, you should try utilizing fields within the other models to do what you want. For example, will a lecture generally have more than one teacher, or just one? You could model either of those with a field in Lecture.
Edit: Apologies, I misread your code initially with regards to the fields. My suggestion is to not use an additional relationship class unless you know why you need it.
If you just want to have a lecture that can have multiple students and multiple lecturers, you do not need to have the intermediate models, Django will handle this for you if you don't specify any. I would only use the intermediate tables if you wanted to store something with that relationship.
Also when trying to add your student and teacher objects to the lecture model, be sure to save them.
Your save code should look something like this:
aStudent = Student(name="Taco")
aTeacher = Teacher(name="Burrito")
aStudent.save()
aTeacher.save()
aLecture = Lecture(name="Cooking With Python!")
aLecture.members.add(aStudent)
aLecture.lecturers.add(aTeacher)
aLecture.save()
Hope this helps!

What are the benefits of having two models instead of one?

I've a Django model
class Person(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey(Team)
And a team model
class Team(models.Model):
name = models.CharField(max_length=50)
Then, I would like to add a 'coach' property which is a one to one relationship to person. If I am not wrong, I have two ways of doing it.
The first approach would be adding the field to Team:
class Team(models.Model):
name = models.CharField(max_length=50)
coach = models.OneToOneField(Person, related_name='master')
The second one would be creating a new model:
class TeamCoach(models.Model):
team = models.OneToOneField(Team)
coach = models.OneToOneField(Person)
Is this right ? is there a big difference for practical purposes ? which are the pro and cons of each approach ?
I will say NEITHER, as every Person has a Team and if every Team has a Coach, it's rather redundant circulation and somewhat unnecessary.
Better to add a field in Person called type directly is more clean and direct, something like:
class Person(models.Model):
# use _ if you care about i18n
TYPES = ('member', 'member',
'coach', 'coach',)
name = models.CharField(max_length=50)
team = models.ForeignKey(Team)
type = models.CharField(max_length=20, choices=TYPES)
Although I would seriously consider refactoring Person to be more generic and get Team to have a ManyToMany to Person... in that case, you can re-use Person in other areas, like Cheerleaders.
class Person(models.Model):
# use _ if you care about i18n
TYPES = ('member', 'member',
'coach', 'coach',)
name = models.CharField(max_length=50)
type = models.CharField(max_length=20, choices=TYPES)
class Team(models.Model):
name = models.CharField(max_length=50)
member = models.ManyToManyField(Person, related_name='master')
Make your models more generic and DRY, they should be easily manageable and not tightly coupled to certain fields (unless absolutely necessary), then the models are more future proof and will not fall under migration nightmare that easily.
Hope this helps.
I can't agree so easy with #Anzel, and since the name of the question is
What are the benefits of having two models instead of one?
I'll try to give my two cents. But before i start i want to place some quotes from the docs.
It doesn’t matter which model has the ManyToManyField, but you should
only put it in one of the models – not both.
Generally, ManyToManyField instances should go in the object that’s
going to be edited on a form. In the above example, toppings is in
Pizza (rather than Topping having a pizzas ManyToManyField ) because
it’s more natural to think about a pizza having toppings than a
topping being on multiple pizzas. The way it’s set up above, the Pizza
form would let users select the toppings.
Basically that's the first thing you should have in mind when creating a M2M relation (your TeamCoach model is that, but more on that in a second) which one is the object holding the relation. What would be more suitable for your problem - choosing a coach for a team when you create it, or choosing a team for a person when you create it? IF you ask me i would prefer the second variant and keep the teams inside of the Person class.
Now lets go to the next section of the docs
Extra fields on many-to-many relationships
When you’re only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
For example, consider the case of an application tracking the musical
groups which musicians belong to. There is a many-to-many relationship
between a person and the groups of which they are a member, so you
could use a ManyToManyField to represent this relationship. However,
there is a lot of detail about the membership that you might want to
collect, such as the date at which the person joined the group.
For these situations, Django allows you to specify the model that will
be used to govern the many-to-many relationship. You can then put
extra fields on the intermediate model. The intermediate model is
associated with the ManyToManyField using the through argument to
point to the model that will act as an intermediary.
That's actually the answer of your question, having an intermediate model give you the ability to store additional data about the collection. Consider the situation where a coach moves to another team next season, if you just update the M2M relation, you will loose the track of his past teams where he was coaching. Or you will never be able to answer the question who was the coach of that team at year XXX. So if you need more data, go with intermediate model. This is also were #Anzel going wrong, the type field is an additional data of that intermediate model, it's place must be inside it.
Now here is how i would probably create the relations:
class Person(models.Model):
name = models.CharField(max_length=50)
teams = models.ManyToManyField('Team', through='TeamRole')
class Team(models.Model):
name = models.CharField(max_length=50)
class TeamRole(models.Model):
COACH = 1
PLAYER = 2
CHEERLEADER = 3
ROLES = (
(COACH, 'Coach'),
(PLAYER, 'Player'),
(CHEERLEADER, 'Cheerleader'),
)
team = models.ForeignKey(Team)
person = models.ForeignKey(Person)
role = models.IntegerField(choices=ROLES)
date_joined = models.DateField()
date_left = models.DateField(blank=True, null=True, default=None)
How will I query this? Well, I can use the role to get what type of persons I'm looking for, and I can also use the date_left field to get the current persons participating in that team right now. Here are a few example methods:
class Person(models.Model):
#...
def get_current_team(self):
return self.teams.filter(teamrole__date_left__isnull=True).get()
class Team(models.Model):
#...
def _get_persons_by_role(self, role, only_active):
persons = self.person_set.filter(teamrole__role=role)
if only_active:
return persons.filter(teamrole__date_left__isnull=True)
return persons
def get_coaches(self, only_active=True):
return self._get_persons_by_role(TeamRole.COACH, only_active)
def get_players(self, only_active=True):
return self._get_persons_by_role(TeamRole.PLAYER, only_active)

Categories