DRF APIView使用request.data将请求验证移动到调度方法

时间:2016-11-20 07:40:03

标签: python django django-rest-framework

我已经创建了一个基本api视图,它从APIView扩展而来,在那里我记录了响应时间,日志请求和其他常见内容。

现在,我还想在这里添加请求验证,使用子类Views中定义的Serializer。我认为适当的地方是将其放在dispatch()方法中。但在我调用API.dispatch()方法之前,request.data尚未准备好。所以,那是行不通的。有人可以帮我正确指导如何将验证移到一个地方吗?

这是类结构:

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

我认为另一种方法可能是在post()方法的顶部使用装饰器。但是,如果只有一个更清洁的方式,而不是在整个项目中设置装饰器?

注意:这与此处的问题类似:Django - DRF - dispatch method flow。但根据那里的建议,我不想只从DRF源代码中复制整个dispatch方法。

3 个答案:

答案 0 :(得分:1)

将django请求处理为DRF请求(并添加request.data属性)的方法是APIView.initialize_requestAPIView.dispatch() method calls it然后继续调用适当的方法处理程序( post / patch / put )。

您可以尝试通过调用它并使用返回的对象来自己完成:

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

但是,我建议不要这样做,因为dispatch()的其他功能可能应该在处理验证之前执行;因此,您可以将上述逻辑移至相关的 post / patch / put 方法。

在这些方法中,您也可以直接使用self.request,因为它已由dispatch()初始化。

答案 1 :(得分:0)

我认为drf-tracking可以满足您的需求。你可能想看一下。

答案 2 :(得分:0)

我不认为你以正确的方式解决这个问题。通过验证记录请求的最佳方法是在您的身份验证类中,并将审核日志添加到请求中。

然后,您可以使用APIView来记录渲染时间,而不是身份验证类中生成的AuditLog

以下是使用令牌身份验证的示例,假设每个请求都有一个标头Authorization: Bearer <Token>

<强> settings.py

...

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

<强>公共/ 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)

现在,您可以在请求中访问AuditLog。因此,您可以在验证之前和之后记录所有内容。