文章目录
1. 引子
Django REST framwork 提供了众多的通用视图基类与扩展类,以简化视图的编写。
REST framework 提供的视图的主要作用有:
- 控制序列化器的执行(检验、保存、转换数据)
- 控制数据库查询的执行
在此之前需要注意的是,DRF 是一个 app,也需要在配置文件中注册。
INSTALLED_APPS = [
'django.contrib.admin', # 后台管理,系统自带后台管理 admin
'django.contrib.auth', # 权限管理会生成6张表
'django.contrib.contenttypes', # 可以对所有 app 的表进行记录
'django.contrib.sessions', # session功能,也就是 django_session表
'django.contrib.messages', # django消息框架 flask的闪现
'django.contrib.staticfiles', # 静态资源相关
'rest_framework', # DRF 注册
]
2. 视图基类
2.1 APIView
APIView 是 REST framework 提供的所有视图的基类,继承自 Django 的 View 父类。
导入语句: rest_framework.views.APIView
2.1.1 APIView 与 View 的不同之处
- 传入到视图方法中的是 REST framework 的 Request 对象,而不是 Django 的 HttpRequeset 对象;
- 视图方法可以返回 REST framework 的 Response 对象,视图会为响应数据设置(render)符合前端要求的格式;
- 任何 APIException 异常都会被捕获到,并且处理成合适的响应信息;
- 在进行 dispatch() 分发前,会对请求进行身份认证、权限检查、流量控制。
- 在 APIView 中仍以常规的类视图定义方法来实现 get() 、post() 或者其他请求方式的方法。
2.1.2 支持定义的类属性
- authentication_classes 列表或元祖,身份认证类
- permissoin_classes 列表或元祖,权限检查类
- throttle_classes 列表或元祖,流量控制类
2.1.3 CBV 源码分析
通过对 cbv 源码和 drf 源码的分析可以了解其大致原理。
- url.py 文件
调用视图函数类需要添加 'as_view()'函数,例如: path('home/', views.Test.as_view()),
- as_view() 函数主要内容
@classonlymethod
def as_view(cls, **initkwargs):
for key in initkwargs:
if key in cls.http_method_names:
...
if not hasattr(cls, key):
...
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__
)
return self.dispatch(request, *args, **kwargs)
...
return view
解释:
函数 as_view 返回的值是 view,也就是实际上路由中执行的是 view 函数,而 as_view 中的 view 函
数返回的是 'self.dispatch(request, *args, **kwargs)'
- dispatch 函数主要内容
def dispatch(self, request, *args, **kwargs):
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)
解释:
1. 首先判断了请求方式,http_method_names 中包含了诸多请求方式,如下所示:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
2. 利用反射的方式获得类中和该请求方式同名的方法
3. 返回 'handler(request, *args, **kwargs)',将获得的方法调用并返还。
2.1.4 APIView 执行流程分析
- url.py 文件
路由的使用并没有改变,需要添加 as_view() 。但是按照名称空间查找顺序,此时 as_view() 方法需要
去继承的 APIView 中查找相当于重写了父类的方法
- APIView 中的 as_view() 方法
@classmethod
def as_view(cls, **initkwargs):
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
...
cls.queryset._fetch_all = force_evaluation
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view)
解释:
1. 重写了 View 中的方法,并继承了父类中的 as_view 方法。并且返回的值是 'csrf_exempt(view)'
2. csrf_exempt 方法可以忽略 csrf 校验,而 APIView 中没有 view ,也就是使用 APIView 父类
View 的 view 方法,只不过忽略了 csrf 校验。
- view 方法
该方法和 cbv 中一致,返回的值是 dispatch 方法,但是该方法按照查找循序也是优先使用 APIView 中
的方法
- APIView 中的 dispatch 方法
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
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)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
解释:
1. 'request = self.initialize_request(request, *args, **kwargs)'包装新的 request对象
以后视图类中用的 request 对象,就是新的 request
2. 'try' 异常捕获,将三大认证的异常以及执行视图函数的异常进行捕获。
3. 'self.initial(request, *args, **kwargs)' 内部封装了三大认证: 认证(登陆认证),频率(1分
钟只能访问接口3次),权限(普通用户不能访问,超级用户才能访问)
4. 在异常捕获代码内重写了原来 dispatch 的代码用于执行视图函数
5. 'self.response = self.finalize_response(request, response, *args, **kwargs)' 用于
包装 response 响应对象,在浏览器中能看到美观的样式,在 postman 只看到 json 格式数据。
2.1.5 Request 对象
在上面的流程分析中发现 request 对象已经被包装过了,也就是说视图类中使用的 request 对象,是rest_framework.request.Request
的对象,原来的是django.core.handlers.wsgi.WSGIRequest
1. Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
2. 无论前端发送的哪种格式的数据,都可以统一的方式读取数据。
常见属性
- data
request.data
返回解析之后的请求体数据。类似于Django中标准的request.POST
和request.FILES
属性。request.POST 转换成普通字典可以使用 request.POST.dict() - query_params
request.query_params
与Django标准的request.GET
相同,可以获取请求地址中的数据
2.1.5 Request 对象源码分析
- Request 类
class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
...
self._request = request
...
解释:
在 __init__ 中把旧的 request 放到了新的 request 的 _request 属性中,接下来就是取出旧的
request 中的属性,逐个取比较麻烦,源码中重写了 '__getattr__' 方法
- _getattr_ 方法
class Request:
...
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
解释:
该双下方法是在实例对象查找不存在的属性时执行,例如要取新的 request.POST,但是很明显没有,此时会
触发双下 __getattr__ 方法,使用反射在旧的 request 中获取属性。
2.1.6 自定义 data
request.data
只在 APIView 中有,若想在 View 中也能使用 data 可以使用装饰器或类的形式实现
- 装饰器
from django.views.decorators.csrf import csrf_exempt, csrf_protect
import json
def outher(func):
def inner(request, *args, **kwargs):
try:
data = json.loads(request.body)
except Exception as e:
data = request.POST
request.data = data
res = func(request, *args, **kwargs)
return res
return csrf_exempt(inner)
- 视图函数
from django.shortcuts import render, HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
class Test(View):
def post(self, request):
data = request.data
print(data, type(data)) # <class 'django.http.request.QueryDict'>
return HttpResponse()
# 重写dispatch对所有方法添加装饰器
@method_decorator(outher)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
也可以通过类的形式。
- 类
import json
from django.views import View
class MyView(View):
def dispatch(self, request, *args, **kwargs):
try:
data = json.loads(request.body)
except Exception as e:
data = request.POST
request.data = data
return super().dispatch(request, *args, **kwargs)
- 视图函数
from django.shortcuts import render, HttpResponse
class Test(MyView):
def post(self, request):
data = request.data
print(data, type(data))
return HttpResponse()
2.1.7 Response 对象
导入语句:from rest_framework.response import Response
。
REST framework 提供了一个响应类 Response,使用该类构造响应对象时,响应的具体数据内容会被转
换(render渲染)成符合前端需求的类型。
REST framework 提供了Renderer 渲染器,用来根据请求头中的 Accept(接收数据类型声明)来自动转
换响应数据到对应格式。如果前端请求中未进行 Accept 声明,则会采用默认方式处理响应数据,我们可以通
过配置来修改默认响应格式。
对请求和响应还可以进行配置,点击此文章: DRF 请求与响应
2.1.8 使用 APIView 编写 5 个接口
使用 APIView 编写查询单个、查询所有、删除单个、添加单个、编辑单个接口,代码示例如下(省略序列化类代码)。
视图函数
from rest_framework.response import Response
from rest_framework.views import APIView
from app01 import models
from app01.serializer import BookSerializer
class BookView(APIView):
queryset = models.Book.objects
serializer_class = BookSerializer
def post(self, request):
res = self.serializer_class(data=request.data)
if res.is_valid():
res.save()
return Response(res.data)
return Response({"code": 100, "msg": "验证不通过", "info": res.errors})
def get(self, request):
res = self.serializer_class(self.queryset.all(), many=True)
return Response(res.data)
class BookView1(APIView):
queryset = models.Book.objects
serializer_class = BookSerializer
def get(self, request, **kwargs):
res = self.serializer_class(self.queryset.filter(**kwargs).first())
return Response(res.data)
def delete(self, request, **kwargs):
obj = self.queryset.filter(**kwargs).delete()
return Response()
def put(self, request, **kwargs):
obj = self.queryset.filter(**kwargs).first()
res = self.serializer_class(obj, data=request.data)
if res.is_valid():
res.save()
return Response(res.data)
return Response({"code": 100, "msg": "验证不通过", "info": res.errors})
路由
from django.contrib import admin
from django.urls import path
from app01.view import views3
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views3.BookView.as_view()),
path('books/<int:pk>', views3.BookView1.as_view()),
]
2.2 GenericAPIView
导入语句:rest_framework.generics.GenericAPIView
该类继承自 APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面 Mixin 扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类
2.2.1 serializer_class
serializer_class 属性用于指明视图使用的序列化器,在视图类中可以指定使用的序列化器
使用方法:
- get_serializer(self, *args, **kwargs)
- 返回序列化器对象,主要用来提供给 Mixin 扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
- 注意,该方法在提供序列化器对象的时候,会向序列化器对象的 context 属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
- request 当前视图的请求对象
view 当前请求的类视图对象
format 当前请求期望返回的数据格式 - 源码如下
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
return serializer_class(*args, **kwargs)
- get_serializer_class(self)
- 当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
- 源码如下,其返回的是类名,在视图类中使用需要加括号
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
2.2.2 queryset
queryset 指明使用的数据查询集,也可以在视图类中指定
使用的方法:
- get_queryset(self)
- 返回视图使用的查询集,主要用来提供给 Mixin 扩展类使用,是列表视图与详情视图获取数据的基础,默认返回 queryset 属性
- 源码如下,简单来看就是若 queryset 为 none,通过assert 断言来抛出异常,然后将 queryset 属性取出判断是否添加 all() 方法后返回出去。
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 = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
- get_object(self)
- 可以用于获取单个查询结果对象,主要用来提供给Mixin扩展类使用
- 若详情访问的模型类对象不存在,会返回404。
- 源码如下
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'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]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
2.2.3 使用 GenericAPIView 编写 5 个接口
与使用 APIView 编写的方式只是获取方法不同
视图函数
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from app01 import models
from app01.serializer import BookSerializer
class BookView(GenericAPIView):
# 名字只能是这个
queryset = models.Book.objects
serializer_class = BookSerializer
def post(self, request):
res = self.get_serializer(data=request.data)
if res.is_valid():
res.save()
return Response(res.data)
return Response({"code": 100, "msg": "验证不通过", "info": res.errors})
def get(self, request):
res = self.get_serializer_class()(self.get_queryset().all(), many=True)
return Response(res.data)
class BookView1(GenericAPIView):
# 名字只能是这个
queryset = models.Book.objects
serializer_class = BookSerializer
def get(self, request, **kwargs):
# res = self.get_serializer(self.get_object()) # 或者使用get_object()
res = self.get_serializer(self.get_queryset().filter(**kwargs).first())
return Response(res.data)
def delete(self, request, **kwargs):
obj = self.get_queryset().filter(**kwargs).delete()
return Response()
def put(self, request, **kwargs):
obj = self.get_queryset().filter(**kwargs).first()
res = self.get_serializer(obj, data=request.data)
if res.is_valid():
res.save()
return Response(res.data)
return Response({"code": 100, "msg": "验证不通过", "info": res.errors})
路由
from django.contrib import admin
from django.urls import path
from app01.view import views2
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views2.BookView.as_view()),
path('books/<int:pk>', views2.BookView1.as_view()),
]