Django antipatterns 〉antipattern 〉Checking ownership through the UserPassesTestMixin Fork me on GitHub

In a view we often want to restrict access to edit an object, for example only the authors of the blog are allowed to edit their Posts.

One often checks this with a UserPassesTestMixin, which looks like:

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin

model = Blog
# …

def test_func(self):
return self.get_object().author == self.request.user

# Why is it a problem?

It is not very efficient, because now the self.get_object() call will be done twice. Indeed, once for the test_func, and one in the .get(…) or .post(…) method, which also will fetch the object. This even gets worse because the .author call will make an extra query, since it is a ForeignKey (or OneToOneField), and thus will lazily load an extra object. If get_object is implemented as a simple database fetch, this thus means we make two unnecessary queries.

We can optimize this by dropping a query by using .author_id instead of .author:

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin

model = Blog
# …

def test_func(self):
return self.get_object().author_id == self.request.user.pk

but now we still call .get_object() multiple times. This might even generate problems if we use extra mixins for example that need to performed before using self.get_object().

# What can be done to resolve the problem?

We can filter the QuerySet to only retrieve objects where the user is the author:

from django.contrib.auth.mixins import LoginRequiredMixin

)
Using the UserPassesTestMixin, by default it will return a HTTP 403 Permission denied response. This gives a hint that there is an object there. If we perform filtering, then a user that aims to edit the post of another user will see a HTTP 404 Not Found, which indicates that, for that user, this page, and therefore the Blog object does not exists.