Django REST Framework Role Filters
djangorestframework-role-filters is a Python library that provides simple and declarative role-based filtering for Django REST Framework views and querysets. It aims to eliminate the need for verbose 'if-else' statements in view logic by centralizing role definitions. The current version is 1.1.0, with releases occurring periodically, typically to update compatibility with newer Django/DRF versions.
Warnings
- breaking Version 1.1.0 dropped support for older Python (3.6/3.7), Django (2.2.x/3.0.x), and Django REST Framework (3.10.x/3.11.x) versions. Ensure your environment meets the new requirements.
- breaking In version 1.0.0, the `RoleFilterMixin`'s `role_filter_group` attribute was replaced by `role_filter_classes` (a list of `RoleFilter` instances).
- gotcha The `get_role_id` method on your `RoleFilterModelViewSet` is critical. It must return a string that matches the `role_id` defined in your `RoleFilter` subclasses. An incorrect or missing implementation will prevent the role-based filtering from activating correctly, potentially leading to unintended access or data exposure.
- gotcha Each `RoleFilter` subclass must explicitly define `get_allowed_actions`, `get_queryset`, and `get_serializer_class` (and optionally `get_serializer`) for its `role_id`. Failing to define these for a particular role might lead to default DRF behaviors (e.g., full queryset access) that bypass your intended role-based restrictions.
Install
-
pip install djangorestframework-role-filters
Imports
- RoleFilter
from rest_framework_role_filters.role_filters import RoleFilter
- RoleFilterModelViewSet
from rest_framework_role_filters.viewsets import RoleFilterModelViewSet
Quickstart
import os
from django.db import models
from django.contrib.auth.models import AbstractUser
from rest_framework import serializers
from rest_framework_role_filters.role_filters import RoleFilter
from rest_framework_role_filters.viewsets import RoleFilterModelViewSet
# --- Mock Django Setup (for runnable example) ---
# This is usually handled by a real Django project setup
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') # Replace with your actual settings
import django
django.setup()
# --- Mock Models ---
class User(AbstractUser):
ROLE_CHOICES = (
('admin', 'Admin'),
('user', 'User'),
)
role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='user')
class Post(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
# --- Mock Serializers ---
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'body', 'user', 'created_at', 'updated_at']
read_only_fields = ['user']
class PostSerializerForUser(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'body', 'created_at', 'updated_at']
read_only_fields = ['user']
# --- Role Filters (role_filters.py) ---
class AdminRoleFilter(RoleFilter):
role_id = 'admin'
def get_allowed_actions(self, request, view, obj=None):
# Admins can do anything
return ['create', 'list', 'retrieve', 'update', 'partial_update', 'destroy']
def get_queryset(self, request, view, queryset):
# Admins see all posts
return queryset.all()
def get_serializer_class(self, request, view):
return PostSerializer
class UserRoleFilter(RoleFilter):
role_id = 'user'
def get_allowed_actions(self, request, view, obj=None):
# Users can create, list, retrieve, update their own posts
return ['create', 'list', 'retrieve', 'update', 'partial_update']
def get_queryset(self, request, view, queryset):
# Users only see their own posts
return queryset.filter(user=request.user)
def get_serializer_class(self, request, view):
return PostSerializerForUser
def get_serializer(self, request, view, serializer_class, *args, **kwargs):
# Example of dynamically modifying serializer fields for a user role
fields = ('body', 'created_at', 'id', 'title', 'updated_at')
return serializer_class(*args, fields=fields, **kwargs)
# --- ViewSet (views.py) ---
class PostViewSet(RoleFilterModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
role_filter_classes = [AdminRoleFilter, UserRoleFilter]
def get_role_id(self, request):
# This method is crucial: it determines the role for the current request
# In a real app, request.user would be an authenticated user object.
# For this example, we assume request.user has a 'role' attribute.
# You might use request.user.is_staff or custom logic here.
if request.user.is_authenticated:
return request.user.role
return 'anonymous' # Fallback or specific anonymous role
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# Example usage (not directly runnable without Django/DRF server):
# from rest_framework.test import APIRequestFactory
# from django.contrib.auth.models import AnonymousUser
#
# factory = APIRequestFactory()
#
# # Simulate an admin user
# admin_user = User(username='admin', role='admin', is_authenticated=True)
# request = factory.get('/posts/')
# request.user = admin_user
# view = PostViewSet.as_view({'get': 'list'})
# response = view(request)
# print(f"Admin response status: {response.status_code}")
#
# # Simulate a regular user
# regular_user = User(username='user1', role='user', is_authenticated=True)
# request = factory.get('/posts/')
# request.user = regular_user
# view = PostViewSet.as_view({'get': 'list'})
# response = view(request)
# print(f"User response status: {response.status_code}")
# In a real Django setup, you would add PostViewSet to your urls.py:
# from django.urls import path, include
# from rest_framework.routers import DefaultRouter
#
# router = DefaultRouter()
# router.register(r'posts', PostViewSet)
#
# urlpatterns = [
# path('api/', include(router.urls)),
# ]