Reputation: 943
I am developing an app where students can evaluate their teachers. I have several models, but the important ones for this question are these:
class Professor(models.Model):
name = models.CharField(max_length=50,null=True)
categories = models.ManyToManyField(Category, related_name='professors')
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=50,null=True)
professors = models.ManyToManyField(Professor, related_name='students',through='Studentprofesor' )
def __str__(self):
return self.name
class Studentprofesor(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
professor = models.ForeignKey(Professor, on_delete=models.CASCADE)
tested = models.BooleanField(default=False)
As far as I knew the main difference between get
and filter
was that I couldn't use get
when there were several objects with the features I was looking for. But apart from that, they worked in a similar way. get
for a single object, filter
for several objects.
However, in this case I get different results when I run get
and filter
.
if I use get
:
Student.objects.get(name="Mike").professors.all()
I obtain:
<QuerySet [<Professor: Tom>, <Professor: Jenn>]>
But if I use filter
:
Student.objects.filter(name="Mike").professors.all()
I obtain:
AttributeError: 'QuerySet' object has no attribute 'professors'
it's as if filter is not able to follow the manytomany relationship between the objects.
Why is this happening?
Upvotes: 2
Views: 1012
Reputation: 476534
There are huge differences between .get(..)
and .filter(..)
. In short: .get(..)
obtains a single model instance that satisfies the given conditions whereas .filter(..)
filters the queryset and produces a queryset that conceptually contains model instances (!) that satisfy the given conditions.
.get(..)
.get
means that you aim to retrieve exactly one instance. So that means that if you write:
Model.objects.get(..)
the result is a Model
instance (given there is such instance). As a result we can thus obtain attributes (like .professors
, etc.) from that single entity. We have guarantees if the call succeeds that: (1) there are no multiple objects for which the filter criteria hold; and (2) there is at least one element for which the filter criteria hold. So the output is always a model instance, not a None
or a QuerySet
.
The .get(..)
function is evaluated eagerly: we immediately perform a query to the database. In case the database returns no entries, or two or more, the Model.DoesNotExist
and MultipleObjectsReturned
exceptions are respectively risen.
Note: since
.get(..)
acts eagerly, adding filters, etc. at the right of.get(..)
has no use (well unless you define afilter
function on that instance, but that is not a good idea either). You can however use functions like.values()
,values_list
,prefetch_related
, etc. on the left side to change the type of output (and prefetch certain parts). For example:Student.objects.values().get(name='Mike')
will result in a dictionary containing the values of that instance.
.filter(..)
Filter on the other hand filters a queryset. That means that it is possible that after we filter, the queryset no longer contains any instances (if the filter is too restrictive) or two ore more (if the filter is too weak to pin to a single entry).
Django does not evaluate such .filter(..)
eagerly. This means that by default Django will not make a query to the database to retrieve entries. Only if you call for example len(..)
over the resulting queryset, or you iterate over it, Django will first perform a database query and then handle the corresponding result.
Since the result of a .filter(..)
is another QuerySet
, we can chain operations together. For example, we can call an extra .filter(..)
or .exclude(..)
, values_list(..)
or any other function supported by the QuerySet
.
Since the result is not a model instance, we can not call the attributes of a model instance. What should be the outcome of a Student.objects.filter(..).name
in case no student matches the criteria? Or what if there are multiple Student
s that match the given constraint?
We can however obtain the list of Professor
s that teach one or more Student
s with the name 'Mike'
with:
# professors with a student called Mike
Professors.objects.filter(students__name="Mike")
A filter will never raise an Model.DoesNotExist
or a MultipleObjectsReturned
exception, since it is perfectly allowed to work with empty QuerySet
s, or QuerySet
s with multiple items.
Upvotes: 2
Reputation: 47354
Bacause filter()
returns queryset (multiple students). But professors
is attribute of single student instance. You can use first()
with filter()
to get single object:
Student.objects.filter(name="Mike").first().professors.all()
Upvotes: 2