Django Best Practices: User permissions
Tags: django, python, tutorial, beginners
Published: 2020-02-21 13:38:17

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](http://djangoforbeginners.com) book as an example. Complete source code can be found [here on Github](https://github.com/wsvincent/djangoforbeginners/tree/master/ch7-blog-app-with-users).

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:

```python
# 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](https://docs.djangoproject.com/en/2.1/topics/auth/default/#the-loginrequired-mixin).

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.

```python
# 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

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](https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin) for this.

```python
# 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 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](https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-single-object/#django.views.generic.detail.SingleObjectMixin.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](https://docs.djangoproject.com/en/dev/ref/class-based-views/base/#django.views.generic.base.View.dispatch) but `UserPassesTestMixin` is elegant and specifically designed for this use case.