For my first technical post, let's talk about queries in Django.
In a view, typically you'd want to make some queries to add to the template context. For example:
def books_list(request): books = Book.objects.all() render(request, 'books/book_list.html', {'books': books})Sometimes, these queries can get a bit more complicated.
For example, say, for whatever reason, you want to display a list of people whose age is an even number.
(The example doesn't really matter to the point of this post. Basically, what I was getting at is, sometimes we need to do complicated queries.)
# in models.py class Person(models.Model): age = models.IntegerField() name = models.CharField(max_length=256) # in views.py def people_with_even_numbered_ages(request): even_aged_people = Person.objects.annotate(modulo_two=F('age') % 2).filter(modulo_two=0) render(request, 'person/even_aged_people.html', {'even_aged_people': even_aged_people})Unfortunately, that isn't very readable. The intent of that query isn't immediately clear.
We could add a comment, but as a general rule, when you need a comment, it's an indicator that whatever you're doing is difficult to understand/explain.
When you have a complicated query, I would recommend using QuerySet methods.
QuerySet methodsDid you know you can add methods to Managers and QuerySets? See Django's documentation on Managers and QuerySets .
A nice thing you can do is to override a QuerySet and have it act as a Manager, so that you don't write the same logic in two classes:
# in models.py class PersonQuerySet(models.QuerySet): def with_even_numbered_ages(self): """Filters people with even-numbered ages""" return self.annotate(modulo_two=F('age') % 2).filter(modulo_two=0) class Person(models.Model): objects = PersonQuerySet.as_manager() age = models.IntegerField() name = models.CharField(max_length=256) # in views.py def people_with_even_numbered_ages(request): even_aged_people = Person.objects.with_even_numbered_ages() render(request, 'person/even_aged_people.html', {'even_aged_people': even_aged_people})The method name with_even_numbered_ages() tells you exactly what the query is doing, which is what we want. You could change the method name if you don't like the constant use of with_ in the method names.
Another benefit of doing it this way is that you can chain QuerySet methods:
class PersonQuerySet(models.QuerySet): def with_even_numbered_ages(self): """Filters people with even-numbered ages""" return self.annotate(modulo_two=F('age') % 2).filter(modulo_two=0) def with_names_containing(self, name=None): """Filters people with names that contain a given string""" return self.filter(name__icontains=name) def some_view(request): people = PersonQuerySet.objects.with_names_containing("Barry").with_even_numbered_ages() render(request, 'person/some_people.html', {'people': people})I think that's a nice way to move filtering logic out of views.