文章目录
基于类的视图的一个关键好处是:它们允许您组合一些可重用的行为。而DRF通过提供大量预构建的视图来利用这一点,为我们提供了一些常用的模式,我们要做的就是选择合适的视图类进行继承即可。
一、APIView类
DRF提供的APIView
类是Django的View
类的子类。它和一般的View
类有以下不同:
- 被传入到处理方法的请求不是Django的
HttpRequest
类的实例,而是DRF的Request类的实例。 - 处理方法可以返回DRF的
Response
,而不只是Django的HttpRequest
。视图会管理内容协议,给响应设置正确的渲染器。 - 任何
APIException
异常都会被捕获,并且传递给合适的响应。 - 传入的请求将经过身份验证,并在将请求分派给处理方法之前运行适当的权限或限频检查。
使用APIView
类和使用一般的View
类非常相似,通常,传入的请求会被分发到合适的处理方法比如get()
、post()
。我们之前一直的视图,都是继承了APIView
。
二、GenericAPIView类
此类是APIView
的子类,对APIView
类进行了扩展,添加了一些具体通用视图(后面会学到,每个视图都有具体的应用场景)都会用到的的方法。而每个具体通用视图则是通过将GenericAPIView
与一个或多个mixin类组合来构建的,即GenericAPIView
与这些mixin类是具体通用视图的父类。
2.1 常用属性
queryset:
设置从该视图返回对象的查询集。 比如
queryset = Book.objects.all()
,之后就会从该查询集中获取模型对象。必须设置该属性或者重写
get_queryset()
方法。如果要覆盖一个视图方法,那么就应该调用
get_queryset()
方法而不是直接访问该属性,这是很重要的。因为DRF第一次求出queryset
的值后,就会缓存起来给所有后续的请求使用。serializer_class:
设置用于验证、反序列化输入以及序列化输出的Serializer类。 必须设置此属性或者重写get_serializer_class() 方法。
lookup_field:查找单个模型对象时,参考的字段。默认是
'pk'
。也就是说,从路由捕获到参数命名为pk后,传入视图中,在按照pk获取模型对象时,DRF会自动将pk作为查找条件找出目标对象。不需要我们再去写
filter(pk=pk)
了。
2.2 常用方法
get_object(self)
:从
queryset
属性设置的基本查询集中获取实例,然后使用lookup_field
属性过滤基本查询集,最后返回一个对象实例。get_queryset(self)
:返回由
queryset
属性指定的模型类的查询集。get_serializer_class(self)
:默认返回
serializer_class
属性的值,即指定的序列化器类。get_serializer(self, instance=None, data=None, many=False, partial=False)
:返回
serializer_class
属性指定的序列化器类的实例。
我们还是通过一个书籍相关的例子来理解上面的方法(模型类和序列化器类不是重点,自己写啊?):
class BookDetailView(GenericAPIView):
# 设置要用到的查询集
queryset = Book.objects.all()
# 设置要用到的序列化器
serializer_class = BookSerializer
# 设置查找模型对象用的字段,默认为“pk”
# 因为我们在路由中捕获的参数命名为了“id”,所以这里也要改为“id”
lookup_field = "id"
def get(self, request, id):
"""返回单本书的信息"""
# 获取单个对象,会自动通过id进行查找
book = self.get_object() # 相当于 book = Book.objects.filter(id=id).first()
# 获取序列化器类并实例化
book_ser = self.get_serializer(book)
return Response(book_ser.data)
def put(self, request, id):
"""修改单本书的信息"""
book = self.get_object()
# 获取序列化器类并实例化,并传入前端提交的数据
book_ser = self.get_serializer(instance=book, data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
def delete(self, request, id):
"""删除单本书籍"""
# 自动通过id删除对象
self.get_object().delete()
return Response({'status': 100, 'msg': '删除成功'})
class BooksView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
"""获取所有书籍"""
# 获取模型类的查询集
book_list = self.get_queryset()
book_ser = self.get_serializer(book_list, many=True)
return Response(book_ser.data)
def post(self, request):
"""添加一本书籍"""
book_ser = self.get_serializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
三、Mixin类
mixin类为具体通用视图提供了一些方法,比如create()
、update()
等。这些mixin类是专门用于组合构建具体通用视图的。
mixin类可以从rest_frame.mixins
导入。
ListModelMixin
:提供一个
list(request, *args, **kwargs)
方法,返回查询集。CreateModelMixin
:提供
create(request, *args, **kwargs)
方法,实现创建和保存一个新的model实例。RetrieveModelMixin
:提供一个
retrieve(request, *args, **kwargs)
方法,实现在响应中返回一个现有模型实例。UpdateModelMixin
:提供一个
update(request, *args, **kwargs)
方法,该方法实现更新和保存现有模型实例。还提供了一个
partial_update(request, *args, **kwargs)
方法,它类似于update
方法,只是用于更新的所有字段都是可选的。这允许支持HTTP PATCH请求。DestroyModelMixin
:提供一个
destroy(request, *args, **kwargs)
方法,该方法实现删除现有模型实例。
四、通用的具体视图类
以下类是具体的通用视图。这些才是我们通常会用到的,除非需要深度定制,否则直接继承使用即可。
这些视图类可以从rest_framework.generics
导入。
CreateAPIView
:用于创建模型实例的具体视图,提供一个post方法处理程序。
比如,我们要创建一本书,就只需要继承
CreateAPIView
,它替我们实现了post方法和创建模型实例保存到数据库的操作:class BookDetailView(CreateAPIView): # 设置要用到的模型类 queryset = Book.objects.all() # 设置要用到的序列化器 serializer_class = BookSerializer # 设置查找模型对象用的字段,默认为”pk“, 这里改为了”id“ lookup_field = "id"
之后设置好路由,使用post方法提交书籍信息就可以直接创建书籍记录了。
下面各种类的使用方法与之类似,需要哪个继承哪个即可。
ListAPIView
:用于列出查询集的具体视图(获取全部),提供一个get方法处理程序。
RetrieveAPIView
:用于检索模型实例的具体视图(获取指定的单个),提供一个get方法处理程序。
DestroyAPIView
:用于删除模型实例的具体视图,提供一个delete方法处理程序。
UpdateAPIView
:用于更新模型实例的具体视图,提供一个put和patch方法处理程序。
ListCreateAPIView
:列出查询集或创建模型实例的具体视图,提供一个get和post方法的处理程序。
也即是说,它是
ListAPIView
和CreateAPIView
的结合。
下面的类也是如此,仅仅是为了书写方便,将多个类结合到了一起而已。
RetrieveUpdateAPIView
:用于检索、更新模型实例的具体视图,提供get, put和patch方法的处理程序。
RetrieveDestroyAPIView
:用于检索或删除模型实例的具体视图,提供 get 和 delete 方法的处理程序。
RetrieveUpdateDestroyAPIView
:用于检索、更新或删除模型实例的具体视图,提供get, put, patch 和delete方法的处理程序。
最后,我们来看看使用具体通用视图类后,之前的书籍例子成了什么样?
from rest_framework.generics import CreateAPIView,ListAPIView, RetrieveUpdateDestroyAPIView
from quickstart.models import Book
from quickstart.serializers import BookSerializer
# Create your views here.
class BookDetailView(CreateAPIView, RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = "id"
class BooksView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
没错,就是这么简洁。但其实还可以更加简洁。
上面的代码中queryset
、serializer_class
等属性还是重复的,说到底,还是因为get()
方法只有一个,但我们需要通过http get方法获取单本书籍或者获取书籍列表,二者发生了冲突,所以只能分成两个视图分别处理。这一点DRF也为我们考虑到了,因此提供了视图集这一功能。
五、视图集和路由器
5.1 视图集(ViewSet)
DRF允许我们将一组相关视图的逻辑合并到一个类中,这个类叫做ViewSet。
ViewSet只是一种基于类的View类型,它不提供任何方法处理程序,如get()
或post()
,而是提供诸如list()
和create()
等处理程序。因此,需要在路由中绑定http请求方法到相应的处理程序。
比如通过as_view()
方法,get、post是http请求方法,list、create是对应的处理程序:
urlpatterns = [
path('snippets/', BookViewSet.as_view(actions={
'get': 'list',
'post': 'create'})
)]
但通常,我们通过路由器(Router
类的实例)注册视图并绑定方法,而不是像以前一样,使用path()
等方法显式地绑定视图。
5.2 路由器(Router)
只要我们使用的是ViewSet类(准确的说因该是ViewSetMixin
类,因为as_view()
方法是被它重写的)而不是View类,我们就不需要自己设计URL。我们需要做的就是使用路由器注册相应的视图集,然后让它执行其余操作。
5.2.1 使用方法
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from quickstart import views
# 创建一个路由器并注册我们的视图集
router = DefaultRouter()
# 第一个参数是url的前缀,后一个是对应的视图
router.register(r'books', views.BooksViewSet) # 前缀不用加斜杠
# API url现在由路由器自动确定
urlpatterns = [
path('', include(router.urls))
]
# 也可以不使用include,直接加进去
# urlpatterns += router.urls
上面的示例将自动生成以下URL模式,保存在router.urls
中:
- URL pattern: ^books/$ Name: ‘book-list’
- URL pattern: ^books/{pk}/$ Name: ‘book-detail’
DefaultRouter
类也自动为我们创建了API ROOT视图,即根路径及其对应的视图。所以原来如果有API ROOT路由或视图,现在可以删掉了。
5.2.2 DefaultRouter类
URL 样式 | HTTP 方法 | 动作 | URL 名称 |
---|---|---|---|
[.format] | GET | automatically generated root view | api-root |
{prefix}/[.format] | GET | list | {basename}-list |
POST | create | ||
{prefix}/{methodname}/[.format] | GET, or as specified by `methods` argument | `@list_route` decorated method | {basename}-{methodname} |
{prefix}/{lookup}/[.format] | GET | retrieve | {basename}-detail |
PUT | update | ||
PATCH | partial_update | ||
DELETE | destroy | ||
{prefix}/{lookup}/{methodname}/[.format] | GET, or as specified by `methods` argument | `@detail_route` decorated method | {basename}-{methodname} |
生成后缀:
DefaultRouter
还可以生成可选的.json样式格式后缀的路由。比如从浏览器访问路径
books.json
,他就会以json格式返回数据,而不是默认的HTML页面。删除url最尾部的斜杠:
# 在实例化时,传入以下参数 router = DefaultRouter(trailing_slash=False)
5.2.3 action装饰器
视图集可以通过使用@action
装饰器装饰一个方法,来标记用于路由的额外动作。这些额外的操作将包含在生成的路由中。
例如,给定UserViewSet
类上的set_password
方法:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import action
class UserViewSet(ModelViewSet):
...
@action(methods=['post'], detail=True)
def set_password(self, request, pk=None):
...
参数说明:
methods
:此操作响应的HTTP方法名称列表,默认仅为GET。detail
:确定此操作是否适用于单个实例请求,False表示请求的是集合(多个实例)。
生成的路由如下:
- URL 路径:
^users/{pk}/set_password/$
- URL 名称:
'user-set-password'
默认情况下,URL路径基于方法名,URL名称则是“ViewSet.basename
-方法名”。
如果您不想使用这两个值中的任何一个的默认值,您可以将url_path
和url_name
参数提供给@action
装饰器。
5.3 ModelViewSet类
学习了这么多视图类,各类之间又有着复杂的继承关系,现在大脑一片混乱??,不知道该继承哪些类。但好在DRF已经提前考虑好了这个问题,为我们提供了ModelViewSet
类。
ModelViewSet
类继承自GenericAPIView
,并继承了各种mixin类。因此,它包含了各种操作的实现。
所以,我们如果确定要使用视图集,那么就继承ModelViewSet
类即可。
ModelViewSet
类提供的操作有list()
、retrieve()
、create()
、update()
、partial_update()
和destroy()
。
最后,看一眼书籍例子的最终形态:
视图:
from rest_framework.viewsets import ModelViewSet
from quickstart.models import Book
from quickstart.serializers import BookSerializer
class BookDetailView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = "id"
路由:
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from quickstart import views
router = DefaultRouter()
router.register(r'books', views.BookDetailView)
urlpatterns = [
path('', include(router.urls))
]