Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django-rest-framework: return Response from .dispatch

I am doing a (more-or-less) custom authentication in a django-rest-framework-based application, where I just need to call another microservice to ask it if a token is valid and what username/userid are associated with it. (I don't have a local table for users).

Having found no out-of-the-box solution, I am overriding the dispatch method (I am using a APIView-based view), where I make the request to the remote service and, if the response is not 200, I want to return a 403 error.

Here's my code:

def dispatch(self, request, *args, **kwargs):
    try:
        user_info = user_from_token_in_request(request)
        return super().dispatch(*args, **kwargs)
    except:
        return Response(
            "No Auth Token provided or bad Auth Token"
            status.HTTP_403_FORBIDDEN,
        )

However, when I pass an invalid token, I get this error: AssertionError: .accepted_renderer not set on Response, because the response context is not initialised when the dispatch method is processed.

Is there a, sort of, more proper way of doing it?

like image 451
Ibolit Avatar asked Oct 12 '17 15:10

Ibolit


People also ask

What is the difference between APIView and Viewset?

APIView allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Viewsets allow us to define functions that match to common API object actions like : LIST, CREATE, RETRIEVE, UPDATE, etc.

How do you pass extra context data to Serializers in Django REST framework?

In function-based views, we can pass extra context to serializer with “context” parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with “self. context”. From example, to get “exclude_email_list” we just used code 'exclude_email_list = self.

What is response in Django REST framework?

— Django documentation. REST framework supports HTTP content negotiation by providing a Response class which allows you to return content that can be rendered into multiple content types, depending on the client request. The Response class subclasses Django's SimpleTemplateResponse .

What is APIView in Django REST framework?

REST framework provides an APIView class, which subclasses Django's View class. APIView classes are different from regular View classes in the following ways: Requests passed to the handler methods will be REST framework's Request instances, not Django's HttpRequest instances.


2 Answers

Instead of returning the Response try raising one of the exceptions provided by DRF, and its exception handler would automatically handle the rest.

from rest_framework.exceptions import PermissionDenied

def dispatch(self, request, *args, **kwargs):
    try:
        # ...
    except:
        raise PermissionDenied("NO Auth Token provided")

With that said, it is not recommended to write your authentication code in your view itself. You should have done this in a custom authentication/permission class.

from rest_framework.authentication import BaseAuthentication

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        try:
            user_info = user_from_token_in_request(request)
        except:
            raise AuthenticationFailed('No auth token provided')

class MyApiView(APIView):
    authentication_classes = [MyAuthentication]

Also make sure, you have defined EXCEPTION_HANDLER in your rest framework settings.

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
like image 133
hspandher Avatar answered Sep 23 '22 14:09

hspandher


To answer the original question, handle_exception is called inside dispatch, so if you are overriding dispatch, it will not be called automatically for you. BUT you can call it directly:

from rest_framework.exceptions import PermissionDenied

class SometimesErrorMixin:
    should_error = None

    def dispatch(self, request, *args, **kwargs):

        if not self.should_error:
            return super().dispatch(request, *args, **kwargs)

        # Lifted from DRF
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        response = self.handle_exception(PermissionDenied())
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

Put this Mixin left most on a ViewSet to use the behavior. Obviously you can replace should_error with the appropriate logic.

like image 21
Aaron McMillin Avatar answered Sep 23 '22 14:09

Aaron McMillin