前言
- 本文内容主要讲解介绍Django Rest Framework框架,结合如下django项目学习食用:Github仓库地址
- 所有请求的调试,均通过postman实现
基本概念
FBV:function based views
学习参照Github仓库的fbv文件夹
特点
-
无需继承类
-
基于函数形式的视图开发
-
fbv默认为get方法,如果提供post或其他类型方法,需要自行if判断
代码实现
# urls.py
urlpatterns = [
path('book/list/', views.book_list),
]
# views.py
def book_list(request):
book_name = '射雕英雄传'
return render(request, 'book_list.html', {'book_name':book_name})
-
客户端访问路由:
http://127.0.0.1:8000/book/list/
时,触发book_list
函数 -
服务端执行
book_list
函数的内容,处理后返回结果
CBV:class based views
特点
- 基于类的视图函数调用
- 用函数将各种请求方式分开,符合面向对象的开发
- CBV的本质是FBV
介绍
- Django Rest Framework 框架所封装的方法都是CBV形式的视图
- 下文将逐步介绍 Django Rest Framework 框架,体会面向对象开发的魅力
CBV_View【Django】— — Django的原生View
学习参照Github仓库的cbv_view文件夹
流程追踪,源码解析
# urls.py
urlpatterns = [
url(r'^books/$', views.BooksView.as_view()),
url(r'^books/(?P<pk>\d+)$', views.BookView.as_view()),
]
客户端访问特定路由,激活自定义类的as_view函数
# views.py
class BooksView(View):
def get(self, request):
pass
def post(self, request):
pass
BooksView类中没有as_view函数,从其父类View里面寻找
@classonlymethod # 允许在不实例化类的时候,调用类方法:as_view
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
return view
在删除了不必要的部分后,可以看出as_view函数的关键在于:其内部定义了一个view函数,并且将该函数作为返回值
即:views.BooksView.as_view() → views.BooksView.view() → views.BookView.dispatch()
self = views.BookView
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
# 补充
http_method_names = [
"get",
"post",
"put",
"patch",
"delete",
"head",
"options",
"trace",
]
可以看到dispatch函数通过getattr()方法,将"get"、"post"等请求与函数get()、post()联系在一起
- request.method.lower() = “get”
- handler = self.get() = views.BookView.get()
注意点
- 若是需要在每个函数执行对应功能前,加入公共的操作,就可以在
BookView
中重写dispatch
函数。但要注意,重写了dispatch
函数需要给定返回结果
as_view()可选参数:**initkwargs
# urls.py
urlpatterns = [
url(r'^books/$', views.BooksView.as_view(para='xxx')),
]
# views.py
class BooksView(View):
para = None
def get(self, request):
pass
def post(self, request):
pass
可以在URL配置中传递额外的参数给视图类,并在视图类中使用这些参数进行处理。请注意,参数名称需要与视图类的构造函数参数名称相匹配。
as_view传入的参数必须在BooksView中定义好,不可出现未定义的类属性变量
CBV_APIView【DRF】— — 基于Django的原生View
学习参照Github仓库的cbv_apiview文件夹
流程追踪,源码解析
同于View,但是在内容上,处理流程更加复杂
视图处理上的差异
# 数据处理上的区别
# view
def post(self, request):
# ===== 差异所在 =====
data = request.body.decode()
data_dict = json.loads(data)
print(type(request.body), type(request.body.decode()), type(data_dict))
# 验证数据
btitle = data_dict.get('btitle')
bpub_date = data_dict.get('bpub_date')
if btitle is None or bpub_date is None:
return JsonResponse({'errors': '错误信息'}, status=400)
# 保存数据
book = BookInfo.objects.create(btitle=btitle, bpub_date=bpub_date)
# APIView
def post(self, request):
# ===== 差异所在 =====
ser = BookInfo_Serializers(data=request.data)
resp = {}
if ser.is_valid():
ser.save()
resp['code'] = 201
resp['code'] = ser.data
else:
print(ser.errors)
resp['code'] = 401
resp['code'] = ser.errors
return Response(resp)
支持序列化器操作,在Request和Response上都有区别
View和APIView的区别
APIView继承自View
APIView | View |
---|---|
传入视图函数中的是Rest Framework的Request对象【优点-1】 | 是Django的原生HttpRequest对象【不足-1】 |
视图方法返回Rest Framework的Response对象【优点-2】 | 是Django的原生JsonResponse对象 |
【优点-1】
- 数据解析:DRF提供了Parser解析器。在接收到请求后会**自动根据Content-Type指明的请求数据类型(如JSON、表单等)**将请求数据进行parse解析,解析为类字典对象保存到Request对象中
- 序列化机制:无论前端发送哪种格式的数据,都可以以统一的方式读取数据,并且与Serializer集成,可方便处理POST、PUT请求
- 完善的异常处理机制(没深入了解)
- 身份验证机制(没深入了解)
【不足-1】
- 需要自行对传入的数据解析:decode()、json()、…
【优点-2】
- 功能扩展:Response的父类和JsonResponse的父类中都有HttppResponse,Response的功能要更加完善
- 序列化支持:与Serializer集成,可直接将Serializer的实例作为Response响应的数据
- 响应格式统一:Respnse响应格式符合Restful API设计规范:状态码、响应数据、其他信息(分页、错误信息)。且这些信息在不同的视图里面是统一的,便于用户处理
- 响应修改灵活:Response提供多种方法、属性,便于访问和修改响应的各个部分
Serializer和ModelSerializer的学习(暂跳过)
CBV_GenericAPIView【DRF】:基于DRF的APIView
学习参照Github仓库的cbv_genericapiview_modelserializers文件夹
APIView和GenericAPIView的区别
GenericAPIView继承自APIView
APIView回顾
- 提供了DRF定义的Resuest对象和Response对象,可以结合序列化器,方便快捷的进行数据处理响应
- 在该过程中,不管是get()、post()或put()方法,都会用到序列化器和query_set对象,并且是重复使用
GenericAPIView新增
- 在APIView的基础上,进一步扩展功能、封装;简化代码实现
视图处理
class Book_Genericapiview_ModelSerializers(GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
'''获取单一和更新和删除'''
def get(self, request, pk):
# # 方法1:APIView常规操作
# try:
# books = BookInfo.objects.get(id=pk)
# except:
# return Response({'errors':'错误信息, 数据不存在'}, status=400)
# 方法2:Genericapiview提供的操作
ser = self.get_serializer(instance=self.get_object(), many=False)
return Response(ser.data)
通过定义类基本属性:queryset和serializer_class,并结合GenericAPIView定义的类方法,实现序列化、单一数据查询、多数据查询等操作
等价操作
GenericAPIView | APIView |
---|---|
self.get_queryset() | BookInfo.objects.all() |
self.get_object() | BookInfo.objects.get(id=pk) |
self.get_serializer_class() | BookSerializer |
self.get_serializer(instance=self.get_queryset(), many=True) | BookSerializer(instance=self.get_queryset(), many=True) |
CBV_GenericAPIView_Mixin【DRF】
学习参照Github仓库的cbv_genericapiview_minin文件夹
Mixin和GenericAPIView的联系
并列为自定义视图类提供父类继承关系
GenericAPIView回顾
- 尽管封装了queryset和serializer_class类,简化了操作。但是对于get、post、put等方法还是需要自己去实现有关查询、新建、更新、删除等具体实现逻辑
Mixin扩展
- Mixin封装了5个类:
- ListModelMixin,提供list函数,用以查询所有的对象数据
- CreateModelMixin,提供了create函数,用以新建对象数据
- RetrieveModelMixin,提供了retrieve函数,用以查询特定id或其他字段的数据
- UpdateModelMixin,提供了update函数,用于更新特定id的对象数据
- DestroyModelMixin,提供了destory函数,用于删除特定id的对象数据
视图处理
# viewspy
class Books_Genericapiview_Minin(ListModelMixin, CreateModelMixin, GenericAPIView):
# GenericAPIView规定要写的类属性
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
'''获取所有和保存'''
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
# 找寻方法也是从左到右,继承顺序很关键
class Book_Genericapiview_Minin(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
# 自定义destory函数,改变删除逻辑
def destroy(self, request, pk):
books = self.get_object()
books.is_delete = True
books.save()
return Response({})
'''获取单一和更新和删除'''
def get(self, request, pk):
return self.retrieve(request, pk)
def put(self, request, pk):
return self.update(request, pk)
# Minin里面定义的delete是物理删除
def delete(self, request, pk):
return self.destroy(request, pk)
CBV_GenericAPIView_Mixin_Repackaging【DRF】
学习参照Github仓库的cbv_genericapiview_minin_repackaging文件夹
Mixin和Mixin_Repackaging的联系
Mixin扩展回顾
- 尽管封装了5个类,写好了增删改查查操作的具体实现逻辑,但是仍需要在定义的时候声明get()、post()等方法
Mixin_Repackaging扩展
- 根据实现逻辑,对五个类进行组合,并在内部写好get、post等方法:
- ListCreateAPIView,提供list函数、create函数,并且内部定义绑定了get函数、post函数
- RetrieveUpdateDestroyAPIView,提供了retrieve、update、destory函数,并且在内部实现了get函数、update函数、destory函数
- …
视图处理
# views.py
class Books_Genericapiview_Minin_Repackaging(ListCreateAPIView):
# GenericAPIView规定要写的类属性
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
class Book_Genericapiview_Minin_Repackaging(RetrieveUpdateDestroyAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
# 自定义destory函数,改变删除逻辑
def destroy(self, request, pk):
books = self.get_object()
books.is_delete = True
books.save()
return Response({})
CBV_Viewset【DRF】
学习参照Github仓库的cbv_viewset文件夹
回顾
- 逐步封装:View、APIView、GenericAPIView、GenericAPIView_Mixin、GenericAPIView_Mixin_Repackaging
- 视图函数的编写越来越简洁,增删改查查功能越来越集中
- 但:不管封装到那一步,关于查操作:查询单个数据和查询全部数据,都是通过两个视图类来做区分
- Viewset在此基础上,进行进一步的封装:
- 重写
despatch
函数,平且可以通过as_view()
函数传参,来将get、post、put等请求,绑定到具体的函数名称上 - 在类视图编写上,做到:一个类视图实现增删改查查所有操作
- 重写
源码解析
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
Viewset继承自ViewSetMixin, views.APIView(即上文所描述的APIView)
class 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'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
pass
ViewSetMixin 重写了 as_view函数,并且实现了 Http请求(get、post、put等)与 函数操作(自定义list函数,create函数)的绑定
视图处理
# views.py
class Books_Viewset(ViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
def get_all(self, request):
pass
def add_object(self, request):
pass
def get_object(self, request, pk):
pass
def update_object(self, request, pk):
pass
def delete_object(self, request, pk):
pass
# urls.py
urlpatterns = [
url(r'^books_viewset/$', views.Books_Viewset.as_view({
'get': 'get_all',
'post': 'add_object'
})),
# 有名分组:P<pk>
url(r'^books_viewset/(?P<pk>\d+)$', views.Books_Viewset.as_view({
'get': 'get_object',
'put': 'update_object',
'delete': 'delete_object'
}))
]
- 将增删改查查在一个视图函数里面实现,但是在urls里面,还是需要通过定义两个url路由才行
- 但是基础的viewset不提供任何的具体操作函数,需要自己编写
CBV_GenericViewSet_Mixin【DRF】
学习参照Github仓库的cbv_viewset文件夹
GenericViewSet_Mixin和ViewSet的区别
ViewSet回顾
- 不提供具体的资源操作方法,需要自定义函数并于Http 请求绑定
GenericViewSet_Mixin新增
- 借助Mixin类提供的默认create、update等资源操作方法,通过在urls路由里面进行绑定,实现http请求与资源操作函数的一一对应
视图处理
# views.py
class Books_Viewset_Pro(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
# urls.py
urlpatterns = [
url(r'^books_viewset_pro/$', views.Books_Viewset_Pro.as_view({
'get': 'list',
'post': 'create'
})),
# 有名分组:P<pk>
url(r'^books_viewset_pro/(?P<pk>\d+)$', views.Books_Viewset_Pro.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
}))
]
- 在urls里面,进行http请求和默认资源操作函数的绑定
- 但是还有优化空间:视图函数的继承、urls路由绑定的简化
CBV_ModelViewSet【DRF】
学习参照Github仓库的cbv_viewset文件夹
ModelViewSet和GenericViewSet_Mixin的区别
GenericViewSet_Mixin回顾
- 视图函数的继承实现有待优化、url路由请求方法和资源操作函数的绑定有待优化
ModelViewSet新增
- 大爹来了,究级封装
视图处理
# views.py
class Books_Viewset_Pro_Max(ModelViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookSerializer
# urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'books_viewset_pro_max', views.Books_Viewset_Pro_Max, basename='books_viewset_pro_max')
urlpatterns = [
url(r'^books_viewset_pro_max/$', views.Books_Viewset_Pro_Max.as_view({'get':'list', 'post':'create'})),
# 有名分组:P<pk>
url(r'^books_viewset_pro_max/(?P<pk>\d+)$', views.Books_Viewset_Pro_Max.as_view({'get':'retrieve', 'put':'update', 'delete':'destroy'})),
]
urlpatterns += router.urls
- 视图函数处理,简洁优美;url路由绑定,同样简洁优美
- http请求方法与资源操作函数的绑定可以通过路由分配,也可以通过as_view()函数传参
注意事项
- 凡单个数据操作:books_viewset_pro_max/pk/
- 基于router分配路由,最后一个斜杠一定要有,不然会落入匹配get请求的url中
- 基于as_view()字典分发的路由,则不需要最后一个斜杠
DRF CBV开发总结
注释
- get(请求全部数据)、get(请求单个数据)、post、put、delete,这5个http request请求缩写为:ggppd
- 将queryset和serializer写进类属性,简称为:写入q、s类属性
CBV模式 | 视图 | 路由 | 总结 |
---|---|---|---|
View(原生Django,非DRF) | 自定义ggppd及实现逻辑(写两个) | 调用as_view()函数匹配(不传参,写两个) | 通过despatch函数实现请求与资源操作函数的绑定 |
APIView | 自定义ggppd及实现逻辑(写两个) | 调用as_view()函数匹配(不传参,写两个) | 通过DRF封装提供的Request请求和Response响应,并结合序列化器,简化数据操作 |
GenericAPIView | 写入q、s类属性 | 调用as_view()函数匹配(不传参,写两个) | 将queryset和serializer写进类属性,并提供调用方法,避免重复使用序列化器 |
自定义ggppd及实现逻辑(写两个) | |||
GenericAPIView_Mixin | 写入q、s类属性 | 调用as_view()函数匹配(不传参,写两个) | 通过定义Mixin类,提供基本的资源操作函数:list、create、retrieve、update、destory |
自定义ggppd,实现逻辑由Mixin类提供(写两个) | |||
GenericAPIView_Mixin_Repackaging | 仅写入q、s类属性(写两个) | 调用as_view()函数匹配(不传参,写两个) | http请求和资源处理函数的绑定在类里面实现,完成进一步封装 |
Viewset | 写入q、s类属性 | 调用as_view()函数匹配(传参,写两个) | 通过as_view传参,完成请求资源与操作函数的绑定 |
自定义ggpppd及实现逻辑(写一个) | |||
GenericViewSet_Mixin | 仅写入q、s类属性(写一个) | 调用as_view()函数匹配(传参,写两个) | http请求和资源处理函数的绑定在类里面实现,完成进一步封装。且该情况下的传参,post只能对应create,除非重写 |
ModelViewSet | 仅写入q、s类属性(写一个) | 调用as_view()函数匹配(传参,写两个) | |
通过router路由分发(不传参,写一个) | 可通过自定义资源操作函数及请求方式和url,在增删改查查的基础上,继续扩展新的url |
DRF CBV开发总结-2
View(原生Django,非DRF)
维度 | 描述 |
---|---|
继承关系 | 无继承 |
视图 | 自定义ggppd及实现逻辑(写两个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
总结 | 通过despatch函数实现请求(ggppd)与资源操作函数的绑定:post请求对应的资源操作函数就是 post(*args, **kwargs) |
APIView(DRF)
维度 | 描述 |
---|---|
继承关系 | View |
视图 | 自定义ggppd及实现逻辑(写两个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
总结 | 通过DRF封装提供的Request请求和Response响应,并结合序列化器,简化数据操作 |
GenericAPIView(DRF)
维度 | 描述 |
---|---|
继承关系 | APIView |
视图 | 写入q、s类属性;自定义ggppd及实现逻辑(写两个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
总结 | 将queryset和serializer写进类属性,并提供调用方法:self.get_object、self.get_queryset(),避免重复使用序列化器 |
GenericAPIView_Mixin(DRF)
维度 | 描述 |
---|---|
继承关系 | ListModelMixin, CreateModelMixin, GenericAPIView |
RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView | |
视图 | 写入q、s类属性;自定义ggppd,实现逻辑由Mixin类提供(写两个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
总结 | 通过定义Mixin类,提供基本的资源操作函数:list、create、retrieve、update、destory |
GenericAPIView_Mixin_Repackaging(DRF)
维度 | 描述 |
---|---|
继承关系 | ListCreateAPIView:ListModelMixin, CreateModelMixin, GenericAPIView |
RetrieveUpdateDestroyAPIView:RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView | |
视图 | 仅写入q、s类属性(写两个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
总结 | http请求和资源处理函数的绑定在类里面实现,完成进一步封装 |
Viewset(DRF)
维度 | 描述 |
---|---|
继承关系 | ViewSetMixin, APIView |
视图 | 写入q、s类属性;自定义ggppd及实现逻辑(写一个视图类) |
路由 | 调用as_view()函数匹配(传参,写两个) |
总结 | 通过as_view传参,完成请求资源与操作函数的绑定 |
GenericViewSet_Mixin(DRF)
维度 | 描述 |
---|---|
继承关系 | GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin |
视图 | 仅写入q、s类属性(写一个视图类) |
路由 | 调用as_view()函数匹配(传参,写两个) |
总结 | http请求和资源处理函数的绑定在类里面实现,完成进一步封装。且该情况下的传参,post只能对应create,除非重写 |
ModelViewSet(DRF)
维度 | 描述 |
---|---|
继承关系 | ModelViewset |
视图 | 仅写入q、s类属性(写一个视图类) |
路由 | 调用as_view()函数匹配(不传参,写两个) |
调用router,进行路由分配(不传参,写一个) | |
总结 | 可通过自定义资源操作函数及请求方式和url,在增删改查查的基础上,继续扩展新的url |