django 视图类和drf视图类区别
模块 | 请求类型 | 继承关系 | 响应数据格式 | 需注销csrf |
---|---|---|---|---|
django | get | View | JsonResponse(字典),没有Response | 否 |
django | post | View | JsonResponse(字典),没有Response | 是 |
drf | get | APIView | JsonResponse(字典),Response(python基本数据类型皆可) | 否 |
drf | post | APIView | JsonResponse(字典),Response(python基本数据类型皆可) | 否 |
# d_proj>urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/',include('api.urls')),
]
# api>urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v1/books/$',views.BookViews.as_view()),
url(r'^v1/books/(?P<pk>d+)/$',views.BookViews.as_view()),
url(r'^v2/books/$',views.BookAPIViews.as_view()),
url(r'^v2/books/(?P<pk>d+)/$',views.BookAPIViews.as_view()),
]
# views.py
from rest_framework.views import APIView
from django.views import View
from django.http import JsonResponse
class BookViews(View):
def get(self,*args,**kwargs):
return JsonResponse({
'msg':'get BookViews'
})
def post(self,*args,**kwargs):
return JsonResponse({
'msg': 'post BookViews'
})
class BookAPIViews(APIView):
def get(self,*args,**kwargs):
return JsonResponse({
'msg': 'get BookAPIViews'
})
def post(self,*args,**kwargs):
return JsonResponse({
'msg': 'post BookAPIViews'
APIView的请求生命周期
APIView的请求生命周期
1)APIView类继承View类,重写了as_view和dispatch方法
2)重写的as_view方法,主体还是django中View的as_view,只是在返回视图view函数地址时,局部禁用csrf认证,所以以后只要是视图类继承了APIView,就不需要注销settings中的csrf中间件了,其实这是就相当于注销掉了settings的中间件csrf。然后调用djangoas_view内部的闭包函数view,在函数view内部返回cls(我们定义的视图类)实例化的对象self.dispatch(request,*args,**kwargs),按照属性的查找顺序(自己类》继承的父类》父类的父类),在APIView中定义的有dispatch方法,所以不会走django》View类中的dispatch方法了。
3)重写的dispatch方法,
在执行请求逻辑前:请求模块(二次封装request)、解析模块(三种数据包格式的数据解析)
在执行请求逻辑中:异常模块(执行出现任何异常交给异常模块处理)
在执行请求逻辑后:响应模块(二次封装response)、渲染模块(响应的数据可以是两种渲染方式:可以是JSON数据格式,也可以是页面),在浏览器输入接口是页面渲染,postman是JSON格式
# urls.py
urlpatterns = [
url(r'^books/$', views.BookAPIViews.as_view()),
url(r'^books/(?P<pk>d+)/$', views.BookAPIViews.as_view()),
]
# 调用中APIView中的as_view()方法:
# rest_framework>views.py>APIView(View)类>as_view方法
@classmethod
def as_view(cls, **initkwargs):
"""
Store the original class on the view function.
This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
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) # 调用django中的as_view()
view.cls = cls
view.initkwargs = initkwargs
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
# 局部禁用csrf
return csrf_exempt(view)
# 调用父类View中的as_view方法
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in six.iteritems(kwargs):
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
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. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs): # 接收路由的有名和无名分组的参数
# print(request)
# print(request.method) # <WSGIRequest: DELETE '/api/books/1/'>
# print(args,kwargs) # () {'pk': '1'}
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs) # APIView类中有dispatch方法,就不会走APIView类继承的View中(django中的View)的dispatch
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=())
return view
# APIView类中的dispatch方法
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 对request对象进行二次封装,包含
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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)
except Exception as exc:
# 异常模块 - 处理请求异常分支的
response = self.handle_exception(exc)
# 二次封装response,处理了结果 渲染
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class BookAPIViews(APIView):
def get(self,*args,**kwargs):
return Response({
'msg': 'get BookAPIViews'
})
def post(self,*args,**kwargs):
return Response({
'msg': 'post BookAPIViews'
})
请求模块
# 走drf的dispatch方法,
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 对request对象进行二次封装,包含
request = self.initialize_request(request, *args, **kwargs) # *********************
self.request = request
# print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
self.headers = self.default_response_headers # deprecate?
try:
# 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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)
except Exception as exc:
# 异常模块 - 处理请求异常分支的
response = self.handle_exception(exc)
# 二次封装response,处理了结果 渲染
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request( # 初始化
request, # 将wsgi的request也出入Request的__init__内部进行初始化
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# rest_framework>request.py>Request类
class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
)
self._request = request # 我们自定义的类内部只有通过self._request访问原始的request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
class BookAPIViews(APIView):
def get(self,request,*args,**kwargs):
return Response({
'msg': 'get BookAPIViews'
})
def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
#在内部将wsgi的request赋值给request._request
print(type(request)) # <class 'rest_framework.request.Request'>
# 就是通过__getattr__走的是request._request.method
print(request.method) # POST 这里request是Request的对象,对象.属性 触发类内部的__getattr__方法 Request类见最下面在最下方
print(request._request.method) # POST 等价于上面
# 走的是方法属性,就是给request._request.GET重新命名
print(request.query_params) # <QueryDict: {'a': ['10']}>
# 走的是方法属性,值依赖于request._full_data
print(request.data) # <QueryDict: {'{
"name":"zhang"
}': ['']}>
return Response({
'msg': 'post BookAPIViews'
})
# ==========================================================================================
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
@property
def DATA(self):
raise NotImplementedError(
'`request.DATA` has been deprecated in favor of `request.data` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
@property
def POST(self):
# Ensure that request.POST uses our request parsing.
if not _hasattr(self, '_data'):
self._load_data_and_files()
if is_form_media_type(self.content_type):
return self._data
return QueryDict('', encoding=self._request._encoding)
@property
def FILES(self):
# Leave this one alone for backwards compat with Django's request.FILES
# Different from the other two cases, which are not valid property
# names on the WSGIRequest class.
if not _hasattr(self, '_files'):
self._load_data_and_files()
return self._files
@property
def QUERY_PARAMS(self):
raise NotImplementedError(
'`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
"""
request.DATA
request.FILES
request.QUERY_PARAMS
"""
# ===========================================================================================
解析模块
解析模块:只处理数据包参数 - form-data,urlencoded,json
1)全局配置所有视图类的解析方式,解析配置可以配置三种
2)局部配置当前视图类的解析方式,解析配置可以配置三种
3)配置的查找顺序:局部(视图类的类属性) => 全局(settings文件的drf配置) => 默认(drf的默认配置)
# 三种解析类解析数据所对应的数据编码格式:
JSONParser---->media_type = 'application/json'
FormParser---->'application/x-www-form-urlencoded'
MultiPartParser---->'multipart/form-data'
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
from rest_framework.views import APIView
from rest_framework.response import Response
class BookAPIViews(APIView):
# 视图类内部自己配置解析类属性(局部配置)************
parser_classes = [JSONParser,FormParser,MultiPartParser]
# def get(self,request,*args,**kwargs):
# return Response({
# 'msg': 'get BookAPIViews'
# })
def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
print(request.query_params) # 接口拼接的参数
print(request.data) # 前端三种编码格式数据解析后都在data内 ,form-data form-urlencoded raw(application/json)
return Response({
'msg': 'post BookAPIViews'
})
解析模块配置
1) 局部配置:
parser_classes = [JSONParser,FormParser,MultiPartParser]
2) 全局配置(在api的settings文件内):# 全局配置解析类:适用于所有视图类
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser', # json
'rest_framework.parsers.FormParser', # form 表单提交form-urlencoded编码格式的数据
# 'rest_framework.parsers.MultiPartParser' # form 表单提交form-data编码格式的数据
],
}
3) 系统配置
rest-framework>settings>__init__()方法内加载DEFAULT
解析类源码分析
入口:APIVIew的dispatch方法的 request=self.initialize_request(request, *args, **kwargs)
获取解析类:parsers=self.get_parsers(),
进行局部全局默认配置查找顺序进行查找:return [parser() for parser in self.parser_classes]
响应模块
data:响应数据
status:响应的网络状态码
template_name:drf完成前后台不分离返回页面,但是就不可以返回data(不许用了解)
headers:响应头,一般不规定,走默认
exception:一般异常响应,会将其设置成True,默认False(不设置也没事)
content_type:默认就是 application/json,不需要处理
from rest_framework.response import Response
from rest_framework import status # 网络状态码
def post(self,request,*args,**kwargs): # <rest_framework.request.Request object at 0x000000000E44BF98>
print(request.query_params) # 接口拼接的参数
print(request.data) # 前端三种编码格式数据解析后都在data内 ,form-data form-urlencoded raw(application/json)
response = Response(
data = {
# 就是让Response类中的__init__的关键字形参data接受
'msg': 'post BookAPIViews'
},
status= status.HTTP_400_BAD_REQUEST
)
print(response.data) # {'msg': 'post BookAPIViews'}
return response
Response类对应内部源码
class Response(SimpleTemplateResponse):
"""
An HttpResponse that allows its data to be rendered into
arbitrary media types.
"""
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
"""
Alters the init arguments slightly.
For example, drop 'template_name', and instead use 'data'.
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
# ===========================================================================================
# 把属性全部加载到Response类的对象的名称空间
self.data = data # self 是Response类的对象
self.template_name = template_name
self.exception = exception
self.content_type = content_type
if headers:
for name, value in headers.items():
self[name] = value
渲染模块
全局配置渲染类
REST_FRAMEWORK = {
# 全局配置渲染类:适用于所有视图类
'DEFAULT_RENDERER_CLASSES': [
# 控制JSON数据在postman的显示
'rest_framework.renderers.JSONRenderer',
# 控制drf页面在浏览器页面渲染
# 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器端能访问到drf页面,上线后尽量关闭
],
}
局部配置
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
系统配置
# rest_framework>views.py>APIView类
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
异常模块
源码分析
# rest_framework>views.py>APIView(View)类内部的方法
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 对request对象进行二次封装,包含
request = self.initialize_request(request, *args, **kwargs)
self.request = request
# print(self.request) # <rest_framework.request.Request object at 0x000000000BCD6DA0>
self.headers = self.default_response_headers # deprecate?
try:
# 三大认证(认证、权限、频率),用来替换csrf安全认证,要比csrf认证强大
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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)
except Exception as exc:
# 异常模块 - 处理请求异常分支的
response = self.handle_exception(exc)
# 二次封装response,处理了结果 渲染
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# rest_framework>views.py>APIView(View)类内部的方法
def handle_exception(self, exc):
"""
Handle any exception that occurs, by returning an appropriate response,
or re-raising the error.
"""
# 出现三大认证相关异常,额外处理一下响应头
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
# 获取异常处理函数exception_handler地址,就独立存在该文件内
exception_handler = self.get_exception_handler()
# 给异常处理提供额外的参数
context = self.get_exception_handler_context() # exc异常对象,context中有视图对象和请求对象
response = exception_handler(exc, context)
# 默认的exception_handler函数只处理客户端异常形成response对象,服务器异常不做处理,返回None
if response is None:
self.raise_uncaught_exception(exc)
response.exception = True
return response
# 在rest_framework>views.py独立存在的函数
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None
自定义异常处理函数
# settings.py配置
REST_FRAMEWORK = {
# 异常模块:
# 系统异常处理函数:
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
# 自定义异常处理函数:指向自己定义的exception.py 文件中的exception_handler函数
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}
# api>exception.py
# 在settings文件中将异常模块配置自己的异常处理函数
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
# 先交给drf处理客户端异常,如果结果response为None代表服务器异常,自己处理
def exception_handler(exc,context):
response = drf_exception_handler(exc,context)
detail = '%s-%s-%s' % (context.get('view'), context.get('request').method, exc)
if not response: # 服务器端错误
response = Response({'detail':detail})
else:
response.data = {'detail':detail}
# 要将response.data.get('detail)信息记录下来
# print(response.data.get('detail'))
# < api.views.BookAPIViewsobjectat0x000000000B9FA3C8 > -PUT - 方法 “PUT” 不被允许。
return response