Django REST Framework - Separate permissions per methods
Permissions are applied to the entire View class, but you can take into account aspects of the request (like the method such as GET or POST) in your authorization decision.
See the built-in IsAuthenticatedOrReadOnly
as an example:
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']class IsAuthenticatedOrReadOnly(BasePermission): """ The request is authenticated as a user, or is a read-only request. """ def has_permission(self, request, view): if (request.method in SAFE_METHODS or request.user and request.user.is_authenticated()): return True return False
I've come across the same problem when using CBV's, as i have fairly complex permissions logic depending on the request method.
The solution i came up with was to use the third party 'rest_condition' app listed at the bottom of this page
http://www.django-rest-framework.org/api-guide/permissions
https://github.com/caxap/rest_condition
I just split the permissions flow logic so that each branch will run, depending on the request method.
from rest_condition import And, Or, Notclass MyClassBasedView(APIView): permission_classes = [Or(And(IsReadOnlyRequest, IsAllowedRetrieveThis, IsAllowedRetrieveThat), And(IsPostRequest, IsAllowedToCreateThis, ...), And(IsPutPatchRequest, ...), And(IsDeleteRequest, ...)]
So the 'Or' determines which branch of the permissions should run depending on the request method and the 'And' wraps the permissions relating to the accepted request method, so all must pass for permission to be granted. You can also mix 'Or', 'And' and 'Not' within each flow to create even more complex permissions.
The permission classes to run each branch simply look like this,
class IsReadyOnlyRequest(permissions.BasePermission): def has_permission(self, request, view): return request.method in permissions.SAFE_METHODSclass IsPostRequest(permissions.BasePermission): def has_permission(self, request, view): return request.method == "POST"... #You get the idea
Update 30 March 2020: My original solution only patched object permissions, not request permissions. I've included an update below to make this work with request permissions as well.
I know this is an old question but I recently ran into the same problem and wanted to share my solution (since the accepted answer wasn't quite what I needed). @GDorn's answer put me on the right track, but it only works with ViewSet
s because of the self.action
I've solved it creating my own decorator:
def method_permission_classes(classes): def decorator(func): def decorated_func(self, *args, **kwargs): self.permission_classes = classes # this call is needed for request permissions self.check_permissions(self.request) return func(self, *args, **kwargs) return decorated_func return decorator
Instead of setting the permission_classes
property on the function, like the built-in decorator does, my decorator wraps the call and sets the permission classes on the view instance that is being called. This way, the normal get_permissions()
doesn't need any changes, since that simply relies on self.permission_classes
.
To work with request permissions, we do need to call check_permission()
from the decorator, because the it's orginally called in initial()
so before the permission_classes
property is patched.
Note The permissions set through the decorator are the only ones called for object permissions, but for request permissions they are in addition to the class wide permissions, because those are always checked before the request method is even called. If you want to specify all permissions per method only, set permission_classes = []
on the class.
Example use case:
from rest_framework import views, permissionsclass MyView(views.APIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) # used for default APIView endpoints queryset = MyModel.objects.all() serializer_class = MySerializer @method_permission_classes((permissions.IsOwnerOfObject,)) # in addition to IsAuthenticatedOrReadOnly def delete(self, request, id): instance = self.get_object() # ...
Hope this helps someone running into the same problem!