【转】详解Django DRF框架中APIView、GenericAPIView、ViewSet区别
转自 https://zhuanlan.zhihu.com/p/72527077?utm_source=com.doc360.client
官方文档地址: https://www.django-rest-framework.org/
首先将两个概念,FBV开发模式与CBV开发模式:FBV指的时Function Base View,基于函数开发视图;CBV指的时Class Base View,基于对象开发视图
本文仅讨论CBV模式中,APIView、GenericAPIView、ViewSet的使用与区别,以及部分源代码实现,继承关系
APIView
APIView与Django的View类似,我们的业务类只需要继承APIView,在URL传递过程,我们只需要调用APIView的as_view()方法,然后URL就会调用业务类对应的HTTP方法,如get,post,put,delete(对应查 增 改 删)方法,我们在业务代码中只需要实现三个功能即可实现get方法:ORM调用、序列化、返回数据
示例代码如下:
# APIView_test.py
class SnippetList(APIView):
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
2. GenericAPIView
而使用GenericAPIView可以使我们的代码更简洁,例如上述代码,我们可以简化为
# GenericAPIView_test.py
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView,
):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
# 我们不用在get post两个方法中都去ORM调用以及序列化调用
return self.list(*args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(*args, **kwargs)
通过继承GenericAPIView,对类的queryset与serializer_class属性赋值,然后get方法可以调用父类的ListModelMixin方法的list()方法,而post方法可以调用父类CreateModelMixin的方法,其他URL分发等与之前的APIView类似
实际上,我们的GenericAPIView继承于APIView,而之前的get方法中ORM调用
# APIView_test.py
snippets = Snippet.objects.all()
调用ORM实际上会在GenericAPIView的get_queryset()方法被使用,
# rest_framework/generics.py
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
"""
很多注释
"""
queryset = None # !!! 传入的queryset
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
"""
省略的掉其他代码,让我们专注与get_query方法
"""
而get方法中序列化调用
# APIView_test.py
serializer = SnippetSerializer(snippets, many=True)
序列化对象则会在GenericAPIView的get_serializer()方法被使用
# rest_framework/generics.py
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
queryset = None
serializer_class = None # !!!注意
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
}
"""
让我们省略其他代码,专注于get_serializer的实现
"""
当我们调用
# APIView_test.py
def get(self, request, *args, **kwargs):
return self.list(*args, **kwargs)
方法,实际上会调用其继承的ListModelMixin类,其中ListModelMixin类会调用
# rest_framework/generics.py
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
queryset = None
serializer_class = None # !!!注意
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
}
"""
让我们省略其他代码,专注于get_serializer的实现
"""
GenericAPIView的get_queryset()方法,将传入的ORM进行调用,这样我们就不用每次get\post\put\delete代码中再进行ORM调用与serializer序列化
当然,这样每次都继承三个类是比较麻烦的,我们可以用一个类继承这三个类,然后我们的业务实例只需要继承一个类即可完成这些工作,当然,Django REST framework已经帮我们做好了
我们可以通过一种混合视图的方法来进行继承
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
通过继承ListCreateAPIView类,而ListCreateAPIView类则为
# rest_framework/generics.py
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
这不正是我们之前继承GenericAPIView写的类吗?
所以,通过混合视图的方法,可以简化我们的代码,使我们代码更加简洁.
3. ViewSets
viewsets与普通view实际上相同,但不需要使用get/put的方式提供更新与添加操作。通常使用Router实例化一组viewsets
与之前的代码不同,比如我们在定义Snippet view的时候定义了两个,一个位SnippetList,另一个为SnippetDetail,但我们可以通过viewsets定义一个类,实现之前两个类才能实现的方法。完成后我们需要将viewsets绑定到url
在设置url的时候我们无需像以前那种方式自己在urlpartten中添加,而是采用router方式进行路由注册,减少代码
# viewset_test.py
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
同时继承了CreateModelMixin、RetrieveModelMixin、UpdateModelMixin与GenericViewSet等几个类,也就是说,继承ModelViewSet可以同时实现增删改查方法,而不用我们自己去写get、post、put、delet方法,减少代码
总结:
viewset实际上属于继承多个mixin模块的Model,在代码最少的情况下让我们实现符合REST风格的增删改查接口
GenericViewSet实际上继承与APIView,在我们不需要完全实现增删改查,而是只实现部分方法地时候,使用混合视图比较方便
普通APIView与Django View类似,通过CBV方式重载父类的get、post、put、delet方法,实现REST风格的接口
当然FBV方式也不是不可取,如我们需要实现一个简单的接口,如获取服务器时间,通过FBV方法其实是最快的一种方法