前言
drf 视图的源码非常的绕,但是实现的功能却非常的神奇。
它能够帮你快速的解决ORM 增删改查的重复代码,非常的方便好用。
下面是它源码中的一句话:
class ViewSetMixin:
"""
This is the magic.
"""
好了,屁话不多说,直接看看drf 视图中的功能吧。
准备工作
此次的Django 采用3 版本,所以相对于1 版本来说有一些差异。
模型表
下面是模型表:
from django.db import models
# Create your models here.
class User(models.Model):
user_id = models.AutoField(primary_key=True)
user_name = models.CharField(max_length=50)
user_gender = models.BooleanField(
[(0,"male"),(1,"female")],default = 0,)
user_age = models.IntegerField()
def __str__(self):
return self.user_name
class Meta:
db_table = ''
managed = True
verbose_name = 'User'
verbose_name_plural = 'Users'
数据如下:
INSERT INTO app01_user(user_name,user_age,user_gender) VALUES
("用户1",18,0),("用户2",19,1),("用户3",1);
序列类
序列类采用ModelSerializer :
from rest_framework import serializers
from . import models
class UserModelSerializers(serializers.ModelSerializer):
class Meta:
model = models.User
fields = "__all__"
url路由
下面是url 路由的设定:
re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view())
封装Rsponse
由于不需要返回原生的Response ,所以我们封装了一个类,用于更加方便的返回Response :
class ResponseMeta(type):
# 对Response类做封装
def __call__(cls,*args,**kwargs):
obj = cls.__new__(cls,**kwargs)
cls.__init__(obj,**kwargs)
return Response(data=obj.__dict__)
class CommonResponse(object,metaclass=ResponseMeta):
# 返回的信息
def __init__(self,status,data=None,errors=None):
self.status = status
self.data = data
self.errors = errors
APIView
继承关系
APIView 的导入如下:
from rest_framework.views import APIView
APIView 继承了原生的DjangoView ,在其之上做了一些封装,使操作更加简单。

封装特性
在APIView 中对原生的request 对象进行封装,最常用的两个属性如下,它弥补了Django 原生View 对JSON 请求格式的数据没有处理的缺陷。
同时,APIView 认为对于GET 请求的资源参数,不应该使用GET 获取,而是应该使用query_params 进行获取。
属性 |
描述 |
request.data |
当请求数据为Json格式时,将以dict形式保存,主要针对request.POST请求 |
request.query_params |
当请求方式为GET时,可获取url中的请求数据 |
接口书写
以下是使用APIView 对User 表进行增删改查的接口书写。
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from . import models
from . import ser
class ResponseMeta(type):
# 对Response类做封装
def __call__(cls,errors=None):
self.status = status
self.data = data
self.errors = errors
class UserAPI(APIView):
def get(self,request,uid=None):
if not uid:
# 获取所有
user_queryset = models.User.objects.all()
if user_queryset.exists():
serialization = ser.UserModelSerializers(instance=user_queryset,many=True)
return CommonResponse(status=100,data=serialization.data,errors=None)
return CommonResponse(status=200,errors="暂时没有任何学生")
else:
user_obj = models.User.objects.filter(pk=uid).first()
if user_obj:
serialization = ser.UserModelSerializers(instance=user_obj)
return CommonResponse(status=100,errors="没有该学生")
def post(self,request):
serialization = ser.UserModelSerializers(data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100,errors=None)
return CommonResponse(status=200,errors=serialization.errors)
def patch(self,uid):
user_obj = models.User.objects.filter(pk=uid).first()
if user_obj:
serialization = ser.UserModelSerializers(instance=user_obj,data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100,errors=None)
else:
return CommonResponse(status=200,errors="修改失败,请检查字段是否一直")
else:
return CommonResponse(status=200,errors="修改失败,请检查该用户是否存在")
def delete(self,uid):
models.User.objects.get(pk=uid).delete()
return CommonResponse(status=100,data="删除成功",errors=None)
问题发现
在上述代码中,问题有以下几点:
- 重复代码多,在每个接口中都需要书写
ORM 查询
- 每个接口都需要针对同一个序列类做出不同的实例化
GenericAPIView
继承关系
GenericAPIView 的导入如下:
from rest_framework.generics import GenericAPIView
以下是它的继承关系:

可以发现它是对APIView 的继承,所以理论上来说应该又多了一些东西。
源码阅读
下面来看一下GenericAPIView 的源码,首先你可以发现大概有4个类属性:
class GenericAPIView(views.APIView):
queryset = None # 要查询的数据表
serializer_class = None # 执行序列化的序列化类
lookup_field = 'pk' # 查询时的查询条件,默认按主键查询
lookup_url_kwarg = None # 如果在视图中,url捕获的查询数据表过滤参数不是pk,你应该进行声明
接着往下看,其实它的方法很少,对外暴露的方法就更少了。

我们这里就先看最常用的,即对外暴露的方法,首先是get_queryset() 。
def get_queryset(self):
assert self.queryset is not None,(
"'%s' should either include a `queryset` attribute,"
"or override the `get_queryset()` method."
% self.__class__.__name__
) # 做验证,即实例属性queryset不能是空,代表这个类属性你必须要声明,你可以选择将它做成类属性也可以做成实例属性
queryset = self.queryset # 进行赋值,将self.queryset赋值为类属性。先在UserAPI的实例中找,找不到再到UserAPI的类中找
if isinstance(queryset,QuerySet):
queryset = queryset.all() # 如果它是一个QuerySET对象,就获取全部,得到一个QuerySetDict对象
return queryset # 进行返回
看到这里发现了一个点,即queryset 这个属性必须要进行赋值,由于属性查找顺序是先查找实例,而后查找类本身,所以我们直接在UserAPI 中声明queryset 为类属性即可。
接下来继续继续看,get_object() ,见明知意,它可以从数据表中获取单个对象:
def get_object(self):
queryset = self.filter_queryset(self.get_queryset()) # 首先会运行get_queryset(),获取一个所有对象的列表,然后进行filter过滤
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field # 这里是对url中获取到的变量进行映射
assert lookup_url_kwarg in self.kwargs,( # 比如,url中获取到的名为uid,如果uid没有在kwargs中,即是{uid:4}中,则抛出异常
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf,or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__,lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} # 进行查询替换,默认的self.lookup_field是pk,将uid替换为pk。那么这里就是{pk:4}
obj = get_object_or_404(queryset,**filter_kwargs) # 获取对象
self.check_object_permissions(self.request,obj) # 进行验证权限
return obj # 返回单个对象
看到这里就发现了,默认查询条件是用pk ,也就是说你的url 中必须要用pk 这个形参名进行分组捕获。否则就需要声明lookup_url_kwarg ,即lokup_url_kwarg="uid" ,然后进行替换组建filter_kwargs 。当然如果你的查询条件不是用的pk ,就需要修改lookup_field 为字段名,如我不是按照pk 进行查询,而是按照name ,就修改lookup_field 为name 。
re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view())
接下来再看另一个方法get_serializer() :
def get_serializer(self,**kwargs):
serializer_class = self.get_serializer_class() # 内部调get_serializer_class
kwargs.setdefault('context',self.get_serializer_context()) # 获取context属性
return serializer_class(*args,**kwargs) # 调用serializer_class并返回
接下来是get_serializer_class() 方法:
def get_serializer_class(self):
assert self.serializer_class is not None,( # 传入的必须不能是None
"'%s' should either include a `serializer_class` attribute,"
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class # 返回设置的属性,serializer_class,即序列化类
OK,其实源代码读到这里就行了。
封装特性
通过上面的源代码分析,总结出如下方法的使用:
方法/属性 |
描述 |
queryset |
将要查询的数据表,类型应该是QuerySet |
serializer_class |
将要执行的序列化类,类型不能为None |
lookup_field |
查询时的查询条件,默认为pk |
lookup_url_kwarg |
视图中url捕获的查询条件变量名如果不是pk,则应该进行指定 |
get_queryset() |
查询获取所有记录 |
get_object() |
查询获取单条记录 |
get_serializer() |
执行序列化对象 |
关于最常用的调用方法就三个,常用属性四个。
其他的方法基本上都是内部调用,所以暂时不深究。
接口书写
接下来使用GenericAPIView 进行接口书写。
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from . import models
from . import ser
class ResponseMeta(type):
# 对Response类做封装
def __call__(cls,errors=None):
self.status = status
self.data = data
self.errors = errors
class UserAPI(GenericAPIView):
queryset = models.User.objects # 传入对象即可,但是更推荐加上all(),它会避免一些问题
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明
def get(self,uid=None):
if not uid:
# 获取所有
user_queryset = self.get_queryset() # 获取所有
if user_queryset.exists():
serialization = self.get_serializer(instance=user_queryset,many=True) # 获取序列化类,序列化多条
return CommonResponse(status=100,errors="暂时没有任何学生")
else:
user_obj = self.get_object()
if user_obj:
serialization = self.get_serializer(instance=user_obj)
return CommonResponse(status=100,request):
serialization = self.get_serializer(data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100,uid):
user_obj = self.get_object()
if user_obj:
serialization = self.get_serializer(instance=user_obj,uid):
self.get_object().delete()
return CommonResponse(status=100,errors=None)
问题发现
相对于使用APIView 来说,它不必再手动去写ORM 语句。
但是对于返回信息、对于验证操作还是要自己写。
mixins中扩展类
五个扩展类
下面是rest_framework.mixins 中的五个扩展类,它们做了更高级别的封装,配合GenericAPIView 使用有奇效。
from rest_framework.mixins import ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin
类 |
描述 |
ListModelMixin |
该类主要负责查询所有记录 |
RetrieveModelMixin |
该类主要负责查询单条记录 |
CreateModelMixin |
该类主要负责创建记录 |
UpdateModelMixin |
该类主要负责对记录做更新操作 |
DestroyModelMixin |
该类主要负责删除记录 |
继承关系
这五个类都继承于object ,是独立的子类。

源码阅读
下面是ListModelMixin 的源码,不难发现,它就是配合GenericAPIView 使用的,因为它会使用get_queryset() 方法,并且,它会自动的返回Response 对象,并把验证结果添加进去:
class ListModelMixin:
def list(self,**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)
至于其他几个类,其实都差不多,这里摘出两个比较特别的类来看一下,分别是CreateModelMixin 与DestroyModelMixin 这两个类。
下面是CreateModelMixin 类的源码:
class CreateModelMixin:
def create(self,**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) # 返回一个location请求头
return Response(serializer.data,status=status.HTTP_201_CREATED,headers=headers) # 注意返回结果,状态码是201
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 类的源码:
class DestroyModelMixin:
def destroy(self,**kwargs):
instance = self.get_object()
self.perform_destroy(instance) # 内部执行删除
return Response(status=status.HTTP_204_NO_CONTENT) # 返回状态码204
def perform_destroy(self,instance):
instance.delete()
那么读这两个类的源码,就是想要让你知道,创建成功后的返回状态码是201,而删除成功的返回状态码是204。这在REST 规范中写的很清楚,可以看见这里也是这么做的。
类与方法
下面是不同的五个扩展类中不同的五个方法,功能与类一样。
类 |
方法 |
描述 |
ListModelMixin |
list() |
查询所有,并返回Response对象 |
RetrieveModelMixin |
retrieve() |
查询单条,并返回Response对象 |
CreateModelMixin |
create() |
创建记录,并返回Response对象 |
UpdateModelMixin |
update() |
更新记录,并返回Response对象 |
DestroyModelMixin |
destroy() |
删除记录,并返回Response对象 |
由于它会自动进行return Response() ,所以我们就不用再对返回对象进行包装了。
接口书写
下面是利用GenericAPIView 与mixins 中的五个扩展类进行接口书写。
from . import models
from . import ser
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin,DestroyModelMixin
class UserAPI(GenericAPIView,ListModelMixin,DestroyModelMixin):
queryset = models.User.objects # 传入对象即可,但是更推荐加上all(),这会避免一些问题
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明
def get(self,uid=None):
if not uid:
# 获取所有
return self.list(request)
else:
return self.retrieve(request,uid)
def post(self,request):
return self.create(request)
def patch(self,uid):
return self.update(request,uid)
def delete(self,uid):
return self.destroy(request,uid)
问题发现
可以看见,代码相比于前两个少了非常非常多。但是还是存在一些问题。
第一个问题就是这个视图UserAPI 继承的类太多了,太长了,其次就是每次都需要在视图中return ,它能不能帮我们自己return 呢?那这个就非常舒服了。
modelViewSet
基本使用
modelViewSet 是针对GenericAPIView 与mixins 中扩展类的结合使用做了一些优化,它可以根据不同的请求自动的做出回应。
同时也不再需要你在视图中进行return 。以下是基本使用方法,但是使用它时我们需要对路由做一些改进,具体的情况等下面的源码分析后你就明白了:
urlpatterns = [
path('admin/',admin.site.urls),path('api/users/',views.UserAPI.as_view(actions={"get":"list","post":"create"})),re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view(actions={"get":"retrieve","patch":"update","delete":"destroy"}))
]
那么在views.py 中,书写的话很简单:
from . import models
from . import ser
from rest_framework.viewsets import ModelViewSet
class UserAPI(ModelViewSet):
queryset = models.User.objects # 传入对象即可,但是更推荐加上all(),这会避免一些问题
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明
继承关系
ModelViewSet 的导入如下:
from rest_framework.viewsets import ModelViewSet
你可看它的源码,它其实也没什么特别之处,就是针对上面第一个问题做了改进。但是你会发现,它会继承一个新的类,即GenericViewSet 这个类。
class ModelViewSet(mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,mixins.ListModelMixin,GenericViewSet):
"""
A viewset that provides default `create()`,`retrieve()`,`update()`,`partial_update()`,`destroy()` and `list()` actions.
"""
pass
下面是它的继承图:

那么GenericViewSet 中又会有什么新的发现呢?我们先看一看它。
GenericViewSet
打开GenericViewSet 中发现什么都没有。但是它继承了ViewSetMixin 。
class GenericViewSet(ViewSetMixin,generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,but does include the base set of generic view behavior,such as
the `get_object` and `get_queryset` methods.
"""
pass
ViewSetMixin
我们可以在上面基本使用时对url 中as_view() 传参发现了一点不一样的地方,我们传递进了一个关键字参数actions ,这个参数非常的蹊跷,因为在APIView 中的as_view() 方法中并没有为该参数预留位置。
def as_view(cls,**initkwargs):
我们再接着看看GenericAPIView 中的as_view() 方法有没有为该参数预留位置,非常遗憾的是在GenericAPIView 中根本就没有as_view() 方法,说明它用了父类也就是APIView 的as_view() 方法
那么只有一个可能,就是ViewSetMixin 覆写了as_view() 方法,那么到底是不是这么回事?我们看一下就知道了:
class ViewSetMixin:
@classonlymethod
def as_view(cls,actions=None,**initkwargs):
是的,那么它内部是怎么做的呢?实际上它的核心代码就是那个for 循环,它会根据不同的请求方式来执行不同的mixins 中五个扩展类的方法,因此我们需要两条url 来放入不同的actions 。由于modelsViewSet 继承了mixins 五个扩展类,所以才能够调用扩展类下的方法。
@classonlymethod
def as_view(cls,**initkwargs): # cls即为UserAPI这个类
cls.name = None
cls.description = None
cls.suffix = None
cls.detail = None
cls.basename = None
if not actions: # 必须传入actions,否则抛出异常
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
for key in initkwargs: # 构造字典,不用管
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key,cls.__name__))
if not hasattr(cls,key):
raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__,key))
if 'name' in initkwargs and 'suffix' in initkwargs: # 不用管,这个也是构造字典
raise TypeError("%s() received both `name` and `suffix`,which are "
"mutually exclusive arguments." % (cls.__name__))
def view(request,**kwargs): # 闭包函数view
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(): # 其实这里是核心代码, actions={"get":"retrieve","delete":"destroy"},或者等于{"get":"list","post":"create"}
handler = getattr(self,action) # 根据请求方式,来执行list、create、retrieve、update、destroy这几个方法
setattr(self,method,handler)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request,**kwargs)
update_wrapper(view,cls,updated=()) # 传入执行update_wrapper(),不用管
update_wrapper(view,cls.dispatch,assigned=()) # 不用管
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
根据不同的请求方式来执行不同的函数方法,可以说这个设计非常的巧妙,所以你可以像下面这样做:
# views.py
from rest_framework.viewsets import ViewSetMixin
class Book6View(ViewSetMixin,APIView): # 一定要放在APIVIew前,因为as_view()的查找顺序一定要先是ViewSetMixin
def get_all_book(self,request):
print("xxxx")
book_list = Book.objects.all()
book_ser = BookSerializer(book_list,many=True)
return Response(book_ser.data)
# urls.py
#继承ViewSetMixin的视图类,路由可以改写成这样
path('books6/',views.Book6View.as_view(actions={'get': 'get_all_book'})), (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|