20👍
You can create a custom permission class extending DRF’s BasePermission
.
You implement has_permission
where you have access to the request
and view
objects. You can check request.user
for the appropriate role and return True
/False
as appropriate.
Have a look at the provided IsAuthenticatedOrReadOnly class (and others) for a good example of how easy it is.
I hope that helps.
95👍
In DRF documentation,
Note: The instance-level has_object_permission method will only be called if the view-level has_permission checks have already passed
Let’s assume following permission about user
object
- List : staff only
- Create : anyone
- Retrieve : own self or staff
- Update, Partial update : own self or staff
- Destroy : staff only
permissons.py
from rest_framework import permissions
class UserPermission(permissions.BasePermission):
def has_permission(self, request, view):
if view.action == 'list':
return request.user.is_authenticated() and request.user.is_admin
elif view.action == 'create':
return True
elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']:
return True
else:
return False
def has_object_permission(self, request, view, obj):
# Deny actions on objects if the user is not authenticated
if not request.user.is_authenticated():
return False
if view.action == 'retrieve':
return obj == request.user or request.user.is_admin
elif view.action in ['update', 'partial_update']:
return obj == request.user or request.user.is_admin
elif view.action == 'destroy':
return request.user.is_admin
else:
return False
views.py
from .models import User
from .permissions import UserPermission
from .serializers import UserSerializer
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (UserPermission,)
For Django 2.0 replace is_authenticated()
with is_authenticated
. The method has been turned into an attribute.
- [Django]-Get SQL query count during a Django shell session
- [Django]-Format numbers in django templates
- [Django]-How to override css in Django Admin?
12👍
From the DRF documentation, you can now do this instead :
The decorator allows you to override any viewset-level configuration
such as permission_classes, serializer_class, filter_backends…:
@action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
- [Django]-Switching from MySQL to Cassandra – Pros/Cons?
- [Django]-Django: Reference to an outer query may only be used in a subquery
- [Django]-Dynamically add field to a form
9👍
I personally hate this kind of Frankenstein’s monster custom permissions, in my opinion, it’s not very idiomatic when it comes to the Django framework.
So I came up with the following solution – it’s very similar to how @list_route
and @detail_route
decorators work.
We are relying on the fact that the methods/functions are first-class objects
First of all, I’m creating such decorator:
decorators.py
def route_action_arguments(**kwargs):
"""
Add arguments to the action method
"""
def decorator(func):
func.route_action_kwargs = kwargs
return func
return decorator
As you can see it adds a dictionary to the function it decorates with parameters passed as arg list
Now I created such mixin:
mixins.py
class RouteActionArgumentsMixin (object):
"""
Use action specific parameters to
provide:
- serializer
- permissions
"""
def _get_kwargs(self):
action = getattr(self, 'action')
if not action:
raise AttributeError
print('getting route kwargs for action:' + action)
action_method = getattr(self, action)
kwargs = getattr(action_method, 'route_action_kwargs')
print(dir(kwargs))
return kwargs
def get_serializer_class(self):
try:
kwargs = self._get_kwargs()
return kwargs['serializer']
except (KeyError, AttributeError):
return super(RouteActionArgumentsMixin, self).get_serializer_class()
def get_permissions(self):
try:
kwargs = self._get_kwargs()
return kwargs['permission_classes']
except (KeyError, AttributeError):
return super(RouteActionArgumentsMixin, self).get_permissions()
Mixin does two things;
when get_permissions
is called, it checks which ‘action’ is executed, and looksup the permission_classes collection from the route_action_kwargs
associated with the viewset.action_method.route_action_kwargs
when get_serializer_class
is called, it does the same and picks the serializer
from route_action_kwargs
Now the way we can use it:
@method_decorator(route_action_arguments(serializer=LoginSerializer), name='create')
class UserViewSet (RouteActionArgumentsMixin, RequestContextMixin, viewsets.ModelViewSet):
"""
User and profile managment viewset
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@list_route(methods=['post'])
@route_action_arguments(permission_classes=(AllowAny,), serializer=LoginSerializer)
def login(self, request):
serializer = self.get_serializer_class()(data=request.data)
For custom routs we define explicitly we can just set the @route_action_arguments
explicitly on the method.
In terms of the generic viewsets and methods, we can still add them using the
@method_decorator
@method_decorator(route_action_arguments(serializer=LoginSerializer), name='create')
class UserViewSet (RouteActionArgumentsMixin, RequestContextMixin, viewsets.ModelViewSet):
- [Django]-Django: Using F arguments in datetime.timedelta inside a query
- [Django]-Django – getting Error "Reverse for 'detail' with no arguments not found. 1 pattern(s) tried:" when using {% url "music:fav" %}
- [Django]-What's the difference between select_related and prefetch_related in Django ORM?
4👍
Django has a permissions class called DjangoObjectPermissions
which uses Django Guardian as an authentication backend.
When you have Django guardian active in your settings you just add permission_classes = [DjandoObjectPermissions]
to your view and it does permission authentication automatically, so you can ‘CRUD’ based on the permission set to a particular django.contrib.auth
group or user.
See a gist with an example.
You can set Django Guardian as your authentication backed
- [Django]-STATIC_ROOT vs STATIC_URL in Django
- [Django]-Django 1.7 – App 'your_app_name' does not have migrations
- [Django]-Convert seconds to hh:mm:ss in Python
2👍
RestFramework’s class-based views have methods for each HTTP verb (ie : HTTP GET => view.get()
etc). You just have to use django.contrib.auth
‘s permissions, users, groups and decorators as documented.
- [Django]-Gunicorn Connection in Use: ('0.0.0.0', 5000)
- [Django]-Testing admin.ModelAdmin in django
- [Django]-How to use refresh token to obtain new access token on django-oauth-toolkit?
2👍
There’s an example of how this can be done in the DRF documentation on viewsets:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action == 'list':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
It is not really clear from the docs that this is the best way to do it – this piece of code is used as an example of how to introspect actions, not how to implement permissions classes per action. It is also not mentioned at all when permissions are discussed.
However I do think this is a bit cleaner than implementing the same logic inside a custom permission. This way you can keep your permissions a bit more granular and descriptive.
- [Django]-How to use curl with Django, csrf tokens and POST requests
- [Django]-How to get form fields' id in Django
- [Django]-Embedding JSON objects in script tags