Python django drf 框架 视图层总结
内容概要
-
两个视图基类
-
五个视图扩展类
-
九个视图子类
-
action装饰器
视图基类
1.APIView
drf 视图 视图类
APIView 是 drf视图类的基类 drf提供的最顶层的类
# APIView跟原生django中的View区别
1-传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
2-视图方法可以返回REST framework的Response对象-
3-任何APIException异常都会被捕获到,并且处理成合适的响应信息;
4-在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制(认证、权限、频率)
2.APIView类属性
APIVIew
-类属性:
renderer_classes # 响应格式
parser_classes #能够解析的请求格式
authentication_classes#认证类
throttle_classes#频率类
permission_classes#权限类
3.基于APIView+ModelSerializer+Resposne写5个接口
# urls 路由层
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/', views.BookView.as_view()),
path('book/<int:pk>/', views.BookDetailView.as_view()),
]
# views 视图层
from rest_framework.views import APIView
from app01 import models
from rest_framework.response import Response
from app01 import serializer
class BookView(APIView):
def get(self, request):
book_query_set = models.Book.objects.all()
serializer_obj = serializer.BookSerializer(instance=book_query_set,many=True)
return Response(serializer_obj.data)
def post(self,request):
serializer_obj = serializer.BookSerializer(data=request.data)
if serializer_obj.is_valid():
serializer_obj.save()
return Response({"msg":'新增成功'})
return Response({"msg":serializer_obj.errors})
class BookDetailView(APIView):
def get(self,request,pk):
book = models.Book.objects.filter(pk=pk).first()
serializer_obj = serializer.BookSerializer(instance=book)
return Response(serializer_obj.data)
def put(self,request,pk):
book = models.Book.objects.filter(pk=pk).first()
serializer_obj = serializer.BookSerializer(instance=book,data=request.data)
if serializer_obj.is_valid():
serializer_obj.save()
return Response({"msg": '修改成功'})
return Response({"msg": serializer_obj.errors})
def delete(self,request,pk):
models.Book.objects.filter(pk=pk).delete()
return Response()
# serializer 序列化类
from rest_framework import serializers
from app01 import models
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ['name', 'price','publish', 'authors', 'publish_detail', 'author_list'] # 序列化反序列化字段
extra_kwargs = {
"publish": {"write_only": True},
"authors": {"write_only": True},
# 'publish_detail': {'read_only': True}, # dict
# 'author_list': {'read_only': True}, # list
}
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
4.GenericAPIView
问题:
如果需要再新写关于作者的五个接口 ----> 又需要写一个CBV
但是这两个CBV的区别仅仅在于使用的 表模型 和 序列化类 不同,其他都是重复的代码。
这岂不是很麻烦?能不能通过继承的方式,优化代码?
如果你想使用GenericAPIview,你需要从如下二种选择其一:
1.在视图类中设置如下属性 (常用)
queryset、serializer_class
2.重写GenericAPIview类的 get_queryset()/get_serializer_class()方法
如果你重写了一个视图方法,那么重要的是 你应该调用get_queryset() 而不是直接的访问queryset属性。
因为queryset将只被设置一次,并且为了后续到来的所有请求,这个结果会被缓存。
总而言之,不要直接访问queryset、serializer_class属性,而是使用GenericAPIview提供的各种方法获取。
5.GenericAPIView类属性/方法
我们通过案例来解释说明:
# 首先指定模型对象 和 序列化类
class BookView(GenericAPIView):
queryset = Book.objects.all()
# queryset = Book.objects 这样也是可行的
serializer_class = BookSerializer
# 以下代码都是等效的
objs = Book.objects.all()
objs = self.get_queryset()
# 以下代码都是等效的
ser = self.get_serializer(instance=objs, many=True)
BookSerializer(instance=objs, many=True)
get_queryset()
get_queryset # 源码解析
1.如果没有在视图类中写queryset属性,然后就调用get_queryset,会抛出异常。
2.获取我们设置的queryset属性,如果是Queryset对象,则调用all()方法,最后将我们设置的queryset类属性返回出去。
get_serializer()
使用get_serializer()方法可以返回序列化器的实例,此序列化器,被应用于校验、反序列化前端输入和序列化后端输出。
get_serializer # 源码解析
1.通过get_serializer_class方法获取了我们在视图类中指定的序列化类
2.添加了一个'context'参数传入我们的序列化类。
相当于BookSerializer(instance=objs, many=True, context={一些数据...})
get_serializer_class()
get_serializer_class方法基本上什么事情都没有做,直接将序列化器返回,有需求可以重写get_serializer_class。
可以实现:不同的接口使用的序列化类不一样。序列化使用某一个序列化类,反序列化用另一个序列化类。
get_object()
就是通过pk参数和get_object方法将模型对象查询出来的。
返回应用于详细视图的对象实例。默认使用 'lookup_field' 参数过滤基本的查询集。
该方法可以被重写以提供更复杂的行为,例如基于多个 URL 参数的对象查找。
lookup_field属性
如果你想使用pk之外的对象查找方式,可以设置lookup_field。如果有更复杂的查找需求,可以重写get_object()。
# 更多GenericAPIview 类属性
基本设置
queryset - 用于从视图返回对象的查询结果集。通常,你必须设置此属性或者重写 get_queryset() 方法。如果你重写了一个视图的方法,重要的是你应该调用 get_queryset() 方法而不是直接访问该属性,因为 queryset 将被计算一次,这些结果将为后续请求缓存起来。
serializer_class - 用于验证和反序列化输入以及用于序列化输出的Serializer类。 通常,你必须设置此属性或者重写get_serializer_class() 方法。
lookup_field - 用于执行各个model实例的对象查找的model字段。默认为 'pk'。 请注意,在使用超链接API时,如果需要使用自定义的值,你需要确保在API视图和序列化类都设置查找字段。
lookup_url_kwarg - 应用于对象查找的URL关键字参数。它的 URL conf 应该包括一个与这个值相对应的关键字参数。如果取消设置,默认情况下使用与 lookup_field相同的值。
配置文件相关
以下属性用于在与列表视图一起使用时控制分页。
pagination_class - 当分页列出结果时应使用的分页类。默认值与 DEFAULT_PAGINATION_CLASS 设置的值相同,即 'rest_framework.pagination.PageNumberPagination'。
filter_backends - 用于过滤查询集的过滤器后端类的列表。默认值与DEFAULT_FILTER_BACKENDS 设置的值相同。
6.基于GenericAPIView+5个视图扩展类
视图类
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)
序列化类
class BookSerializer(serializers.ModelSerializer):
# 跟表有关联
class Meta:
model = Book
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
路由
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
五个视图扩展类
五个视图扩展类并不是视图类
我们通过导入
from rest_framework.mixins import ListModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin,CreateModelMixin
来使用五个视图类,
因为我们在编写项目的时候可能一个CBV里面不需要全部的接口,这样的话我们就可以通过视图扩展类+GenericAPIView来实现
九个视图子类
# 两个视图基类
1.APIView 2.GenericAPIView
APIView: renderer_classes响应格式类 parser_classes请求解析类 跟数据库解耦合
GenericAPIView:queryset数据集 serializer_class序列化类 跟数据库耦合
# 5个视图扩展类 (提供方法)
ListModelMixin --> list --> 查询所有
RetrieveModelMixin --> retrieve --> 查询一个
CreateModelMixin --> create --> 新增一个
UpdateModelMixin --> update --> 修改一个
DestroyModelMixin --> destroy --> 删除一个
# 9个视图子类
继承关系公式: 视图子类 = n * 视图扩展类 + GenericAPIView
# 示例:
ListAPIView = ListModelMixin + GenericAPIView
RetrieveAPIView = RetrieveModelMixin + GenericAPIView
CreateAPIView = CreateModelMixin + GenericAPIView
...
RetrieveDestroyAPIView = RetrieveModelMixin + DestroyModelMixin + GenericAPIView
RetrieveUpdateDestroyAPIView = RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + GenericAPIView
'''
总结:9个视图子类都继承GenericAPIView
'''
使用视图子类编写五个接口
#
## 路由
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookView.as_view()),
]
# 视图类
class BookView(ListCreateAPIView): # 查询所有,新增一个
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(RetrieveUpdateDestroyAPIView): # 新增一个,修改一个,删除一个
queryset = Book.objects.all()
serializer_class = BookSerializer
视图集
1.继承ModelViewSet类写五个接口
# 路由
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'})),
]
# 视图类
class BookView(ModelViewSet): # 查询所有,新增一个
queryset = Book.objects.all()
serializer_class = BookSerializer
我们可以看一下modelviewset内部的继承关系
从注释也可以看出来他继承了 5个视图扩展类,也就是说ModelViewSet内部具备所有的"动作",也就是例如:
create()、list()、update()、retrieve()、destroy()这些方法
但是我们请求来了,还是会调用视图类中的get()、post()、put()、delete()这些方法.
只要调用方法就会到继承的五个视图扩展类中找寻相关方法!
我们在urls中编写的 as_view()
里面还编写了请求方式和对应的方法
这是因为ModelViewSet继承的最后一个类GenericViewSet,他的父类ViewSetMixin重写了as_view。
可以得知,一旦继承ModelViewSet,路由层的写法就变了!
现在需要这样写:
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'})),
]
# 这样写的意思是:
对于books/这个路由:
get请求 --执行--> list方法
post请求 --执行--> create方法
对于books/<int:pk>/这个路由:
get请求 --执行--> retrieve方法
put请求 --执行--> updata方法
delete请求 --执行--> destroy方法
2.继承 ReadOnlyModelView编写2个只读接口
# 路由
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
查看 readonlymodelview内部继承关系
这个类中只有list方法和retrieve方法。他同样继承了魔法类ViewSetMixin。
所以继承这个类就只能写两个只读接口:查询所有、查询一个
ViewSetMixin源码分析
查找as_view方法
路由写法为什么变了?
导致路由写法变了的原因是: ViewSetMixin
当请求来了之后,会执行ViewSetMixin类中的as_view方法的返回值
请求来了
路由匹配成功get请求,
匹配成功books,
会执行 views.BookView.as_view({'get': 'list', 'post': 'create'})()
读as_view【这个as_view是ViewSetMixin的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):
self = cls(**initkwargs)
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
self.action_map = actions
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
return self.dispatch(request, *args, **kwargs)
# 去除了csrf校验
return csrf_exempt(view)
如果不给actions传参数,直接抛出异常。
也就是不给as_view()传字典,就会抛出异常。
as_view执行完后会返回内层函数view:(这里执行的view是去除了csrf校验的)
# 路由匹配成功执行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)
return self.dispatch(request, *args, **kwargs) #dispatch是APIView的
# 关于这里self.dipatch的说明
self.dipatch是APIView的dispatch
'''
self.dipatch --进行--> 封装新request, 执行三大认证 --调用--> django view的dispatch
'''
# 关于反射的总结
反射得到的是我们继承的List create方法
反射修改对象的属性 比如将get方法修改为存放list方法
最后的dispatch作用是获取你写的CBV类中的get方法(此时get方法 --> list方法)。
魔法类可以修改对象中的属性所指向的方法。
# 关于整体的总结:
-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw)
-2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
-只要传入actions,以后访问get就是访问list,访问post,就是访问create
-3 其他执行跟之前一样
-4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】