Django Best Practices: User permissions
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?
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']
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']
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
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.
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.