我们也可以使用类视图编写 API 视图来简化函数视图的代码。这是一个强大的模式,它允许我们重用常用功能,并帮助我们保持代码DRY。
一,类视图概述
(一)使用方法
先看看类视图的使用示例:
视图:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
class ListUsers(APIView):
"""
列出系统中的所有用户的视图。
* 需要token认证
* 只有管理员用户可以访问这个视图。
"""
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
def get(self, request, format=None):
"""
Return a list of all users.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
路由:
from MyAPP import views
urlpatterns = [
url(r'^userlist/$', views.ListUsers.as_view()),
]
首先在路由中调用APIView
子类的as_view
方法,实现类视图的功能。因为APIView
类继承自 Django 的View
类,所以它们的本质功能都是一样的:将原始类视图映射到视图函数上。
我们首先将函数视图重构类视图:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
列出所有的snippets或者创建一个新的snippet。
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class SnippetDetail(APIView):
"""
检索,更新或删除一个snippet示例。
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
由于视图的实现方式发生改变,因此还要重构 URL :
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
到此为止,这已经实现了最简单的类视图,就像使用普通类函数那样,由类函数将请求分分发到同名的类方法中。
看起来似乎与函数视图差别不大。主要是因为此时的类视图还没有使用到通用的组件,后面一步步将它们添加进来。
(二)源码简述
但是实际上,REST framework 的类视图在实现上与 django 类视图最大的差别就是前者提供了扩展自 django HttpRequest
对象的 rest_framework.request.Request
对象,并以此作为后续权限与验证的基础之一。
先看看调用的 APIVview.as_view
方法后是怎样产生 Request
对象的:
一,类视图
REST framework 提供了一个APIView
类,它是 Django 的View
类的子类,设计理念上算算是一脉相承,但具体上有以下不同:
- 被传入到处理方法的请求是 REST framework 的
Request
类的实例,而不是 Django 的HttpRequest
类的实例。 - 处理方法返回 REST framework 的
Response
类的实例,而不是 Django 的HttpRequest
类的实例。视图会管理内容协议,给响应设置正确的渲染器。 - 任何 API Exception 异常都会被捕获,并且传递给合适的响应。
- 被传入地请求将会经过认证,而且合适的权限和(或)限流检查会在请求被派发到处理方法之前运行。
使用APIView
类和使用一般的 View
类非常相似,通常,进入的请求会被分发到合适处理方法比如 .get()
,或者.post()
。
另外,很多属性会被设定在控制 API 策略的各种切面的类上。
(一)API 策略属性(API policy attributes)
以下属性控制了 API 视图的可拔插实现:
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
(二)API策略实例化方法(API policy instantiation methods)
下面这些方法被 REST framework 用来实例化各种可拔插的 API 策略,通常不需要重写这些方法:
-
.get_renderers(self)
:实例化并返回此视图可以使用的渲染器列表。 -
.get_parsers(self)
:实例化并返回此视图可以使用的解析器列表。 -
.get_authenticators(self)
:实例化并返回此视图可以使用的身份验证器列表。 -
.get_throttles(self)
:实例化并返回此视图使用的限制列表。 -
.get_permissions(self)
:实例化并返回此视图所需的权限列表。 -
.get_content_negotiator(self)
:实例化并返回要使用的内容协商类。 -
.get_exception_handler(self)
:返回此视图使用的异常处理程序。
(三)API策略实现方法(API policy implementation methods)
下面这些方法会在请求被分发到具体的处理方法之前调用:
.check_permissions(self, request)
:检查是否应允许请求。如果不允许请求,则引发适当的异常。.check_throttles(self, request)
:检查是否应限制请求。如果请求受到限制,则引发适当的异常。.perform_authentication(self, request)
:对传入请求执行身份验证。.perform_content_negotiation(self, request, force=False)
:确定使用哪个渲染器和媒体类型来渲染响应。
(四)调度方法(Dispatch methods)
下面这些方法会被视图的 .dispatch()
方法直接调用。它们在调用get()
, post()
, put()
, patch()
和delete()
之类的请求处理方法之前或者之后执行任何需要执行的操作。
.initial(self, request, *args, **kwargs)
:在处理方法调用之前进行任何需要的动作。 这个方法用于执行权限认证和限制,并且执行内容协商。 你通常不需要重写此方法。.handle_exception(self, exc)
:
任何被处理请求的方法抛出的异常都会被传递给这个方法,这个方法既不返回Response
的实例,也不重新抛出异常。默认的实现会处理rest_framework.expceptions.APIException
的任何子类异常,以及 Django 的 Http404 和 PermissionDenied 异常,并且返回一个适当的错误响应。如果你需要在自己的API中自定义返回的错误响应,你需要重写这个方法。.initialize_request(self, request, *args, **kwargs)
:
确保传递给请求处理方法的请求对象是Request
的实例,而不是通常的 DjangoHttpResquest
的实例。你通常不需要重写这个方法。.finalize_response(self, request, response, *args, **kwargs)
:
确保任何从处理请求的方法返回的Response
对象被渲染到由内容协商决定的正确内容类型。 你通常不需要重写这个方法。
二,使用混合(mixins)
使用类视图的最大优势之一是它可以轻松地创建可复用的行为。
REST framework 也提供了许多 mixin 类供使用。
- mixin 类可以从
rest_framework.mixins
导入。 - mixin 类提供了操作方法,而不是直接定义处理方法。这允许更灵活的行为组合。
1,ListModelMixin
: 提供 .list(request,*args,**kwargs) 方法
,它能列出查询集。
- 如果填充了查询集,则返回 200 OK 响应,并将查询集的序列化表示作为响应的主体。响应数据可以选择分页。
2,CreateModelMixin
: 提供 .create(request,* args,** kwargs)
方法,能创建和保存新模型实例。
- 如果对象被创建,则返回一个 201 Created 响应,并将该对象的序列化表示形式作为响应的主体。
- 如果该表示包含名为 url 的键,则响应的 Location header 将填充该值。
- 如果为创建对象而提供的请求数据无效,则将返回 400 Bad Request 响应,并将错误详细信息作为响应的主体。
3,RetrieveModelMixin
: 提供 .retrieve(request,* args,** kwargs)
方法,能在响应中返回现有模型实例。
- 如果可以检索对象,则返回一个 200 OK 响应,并将对象的序列化表示作为响应的主体。否则它将返回 404 Not Found。
4,UpdateModelMixin
: 提供 .update(request,* args,** kwargs)
方法,能更新和保存现有模型实例;还提供了 .partial_update(request,* args,** kwargs)
方法,该方法与 update
方法类似,不同之处在于更新的所有字段都是可选的。这允许支持 HTTP PATCH 请求。
- 如果对象被更新,则返回 200 OK 响应,并将对象的序列化表示作为响应的主体。
- 如果为更新对象而提供的请求数据无效,则将返回 400 Bad Request 响应,并将错误详细信息作为响应的主体。
5,DestroyModelMixin
: 提供 .destroy(request,* args,** kwargs)
方法,能删除现有模型实例。
- 如果对象被删除,则返回 204 No Content,否则它将返回 404 Not Found。
三,通用类视图 API 参考
基于类的视图的主要优点之一是它们允许你组合一些可重用的行为。 REST framework 通过提供许多预先构建的视图来提供常用的模式来利用这一优点。
REST framework 提供的通用视图允许您快速构建与数据库模型密切映射的API视图。
如果通用视图不适合你的 API 的需求,你可以选择使用常规 APIView
类,或重用通用视图使用的 mixins 和基类来组成你自己的一组可重用的通用视图。
(一)GenericAPIView
该类扩展了 REST framework 的 APIView
类,为标准列表和详细视图添加了通常所需的行为。
1,属性
(1)基本设置
以下属性控制着基本的视图行为。
1,queryset
—— 用于从该视图返回对象的 queryset 。
- 必须设置此属性,或重写
get_queryset()
方法。如果重写视图方法,则应该调用get_queryset()
,而不是直接访问此属性,因为 queryset 将只被计算一次,并且这些结果将被缓存以用于所有后续请求。
2,serializer_class
—— 应该用于验证和反序列化输入以及序列化输出的序列化器类。
- 必须设置这个属性,或者重写
get_serializer_class()
方法。
3,lookup_field
—— 应该用于执行单个模型实例的对象查找的模型字段。默认为 pk 。
- 注意,在使用超链接 API 时,如果需要使用自定义值,则应该确保 API 视图和序列化器类都设置了查找字段。
4,lookup_url_kwarg
—— 应该用于对象查找的 URL 关键字参数,同时 URL 配置应该包含一个与此值对应的关键字参数。
- 如果取消此设置,则默认使用与
lookup_field
相同的值。
(2)分页
与列表视图一起使用时,以下属性用于控制分页。
1,pagination_class
—— 在对列表结果进行分页时应该使用的分页类。
- 设置
pagination_class = None
将禁用此视图的分页。 - 默认为与
DEFAULT_PAGINATION_CLASS
设置相同的值,即
rest_framework.pagination.PageNumberPagination
,需要在 settings.py 中进行如下配置:
# 分页设置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每页显示多少条数据
'PAGE_SIZE': 2
}
(3)过滤
1,filter_backends
—— 应该用于过滤查询集的后端类的列表。
- 默认值与
DEFAULT_FILTER_BACKENDS
设置相同。
2,方法
(1)基本方法
1,get_queryset(self)
——返回用于列表视图的查询集,并用作详细视图中查询的基础。
- 默认返回
queryset
属性指定的查询集。 应该始终使用此方法,而不是直接访问self.queryset
,因为self.queryset
只被计算一次,并且这些结果将被缓存以用于所有后续请求。 - 可以重写来提供动态行为,例如返回查询集,这是针对请求的用户所特有的。
def get_queryset(self):
user = self.request.user
return user.accounts.all()
2,get_object(self)
——返回用于详细视图的对象实例。
- 默认使用
lookup_field
参数来过滤基本查询集。 - 可以重写以提供更复杂的行为,例如基于多个 URL kwarg 的对象查找。
- 如果 API 不包含任何对象级权限,则可以选择不执行
self.check_object_permissions
,只是简单的返回get_object_or_404
查找的对象即可。
def get_object(self):
queryset = self.get_queryset()
filter = {}
for field in self.multiple_lookup_fields:
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
return obj
3,filter_queryset(self, queryset)
——给定一个查询集,使用任何后端过滤器进行过滤,返回一个新的查询集。
def filter_queryset(self, queryset):
filter_backends = (CategoryFilter,)
if 'geo_route' in self.request.query_params:
filter_backends = (GeoRouteFilter, CategoryFilter)
elif 'geo_point' in self.request.query_params:
filter_backends = (GeoPointFilter, CategoryFilter)
for backend in list(filter_backends):
queryset = backend().filter_queryset(self.request, queryset, view=self)
return queryset
4,get_serializer_class(self)
——返回用于序列化的类。
- 默认返回
serializer_class
属性。 - 可以重写以提供动态行为,例如使用不同的序列化器进行读写操作,或者为不同类型的用户提供不同的序列化器。
def get_serializer_class(self):
if self.request.user.is_staff:
return FullAccountSerializer
return BasicAccountSerializer
(2)保存和删除钩子
以下方法由 mixin 类提供,可以很轻松地重写对象的保存和删除行为。
- 这些钩子对于设置请求中隐含的但不是请求数据的一部分的属性特别有用(例如,你可以根据请求用户或基于 URL 关键字参数在对象上设置属性)。
- 这些可重写的关键点对于添加在保存对象之前或之后发生的行为(例如通过电子邮件发送确认或记录更新日志)也特别有用。
- 还可以使用这些钩子通过抛出
ValidationError()
来提供额外的验证。当你需要在数据库保存时应用一些验证逻辑时,这会很有用。
1,perform_create(self,serializer)
—— 在保存新对象实例时由 CreateModelMixin
调用。
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_create(self, serializer):
queryset = SignupRequest.objects.filter(user=self.request.user)
if queryset.exists():
raise ValidationError('You have already signed up')
serializer.save(user=self.request.user)
2,perform_update(self,serializer)
—— 在保存现有对象实例时由 UpdateModelMixin 调用。
def perform_update(self, serializer):
instance = serializer.save()
send_email_confirmation(user=self.request.user, modified=instance)
3,perform_destroy(self,instance)
—— 在删除对象实例时由 DestroyModelMixin
调用。
(3)其他方法
通常不需要重写以下方法,但使用 GenericAPIView
编写自定义视图,则可能需要调用它们。
get_serializer_context(self)
—— 返回包含应该被提供给序列化器的任何附加上下文的字典。默认包括 “request”,“view” 和 “format” 键。get_serializer(self,instance = None,data = None,many = False,partial = False)
—— 返回一个序列化器实例。get_paginated_response(self,data)
—— 返回分页样式的Response
对象。paginate_queryset(self, queryset)
—— 根据需要为查询集分页,或者返回一个页面对象;如果没有为该视图配置分页,则为 None。
到目前为止,我们使用的创建/获取/更新/删除操作和我们创建的普通函数 API 视图非常相似。既然使用了类视图,那么就能将这些常见的行为用 REST framework 的 mixin 来实现:
from snippet.models import Snippet
from snippet.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
花点时间好好看下这里的具体实现方式:
- 使用
GenericAPIView
提供核心功能。 - 使用
ListModelMixin
、CreateModelMixin
、RetrieveModelMixin
、UpdateModelMixin
和DestroyModelMixin
这几个 mixin 类分别提供.list()
、.create()
、.retrieve()
、.update()
和.destroy()
操作。 - 明确地将
get
、post
、put
和delete
方法分别绑定到适当 mixin 类提供的操作。
运行服务器,访问http://127.0.0.1:8000/snippets/
(二)具体通用视图类 (Concrete View Classes)
每个具体的类视图都是通过将 GenericAPIView
类和一个或多个 minxin
类相互结合来构建的。
以下类是具体的通用视图。最好是使用通用视图,除非你需要大量定制的行为。
- 可以从
rest_framework.generics
导入具体视图类。
1,CreateAPIView
用于仅创建端点。
- 提供 post 方法处理程序。
- 扩展:GenericAPIView,CreateModelMixin
2,ListAPIView
用只读端点表示模型实例的集合。
- 提供 get 方法处理程序。
- 扩展:GenericAPIView,ListModelMixin
3,RetrieveAPIView
用只读端点表示单个模型实例。
- 提供 get 方法处理程序。
- 扩展:GenericAPIView, RetrieveModelMixin
4,DestroyAPIView
用于仅删除单个模型实例的端点。
- 提供 delete 方法处理程序。
- 扩展:GenericAPIView, DestroyModelMixin
5,UpdateAPIView
用于只更新单个模型实例的端点。
- 提供 put 和 patch 方法处理程序。
- 扩展:GenericAPIView, UpdateModelMixin
6,ListCreateAPIView
用读写端点来表示模型实例的集合。
- 提供 get 和 post 方法处理程序。
- 扩展:GenericAPIView, ListModelMixin, CreateModelMixin
7,RetrieveUpdateAPIView
用于读取或更新端点以表示单个模型实例。
- 提供 get,put 和 patch 方法处理程序。
- 扩展:GenericAPIView, RetrieveModelMixin, UpdateModelMixin
8,RetrieveDestroyAPIView
用于读取或删除端点以表示单个模型实例。
- 提供 get 和 delete 方法处理程序。
- 扩展:GenericAPIView, RetrieveModelMixin, DestroyModelMixin
9,RetrieveUpdateDestroyAPIView
用于读写删除端点以表示单个模型实例。
- 提供 get ,put,patch 和 delete 方法处理程序。
- 扩展:GenericAPIView, RetrieveModelMixin, UpdateModelMixin、DestroyModelMixin
因此,尽管之前已经通过 mixin 类用更少的代码重写了视图,但我们还可以再进一步,使用通用类视图:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
到目前为止,经过对类视图版本的几次迭代,基本上了解了类视图的使用方法。
(三)自定义通用类视图
如果内置的那些通用类视图不能满足需要,我们也能自定义通用类视图。
1,创建自定义 mixins
例如,如果您需要根据 URL 中的多个字段查找对象,则可以创建如下所示的 mixin 类:
class MultipleFieldLookupMixin(object):
"""
将此 mixin 应用于任何视图或视图集,以基于`lookup_fields`属性获得多个字段过滤,而不是默认的单字段过滤。
"""
def get_object(self):
queryset = self.get_queryset() # 获取基本查询集
queryset = self.filter_queryset(queryset) # 应用过滤器
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # 忽略空字段
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # 查找对象
self.check_object_permissions(self.request, obj)
return obj
然后可以在需要应用自定义行为的任何时候,将该 mixin 应用于视图或视图集:
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')
2,创建自定义基类
如果要在多个视图中使用 mixin,则可以更进一步,创建自己的一组基本视图,然后可以在整个项目中使用。例如:
class BaseRetrieveView(MultipleFieldLookupMixin,
generics.RetrieveAPIView):
pass
class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin,
generics.RetrieveUpdateDestroyAPIView):
pass