简体   繁体   中英

DRF APIView move request validation to dispatch method using request.data

I've created a base api view, which extends from APIView , where I log response time, log request, and other common stuffs.

Now, I also want to add request validation here, using the Serializer defined in sub-class Views. I thought the appropriate place is to put that in dispatch() method. But before I call API.dispatch() method, request.data is not prepared. So, that won't work. Can someone help me in right direction as to how to move validation to a single place?

Here's the class structure:

class BaseView(APIView):
    validation_serializer = None

    def dispatch(self, request, *args, **kwargs):
        # Some code here
        # How to use `validation_serializer` here, to validate request data?
        # `request.data` is not available here.
        response = super(BaseView, self).dispatch(request, *args, **kwargs)
        # Some code here
        return response

class MyView(BaseView):
    validation_serializer = ViewValidationSerializer

    def post(self, request, *args, **kwargs):
        pass

I thought another approach could be use decorator on the top of post() method. But if only there was an cleaner way, than putting decorators all across the project?

Note: It's similar to the question here: Django - DRF - dispatch method flow . But as per the suggestion there, I don't want to just copy the entire dispatch method from DRF source code.

The method that processes the django request into a DRF request (and adds the request.data property) is the APIView.initialize_request . The APIView.dispatch() method calls it and then proceeds to call the appropriate method handler ( post/patch/put ).

You can try to do that yourself by calling it and using the returned object:

class BaseView(APIView):
    validation_serializer = None

    def dispatch(self, request, *args, **kwargs):
        request = self.initialize_request(request, *args, **kwargs)
        kwargs['context'] = self.get_serializer_context()
        serializer = self.validation_serializer(data=request.data, *args, **kwargs)

        # use `raise_exception=True` to raise a ValidationError
        serializer.is_valid(raise_exception=True)

        response = super(BaseView, self).dispatch(request, *args, **kwargs)
        return response

However, I would suggest against this, as other functionality of dispatch() probably should be performed prior to handling validation; thus, you could moving the above logic to the relevant post/patch/put methods instead.

In these methods you can also use self.request directly since it was already initialized by dispatch() .

I think drf-tracking does what you are looking for. You may want to check it out.

I don't think you're going about this the correct way. The best way to log the request, with validation is in your Authentication Class and add the audit log to the request.

Then you can use your APIView to log the render time, against the AuditLog generated in the Authentication Class.


Here's an example using Token Authentication, assuming each request has a header Authorization: Bearer <Token> .

settings.py

...

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'common.authentication.MyTokenAuthenticationClass'
    ),
    ...,
}

common/authentication.py

from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions

from accounts.models import Token, AuditLog


class MyTokenAuthenticationClass(authentication.BaseAuthentication):

    def authenticate(self, request):

        # Grab the Athorization Header from the HTTP Request
        auth = authentication.get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'bearer':
            return None

        # Check that Token header is properly formatted and present, raise errors if not
        if len(auth) == 1:
            msg = _('Invalid token header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid token header. Credentials string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            token = Token.objects.get(token=auth[1])
            # Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
            token.last_ip = get_real_ip(request)
            token.last_login = timezone.now()
            token.save()

            # Add the saved token instance to the request context
            request.token = token

        except Token.DoesNotExist:
            raise exceptions.AuthenticationFailed('Invalid token.')

        # At this point, insert the Log into your AuditLog table and add to request:
        request.audit_log = AuditLog.objects.create(
            user_id=token.user,
            request_payload=request.body,
            # Additional fields
            ...
        )

        # Return the Authenticated User associated with the Token
        return (token.user, token)

Now, you have access to the AuditLog in your request. So you can log everything before and after validation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM