Foad Moha

Foad Moha

Backend Python Developer

© 2021

Best place to put queries in a Django project? Model Managers!

A question often arises when Django projects start to grow: “Where to put all the queries?” When it comes to this question, a good concept to have in mind is fat models and thin views. This means that, as much as possible, you should try to keep business logic out of views and closer to your models.

According to Djagno documentation, you should:

Define custom methods on a model to add custom “row-level” functionality to your objects. Whereas Manager methods are intended to do “table-wide” things, model methods should act on a particular model instance. This is a valuable technique for keeping business logic in one place – the model.

Queries on a model can be considered “table-wide” logic, so a good place for them is in the model Manager methods.

Here is an example to illustrate this point:

Let’s say you have a model called Membership in your project. A Membership is connected to a User and an Organization. Now, in one of your views you want to present all the memberships a user has. You could do it like this:

# models.py
class Membership(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE)

# views.py
def my_memberships(request):
    memberships = Membership.objects.filter(user=request.user)
    return render(request, 'myapp/my_memberships.html', {'memberships': memberships})

This is all good and dandy, but what if you want to make another view that needs to retrieve all the user’s memberships? Then you would need to copy the memberships query to the new view. You would be duplicating code!

A better way is to move this query to the Membership model manager:

#models.py
class MembershipManager(models.Manager):
    def get_memberships_for_user(self, user):
        return self.filter(user=user)


class Membership(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE)

    objects = MembershipManager()

#views.py
def my_memberships(request):
    memberships = Membership.objects.get_memberships_for_user(request.user)
    return render(request, 'myapp/my_memberships.html', {'memberships': memberships})

This way, we can reuse the query logic in other places and avoid duplication. Views that use this method will be more readable and self-documenting. Another added benefit is that you can test this query separately without needing to test the entire view.

You can read more about model managers here.