REST framework 提供了一个APIView
类,它是 Django 的View
类的子类。
REST framework 主要的几种 view 以及他们之间的关系:
# mixins
到目前为止,我们使用的 创建 / 获取 / 更新 / 删除
操作和我们创建的任何基于模型的 API 视图非常相似。
这些常见的行为是在 REST 框架的 mixin 类中实现的
Mixin 类提供用于提供基本视图行为的操作。注意 mixin 类提供动作方法,而不是直接定义处理程序方法,例如 .get()
和 .post()
, 这允许更灵活的行为组成。
Mixin 类可以从 rest_framework.mixins
导入。
mixins | 作用 | 对应 HTTP 的请求方法 |
---|---|---|
mixins.ListModelMixin | 定义list方法,返回一个queryset的列表 | GET |
mixins.CreateModelMixin | 定义create方法,创建一个实例 | POST |
mixins.RetrieveModelMixin | 定义retrieve方法,返回一个具体的实例 | GET |
mixins.UpdateModelMixin | 定义update方法,对某个实例进行更新 | PUT/PATCH |
mixins.DestroyModelMixin | 定义delete方法,删除某个实例 | DELETE |
# 使用详解
# 1.APIView
APIView 对 django 本身的 View 进行封装
APIView
类和一般的View
类有以下不同:
被传入到处理方法的请求不会是 Django 的
HttpRequest
类的实例,而是 REST framework 的Request
类的实例。处理方法可以返回 REST framework 的
Response
,而不是 Django 的HttpRequest
。视图会管理内容协议,给响应设置正确的渲染器。任何
APIException
异常都会被捕获,并且传递给合适的响应。进入的请求将会经过认证,合适的权限和(或)节流检查会在请求被派发到处理方法之前
- authentication_classes:用户登录认证方式,session 或者 token 等等。
- permission_classes:权限设置,是否需要登录等。
- throttle_classes:限速设置,对用户进行一定的访问次数限制等等
使用APIView
类和使用一般的View
类非常相似,通常,进入的请求会被分发到合适处理方法比如.get()
,或者.post
。
另外,很多属性会被设定在控制 API 策略的各种切面的类上。
from django.contrib.auth.models import User
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)
# 2.GenericAPIView
该类对 APIView 进行更高层次的封装,扩展了 REST 框架的
APIView
类,为标准 list 和 detail view 添加了通常需要的行为。
提供的每个具体通用视图是通过将 GenericAPIView
与一个或多个 mixin 类组合来构建的。
from rest_framework import mixins
from rest_framework import generics
class CourseListView(mixins.ListModelMixin, generics.GenericAPIView):
"""课程列表页"""
queryset = Course.objects.all() # 你自己的查询对象
serialize_class = CourseSerializer # 你自己的序列化器
def get(self, request, *args, **kwargs):
# list 方法是存在于 mixins 中的,同理,create 等等也是
# GenericAPIView没有这些方法!
return self.list(request, *args, **kwargs)
上述中,继承了 mixins
中的 ListModelMixin
,就是对应把 HTTP
的 get 方法转换调用 list 方法,list 方法会返回 queryset 的 json 数据
GenericAPIView 对 APIView 再次封装,实现了强大功能:
- 加入
queryset
属性,可以直接设置这个属性,不必再将实例化的data
,再次传给seriliazer
,系统会自动检测到。除此之外,可以重载 get_queryset(),这样就不必设置’queryset=*’,这样就变得更加灵活,可以进行完全的自定义。 - 加入
serializer_class
属性与实现get_serializer_class()
方法。两者的存在一个即可,通过这个,在返回时,不必去指定某个 serializer。 - 设置过滤器模板:
filter_backends
- 设置分页模板:
pagination_class
- 加入 lookup_field=”pk”,以及实现了 get_object 方法,这个用得场景不多,但十分重要。它们两者的关系同 1,要么设置属性,要么重载方法。它们的功能在于获取某一个实例时,指定传进来的后缀是什么。
例如 ,获取具体的某个课程,假设传进来的 URL 为:http://127.0.0.1:8000/course/1/
,系统会默认这个 1 指的是 course 的 id。
那么,现在面临一个问题,假设我定义了一个用户收藏的 Model,我想要知道我 id 为 1 的主机是否收藏了,我传进来的 URL 为:http://127.0.0.1:8000/userfav/1/
, 系统会默认获取 userfav
的 id=1
的实例,这个逻辑明显是错的,我们需要获取 course
的 id=1
的收藏记录,所以我们就需要用到这个属性或者重载 lookup_field="course_id"
这个方法。
# GenericAPIView 的不足之处:
既然 GenericAPIView 以及它相关的 View 已经完成了许许多多的功能,那么还要 ViewSet 干嘛
首先,我们思考一个问题,同样上面的例子,我们在功能上,要获取课程的列表,也要获取某个课程的具体信息。那么怎么实现,按照 GenericAPIView,我们可以这样实现:
class CourseView(ListAPIView,RetrieveAPIView):
"""只需要在上面的基础上,再继承 RetrieveAPIView 就 ok 了"""
queryset = Course.objects.all()
serialize_class = CourseSerializer
但这样实现有一个问题,关于 serialize_class
,显然,当获取课程列表时,只需要传回去所有课程的简要信息,如课程名字,老师,封面等等,但当获取课程的具体信息,我们还要将他们的章节以及相关下载资料(很明显,章节是另外一个 model,有一个外键指向 course),这些信息会很多,在获取课程列表,将这些传回去显然是不理智的。
那么,还需要再定义一个 CourseDetailSerializer
,在 GET /courses/
的时候,使用 CourseSerializer
,在 GET /courses/id/
的时候,使用 CourseDetailSerializer
。
那么,问题来了,我们怎么获取到是哪个 action 方法?这个时候,viewset 就出场了
# 3.Viewset
GenericViewSet 继承了 GenericAPIView,依然有 get_queryset, get_serialize_class 相关属性与方法 GenericViewSet 重写了 as_view 方法,可以获取到 HTTP 的请求方法
使用ViewSet
类比使用View
类有两个主要优点。
- 重复逻辑可以组合成一个类。在上面的示例中,我们只需要指定
queryset
一次,它将在多个视图中使用。 - 通过使用路由器,我们不再需要处理自己的 URL 连接。
如:上述问题中我们可以这样解决
# 方法一:
from rest_framework import viewsets
import...
class CourseViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = Course.objects.all()
def get_serializer_class(self): # 重写 get_serializer_class 方法
if self.action == 'list':
return CourseSerializer
return CourseDetailSerializer
http 请求方法与 mixins 的方法进行绑定
因为 GenericViewSet 本身依然不存在 list, create 方法,需要我们与 mixins 一起混合使用,那么新问题来了? 我们依然需要自己写 get、post 方法,然后再 return list 或者 create 等方法吗?
当然不!重写 as_view 的方法为我们提供了绑定的功能,我们在设置 url 的时候:
进行绑定
courses = CourseViewSet.as_view({'get': 'list', 'post': 'create'})
urlpatterns = [
# ...
url(r'courses/', CourseViewSet.as_view(), name='courses')
]
这样,我们就将 http 请求方法与 mixins 方法进行了关联。那么还有更简洁的方法吗?很明显,当然有,这个时候,route 就登场了!
# 方法二:route 方法注册与绑定
因为我们使用 ViewSet 类而不是 View 类,实际上不用自己设计 URL conf 及绑定 HTTP 方法。
连接 resources 到 views 和 urls 的约定可以使用 Router 类自动处理
我们需要做的仅仅是正确的注册 View 到 Router 中,然后让它执行其余操作
新的 urls.py 代码如下:
from rest_framework.routers import DefaultRouter
router = DefaultRouter() # 只需要实现一次
router.register(r'courses', CourseViewSet, base_name='courses')
urlpatterns = [
# ...
url(r'^', include(router.urls)),
]
route 中使用的一定要是 ViewSet,用 router.register 的方法注册 url 不仅可以很好的管理 url,不会导致 url 过多而混乱,而且还能实现 http 方法与 mixins 中的相关方法进行连接。
# ModelViewSet:
在 viewset
中,还提供了两个以及与 mixins
绑定好的 ViewSet
。
当然,这两个 ViewSet
完全可以自己实现,它只是把各类 mixins
与 GenericViewSet
继承在一起了:
class ReadOnlyModelViewSet(
mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet
):
# 满足只有 GET 方法请求的情景
pass
class ModelViewSet(
mixins.CreateModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.DestroyModelMixin,
mixins.ListModelMixin, GenericViewSet
):
# 满足所有请求都有的情景
pass
在开发的时候,直接继承 viewsets.ModelViewSet 就可以了,这样就不用写 list、create、update 等方法了 当然具体问题具体分析,如果你的需求与 DRF 提供的不一致,那么你就可以重写相应的方法即可
假如有这样一个需求,你可能需要过滤查询集,以确保只返回与当前通过身份验证的用户发出的请求相关的结果。
from rest_framework import status
class CourserViewSet(ModelViewSet):
"""每个用户只可以查看 owner 属于自己的条目,可以创建条目"""
def list(self, request, *args, **kwargs):
self.queryset = Course.objects.filter(owner=request.user.id)
self.serializer_class = CourseSerializer
return super(CourserViewSet, self).list(request, *args, **kwargs)
def create(self, request, format=None):
serializer = CourseSerializer(data=request.data)
if serializer.is_valid():
# .save()是调用CourseSerializer中的create()方法
serializer.save(owner=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
这里定义了两个方法,list
方式中我们我们重写了 queryset
(就是对返回结果做了一次过滤),然后对于 serializer_class
指定了一个序列化类。
并且我们使用 super
方法,继续载入 viewset
中的 list
方法,这里只会覆盖原有 list
的 queryset
和 serializer_class
。
而对于 create 方法,我们则是完全重写了原 viewset 中的 create 方法。这里只是演示一下再 ViewSet 模式下如何来做工作。原则就是能用 ViewSet 内置的就是用内置的,内置的不满足需求就可以重写部分或全部重写。
另外,如果只是过滤查询集,最简单方法是重写 .get_queryset()
方法即可。
重写此方法允许你以多种不同方式自定义视图返回的查询集。
class CourseViewSet(ModelViewSet):
"""每个用户只可以查看 owner 属于自己的条目,可以创建条目"""
serializer_class = CourseSerializer
def list(self, request, *args, **kwargs):
return Course.objects.filter(owner=request.user.id)
# 自定义 ViewSet 基类
要创建基础视图集类,提供 create
,list
和retrieve
操作,继承GenericViewSet
和混入所需的操作:
class CreateListRetrieveViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
""" A viewset that provides `retrieve`, `create`, and `list` actions.
To use it,
override the class and set the `.queryset` and `.serializer_class` attributes.
"""
pass
ps:在开发时是使用 ViewSet 与 mixins 方法结合进行可以为我们节省很多功夫