restframework(一)

restframework

restframework是django对restful架构扩展的第三方库

restful架构

主要原则

  • 网络上的所有事物都被抽象成为资源
  • 每个资源都有唯一的资源标识符
  • 同一个资源具有多种表现形式(xml, json等)
  • 对资源的各种操作不会修改资源标识符
  • 所有操作均是无状态的
  • 符合REST原则的架构

什么是restful

对应的中文是rest式的;Restful web service是一种常见的rest的应用,是遵守了rest风格的web服务,rest式的web服务是一种ROA(面向资源架构)

如何使用

http方法 资源操作 幂等 安全
GET SELECT
POST INSERT
PUT UPDATE
DELETE DELETE

注释: 幂等性:对同一个接口的多次访问,得到的资源状态是相同的。

安全性:对该REST 接口访问,不会使服务端资源状态发生改变

restframework

基础使用

urls.py

urlpatterns = [
    url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
    url(r'^books/(?P\d+)$', views.BookViewSet.as_view({
                'get': 'retrieve',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            }),name="book_detail"),
]

view.py

class BookSerializers(serializers.ModelSerializer):
      class Meta:
          model=Book
          fields="__all__"

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializers

补充一个urls.py的另一种写法

from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'books', views.BookViewSet)
urlpatterns = [
     url(r'', include(router.urls)),
]

其实这种写法是官网给出的方法,第一种方法仅便于理解其执行了什么。

源码剖析

其实很容易看出来 restframework 是通过 CBV 的形式来实现的。 CBV 该如何入手呢?

思考:

django的执行顺序:

请求从客户端发出: 通过Django的WSGI 依次经过中间件 process_request 到 拿到视图 又通过process_view 到 url路由分发,到视图 在视图中获取数据渲染页面 又返回到 process_response 到 WSGI 最终返回给客户端
我们并没有设计到中间件部分,所以跳过中间件内容 直接到url的路由分发
所以可以得出应该从url路由分发开始入手

基础流程

我们先看一些简单的方式

urlpatterns = [
    url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
    url(r'^books/(?P\d+)$', views.BookViewSet.as_view({
                'get': 'retrieve',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            }),name="book_detail"),
]

as_view()方法

如果找一个方法 一定要记住self是谁,这个方法是谁调用的,子类没有找父类

我们找到了自己写的这个类,我们并没有写as_view()方法 所以继续找它的继承类 ModelViewSet

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializers

ModelViewSet

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

没有发现as_view() 继续找

GenericViewSet

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass

ViewSetMinxin

终于我们找到了 as_view()方法 大体看一下

@classonlymethod
def as_view(cls, actions=None, **initkwargs):
    ...
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        # We also store the mapping of request methods to actions,
        # so that we can later set the action attribute.
        # eg. `self.action = 'list'` on an incoming GET request.
        self.action_map = actions

        # Bind methods to actions
        # This is the bit that's different to a standard view
        for method, action in actions.items():
            handler = getattr(self, action)
            setattr(self, method, handler)

        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get

        self.request = request
        self.args = args
        self.kwargs = kwargs

        # And continue as usual
        return self.dispatch(request, *args, **kwargs)

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())

    # We need to set these on the view function, so that breadcrumb
    # generation can pick out these bits of information from a
    # resolved URL.
    view.cls = cls
    view.initkwargs = initkwargs
    view.suffix = initkwargs.get('suffix', None)
    view.actions = actions
    return csrf_exempt(view)

其实找as_view()方法 我们主要看的内容是view()方法

def view(request, *args, **kwargs):
        self = cls(**initkwargs)

        self.action_map = actions
        # 这里注意这个actions是什么 def as_view(cls, actions=None, **initkwargs):
         # actions = {"get":"list","post":"create"} 

        for method, action in actions.items():
            handler = getattr(self, action) 
            # handler = self.list ; self.create ===> 这里是将 getattr 写开后的样子 便于理解
            # mixins.CreateModelMixin,
            # mixins.RetrieveModelMixin,
            # mixins.UpdateModelMixin,
            # mixins.DestroyModelMixin,
            # mixins.ListModelMixin,
            # 最终可以从上面的这5个类中找到对应的方法, 可以自己看下
            setattr(self, method, handler)
            # setattr ===> self.get = list; self.post = create

        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get

        self.request = request
        self.args = args
        self.kwargs = kwargs

        # And continue as usual
        return self.dispatch(request, *args, **kwargs)  # 最终调用了self.dispatch()方法, 接下来找dispatch方法

ModelViewSet->GenericViewSet->GenericAPIView->APIView

APIView dispatch()

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 做了一步封装, 后面说
    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)
            # request.method.lower() ==> [get, post, put, delete...请求方式]
            # 请注意!!!  这里的handler是什么
            # 在之前的view方法中 => self.get self.post 的值已经被替换成了 list create等方法

        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)
        # response 就是self.list()的返回结果。具体self.list是怎么执行的 后面会说

    except Exception as exc:
        response = self.handle_exception(exc)

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

ok到这里,已经拿到了response 思考一个问题:dispatch 是谁调用的, 最终返回到哪里

dispatch 是 view调用的 view是 as_view调用的 as_view 就是我们url路由分配的时候调用的方法

基础功能

刚刚有个self.list没说,这个list是什么呢?

要记住self是谁

我们来找self.list()

BookViewSet开始找,没找到就找父类

ModelViewSet

看这个类的注释: 这是restframework 给出的注释,发现list方法了么

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

ListModelMixin

在说list之前先记住我们自己写的BookViewSet类

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializers
class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # 这里的queryset 其实就是我们在BookViewSet中写的那个queryset ,经过了一系列的骚操作原封不动的传了过来

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        # 从page = 开始 这里是分页的内容, 后面会说

        serializer = self.get_serializer(queryset, many=True)
        """
            def get_serializer(self, *args, **kwargs):
                serializer_class = self.get_serializer_class()

                    # get_serializer_class() == > return self.serializer_class == > 也就是我们自己写的serializer_class

                kwargs['context'] = self.get_serializer_context()
                return serializer_class(*args, **kwargs)  # 最终返回一个BookSerializers 的实例对象

        serializer = BookSerializers(*args, **kwargs)
        """
        return Response(serializer.data) # 返回了Response(serializer.data) 并最终给了as_view方法

其他的create 、delete方法都类似,这里就不进行赘述了

另一种封装

还记得url的另一种官方文档中的写法么?

其实仅仅是一种封装,使用起来更加简便

from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'books', views.BookViewSet)
urlpatterns = [
     url(r'', include(router.urls)),
]

首先我们先从DefaultRouter入手

DefaultRouter

class DefaultRouter(SimpleRouter):
    """
    The default router extends the SimpleRouter, but also adds in a default
    API root view, and adds format suffix patterns to the URLs.
    """
    include_root_view = True
    include_format_suffixes = True
    root_view_name = 'api-root'
    default_schema_renderers = None
    APIRootView = APIRootView
    APISchemaView = SchemaView
    SchemaGenerator = SchemaGenerator

    def __init__(self, *args, **kwargs):
        if 'root_renderers' in kwargs:
            self.root_renderers = kwargs.pop('root_renderers')
        else:
            self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
            # self.root_renderers = [
                #'rest_framework.renderers.JSONRenderer',
                # 'rest_framework.renderers.BrowsableAPIRenderer']
            # 这个就是对restframework的一个初始化, 使用restframework时, 会有2种模式 一种是通过 浏览器访问的时候会得到一个html 页面  另一种是访问接口 得到一个Json数据    
        super(DefaultRouter, self).__init__(*args, **kwargs) # 执行了父类的 __init__
    ...

SimpleRoouter

def __init__(self, trailing_slash=True):
    self.trailing_slash = trailing_slash and '/' or ''
    # self.trailing_slash = '/'
    super(SimpleRouter, self).__init__() # 执行了父类的 __init__

BaseRouter

class BaseRouter(object):
    def __init__(self):
        self.registry = [] # 初始化了一个东西

继续往下看

router.register(r'books', views.BookViewSet)

找register方法

class BaseRouter(object):
    ...
    def register(self, prefix, viewset, base_name=None):
        if base_name is None:
            base_name = self.get_default_base_name(viewset)
            """
                    def get_default_base_name(self, viewset):
                        # viewset 是 views.BookViewSet 
                        queryset = getattr(viewset, 'queryset', None)
                       # queryset = Book.objects.all()
                        assert queryset is not None, '`base_name` argument not specified, and could ' \
                            'not automatically determine the name from the viewset, as ' \
                            'it does not have a `.queryset` attribute.'

                        return queryset.model._meta.object_name.lower()
                        # 返回了model名字的小写
            """
            # base_name = books
        self.registry.append((prefix, viewset, base_name)) # 然后将(r'books', BookViewSet, 'books') 添加到self.registry

继续走

urlpatterns = [
     url(r'', include(router.urls)),
]
# include 分发url 之类的是django自己做的,所以这里不作考虑
# 我们需要看的是 router.urls

DefaultRouter—>SimpleRouter->BaseRouter

BaseRouter

@property
def urls(self):
    if not hasattr(self, '_urls'):
        self._urls = self.get_urls()
    return self._urls 

其实就是执行了get_urls方法

DefaultRouter

# 这里执行的get_urls() 是 DefaultRouter 类下的方法
def get_urls(self):
    urls = super(DefaultRouter, self).get_urls()
    # 执行父类的get_urls() 父类的代码紧接下面
    if self.include_root_view:
        view = self.get_api_root_view(api_urls=urls)
        root_url = url(r'^$', view, name=self.root_view_name)
        urls.append(root_url)

        if self.include_format_suffixes:
            urls = format_suffix_patterns(urls)

            return urls

SimpleRouter

def get_urls(self):
    """
    Use the registered viewsets to generate a list of URL patterns.
    """
    ret = []

    for prefix, viewset, basename in self.registry:
        # 还记得 self.registry 么
        # (r'books', BookViewSet, 'books')
        lookup = self.get_lookup_regex(viewset)
        # lookup 是一个字符串的拼接样式
        routes = self.get_routes(viewset)

        # 开始拼接url并保存到ret中
        for route in routes:

            # Only actions which actually exist on the viewset will be bound
            mapping = self.get_method_map(viewset, route.mapping)
            if not mapping:
                continue

            # Build the url pattern
            regex = route.url.format(
                prefix=prefix,
                lookup=lookup,
                trailing_slash=self.trailing_slash
            )

            # If there is no prefix, the first part of the url is probably
            #   controlled by project's urls.py and the router is in an app,
            #   so a slash in the beginning will (A) cause Django to give
            #   warnings and (B) generate URLS that will require using '//'.
            if not prefix and regex[:2] == '^/':
                regex = '^' + regex[2:]

            initkwargs = route.initkwargs.copy()
            initkwargs.update({
                'basename': basename,
            })

            view = viewset.as_view(mapping, **initkwargs)
            name = route.name.format(basename=basename)
            ret.append(url(regex, view, name=name))
        # 最终ret = [, [^/.]+)/$>]
    return ret
# 所以urls方法 得到的 [, [^/.]+)/$>] 这个列表 并放到 include中被执行

你可能感兴趣的:(django)