djangorestframework源码分析1:generics中的view执行流程
发布日期:2021-07-25 13:04:47 浏览次数:18 分类:技术文章

本文共 21283 字,大约阅读时间需要 70 分钟。

djangorestframework源码分析

本文环境python3.5.2,djangorestframework (3.5.1)系列

djangorestframework源码分析-generics的执行流程

在使用Django框架的同时,一般会使用djangorestframework第三方库对Django的处理做扩展,以便更易于开发。本文就分析restframework的generics中的view的处理流程,示例代码如下;

... # 路由配置 ('^api/business_application/?$', TestAPI.as_view()), ... # 接口函数from rest_framework.generics import GenericAPIViewfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework.response import Responseclass TestAPI(GenericAPIView):    permission_classes = (IsAuthenticated,)    def post(self, request):        return Response({"detail": "ok"})

这里先配置了路由,然后设置了对应的TestAPI处理函数。

generics中的GenericAPIView处理过程

首先查看GenericAPIView类的定义,定义如下;

from django.views.generic import Viewclass GenericAPIView(views.APIView):    """    Base class for all other generic views.    """    # You'll need to either set these attributes,    # or override `get_queryset()`/`get_serializer_class()`.    # If you are overriding a view method, it is important that you call    # `get_queryset()` instead of accessing the `queryset` property directly,    # as `queryset` will get evaluated only once, and those results are cached    # for all subsequent requests.    queryset = None    serializer_class = None    # If you want to use object lookups other than pk, set 'lookup_field'.    # For more complex lookup requirements override `get_object()`.    lookup_field = 'pk'    lookup_url_kwarg = None    # The filter backend classes to use for queryset filtering    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS    # The style to use for queryset pagination.    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS    ...

该类继承自views.APIView类,继续查看该类,

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        # Mark the view as being included or excluded from schema generation.        exclude_from_schema = False        @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(APIView, cls).as_view(**initkwargs)            view.cls = cls            view.initkwargs = initkwargs            # Note: session based authentication is explicitly CSRF validated,            # all other authentication is CSRF exempt.            return csrf_exempt(view)

APIView类继承自Django中的View类,所以根据在Django处理流程中的分析可知,在调用TestAPI.as_view()处理的时候就调用APIView类的该方法,处理方法与Django中的View的处理流程相同,当路由匹配上后就调用了self.dispatch方法进行处理,此时就调用了APIView中的该方法;

# Note: Views are made CSRF exempt from within `as_view` as to prevent# accidental removal of this exemption in cases where `dispatch` needs to# be overridden.def dispatch(self, request, *args, **kwargs):    """    `.dispatch()` is pretty much the same as Django's regular dispatch,    but with extra hooks for startup, finalize, and exception handling.    """    self.args = args    self.kwargs = kwargs    request = self.initialize_request(request, *args, **kwargs)             # 处理request    self.request = 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                          # 如果没有找到则使用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

通过如上流程可知,权限的检查等处理过程都在self.initial函数中处理,继续查看该函数,

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)                                    # 检查request.user是否有该属性    self.check_permissions(request)                                         # 检查权限类    self.check_throttles(request)                                           # 检查是否超出了访问的频率

其中,最重要的就是做了相关的权限的检查,是否满足配置的权限类是否超过了访问频率;

def get_permissions(self):    """    Instantiates and returns the list of permissions that this view requires.    """    return [permission() for permission in self.permission_classes]     # 依次实例化权限类def get_throttles(self):    """    Instantiates and returns the list of throttles that this view uses.    """    return [throttle() for throttle in self.throttle_classes]           # 依次实例化节流类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):                # 执行实例的has_permission方法 判断是否有权限执行            self.permission_denied(                request, message=getattr(permission, 'message', None)            )def check_throttles(self, request):    """    Check if request should be throttled.    Raises an appropriate exception if the request is throttled.    """    for throttle in self.get_throttles():                               # 以此执行该限流类        if not throttle.allow_request(request, self):                   # 判断是否超过了访问频率            self.throttled(request, throttle.wait())

其中self.permission_classes和self.throttle_classes都是编写接口时配置的,默认为空,如果需要使用并配置则需要进行重写相关方法;

首先分析一下permission类的执行过程;

class BasePermission(object):    """    A base class from which all permission classes should inherit.    """    def has_permission(self, request, view):        """        Return `True` if permission is granted, `False` otherwise.        """        return True    def has_object_permission(self, request, view, obj):        """        Return `True` if permission is granted, `False` otherwise.        """        return Trueclass IsAuthenticated(BasePermission):    """    Allows access only to authenticated users.    """    def has_permission(self, request, view):        return request.user and is_authenticated(request.user)

此时就是执行了IsAuthenticated实例的has_permission方法,判断是否有user属性并且user是否登陆了,如果需要添加自定义权限可以重写该方法就好。

继续查看Throttle类,以AnonRateThrottle类说明;

class BaseThrottle(object):    """    Rate throttling of requests.    """    def allow_request(self, request, view):        """        Return `True` if the request should be allowed, `False` otherwise.        """        raise NotImplementedError('.allow_request() must be overridden')    def get_ident(self, request):        """        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR        if present and number of proxies is > 0. If not use all of        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.        """        xff = request.META.get('HTTP_X_FORWARDED_FOR')        remote_addr = request.META.get('REMOTE_ADDR')        num_proxies = api_settings.NUM_PROXIES        if num_proxies is not None:            if num_proxies == 0 or xff is None:                return remote_addr            addrs = xff.split(',')            client_addr = addrs[-min(num_proxies, len(addrs))]            return client_addr.strip()        return ''.join(xff.split()) if xff else remote_addr    def wait(self):        """        Optionally, return a recommended number of seconds to wait before        the next request.        """        return Noneclass SimpleRateThrottle(BaseThrottle):    """    A simple cache implementation, that only requires `.get_cache_key()`    to be overridden.    The rate (requests / seconds) is set by a `throttle` attribute on the View    class.  The attribute is a string of the form 'number_of_requests/period'.    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')    Previous request information used for throttling is stored in the cache.    """    cache = default_cache                                       timer = time.time    cache_format = 'throttle_%(scope)s_%(ident)s'                               # 渲染的缓存值    scope = None    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES                        # 获取配置的频率    def __init__(self):        if not getattr(self, 'rate', None):                                     # 如果没有改属性            self.rate = self.get_rate()                                         # 获取并设置该属性        self.num_requests, self.duration = self.parse_rate(self.rate)           # 解析次数,和时间    def get_cache_key(self, request, view):        """        Should return a unique cache-key which can be used for throttling.        Must be overridden.        May return `None` if the request should not be throttled.        """        raise NotImplementedError('.get_cache_key() must be overridden')    def get_rate(self):        """        Determine the string representation of the allowed request rate.        """        if not getattr(self, 'scope', None):            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %                   self.__class__.__name__)            raise ImproperlyConfigured(msg)        try:            return self.THROTTLE_RATES[self.scope]        except KeyError:            msg = "No default throttle rate set for '%s' scope" % self.scope            raise ImproperlyConfigured(msg)    def parse_rate(self, rate):        """        Given the request rate string, return a two tuple of:        
,
""" if rate is None: return (None, None) num, period = rate.split('/') # 解析时间 num_requests = int(num) # 转换数字 duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] # 获取对应的时间 return (num_requests, duration) # 返回 def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ if self.rate is None: # 如果没有rate属性直接返回 return True self.key = self.get_cache_key(request, view) # 获取缓存的key if self.key is None: # 如果没有则返回 return True self.history = self.cache.get(self.key, []) # 获取history self.now = self.timer() # 获取当前时间 # Drop any requests from the history which have now passed the # throttle duration while self.history and self.history[-1] <= self.now - self.duration: # 删除掉已经过期的时间 self.history.pop() if len(self.history) >= self.num_requests: # 如果长度大于设置的数字则失败 return self.throttle_failure() return self.throttle_success() # 否则就是成功 def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) # 将当前的时间加入到列表中的第一位 self.cache.set(self.key, self.history, self.duration) # 设置到缓存中 return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) # 计算下次间隔时间 else: remaining_duration = self.duration available_requests = self.num_requests - len(self.history) + 1 if available_requests <= 0: return None return remaining_duration / float(available_requests)class AnonRateThrottle(SimpleRateThrottle): """ Limits the rate of API calls that may be made by a anonymous users. The IP address of the request will be used as the unique cache key. """ scope = 'anon' def get_cache_key(self, request, view): if is_authenticated(request.user): # 如果已经认证则不检查 return None # Only throttle unauthenticated requests. return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) }

主要就是通过一个列表检查访问时间并通过缓存缓存一个列表来进行判断,检查该列表的长度是否和设置的访问次数的大小来判断是否能够继续访问。

当处理完成后,就会调用view的相应方法去处理该请求,请求完成后就返回给前端。

在rest_framework中GenericAPIView继承自APIView,该类主要就是方便了序列表渲染和分页,是CreateAPIView,ListAPIView和RetrieveUpdateAPIView类的基类。

class GenericAPIView(views.APIView):    """    Base class for all other generic views.    """    # You'll need to either set these attributes,    # or override `get_queryset()`/`get_serializer_class()`.    # If you are overriding a view method, it is important that you call    # `get_queryset()` instead of accessing the `queryset` property directly,    # as `queryset` will get evaluated only once, and those results are cached    # for all subsequent requests.    queryset = None                                                             serializer_class = None                                                         # 序列化类    # If you want to use object lookups other than pk, set 'lookup_field'.    # For more complex lookup requirements override `get_object()`.    lookup_field = 'pk'    lookup_url_kwarg = None                                                         # url中的key字段名称    # The filter backend classes to use for queryset filtering    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS                          # 配置的过滤模板    # The style to use for queryset pagination.    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS                        # 分页类    def get_queryset(self):        """        Get the list of items for this view.        This must be an iterable, and may be a queryset.        Defaults to using `self.queryset`.        This method should always be used rather than accessing `self.queryset`        directly, as `self.queryset` gets evaluated only once, and those results        are cached for all subsequent requests.        You may want to override this if you need to provide different        querysets depending on the incoming request.        (Eg. return a list of items that is specific to the user)        """        assert self.queryset is not None, (            "'%s' should either include a `queryset` attribute, "            "or override the `get_queryset()` method."            % self.__class__.__name__        )        queryset = self.queryset        if isinstance(queryset, QuerySet):            # Ensure queryset is re-evaluated on each request.            queryset = queryset.all()        return queryset                                                         # 获取配置的queryset    def get_object(self):        """        Returns the object the view is displaying.        You may want to override this if you need to provide non-standard        queryset lookups.  Eg if objects are referenced using multiple        keyword arguments in the url conf.        """        queryset = self.filter_queryset(self.get_queryset())        # Perform the lookup filtering.        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field           # 通过url获取配置去获取条件        assert lookup_url_kwarg in self.kwargs, (            'Expected view %s to be called with a URL keyword argument '            'named "%s". Fix your URL conf, or set the `.lookup_field` '            'attribute on the view correctly.' %            (self.__class__.__name__, lookup_url_kwarg)        )        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}        obj = get_object_or_404(queryset, **filter_kwargs)                      # 获取对应Model的实例        # May raise a permission denied        self.check_object_permissions(self.request, obj)                        # 检查是否有该实例的权限        return obj                                                              # 返回    def get_serializer(self, *args, **kwargs):        """        Return the serializer instance that should be used for validating and        deserializing input, and for serializing output.        """        serializer_class = self.get_serializer_class()        kwargs['context'] = self.get_serializer_context()        return serializer_class(*args, **kwargs)                                # 实例化序列化类    def get_serializer_class(self):        """        Return the class to use for the serializer.        Defaults to using `self.serializer_class`.        You may want to override this if you need to provide different        serializations depending on the incoming request.        (Eg. admins get full serialization, others get basic serialization)        """        assert self.serializer_class is not None, (            "'%s' should either include a `serializer_class` attribute, "            "or override the `get_serializer_class()` method."            % self.__class__.__name__        )        return self.serializer_class                                                # 获取序列化类    def get_serializer_context(self):        """        Extra context provided to the serializer class.        """        return {            'request': self.request,            'format': self.format_kwarg,            'view': self        }                                                                           # 获取序列化的context    def filter_queryset(self, queryset):        """        Given a queryset, filter it with whichever filter backend is in use.        You are unlikely to want to override this method, although you may need        to call it either from a list view, or from a custom `get_object`        method if you want to apply the configured filtering backend to the        default queryset.        """        for backend in list(self.filter_backends):                                  # 过滤模板,根据条件过滤            queryset = backend().filter_queryset(self.request, queryset, self)        return queryset    @property    def paginator(self):        """        The paginator instance associated with the view, or `None`.        """        if not hasattr(self, '_paginator'):                                 # 判断是否有_paginator属性            if self.pagination_class is None:                self._paginator = None            else:                self._paginator = self.pagination_class()                   # 为空则实例化分页类        return self._paginator    def paginate_queryset(self, queryset):        """        Return a single page of results, or `None` if pagination is disabled.        """        if self.paginator is None:            return None        return self.paginator.paginate_queryset(queryset, self.request, view=self)    def get_paginated_response(self, data):        """        Return a paginated style `Response` object for the given output data.        """        assert self.paginator is not None        return self.paginator.get_paginated_response(data)

该类所有的方法都为后面的RetrieveUpdateAPIView等方法进行了扩展。

总结

本文内容相对简单,大致描述了djangorestframework中的view的处理流程,其中进行了相关的权限检查和访问频率的检查,然后讲述了generics类的基本构成,该类提供的方法都是其他APIView的实现其功能的基础。

转载地址:https://blog.csdn.net/qq_33339479/article/details/82718216 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:djangorestframework源码分析2:serializer序列化数据的执行流程
下一篇:Django源码分析10:makemigrations命令概述

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月04日 05时09分40秒