Django Best Practices: User permissions

Will Vincent - Feb 21 '20 - - Dev Community

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. 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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

UserPassesTestMixin

A next-level permissions is something 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
Enter fullscreen mode Exit fullscreen mode

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 they must 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .