[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
但这样实现有一个问题,关于serialize_class,显然,当获取课程列表时,只需要传回去所有课程的简要信息,如课程名字,老师,封面等等,但当获取课程的具体信息,我们还要将他们的章节以及相关下载资料(很明显,章节是另外一个model,有一个外键指向course),这些信息会很多,在获取课程列表,将这些传回去显然是不理智的。那么,还需要再定义一个CourseDetailSerializer,在get /courses/的时候,使用CourseSerializer,在get /courses/id/的时候,使用CourseDetailSerializer。
 
那么,问题来了,我们怎么获取到是哪个action方法?这个时候,viewset就出场了!
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.

posted @ 2022-06-25 14:43  郝壹贰叁  阅读(170)  评论(0编辑  收藏  举报