纸上得来终觉浅,绝知此事要躬行。
前言
今天就来谈谈如何从Django
过渡到Django REST framework
,之所以要用Django REST framework
,因为它解决了Django
在实现前后端分离项目开发时的复杂性,其由于强大的序列化器、反序列化器、认证组件、权限组件、限流组件以及各种视图集很方便的实现各种符合Restful
风格的接口。
那么接下来让我们一起来探索这一过程,本章主要用来回顾以及对比Django
和Django REST framework
接口设计实现的差异,如果你已经了解可直接看其他章节的内容。
回顾原生Django
首先新建Django
项目,之后创建一个app
名称为api
,包括一些数据库的配置、创建超级用户等等,在此就不做演示,直接从代码定义开始:
- 定义模型类
class UserModel(models.Model):
GENDER = (
(0, '女'),
(1, '男')
)
username = models.CharField(max_length=64)
password = models.CharField(max_length=32)
phone = models.CharField(max_length=11, null=True, default=None)
gender = models.IntegerField(choices=GENDER, default=1)
avatar = models.ImageField(upload_to='avatar', default='avatar/default.jpeg')
class Meta:
db_table = "user" # 设置数据库表名
verbose_name = "用户" # 设置后台
verbose_name_plural = verbose_name
def __str__(self):
return self.username
- 主路由
urls
from django.conf.urls import url
urlpatterns = [
path('admin/', admin.site.urls),
url('^api/', include('api.urls')),
]
- 应用
api
下的路由urls
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^users/$', views.User.as_view()),
url(r'^users/(?P<pk>d+)/$', views.User.as_view()),
]
CBV
视图编写
class User(View):
def get(self, request, *args, **kwargs):
print(args) # ()
print(kwargs) # {'pk': '1'}
pk = kwargs.get("pk")
resp = {
"status": 0,
"message": "ok",
"results": {}
}
if not pk:
user_info = []
users = UserModel.objects.all()
for user in users:
dic = {
"username": user.username,
"phone": user.phone,
"gender": user.get_gender_display(),
"icon": str(user.icon)
}
user_info.append(dic)
resp["results"] = user_info
else:
user = UserModel.objects.filter(pk=pk).first()
resp["results"] = {
"username": user.username,
"phone": user.phone,
"gender": user.get_gender_display(),
"icon": str(user.icon)
}
# JsonResponse("ok", safe=false) 返回字符串,safe需要等于false,默认为True,要求参数为字典
return JsonResponse(resp, json_dumps_params={'ensure_ascii': False})
def post(self, request, *args, **kwargs):
pass
写了一个比较简单的获取用户的类视图,不够完善,异常也没有处理,主要是想回顾一下原生Django
的写法,不作为重点。
- 单个用户请求url地址为:
http://127.0.0.1:8000/api/users/1/
,下面是返回的结果:
{
"status": 0,
"message": "ok",
"results": {
"username": "Tom",
"phone": "22222222222",
"gender": "男",
"icon": "icon/home_t1aBbSD.jpg"
}
}
- 多个用户请求url地址:
http://127.0.0.1:8000/api/users/
,下面是返回的结果:
{
"status": 0,
"message": "ok",
"results": [
{
"username": "Tom",
"phone": "22222222222",
"gender": "男",
"icon": "icon/home_t1aBbSD.jpg"
},
{
"username": "Jack",
"phone": "11111111111",
"gender": "女",
"icon": "icon/logo_l7UKz0x.png"
}
]
}
原生Django CBV
请求源码分析
- 直接从应用的
urls.py
中的as_view()
入手,进入as_view()
的源码,
- 进入
as_view
方法发现是一个闭包,执行一些参数校验直接返回view
方法的引用,也就是说url
匹配的第二个参数其实就是view
。
思考:as_view()
方法返回的view
何时被执行?下面我们来查看这一过程,我们把url
改成了下面的方式,让as_view()
返回的view
赋给handle
,然后把handle
作为url
的参数传递。
from django.conf.urls import url
from . import views
print(views.User.as_view())
handle = views.User.as_view() # 这个handle就是闭包返回的view
urlpatterns = [
url(r'^users/$', handle),
url(r'^users/(?P<pk>d+)/$', views.User.as_view()),
]
进入url
的源码,发现调用的是re_path
,这是Django为我们做了一层封装,其实可以直接用re_path
进行路由匹配,接着我们通过打印,发现返回的是一个URLPattern
类的实例:
def url(regex, view, kwargs=None, name=None):
print("=========", re_path(regex, view, kwargs, name))
return re_path(regex, view, kwargs, name)
========= <URLPattern '^users/$'>
========= <URLPattern '^users/(?P<pk>d+)/$'>
接着进入源码django/urls/resolvers.py
找到URLPattern
类,发现我们的在url
中的参数全部传递到这里,至于最开始说的as_view
闭包返回的view
就传递给了callback
,也就是我们的handle
传递给了callback
,每次当请求来临时,url解析器完成url的解析,匹配到相应的回调函数,然后执行(至于什么时候执行就不在深入了)。
class URLPattern:
def __init__(self, pattern, callback, default_args=None, name=None):
self.pattern = pattern
self.callback = callback # the view
self.default_args = default_args or {}
self.name = name
目前到这里,我们发现从url
匹配===>as_view()
执行返回view
===>view
传递给了URLPattern
的callbak
,这整个过程最终执行了as_view
闭包内的view
方法,view
方法主要有实例化对象,为对象增加属性,然后调用dispath()
查找执行方法几个步骤。而view
方法最主要的就是下面的dispatch
方法,ctrl+b
进入查看dispatch()
方法。
dispatch()
方法就是判断类视图实例(self就是我们自己定义的视图类的实例)是否存在方法,然后执行返回结果
调用顺序: as_view --> view --> dispatch
- 可以看出as_view实际上是一个闭包, 它的作用做一些校验工作, 再返回view方法.
- 而view方法的作用是给请求对象补充三个参数, 并调用 dispatch方法处理
- dispatch方法查找到指定的请求方法, 并执行
可以得出结论: 实际上真正实现查找的方法是 dispatch方法
OK,到这里我们差不多理解了原生Django
的整个请求流程,至于post
请求主要步骤也就是接受参数request.POST
,然后校验插入数据库,组织数据返回结果,底层请求也是上面的流程,最终由dispatch
做分发执行类视图定义的方法。
因此我们会发现在整个类视图中组织数据是最频繁的操作,假如当一个表字段很多时,我们重复组织的数据就会很多,当然也可以抽取公共的部分进行封装,但是总感觉有点拖泥带水,为了解决这个问题,便有了下面的Django REST framework
框架的诞生。
基于DRF的请求定义
由于上面的路由以及模型类定义都是一样的,这里就主要定义一下类视图的编写:
from rest_framework.views import APIView
class User(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get("pk")
if pk:
user = UserModel.objects.filter(pk=pk).first()
if user:
return Response({
'status': 0,
'msg': 'OK',
'results': serializers.UserSerializer(instance=user).data
})
else:
return Response({
'status': 2,
'msg': '用户不存在',
})
else:
users = UserModel.objects.all()
return Response({
'status': 0,
'msg': 'OK',
'results': serializers.UserSerializer(instance=users, many=True).data
})
def post(self, request, *args, **kwargs):
data = request.data
# 校验数据
user_des = serializers.UserDeserializer(data=data)
if user_des.is_valid():
# 校验通过
user_obj = user_des.save()
return Response({
'status': 0,
'msg': 'OK',
'results': serializers.UserSerializer(instance=user_obj).data
})
else:
# 校验失败
return Response({
'status': 1,
'msg': user_des.errors,
})
注意这个类视图继承的是rest_framework.view
下面的APIView
,然后类中定义了get
和post
的请求方法,至于其中的serializers
这些东西是Django REST framework
的序列化组件,这里不做过多解释,我们主要分析一下对于Django REST framework
的请求流程。
DRF
请求源码分析
- 根据应用中
urls.py
,走as_view
方法,但是视图类没有该方法,所以请求走的是父类APIView
的as_view
方法
-
在
APIView
的as_view
调用父类(django原生View)的as_view
,同时还禁用了 csrf 认证,返回view
-
和原生一样接下来就是执行
view
,走dispatch
方法,同样视图类没有该方法,所以请求走的是父类APIView
,发现APIView
重写了dispatch
所以就不用走原生View
内部的dispatch
- 执行完成
dispatch
的所有方法最终返回响应
通过发现DRF
的核心就是对原生dispatch
方法做了很多的改动,从一个请求到最终的响应过程,首先对request
做了调整,之后就是三大认证组件,然后做了一些异常捕获的处理,之后对响应的数据进行了封装,用过DRF
的都知道,DRF
自带了一个浏览器的访问调试界面,这个就是最后的渲染模块做的事。
针对每个模块具体做了那些改进,我用了另一篇文章来详细介绍。
相关参考:
https://www.jianshu.com/p/17860becea09
https://www.cnblogs.com/wangcuican/p/11674996.html