title: How to combine/mix object level and user level permissions in DRF?
body: <p>I am currently working on a DRF project where Admin users, Teacher users and Owner users should be able to have access to an objects detail-view. Basically all user types except those who are not the owner of the object or a teacher or an admin user. I am able to implement the separate permissions for each, but when I need to combine these permissions on a view I hit a roadblock because the user level perms are checked before object level perms. Thus I cannot use boolean operands to combine them and I have to write these ugly permission classes for my views. </p>
  
  <h2>My question is:</h2>
  
  <p>How can I implement these permissions on my detail-view in a cleaner way or, alternatively, is there a cleaner way to obtain my result?</p>
  
  <p>As you will see, I violate DRY because I have an IsAdminOrOwner and IsAdminOrTeacherOrOwner perm. You will also note in my view that I overwrite get_permissions() to have appropriate permission classes for the respective request methods. Any comments on this and other implementations are welcome, I want critique so that I can improve upon it.</p>
  
  <h2>Here follows permissions.py:</h2>
  
  <pre><code>from rest_framework import permissions
  from rest_framework.permissions import IsAdminUser
  
  class IsOwner(permissions.BasePermission):
  
      def has_permission(self, request, view):
          return True
  
      def has_object_permission(self, request, view, obj):
          return request.user == obj
  
  class IsTeacher(permissions.BasePermission):
  
      def has_permission(self, request, view):
          return request.user.groups.filter(
              name='teacher_group').exists()
  
  class IsAdminOrOwner(permissions.BasePermission):
  
      def has_object_permission(self, *args):
          is_owner = IsOwner().has_object_permission(*args)
  
          #convert tuple to list
          new_args = list(args)
          #remove object for non-object permission args
          new_args.pop()
          is_admin = IsAdminUser().has_permission(*new_args)
  
          return is_owner or is_admin
  
  class IsAdminOrTeacherOrOwner(permissions.BasePermission):
  
      def has_object_permission(self, *args):
          is_owner = IsOwner().has_object_permission(*args)
  
          #convert tuple to list
          new_args = list(args)
          #remove object for non-object permission args
          new_args.pop()
          is_admin = IsAdminUser().has_permission(*new_args)
          is_teacher = IsTeacher().has_permission(*new_args)
  
          return is_admin or is_teacher or is_owner
  </code></pre>
  
  <h2>And here follows my view:</h2>
  
  <pre><code>class UserRetrieveUpdateView(APIView):
      serializer_class = UserSerializer
  
      def get_permissions(self):
          #the = is essential, because with each view
          #it resets the permission classes
          #if we did not implement it, the permissions
          #would have added up incorrectly
          self.permission_classes = [IsAuthenticated]
  
          if self.request.method == 'GET':
              self.permission_classes.append(
                  IsAdminOrTeacherOrOwner)
  
          elif self.request.method == 'PUT':
              self.permission_classes.append(IsOwner)
  
          return super().get_permissions()
  
      def get_object(self, pk):
          try:
              user = User.objects.get(pk=pk)
              self.check_object_permissions(self.request, user)
              return user
          except User.DoesNotExist:
              raise Http404
  
      def get(self, request, pk, format=None):
          user = self.get_object(pk)
          serializer = self.serializer_class(user)
          return Response(serializer.data, status.HTTP_200_OK)
  
      def put(self, request, pk, format=None):
          user = self.get_object(pk)
          #we use partial to update only certain values
          serializer = self.serializer_class(user,
              data=request.data, partial=True)
  
          if serializer.is_valid():
              serializer.save()
              return Response(serializer.data,
                  status.HTTP_200_OK)
  
          return Response(status=status.HTTP_400_BAD_REQUEST)
  </code></pre>