简体   繁体   中英

Django Rest Framework Permissions and Ownership

I have two simple models

class User(AbstractUser): 
    pass


class Vacation(Model):
    id    = models.AutoField(primary_key=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)

I am not really sure what is the scalable way of doing user permissions for Django Rest Framework. In particular:

  • Users should only be able to see their own vacations
  • On the /vacation endpoint, user would see a filtered list
  • On the /vacation/$id endpoint, user would get a 403 if not owner
  • Users should only be able to Create/Update vacations as long as they are the owners of that object (through Foreign Key)

What is the best way to achieve this in a future-proof fashion. Say if further down the line:

  • I add a different user type, which can view all vacations, but can only create/update/delete their own
  • I add another model, where users can read, but cannot write

Thank you!

From the docs:

Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. If any permission check fails an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run.

REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.

For your current need you can define your own Permission class:

class IsVacationOwner(permissions.BasePermission):
    # for view permission
    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated

    # for object level permissions
    def has_object_permission(self, request, view, vacation_obj):
        return vacation_obj.owner.id == request.user.id

And add this permission to your view. For example on a viewset:

class VacationViewSet(viewsets.ModelViewSet):
    permission_classes = (IsVacationOwner,)

One thing is important to notice here, since you will respond with a filtered list for '/vacations' , make sure you filter them using the request.user . Because object level permission will not be applicable for lists.

For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.

For your future requirement, you can always set the permissions conditionally with the help of get_permissions method.

class VacationViewSet(viewsets.ModelViewSet):
    def get_permissions(self):
        if self.action == 'list':
            # vacations can be seen by anyone
            # remember to remove the filter for list though
            permission_classes = [IsAuthenticated] 

            # or maybe that special type of user you mentioned
            # write a `IsSpecialUser` permission class first btw
            permission_classes = [IsSpecialUser] 
        else:
            permission_classes = [IsVacationOwner]

        return [permission() for permission in permission_classes]

DRF has great documentation . I hope this helps you to get started and helps you to approach different use cases according to your future needs.

I would suggest you to use drf-viewsets link . We are going to use vacation viewset to do this work.

our urls.py

from your_app.views import VacationViewSet
router.register('api/vacations/', VacationViewSet)

our serializers.py

from rest_framework import serializers
from your_app.models import Vacation

class VacationSerializer(serializers.ModelSerializer):

    class Meta:
        model = Vacation
        fields = ('id', 'owner',)
        read_only_fields = ('id',)

our views.py

Here we are going to overwrite viewset's retrive and list method. There are other possible way to do that but i like this most as i can able to see what is happening in code. Django model viewset inherited link of drf-mixins retrive and list method.

from rest_framework import viewsets, permissions, exceptions, status
from your_app.models import Vacation, User
from your_app.serializers import VacationSerializer 


class VacationViewSet(viewsets.ModelViewSet):
    queryset = Vacation.objects.all()
    permission_classes = [IsAuthenticated]
    serializer = VacationSerializer

    # we are going to overwrite list and retrive
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # now we are going to filter on user 
        queryset = queryset.filter(owner=self.request.user) 
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        # not permitted check
        if instance.owner is not self.request.user:
             raise exceptions.PermissionDenied()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

Django rest framework provides in-build settings for this

Just import the required permission and add it to you class variable permission_classes

in my_name.api.views

from rest_framework.permissions import ( AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly,)

class Vacation(ListAPIView):
    serializer_class = VacationListSerializer
    permission_classes = [IsAuthenticated]

You can add multiple permission classes as a list

Furthur, in case this is not helpful, you can always filter the model objects as

Mymodel.objects.filter(owner = self.request.user)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM