手摸手,带你用Django REST Framework撸接口系列三(视图篇)

今天这边文章主要讲解Django REST framework视图,除了最初接触django会用到基于函数的视图,在真正项目开发中我们都会用到类视图,Django REST framework对我们的视图做了进一步的封装。

请求与响应

在最开始接触Django REST framework的时候我们最先用到的就是APIView,它其实是直接继承了我们django自带的View类,在它的基础上对request进行了进一步的封装。

request

REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。

REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。

Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。

无论前端发送的哪种格式的数据,我们都可以以统一的方式读取数据。

常用属性

request.data

request.data 返回解析之后的请求体数据。类似于Django中标准的request.POSTrequest.FILES属性,但提供如下特性:

  • 包含了解析之后的文件和非文件数据
  • 包含了对POST、PUT、PATCH请求方式解析后的数据
  • 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
Request.query_params

request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。

Response

模块位置:rest_framework.response.Response

REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

构造方式

1
Response(data, status=None, template_name=None, headers=None, content_type=None)

data数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data(例如会渲染成前端可以识别的json格式)。

data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

参数说明:

  • data: 为响应准备的序列化处理后的数据;
  • status: 状态码,默认200;
  • template_name: 模板名称,如果使用HTMLRenderer 时需指明;
  • headers: 用于存放响应头信息的字典;
  • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

状态码

为了方便设置状态码,REST framewrok在rest_framework.status模块中提供了常用状态码常量(用于Response(status=status.HTTP_200_OK))。

信息告知 - 1xx

信息,服务器收到请求,需要请求者继续执行操作

1
2
HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS

成功 - 2xx

成功,操作被成功接收并处理

1
2
3
4
5
6
7
8
HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS

重定向 - 3xx

重定向,需要进一步的操作以完成请求

1
2
3
4
5
6
7
8
HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT

客户端错误 - 4xx

客户端错误,请求包含语法错误或无法完成请求

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
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS

服务器错误 - 5xx

内部服务器错误

1
2
3
4
5
6
7
8
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

状态码含义

更多状态码含义可参考:https://seo.juziseo.com/doc/http_code/

状态码 状态码英文名称 中文描述
100 Continue 继续。客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
207 Multi-Status 请求已成功处理,返回了多个状态的XML消息
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
422 Unprocessable Entity 表示请求格式正确,但是由于含有语义错误,无法响应。
423 Locked 表示当前资源被锁定
424 Failed Dependency 表示由于之前的某个请求发生的错误,导致当前请求失败,例如PROPPATCH。
428 Precondition Required (请求未带条件)表示服务器要求请求必须带上条件。
429 Too Many Requests (并发请求过多)表示用户在一段给定的时间内发送过多的请求
431 Request Header Fields Too Large 请求头过大-表示服务器不能处理请求,因为请求的单一请求头或请求头整体过大
451 Unavailable For Legal Reasons 访问被拒绝(法律的要求)-表示(由IETF在2015核准后新增加)该访问因法律的要求而被拒绝。
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理
507 Insufficient Storage 表示服务器无法存储完成请求所必须的内容。这个状况被认为是临时的
511 Network Authentication Required 表示客户端需要经过验证以获得网络连接许可

视图类关系

REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。

视图类继承关系

简洁来讲drf有以下视图基类和扩展类

视图的方法与属性

视图说明

说这个之前我们先引入一个模型实例,以这个模型为例来做后面的演示。

示例模型

模型类

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 django.contrib.auth.models import User
from django.db import models


class Article(models.Model):
"""文章模型"""
# 文章状态
STATUS_CHOICES = (
('p', '发布'),
('d', '草稿'),
)

title = models.CharField(verbose_name='标题', max_length=90, db_index=True)
body = models.TextField(verbose_name='内容', blank=True)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE, related_name='articles')
status = models.CharField(verbose_name='状态', max_length=1, choices=STATUS_CHOICES, default='s', null=True, blank=True)
created_time = models.DateTimeField(verbose_name='发布日期', auto_now_add=True)

def __str__(self):
"""返回文章标题"""
return self.title

class Meta:
# 排序,主键倒序
ordering = ['-pk']
verbose_name = "文章"
verbose_name_plural = verbose_name

序列化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from rest_framework import serializers

from api.models import Article


class ArticleSerializer(serializers.ModelSerializer):
"""文章序列化类"""

class Meta:
model = Article
fields = "__all__"
read_only_fields = ('id', 'created_time', 'author')

def create(self, validated_data):
"""返回创建的新文章实例"""
return Article.objects.create(**validated_data)

def update(self, instance, validated_data):
"""更新已有的文章实例并返回"""
instance.title = validated_data.get('title', instance.title)
instance.body = validated_data.get('body', instance.body)
instance.status = validated_data.get('status', instance.status)
instance.save()
return instance

两个基类

APIView

位置:rest_framework.views.APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

APIViewView的不同之处在于:

  • 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
  • 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
  • 任何APIException异常都会被捕获到,并且处理成合适的响应信息;
  • 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的属性
  • authentication_classes 列表或元祖,身份认证类
  • permissoin_classes 列表或元祖,权限检查类
  • throttle_classes 列表或元祖,流量控制类

APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。

实例(文章增删改查)
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from api.models import Article
from api.serializers.article import ArticleSerializer


class ArticleView(APIView):
"""文章列表视图类"""
def get(self, request):
"""文章列表"""
articles = Article.objects.all()
# 序列化文章列表
serializers = ArticleSerializer(articles, many=True)
return Response(serializers.data)

def post(self, request):
"""添加文章"""
serializers = ArticleSerializer(data=request.data)
# 判断是否通过序列化字段验证
if serializers.is_valid():
# 序列化中文章作者我们设为了只读,所以无法通过前端修改作者,这里手动将request.user与author绑定。执行序列化类中的create方法
serializers.save(author=request.user)
return Response(serializers.data, status=status.HTTP_201_CREATED)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)


class ArticleDetailView(APIView):
"""文章详情视图类"""
def get(self, request, pk):
# 查询文章对象
article_obj = Article.objects.get(pk=pk)
# 序列化文章详情
serializers = ArticleSerializer(article_obj)
return Response(serializers.data)

def put(self, request, pk):
"""更新文章"""
# 查询文章
article_obj = Article.objects.get(pk=pk)
# 序列化请求数据
serializers = ArticleSerializer(article_obj, data=request.data)
# 验证数据
if serializers.is_valid():
# 保存数据,会执行序列化类中的update方法
serializers.save()
return Response(serializers.data)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk):
"""删除文章"""
Article.objects.filter(pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)

对应的路由

1
2
path('articles/', ArticleView.as_view(), name="articles"),
re_path(r'articles/(?P<pk>[0-9]+)/', ArticleDetailView.as_view(), name="article_detail"),

GenericAPIView

位置:rest_framework.generics.GenericAPIView

继承自APIVIew,增加了对于列表视图和详情视图可能用到的通用支持方法。通常使用时,可搭配一个或多个Mixin扩展类。

支持定义的属性
  • 列表视图与详情视图通用:
    • queryset 列表视图的查询集
    • serializer_class 视图使用的序列化器
  • 列表视图使用:
    • pagination_class 分页控制类
    • filter_backends 过滤控制后端
  • 详情页视图使用:
    • lookup_field 查询单一数据库对象时使用的条件字段,默认为’pk
    • lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
提供的方法
  • 列表视图与详情视图通用:

    • get_queryset(self)

      返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写,例如:

      1
      2
      3
      4
      def get_queryset(self):
      """返回当前登录用户发表的所有文章"""
      user = self.request.user
      return user.articles.all()
    • get_serializer_class(self)

      返回序列化器类,默认返回serializer_class,可以重写,例如:

      1
      2
      3
      4
      5
      def get_serializer_class(self):
      """判断用户是否是管理员,返回不同的序列化类"""
      if self.request.user.is_staff:
      return FullUserSerializer
      return SimpleUserSerializer
    • get_serializer(self, *args, **kwargs)

      返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法。

      注意,在提供序列化器对象的时候,REST framework会向对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。

  • 详情视图使用:

    • get_object(self) 返回详情视图所需的模型类数据对象,默认使用lookup_field参数来过滤queryset。 在试图中可以调用该方法获取详情信息的模型类对象。

      若详情访问的模型类对象不存在,会返回404。

      该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。

示例
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/3/23 17:40
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : article.py
from rest_framework import status, generics
from rest_framework.response import Response

from api.models import Article
from api.serializers.article import ArticleSerializer


class ArticleView(generics.GenericAPIView):
"""文章列表及添加文章视图"""
# 序列化器
serializer_class = ArticleSerializer
# 查询集
queryset = Article.objects.all()
"""文章列表视图类"""
def get(self, request):
"""文章列表"""
# 获取查询集
articles = self.get_queryset()
# 序列化文章列表
serializers = self.get_serializer(instance=articles, many=True)
return Response(serializers.data)

def post(self, request):
serializers = self.get_serializer(data=request.data)
# 判断是否通过序列化字段验证
if serializers.is_valid():
serializers.save(author=request.user)
return Response(serializers.data, status=status.HTTP_201_CREATED)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)


class ArticleDetailView(generics.GenericAPIView):
"""文章详情视图类"""
# 序列化器
serializer_class = ArticleSerializer
# 查询集
queryset = Article.objects.all()

def get(self, request, pk):
# 查询文章对象
article_obj = self.get_object()
# 序列化文章详情
serializers = self.get_serializer(article_obj)
return Response(serializers.data)

def put(self, request, pk):
# 查询文章
article_obj = self.get_object()
# 序列化请求数据
serializers = self.get_serializer(instance=article_obj, data=request.data)
# 验证数据
if serializers.is_valid():
serializers.save()
return Response(serializers.data)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk):
Article.objects.filter(pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)

对应的路由:

1
2
path('articles/', ArticleView.as_view(), name="articles"),
re_path(r'articles/(?P<pk>[0-9]+)/', ArticleDetailView.as_view(), name="article_detail"),

从示例代码可以看出,使用了GenericAPIView之后,代码并没有精简,相反代码比之前更多了,只是可以将查询集和序列化器独立出来不用重复写了,但是进入到请求方法中依然要获取一下上面定义查询集和序列化器,所以GenericAPIView主要作用是配合扩展类使用的,下面我们来说说扩展类。

五个扩展类

我们已经有get, post, delete等方法了,为什么mixin类引入的方法要以list, create, retrieve, destroy方法命名呢? 这是因为请求方法不如操作名字清晰,比如get方法同时对应了获取对象列表和单个对象两种操作,使用list和retrieve方法后则很容易区分。另外post方法接受用户发过来的请求数据后,有时只需转发不需要创建模式对象实例,所以post方法不能简单等于create方法。

ListModelMixin

列表视图扩展类,提供list(request, *args, **kwargs)方法快速实现列表视图,返回200状态码。

该Mixin的list方法会对数据进行过滤和分页。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ListModelMixin(object):
"""
列出查询集
"""
def list(self, request, *args, **kwargs):
# 过滤
queryset = self.filter_queryset(self.get_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)
# 序列化
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

CreateModelMixin

创建视图扩展类,提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。

如果序列化器对前端发送的数据验证失败,返回400错误。

perform_create这个钩子函数是CreateModelMixin类自带的,用于执行创建对象时需要执行的其它方法,比如发送邮件等功能,有点类似于Django的信号。类似的钩子函数还有UpdateModelMixin提供的perform_update方法和DestroyModelMixin提供的perform_destroy方法。

数据通过了验证开始执行这个,我们之前的实例中创建文章要绑定一个作者,因为作者字段是只读的,我们可以在这个函数里手动去绑定作者。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CreateModelMixin(object):
"""
创建模型实例
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

def perform_create(self, serializer):
serializer.save()

def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}

RetrieveModelMixin

详情视图扩展类,提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。

如果存在,返回200, 否则返回404。

源代码:

1
2
3
4
5
6
7
8
9
10
class RetrieveModelMixin(object):
"""
查询一个模型实例.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)

UpdateModelMixin

更新视图扩展类,提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。

同时也提供partial_update(request, *args, **kwargs)方法,可以实现局部更新。

成功返回200,序列化器校验数据失败时,返回400错误。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class UpdateModelMixin(object):
"""
更新模型实例.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)

if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}

return Response(serializer.data)

def perform_update(self, serializer):
serializer.save()

def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)

DestroyModelMixin

删除视图扩展类,提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。

成功返回204,不存在返回404。

源代码:

1
2
3
4
5
6
7
8
9
10
11
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

def perform_destroy(self, instance):
instance.delete()

实例

接下来的实例我们使用前面提到的GenericAPIView和扩展类组合来完成我们的实例

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/3/23 17:40
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : article.py
from rest_framework import generics, mixins

from api.models import Article
from api.serializers.article import ArticleSerializer


class ArticleView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
"""文章列表视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer

def get(self, request, *args, **kwargs):
"""文章列表"""
return self.list(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
"""添加文章"""
return self.create(request, *args, **kwargs)

def perform_create(self, serializer):
# 将request.user与author绑定。调用create方法时执行如下函数。
serializer.save(author=self.request.user)


class ArticleDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
"""文章详情视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer

def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)

def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)

路由

1
2
path('articles/', ArticleView.as_view(), name="articles"),
re_path(r'articles/(?P<pk>[0-9]+)/', ArticleDetailView.as_view(), name="article_detail"),

GenericAPIView 类继承了APIView类,提供了基础的API视图。它对用户请求进行了转发,并对Django自带的request对象进行了封装。不过它比APIView类更强大,因为它还可以通过querysetserializer_class属性指定需要序列化与反序列化的模型或queryset及所用到的序列化器类。

这里的 ListModelMixinCreateModelMixin类则分别引入了.list() .create()方法,当用户发送get请求时调用Mixin提供的list()方法,将指定queryset序列化后输出,发送post请求时调用Mixin提供的create()方法,创建新的实例对象。

DRF还提供RetrieveModelMixin, UpdateModelMixinDestroyModelMixin类,实现了对单个对象实例的查、改和删操作

使用通用视图Generics.*类

将Mixin类和GenericAPI类混配,已经帮助我们减少了一些代码,但我们还可以做得更好,比如将get请求与mixin提供的list方法进行绑定感觉有些多余。幸好DRF还提供了一套常用的将 Mixin 类与 GenericAPI类已经组合好了的视图,开箱即用,可以进一步简化我们的代码

CreateAPIView

提供 post 方法

继承自: GenericAPIView、CreateModelMixin

ListAPIView

提供 get 方法

继承自:GenericAPIView、ListModelMixin

RetireveAPIView

提供 get 方法

继承自: GenericAPIView、RetrieveModelMixin

DestoryAPIView

提供 delete 方法

继承自:GenericAPIView、DestoryModelMixin

UpdateAPIView

提供 put 和 patch 方法

继承自:GenericAPIView、UpdateModelMixin

RetrieveUpdateAPIView

提供 get、put、patch方法

继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

RetrieveUpdateDestoryAPIView

提供 get、put、patch、delete方法

继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin

实例

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
28
29
30
31
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/3/23 17:40
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : article.py
from rest_framework import generics

from api.models import Article
from api.serializers.article import ArticleSerializer


class ArticleView(generics.ListCreateAPIView):
"""文章列表视图类"""
# 查询集
queryset = Article.objects.all()
# 序列化类
serializer_class = ArticleSerializer

def perform_create(self, serializer):
# 将request.user与author绑定。调用create方法时执行如下函数。
serializer.save(author=self.request.user)


class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""文章详情视图类"""
# 查询集
queryset = Article.objects.all()
# 序列化类
serializer_class = ArticleSerializer

路由

1
2
path('articles/', ArticleView.as_view(), name="articles"),
re_path(r'articles/(?P<pk>[0-9]+)/', ArticleDetailView.as_view(), name="article_detail"),

顾名思义,generics.ListCreateAPIView类支持List、Create两种视图功能,分别对应GET和POST请求。generics.RetrieveUpdateDestroyAPIView支持Retrieve、Update、Destroy操作,其对应方法分别是GET、PUT和DELETE。

寥寥几行,实现了我们所有想要的功能,神不神奇?

其它常用generics类视图还包括ListAPIView, RetrieveAPIView, RetrieveUpdateAPIView等等。你可以根据实际需求使用,为你的API写视图时只需要定义querysetserializer_class即可。

视图集ViewSet

使用通用视图generics类后视图代码已经大大简化,但是ArticleView和ArticleDetailView两个类中querysetserializer_class属性依然存在代码重复。使用视图集可以将两个类视图进一步合并,一次性提供List、Create、Retrieve、Update、Destroy这5种常见操作,这样querysetseralizer_class属性也只需定义一次就好, 这就变成了视图集(viewset)。

ViewSet

继承自APIView,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。

在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

GenericViewSet

继承自GenericAPIView,作用也与GenericAPIVIew类似,提供了get_object、get_queryset等方法便于列表视图与详情信息视图的开发。

ModelViewSet

继承自GenericAPIVIew,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。

ReadOnlyModelViewSet

继承自GenericAPIVIew,同时包括了ListModelMixin、RetrieveModelMixin。

到了视图集这里,我们的目的很明确就是简化代码,以上每个视图集都注明了继承的类,从继承关系上可以看出来大概的使用方式,我们这里一步到位只讲解下代码最简化的ModelViewSet,直接上实例

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/3/23 17:40
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : article.py
from rest_framework import viewsets

from api.models import Article
from api.serializers.article import ArticleSerializer


class ArticleView(viewsets.ModelViewSet):
"""文章列表视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer

def perform_create(self, serializer):
# 将request.user与author绑定。调用create方法时执行如下函数。
serializer.save(author=self.request.user)

使用视图集后,我们需要使用DRF提供的路由router来分发urls,因为一个视图集现在对应多个urls,而不像之前的一个url对应一个视图函数或一个视图类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.urls import path, re_path
from rest_framework.routers import DefaultRouter

from api.views.article import ArticleView

app_name = "api"
urlpatterns = [
# path('articles/', ArticleView.as_view(), name="articles"),
# re_path(r'articles/(?P<pk>[0-9]+)/', ArticleDetailView.as_view(), name="article_detail"),
]

router = DefaultRouter()
router.register('articles', viewset=ArticleView)

urlpatterns += router.urls

你或许又要问了,一个视图集对应List、Create、Retrieve、Update、Destroy这5种操作。有时候我只需要其中的一种或几种操作,该如何实现呢?答案是在urls.py中指定方法映射即可,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

from django.urls import path, re_path
from rest_framework.routers import DefaultRouter

from api.views.article import ArticleView

article_list = views.ArticleView.as_view(
{
'get': 'list',
'post': 'create'
})

article_detail = views.ArticleView.as_view({
'get': 'retrieve', # 只处理get请求,获取单个记录
})


urlpatterns = [
re_path(r'^articles/$', article_list),
re_path(r'^articles/(?P<pk>[0-9]+)$', article_detail),
]

另外DRF还提供了ReadOnlyModelViewSet这个类,它仅支持list和retrive这两个可读的操作

视图集中定义附加action动作

在视图集中,除了上述默认的方法动作外,还可以添加自定义动作。

添加自定义动作需要使用rest_framework.decorators.action装饰器。

以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。

action装饰器可以接收两个参数:

  • methods: 该action支持的请求方式,列表传递

  • detail

    : 表示是action中要处理的是否是视图资源的对象(即是否通过url路径获取主键)

    • True 表示使用通过URL获取的主键对应的数据对象
    • False 表示不使用URL获取主键

举例:

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
28
29
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class ArticleView(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer

# detail为False 表示不需要处理具体的Article对象
@action(methods=['get'], detail=False)
def latest(self, request):
"""
返回最新的文章信息
"""
article_obj = Article.objects.latest('id')
serializer = self.get_serializer(article_obj)
return Response(serializer.data)

# detail为True,表示要处理具体与pk主键对应的Article对象
@action(methods=['put'], detail=True)
def status(self, request, pk):
"""
修改文章为发布状态
"""
article_obj = self.get_object()
article_obj.status = "d"
article_obj.save()
serializer = self.get_serializer(article_obj)
return Response(serializer.data)

url的定义

1
2
3
4
5
6
urlpatterns = [
url(r'^articles/$', ArticleView.as_view({'get': 'list'})),
url(r'^articles/latest/$', ArticleView.as_view({'get': 'latest'})),
url(r'^articles/(?P<pk>\d+)/$', ArticleView.as_view({'get': 'retrieve'})),
url(r'^articles/(?P<pk>\d+)/status/$', ArticleView.as_view({'put': 'status'})),
]

视图集继承关系

总结

本文使用了DRF提供的多种基于类的API视图的重写了文章资源API。那么这几种方式到底哪种更好呢? 答案是各有利弊。小编个人认为大家只需掌握以下三种方式即可:

  • 基础的API类:可读性最高、代码最多、灵活性最高。当你需要对的API行为进行个性化定制时,建议使用这种方式。
  • 通用generics类:可读性好、代码适中、灵活性较高。当你需要对一个模型进行标准的增删查改全部或部分操作时建议使用这种方式。
  • 使用视图集viewset: 可读性较低、代码最少、灵活性最低。当你需要对一个模型进行标准的增删查改的全部操作且不需定制API行为时建议使用这种方式。

至于mixin类和GenericAPI的混用,这个和generics类没什么区别,大家不看也罢。

使用CBV类可以简化代码,增加重用,在很多情况下我们还需要重写父类的方法,比如get_queryset, get_serializer_class方法以实现特殊的功能。