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 注册
]
APIView 是 REST framework 提供的所有视图的基类,继承自 Django 的 View 父类。
导入语句: rest_framework.views.APIView
通过对 cbv 源码和 drf 源码的分析可以了解其大致原理。
调用视图函数类需要添加 'as_view()'函数,例如: path('home/', views.Test.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)'
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)',将获得的方法调用并返还。
路由的使用并没有改变,需要添加 as_view() 。但是按照名称空间查找顺序,此时 as_view() 方法需要
去继承的 APIView 中查找相当于重写了父类的方法
@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 校验。
该方法和 cbv 中一致,返回的值是 dispatch 方法,但是该方法按照查找循序也是优先使用 APIView 中
的方法
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 格式数据。
在上面的流程分析中发现 request 对象已经被包装过了,也就是说视图类中使用的 request 对象,是rest_framework.request.Request
的对象,原来的是django.core.handlers.wsgi.WSGIRequest
1. Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
2. 无论前端发送的哪种格式的数据,都可以统一的方式读取数据。
常见属性
request.data
返回解析之后的请求体数据。类似于Django中标准的 request.POST
和 request.FILES
属性。request.POST 转换成普通字典可以使用 request.POST.dict()request.query_params
与Django标准的 request.GET
相同,可以获取请求地址中的数据class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
...
self._request = request
...
解释:
在 __init__ 中把旧的 request 放到了新的 request 的 _request 属性中,接下来就是取出旧的
request 中的属性,逐个取比较麻烦,源码中重写了 '__getattr__' 方法
class Request:
...
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
解释:
该双下方法是在实例对象查找不存在的属性时执行,例如要取新的 request.POST,但是很明显没有,此时会
触发双下 __getattr__ 方法,使用反射在旧的 request 中获取属性。
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)) #
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()
导入语句:from rest_framework.response import Response
。
REST framework 提供了一个响应类 Response,使用该类构造响应对象时,响应的具体数据内容会被转
换(render渲染)成符合前端需求的类型。
REST framework 提供了Renderer 渲染器,用来根据请求头中的 Accept(接收数据类型声明)来自动转
换响应数据到对应格式。如果前端请求中未进行 Accept 声明,则会采用默认方式处理响应数据,我们可以通
过配置来修改默认响应格式。
对请求和响应还可以进行配置,点击此文章: DRF 请求与响应
使用 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/' , views3.BookView1.as_view()),
]
导入语句:rest_framework.generics.GenericAPIView
该类继承自 APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面 Mixin 扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类
serializer_class 属性用于指明视图使用的序列化器,在视图类中可以指定使用的序列化器
使用方法:
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)
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
queryset 指明使用的数据查询集,也可以在视图类中指定
使用的方法:
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
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
与使用 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/' , views2.BookView1.as_view()),
]