Please enable Javascript to view the contents

restframework 中的 Viewset 和 Serializer

 ·  ☕ 4 分钟

1. Django 中的 View Class

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

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

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

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

1
2
3
4
5
6
from django.conf.urls import url
from myapp import views

urlpatterns = [
    url(r'^fruit/', views.FruitView.as_view()),
]

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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 类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 类:

1
2
3
4
5
6
7
8
9
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,可以快速组合需要的操作。请看下面这个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 中,新增:

1
2
3
4
5
6
7
8
9
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 中,新增:

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
  • 序列化:对象 -> Json
1
2
3
serializer = CommentSerializer(comment)
serializer.data
{'email': '[email protected]', 'content': 'foo bar'}
  • 反序列化:String -> Json
1
2
3
4
5
serializer = CommentSerializer(data=data)
serializer.is_valid()
True
serializer.validated_data
{'content': 'foo bar', 'email': '[email protected]'

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

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

3.2 ModelSerializer

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

  • 自动产生基于模型的 fileds
  • 自动产生验证器,比如 unique_together 验证器
  • 默认包含 create 和 update 方法
  • 外键被映射为 PrimaryKeyRelatedField
1
2
3
4
5
6
from .models import Snippet

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

4. 参考


微信公众号
作者
微信公众号