Django REST Framework 是什么
如果你打算使用 Django 搭建一个 RESTful API 后端,你完全有必要学习 Django REST Framework。
Django REST Framework 提供了 Serializers、APIView、GeneticAPIView、ViewSets、权限管理、搜索、分页等功能。这些功能、特性可以全部加入我们的 RESTful 后端,也可以选一部分加入。
如果你和我一样,第一次接触后端,尚不了解 RESTful 后端的组成、功能,可能也会对上面的这些概念懵圈。
用通俗的话来说,RESTful 后端开发过程中,包含了相当多的重复元素,比如:
- 将数据模型(Django 中特指我们编写的 models.Model 类)变为 json 字符串发送给前端,这个变的过程称为
序列化
; - RESTful API 应该对
GET /activities/
这类请求提供获取活动列表的功能,对POST /activities/
提供创建活动的功能; - 对于获取活动列表的功能,还应当具有搜索和分页的功能;
- RESTful API 应该对
GET /activities/1/
这类请求提供获取 id 为 1 的活动信息的功能,对PUT /activities/1/
和PATCH /activities/1/
提供修改 id 为 1 的活动信息的功能,对DELETE /activities/1/
提供删除 id 为 1 的活动的功能 - 当然,还需要判定用户是否有权限获取、修改
上述步骤,使用纯 Django 也可以完成。而 Django REST Framework 所完成的,就是将具体的、重复的、步骤抽象化、简短化:将 json 到数据模型抽象化为序列器 Serializer
类,借助 Serializer
类编写 序列化
的过程可以最短缩减到三行;将提供了通用 RESTful 功能(如提供列表、创建对象、修改、删除)的一些 Django 视图 View
抽象化为 GeneticAPIView
,读者在后面可以看到,对不同的 HTTP 方法提供不同功能的代码借助 GeneticAPIView
可以最短缩减到三行,搜索分页加起来也可以不超过十行;甚至的甚至,相同操作的不同 Views(如,需要对 /activities/
和 /users/
提供相同的列表、创建、获取、修改、删除方法)甚至可以用一个 ViewSet
进行描述。
当然,这种方法适合描述 RESTful API 中大部分通用功能,如果涉及到更细化的操作,如 signup
和 login
,就没法用到 ViewSets 这类操作了。而在过于抽象的代码上,如果需求发生了少许更改,也可能导致较大的代码改动,如对于 /activities/
和 /users/
,原本二者提供的功能相同,使用一个 ViewSet
就可以描述,突然要求对 /users/
中的注册过程添加一个验证码,都会导致这部分的代码重写。
综上所属,抽象化有其优点也有其缺点,在实际编码过程中,并不一定使用到 ViewSets
,我的项目中最抽象也只用到了 GeneticAPIView
。
Django 入门
Django REST Framework 教程中也包含了必要的 Django 知识,不过我仍然建议简单看一下 Django 入门教程;以及,很多时候也会去查 Django 的文档。
Django REST Framework 官方教程
在序列化后修改字段
DRF 的 GeneticAPIView 写起来真的很爽,把 Models 和 Serializer 写好以后,GeneticAPIView 只需要几行就能写完,完成搜索、分页、序列化、返回 Response。但是,不可避免的是,某些时候想要传回更多的字段,或者由于权限问题隐藏某些字段。这个时候,我们就需要自己改写一部分 GeneticAPIView。
在 view 中的 serializer_data 中添加字段
需求是这样的:原本的 login 函数完成了接收 username
和 password
并验证,正确后将该用户的信息用 UserSerializer
序列化后返回:
1 | def login(request: WSGIRequest) -> Response: |
由于某些原因,我需要把 csrftoken
加入到返回的 JSON 中。csrftoken
的获取方法是 csrf.get_token(request)
。如何将 csrftoken
加入序列化字段呢?
最容易想到,但最麻烦且最不美观的方法是修改 UserSerializer
。有没有其他方法呢?
在最后一行打断点然后调试,执行到这里时看一眼 serializer.data
的类型,是 OrderedDict
。OrderedDict
就很好办了,在 Response 之前修改一下,添加一个字段就可以了。
1 | def login(request: WSGIRequest) -> Response: |
在 GeneticAPIView 的 serializer_data 中修改字段
这个看起来就要麻烦一点了,毕竟本身 GeneticAPIView 部分是一个函数都没有写。
1 | class UserListView(ListAPIView): |
这里的需求是:为了保护用户隐私,对于非管理员用户,不让其获取用户的 username
和 student_id
,将返回的 username
改为 ***
、student_id
改为前四位(代表入学年份)。
这里就有两种思路了:修改每一个 View 的处理过程(不仅仅是这个 View 需要保护隐私,其他 View 也应当保护隐私);或者直接修改 Serializer
的序列化过程。
思路 1:修改 View 的过程
我们可以考虑类似于上面添加 csrftoken
字段的思路。读 ListAPIView
的源码后,可以知道,get
方法调用了 list
方法,而 list
中做了查询、分页、序列化、包装成分页形式再返回的操作。
1 | class ListAPIView(mixins.ListModelMixin, |
我们只需要把官方的 list 方法复制下来,然后改写一下,在序列化以后判断是否是管理员,对于非管理员,替换每个 username
和 student_id
就可以了。
注意到官方给的 list
用 if 判断了是否用到分页功能,针对不同情况作了处理。我们已经用到了分页,所以可以把 if 删掉。
1 | class UserListView(ListAPIView): |
这种方法的缺点就是需要对每一个 View 进行修改,而且在 List 的结果中,非管理员也不能获得自己的详细信息了。
思路 2:修改 Serializer
上面的方法需要对每个 View 进行修改,并且新增的 View 如果忘记修改了,还会导致数据泄露。有没有从 Model 或 Serializer 下手的方法呢?
这种方案要解决两个问题,一是序列化的过程中并没有 request
,没有 request
我们就没法判断当前用户是否是管理员,所以需要通过什么方法传进去;二是需要自定义序列化的过程。
对于第一个问题,DRF 文档 中提到,可以在序列化时提供 context
。
1 | serializer = AccountSerializer(account, context={'request': request}) |
在 context
中提供 user 信息,我们就可以判断用户的身份了。更好的是,GeneticAPIView 在序列化时,提供了默认的 context
:
1 | class GenericAPIView(views.APIView): |
这下我们直接在 Serializer 里用就行了,GeneticAPIView 一行都不用修改。
对于第二个问题,查询资料发现,DRF 序列化的函数为 to_representation
,其定义如下:
1 | class Serializer(BaseSerializer, metaclass=SerializerMetaclass): |
我们只需要重写它即可,把上面的代码复制下来,然后在 return 之前判断用户身份,替换 ret
的 student_id
和 username
字段。
不对,连复制都不需要,我们只需要调用父类的 to_representation()
,然后加两行就可以了!
1 | def to_representation(self, instance): |
REST API 标准
开发 REST 的工具有了,那标准呢?这是一个非常重要的问题。就像学了 C 语言后能写出很多的程序,但是常用的代码风格、代码库依旧要参考其他的标准。
我为此新开了一篇博文:RESTful API 标准。
DRF 项目部署
可以参考 https://github.com/uestc-msc/uestcmsc_webapp_backend/blob/lyh543/docs/deploy/deploy.md
Swagger 文档生成
可以看 drf-yasg。