Currently, Django provides decorators like @login_required and @permission_required to restrict view access. However, there is no built-in support for role-based access control, which is a common requirement for many applications.
I want to propose adding the new role_required decorator that allows developers to restrict access to views based on user roles. The decorator will:
Check if the user has one or more specified roles.
Support both “any role” (test_all=False) and “all roles” (test_all=True) modes.
Redirect unauthorized users to the appropriate page or a custom URL.
This feature will make it easier for developers to implement role-based access control without writing custom decorators.
Example Use Case
A marketplace application might have roles like is_seller, is_buyer, and is_employee. The role_required decorator can be used to restrict access to specific views:
from django.contrib.auth.decorators import role_required
@role_required(['is_seller'])
def seller_dashboard(request):
# Only users with the 'is_seller' role can access this view.
pass
@role_required(['is_admin', 'is_moderator'], test_all=True)
def admin_dashboard(request):
# Only users with both 'is_admin' and 'is_moderator' roles can access this view.
pass
One of the Django main developers told me that they want to keep Django a core framework, not providing every utility that might be useful, that’s good, however, I see some built-in features and some CBVs their main role is to write fewer code and simplify the development process so that I see the role_required decorator, he advised me to use the permission_required instead, however, the role_required is mostly like the login_required, not just restricting users from some model permissions (this is my POV).
Since Django deals with different User attrs such as is_staff (and some projects need more than the is_staff role) then I could say that we’re already working with roles. (please correct me if I’m wrong)
Yes, this decorator looks for that attribute on the passed user object to evaluate if it passes or not, it’s based on the user_passes_test function, here is my implementation:
def role_required(roles: list[str], test_all=False, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user has a specific role,
redirecting to the log-in page if necessary.
role: must be a list of valid string user attributes as they were declared in their models
test_all: bool value that determines if all roles are required or just one.
"""
def _test_role(user):
if test_all:
return user.is_authenticated and all(getattr(user, role, False) for role in roles)
return user.is_authenticated and any(getattr(user, role, False) for role in roles)
actual_decorator = user_passes_test(
lambda u: _test_role(u),
login_url,
redirect_field_name
)
return actual_decorator
Yes I know there are some 3rd party libraries that provide RBAC, however, I’m just looking for the simplest way to achieve the desired results by making a built-in function with only 1 line of code to work with rather than dealing with 3rd party library.
Yes, I agree with you, and I believe there is more than one way to achieve the same result, however (based on my knowledge) I need to write the same block of code for every target view to ensure that the user belongs to that group with those permissions (unless there is another simple way that I don’t know yet), but this role_required deco will save me some time and simplify the process with only 1 line of code.
I would like to hear from you again sir ^^
A role is not a permission - a role is a set of permissions granting ability to perform functions associated with that role.
You don’t check for “role membership” at the view level - you check for specific permissions needed for that view.
The permission is assigned to one or more roles, allowing for access to a view to be granted to an indeterminate number of roles without requiring any changes to the code.
Afaik, the Django “Group” model is design for Models permission not View permission. And if I need to combine Role (Manager, assistant,…), Dept (IT, HR, Sales,…) and BusinessFunctional (securiry, maintainer, Qc, Qa, dev,…) into a new CustomRole and assign permission to that role. Can build in Django “Group” handle it?
This is not correct. Functionally, the standard Django “Group” exists as a “container” for a set of Permissions.
Groups are designed to allow permissions to be shared among multiple Users. (Each User in a Group have all the permissions assigned to that group.)
What those permissions are used for is entirely up to the developer.
Yes, with the appropriate definition and understanding of the term “Role”.
In a “Role-Based Access Control” (RBAC) system, a Django Group is a Role. You assign the permissions to those roles (Groups), then test that the user accessing a view has the required permission.
Access to resources should be evaluated by permissions, not by roles. Roles are for the users to group certain permissions. It allows for flexibility, and should ideally be customizable by the admin. Hence, you should check if a user has a permission, not a role.
If roles were defined by the developer and fixed, then why use roles and not just a permission instead? Introducing roles would be just superfluous then.
Or, if you were to have roles and you change them, you would have to revisit all decorators and reevaluate if they’re still correct. If you were to check permissions, you would not have to do that.
The above also aligns with how RBAC is implemented in many other tools like Microsoft Entra ID.
This, I’m against your suggestion and you should really check against permissions and implement roles as a tool for the users to simplify access control.