drf视图家族
APIView
# APIView继承自django原生的View,它在View的基础上做了两件事:
- 使用csrf_exempt()去除了所有请求的csrf认证
- 重写了View的dispatch方法,用drf的Request对象替换原生的当前请求request对象
- 做了三大认证:认证、权限、频率
- 封装了响应对象Response
# 使用APIView写五大接口的方法,需要自己分别实现五个方法,它的好处是可控性高。
# 代码
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
author = models.CharField(max_length=32)
# ser.py
- 使用模型类序列化器ModelSerializer
- 好处:不用重复写模型中的字段;基于序列化器新增和修改数据不用重写create()和update()
from rest_framework import serializers
from app01 import models
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = '__all__'
# view.py
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import utils, models, ser
class BookAPIView(APIView):
def get(self, request):
book_list = models.Book.objects.all()
ser_obj = ser.BookModelSerializer(book_list, many=True)
back_info_obj = utils.BackInfo(ser_obj.data)
return Response(back_info_obj.results)
def post(self, request):
ser_obj = ser.BookModelSerializer(data=request.data)
if ser_obj.is_valid():
ser_obj.save()
back_info_obj = utils.BackInfo(ser_obj.data)
else:
back_info_obj = utils.BackInfo().add_error(code='400', message='新增失败', data=ser_obj.errors)
return Response(back_info_obj.results)
class BookAPIView2(APIView):
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
ser_obj = ser.BookModelSerializer(instance=book_obj)
back_info_obj = utils.BackInfo(data=ser_obj.data)
return Response(back_info_obj.results)
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
ser_obj = ser.BookModelSerializer(instance=book_obj, data=request.data)
if ser_obj.is_valid():
ser_obj.save()
back_info_obj = utils.BackInfo(ser_obj.data)
else:
back_info_obj = utils.BackInfo().add_error(code='400', message='新增失败', data=ser_obj.errors)
return Response(back_info_obj.results)
def delete(self, request, pk):
models.Book.objects.filter(pk=pk).delete()
back_info_obj = utils.BackInfo()
return Response(back_info_obj.results)
# urls.py
# 基于APIView的视图
url(r'books/$', views.BookAPIView.as_view()),
url(r'books/(?P<pk>\d+)', views.BookAPIView2.as_view()),
GenericAPIView
# GenericAPIView继承自APIView,它在APIView的功能上增加了两个功能:
- 通用的模型类 queryset = None;
- 通用的序列化器 serializer_class = None
# 三个常用方法
- get_queryset() # 获取queryset对象,用于查询多条数据
- get_object() # 获取一条对象数据
- get_serializer(*args, **kwargs) # 获取序列化器对象
# GenericAPIView提供的这些方法是五个扩展类的基础
# 使用GenericAPIView写不同类的接口,只是queryset参数和serializer_class参数的不同,其他的方法几乎完全一样
# 代码: 基于GenericAPIView写的接口
# views.py
from rest_framework.generics import GenericAPIView
class BookGenericAPIView(GenericAPIView):
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
def get(self, request):
book_list = self.get_queryset()
ser_obj = self.get_serializer(book_list, many=True)
back_info_obj = utils.BackInfo(ser_obj.data)
return Response(back_info_obj.results)
def post(self, request):
ser_obj = self.get_serializer(data=request.data)
if ser_obj.is_valid():
ser_obj.save()
back_info_obj = utils.BackInfo(ser_obj.data)
else:
back_info_obj = utils.BackInfo().add_error(code='400', message='新增失败', data=ser_obj.errors)
return Response(back_info_obj.results)
class BookGenericAPIView2(GenericAPIView):
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
def get(self, request, pk):
book_obj = self.get_object()
ser_obj = self.get_serializer(book_obj)
back_info_obj = utils.BackInfo(data=ser_obj.data)
return Response(back_info_obj.results)
def put(self, request, pk):
book_obj = self.get_object()
ser_obj = self.get_serializer(instance=book_obj, data=request.data)
if ser_obj.is_valid():
ser_obj.save()
back_info_obj = utils.BackInfo(ser_obj.data)
else:
back_info_obj = utils.BackInfo().add_error(code='400', message='新增失败', data=ser_obj.errors)
return Response(back_info_obj.results)
def delete(self, request, pk):
self.get_object().delete()
back_info_obj = utils.BackInfo()
return Response(back_info_obj.results)
# urls.py
url(r'books2/$', views.BookGenericAPIView.as_view()),
url(r'books2/(?P<pk>\d+)', views.BookGenericAPIView2.as_view()),
五个扩展类
# 使用GenericAPIView写不同类的接口,只是queryset参数和serializer_class参数的不同,其他的方法几乎完全一样
# 这部分代码drf帮我们实现了,在五个扩展类中
# 提供了几种后端视图(对数据资源进行曾删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
# 这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
ListModelMixin
提供list(request, *args, **kwargs)方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
CreateModelMixin
提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
RetrieveModelMixin
提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
UpdateModelMixin
提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
DestroyModelMixin
提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
# 代码:基于GenericAPIView和5个视图扩展类写的接口
# views.py
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class BookGenericAPIVMixinView(GenericAPIView, ListModelMixin, CreateModelMixin) :
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
def get(self, request):
return self.list(request)
def post(self, request):
# back_info_obj = utils.BackInfo(ser_obj.data)
return self.create(request)
class BookGenericAPIVMixinView2(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
# urls.py
url(r'books3/$', views.BookGenericAPIVMixinView.as_view()),
url(r'books3/(?P<pk>\d+)', views.BookGenericAPIVMixinView2.as_view()),
ModelViewSet
# ModelViewSet继承五个扩展类和GenericViewSet
# GenericViewSet继承ViewSetMixin和GenericAPIView
# 继承该类可以简单实现5个接口的,且只需要一个视图类,关键点就在于其父类ViewSetMixin
# 这里面的重点在于ViewSetMixin,该类有一个as_view()方法,GenericViewSet优先使用该as_view()方法
# 该as_view()方法内通过url()的参数actions实现请求方法的分配
# 代码:基于ModelViewSet
# views.py
from rest_framework.viewsets import ModelViewSet
class BookModelViewSetMixinView(ModelViewSet):
queryset = models.Book
serializer_class = ser.BookModelSerializer
# urls.py
#当路径匹配,是get请求,会执行Book5View的list方法
url(r'books4/$', views.BookModelViewSetMixinView.as_view(actions={'get': 'list', 'post': 'create'})),
url(r'books4/(?P<pk>\d+)', views.BookModelViewSetMixinView.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
ViewSetMixin源码阅读
ViewSetMixin类的核心是提供了两个方法:as_view()
和initialize_request()
方法;
as_view()方法
# as_view()方法,且配合url的actions参数,可以实现请求方式的动态指定分配
# 核心代码(所以路由中只要配置了对应关系,比如actions={'get':'list'}),当get请求来,就会执行list方法
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.action_map = actions
for method, action in actions.items(): # actions指的是url中指定的字典actions = {'get': 'list', }
handler = getattr(self, action) # handler = view.list
setattr(self, method, handler) # view.get = view.list
"""
如果url中通过actions字典,指定了get和list的键值对,那么通过上述两个反射操作,就将view.get属性赋值了一个list方法的内存地址。
等url匹配成功后,通过dispatch反射调用请求方法时,此时该view有了get属性,那么就执行get加括号执行,其实执行的就是被替换的list方法。
如果此时没有通过actions指定get个list的关系,那程序运行到dispathc里面就会找当前对象的get的方法;
自己有的话就用自己的,自己找不到就一层层找父类的。
"""
initialize_request()方法
# initialize_request()方法
# ViewSetMixin也提供了initialize_request方法,如果使用了该类,且在该方法被优先调用到,那么该方法就会被执行
- 该方法内部其实调用了super().initialize_request(),即调用当前视图对象的下一个父类的initialize_request方法,
- 该方法的核心点是该当前视图对象增加一个action属性,action的值就是当前请求方式
def initialize_request(self, request, *args, **kwargs):
"""
Set the `.action` attribute on the view, depending on the request method.
"""
request = super().initialize_request(request, *args, **kwargs)
method = request.method.lower()
if method == 'options':
# This is a special case as we always provide handling for the
# options method in the base `View` class.
# Unlike the other explicitly defined actions, 'metadata' is implicit.
self.action = 'metadata'
else:
self.action = self.action_map.get(method) # 通过该方法后,视图对象中就会增加一个action属性
return request
"""
该方法被调用后,就会给当前视图对象增加一个action属性,属性值是当前请求的类型,如:get、post等
这样做了之后,以后在视图类中不仅可以通过request取到当前请求方式,还可以通过当前视图类取到请求方式。
"""
九个通用API视图子集
# 九个通用API视图子集: 继承GenericAPIView和五个扩展类,实现不同请求方式的定制
CreateAPIView(CreateModelMixin, GenericAPIView):写了post()
DestroyAPIView(DestroyModelMixin, GenericAPIView):写了delete()
ListAPIView(ListModelMixin, GenericAPIView):写了get()
RetrieveAPIView(RetrieveModelMixin, GenericAPIView):写了get()
UpdateAPIView(UpdateModelMixin, GenericAPIView):put()、patch()
ListCreateAPIView(ListModelMixin, CreateModelMixin, GenericAPIView):get()、post()
RetrieveDestroyAPIView(RetrieveModelMixin, DestroyModelMixin, GenericAPIView):get()、delete()
RetrieveUpdateAPIView(RetrieveModelMixin, UpdateModelMixin, GenericAPIView):get()、put()、patch()
RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView):get()、put()、patch()、delete()
# 代码:仅实现查询图书所有的接口
# views.py
class BookListAPIView(ListAPIView):
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
# urls.py
url(r'books6/$', views.BookListAPIView.as_view()),
ViewSetMixin & APIView
其实drf给我们提供了一个同时继承了这两个类的一个视图集合类ViewSet
class ViewSet(ViewSetMixin, views.APIView): """ The base ViewSet class does not provide any actions by default. """ pass
# 基于ViewSetMixin的视图类和APIView编写接口
# 利用ViewSetMixin的as_view()方法,在url中通过actions分配请求处理的方法
# 因为仅使用APIView,所有需要自己实现接口功能
# 自定制化强,可控性高
# 代码:
# views.py
from rest_framework.viewsets import ViewSetMixin
class BookViewSetMixin(ViewSetMixin, APIView):
# ViewSetMixin一定要放在第一个父类位置,才能使用到ViewSetMixin的as_views()
def get_all_book(self,request):
book_list = models.Book.objects.all()
book_ser = ser.BookModelSerializer(book_list, many=True)
return Response(book_ser.data)
# urls.py:指定处理get请求的视图方法
url(r'books5/$', views.BookViewSetMixin.as_view(actions={'get': 'get_all_book'})),
ReadOnlyModelViewSet
# ReadOnlyModelViewSet继承自五个扩展类中的两个取数据的扩展类,又继承GenericViewSet,
# GenericViewSet继承了ViewSetMixin,可以使用其as_view()方式
# 因此,它只可以取数据,并且通过url中actions参数指定get的请求处理方法,list()、retrieve()
# 代码:
# views.py
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookReadOnlyModelViewSet(ReadOnlyModelViewSet):
queryset = models.Book.objects
serializer_class = ser.BookModelSerializer
# urls.py
url(r'books7/$', views.BookReadOnlyModelViewSet.as_view(actions={'get': 'list'})),
url(r'books7/(?P<pk>\d+)', views.BookReadOnlyModelViewSet.as_view(actions={'get': 'retrieve'})),
视图类继承关系
两个基本视图类
# APIView # 去除csrf认证、封装request、三大认证、封装reaponse等
# GenericAPIView # 提供了通用基于类的视图处理办法,两个参数:queryset\serializer_class
五个视图扩展类:分别提供了具体的增删该查方法:list\get\update\create\destroy\retrieve
"""
CreateModelMixin
DestroyModelMixin
ListModelMixin
RetrieveModelMixin
UpdateModelMixin
"""
九个通用视图子类:GenericAPIView和五个视图扩展类的组合
CreateAPIView
DestroyAPIView
GenericAPIView
ListAPIView
ListCreateAPIView
RetrieveAPIView
RetrieveDestroyAPIView
RetrieveUpdateAPIView
RetrieveUpdateDestroyAPIView
UpdateAPIView
1个视图集合扩展类
ViewSetMixin
"""
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'})
"""
视图集合扩展类ViewSetMixin
和其他的组合
ViewSetMixin + APIView -----> ViewSet
ViewSetMixin + GenericAPIView -----> GenericViewSet
GenericViewSet + RetrieveModelMixin + ListModelMixin ------> ReadOnlyModelViewSet
GenericViewSet + 五个视图扩展类 -------> ModelViewSet
视图继承关系图