drf视图类 视图基类、视图扩展类、视图子类、视图集以及路由类
drf视图类 视图基类、视图扩展类、视图子类、视图集
两个视图基类
APIView
这个视图类在前文已经介绍过了,web常见5个接口--APIView的最后有提到:
APIView也是继承了django的原生视图类View,而APIView做了更多的工作来方便我们编写视图函数。以下为查询接口的一段代码,利用率drf的APIView和Response
而这个视图类也是drf框架提供给我们的视图基类之一,它相对于原生的django视图类来说有以下特点:
-
执行流程:新的reqeust,三大认证,全局异常
-
重写了as_view,dispatch
-
类属性:
parser_classes 请求格式(有三个)
renderer_classes 响应格式(有两个)
authentication_classes 认证类 throttle_classes 频率类 permission_classes 权限类
GenericAPIView
GenericAPIView又继承了APIView,而它的特点就在于与数据库打交道比较方便。
我们对比两个APIView的子类(视图类),发现如果都是一样的接口时,内部程序的差异就只有模型表和序列化类不同。如下:
# 书籍视图类
class BookView(APIView):
def get(self, request):
queryset = Book.objects.all()
ser = BookSerializer(instance=queryset, many=True)
return Response(ser.data)
# 出版社视图类
class PublishView(APIView):
def get(self, request):
queryset = Publish.objects.all()
ser = PublishSerializer(instance=queryset, many=True)
return Response(ser.data)
所以这样冗余的代码显然我们可以对其进行优化,drf也为我们提供了GenericAPIView类。
继承了这个类产生的子类也是drf视图基类之一,它代表的是常用的接口视图类,我们需要提供两个重要属性给GenericAPIView类:
- queryset:这个参数就是模型表查询语句,(注意orm是惰性的,诸如
Book.objects.all()
的这类语句还没有实际去数据库) - serializer_class:这个参数就是对应的序列化类
以上两个参数属性是必要的,还有一些其他可支配的参数,用以下形式配置到类中:
class BookView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = 'pk' # 当路由分组是detail类的时候分出的字段名,默认pk
filter_backends = '过滤类的配置(了解)'
pagination_class = '分页类的配置(了解)'
而GenericAPIView内部也书写了一下几个方法:
- get_queryset:获取序列化对象queryset
- get_object:获取单个对象
- get_serialzer:获取序列化类,还有一个差不多的get_serializer_class,一般覆写
为什么采取函数去拿到queryset和序列化类属性呢,因为在有些场景中,我们可能根据不同的请求方式和接口来分配不同的queryset,用函数便于覆写和分支
采取GenericAPIView基类,我们在配置好queryset和serializer_class后,就可以搭配视图扩展类,来快速的书写get、post等请求处理函数了。
五个视图扩展类
在rest_framework.mixins中有一些类,这些类必须搭配GenericAPIView使用,因为它们本身缺少一些方法,不具备视图类的特点。这种类一般以Mixin等后缀结尾,表示配合一种类通过多继承产生子类,这里就是产生了一个视图类。
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin,ListModelMixin
# 基于GenericAPIView+5个视图扩展类写接口
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
self中的list、create、retrive、update、destroy就封装好了根据queryset和serializer来操作数据库的对应操作,对应查所有,新增,查单个,修改,删除。而我们对于接口的开设这个写法也是固定的,只是根据不同的接口的排列组合,我们可以排列组合得到9种视图子类。
九个视图子类
-
五个单接口的视图子类
ListAPIView、CreateAPIView、RetrieveAPIView、UpdateAPIView、DestroyAPIView
-
三个两两组合的子类
ListCreateAPIView -- 查所有和新增属于没有额外路由的
RetrieveUpdateAPIView、RetrieveDestroyAPIView -- 有额外路由的两两组合,认为有查的接口才能改删
-
一个三接口的detail子类
RetrieveUpdateDestroyAPIView
这样我们在编写五个接口就会像下面这样:
# 视图类
class BookView(ListCreateAPIView): # 查询所有,新增一个
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询一个,修改一个,删除一个
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
而两个类干脆合并到一块,于是drf又提供了视图集。
视图集
ModelViewSet集合五个接口
class BookView(ModelViewSet): # 五个接口都齐了
queryset = Book.objects.all()
serializer_class = BookSerializer
注意通过视图集得到的视图类,我们再用原本的路由就不行了,并会抛出这样一个异常:
The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`
提示我们需要按照示例一样,在as_view()函数中添加参数,所以原本路由失效的原因就是因为,视图集重写了as_view函数。
urlpatterns = [
path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
重写as_view位置分析
那么是在哪里重写的呢,我们查看视图集发现其继承了6个父类,排除五个视图扩展类和视图基类GenericAPIView,最终定位到ViewSetMixin中,看形式,这个类也是配合基类使用的。在其中就重写了as_view()方法,而在我们的视图类执行时,它就会到第一个父类中先寻找as_view方法,所以ViewSetMixin在继承顺序上注意要排在前面。
ViewSetMixin的as_view方法简析
# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行 views.BookView.as_view({'get': 'list', 'post': 'create'})()------>读as_view【这个as_view是ViewSetMixin的as_view】
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
# 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# 。。。。其他代码不用看
def view(request, *args, **kwargs):
...
return self.dispatch(request, *args, **kwargs)
# 去除了csrf校验
return csrf_exempt(view)
# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执
行ViewSetMixin----》as_view----》内的view()
def view(request, *args, **kwargs):
#actions 是传入的字典--->{'get': 'list', 'post': 'create'}
self.action_map = actions
# 第一次循环:method:get,action:list
# 第一次循环:method:post,action:create
for method, action in actions.items():
# 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
# handler就是视图类中的list方法
handler = getattr(self, action)
# 反射修改:把method:get请求方法,handler:list
# 视图类的对象的get方法,设置为list
setattr(self, method, handler)
# dispatch是APIView的,经历上述循环后,我们就让请求类型函数映射到了视图类中的函数
return self.dispatch(request, *args, **kwargs)
总结:
- 要求传入action参数,否则报错
- 返回去除csrf认证的view函数
- view函数中最终还是执行了APIView中的dispatch,遵循按请求类型分发
- 但在执行dispatch前通过对action进行反射,将请求名如get映射到视图类中本身拥有的函数(或者父类中的函数)如list。
所以
ReadOnlyModelViewSet编写两个序列化接口
ReadOnlyModelViewSet集合了查询多个和查询单个这两个接口,实际上就是直接继承了两个只读视图扩展类和含viewsetmixin的genericviewset。
# 路由
urlpatterns = [
path('books/', views.BookView.as_view({'get': 'list'})),
path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve'})),
]
# 视图类
class BookView(ReadOnlyModelViewSet): # 查询所有,新增一个
queryset = Book.objects.all()
serializer_class = BookSerializer
rest_framework.viewsets包下的类
视图集除了两个基础视图集类:ModelViewSet和ReadOnlyModelViewSet
还有:
- ViewSetMixin:专门用于改写as_view修改请求类型和函数的映射关系的
- ViewSet:ViewSetMixin+ APIView
- GenericViewSet:ViewSetMixin+ GenericAPIView
这三个就是用来改写路由的写法的。
路由类
在上述视图对应的路由中,我们一般采取:
- path('books/', views.BookView.as_view())
- path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'}))
这两种方式都采取了原生的或者drf改写as_view方法,而在drf改写的方法中,手动书写常用的映射关系比较麻烦,所以drf又提供了路由类帮助我们生成路由。
自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter # 两个路由类,一般用前者
router = SimpleRouter()
router.register('book', views.BookView, 'book')
"""
注意这里的BookView必须是继承了改写as_view的视图类,即两个视图集或者
viewset.ViewSet和viewset.GenericViewSet
"""
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)) # include方式可以在这层路由前再添加一些路由
]
# 也可以采取urlpatterns += router.urls的方式
路由类会自动默认的将请求按以下方式映射:
- get--->list
- get---->retrieve
- put---->update
- post---->create
- delete---->destory
这里的get是按照什么映射成查单个和查多个的呢?
可以根据路由是否拥有pk字段来判断
action装饰器
action是配合路由类使用的,将其加装在接口函数的上方,传入一些属性,路由类便能根据这些属性生成匹配的路由映射,路由层生成路由的代码与上述一致。
class SendView(ViewSet):
# methods指定请求方法,可以传多个
# detail:只能传True和False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
# url_path:生成send后路径的名字,默认以方法名命名
# url_name:别名,反向解析使用,了解即可
@action(methods=['POST'], detail=False)
def send_sms(self, request):
...
而这种方式生成的路由,会在视图类注册的总路由(book)后再加上action中的url_path,也就是说一个视图类中可以含有路由不同的接口函数。
通过action方法和属性分发不同的序列化类
class SendView(GenericViewSet):
queryset = None
serializer_class = '序列化类'
def get_serializer(self, *args, **kwargs):
if self.action=='hear':
return '某个序列化类'
else:
return '另一个序列化列'
@action(methods=['GET'], detail=True)
def send_sms(self, request,pk):
print(pk)
# 手机号,从哪去,假设get请求,携带了参数
phone = request.query_params.get('phone')
print('发送成功,%s' % phone)
return Response({'code': 100, 'msg': '发送成功'})
@action(methods=['GET'], detail=True)
def hear(self,request): # get
# 序列化类
pass
@action(methods=['GET'], detail=True)
def login(self,request): # get
# 序列化类
pass