Reputation: 61
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.
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?
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.
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
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)
Upvotes: 3
Views: 1609
Reputation: 6296
Permissions in DRF can be combined using the bitwise OR operator. For example, you could do this:
permission_classes = (IsAdmin | IsOwner | IsTeacher)
In this way, you don't have to define separate classes to combine them.
Upvotes: 4