视图家族类概括
视图家族
from rest_framework import views, mixins, generics, viewsets
views:视图类 - APIView,GenericAPIView(在generics中)
mixins:视图工具类 - CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
generics:工具视图类 - 九个类
viewsets:视图集 - 两个视图集基类,两个视图集子类,一个工具类
GenericAPIview讲解
回顾APIView类:1)拥有View的所有 2)重写as_view 3)重写dispatch 4)一系列类属性
GenericAPIView类:1)拥有APIView的所有
不能直接写到objects结束,因为objects结束时,不是QuerySet对象,而是Manager对象,但 .all() 和 .filter() 后一定是QuerySet对象
GenericAPIView源码:
# GenericAPIView源码 class GenericAPIView(views.APIView): """ Base class for all other generic views. """ # You'll need to either set these attributes, # or override `get_queryset()`/`get_serializer_class()`. # If you are overriding a view method, it is important that you call # `get_queryset()` instead of accessing the `queryset` property directly, # as `queryset` will get evaluated only once, and those results are cached # for all subsequent requests. queryset = None serializer_class = None # If you want to use object lookups other than pk, set 'lookup_field'. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk' lookup_url_kwarg = None # The filter backend classes to use for queryset filtering filter_backends = api_settings.DEFAULT_FILTER_BACKENDS # The style to use for queryset pagination. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS def get_queryset(self): """ Get the list of items for this view. This must be an iterable, and may be a queryset. Defaults to using `self.queryset`. This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests. You may want to override this if you need to provide different querysets depending on the incoming request. (Eg. return a list of items that is specific to the user) """ assert self.queryset is not None, ( "'%s' should either include a `queryset` attribute, " "or override the `get_queryset()` method." % self.__class__.__name__ ) queryset = self.queryset if isinstance(queryset, QuerySet): # Ensure queryset is re-evaluated on each request. queryset = queryset.all() return queryset def get_object(self): """ Returns the object the view is displaying. You may want to override this if you need to provide non-standard queryset lookups. Eg if objects are referenced using multiple keyword arguments in the url conf. """ queryset = self.filter_queryset(self.get_queryset()) # Perform the lookup filtering. lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field assert lookup_url_kwarg in self.kwargs, ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % (self.__class__.__name__, lookup_url_kwarg) ) filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} obj = get_object_or_404(queryset, **filter_kwargs) # May raise a permission denied self.check_object_permissions(self.request, obj) return obj def get_serializer(self, *args, **kwargs): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. """ serializer_class = self.get_serializer_class() kwargs['context'] = self.get_serializer_context() return serializer_class(*args, **kwargs) def get_serializer_class(self): """ Return the class to use for the serializer. Defaults to using `self.serializer_class`. You may want to override this if you need to provide different serializations depending on the incoming request. (Eg. admins get full serialization, others get basic serialization) """ assert self.serializer_class is not None, ( "'%s' should either include a `serializer_class` attribute, " "or override the `get_serializer_class()` method." % self.__class__.__name__ ) return self.serializer_class def get_serializer_context(self): """ Extra context provided to the serializer class. """ return { 'request': self.request, 'format': self.format_kwarg, 'view': self } def filter_queryset(self, queryset): """ Given a queryset, filter it with whichever filter backend is in use. You are unlikely to want to override this method, although you may need to call it either from a list view, or from a custom `get_object` method if you want to apply the configured filtering backend to the default queryset. """ for backend in list(self.filter_backends): queryset = backend().filter_queryset(self.request, queryset, self) return queryset @property def paginator(self): """ The paginator instance associated with the view, or `None`. """ if not hasattr(self, '_paginator'): if self.pagination_class is None: self._paginator = None else: self._paginator = self.pagination_class() return self._paginator def paginate_queryset(self, queryset): """ Return a single page of results, or `None` if pagination is disabled. """ if self.paginator is None: return None return self.paginator.paginate_queryset(queryset, self.request, view=self) def get_paginated_response(self, data): """ Return a paginated style `Response` object for the given output data. """ assert self.paginator is not None return self.paginator.get_paginated_response(data)
Mixin视图工具类
# mixins视图工具类 单查 群查 单增 from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, CreateModelMixin class CarReadCreateGenericAPIView(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericAPIView): queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer lookup_url_kwarg = 'pk' # 群查 # """ def get(self, request, *args, **kwargs): # car_query = self.get_queryset() # car_ser = self.get_serializer(car_query, many=True) # return APIResponse(results=car_ser.data) return self.list(request, *args, **kwargs) # """ # 单查 def get(self, request, *args, **kwargs): # car_obj = self.get_object() # car_ser = self.get_serializer(car_obj) # return APIResponse(results=car_ser.data) response = self.retrieve(request, *args, **kwargs) # retrieve返回的是 Response(serializer.data) return APIResponse(results=response.data) # 也可以返回自定义的APIResponse # 单增 def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)
Generics工具视图类
# Generics工具视图类 # 单独完成单查接口 from rest_framework.generics import RetrieveAPIView class CarRetrieveAPIView(RetrieveAPIView): # RetrieveAPIView内部已经写了get单查方法,不需要在写了 queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer lookup_url_kwarg = 'pk' # 单独完成群查接口 from rest_framework.generics import ListAPIView class CarListAPIView(ListAPIView): # ListAPIView内部已经写了get单查方法,不需要在写了 queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer # 单查(get)、单整体改(put)、单局部改(patch)、单删接口(delete) from rest_framework.generics import RetrieveUpdateDestroyAPIView class CarRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView): # RetrieveUpdateDestroyAPIView内部写好了 queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer
总结:
1、APIView:
1)拥有View的所有 2)重写as_view 3)重写dispatch 4)一系列类属性
2、GenericAPIView
1)继承APIView,所以拥有APIView的所有
2)get_queryset方法,配置queryset类属性,提供视图类相关的Models
3)在第二条基础上,get_object方法,配置lookup_url_kwarg类属性,提供视图类相关的具体Model
4)get_serializer方法,配置serializer_class类属性,提供视图类相关的序列化对象
总结:GenericAPIView就是在APIView基础上额外提供了三个方法,三个类属性,如果不配合视图工具类,体现不出优势
目的:视图中的增删改查逻辑相似,但操作的资源不一致,操作资源就是操作 资源对象们、资源对象以及资源相关的序列化类,
将这三者形成配置,那操作逻辑就一致,就可以进行封装
3、mixins视图工具类们:五个类,六个方法
1)要配合GenericAPIView类使用,将单查、群查、单增,单整体改,单局部改,单删六个接口
封装成retrieve、list、create、update、partial_update、destroy六个方法
原因:六个方法的实现体,调用的方法就是GenericAPIView提供的,所以要配合GenericAPIView类使用
4、generics工具视图类们:九种组合
1)帮我们将不同个数不同种类的mixins与GenericAPIView进行组合
2)不同的组合帮我们实现好对应的get、post、put、patch、delete方法
3)需要我们自己配置三个类属性即可:queryset、serializer_class、lookup_url_kwarg
class CarView(APIView): def get(self): obj, ser, response, class CarView(GenericAPIView): # 不会出现,中间产物 queryset, serializer_class, lookup_url_kwarg, def get(self): obj, ser, response, class CarView(RetrieveModelMixin, GenericAPIView): # 自定义组合,可以超过九种 queryset, serializer_class, lookup_url_kwarg, def get(self): self.retrieve() class CarView(RetrieveAPIView): # 最终产物,系统只提供了九种组合,RetrieveAPIView是其中一种 queryset, serializer_class, lookup_url_kwarg,
视图集
单查群查合起来:
# 视图集 from rest_framework.viewsets import ViewSetMixin, GenericViewSet, ViewSet, ModelViewSet class CarReadOnlyAPIView(RetrieveModelMixin, ListModelMixin, GenericViewSet): # def many_get(self, request, *args, **kwargs): # return self.list(request, *args, **kwargs) # # def single_get(self, request, *args, **kwargs): # return self.retrieve(request, *args, **kwargs) queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer
视图集总结
核心:视图集都继承了 ViewSetMixin类,该类重写了as_view方法,相比APIView的as_view方法,额外多出了一个参数actions
as_view({'get': 'list'}) 传入的{'get': 'list'}就被actions介绍,原理是将get请求映射给视图类的list函数进行处理
1)为什么有GenericViewSet和ViewSet两个视图集基类
GenericViewSet(ViewSetMixin, GenericAPIView),该分支严格满足资源接口
ViewSet(ViewSetMixin, APIView),该分支满足的接口与资源Model类关系不是特别密切:登录接口、短信验证码接口
2)ReadOnlyModelViewSet,ModelViewSet两个视图集子类,就是做个一堆mixin与GenericViewSet相结合,
自己在urls文件中配置as_view设置映射关系
# 六大接口 class CarModelViewSet(ModelViewSet): queryset = models.Car.objects.filter(is_delete=False).all() serializer_class = serializers.CarModelSerializer # 分析:从实际开发角度分析不合理点 # 1)没有群增,群整体改,群局部改,群删四个接口 # 2)删除操作视图集默认走的destroy方法是将资源从数据库中删除,通常一个做字段is_delete字段修改表示删除 # 3)响应的结果只有数据,没有数据状态码和状态信息 # 解决1, # 群整体改,群局部改,全删三个接口可以独立成三个方法 def many_update(self, request, *args, **kwargs): return APIResponse(msg='这个地方是群整体改,你会写!') def many_partial_update(self, request, *args, **kwargs): return APIResponse(msg='这个地方是群局部改,你会写!') def many_destroy(self, request, *args, **kwargs): return APIResponse(msg='这个地方是群删,你会写!') # 群增与单增必须公用一个接口,都要走create方法 - 重写create方法,用逻辑进行拆分 def create(self, request, *args, **kwargs): request_data = request.data if isinstance(request_data, list): car_ser = self.get_serializer(data=request_data, many=True) car_ser.is_valid(raise_exception=True) car_obj = car_ser.save() return APIResponse(msg='群增成功', results=self.get_serializer(car_obj, many=True).data) return super().create(request, *args, **kwargs) # 解决2,destroy方法是完成is_delete字段值修改 - 重写destroy方法,自定义实现体 def destroy(self, request, *args, **kwargs): car_obj = self.get_object() # 拿不到直接就抛异常
car_obj.is_delete = True car_obj.save() return APIResponse(msg='删除成功') # 解决3,让群查有状态码和状态信息 - 重写list方法 def list(self, request, *args, **kwargs): response = super().list(request, *args, **kwargs) return APIResponse(results=response.data)
url(r'^v7/cars/$', views.CarModelViewSet.as_view({ 'get': 'list', # 群查 'post': 'create', 'put': 'many_update', # 群整体改 'patch': 'many_partial_update', # 群局部改 'delete': 'many_destroy', # 群删 })), url(r'^v7/cars/(?P<pk>d+)/$', views.CarModelViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy', })),
视图集路由层
# 路由层 from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register('v7/cars', views.CarModelViewSet, basename='car') router.register('books', views.BookModelViewSet, basename='book') # router.register('users', views.UserModelViewSet, basename='user') urlpatterns = [ # url(r'^v7/cars/$', views.CarModelViewSet.as_view({ # 'get': 'list', # 群查 # 'post': 'create', # 'put': 'many_update', # 群整体改 # 'patch': 'many_partial_update', # 群局部改 # 'delete': 'many_destroy', # 群删 # })), # url(r'^v7/cars/(?P<pk>d+)/$', views.CarModelViewSet.as_view({ # 'get': 'retrieve', # 'put': 'update', # 'patch': 'partial_update', # 'delete': 'destroy', # })), url(r'', include(router.urls)) ] # urlpatterns.extend(router.urls) print(router.urls)