Django Best Practices: User Permissions
Updated
Table of Contents
Setting user permissions is a common part of most Django projects and can become quite complex quickly. We'll use the Blog example from my Django for Beginners book as an example. The complete source code can be found here on Github.
The example is a basic blog website with user accounts but no permissions. So, how could we add some?
Views
Generally, permissions are set in the views.py
file. The current view for updating an existing blog post, BlogUpdateView
, looks as follows:
# blog/views.py
class BlogUpdateView(UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
LoginRequired
Now, let's assume we want a user to be logged in before they can access BlogUpdateView
. There are multiple ways to do this, but the simplest, in my opinion, is to use the built-in LoginRequiredMixin.
If you've never used a mixin before, they are called in order from left to right, so we'll want to add the login mixin before UpdateView
. That means if a user is not logged in, they'll see an error message.
# blog/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
UserPassesTestMixin
Next-level permissions are specific to the user. In our case, let's enforce the rule that only the author of a blog post can update it. We can use the built-in UserPassesTestMixin for this.
# blog/views.py
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class BlogUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
def test_func(self):
obj = self.get_object()
return obj.author == self.request.user
Note that we import UserPassesTestMixin
at the top and add it second in our list of mixins for BlogUpdateView
. That means a user must first be logged in and then pass the user test before accessing UpdateView
. Could we put UserPassesTestMixin
first? Yes, but generally, it's better to start with the most general permissions and then become more granular as you move along to the right.
The test_func
method is used by UserPassesTestMixin
for our logic. We need to override it. In this case, we set the variable obj
to the current object returned by the view using get_object. Then we say, if the author
on the current object matches the current user on the webpage (whoever is logged in and trying to make the change), then allow it. If false, an error will be thrown.
There are other ways to set per-user permissions, including overriding the dispatch method, but UserPassesTestMixin
is elegant and specifically designed for this use case.