In a view we often want to restrict access to edit an
object, for example only the author
s of the
blog are allowed to edit their Post
s.
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
.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
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
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
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.
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.