Managing User Permissions in Django
Tags: python, django
Published: 2022-09-28 22:14:12

*This article was originally written by [Muhammed Ali](https://www.honeybadger.io/blog/authors/muhammedali/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/django-permissions/).*

All applications need some sort of check or permission that controls what each user can do and determine whether the user is authenticated.
In this article, you will learn how to set up multiple user roles, as well as how user permissions can be set depending on the user role. Furthermore, I will illustrate how to use Django’s built-in Users and Groups to apply permissions to multiple users.
While a lot of articles only cover user permissions and the default Django user permissions, I will take it a step further by building an application with different user roles and then set permissions based on the role of the user.

### Prerequisites

To follow this article, the following are required:

- Prior working knowledge of Django and Python
- Python 3.7 or higher versions installed

## Working with Django’s Built-in Permissions

By default, Django provides a built-in authorization (permissions). This feature enables you to define what actions each user is permitted to perform. The framework includes built-in models for `Users` and `Groups`, which are used to provide permissions to a group of users, permissions/flags that designate whether a user may perform a task, view tools for restricting content, etc. When `django.contrib.auth` is listed in your `INSTALLED_APPS`, it will provide the four default permissions – add, change, delete, and the view for each model.
Now, let's assume you have a project with an app `product` and a model `Order`. To test which users have basic permissions, you can use the following code.

```shell
python manage.py shell
user = User.objects.first()
```

- add: **`user.has_perm('product.add_order')`**
- change: **`user.has_perm('product.change_order')`**
- delete: **`user.has_perm('product.delete_order')`**
- view: **`user.has_perm('product.view_order')`**
Adding *permissions* to restrict a function to only users that have that particular permission can be done by using a Django built-in [decorator](https://www.datacamp.com/community/tutorials/decorators-python), `permission_required`.

```python
from django.contrib.auth.decorators import permission_required

@permission_required('product.change_name')
def admin_view(request):
    """Raise permission denied exception or redirect user"""
```

If you are using a class-based view, you just need to use a mixin, `PermissionRequiredMixin`:

```python
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView

class ProductListView(PermissionRequiredMixin, ListView):
    permission_required = 'product.change_name'
```

## How to add Custom Permissions to Models

If permissions are not stated in the views, you can specify them in your models. Attaching permissions is done on the model's `class Meta` using the permissions field. You will be able to specify as many permissions as you need, but it must be in a tuple. For example, you might define a permission to allow a user to change the name of a product like this:

```python
from django.db import models

class Product(models.Model):
    user = models.ForeignKey(User)    
    class Meta:
        permissions = (("change_name", "can change name of product"),)
```

To implement the changes, run `python manage.py makemigrations` and `migrate`.

## How to Add Custom Permissions to Templates

The user's permissions can be retrieved by calling `{{ perms }}` in the template. To check if a particular user has permission to view the template, all you have to do is get `perms` from the particular model holding the permissions. It should look like this:

 `{{ perms.<name_of_app>.<permission>}}` e.g., `{{ perms.app.change_name }}` .
With an `if` statement, you can add a permission to specific portions of your template.

```html
{% if perms.app.change_name %}
<p>can change name of product</p>
    
{% endif %}
```

By doing this, only users with `change_name` permission can use that particular section of the template.

### How to add Custom Permissions to Views

Just like when you were working with the built-in Django permissions, you can also add custom permissions to views by using the `permission_required` decorator or, in a class-based view, using the `PermissionRequiredMixin`. For function-based views:

```python
from django.contrib.auth.decorators import permission_required

@permission_required('app.change_name')
def the_view(request):
    ...
```

For class-based views:

```python
from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'app.change_name'
    # Or multiple permissions
    permission_required = ('app.change_name', 'app.edit_name')
    # Note that 'catalog.can_edit' is just an example, you can replace it with
    # whatever permissions you have created
   
```

### ***Note***

*If a user is not permitted to access a `view` and they try to access it, `PermissionRequiredMixin` and  `@permission_required` act differently. `@permission_required` redirects to the login screen, `PermissionRequiredMixin` returns HTTP 403 (Status Forbidden) if a user is logged in but does not have the correct permission.*

The  `PermissionRequiredMixin` default behavior is optimal when building apps. To duplicate this behavior in function-based views, you will have to add the `@login_required` decorator and apply `@permission_required`  with `raise_exception=True`. This is how it will look:

```python
from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('app.change_name', raise_exception=True)
def the_view(request):
    ...
```

## Add Permissions to Multiple User Types

Usually, when you are implementing multiple user types, you'll need to make sure each user type has some restrictions. For example, if you are building an assignment application where the teacher can set questions and students can only answer questions. In this case, you must set the permission system in such a way that students will not have the option to set questions. To illustrate this, let's assume you are trying to build an application where an authenticated teacher can create assignments and authenticated students can answer questions.
Assuming our `models.py` file looks like this:

```python
from django.contrib.auth.models import AbstractUser
from django.db import models

**class** **User**(AbstractUser):
    is_student **=** models**.**BooleanField(default**=**False)
    is_teacher **=** models**.**BooleanField(default**=**False)

**class** **Student**(models**.**Model):
    user **=** models**.**OneToOneField(User, on_delete**=**models**.**CASCADE, primary_key**=**True)
    subjects **=** models**.**ManyToManyField(Subject, related_name**=**'subject')
```

To handle permissions for these different users, you need to create a custom decorator of your own.

```python
# decorator.py

from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test

def for_students(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url='login'):
    '''
    A decorator to check logged in user could be a student and redirects to the login page if the user isn't authenticated.
    '''
    actual_decorator = user_passes_test(
        lambda u: u.is_active and u.is_student,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

def for_teachers(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url='login'):
    '''
    A decorator to check whether the logged-in user is a teacher and 
    redirect to the login page if the user is not authenticated.
    '''
    actual_decorator = user_passes_test(
        lambda u: u.is_active and u.is_teacher,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator-
```

Now, to use them in the views, all you need to do is call it as you did previously. It should look like this:

```python
# views.py 
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
from .decorators import for_students, for_teachers
from .models import Assignment #this is an assumption

@login_required
@student_required  # <-- here is the decorator
def answer_assignment(request, pk):
    quiz = get_object_or_404(Assignment, pk=pk)
    student = request.user.student

    # body of the view...

```

If you are using class-based views, your view will look like this:

```python
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from .decorators import for_students

@method_decorator([login_required, student_required], name='dispatch')
class AnswerAssignment(CreateView):

    # body of the view...
```

## Add Permissions to a Group

Thankfully, Django makes it very easy to create groups with the admin panel provided by default.
This section will contain an illustration of how you can potentially add permissions to groups in your application. Let's assume you are building a website that provides real estate suggestions, and if a user is subscribed to "Tier 1", they can view 3 suggestions, and "Tier 2" can view 6 suggestions.
With the above in mind, your `models.py` file will look like this:

```python
from django.contrib.auth.models import AbstractUser
from django.db import models
 

class User(AbstractUser):
    first_name = models.CharField(blank = True, max_length = 20)                      
    last_name = models.CharField(blank = True, max_length = 20)                      
    # You can add more User fields as required
 
    # define the custom permissions
    # related to User.
    class Meta:
        permissions = (("tier_1", "Can view 3 suggestions"), ("tier_2", "Can view 6 suggestions"),
        # You can add other custom permissions as required
        )
 
```

If you are using `AbstractUser` in Django, you must add `AUTH_USER_MODEL = 'YourAppName.YourClassName'` . This way, you are telling Django to use our custom user model instead of the default one.
The code below should go in your `admin.py` file so that you can see your user model.

```python
from django.contrib import admin
from .models import User

admin.site.register(User)
```

To implement the changes, run `$ python manage.py makemigrations` and `$ python manage.py migrate`.
Next, you will create a superuser that you will use to create the groups.
To do this, run `$ python3 manage.py createsuperuser` and fill in the necessary details.
Now, run your server with `$ python manage.py runserver` and head to the [admin](http://127.0.0.1:8000/admin/) and login with the user details you just created.
This is what you should see after logging in:
![Image of admin page](https://www.honeybadger.io/images/blog/posts/django-permissions/admin.png)
What you will do next is click on the "**+ Add**" button on the "**Groups**" row.
You will be prompted with a form that looks like this:

![Image of group form](https://www.honeybadger.io/images/blog/posts/django-permissions/group.png)

You will see that you can select various permissions and attach them to a particular group.

## Conclusion

This article's aim was to take you from not knowing how to manage permissions in Django applications to showing you the different ways in which you can manipulate and manage permissions when building Django applications. Hopefully, I was able to do that. With this article, you should have learned how to add permissions to views, models, and templates. I also showed how you can potentially create your own custom permission. Finally, I was able to show how to add permissions to groups using the Django admin panel. Now that you have learned all these, hopefully you will be able to start implementing the knowledge in your future Django projects.