djangopermissionsdjango-rest-frameworkdjango-permissionsdjango-2.1

Django Rest Framework Custom Permission's Message Not Shown


I'm writing an application with the Django Rest Framework.

I created a custom permission. I provided a message attribute to the custom permission, but still the default detail gets returned.

Let me give you my code.

permissions.py:

from annoying.functions import get_object_or_None
from rest_framework import permissions

from intquestions.models import IntQuestion

from ..models import Candidate, CandidatePickedIntChoice

CANDIDATE_ALREADY_ANSWERED = "This candidate already answered all questions."


class CandidateAnsweredQuestionsPermission(permissions.BasePermission):
    """
    Permission to check if the candidate has answered all questions.
    Expects candidate's email or UUID in the request's body.
    """
    message = CANDIDATE_ALREADY_ANSWERED

    def has_permission(self, request, view):
        candidate = None
        email = request.data.get("email", None)
        if email:
            candidate = get_object_or_None(Candidate, email=email)
        else:
            uuid = request.data.get("candidate", None)
            if uuid:
                candidate = get_object_or_None(Candidate, uuid=uuid)

        if candidate:
            picked_choices = CandidatePickedIntChoice.objects.filter(
                candidate=candidate
            ).count()
            total_int_questions = IntQuestion.objects.count()

            if picked_choices >= total_int_questions:
                return False

        return True

views.py:

from annoying.functions import get_object_or_None
from rest_framework import generics, status
from rest_framework.response import Response

from ..models import Candidate, CandidatePickedIntChoice
from .permissions import CandidateAnsweredQuestionsPermission
from .serializers import CandidateSerializer


class CandidateCreateAPIView(generics.CreateAPIView):
    serializer_class = CandidateSerializer
    queryset = Candidate.objects.all()
    permission_classes = (CandidateAnsweredQuestionsPermission,)

    def create(self, request, *args, **kwargs):
        candidate = get_object_or_None(Candidate, email=request.data.get("email", None))
        if not candidate:
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
        else:
            serializer = self.get_serializer(candidate, data=request.data)
            serializer.is_valid(raise_exception=True)
            return Response(serializer.data, status=status.HTTP_200_OK)

Note: The app I'm building lets candidates answer questions. The reason I overwrote the create function like this, is so that candidates who haven't yet finished all questions are still able to answer all the questions.

Why is the permission message the default "Authentication credentials were not provided." instead of my own?


Solution

  • The message Authentication credentials were not provided. says that, you are not provided the credentials. It differs from credentials are wrong message

    Next thing is, there is not attribute message for the BasePermission class, so it won't use your message attribute unless you forced. ( Source Code )

    How to show the custom PermissionDenied message?
    The PermissionDenied exception raised from permission_denied() method ove viewset, ( Source Code )
    So your view should be like,

    from rest_framework import exceptions
    
    
    class CandidateCreateAPIView(generics.CreateAPIView):
        # your code
        def permission_denied(self, request, message=None):
            if request.authenticators and not request.successful_authenticator:
                raise exceptions.NotAuthenticated()
            raise exceptions.PermissionDenied(detail=CANDIDATE_ALREADY_ANSWERED)