DRF Testing and Custom Permissions

Hi All,

I’m continuing my journey of testing my Django Rest Framework application as I add new views and more functionality. I must admit, at this stage, I’m finding testing harder than actually coding and building my app. I feel that there are far fewer resources on testing DRF available than there are resources talking about building a REST framework with DRF. C’est la vie, though, I soldier on.

My issue that I’m currently facing is that I’m receiving a 403 error when testing one of my DRF ViewSets. I can confirm that the view, and its permissions work fine when using the browser or a regular python script to access the endpoint.

Let’s start with the model that is used in my ViewSet

class QuizTracking(models.Model):
    case_attempt = models.ForeignKey(CaseAttempt, on_delete=models.CASCADE)
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)

Of note here is that there is a FK to a user. This is used when determining permissions.

Here is my test function. I have not included code for the entire class for the sake of brevity.

    def test_question_retrieve(self):
        """
            Check that quiz tracking/ID returns a 200 OK and the content is correct
        """
        jim = User(username='jimmy', password='monkey123', email='jimmy@jim.com')
        jim.save()
        quiz_tracking = QuizTracking(answer=self.answer, case_attempt=self.case_attempt, user=jim)
        quiz_tracking.save()
        request = self.factory.get(f'/api/v1/progress/quiz-tracking/{quiz_tracking.id}')

        # How do I refernce my custom permission using Permission.objects.get() ?
        # permission = Permission.objects.get()
        # jim.user_permissions.add(permission)

        # self.test_user.refresh_from_db()
        force_authenticate(request, user=jim)
        response = self.quiz_detail_view(request, pk=quiz_tracking.id)
        print(response.data)
        print(jim.id)
        print(quiz_tracking.user.id)

        self.assertContains(response, 'answer')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

In the above code, I define a user, jim and a quiz_tracking object owned by jim.

I build my request, force_authenticate the requst and the execute my request and store the response in response.

The interesting things to note here are:

  • jim.id and quiz_tracking.user.id are the same value
  • I receive a 403 response with
{'detail': ErrorDetail(string='You do not have permission to perform this action.', code='permission_denied')}

You may have noticed that I have commented out permission = Permission.objects.get() My understanding is that I need to pass this my Permission Class, which in my case is IsUser. However, there is no record of this in my DB and hence Permission.objects.get('IsUSer') call fails.

So my questions are as follows:

  • How do I authenticate my request so that I receive a 200 OK?
  • Do I need to add a permission to my user in my tests, and if so, which permission and with what syntax?

Below is my view and below that is my custom permission file defining IsUser

class QuickTrackingViewSet(viewsets.ModelViewSet):
    serializer_class = QuizTrackingSerializser

    def get_queryset(self):
        return QuizTracking.objects.all().filter(user=self.request.user)

    def get_permissions(self):
        if self.action == 'list':
            self.permission_classes = [IsUser, ]
        elif self.action == 'retrieve':
            self.permission_classes = [IsUser, ]

        return super(self.__class__, self).get_permissions()

N.B. If I comment out def get_permissions(), then my test passes without problem.

My custom permission.py

from rest_framework.permissions import BasePermission


class IsSuperUser(BasePermission):

    def has_permission(self, request, view):
        return request.user and request.user.is_superuser


class IsUser(BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.user:
            if request.user.is_superuser:
                return True
            else:
                return obj == request.user
        else:
            return False

Cheers,

C

My error was in my permissions.py file. My IsUser has_object_permission worked when the object I was querying was a user, for example at a /users/ endpoint, but this didn’t work when the object in question wasn’t a user object.

For objects which have a user attribute such as my QuizTracking model, I need to rewrite my method along the lines of the following:

class IsUser(BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_superuser

    def has_object_permission(self, request, view, obj):
        return request.user.is_superuser or obj.user.id == request.user.id

In the above I am checking to ensure that the object’s user.id matches that of the user making the request.

Big thank you to Enthusiast Martin on StackOverflow for pointing out what I now see as my obvious error and for refactoring my code. Hats off to you, Martin!

Cheers,

C