Innovate anywhere, anytime withruncode.io Your cloud-based dev studio.
Django

Custom Decorators To Check User Roles And Permissions In Django

2022-07-20

A decorator is a function that takes another function and returns a newer,prettier version of that function.

To know more about decorators in python see here https://micropyramid.com/blog/programming-with-python-decorators/

The most common use of a decorator is the login_required. This decorator is used in conjunction
with a view that restricts access to authenticated users only.

from django.contrib.auth.decorators import login_required

@login_required(login_url='/dashboard/')

def index(request):

    return render(request, 'index.html')

This decorator is very useful because we don’t have to actually change anything about my view to restrict access to it.

login required takes an optional argument login_url. If a user is authenticated, it successfully executes the function, 
if not it redirects the users to URL specified in the login_url option. If we don't mention login_url option, we must define login_url in settings.py

But the login_required decorator does not check when the logged in user is active or not. We can use the "user_passes_test" decorator to write our own custom decorators

from django.contrib.auth.decorators import login_required, user_passes_test

user_login_required = user_passes_test(lambda user: user.is_active, login_url='/')

def active_user_required(view_func):
    decorated_view_func = login_required(user_login_required(view_func))
    return decorated_view_func

@active_user_required
def index(request):
    return render(request, 'index.html')

Here user_passes_test decorator returns the value of is_active, which is a boolean that designates if the user is active.
and login_url parameter, which will redirect to this URL if the user is not active. So we can use active_user_required decorator to

If you want to execute the view according to the user role, we can write decorators using model methods

def is_recruiter(self):
    if str(self.user_type) == 'Recruiter':
        return True
    else:
        return False
rec_login_required = user_passes_test(lambda u: True if u.is_recruiter else False, login_url='/')

def recruiter_login_required(view_func):
    decorated_view_func = login_required(rec_login_required(view_func), login_url='/')
    return decorated_view_func

@recruiter_login_required
def index(request):
    return render(request, 'index.html')

Here is_recruiter is a model method which checks the user role. We are using the model method in the user_passes_test decorator. If user role satisfied, then it executes the function, otherwise, it redirects the home page

If you want to execute the view according to the user permission, we can write parameterized decorators using Django permissions
We can list user permissions in the user model metaclass.

class Meta:
    permissions = (
            ('blog_view', 'can view blog posts and categories'),
            ('blog_edit', 'can edit blog category and post'),
            ("support_view", "can view tickets"),
            ("support_edit", "can edit tickets"),
            ("activity_view", "can view recruiters, applicants, data, posts"),
            ("activity_edit", "can edit data"),
        )

def has_perm(self, perm, obj=None):
    try:
        user_perm = self.user_permissions.get(codename=perm)
    except ObjectDoesNotExist:
        user_perm = False
    if user_perm:
        return True
    else:
        return False

def permission_required(*perms):
    return user_passes_test(lambda u: any(u.has_perm(perm) for perm in perms), login_url='/')

@permission_required("activity_view", "activity_edit")
def index(request):
    return render(request, 'index.html')

Here we can pass user permissions values in decorator itself, and in the permission_required, we are checking user is having particular permission or not using for loop. If user don't have permits, it redirects the home page.