[Django] 21 - DRF: APIView vs Viewsets
如何加快开发?
需要深度理解。
Ref: Django的DRF(二):APIView(一级视图)、GenericAPIView(二级视图)、三级视图、ViewSets(视图集)、Router(路由)
一.APIView(一级视图)
DRF的APIViews官网:https://www.django-rest-framework.org/api-guide/views/
class BookListAPIView(APIView):
def get(self,request): # 查询所有的书籍 books = BookInfo.objects.all() # 将对象列表转换成字典列表 serializer = BookInfoModelSerializer(instance=books,many=True) # 返回响应 return Response(serializer.data) def post(self,request): # 获取参数 data_dict = request.data # 创建序列化器 serializer = BookInfoModelSerializer(data=data_dict) # 校验 serializer.is_valid(raise_exception=True) # 入库 serializer.save() # 返回响应 return Response(serializer.data,status=status.HTTP_201_CREATED)
# 序列化器和APIView实现详情视图 class BookDetailAPIView(APIView):
def get(self,request,book_id): #1 获取书籍 book = BookInfo.objects.get(pk=book_id) #2 创建序列化对象 serializer = BookInfoModelSerializer(instance=book) #3 返回响应 return Response(serializer.data,status=status.HTTP_200_OK)
def put(self,request,book_id): # 获取数据,获取对象 dict_data = request.data book = BookInfo.objects.get(pk=book_id) # 创建序列化对象 serializer = BookInfoModelSerializer(instance=book,data=dict_data) # 校验,入库 serializer.is_valid(raise_exception=True) serializer.save() #返回响应 return Response(serializer.data,status=status.HTTP_201_CREATED)
def delete(self,request,book_id): BookInfo.objects.get(pk=book_id).delete() return Response(status=status.HTTP_204_NO_CONTENT)
二.GenericAPIView(二级视图)
DRF的GenericAPIView官网:https://www.django-rest-framework.org/api-guide/generic-views/#genericapiview
# 使用二级视图GenericAPIView实现列表视图 class BookListGenericAPIView(GenericAPIView):
# 提供公共的属性, 应该是默认成员变量 ----> 潜规则 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer def get(self, request): # 查询所有的书籍(2种) # books = self.queryset books = self.get_queryset()
# 将对象列表转换成字典列表(3种方法) # serializer = self.serializer_class(instance=books, many=True) # serializer = self.get_serializer_class()(instance=books, many=True) serializer = self.get_serializer(instance=books, many=True) # 返回响应 return Response(serializer.data) def post(self, request): # 获取参数 data_dict = request.data # 创建序列化器 serializer = self.get_serializer(data=data_dict) # 校验 serializer.is_valid(raise_exception=True) # 入库 serializer.save() # 返回响应 return Response(serializer.data, status=status.HTTP_201_CREATED)
# 使用二级视图GenericAPIView实现详情视图 class BookDetailGenericAPIView(GenericAPIView):
# 提供通用的属性 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
def get(self, request, pk): # 1 获取书籍 # get_object()根据pk到queryset中取出书籍对象 book = self.get_object() # 2 创建序列化对象 serializer = self.get_serializer(instance=book) # 3 返回响应 return Response(serializer.data, status=status.HTTP_200_OK) def put(self, request, pk): # 获取数据,获取对象 dict_data = request.data book = self.get_object() # 创建序列化对象 serializer = self.get_serializer(instance=book, data=dict_data) # 校验,入库 serializer.is_valid(raise_exception=True) serializer.save() # 返回响应 return Response(serializer.data, status=status.HTTP_201_CREATED) def delete(self, request, pk): self.get_object().delete() return Response(status=status.HTTP_204_NO_CONTENT)
-
定义的属性和方法
1)支持定义的属性:
列表视图与详情视图通用:
queryset 列表视图的查询集
serializer_class 视图使用的序列化器
列表视图使用:
pagination_class 分页控制类
filter_backends 过滤控制后端
详情页视图使用:
lookup_field 查询单一数据库对象时使用的条件字段,默认为’pk’
lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
2)提供的方法:
列表视图与详情视图通用:
get_queryset(self):返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写
get_serializer_class(self):返回序列化器类,默认返回serializer_class,可以重写
get_serializer(self, args, *kwargs):返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法。
注意,在提供序列化器对象的时候,REST framework会向对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
详情视图使用:
get_object(self): 返回详情视图所需的模型类数据对象,默认使用lookup_field参数来过滤queryset。 在试图中可以调用该方法获取详情信息的模型类对象。
若详情访问的模型类对象不存在,会返回404。
该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。
扩展类 (MiXin)
DRF的MiXin官网:https://www.django-rest-framework.org/api-guide/generic-views/#mixins
MiXin的特点:
1.mixin类提供用于提供基本视图行为(列表视图,详情视图)的操作。
2.配合二级视图GenericAPIView使用的常见的mixin类:
REST 变为了如上的名字,形成了mapping。
[GET, POST]
from rest_framework.mixins import ListModelMixin, CreateModelMixin class BookListMixinGenericAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
# 提供公共的属性 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer def get(self, request): return self.list(request) # 如果没有骚操作,直接使用返回数据库一一对应的内容,谓之快! def post(self, request): return self.create(request)
(1) ListModelMixin
该 Mixin 的 list方法会对数据进行过滤和分页。
class ListModelMixin(object): """ List a queryset. """
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)
(2) CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。
class CreateModelMixin(object): """ Create a model instance. """
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 {}
[GET, PUT, DELETE]
# mixin和二级视图GenericAPIView,实现详情视图 class BookDetailMixinGenericAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): # 提供通用的属性 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer lookup_field = 'id' def get(self, request, id): return self.retrieve(request) def put(self, request, id): return self.update(request) def delete(self, request, id): return self.destroy(request)
(3) RetrieveModelMixin
提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。
class RetrieveModelMixin(object): """ Retrieve a model instance. """
def retrieve(self, request, *args, **kwargs): # 获取对象,会检查对象的权限 instance = self.get_object() # 序列化 serializer = self.get_serializer(instance) return Response(serializer.data)
(4) UpdateModelMixin
提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。
class UpdateModelMixin(object): """ Update a model instance. """
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)
(5) DestroyModelMixin
提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。
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()
三.通用视图(三级视图)
DRF的Concrete View Classes官网:https://www.django-rest-framework.org/api-guide/generic-views/#concrete-view-classes
也就是直接省掉了method,只定义成员变量了 ... 当然了,也定义了自己的类。
# 三级视图实现列表视图 class BookListThirdView(ListAPIView, CreateAPIView): # 提供公共的属性 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
# 三级视图实现详情视图 class BookDetailThirdView(RetrieveAPIView, UpdateAPIView, DestroyAPIView): # 提供通用的属性 queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer lookup_field = 'id'
四.ViewSets(视图集)
DRF的ViewSets官网:https://www.django-rest-framework.org/api-guide/viewsets/
GenericAPIView的不足之处
Ref: Django Rest Framework(3)-----APIView与Viewsets
既然GenericAPIView以及它相关的View已经完成了许许多多的功能,那么还要ViewSet干嘛!
首先,我们思考一个问题,同样上面的例子,我们在功能上,要获取课程的列表,也要获取某个课程的具体信息。那么怎么实现,按照GenericAPIView,我们可以这样实现:
class CourseView(ListAPIView,RetrieveAPIView):
# 只需要在上面的基础上,再继承RetrieveAPIView就ok了。
queryset = Course.objects.all()
serialize_class = CourseSerializer
from rest_framework import viewsets import...
class CourseViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): queryset = Course.objects.all() def get_serializer_class(self): # 重写get_serializer_class方法 if self.action == 'list': # <---- return CourseSerializer return CourseDetailSerializer
使用ViewSet
类比使用View
类有两个主要优点。
-
- 重复逻辑可以组合成一个类。在上面的示例中,我们只需要指定
queryset
一次,它将在多个视图中使用。
- 重复逻辑可以组合成一个类。在上面的示例中,我们只需要指定
-
- 通过使用路由器,我们不再需要处理自己的URL连接。
重复逻辑组成一个类
Ref: Django Rest Framework Series - Viewsets and Routers with React Front-end Example - Part-4【讲得足够详细】
Ref: Django通用视图APIView和视图集ViewSet的介绍和使用
-
ViewSet
相比Mixin,不再使用post, get, etc,也不需要其作为”入口“,这里直接做了默认方法了呢。
# 使用viewset实现获取所有和单个 class BooksViewSet(viewsets.ViewSet): """ A simple ViewSet for listing or retrieving books. """
queryset = BookInfo.objects.all() # 统一写在这里如何?
def list(self, request): # queryset = BookInfo.objects.all()
# todo: preprocess queryset serializer = BookInfoModelSerializer(instance=queryset, many=True) return Response(serializer.data) def retrieve(self, request, pk=None): # queryset = BookInfo.objects.all() book = get_object_or_404(queryset, pk=pk)
# todo: preprocess queryset serializer = BookInfoModelSerializer(instance=book) return Response(serializer.data)
设置路由:
url(r'^viewset/$', views.BooksViewSet.as_view({'get': 'list'})), url(r'^viewset/(?P<pk>\d+)/$', views.BooksViewSet.as_view({'get': 'retrieve'})),
-
ModelViewSet
方法都省去了,毕竟就是那固定的五个。
# ModelViewSet实现列表视图,详情视图功能 class BookModelViewSet(ModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
# Rewrite this method to customize the objects.
def get_queryset(self):
''' Define custom QuerySet'''
return BookInfo.object.all()
# 在视图集中,除了上述默认的方法动作外,还可以添加自定义动作,道理是一样的。
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('pk')
return get_object_or_404(BookInfo, slug=item)
设置路由:
url(r'^model_viewset/$', views.BookModelViewSet.as_view({'get': 'list',"post":"create"})), url(r'^model_viewset/(?P<pk>\d+)/$', views.BookModelViewSet.as_view({'get': 'retrieve','put':'update','delete':'destroy'})),
-
ReadOnlyModelViewSet
只读属性,例如:对于ingredient from another independent system而言, only readable is completely enough.
# 使用ReadOnlyModelViewSet实现获取单个和所有 class BooksReadOnlyModelViewSet(ReadOnlyModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
设置路由:
url(r'^readonly_viewset/$', views.BooksReadOnlyModelViewSet.as_view({'get': 'list'})), url(r'^readonly_viewset/(?P<pk>\d+)/$', views.BooksReadOnlyModelViewSet.as_view({'get': 'retrieve'})),
Router(路由)
REST framework提供了两个router:SimpleRouter和DefaultRouter。
-
DefaultRouter
r'^readonly_viewset/(?P<pk>\d+)/$' 看着都烦!所以,router.register简单多了,如下所示。
from . import views from rest_framework.routers import DefaultRouter urlpatterns = [ ] # 创建路由对象 router = DefaultRouter()
# 注册视图表(该视图集的路由前缀,viewset, 路由名称的前缀) router.register('books', views.BookModelViewSet, basename='test') urlpatterns += router.urls
# 输出结果: print(urlpatterns)
如下可知,Routers能够帮助我们快速实现路由信息。
# 列表路由及其json形式 [<RegexURLPattern test-list ^books/$>, <RegexURLPattern test-list ^books\.(?P<format>[a-z0-9]+)/?$>, # 详情路由及其json形式 <RegexURLPattern test-detail ^books/(?P<pk>[^/.]+)/$>, <RegexURLPattern test-detail ^books/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>, # 根路由及其json形式 <RegexURLPattern api-root ^$>, <RegexURLPattern api-root ^\.(?P<format>[a-z0-9]+)/?$>]
DefaultRouter与SimpleRouter的区别是:DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据
以上,嫌弃太多?那就看看下面这个如何 by SimpleRouter。
[<RegexURLPattern test-list ^books/$>, <RegexURLPattern test-detail ^books/(?P<pk>[^/.]+)/$>]
-
SimpleRouter
from . import views from rest_framework.routers import SimpleRouter urlpatterns = [ ] # 创建路由对象 router = SimpleRouter()
# 注册视图表 router.register('books', views.BookModelViewSet, basename='test') urlpatterns += router.urls
# 输出结果: print(urlpatterns)
ViewSet包含action装饰器
class BookModelViewSet(ModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoModelSerializer
# 需求:获取阅读量大于111的书籍 @action(methods=['get'], detail=False) # 生成路由规则:前缀/方法名 def bread_book(self, request): # 获取指定书籍 books = BookInfo.objects.filter(bread__gt=111) # 创建序列化器对象 serializer = self.get_serializer(instance=books, many=True) # 返回响应 return Response(serializer.data)
# 需求:修改书籍编号为13的,阅读量为222 @action(methods=['put'], detail=True) # 生成路由规则:前缀/{pk}/方法名/ def update_book_bread(self, request, pk): # 获取参数 book = self.get_object() data = request.data # 创建序列化器对象 serializer = self.get_serializer(instance=book, data=data, partial=True) # 校验,入库 serializer.is_valid(raise_exception=True) serializer.save() # 返回响应 return Response(serializer.data, status=status.HTTP_201_CREATED)
相较于之前的”自定义rewrite default method,这里的路由生成已经在action中默认定义。
from . import views from rest_framework.routers import SimpleRouter
urlpatterns = [ ] # 创建路由对象 router = SimpleRouter()
# 注册视图表 router.register('books', views.BookModelViewSet, basename='test') urlpatterns += router.urls
# 输出结果: print(urlpatterns)
End.