一、层次结构
GenericViewSet(ViewSetMixin, generics.GenericAPIView) ---DRF GenericAPIView(views.APIView) ---DRF APIView(View) ---DRF View ---Django
第一阶:
# 第一阶: ViewSetMixin ViewSet(ViewSetMixin, views.APIView) GenericViewSet(ViewSetMixin, generics.GenericAPIView) ModelViewSet(mixins.{C/R/U/D/L}ModelMixin, GenericViewSet) ReadOnlyModelViewSet(mixins.{R/L}ModelMixin, GenericViewSet)
事实上,除了ViewSetMixin以外,剩余的4个同阶类的内容都为空(只有PASS),ViewSetMixin增加了什么行为,后续再解释。
第二阶:
# 第二阶: GenericAPIView(views.APIView) CreateAPIView ListAPIView RetrieveAPIView DestroyAPIView UpdateAPIView ListCreateAPIView RetrieveUpdateAPIView RetrieveDestroyAPIView RetrieveUpdateDestroyAPIView
除GenericAPIView以外,剩余的同阶类,实质上是GenericAPIView与mixins.{CRUDL}ModelMixin的组合继承。
在类中,通过重写相应的HTTP方法(get、put、delete等),调用mixis.{CRUDL}ModelView中的create、list、retrieve等方法。
# Concrete view classes that provide method handlers # by composing the mixin classes with the base view. class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): """ Concrete view for creating a model instance. """ def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 以上为CreateAPIView源代码
第三阶:
# 第三阶 APIView(View)
APIView(View)为独一阶,地位非常独特。
第四阶:
# 第四阶 ContextMixin View TemplateResponseMixin TemplateView(TemplateResponseMixin, ContextMixin, View) RedirectView(View)
第四阶由Django提供,较为底层,一般很少去关注和使用,这里也不展开做详尽分析。
二、View、APIView、GenericAPIView、GenericViewSet的差别
1. Django View
import json from django.views.generic.base import View from django.core import serializers from django.http import JsonResponse from .models import Course class CourseListView(View): def get(self, request): """ 通过Django的View实现课程列表页 """ courses = Course.objects.all()[:10] json_data = serializers.serialize('json', Courses) json_data = json.loads(json_data) return JsonResponse(json_data, safe=False) # 上述代码使用Django自身的模块,返回application/json的数据,可以返回HttpResponse,也可以是其子类JsonResponse # 在Django中也有serializers,不同于DRF的serializers,它只能对基本类型进行JSON序列化、反序列化
这是一个普通的CBV,Django通过as_view和dispatch函数,将request请求传递给get(self, request)方法,从而返回response。
2. APIView
如果用APIView来实现,代码类似于:
from rest_framework.views import APIView from rest_framework.response import Response from .serializers import CourseSerializer class CourseListView(APIView): def get(self, request, format=None): """ 通过APIView实现课程列表页 """ courses = Course.objects.all() serializer = CourseSerializer(courses, many=True) return Response(serializer.data) # 在APIView这个例子中,调用了drf本身的serializer和Response。
APIView与View的不同之处在于:
- 请求和返回使用的DRF的
Request、
Response,
而不是Django的HttpRequest、
HttpResponse;
- 任何APIException异常都会被捕获到,并且处理成合适的响应信息;
- 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
3. GenericAPIView
如果用GenericAPIView实现,代码类似于:
# 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() serializer = self.get_serializer(book) return Response(serializer.data)
由于mixins.{CRUDL}ModelMixin的存在,我们往往可以这么写,
from rest_framework import mixins from rest_framework import generics class CourseListView(mixins.ListModelMixin, generics.GenericAPIView): """ 课程列表页 """ queryset = Course.objects.all() serialize_class = CourseSerializer def get(self, request, *args, **kwargs): # list方法是存在于mixins中的,同理,create等等也是,GenericAPIView没有这些方法! return self.list(request, *args, **kwargs)
# 如果我们直接继承ListAPIView(mixins.ListModelMixin, GenericAPIView),那么def get(...)方法都可以不写 class CourseListView(ListAPIView): """ 课程列表页 """ queryset = Course.objects.all() serialize_class = CourseSerializer
- queryset 指定queryset
- serializer_class 指定serializer
提供的方法:
- get_queryset(self)
- 通过访问self.queryset,获取queryset,两者一般择其一;
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
- get_serializer_class(self)
- 通过访问self.serializer_class,获取serializer_class,两者一般择其一;
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
- get_serializer(self, args, *kwargs)
- get_serializer_context(self)
- 创建request、format、view三个数据对象,作为serializer实例化时的context属性;
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_context(self): """ Extra context provided to the serializer class. """ return { 'request': self.request, 'format': self.format_kwarg, 'view': self }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
- filter_queryset()
- 将filter_backends的过滤类,应用到queryset上;
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
4. GenericViewSet
GenericViewSet(ViewSetMixin, generics.GenericAPIView),实际上等于ViewSetMixin + GenericAPIView,而ViewSetMixin的主要工作,就是重写了as_view方法。
class ViewSetMixin(object): """ This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the 'GET' and 'POST' methods to the 'list' and 'create' actions... view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) """
我们回过头来看,上述不同视图在接受web请求时,as_view和CBV方法(get、put、post等)的协同工作方式是不同的:
View:
APIView:
- 同上
GenericAPIView:
GenericViewSet
如何选择:
- 如果使用router.register进行URL请求的注册与绑定,建议使用GenericViewSet,最为高效、规范、合理;
- 如果需要重构原有的FBV,建议使用GenericAPIView,改动小、变动少;