使用APIView实现CBV,示例

python
from django.http import HttpResponse  
  
from rest_framework.views import APIView
class TestView(APIView):
    def post(self, request):
        return HttpResponse("view post ...")

url配置

python
path('test/', views.TestView.as_view(), name='test')

查看as_view()

python
class APIView(View):
    # The following policies may be set at either globally, or per-view.  
  renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES  
  parser_classes = api_settings.DEFAULT_PARSER_CLASSES  
  authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  
  throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES  
  permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES  
  content_negotiation_class=api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS  
  metadata_class = api_settings.DEFAULT_METADATA_CLASS  
  versioning_class = api_settings.DEFAULT_VERSIONING_CLASS   
 # Allow dependency injection of other settings to make testing easier.  
  settings = api_settings  
  schema = DefaultSchema()
  # The following policies may be set at either globally, or per-view.  
  renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES  
  parser_classes = api_settings.DEFAULT_PARSER_CLASSES  
  authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  
  throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES  
  permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES  
  content_negotiation_class =api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS  
  metadata_class = api_settings.DEFAULT_METADATA_CLASS  
  versioning_class = api_settings.DEFAULT_VERSIONING_CLASS  
    
  # Allow dependency injection of other settings to make testing easier.  
  settings = api_settings  
  schema = DefaultSchema()
    ...
  
    @classmethod  
    def as_view(cls, **initkwargs):  
        """  
        Store the original class on the view function.  
        This allows us to discover information about the view when we do URL    reverse lookups.  Used for breadcrumb generation.    """    
      if isinstance(getattr(cls, 'queryset', None),models.query.QuerySet): 
            def force_evaluation():  
                raise RuntimeError(  
                    'Do not evaluate the `.queryset` attribute directly, '  
                    'as the result will be cached and reused between requests. '                'Use `.all()` or call `.get_queryset()` instead.'            )  
            cls.queryset._fetch_all = force_evaluation  
      
        view = super().as_view(**initkwargs)  
        view.cls = cls  
        view.initkwargs = initkwargs  
      
        # Exempt all DRF views from Django's LoginRequiredMiddleware. Users should set  
        # DEFAULT\_PERMISSION\_CLASSES to'rest\_framework.permissions.IsAuthenticated' instead                  
        view.login_required = False  
      
        # Note: session based authentication is explicitly CSRF validated,  
        # all other authentication is CSRF exempt.   
        return csrf_exempt(view)

APIView继承View,就是django自带的那个CBV实现类。这个类中自己定义了一堆类属性,没有重写自己的__init__方法。 在重写的as_view中: (1)调用父类的as_view方法 所以这里的as_view与View类没有区别,里面的cls绑定的是你自己定义的TestView(继承APIView)。 (2)同时给as_view函数对象声明一个login_required属性 (3)使用csrf_exempt装饰返回原来的view

在as_view中会调用dispatch方法,在APIView中找到了它的重写: APIView中dispatch:

python
def dispatch(self, request, *args, **kwargs):  
    """  
    Dispatch the incoming request to the appropriate handler method.  
    This method implements the core request/response lifecycle for    Django REST Framework views:  
    1. Wraps the incoming Django HttpRequest in a REST framework Request.    2. Performs content negotiation, authentication, permission, and        throttling checks.    3. Resolves and calls the appropriate HTTP method handler        (e.g. get(), post(), put()).    4. Handles any exceptions raised during processing and converts        them into appropriate Response objects.    5. Finalizes and returns the response with proper rendering        and headers applied.    """   
    self.args = args  
    self.kwargs = kwargs  
    request = self.initialize_request(request, *args, **kwargs)  
    self.request = request  
    self.headers = self.default_response_headers  # deprecate?  
  
    try:  
        self.initial(request, *args, **kwargs)  
  
        # Get the appropriate handler method  
        if request.method.lower() in self.http_method_names:  
            handler = getattr(self, request.method.lower(),  self.http——method_not_allowed)  
        else:  
            handler = self.http_method_not_allowed  
  
        response = handler(request, *args, **kwargs)  
  
    except Exception as exc:  
        response = self.handle_exception(exc)  
  
    self.response = self.finalize_response(request, response, *args, **kwargs)  
    return self.response

View中的dispatch:

python
def dispatch(self, request, *args, **kwargs):  
    # Try to dispatch to the right method; if a method doesn't exist,  
    # defer to the error handler. Also defer to the error handler if the    # request method isn't on the approved list.    method = request.method.lower()  
    if method in self.http_method_names:  
        handler = getattr(self, method, self.http_method_not_allowed)  
    else:  
        handler = self.http_method_not_allowed  
    return handler(request, *args, **kwargs)

不同点: (1)将请求参数先注入给实例属性

python
self.args = args  
self.kwargs = kwargs 

(2)重新包装request,并注入给实例属性request属性中

text
request = self.initialize_request(request, *args, **kwargs)  
self.request = request  

我的理解是这个注入就是在为后续继承子类方便操作,APIView也只是restframework基础底座。这样后面都是类与实例的操作,完全CBV。

python
def initialize_request(self, request, *args, **kwargs):  
    """  
    Returns the initial request object.    """    
    parser_context = self.get_parser_context(request)  
  
    return Request(  
        request,  
        parsers=self.get_parsers(),  
        authenticators=self.get_authenticators(),  
        negotiator=self.get_content_negotiator(),  
        parser_context=parser_context  
    )

在initialize_request函数中,对WSGI传进来的request重新包装,返回Request实例,所以后续继承APIView的子类中的self.request将不再是原来request。 get_parser_context解析:

python
def get_parser_context(self, http_request):  
    """  
    Returns a dict that is passed through to Parser.parse(),    as the `parser_context` keyword argument.    """    
  # Note: Additionally \`request\` and \`encoding\` will also be added  
    #       to the context by the Request object.   
   return {  
        'view': self,  
        'args': getattr(self, 'args', ()),  
        'kwargs': getattr(self, 'kwargs', {})  
    }

get_parser_context中直接返回一个字典,里面存放实例,args,kwargs。

get_parsers()解析:

python
def get_parsers(self):  
    """  
    Instantiates and returns the list of parsers that this view can use.    """    return [parser() for parser in self.parser_classes]

循环parser_classes类并返回他们的实例对象列表。找到parser_classes = api_settings.DEFAULT_PARSER_CLASSES,parser_classes是一个类变量。

python
from rest_framework.settings import api_settings

class APIView(View):
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES

我们接着来看api_settings是什么:

python
class APISettings:
    """
    A settings object that allows REST Framework settings to be accessed as
    properties. For example:

        from rest_framework.settings import api_settings
        print(api_settings.DEFAULT_RENDERER_CLASSES)

    Any setting with string import paths will be automatically resolved
    and return the class, rather than the string literal.

    Note:
    This is an internal class that is only compatible with settings namespaced
    under the REST_FRAMEWORK name. It is not intended to be used by 3rd-party
    apps, and test helpers like `override_settings` may not work as expected.
    """
    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

    @property
    def user_settings(self):
        if not hasattr(self, '_user_settings'):
            self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
        return self._user_settings

    def __getattr__(self, attr):
        if attr not in self.defaults:
            raise AttributeError("Invalid API setting: '%s'" % attr)

        try:
            # Check if present in user settings
            val = self.user_settings[attr]
        except KeyError:
            # Fall back to defaults
            val = self.defaults[attr]

        # Coerce import strings into classes
        if attr in self.import_strings:
            val = perform_import(val, attr)

        # Cache the result
        self._cached_attrs.add(attr)
        setattr(self, attr, val)
        return val

    def __check_user_settings(self, user_settings):
        SETTINGS_DOC = "https://www.django-rest-framework.org/api-guide/settings/"
        for setting in REMOVED_SETTINGS:
            if setting in user_settings:
                raise RuntimeError("The '%s' setting has been removed. Please refer to '%s' for available settings." % (setting, SETTINGS_DOC))
        return user_settings

    def reload(self):
        for attr in self._cached_attrs:
            delattr(self, attr)
        self._cached_attrs.clear()
        if hasattr(self, '_user_settings'):
            delattr(self, '_user_settings')

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

api_settings是一个 APISettings类示例,初始化默认传入DEFAULTS与IMPORT_STRINGS。再看看这两参数是什么:

python
DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
  ...
}

DEFAULTS时一个字典,这个暂时不解析每个字典作用,毕竟结合例子更好理解。

python
# List of settings that may be in string import notation.
IMPORT_STRINGS = [
    'DEFAULT_RENDERER_CLASSES',
    'DEFAULT_PARSER_CLASSES',
    ...
]

IMPORT_STRINGS是一个列表,这里暂不解析每个里面什么作用。 在来看看APISettings实现:

python
class APISettings:
    def __init__(self, user_settings=None, defaults=None,import_strings=None):
        if user_settings:
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

初始化时将两个默认的字典与列表给了示例属性。如果有传入就覆盖。

回到

python
parser_classes = api_settings.DEFAULT_PARSER_CLASSES

其实在APIView类定义的那些类属性在APISettings都没有定义。那么就会统一触发__getattr__方法

python
def __getattr__(self, attr):  
    if attr not in self.defaults:  
        raise AttributeError("Invalid API setting: '%s'" % attr)  
  
    try:  
        # Check if present in user settings  
        val = self.user_settings[attr]  
    except KeyError:  
        # Fall back to defaults  
        val = self.defaults[attr]  
  
    # Coerce import strings into classes  
    if attr in self.import_strings:  
        val = perform_import(val, attr)  
  
    # Cache the result  
    self._cached_attrs.add(attr)  
    setattr(self, attr, val)  
    return val

首先校验调用的属性DEFAULT_PARSER_CLASSES是否在实例self.defaults中,我们在DEFAULTS里面找到了这个键对应的值:

python
'DEFAULT_PARSER_CLASSES': [  
    'rest_framework.parsers.JSONParser',  
    'rest_framework.parsers.FormParser',  
    'rest_framework.parsers.MultiPartParser'  
],

所以val的值为

python
[  
    'rest_framework.parsers.JSONParser',  
    'rest_framework.parsers.FormParser',  
    'rest_framework.parsers.MultiPartParser'  
]

在import_strings中也找到了"DEFAULT_PARSER_CLASSES",然后传给perform_impor函数:

python
def perform_import(val, setting_name):
    """
    If the given setting is a string import notation,
    then perform the necessary import or imports.
    """
    if val is None:
        return None
    elif isinstance(val, str):
        return import_from_string(val, setting_name)
    elif isinstance(val, (list, tuple)):
        return [import_from_string(item, setting_name) for item in val]
    return val

由于我们拿到的是列表,"elif isinstance(val, (list, tuple))"为True。通过列表生成式调用import_from_string函数并传入每一个item与attr,再来看下import_from_string干嘛的:

python
from django.utils.module_loading import import_string
def import_from_string(val, setting_name):
    """
    Attempt to import a class from a string representation.
    """
    try:
        return import_string(val)
    except ImportError as e:
        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
        raise ImportError(msg)

import_string是一个django.utils.module_loading内的工具包,这就直接描述这个工具函数的作用了:按照字符串路径,"."为分隔符,直接找到对应的类或者属性或者变量,主要看最后"."后面是什么,通过import_string就把它倒入,有赋值就赋值给变量。最终 val拿到的是JSONParser类,FormParser类,MultiPartParser类的列表。回到__getattr__里:

python
 # Cache the result  
    self._cached_attrs.add(attr)  
    setattr(self, attr, val)  
    return val

拿到对应类列表后,将attr存入self._cached_attrs字典中(这个初始化时默认空字典),相当于缓存到这个_cached_attrs里。由于__getattr__是外层APIView类里定义类属性parser_classes=api_settings.DEFAULT_PARSER_CLASSES,所以setattr(self, attr, val) 就会将获取到的attr,val注册到api_settings实例中,属性名就是DEFAULT_PARSER_CLASSES,值就是获取到对应的类列表。 回到get_parsers函数里,在函数中返回了对应类列表的实例对象列表:

python
def get_parsers(self):  
    """  
    Instantiates and returns the list of parsers that this view can use.    """    return [parser() for parser in self.parser_classes]

继续initialize_request解析:

python
def initialize_request(self, request, *args, **kwargs):  
    """  
    Returns the initial request object.    """    
    parser_context = self.get_parser_context(request)  
  
    return Request(  
        request,  
        parsers=self.get_parsers(),  
        authenticators=self.get_authenticators(),  
        negotiator=self.get_content_negotiator(),  
        parser_context=parser_context  
    )

self.get_authenticators()过程跟self.get_parsers()一样,这里直接给出结果:

python
[SessionAuthentication(),BasicAuthentication()]

self.get_content_negotiator()的结果: DefaultContentNegotiation()--->单个就不是列表了

进入 Request类定义,看Request实例初始化过程:

python
def __init__(self, request, parsers=None, authenticators=None,  
             negotiator=None, parser_context=None):  
    assert isinstance(request, HttpRequest), (  
        'The `request` argument must be an instance of '  
        '`django.http.HttpRequest`, not `{}.{}`.'        .format(request.__class__.__module__, request.__class__.__name__)  
    )  
  
    self._request = request  
    self.parsers = parsers or ()  
    self.authenticators = authenticators or ()  
    self.negotiator = negotiator or self._default_negotiator()  
    self.parser_context = parser_context  
    self._data = Empty  
    self._files = Empty  
    self._full_data = Empty  
    self._content_type = Empty  
    self._stream = Empty  
  
    if self.parser_context is None:  
        self.parser_context = {}  
    self.parser_context['request'] = self  
    self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET  
  
    force_user = getattr(request, '_force_auth_user', None)  
    force_token = getattr(request, '_force_auth_token', None)  
    if force_user is not None or force_token is not None:  
        forced_auth = ForcedAuthentication(force_user, force_token)  
        self.authenticators = (forced_auth,)

主要是做一些初始化,开头校验request是否HttpRequest类,原来WSGI的request注入给_request属性这里的self是Request实例,不是外面的APIView的实例。最终这个实例Request重新赋值给APIView实例的request属性,所以要在APIView里要拿到原来WSGI的request需要self.request._request

(3)设置sself.headers属性 这里就是设置了响应头,暂时存放到实例属性中

python
self.headers = self.default_response_headers 

构建响应头

python
@property  
def default_response_headers(self):  
    headers = {  
        'Allow': ', '.join(self.allowed_methods),  
    }  
    if len(self.renderer_classes) > 1:  
        headers['Vary'] = 'Accept'  
    return headers

调用了self.allowed_methods,APIView类里找到这个方法:

python
@property  
def allowed_methods(self):  
    """  
    Wrap Django's private `_allowed_methods` interface in a public property.    """    
  return self._allowed_methods()

又调用了self._allowed_methods(),这个在AIPIView类里没有定义,在View里有定义:

python
def _allowed_methods(self):  
    return [m.upper() for m in self.http_method_names if hasattr(self, m)]

循环self.http_method_names方法: 这里的self不是APIView类实例了,因为dispatch是在as_view里调用的,里面cls是最外层继承APIView类的类,但是基于MRO查找,我们分析时一般从继承类往上找 在APIView类中没有定义_allowed_methods,这个在View中找到了。所以self.http_method_names就是APIView类中定义的类属性http_method_names,如果方法在self(继承APIView类的子类实例)中有定义,将方法转为大些。最终default_response_headers里Allow值为最终实例对象定义的方法的的大写列表(方法得都在View定义的http_method_names允许中)

python
headers = {  
        'Allow': ', '.join(self.allowed_methods),  
    }  

根据查找self.get_parsers()一样的方法,self.renderer_classes值为

text
[  
    'rest_framework.renderers.JSONRenderer',  
    'rest_framework.renderers.BrowsableAPIRenderer',  
]

所以 headers['Vary'] = 'Accept'
响应头构造完成

(4)初始化与校验

python
self.initial(request, *args, **kwargs)

python

python
def initial(self, request, *args, **kwargs):  
    """  
    Runs anything that needs to occur prior to calling the method handler.    """    
    self.format_kwarg = self.get_format_suffix(**kwargs)  
  
    # Perform content negotiation and store the accepted info on the request  
    neg = self.perform_content_negotiation(request)  
    request.accepted_renderer, request.accepted_media_type = neg  
  
    # Determine the API version, if versioning is in use.  
    version, scheme = self.determine_version(request, *args, **kwargs)  
    request.version, request.versioning_scheme = version, scheme  
  
    # Ensure that the incoming request is permitted  
    self.perform_authentication(request)  
    self.check_permissions(request)  
    self.check_throttles(request)

这里就是在进行相关初始化及校验了 get_format_suffix代码:

python
class APIView(View):
    settings = api_settings
  
def get_format_suffix(self, **kwargs):  
    """  
    Determine if the request includes a '.json' style format suffix    """    if self.settings.FORMAT_SUFFIX_KWARG:  
        return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)

self.settings就是我们前面分析过的APISettings类实例,找一下有没有FORMAT_SUFFIX_KWARG,不在import_strings列表里,上面是None。self.perform_content_negotiation(request)是实现一个渲染选择器 实现API选择

python
  # Determine the API version, if versioning is in use.  
  version, scheme = self.determine_version(request, *args, **kwargs)  
  request.version, request.versioning_scheme = version, scheme 

self.perform_authentication(request) 分析:

python
def perform_authentication(self, request):  
    """  
    Perform authentication on the incoming request.  
    Note that if you override this and simply 'pass', then authentication    will instead be performed lazily, the first time either         `request.user` or `request.auth` is accessed.    
    """    
    request.user

在Request类中找user实现:

python
@property  
def user(self):  
    """  
    Returns the user associated with the current request, as authenticated    by the authentication classes provided to the request.    """   
  if not hasattr(self, '_user'):  
        with wrap_attributeerrors():  
            self._authenticate()  
  return self._user

刚开始request肯定是没有_user属性或者方法的, 通过with wrap_attributeerrors():防止认证代码中的代码拼写错误(AttributeError)被 Python 误判,确保控制台能打印出真实的报错原因,而不是抛出诡异的框架错误。self._authenticate() 被执行了,找到这样实现:

python
def _authenticate(self):  
    """  
    Attempt to authenticate the request using each authentication instance    in turn.    """    
  for authenticator in self.authenticators:  
        try:  
            user_auth_tuple = authenticator.authenticate(self)  
        except exceptions.APIException:  
            self._not_authenticated()  
            raise  
  
        if user_auth_tuple is not None:  
            self._authenticator = authenticator  
            self.user, self.auth = user_auth_tuple  
            return  
  
    self._not_authenticated()

在Request实例化时,将WSGI的request传给了__init__方法初始化,所以下面的request就是WSGI的request。

python
force_user = getattr(request, '_force_auth_user', None)  
force_token = getattr(request, '_force_auth_token', None)  
if force_user is not None or force_token is not None:  
    forced_auth = ForcedAuthentication(force_user, force_token)  
    self.authenticators = (forced_auth,)

假设我们请求里有_force_auth_user或者_force_auth_token,看下ForcedAuthentication的初始化过程:

python
class ForcedAuthentication:
    """
    This authentication class is used if the test client or request factory
    forcibly authenticated the request.
    """

    def __init__(self, force_user, force_token):
        self.force_user = force_user
        self.force_token = force_token

    def authenticate(self, request):
        return (self.force_user, self.force_token)

只是初始化了两个属性。self.authenticators = (forced_auth,)存放着元组,其中有一个ForcedAuthentication实例。定义了一个方法 authenticate

在_authenticate函数中循环了self.authenticators元组。返回(self.force_user, self.force_token)元组。_authenticate是被Request类实例化对象request调用的,所以下段代码,将ForcedAuthentication类实例赋值给request._authenticator。两个属性分别赋值给request.user, request.auth

python
def _authenticate(self):  
    """  
    Attempt to authenticate the request using each authentication instance    in turn.   
    """    
  for authenticator in self.authenticators:  
        try:  
            user_auth_tuple = authenticator.authenticate(self)  
        except exceptions.APIException:  
            self._not_authenticated()  
            raise  
  
        if user_auth_tuple is not None:  
            self._authenticator = authenticator  
            self.user, self.auth = user_auth_tuple  
            return  
  
self._not_authenticated()

由于外层request没有user,auth这里就触发了setattr方法:

python
@user.setter  
def user(self, value):  
    """  
    Sets the user on the current request. This is necessary to maintain    compatibility with django.contrib.auth where the user property is    set in the login and logout functions.  
    Note that we also set the user on Django's underlying \`HttpRequest\`    instance, ensuring that it is available to any middleware in the stack.    """    
    self._user = value  
    self._request.user = value

将获取到的force_user赋值个外层request的_user属性及原生WSGI的request的user属性(在Request类实例化时,会将WSGI的request注入给实例属性_request属性)

python
@auth.setter  
def auth(self, value):  
    """  
    Sets any non-user authentication information associated with the    request, such as an authentication token.    """    
  self._auth = value  
  self._request.auth = value

将获取到的force_auth赋值个外层request的_auth属性及原生WSGI的request的auth属性(在Request类实例化时,会将WSGI的request注入给实例属性_request属性)

同时也是实现了:

python
@property  
def user(self):  
    """  
    Returns the user associated with the current request, as authenticated    by the authentication classes provided to the request.    """    
    if not hasattr(self, '_user'):  
        with wrap_attributeerrors():  
            self._authenticate()  
    return self._user

@property  
def auth(self):  
    """  
    Returns any non-user authentication information associated with the    request, such as an authentication token.    """  
  if not hasattr(self, '_auth'):  
        with wrap_attributeerrors():  
            self._authenticate()  
    return self._auth

外层也可以request.user

python
def perform_authentication(self, request):  
    """  
    Perform authentication on the incoming request.  
    Note that if you override this and simply 'pass', then authentication    will instead be performed lazily, the first time either                `request.user` or `request.auth` is accessed.    
    """    
    request.user

perform_authentication就是在给request注入user,auth属性。从WSGI的request中获取

python
force_user = getattr(request, '_force_auth_user', None)  
force_token = getattr(request, '_force_auth_token', None) 

回到initial方法里继续:

python
self.check_permissions(request)

找到check_permissions代码:

python
def check_permissions(self, request):  
    """  
    Check if the request should be permitted.    Raises an appropriate exception if the request is not permitted.    """    
  for permission in  self.get_permissions():  
        if not permission.has_permission(request, self):  
            self.permission_denied(  
                request,  
                message=getattr(permission, 'message', None),  
                code=getattr(permission, 'code', None)  
            )

self.get_permissions()

python
def get_permissions(self):  
    """  
    Instantiates and returns the list of permissions that this view requires.    """   
    return [permission() for permission in self.permission_classes]

self.permission_classes就是我们在APIView类定义的类属性。其实类属性大多是后续我们自己要实现的,之所以通过api_settings初始化,怕你没有自己写就默认。包括permission_classes也是我们自己写的。默认的通过上面讲过的查找方法也是找得到的,假设我们自己写了permission_classes,那得按他们的规范来。毕竟我们写了是APIView在调用实现。循环我们的填写的permission_classes列表(其实元组也可以)。调用has_permission方法判断是否有权限。所以需要一个has_permission方法。在False情况下调用elf.permission_denied。这个就是直接返回response了。

回到initial方法里继续:

python
self.check_throttles(request)

这里就直接给结论了:检查当前请求是否高频恶意刷接口(防刷/限流),如果访问次数超标,立刻抛出 429 异常中断后续业务逻辑,并告诉前端“你需要等待多少秒后才能再次访问 到这里initial方法调用完成了,回到dispatch内: 接下来就是跟View里的dispatch,根据请求方法调用对应的视图方法,获得返回值

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

  # Get the appropriate handler method  
  if request.method.lower() in self.http_method_names:  
      handler = getattr(self, request.method.lower(),  
                        self.http_method_not_allowed)  
  else:  
      handler = self.http_method_not_allowed  

  response = handler(request, *args, **kwargs)

最后就是构造response并返回

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

finalize_response代码:

python
def finalize_response(self, request, response, *args, **kwargs):  
    """  
    Returns the final response object.    """    # Make the error obvious if a proper response is not returned  
    assert isinstance(response, HttpResponseBase), (  
        'Expected a `Response`, `HttpResponse` or `StreamingHttpResponse` '  
        'to be returned from the view, but received a \`%s\`'        % type(response)  
    )  
  
    if isinstance(response, Response):  
        if not getattr(request, 'accepted_renderer', None):  
            neg = self.perform_content_negotiation(request, force=True)  
            request.accepted_renderer, request.accepted_media_type = neg  
  
        response.accepted_renderer = request.accepted\_renderer  
        response.accepted_media_type = request.accepted_media_type  
        response.renderer_context = self.get_renderer_context()  
  
    # Add new vary headers to the response instead of overwriting.  
    vary_headers = self.headers.pop('Vary', None)  
    if vary_headers is not None:  
        patch_vary_headers(response, list(split_header_value(vary_headers)))  
  
    for key, value in self.headers.items():  
        response[key] = value  
  
    return response

里面校验返回的reponse是否合法类型对象,Response,HttpResponse,StreamingHttpResponse。所以在你写统一响应时继承一下其中一个。 如果是Response类型,额外添加一些内容。

到这里整个dispatch路由->执行视图->返回响应 完成

总结

  • 在APIView类中,继承了View类同时自己生声明了一些类属性。这些属性配置了默认值。当然也可以覆盖
  • as_view方法通过super()调用父类的实现。但是里面cls却是最终我们自己写的类(继承APIView)
  • APIview重写了dispatch方法。在这个方法中重写了request,也是不原来request包装,通过Request类实例化了一个request实例,并且将最先WSGI传过来的request注入给这个request._request。
  • 从最初的request中,初始化request.user,request.auth
  • 根据 permission_classes 列表校验权限,如果通过就调用视图函数得到response
  • 最后包装response并返回

如果我们直接继承APIView,除了实现了View类有的功能,还可以自定义permission_classes来权限校验了。