DRF 4 视图类路由含源码详解
4,视图类集合,路由
4.1 APIView
APIView是最最基础的视图类,如果只继承APIView,一切都得手写
book_list=Book.objects.all()
book_ser=BookSerializer(book_list,many=True)
手动写业务逻辑处理不同的请求
def get(self,request):
def post(self,request):
视图的类需要写两个,分别对应两个url的有名分组传参和不传参情况.
class BookView(APIView):
def get(self,request):
book_list=Book.objects.all()
book_ser=BookSerializer(book_list,many=True)
return Response(book_ser.data)
def post(self,request):
book_ser = BookSerializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status':101,'msg':'校验失败'})
class BookDetailView(APIView):
def get(self, request,pk):
book = Book.objects.all().filter(pk=pk).first()
book_ser = BookSerializer(book)
return Response(book_ser.data)
def put(self, request,pk):
book = Book.objects.all().filter(pk=pk).first()
book_ser = BookSerializer(instance=book,data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
def delete(self,request,pk):
ret=Book.objects.filter(pk=pk).delete()
return Response({'status': 100, 'msg': '删除成功'})
路由配置:
url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())
4.2 GenericAPIView
简化步骤,在类里直接申明了操作哪张表,调用哪个序列化器:
queryset = models.Booklib.objects
serializer_class = BookModelSerializer
GenericAPIView类里封装的4个常用方法:
get_queryset() 获取多条表查询对象
get_object() 获取一条表查询对象(必须传pk,源码规定的)
-get_object()源码解析
#如果配置了过滤的话,先过滤queryset对象
queryset = self.filter_queryset(self.get_queryset())
# lookup_url_kwarg就是pk,路由中有名分组分出来的pk
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
# {pk:4} 4 浏览器地址中要查询的id号http://127.0.0.1:8000/books6/4/
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
# 根据pk去queryset中get单个对象
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
get_serializer() 调用序列化类,返回序列化的对象
def get_serializer(self, *args, **kwargs):
#这里调用的序列化类是get_serializer_class()返回的序列化类
serializer_class = self.get_serializer_class()
return serializer_class(*args, **kwargs)
get_serializer_class() 之所以加这一步是为你提供了选择序列化类的方法,重写该方法能实现一个视图函数配置多个序列化类,根据业务逻辑调用不同的序列化类.
def get_serializer_class(self):
assert self.serializer_class is not None, (
% self.__class__.__name__
)
return self.serializer_class
调用这些方法举例:
book=self.get_queryset().filter(pk=num).first()
ser_obj=self.get_serializer(book)
注意事项:
由于没有继承Mixin模块的5个类,这里还需要注意以下两点(通常情况下不会单独继承GenericAPIView类的,配合Mixin模块的5个类使用更佳)
- 反序列化数据化时还需要手动判断数据是否合法,并调用序列化器的save方法
class Books2(GenericAPIView):
queryset = models.Booklib.objects.all()
serializer_class = BookModelSerializer
def get(self,request):
book=self.get_queryset()
book_ser=self.get_serializer(book,many=True)
return CommomResponse(data=book_ser.data,headers={'key':'value'})
def post(self,request):#创建,修改了id为只读属性,创建时不需要id参数
book_ser=self.get_serializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return CommomResponse(data=book_ser.data)
else:
return CommomResponse(101,'数据不和法',book_ser.errors)
class Book2(GenericAPIView):
queryset = models.Booklib.objects
serializer_class = ser.BookModelSerializer
def get(self,request,num):
book=self.get_queryset().filter(pk=num).first()
ser_obj=self.get_serializer(book)
return CommomResponse(data=ser_obj.data)
def delete(self,request,num):
book=self.get_queryset().filter(pk=num).delete()
return CommomResponse(msg='删除成功')
def put(self,request,num):
book=self.get_queryset().filter(pk=num).first()#只有一个值时一定有用.first()
ser_obj=self.get_serializer(book,request.data)
if ser_obj.is_valid():
ser_obj.save() #这是序列化器的save方法,不是queryset的
return CommomResponse(data=ser_obj.data)
else:
return CommomResponse(msg='数据不合法')
路由生成:
url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())
4.3 Mixns模块的五个扩展类
这五个扩展类就是为了配合GenericAPIView而设计的,两者配合是有可以大大减少重复代码的书写
5个视图扩展类源码分析:
ListModelMixin-->get
获取所有数据,并进行了群查结果的过滤和分页
lass ListModelMixin:
def list(self, request, *args, **kwargs):
#这里先进行过滤和分页操作
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
#序列化表对象
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
RetrieveModelMixin-->有参数的get
获取一条数据,不用分页,过滤已经再GenericAPIView的get_object方法中进行了
lass RetrieveModelMixin:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
CreateModelMixin-->post
UpdateModelMixin-->put
创建和更新原理类似,这里以创建分析
lass CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
#这里进行验证表保存数据
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
DestroyModelMixin-->delete
lass DestroyModelMixin:
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
使用:需要哪个功能继承哪个类,在不同请求方法下调用该类对应的方法即可.
必要时需要重写5个类的方法
重写list时需要注意:配置了过滤和分页的情况,可以直接cp源码再修改.
class Books3(GenericAPIView,ListModelMixin,CreateModelMixin):
queryset = models.Booklib.objects
serializer_class = ser.BookModelSerializer
def get(self,request):
return self.list(request)
def post(self,request):
return self.create(request)
class Book3(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
queryset = models.Booklib.objects
serializer_class = ser.BookModelSerializer
def get(self,request,pk):
return self.retrieve(request,pk)
def put(self,request,pk):
return self.update(request,pk)
def delete(self,request,pk):
return self.destroy(request,pk)
路由配置:
这5个类只是为了配合GenericAPIView跟路由没有半毛钱关系
4.4 九个组合拓展类
这九个类是generics模块将GenericAPIView和五个视图扩展类进行整合得到类(如果你喜欢自动生成路由,那么基本就用不到它们)分别是:
5个基础组合类
CreateAPIView,ListAPIView,RetrieveAPIView,DestroyAPIView,UpdateAPIView
4个扩展组合类
ListCreateAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView,RetrieveUpdateDestroyAPIView
由于五个视图拓展类会自动调用GenericAPIView的get_object,get_queryset,get_serializer,方法
所以使用方法很简单:只需要指明queryset,serializer_class即可
class Books4(ListCreateAPIView):
queryset = models.Booklib.objects
#这里需要一个queryse对像所以要去到object
serializer_class = ser.BookModelSerializer
class Book4(RetrieveUpdateDestroyAPIView):
queryset = models.Booklib.objects
serializer_class = ser.BookModelSerializer
路由生成:
url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())
4.5ViewSetMixin
由于APIView源码中用反射的方式直接获取视图对象的方法属性执行,所以APIView有个致命的弱点,即只继承了APIView的类中的方法属性名需要和请求方式对应才能被自动调用执行.
ViewSetMixin的出现就是为了改变这种现状的,即继承了ViewSetMixin的类可以自行命名函数,而且可以被调用,
但是路由配置的方法就改变了.
实现原理:
1,首先需要在路由配置加入参数actions={'k':'v'},k是请求方法,v是视图类对象的方法名
path('books/', views.BookViewSet.as_view(actions={'get':'get_1'}))
2,ViewSetMixin重写了as_view的view方法
def as_view(cls, actions=None, **initkwargs):
#这里比APIView的参数多了一个actions
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.action_map = actions
#将请求方式和响应方法重新建立关系
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs)
注意:因为ViewSetMixin重写了as_view,查找as_view时会现去继承的第一父类的名称空间查找,如果APIView在前则,不会执行重写的as_view导致功能失效.
使用举例:
class Book05(ViewSetMixin,APIView):
def get_one_book(self,request,pk):
obj_list = models.Booklib.objects.filter(pk=pk).first()
ser_book = ser.BookModelSerializer(obj_list)
return CommomResponse(data=ser_book.data)
def get_books(self,request):
obj_list = models.Booklib.objects.all()
ser_book=ser.BookModelSerializer(obj_list,many=True)
return CommomResponse(data=ser_book.data)
由于ViewSetMixin需要和APIView或GenericAPIView配合使用所以就有了下面两个类:
ViewSet=ViewSetMixin+APIView
GenericViewSet=ViewSetMixin+GenericAPIView
其实Mixins模块的5个拓展类也是经常使用的,所以就有了ModelViewSet,该类继承了6个父类
ModelViewSet=GenericViewSet+CreateModelMixin+DestroyModelMixin+ListModelMixin+RetrieveModelMixin+UpdateModelMixin
路由配置:此处路由配置以及改变了,注意actions={'get':'get_books'}
url(r'^api/books5/$', views.Book05.as_view(actions={'get':'get_books'})),
url(r'^api/book5/(?P<pk>\d+)/', views.Book05.as_view(actions={'get':'get_one_book'})),
4.6自动生成路由
每次都在路由配置参数太麻烦了,所以作者就设计了自动生成路由的类,想要使用自动生成路由,视图类必须继承ViewSetMixin类或其子类即ViewSet或GenericViewSet或ModelViewSet.(其实还有其他的类只是不常用)
使用步骤:在路由层
第一步:导入routers模块
第二步:实例化得到路由对象
第三步:调用路由对象的注册功能
第四部:自动生成的路由,加入到原路由列表中
# 继承自视图类,ModelViewSet的路由写法(自动生成路由)
-urls.py
# 第一步:导入routers模块
from rest_framework import routers
# 第二步:有两个类,实例化得到对象
# routers.DefaultRouter 生成的路由更多
# routers.SimpleRouter
router=routers.DefaultRouter()
# 第三步:调用对象的注册功能
# router.register('前缀','继承自ModelViewSet视图类','别名')
#如果使用了路由分发,这里前缀也不需要写了,只写一个''就行
router.register('books',views.BookViewSet)
# 第四步
urlpatterns+=router.urls
4.7 action的使用
使用了自动生成路由后没法直接给路由加actions参数了,但可以再视图类里导入actions装饰器实现
第一步:from rest_framework import decorators
第二步:第一个参数传入列表,列表中写请求方式['GET','POST'],第二个参数传布尔值,需要携带参数就写True
# action干什么用?为了给继承自ModelViewSet的视图类中定义的函数也添加路由使用
class BookViewSet(ModelViewSet):
queryset =Book.objects.all()
serializer_class = BookSerializer
@action(methods=[get,],detail=True)
#如果detail=True 会生成一条带有有名分组的路由匹配:如下
#^books/(?P<pk>[^/.]+)/get_1/$ [name='book-get-1']
#具体形如:/books/1/get_1 ,
#如果detail=False 会生成一条普通的路由:如下
#^books/get_1/$ [name='book-get-1']
#具体形如:/books/get_1 ,
def get_1(self,request,pk):
book=self.get_queryset()[:2] # 从0开始截取一条
ser=self.get_serializer(book,many=True)
return Response(ser.data)
4.8 使用技巧总结
1,如果想要自动配置路由视图类必须继承ViewSetMixin类或其子类
2,GenericAPIView和五个拓展类搭配使用效果更佳
3,如果想使用过滤,分页功能视图类必须继承GenericAPIView类或其子类且需要搭配五个拓展类使用
4,具体选择哪些类需要结合业务需求和以上3条技巧考虑
5,如果业务需求很复杂,可以考虑重写某些类的方法实现(有时间再总结)