Tidbits | Feb. 11, 2020

Pro-Tip – A Tip About DRF Permissions

by Lacey Williams Henschel |   More posts by Lacey

Recently, I needed to structure permissions in an API view so that someone could access an API if they were a superuser or they had some other status, like being a manager. The Django Rest Framework Permissions docs say that “If any permission check fails an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised,” which made me think I’d have to override the get_permissions() method of the view or write a series of complicated IsUserTypeA, IsUserTypeAOrUserTypeB permissions classes, in order to do what I wanted. That didn’t seem ideal.

Then a colleague pointed out one line in the DRF docs further down on the same page: “Note: it supports & (and), | (or) and ~ (not).” This was exactly what I needed.

Here’s how it works.

All permissions must return True

The expected way that DRF permissions work is as stated above; when you list multiple permission classes in permission_classes, each of them must return True in order for the user to have access to that view. This is most likely what you’re used to when implementing DRF permissions.

from rest_framework.permissions import IsAuthenticated, IsAdminUser, ReadOnly
from rest_framework.viewsets import ModelViewSet

from my_app.permissions import IsStudent, IsFaculty


class MyModelViewSet(ModelViewSet):
    permission_classes = (IsAuthenticated, IsAdminUser)

In this case, the user must be authenticated and be an admin user, or else they cannot access any part of the view.

The | (or) operator

Using the | (or) operator in DRF permissions allows you to set multiple conditions the user might meet and be able to access this view. For instance, for an educational app, a view might be available to students or faculty.

from rest_framework.permissions import IsAuthenticated, IsAdminUser, ReadOnly
from rest_framework.viewsets import ModelViewSet

from my_app.permissions import IsStudent, IsFaculty


class MyModelViewSet(ModelViewSet):
    permission_classes = (IsAdminUser | IsStudent | IsFaculty)

This expression (IsAdminUser | IsStudent | IsFaculty) allows a user who is an admin, a student, or a faculty member to access the view. Instead of needing to pass all three of these conditions, a user only needs to pass one.

The way I accomplished this before was a custom permissions class named something like IsAdminStudentOrFaculty, but with multiple roles and complex permissions, this meant creating custom permission classes for nearly every combination of user role. (In this example, I refer to custom permissions IsStudent and IsFaculty; there are some examples in the docs of how to implement custom permissions like these.)

The & (and) operator

Especially combined with the | (or) operator, the & (and) operator is really powerful. It enables you to set clear permissions boundaries without writing increasingly complex custom permissions classes.

from rest_framework.permissions import IsAuthenticated, IsAdminUser, ReadOnly
from rest_framework.viewsets import ModelViewSet

from my_app.permissions import IsStudent, IsFaculty


class MyModelViewSet(ModelViewSet):
    permission_classes = (IsAuthenticated & (IsAdminUser | IsFaculty | ReadOnly))

This example, (IsAuthenticated & (IsAdminUser | IsFaculty | ReadOnly)), requires that all users accessing this view be authenticated. In addition, it sets other permissions: a user must be authenticated and be an admin or a faculty member, or they only have “read” access to this view.

The ~ (not) operator

The ~ (not) operator allows you to simplify your permissions to permit everyone except users who meet specific criteria. Personally, I prefer to set permissions with positive language, like “allow admins and faculty” rather than “don’t allow students,” but there are probably cases where the ~ operator is a great option.

from rest_framework.permissions import IsAuthenticated, IsAdminUser, ReadOnly
from rest_framework.viewsets import ModelViewSet

from my_app.permissions import IsStudent, IsFaculty


class MyModelViewSet(ModelViewSet):
    permission_classes = (~IsStudent & IsAuthenticated)

(~IsStudent & IsAuthenticated) says that anyone who is authenticated and who is not a student can access this view.

I hope you, like me, now can free yourself from the cumbersome task of writing too many custom permission classes in your apps with several different types of user roles and instead make use of simpler permission classes and the &, | and ~ operators!

Further Reading

Thanks to Jeff Triplett for reviewing a draft of this article and finding this tip in the docs in the first place!

I needed to structure permissions in an API view that had multiple user roles, and I learned about using the & (and), | (or) and ~ (not) operators with Django REST Framework permissions.{% else %}

2020-02-11T17:44:26.038391 2020-02-11T17:44:26.005304 2020