about
django3.2 + python3.9.7 + mysql5.7.20
由于Django默认的视图View
提供的功能不尽如人意,所以drf中在原View
的基础上做了封装,更方便我们使用,它就是APIView
以及子类。
drf提供的视图主要作用:
- 控制序列化器的执行(数据校验、保存、序列化与反序列化)。
- 控制数据库查询的执行。
- 对请求类和响应类做了进一步的封装。
APIView
APIView
是REST framework提供的所有视图的基类,继承自Django的View
父类。
APIView
与View
的不同之处在于:
- 传入到视图方法中的是REST framework的
Request
对象,而不是Django的HttpRequeset
对象。 - 视图方法可以返回REST framework的
Response
对象,视图会为响应数据设置(render)符合前端要求的格式。 - 任何
APIException
异常都会被捕获到,并且处理成合适的响应信息。 - 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的属性
- authentication_classes 列表或元祖,身份认证类
- permissoin_classes 列表或元祖,权限检查类
- throttle_classes 列表或元祖,流量控制类
在APIView
中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。
请求与响应
本小节主要围绕:
# 学习django cbv时的View
from django.views import View
# 在django View的基础上做了封装
from rest_framework.views import APIView
# 对django HtppResponse做了封装
from rest_framework.response import Response
这三个类展开讲解,主要介绍其中的不同。
Request
drf的request对象,是通过Request类对django原生request对象做了进一步的封装:
from rest_framework.request import Request
对于不同的请求类型,我们后端获取数据也不太一样,通过postman发送请求,我们分别来看后端都是如何获取数据的。
get请求
get请求没啥说的:
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def get(self, request):
"""
postman发送请求
GET http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
"""
# 这个request是drf封装后的
print(request) # <rest_framework.request.Request: GET '/book/list/?k1=v1&k2=v2'>
# 这个才是django原生的
print(request._request) # <WSGIRequest: GET '/book/list/?k1=v1&k2=v2'>
print(request.GET) # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
print(request.query_params) # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
print(request.data) # {}
return Response({"type": "get", "code": 0})
get请求,你可以通过request.GET
,也可以使用drf的request.query_params
来获取数据。
POST请求,请求类型是json数据
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def post(self, request):
"""
postman
POST Content-Type:application/json
http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
"""
print(request) # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
print(request.headers['Content-Type']) # application/json
print(request.POST) # <QueryDict: {}>
print(request.data) # {'title': 'Python快速入门55', 'author': ['张开', '王开']}
print(request.query_params) # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
return Response({"type": "post", "code": 0})
很明显,如果请求体的数据是json,我们通过request.data
获取即可。
如果url上携带了参数,我们通过request.query_params
获取。
POST请求,请求类型是x-www-form-urlencoded数据
form表单提交的post请求,也是urlencoded类型。
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def post(self, request):
"""
postman
POST Content-Type:application/x-www-form-urlencoded
http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
"""
print(request) # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
print(request.headers['Content-Type']) # application/x-www-form-urlencoded
print(request.POST) # <QueryDict: {'title': ['Python快速入门55'], 'author': ['张开', '王开']}>
print(request.POST.get("author")) # 王开
print(request.POST.getlist("author")) # ['张开', '王开']
print(request.data) # <QueryDict: {'title': ['Python快速入门55'], 'author': ['张开', '王开']}>
print(request.data.get("author")) # 王开
print(request.data.getlist("author")) # ['张开', '王开']
print(request.query_params) # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
return Response({"type": "post", "code": 0})
对于这样的请求,我们基本上可以通过request.POST
和request.data
来获取数据。
这个跟django原生的request.POST
基本一样。
POST请求,请求类型是form-data数据
提交的数据包含文件。
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def get(self, request):
"""
postman发送请求
GET
"""
return Response({"type": "get", "code": 0})
def post(self, request):
"""
postman
POST Content-Type:multipart/form-data; boundary=--------------------------251656075156781150213005
http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
"""
print(request) # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
print(request.headers['Content-Type']) # multipart/form-data; boundary=--------------------------251656075156781150213005
print(request.POST) # <QueryDict: {'k1': ['v1']}>
print(request.FILES) # <MultiValueDict: {'a.py': [<InMemoryUploadedFile: 牌.py (application/octet-stream)>]}>
print(request.data) # <QueryDict: {'k1': ['v1'], 'a.py': [<InMemoryUploadedFile: 牌.py (application/octet-stream)>]}>
print(request.query_params) # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
return Response({"type": "post", "code": 0})
那么,按照原来的套路,对于文件的操作还是通过requet.POST
和requet.FILES
结合来处理。
而drf也就是将它都封装到了request.data
中了。
Response
前文有提过,这个Response就是drf对django的HttpResponse进行了封装:
# 对django HtppResponse做了封装
from rest_framework.response import Response
Renderer
渲染器
REST framework提供了Renderer
渲染器,用来根据请求头中的Accept
(客户端希望接收的数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。
例如你直接用浏览器访问,Response则会render一个页面。
或者你可以通过url传参format=json
格式获取json类型的返回:
如果你是postman发起get请求,也会默认得到json类型的数据。
这些都有赖于drf提供的解析器来根据请求来决定返回什么。
你可以在drf的配置文件中看到:
# 这么导入
from rest_framework import settings
# drf的很多设置都在这个配置文件中,我们目前只关注render的解析器
DEFAULTS = {
# Base API policies
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
当然了,我们也可以在我们的配置文件中,设置这两个参数,来决定如何返回:
# 我们自己项目的settings.py
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
# 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器
)
}
如上配置,我们将默认的BrowsableAPIRenderer
渲染器注释掉,那么浏览器直接访问也仅会的得到json类型的数据了。
构造方式
来看Response的其它配置:
from rest_framework.response import Response
Response(data, status=None, template_name=None, headers=None, content_type=None)
data
:只需要传递Python的内建数据类型就可以了,drf会使用renderer
渲染器处理data
。注意,data
不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer
序列化器序列化处理后(转为了Python字典类型)再传递给data
参数。status
:状态码,默认是200,你也可以手动指定。python# 可以这样写 return Response({'msg': 'ok'},status=204) # 也可以从def体统的状态码文件中导入 from rest_framework import status return Response({'msg': 'ok'},status=status.HTTP_204_NO_CONTENT)
template_name
: 模板名称,如果使用HTMLRenderer
时需指明;就是有些人觉得Response返回的那个页面丑,那么就可以通过这个模板自行定制。headers
: 用于存放响应头信息的字典;比如放一些cookie啊或者一些自定制的响应头啊都可以,例如:pythonreturn Response({'msg': 'ok'},status=204,headers={'xx':'oo'})
content_type
: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据(accept请求头)来设置该参数。
除了上述这些参数之外,Response类的实例化对象还有一下这些属性,当然不常用就是了:
response.data
:传给response对象的序列化后,但尚未render处理的数据。response.status_code
:状态码的数字。response.content
:经过render处理后的响应数据。
GenericAPIView
通用视图类(GenericAPIView)是APIView
的子类,主要增加了操作序列化器和数据库查询的方法。
其主要作用是结合Mixin扩展类简化视图类的编写。
那么GenericAPIView
给我们提供了哪些功能,先大致看下,后面会具体的应用:
属性:
serializer_class
,指明视图使用的序列化器类,如果没有指定get_serializer_class(self)
方法,那么该属性为必填属性。queryset
,指明使用的数据查询集。pagination_class
,指明分页控制类。filter_backends
,指明过滤控制后端(查询一些数据的过滤条件等等)。
方法:
get_serializer_class(self)
,如果一个视图类中根据不同的业务场景,需要使用多个序列化器类,那么就在这个方法中进行处理,如果有了该方法,就可以不用声明serializer_class
属性了。python# 在我们的视图类中,重写get_serializer_class方法 def get_serializer_class(self): if self.request.user.is_staff: return FullAccountSerializer return BasicAccountSerializer
get_serializer(self, *args, **kwargs)
,返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
比如
serializer = self.get_serializer(instance=self.get_object(),context={'pk':pk})
,下面的request和view我们后面会用到,现在先了解一下,后面使用就知道了。- request 当前视图的请求对象。
- view 当前请求的类视图对象。
- format 当前请求期望返回的数据格式。
get_queryset(self)
,返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset
属性,可以重写,例如:pythondef get_queryset(self): user = self.request.user return user.accounts.all()
get_object(self)
,返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用。在试图中可以调用该方法获取详情信息的模型类对象。若详情访问的模型类对象不存在,会返回404。该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。python# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()), class BookDetailView(GenericAPIView): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer def get(self, request, pk): book = self.get_object() # get_object()方法根据pk参数查找queryset中的数据对象 serializer = self.get_serializer(book) return Response(serializer.data)
先来看我们原来使用APIView
时,我们写的增删改查。
楔子
还是咱们之前序列化器部分用过的book表。
models.py
:
from django.db import models
class Book(models.Model):
""" 书籍表 """
title = models.CharField(max_length=32, verbose_name='书籍名称')
choice = ((1, "Python"), (2, "Linux"), (3, "Go"))
category = models.IntegerField(choices=choice, verbose_name="书籍类别")
pub_time = models.DateField(verbose_name="出版日期")
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社')
author = models.ManyToManyField(to='author', verbose_name='作者')
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "书籍表"
db_table = verbose_name_plural
class Publisher(models.Model):
""" 出版社表 """
title = models.CharField(max_length=32, verbose_name="出版社名称")
address = models.CharField(max_length=255, verbose_name="地址")
email = models.CharField(max_length=32, default="publisher@qq.com")
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "出版社表"
db_table = verbose_name_plural
class Author(models.Model):
""" 作者表 """
name = models.CharField(max_length=32, verbose_name="作者名称")
age = models.IntegerField(verbose_name="作者名称")
phone = models.CharField(max_length=20, default='182111101111')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "作者表"
db_table = verbose_name_plural
主路由urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('stu/', include("student.urls")),
path('ser/', include("ser.urls")),
path('book/', include("book.urls")),
]
book app的urls.py
:
from django.urls import path
from .views import BookView, BookRetrieveView
urlpatterns = [
path('list/', BookView.as_view()),
path('list/<int:pk>', BookRetrieveView.as_view()),
]
views.py
:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
category_info = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True)
def get_category_info(self, obj):
return obj.get_category_display()
def get_publisher_info(self, obj):
pub_obj = obj.publisher
return {"id": pub_obj.id, "title": pub_obj.title}
def get_author_info(self, obj):
au_obj = obj.author.all()
return [{"id": i.id, "name": i.name} for i in au_obj]
class Meta:
model = Book
fields = "__all__"
extra_kwargs = {
"category": {"write_only": True},
"publisher": {"write_only": True},
"author": {"write_only": True},
}
class BookView(APIView):
"""
主要处理:
get: 客户端请求所有书籍信息
post: 添加书籍信息
"""
def get(self, request):
""" 返回所有书籍信息 """
book_list = Book.objects.all()
ser_obj = BookSerializer(instance=book_list, many=True)
return Response(ser_obj.data)
def post(self, request):
""" 添加书籍 """
data = request.data
ser_obj = BookSerializer(data=data)
if ser_obj.is_valid():
ser_obj.save()
print(222, ser_obj.data)
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
class BookRetrieveView(APIView):
"""
主要根据id进行:
get: 客户端根据id获取单条书籍信息
put: 更新书籍信息
delete: 删除书籍信息
"""
def get(self, request, pk):
""" 返回单条书籍信息 """
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj)
return Response(ser_obj.data)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
def put(self, request, pk):
""" 更新书籍 """
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
data = request.data
ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
if ser_obj.is_valid():
ser_obj.save()
print(4444, ser_obj.data)
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
def delete(self, request, pk):
""" 删除书籍 """
# ret = book_obj = Book.objects.filter(pk=pk).first()
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj)
# 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
data = ser_obj.data
book_obj.delete()
return Response(data)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
因为目前我们都是通过请求类型对应方法名来处理该请求,那么问题来了,获取单条数据和获取所有数据都是get请求,但一个视图类中不能写两个同名的方法, 所以,折中,我们搞了两套路由和视图类来处理当前的增删改查,当然,这种问题后面我们会解决掉,暂时先这么写着。
我们结合postman来完成对book表的增删改查操作:
# GET
# http://127.0.0.1:8000/book/list/
# 结果太多,不做展示了
# POST
# http://127.0.0.1:8000/book/list/
{
"category": 1,
"publisher": 1,
"author": [1,2],
"title": "Python快速入门66",
"pub_time": "2022-04-16"
}
# response
{
"id": 15,
"category_info": "Python",
"publisher_info": {
"id": 1,
"title": "沙河出版社"
},
"author_info": [
{
"id": 1,
"name": "张开"
},
{
"id": 2,
"name": "李开"
}
],
"title": "Python快速入门66",
"pub_time": "2022-04-16"
}
# PUT
# http://127.0.0.1:8000/book/list/15
{
"category": 2,
"publisher": 2,
"author": [2,3],
"title": "Python快速入门6666",
"pub_time": "2022-03-16"
}
# response
{
"id": 15,
"category_info": "Linux",
"publisher_info": {
"id": 2,
"title": "高教园出版社"
},
"author_info": [
{
"id": 2,
"name": "李开"
},
{
"id": 3,
"name": "王开"
}
],
"title": "Python快速入门6666",
"pub_time": "2022-03-16"
}
# DELETE
# http://127.0.0.1:8000/book/list/13
# 请求无需携带参数
# response
{
"id": 13,
"category_info": "Linux",
"publisher_info": {
"id": 2,
"title": "高教园出版社"
},
"author_info": [
{
"id": 2,
"name": "李开"
},
{
"id": 3,
"name": "王开"
}
],
"title": "Python快速入门4",
"pub_time": "2022-06-15"
}
看着非常完美,增删改查都可以,并且可以套用到其它模型类中。
但是,问题来了,如果模型类有很多,每个模型类都要这么写,代码逻辑都一致,这样就会显得很冗余。所以,我们要想招给它精简精简。
GenericAPIView快速上手
现在,我们要用GenericAPIView
来优化我们的视图类的代码。
模型类、路由、序列化器都不变,我们就把视图类简单调整下。
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
# 必填属性
queryset = Book.objects.all()
# 如果没有重写get_serializer_class方法,则必填serializer_class
serializer_class = BookSerializer
def get(self, request):
""" 返回所有书籍信息 """
# 原来这么写
# book_list = Book.objects.all()
# ser_obj = BookSerializer(instance=book_list, many=True)
# return Response(ser_obj.data)
# 现在这么写
ser_obj = self.serializer_class(instance=self.get_queryset(), many=True)
return Response(ser_obj.data)
def post(self, request):
""" 添加书籍 """
# 原来这么写
# data = request.data
# ser_obj = BookSerializer(data=data)
# if ser_obj.is_valid():
# ser_obj.save()
# print(222, ser_obj.data)
# return Response(ser_obj.data)
# else:
# return Response(ser_obj.errors)
# 现在这么写
data = request.data
ser_obj = self.serializer_class(data=data)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
class BookRetrieveView(GenericAPIView):
# 必填属性
queryset = Book.objects.all()
# 如果没有重写get_serializer_class方法,则必填serializer_class
serializer_class = BookSerializer
# 默认self.get_object会根据pk过滤单条数据,如果你用的是id,所以必须声明lookup_field
# lookup_field = "id"
def get(self, request, pk):
""" 返回单条书籍信息 """
# 原来这么写
# book_obj = Book.objects.filter(pk=pk).first()
# if book_obj:
# ser_obj = BookSerializer(instance=book_obj)
# return Response(ser_obj.data)
# else:
# return Response({"code": 1, "msg": "请求的数据id错误"})
# 现在这么写
ser_obj = self.serializer_class(instance=self.get_object())
# 如果根据id没有get到数据,就会在内部直接返回{"detail": "Not found."}
# 如果有数据,就从这里返回
return Response(ser_obj.data)
def put(self, request, pk):
""" 更新书籍 """
# 原来这么写
# book_obj = Book.objects.filter(pk=pk).first()
# if book_obj:
# data = request.data
# ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
# if ser_obj.is_valid():
# ser_obj.save()
# print(4444, ser_obj.data)
# return Response(ser_obj.data)
# else:
# return Response(ser_obj.errors)
# else:
# return Response({"code": 1, "msg": "请求的数据id错误"})
# 现在这么写
ser_obj = self.serializer_class(instance=self.get_object(), data=request.data, partial=True)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
def delete(self, request, pk):
""" 删除书籍 """
# 原来这么写
# book_obj = Book.objects.filter(pk=pk).first()
# if book_obj:
# ser_obj = BookSerializer(instance=book_obj)
# # 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
# data = ser_obj.data
# book_obj.delete()
# return Response(data)
# else:
# return Response({"code": 1, "msg": "请求的数据id错误"})
# 现在这么写
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
ser_obj = self.serializer_class(instance=book_obj)
# 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
data = ser_obj.data
book_obj.delete()
return Response(data)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
这么看,代码量并没有少多少!
GenericAPIView只是帮我们把数据库查询和调用序列化器类做了封装,目的是为了将一些公共性质的代码单独封装,其它感觉没啥用。
所以,我们往下继续学习,学习其它的Mixin扩展类,来进一步对简化我们的视图类。
Mixin系列扩展类
首先啊,Mixin系列的类一般叫做混合类、或者扩展类,通常都是搭配其它类来使用,单独使用的场景不多。
那么,搭配GenericAPIView类使用的Mixin混合类,也有这么几个:
from rest_framework.generics import GenericAPIView
# 搭配GenericAPIView使用的几个扩展类
from rest_framework.mixins import ListModelMixin # 获取所有数据
from rest_framework.mixins import CreateModelMixin # 添加一条数据
from rest_framework.mixins import RetrieveModelMixin # 获取单条数据
from rest_framework.mixins import UpdateModelMixin # 更新数据
from rest_framework.mixins import DestroyModelMixin # 删除数据
ListModelMixin
,获取多条数据的列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图。返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
CreateModelMixin
,添加数据的创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。如果序列化器对前端发送的数据验证失败,返回400错误。
RetrieveModelMixin
,获取单条数据,详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。如果存在,返回200, 否则返回404。
UpdateModelMixin
,更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。同时也提供
partial_update(request, *args, **kwargs)
方法,可以实现局部更新。成功返回200,序列化器校验数据失败时,返回400错误。
DestroyModelMixin
,删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。成功返回204,不存在返回404。
这几个类中也可以支持分页和条件过滤,后面我们会讲到。
来看示例,代码有变动的只有两个视图类:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import CreateModelMixin
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.mixins import UpdateModelMixin
from rest_framework.mixins import DestroyModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
""" 返回所有书籍信息 """
# 调用ListModelMixin中的list方法
return self.list(request)
def post(self, request):
""" 添加书籍 """
# 调用CreateModelMixin中的create方法
return self.create(request)
class BookRetrieveView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, pk):
""" 返回单条书籍信息 """
# 调用RetrieveModelMixin中的retrieve方法
return self.retrieve(request, pk)
def put(self, request, pk):
""" 更新书籍 """
# 调用UpdateModelMixin中的update方法
return self.update(request, pk, partial=True)
def delete(self, request, pk):
""" 删除书籍 """
# 调用DestroyModelMixin中的destroy方法
# 默认删除成功,返回None,状态码是204
return self.destroy(request, pk)
这几个类帮我们实现了增删改查,方便倒是方便了,当然了如果业务逻辑比较复杂的话,这几个类就不适用了啊。
当然了,你如果需要扩展,也可以自己扩展,不用Mixin扩展类。
GenericAPIView的视图子类
在Mixin系列扩展类中,我们调用其内部的方法帮我们实现了增删改查,那么在本小节,我们可以通过GenericAPIView的视图子类,继续简化我们的代码。
from rest_framework.generics import ListAPIView
"""
ListAPIView = GenericAPIView + ListModelMixin
提供了 get 方法,返回所有数据
"""
from rest_framework.generics import CreateAPIView
"""
CreateAPIView = GenericAPIView + CreateModelMixin
提供 post 方法,添加一条数据
"""
from rest_framework.generics import RetrieveAPIView
"""
RetrieveAPIView = GenericAPIView + RetrieveModelMixin
提供 get 方法,返回单条数据
"""
from rest_framework.generics import UpdateAPIView
"""
UpdateAPIView = GenericAPIView + UpdateModelMixin
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
"""
from rest_framework.generics import DestroyAPIView
"""
DestroyAPIView = GenericAPIView + DestroyModelMixin
提供 delete 方法,删除一条数据
"""
from rest_framework.generics import RetrieveUpdateAPIView
"""
RetrieveUpdateAPIView = GenericAPIView + RetrieveModelMixin + UpdateModelMixin
提供 get 方法,获取单条数据
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
"""
from rest_framework.generics import RetrieveUpdateDestroyAPIView
"""
RetrieveUpdateDestroyAPIView = GenericAPIView + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin
提供 get 方法,获取单条数据
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
提供 delete 方法,删除一条数据
"""
让我们的视图类根据情况继承上面的类,就不用继承那么多Minin系列的类了。
ListAPIView/CreateAPIView/RetrieveAPIView/UpdateAPIView/DestroyAPIView
先来看这几个视图类的用法。
还是只动两个视图类,其它不变:
from rest_framework.generics import ListAPIView
from rest_framework.generics import CreateAPIView
from rest_framework.generics import RetrieveAPIView
from rest_framework.generics import UpdateAPIView
from rest_framework.generics import DestroyAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class BookView(ListAPIView, CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询所有记录的get方法由ListAPIView负责
# 添加记录的post方法由CreateAPIView负责
# 所以,我们都可以不写了
class BookRetrieveView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询单条记录的get方法由RetrieveAPIView负责
# 更新的put方法由UpdateAPIView负责
# 注意,全部字段都更新,客户端使用put请求提交,如果局部字段更新,就要使用patch请求了
# 删除的delete由DestroyAPIView负责
# 所以,我们都可以不写了
RetrieveUpdateAPIView/RetrieveUpdateDestroyAPIView
通过继承关系和提供的功能来看,RetrieveUpdateAPIView
只有获取一条数据、更新(部分/全部)数据,没有删除功能;而RetrieveUpdateDestroyAPIView
则在RetrieveUpdateAPIView
的基础上提供了删除功能。
所以这里的示例我们只需要演示RetrieveUpdateDestroyAPIView
的用法就行了。
from rest_framework import serializers
from rest_framework.generics import ListAPIView
from rest_framework.generics import CreateAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import DestroyAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Book
class BookSerializer(serializers.ModelSerializer):
category_info = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True)
def get_category_info(self, obj):
return obj.get_category_display()
def get_publisher_info(self, obj):
pub_obj = obj.publisher
return {"id": pub_obj.id, "title": pub_obj.title}
def get_author_info(self, obj):
au_obj = obj.author.all()
return [{"id": i.id, "name": i.name} for i in au_obj]
class Meta:
model = Book
fields = "__all__"
extra_kwargs = {
"category": {"write_only": True},
"publisher": {"write_only": True},
"author": {"write_only": True},
}
class BookView(ListAPIView, CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询所有记录的get方法由ListAPIView负责
# 添加记录的post方法由CreateAPIView负责
# 所以,我们都可以不写了
class BookRetrieveView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# RetrieveUpdateDestroyAPIView把需要的几个功能都实现了,所以都不用写了
# 注意,全部字段都更新,客户端使用put请求提交,如果局部字段更新,就要使用patch请求了
# 或者用RetrieveUpdateAPIView,DestroyAPIView结合也行
# class BookRetrieveView(RetrieveUpdateAPIView,DestroyAPIView):
# queryset = Book.objects.all()
# serializer_class = BookSerializer
视图集ViewSet
目前为止,我们需要两个视图类来处理增删改查:通过两个路由和两个视图类,来解决带不带pk的两种情况。
那么现在,在本小节中,我们通过action字典,来将两个视图类合并为一个视图类(至于将两个路由合并成一个,将在路由部分讲),不同的请求通过action动作进行映射,让我们定义的方法名可以不再必须是get
、post
方式。
然后结合那Mixin扩展类或者GenericAPIView扩展子类实现增删改查。
本小节的示例,主要修改视图类和路由部分的代码,其它的代码不变,所以,models、序列化器类这些都用上面的,不再贴出来了。
ViewSet
from rest_framework.viewsets import ViewSet
"""
ViewSet = ViewSetMixin + APIView
"""
ViewSet
作用类似于APIView
,提供了身份认证、权限校验、限流等功能。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{'get':'list'})的映射处理工作。
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
上面这句话怎么理解呢?简单来说,就是通过不同的视图类的组合,我们的代码量也不同。
视图类只继承ViewSet
来看视图类只继承ViewSet的情况,我们的代码量:
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView
urlpatterns = [
path('list/', BookView.as_view({"get": "get_all", "post": "create"})),
path('list/<int:pk>', BookView.as_view({"get": "get_one", "put": "update", "delete": "destroy"})),
]
# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class BookView(ViewSet):
def get_all(self, request):
""" 返回所有书籍信息 """
book_list = Book.objects.all()
ser_obj = BookSerializer(instance=book_list, many=True)
return Response(ser_obj.data)
def create(self, request):
""" 添加书籍 """
data = request.data
ser_obj = BookSerializer(data=data)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
def get_one(self, request, pk):
""" 返回单条书籍信息 """
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj)
return Response(ser_obj.data)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
def update(self, request, pk):
""" 更新书籍 """
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
data = request.data
ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
def destroy(self, request, pk):
""" 删除书籍 """
# ret = book_obj = Book.objects.filter(pk=pk).first()
book_obj = Book.objects.filter(pk=pk).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj)
# 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
data = ser_obj.data
book_obj.delete()
return Response(data)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
重要的:
ViewSet
重写了as_view
方法,所以路由中才支持写字典,进行映射。- 由于我们的视图类只继承
ViewSet
的话,则你可以自定义方法名来对应不同的请求类型。
ViewSet搭配Mixin扩展类使用
问题来了,你肯定想着,上面这些都可以通过学过的Mixin扩展类和视图子类来简化代码啊,的确可以。
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView
urlpatterns = [
path('list/', BookView.as_view({"get": "list", "post": "create"})),
path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin
class BookView(ViewSet, GenericAPIView, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
增删改查都由几个Mixin扩展类实现了,但Mixin扩展类又依赖GenericAPIView
视图类,所以不能忘了继承GenericAPIView
。
ViewSet搭配GenericAPIView视图子类使用
这个就减少了很多继承代码。
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView
urlpatterns = [
path('list/', BookView.as_view({"get": "list", "post": "create"})),
path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.generics import RetrieveUpdateDestroyAPIView, ListCreateAPIView
class BookView(ViewSet, RetrieveUpdateDestroyAPIView, ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
增删改查由RetrieveUpdateDestroyAPIView
、ListCreateAPIView
或其父类实现。
GenericViewSet
GenericViewSet
没什么新花样,就是视图类继承了:
from rest_framework.viewsets import ViewSet, GenericViewSet
"""
GenericViewSet = ViewSet + GenericAPIView
"""
代码量少了那么一丢丢:
# 我们在 ViewSet 中这么继承
class BookView(ViewSet, GenericAPIView, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 现在通过 GenericViewSet 的继承变成了这样
class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
路由不变,所以就不贴出来了。
ModelViewSet
ModelViewSet
的话,其实也没什么新花样,就是对视图类的继承又多套了个马甲:
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet
"""
ModelViewSet = GenericViewSet + CreateModelMixin + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + ListModelMixin
"""
代码量相对于GenericViewSet
又少了那么一丢丢的继承代码:
# 我们在 GenericViewSet 中这么继承
class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 现在通过 ModelViewSet 的继承变成了这样
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
路由不变,所以就不贴出来了。
ReadOnlyModelViewSet
ReadOnlyModelViewSet
的话,用的比较少,当然了,相对于上面几个视图集,就是调整了下视图类的继承,它长这样:
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
"""
ReadOnlyModelViewSet = GenericViewSet + RetrieveModelMixin + ListModelMixin
"""
见名知意,ReadOnlyModelViewSet
只提供了查询单条和查询所有数据这两个方法,应用于某些只读的特殊场景。
当然,我们通过不同的视图类的搭配,也可以搞出来一些针对特殊场景的视图类,所以ReadOnlyModelViewSet
没啥特别的,这里不再多表。
self.action
视图集还提供了一个self.action
属性,这个属性就是当前请求的请求类型,我们可以在我们的视图类中,进行使用。
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class BookView(ModelViewSet):
queryset = Book.objects.all()
# serializer_class = BookSerializer
def get_serializer_class(self):
print(self.action)
# 根据请求的方法不同,我们在一些特殊场景中,可以使用不同的序列化器类
if self.action.lower() == "list":
return BookSerializer
else:
return Book2Serializer # 这个 Book2Serializer 你可以替换为其它的序列化器类
路由routers
上面视图集部分,我们将两个视图类合并成一个视图类。
那么本章节,路由部分继续优化路由,将两个路由合并成一个路由。
Django rest framework提供了两个路由类:
from rest_framework.routers import DefaultRouter, SimpleRouter
这两个路由类,用法一致,区别就是DefaultRouter
相比SimpleRouter
多提供个根路由,后面会演示。
快速上手
首先models不变,视图类和序列化器类也不变:
from rest_framework import serializers
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from .models import Book
class BookSerializer(serializers.ModelSerializer):
category_info = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True)
def get_category_info(self, obj):
return obj.get_category_display()
def get_publisher_info(self, obj):
pub_obj = obj.publisher
return {"id": pub_obj.id, "title": pub_obj.title}
def get_author_info(self, obj):
au_obj = obj.author.all()
return [{"id": i.id, "name": i.name} for i in au_obj]
class Meta:
model = Book
fields = "__all__"
extra_kwargs = {
"category": {"write_only": True},
"publisher": {"write_only": True},
"author": {"write_only": True},
}
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
来看这两个路由类怎么用。
上面说了,DefaultRouter
和SimpleRouter
用法一直,这里用SimpleRouter
来演示。
主要修改urls.py
:
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView
router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
"""
prefix[必填参数]:路由前缀
http://127.0.0.1:8000/book/list/
http://127.0.0.1:8000/book/list/1/ # 注意,如果没有最后的/,则会301重定向到带/的路由
viewset[必填参数]:视图类,注意不用写as_view
basename[可选参数]:路由别名
"""
print(router.urls)
"""
由下面打印结果可以看到,router对象内部会帮我们生成我们需要的那5个增删改查的接口
并且还很贴心的搞上了路由别名
[
<URLPattern '^list/$' [name='book-list-list']>,
<URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>
]
"""
urlpatterns = [
# path('list/', BookView.as_view({"get": "list", "post": "create"})),
# path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls
你也可以通过router.register
注册其它视图类。
这样,通过路由类和视图集类,就能让我们原来的两套视图类和两个路由都合并成一个,而且代码也非常简单。
添加路由的两种方式
上面的示例i,我们router.register
注册之后,用的是urlpatterns += router.urls
这种方式将router生成的路由列表合并到urlpatterns中。
那么除此之外,还有另外一种方式来实现:
from django.urls import path, include, re_path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView
router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
# print(router.urls)
urlpatterns = [
# 方式1,通过include分发实现
re_path(r"^", include(router.urls)),
# path('list/', BookView.as_view({"get": "list", "post": "create"})),
# path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# 方式2,列表合并实现
# urlpatterns += router.urls
DefaultRouter和SimpleRouter的区别
上面反复说道,这俩路由类用法一致,那总要有区别吧!是的,来看区别。
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView
# router = SimpleRouter()
# router.register(prefix="list", viewset=BookView, basename='book-list')
# print(router.urls)
"""
[
<URLPattern '^list/$' [name='book-list-list']>,
<URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>
]
"""
router = DefaultRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
print(router.urls)
"""
[
<URLPattern '^list/$' [name='book-list-list']>,
<URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>,
# 下面是多出来的
# 这俩支持加format参数
<URLPattern '^list\.(?P<format>[a-z0-9]+)/?$' [name='book-list-list']>,
<URLPattern '^list/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='book-list-detail']>,
http://127.0.0.1:8000/book/list?format=json
http://127.0.0.1:8000/book/list/?format=json
# 下面这俩就是所谓的 DefaultRouter 支持的根路由
<URLPattern '^$' [name='api-root']>,
<URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>
]
"""
urlpatterns = [
# path('list/', BookView.as_view({"get": "list", "post": "create"})),
# path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls
通过注释部分的打印结果,DefaultRouter
就多了api-root
路由。这个是干啥的呢?
# 首先这是经过路由分发后,当前app的根路由
http://127.0.0.1:8000/book
# 我们访问BookView视图类是这么访问的
http://127.0.0.1:8000/book/list
http://127.0.0.1:8000/book/list/1/
那么,使用DefaultRouter
的话,你访问当前app的跟路由,它返回:
# http://127.0.0.1:8000/book/
# 返回
{
"list": "http://127.0.0.1:8000/book/list/"
# 如果你还注册了其它路由,都会在这里列出来
}
它返回的是当前app中路由中所有你注册的路由。
如果你使用SimpleRouter
注册路由的话,你访问当前app的跟路由,它将返回一个Page not found at /book/
的大黄页。
这就是这两个路由类的区别。
action装饰器
上面那两个路由类只能帮我们生成增删改查的那五个接口。
http://127.0.0.1:8000/book/list
http://127.0.0.1:8000/book/list/1/
问题来了,如果我想在视图类中,自定义其它视图方法来处理一些特殊的需求,那路由怎么生成?即怎么生成如下这种路由:
http://127.0.0.1:8000/book/detail/ # 带pk的场景
http://127.0.0.1:8000/book/frist_five/ # 取前五条,不带pk的场景
http://127.0.0.1:8000/book/last_five/ # 取后五条,不带pk的场景
针对这种情况,路由不变,我们需要在视图类中通过装饰器的方式来完成。
# --------------------- views.py ---------------------
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action # 就这个装饰器
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(methods=['get', 'post'], detail=True, url_path='detail_book', url_name='detail_book')
def detail_book(self, request, pk):
""" 模拟特殊场景,根据pk查看某条记录的详情信息 """
return Response(f"返回{pk}的记录详情")
@action(methods=['get'], detail=False, url_path='first_five', url_name='first_five')
def first_five(self, request):
""" 模拟特殊场景,返回前五条记录 """
return Response(f"返回前五条记录")
# --------------------- urls.py ---------------------
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView
router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
print(router.urls)
"""
[
<URLPattern '^list/$' [name='book-list-list']>,
<URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>,
# 下面两个是加了action装饰器后,生成的路由
# 下面这个不带pk的路由倒没啥可说的
<URLPattern '^list/first_five/$' [name='book-list-first_five']>,
# 但注意这个带pk的,它的pk不跟咱们通常定义的一样,在路由的最后,它是在中间,这点要特别注意
<URLPattern '^list/(?P<pk>[^/.]+)/detail_book/$' [name='book-list-detail_book']>
]
"""
urlpatterns = [
# path('list/', BookView.as_view({"get": "list", "post": "create"})),
# path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls
关于action
装饰器的几个参数介绍:
methods
,允许哪些http请求类型访问当前视图方法。detail
,如果是True,则表示接收参数,那么你视图方法中也别忘了接收;如果是False,表示不需要参数。url_path
,路由路径,如果不指定,则默认使用当前视图方法名作为路由路径。url_name
,路由别名。
你可以通过下面的url访问了:
http://127.0.0.1:8000/book/list/first_five
http://127.0.0.1:8000/book/list/1/detail_book
action中,如果URL带正则的话,你需要注意:
class XXXXSet(GenericViewSet):
# 注意,如果url_path是一个带正则的路径,那么我们不必写后面的终止符$,drf内部会自带终止符
# 即,我们不要这么写
# url_path="query/(?P<order_number>[0-9]+)/$"
# drf会渲染成
# xxx/xxx/query/(?P<order_number>[0-9]+)/$/$
# 所以我们直接这么写即可
# url_path="query/(?P<order_number>[0-9]+)"
# drf会渲染成
# xxx/xxx/query/(?P<order_number>[0-9]+)/$
@action(detail=False, methods=['get'], url_path="query/(?P<order_number>[0-9]+)")
def query(self, request, order_number):
pass
路由类生成URL的方式
SimpleRouter
DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。