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
One often checks this with a
UserPassesTestMixin, which looks like:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin class BlogEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): 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
.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
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
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin class BlogEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): 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
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 class BlogEditView(LoginRequiredMixin, UpdateView): model = Blog # … def get_queryset(self, *args, **kwargs): return super().get_queryset(*args, **kwargs).filter( author=self.request.user )
Here we leave filtering to the database side. This might make the query slightly more expensive, but often a good way to measure performance is the number of queries, and not the queries itself.
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.