11
Option 1.
Change the decorator to a class attribute and simply set the permission_classes to IsAuthenticatedOrReadOnly
.
IsAuthenticatedOrReadOnly
will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the “safe” methods; GET, HEAD or OPTIONS.
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class EntityViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet
):
queryset = Entity.objects.all()
serializer_class = EntitySerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
Option 2.
Create your own permission
and tailor it to your needs. Then, set it as a class attribute.
Example permissions.py
:
from rest_framework.permissions import BasePermission
class IsStaff(BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated and request.user.is_staff
Your file:
from .permissions import IsStaff
class EntityViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet
):
queryset = Entity.objects.all()
serializer_class = EntitySerializer
permission_classes = (IsStaff,)
Option 3.
Override the get_permissions
method in your class.
from rest_framework.permissions import AllowAny
def get_permissions(self):
if self.action == 'retrieve':
return [AllowAny]
return [IsAuthenticated]
Option 4.
If you don’t specify any permissions for the retrieve
method, Django Rest Framework is gonna apply the default one that you have specified in your settings.py
, which in this case is IsAuthenticated
. What you should do is, for the retrieve
method, allow any user.
from rest_framework.permissions import AllowAny
@permission_classes([AllowAny])
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
Note that when you set new permission classes through class attribute or decorators you’re telling the view to ignore the default list set over the settings.py
file.
Don’t worry too much about the authentication_classes
, since the AllowAny
permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.
3
If you are using Class Based DRF Views, use, override get_permissions
method to set the permission properly,
for example,
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
class MyViewSet(...):
def get_permissions(self):
if self.action == 'retrieve':
retrieve_permission_list = [AllowAny, IsAdminUser, ]
return [permission() for permission in retrieve_permission_list]
elif self.action == 'create':
create_permission_list = [IsAuthenticated, IsAdminUser]
return [permission() for permission in create_permission_list]
else:
return super().get_permissions()
In your case,
class EntityViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
...
def get_permissions(self):
if self.action == 'retrieve':
return []
else:
return super().get_permissions()
- [Django]-Django multi-tenant
- [Django]-Django.core.exceptions.ImproperlyConfigured: Requested setting REST_FRAMEWORK
- [Django]-Passing context from child to parent template in Django
- [Django]-ImportError: No module named south
2
The permission checks are actually done in the check_permissions
method, which calls each of the defined permissions retrieved via the get_permissions
method.
So the simplest and easiest would be to override check_permissions
and add your permission logic there based on the action that is requested (based on method-action map). Add the following to your EntityViewSet
class:
def check_permissions(self, request):
if self.action == 'retrieve':
return
super().check_permissions(request)
So no permission check when action
is retrieve
, otherwise do as usual.
FWIW, check_permissions
is actually defined in views.APIView
(superclass of generics.GenericAPIView
).
Why your authentication_classes
/permission_classes
decorators are ignored?
The authentication_classes
/permission_classes
decorators are designed for function based views e.g. created by the api_view
decorator.
The permission_classes
decorator sets permission_classes
attribute on the decorated function:
def permission_classes(permission_classes):
def decorator(func):
func.permission_classes = permission_classes
return func
return decorator
In your case, that’s the retrieve
method but you need to set the attribute on the (to be created) instance or on the class (EntityViewSet
). As a result the permissions from your permission_classes
decorator on the retrieve
method does not have any impact. authentication_classes
decorator also works the same way so that didn’t work either.
FWIW, api_view
actually creates a subclass of views.APIView
class dynamically and sets the permission_classes
, authentication_classes
attributes to the subclass from the decorated function 1. So in that case, it makes sense for the decorated classes to use the authentication_classes
/permission_classes
decorators as those attributes will eventually be applied as-is.
1 The default setting is used if the decorated function does not have the attribute e.g. the relevant decorator was not applied.
- [Django]-Using HttpResponseRedirect, but browser is not showing correct URL
- [Django]-MongoDB storage along with MySQL XPath features
- [Django]-How can I exclude rest-auth endpoint from the Django built-in API documentation?
- [Django]-How to dynamically make an existing non-abstract django model, abstract?
- [Django]-Django Administration: Delete record from custom user model
2
My suggestion is to change the default permission class in the settings file
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'api.authentication.token_authentication.TokenAuthentication'
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
]
}
Now you don’t have to apply permission class on the views. This will be applied by default.