Django comes with a powerful permission system out-of-the-box.
In this article, we'll look at how to assign permissions to users and groups in order to authorize them to perform specific actions.
Contents
Objectives
By the end of this article, you will be able to:
- Explain how Django's permissions and groups work
- Harness the power of Django's built-in permission system
Authentication vs Authorization
This article is concerned with authorization.
- Authentication is the process of confirming if a user has access to a system. Typically, a username/email and password is used to authenticate a user.
- Authorization: pertains to what the "authenticated" user can do in a system.
Put another way, authentication answers the question 'who are you?' while authorization answers 'what can you do?'.
User-level Permissions
When django.contrib.auth
is added to the INSTALLED_APPS
setting in the settings.py file, Django automatically creates add
, change
, delete
and view
permissions for each Django model that's created.
Permissions in Django follow the following naming sequence:
{app}.{action}_{model_name}
Notes:
app
is the name of the Django app the associated model resides inaction
: isadd
,change
,delete
, orview
model_name
: is name of the model in lowercase
Let's assume we have the following model in an app called "blog":
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=400)
body = models.TextField()
By default, Django will create the following permissions:
blog.add_post
blog.change_post
blog.delete_post
blog.view_post
You can then check if a user (via a Django user object) has permissions with the has_perm()
method:
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
from blog.models import Post
content_type = ContentType.objects.get_for_model(Post)
post_permission = Permission.objects.filter(content_type=content_type)
print([perm.codename for perm in post_permission])
# => ['add_post', 'change_post', 'delete_post', 'view_post']
user = User.objects.create_user(username="test", password="test", email="[email protected]")
# Check if the user has permissions already
print(user.has_perm("blog.view_post"))
# => False
# To add permissions
for perm in post_permission:
user.user_permissions.add(perm)
print(user.has_perm("blog.view_post"))
# => False
# Why? This is because Django's permissions do not take
# effect until you allocate a new instance of the user.
user = get_user_model().objects.get(email="[email protected]")
print(user.has_perm("blog.view_post"))
# => True
Superusers will always have permission set to True
even if the permission does not exist:
from django.contrib.auth.models import User
superuser = User.objects.create_superuser(
username="super", password="test", email="[email protected]"
)
# Output will be true
print(superuser.has_perm("blog.view_post"))
# Output will be true even if the permission does not exists
print(superuser.has_perm("foo.add_bar"))
A superuser is a user type in Django that has every permission in the system. Whether custom permissions or Django-created permissions, superusers have access to every permission.
A staff user is just like any other user in your system BUT with the added advantage of being able to access the Django Admin interface. The Django Admin interface is only accessible to superusers and staff users.
Group-level Permissions
Having to assign permissions each time to users is tedious and not scalable. There might be instances where you would want to add new permissions to a set of users. Here's where Django groups come into play.
What's a group?
- English Definition: A group is a set of objects which are classed together.
- Django Definition: Group models are a generic way of categorizing users so you can apply permissions, or some other label, to those users. A user can belong to any number of groups.
With Django, you can create groups to class users and assign permissions to each group so when creating users, you can just assign the user to a group and, in turn, the user has all the permissions from that group.
To create a group, you need the Group
model from django.contrib.auth.models
.
Let's create groups for the following roles:
Author
: Can view and add postsEditor
: Can view, add, and edit postsPublisher
: Can view, add, edit, and delete posts
Code:
from django.contrib.auth.models import Group, User, Permission
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
from blog.models import Post
author_group, created = Group.objects.get_or_create(name="Author")
editor_group, created = Group.objects.get_or_create(name="Editor")
publisher_group, created = Group.objects.get_or_create(name="Publisher")
content_type = ContentType.objects.get_for_model(Post)
post_permission = Permission.objects.filter(content_type=content_type)
print([perm.codename for perm in post_permission])
# => ['add_post', 'change_post', 'delete_post', 'view_post']
for perm in post_permission:
if perm.codename == "delete_post":
publisher_group.permissions.add(perm)
elif perm.codename == "change_post":
editor_group.permissions.add(perm)
publisher_group.permissions.add(perm)
else:
author_group.permissions.add(perm)
editor_group.permissions.add(perm)
publisher_group.permissions.add(perm)
user = User.objects.get(username="test")
user.groups.add(author_group) # Add the user to the Author group
user = get_object_or_404(User, pk=user.id)
print(user.has_perm("blog.delete_post")) # => False
print(user.has_perm("blog.change_post")) # => False
print(user.has_perm("blog.view_post")) # => True
print(user.has_perm("blog.add_post")) # => True
Enforcing Permissions
Aside for the Django Admin, permissions are typically enforced at the view layer since the user is obtained from the request object.
To enforce permissions in class-based views, you can use the PermissionRequiredMixin from django.contrib.auth.mixins
like so:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView
from blog.models import Post
class PostListView(PermissionRequiredMixin, ListView):
permission_required = "blog.view_post"
template_name = "post.html"
model = Post
permission_required
can either be a single permission or an iterable of permissions. If using an iterable, a user must have ALL the permissions before they can access the view:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView
from blog.models import Post
class PostListView(PermissionRequiredMixin, ListView):
permission_required = ("blog.view_post", "blog.add_post")
template_name = "post.html"
model = Post
For function-based views, use the permission_required
decorator:
from django.contrib.auth.decorators import permission_required
@permission_required("blog.view_post")
def post_list_view(request):
return HttpResponse()
You can also check for permissions in your Django templates. With Django's auth context processors, a perms variable is available by default when you render your template. The perms
variable actually contains all permissions in your Django application.
For example:
{% if perms.blog.view_post %}
{# Your content here #}
{% endif %}
Model-level Permissions
You can also add custom permissions to a Django model via the model Meta options.
Let's add an is_published
flag to the Post
model:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=400)
body = models.TextField()
is_published = models.Boolean(default=False)
Next, we'll set a custom permission called set_published_status
:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=400)
body = models.TextField()
is_published = models.Boolean(default=False)
class Meta:
permissions = [
(
"set_published_status",
"Can set the status of the post to either publish or not"
)
]
In order to enforce this permission, we can use the UserPassesTestMixin
Django provided mixin in our view, giving us the flexibility to explicitly check whether a user has the required permission or not.
Here's what a class-based view might look like that checks whether a user has permission to set the published status of a post:
from django.contrib.auth.mixins import UserPassesTestMixin
from django.shortcuts import render
from django.views.generic import View
from blog.models import Post
class PostListView(UserPassesTestMixin, View):
template_name = "post_details.html"
def test_func(self):
return self.request.user.has_perm("blog.set_published_status")
def post(self, request, *args, **kwargs):
post_id = request.POST.get('post_id')
published_status = request.POST.get('published_status')
if post_id:
post = Post.objects.get(pk=post_id)
post.is_published = bool(published_status)
post.save()
return render(request, self.template_name)
So, with UserPassesTestMixin
, you need to override the test_func
method of the class and add your own test. Do note that the return value of this method must always be a boolean.
Object-level Permissions
If you're using Django REST Framework, it already has object-level permissions built into the base permission class. BasePermission
has has_permission
, which is basically for list views, and has_object_permission
, which checks if the user has permission to access a single model instance.
For more on permissions in Django REST Framework, review Permissions in Django REST Framework.
If you're not using Django REST Framework, to implement object-level permissions, you can use a third-party, like:
For more permissions-related packages, check out Django Packages.
Conclusion
In this article, you've learnt how to add permissions to a Django model and check for permissions. If you have a set number of user types, you can create each user type as a group and give the necessary permissions to the group. Then, for every user that is added into the system and into the required group, the permissions are automatically granted to each user.