目录

    1. Django 中的 View Class

    首先回忆一下,Django 对请求的处理逻辑。收到一次请求之后,Django 会生成一个 WSGIHandler 类型的 handler,由 handler 控制整个处理流程。

    那么,请求的 URL 与 View 是如何关联的呢?

    Django 首先根据 ROOT_URLCONF 的配置加载 URLconf,按顺序逐个匹配 URLconf 的 URLpatterns,匹配即停止。 然后,将 HttpRequest 对象作为参数,调用 URLpatterns 的 view 函数。

    再来看看,类视图路由的配置方法:

    from django.conf.urls import url
    from myapp import views
    
    urlpatterns = [
        url(r'^fruit/', views.FruitView.as_view()),
    ]
    

    django/views/generic/base.py 定义了 View 类:

    class View(object):
        @classonlymethod
        def as_view(cls, **initkwargs):
            ...
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
            ...
            return view
    
        def dispatch(self, request, *args, **kwargs):
            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
            return handler(request, *args, **kwargs)
    

    通过调用 as_view() 方法,将返回 dispatch() 函数给路由函数。

    dispatch() 函数,会根据请求的方法,在 View 类中调用与请求方法同名的函数。

    这样,处理逻辑就非常清楚了。相较于函数视图,类视图多了 dispatch 这一步骤,可以理解为二次路由。

    2. restframework 中的 View Class

    restframwork 继承 Django View,实现了三个层级的 View,分别是 APIView、GenericAPIView、GenericViewSet。下面,我们来逐个分析。

    2.1 APIView

    rest_framewor/views.py 定义了 APIView 类:

    from rest_framework.request import Request
    
    class APIView(View):
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        ...
        def initial(self, request, *args, **kwargs):
            self.perform_authentication(request)
            self.check_permissions(request)
            self.check_throttles(request)
        def dispatch(self, request, *args, **kwargs):
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            try:
                self.initial(request, *args, **kwargs)
                handler = getattr(self, request.method.lower(),
                                      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
    

    在 initial 函数中,会校验接口的版本、认证授权、权限验证、访问频率等。同时,restframework 对 request 进行了再次封装。

    在使用上 restframework 的 APIView 与 Django View 差别不大,但是对 View 提供的功能进行了增强。

    2.2 GenericAPIView

    rest_framework/generics.py 定义了 GenericAPIView 类:

    class GenericAPIView(views.APIView):
        queryset = None
        serializer_class = None
        lookup_field = 'pk'
        lookup_url_kwarg = None
        filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
        pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
        def get_queryset(self):
            ...
    

    GenericAPIView 继承自 APIView,新增了一些类方法:

    • get_queryset,获取 QuerySet
    • get_object,获取单条数据
    • get_serializer,获取序列化后的数据
    • get_serializer_class,获取需要序列化的model类
    • get_serializer_context,获取序列化的数据,定义了某种格式的字典
    • paginator,分页器

    除了直接继承 GenericAPIView 实现 View Class,restframework 还提供了大量 mixins。通过继承 mixins,可以快速组合需要的操作。请看下面这个例子:

    from rest_framework import mixins
    from rest_framework import generics
    class BookView(mixins.ListModelMixin,
                   mixins.CreateModelMixin,
                   generics.GenericAPIView):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializers
    
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    

    更多 mixins:

    mixins功能对应的 HTTP 请求方法
    mixins.ListModelMixin定义 list 方法,返回一个 querylist 的列表GET
    mixins.CreateModelMixin定义 create 方法,创建一个示例POST
    mixins.RetrieveModelMixin定义 retrieve 方法,返回一个具体的示例GET
    mixins.UpdateModelMixin定义 update 方法,对某个实例进行更新PUT/PATCH
    mixins.DestroyModelMixin定义 delete 方法,删除某个实例DELETE

    2.4 GenericViewSet

    GenericAPIView 提供了增、删、改、查等基本操作,可以用于快速开发 API。但,如果需要对这些操作进行组合,实现复杂的业务逻辑时,可能就不那么方便了。 幸运的是,restframework 提供了 GenericViewSet,用于更复杂的业务逻辑处理。

    rest_framework/viewsets.py 中定义了 GenericAPIView 类:

    class ViewSetMixin(object):
        @classonlymethod
        def as_view(cls, actions=None, **initkwargs):
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                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
                return self.dispatch(request, *args, **kwargs)
            view.cls = cls
            view.initkwargs = initkwargs
            view.suffix = initkwargs.get('suffix', None)
            view.actions = actions
            return csrf_exempt(view)
    class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
        pass
    

    内置的 ViewSet 有 5 种,分别是

    • ViewSetMixin
    • ViewSet
    • GenericViewSet
    • ReadOnlyModelViewSet
    • ModelViewSet

    有两种方式,可以将 ViewSet 绑定到指定的 URL 上:

    1. 手动将 ViewSet 绑定到 URL

    在 urls.py 中,新增:

    from .views import SnippetViewSet
    
    snippet_list = SnippetViewSet.as_view({
        'get': 'list',
        'post': 'create'
    })
    
    urlpatterns = [
        url(r'^snippets/$', snippet_list, name='snippet-list'),
    

    snippets/ 路径下,get 请求方法,分发到 list 函数;post 请求方法,分发到 create 方法。

    1. 使用 restframework 提供的 routers

    在 urls.py 中,新增:

    from rest_framework.routers import DefaultRouter
    
    from .views import SnippetViewSet
    
    router = DefaultRouter()
    router.register(r'snippets', SnippetViewSet)
    urlpatterns = [
        url(r'^', include(router.urls)),
    ]
    

    3. Serializer

    restframework 中的 Serializer 和 ModelSerializer 与 Django 的 Form 和 ModelForm 类似。

    3.1 Serializer

    from rest_framework import serializers
    
    class CommentSerializer(serializers.Serializer):
        email = serializers.EmailField()
        content = serializers.CharField(max_length=200)
    
    • 序列化:对象 -> Json
    $ serializer = CommentSerializer(comment)
    $ serializer.data
    {'email': 'mail@example.com', 'content': 'foo bar'}
    
    • 反序列化:String -> Json
    $ serializer = CommentSerializer(data=data)
    $ serializer.is_valid()
    True
    $ serializer.validated_data
    {'content': 'foo bar', 'email': 'mail@example.com'
    

    还可以用于验证,提交的数据是否符合合法:

    serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            pass
    

    3.2 ModelSerializer

    相较与上面一个字段一个字段的声明,ModelSerializer 可以快速建立相关模型的 serializer 模型。提供如下特性:

    • 自动产生基于模型的 fileds
    • 自动产生验证器,比如 unique_together 验证器
    • 默认包含 create 和 update 方法
    • 外键被映射为 PrimaryKeyRelatedField
    from .models import Snippet
    
    class SnippetSerializer(serializers.ModelSerializer):
        class Meta:
            model = Snippet
            fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
    

    4. 参考