1. 程式人生 > >djangorestframework原始碼分析1:generics中的view執行流程

djangorestframework原始碼分析1:generics中的view執行流程

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 GenericAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

class TestAPI(GenericAPIView):
    permission_classes = (IsAuthenticated,)

    def post(self, request):
        return Response({"detail": "ok"})

這裡先配置了路由,然後設定了對應的TestAPI處理函式。

generics中的GenericAPIView處理過程

首先檢視GenericAPIView類的定義,定義如下;

from django.views.generic import View

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

    # 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 True


class 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 None


class 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:
        <allowed number of requests>, <period of time in seconds>
        """
        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的實現其功能的基礎。