I\'m writing a rest API with the Django REST framework, and I\'d like to protect certain endpoints with permissions. The permission classes look like they provide an elegant
I think all of the other answers are great but we shouldn't suppress the default actions' permission_classes
defined in their decorators directly. So,
from rest_framework import viewsets
from rest_framework import permissions
class BaseModelViewSet(viewsets.ModelViewSet):
queryset = ''
serializer_class = ''
permission_classes = (permissions.AllowAny,)
# Refer to https://stackoverflow.com/a/35987077/1677041
permission_classes_by_action = {
'create': permission_classes,
'list': permission_classes,
'retrieve': permission_classes,
'update': permission_classes,
'destroy': permission_classes,
}
def get_permissions(self):
try:
return [permission() for permission in self.permission_classes_by_action[self.action]]
except KeyError:
if self.action:
action_func = getattr(self, self.action, {})
action_func_kwargs = getattr(action_func, 'kwargs', {})
permission_classes = action_func_kwargs.get('permission_classes')
else:
permission_classes = None
return [permission() for permission in (permission_classes or self.permission_classes)]
Now we could define the permission_classes
in these two ways. Since we defined the default global permission_classes_by_action
in the superclass, we could drop that definition for all the actions in option 2.
class EntityViewSet(BaseModelViewSet):
"""EntityViewSet"""
queryset = Entity.objects.all()
serializer_class = EntitySerializer
permission_classes_by_action = {
'create': (permissions.IsAdminUser,),
'list': (permissions.IsAuthenticatedOrReadOnly,),
'retrieve': (permissions.AllowAny,),
'update': (permissions.AllowAny,),
'destroy': (permissions.IsAdminUser,),
'search': (permissions.IsAuthenticated,) # <--- Option 1
}
@action(detail=False, methods=['post'], permission_classes=(permissions.IsAuthenticated,)) # <--- Option 2
def search(self, request, format=None):
pass
I'm probably late to answer this, but I used a mixin, as one of the commenters pointed out. Taking the answer from @Itachi, this is my mixin implementation:
class ViewSetActionPermissionMixin:
def get_permissions(self):
"""Return the permission classes based on action.
Look for permission classes in a dict mapping action to
permission classes array, ie.:
class MyViewSet(ViewSetActionPermissionMixin, ViewSet):
...
permission_classes = [AllowAny]
permission_action_classes = {
'list': [IsAuthenticated]
'create': [IsAdminUser]
'my_action': [MyCustomPermission]
}
@action(...)
def my_action:
...
If there is no action in the dict mapping, then the default
permission_classes is returned. If a custom action has its
permission_classes defined in the action decorator, then that
supercedes the value defined in the dict mapping.
"""
try:
return [
permission()
for permission in self.permission_action_classes[self.action]
]
except KeyError:
if self.action:
action_func = getattr(self, self.action, {})
action_func_kwargs = getattr(action_func, "kwargs", {})
permission_classes = action_func_kwargs.get(
"permission_classes"
)
else:
permission_classes = None
return [
permission()
for permission in (
permission_classes or self.permission_classes
)
]
And here's how to use the mixin:
class MyViewSet(ViewSetActionPermissionMixin, ModelViewSet):
...
permission_action_classes = {
"list": [AllowAny],
"create": [IsAdminUser],
"custom_action": [MyCustomPermission],
}
@action(...)
def custom_action(self, request, *args, **kwargs):
...
I created a superclass that is derived from @ilse2005's answer. In all subsequent django views you can inherit this to achieve action level permission control.
class MixedPermissionModelViewSet(viewsets.ModelViewSet):
'''
Mixed permission base model allowing for action level
permission control. Subclasses may define their permissions
by creating a 'permission_classes_by_action' variable.
Example:
permission_classes_by_action = {'list': [AllowAny],
'create': [IsAdminUser]}
'''
permission_classes_by_action = {}
def get_permissions(self):
try:
# return permission_classes depending on `action`
return [permission() for permission in self.permission_classes_by_action[self.action]]
except KeyError:
# action is not set return default permission_classes
return [permission() for permission in self.permission_classes]
I think there is no inbuilt solution for that. But you can achieve this by overriding the get_permissions
method:
from rest_framework.permissions import AllowAny, IsAdminUser
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes_by_action = {'create': [AllowAny],
'list': [IsAdminUser]}
def create(self, request, *args, **kwargs):
return super(UserViewSet, self).create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
return super(UserViewSet, self).list(request, *args, **kwargs)
def get_permissions(self):
try:
# return permission_classes depending on `action`
return [permission() for permission in self.permission_classes_by_action[self.action]]
except KeyError:
# action is not set return default permission_classes
return [permission() for permission in self.permission_classes]