Search code examples
pythondjangodjango-rest-frameworkdjango-rest-viewsets

django rest framework viewset permission based on method


So I'm writing my first project with DRF and I'm having some issues with setting up permissions for my viewsets. I already have authentication working with djangorestframework-jwt. Currently, I have a few different ViewSets defined. What I would like to do is allow the owner of a model object to make any changes they would like to that object, but prevent everyone else (aside admins) from even viewing the objects. Basically, I need a way of applying permission classes to specific methods to allow only admins to view 'list', owners to 'update, destroy, etc' and authenticated users to 'create'. Currently I have something like this:

class LinkViewSet(viewsets.ModelViewSet):
   queryset = Link.objects.all()
   serializer_class = LinkSerializer

with a model of

class Link(models.Model):
   name = models.CharField(max_length=200)
   url = models.URLField()
   # another model with a OneToMany relationship
   section = models.ForeignKey('homepage.LinkSection', related_name='links', on_delete=models.CASCADE
   owner = models.ForeignKey('homepage.UserProfile'), related_name='links', on_delete=models.CASCADE)

and the permissions class I want to apply

class IsOwner(permissions.BasePermission):
   def has_object_permissions(self, request, view, obj):
      return obj.owner == request.user.userprofile

I'm sure it's possible to achieve this by writing completely custom views but I have a gut feeling that there is an easier way to do this especially since this is basically the last thing I have to do to finish the API. Thanks for any help and let me know if you need any more info.


Solution

  • I was able to create a permission class by checking which action was used in the view as follows here:

    class IsOwner(permissions.BasePermission):
    '''
    Custom permission to only give the owner of the object access
    '''
    message = 'You must be the owner of this object'
    
    def has_permission(self, request, view):
        if view.action == 'list' and not request.user.is_staff:
            print('has_permission false')
            return False
        else:
            print('has_permission true')
            return True
    
    def has_object_permission(self, request, view, obj):
        print('enter has_object_permission')
        # only allow the owner to make changes
        user = self.get_user_for_obj(obj)
        print(f'user: {user.username}')
        if request.user.is_staff:
            print('has_object_permission true: staff')
            return True
        elif view.action == 'create':
            print('has_object_permission true: create')
            return True
        elif user == request.user:
            print('has_object_permission true: owner')
            return True # in practice, an editor will have a profile
        else:
            print('has_object_permission false')
            return False
    
    def get_user_for_obj(self, obj):
        model = type(obj)
        if model is models.UserProfile:
            return obj.user
        else:
            return obj.owner.user
    

    get_user_for_obj is specifically for my implementation as a helper method since my model is inconsistent in how to obtain a user instance. You don't want to make has_permission too restrictive because has_object_permission will only run if has_permission returns True or if the method is not overridden.