问题:为什么前端的请求过来能自动识别Django的CBV模式里的GET或POST方法
案例:
- views.py
# 视图层
from django.shortcuts import render, HttpResponse
from django.views import View
class CBVTest(View):
def get(self, request):
return render(request, 'cbv.html')
def post(self, request):
return HttpResponse('cbv post method')
- url.py
# 路由层
from app import views
urlpatterns = [
url(r'^cbv/', views.CBVTest.as_view()), # 注意:这里的as_view方法是View类中的方法,且加括号运行了
]
CBV源码分析
流程:
自定义的视图类(CBVTest)继承了View,在路由里CBVTest.as_view()的这个as_view方法是View类的方法,并且及括号执行了,返回结果是在as_view方法中定义的view函数
此时路由就已经是CBVTest.view了,当请求过来触发view方法,在view方法中又触发了View类中的dispatch方法
dispatch方法中利用反射获取CBVTest中的GET或POST方法,做到请求分发的效果
源码:
View类中的as_view方法:
@classonlymethod
def as_view(cls, **initkwargs):
...
# 定义了view方法
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
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__
)
# 调用View类中的dispatch方法,并将结果返回
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# as_view方法将定义的view方法返回
return view
View类中的dispatch方法:
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)
drf请求源码分析
案例:
- views.py
# 视图层
from rest_framework.views import APIView
from rest_framework.response import Response
user_list = [{'id': 1, 'name': 'Bob'}, {'id': 2, 'name': 'Tom'}]
class Users(APIView):
def get(self, request, *args, **kwargs):
return Response({
'status': 0,
'msg': 'ok',
'results': user_list
})
def post(self, request, *args, **kwargs):
# request对formdata,urlencoded,json三个格式参数均能解析
name = request.data.get('name')
id = len(user_list) + 1
user = {'id': id, 'name': name}
user_list.append(user)
return Response({
'status': '0',
'msg': 'ok',
'results': user
})
- urls.py
# 路由层
from app import views
urlpatterns = [
url(r'^users/', views.Users.as_view()),
]
流程:
自定义的视图类(Users)继承了APIView,在路由里Users.as_view()的这个as_view方法是APIView类的方法,并且加括号执行了,在as_view方法里调用了APIView类的父类(View)里的as_view方法,并且禁用了csrf
APIView重写了dispatch方法,所以在as_view方法中调用的是APIView的dispatch方法(完成了很多重要工作),完成任务分发
补充:APIView中的dispatch方法中完成了二次封装request,认证,权限,频率功能
源码:
def as_view(cls, **initkwargs):
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '
'as the result will be cached and reused between requests. '
'Use `.all()` or call `.get_queryset()` instead.'
)
cls.queryset._fetch_all = force_evaluation
view = super().as_view(**initkwargs) # APIView调用其父类的as_view方法,返回的这个view就是Django里View类中as_view返回的那个view
view.cls = cls # 将视图类添加到view的名称空间中,后续要在view中实例化视图类,拿到视图类的对象
view.initkwargs = initkwargs # 实例化视图类时用到
return csrf_exempt(view) # 局部禁用csrf(只有直接或间接继承了APIView的视图类才禁用), DRF有自己的认证类,而且高级
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# 请求模块: initialize_request 对requset进行了二次封装
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
# 这个try捕获异常 (1.捕获三大认证不通过时抛出的异常, 2.捕获视图类请求方式没实现时抛出的异常 ......走到逻辑里发生的异常都会被捕获)
try:
# 三大认证模块
self.initial(request, *args, **kwargs)
# 三大认证通过后才能访问视图类
# Get the appropriate handler method
# 这里和原生Django一样的操作: 利用反射获取视图类中与前端请求方法一样的方法
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
response = handler(request, *args, **kwargs)
# 例如: response = post(self, request, *args, **kwargs)
# response 就是返回给前台的结果
except Exception as exc: # exc是异常信息
# DRF 异常模块,
response = self.handle_exception(exc) # response就是被DRF处理过的异常,会返回给前台
# 这里将response进行了二次封装,finalize_response就是DRF的渲染模块,针对浏览器, 工具(Postman), Ajax 有不同的渲染方式
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
initial(self, request, *args, **kwargs)
# 认证
self.perform_authentication(request)
# 权限
self.check_permissions(request)
# 频率
self.check_throttles(request)
drf中的request对象
源码入口
APIView类的dispatch方法中:request = self.initialize_request(request, *args, **kwargs)
源码分析
def initialize_request(self, request, *args, **kwargs):
# 准备解析的数据 parser_context就是准备解析的数据
parser_context = self.get_parser_context(request)
# 二次封装request,使DRF的request兼容Django的request
return Request(
request, # 原生的request
parsers=self.get_parsers(), # 解析模块(在此处解析), 解析form_data, URLencoding, json格式的数据
authenticators=self.get_authenticators(), # get_authenticators就是一堆认证类的对象组成的列表
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
- 1) drf 对原生request做了二次封装,request._request就是原生request
- 2) 原生request对象的属性和方法都可以被drf的request对象直接访问(兼容)
- 3) drf请求的所有url拼接参数均被解析到query_params中,所有数据包数据都被解析到data中
基于restful规范的drf接口
api应用的子路由: api/url.py
from django.conf.urls import url
from . import views
urlpatterns = [
# 这里的as_view是APIView类中的,作用是禁用了csrf_exempt,并返回view,调用了View类中的as_view方法
url(r'^test/$',views.Test.as_view())
]
视图层: views.py
# APIView本质是继承了View
class Test(APIView):
def get(self, request, *args, **kwargs):
# url 拼接的参数
print(request._request.GET) # 二次封装request
print(request.GET) # 兼容
print(request.query_params) # 扩展,GET请求拼接的参数这里都有
return Response('drf get ok')
def post(self, request, *args, **kwargs):
# 请求携带的数据包
print(request._request.POST) # 二次封装方式,没有json方式的数据
print(request.POST) # 兼容,没有json方式的数据
print(request.data) # 拓展,兼容性最强,三种数据方式都可以
print(request.query_params) # post拼接的数据也可以接受到
return Response('drf post ok')
url拼接参数 : 只有一种传参方式,参数都在query_params中
数据包参数 : 有三种传参方式 form-data,urlencoded,json,参数都在data中