跳至主要內容

Rest-framework专栏讲解(三):Generic view

Mr.暴走の海鸽约 3874 字大约 13 分钟

目录


点击跳转到 Rest-Framework 专栏目录open in new window

官方原文链接open in new window

通用视图open in new window

基于类的视图的一个主要优点是它们允许你编写可重复使用的行为。 REST framework 通过提供大量预构建视图来提供常用模式,从而充分利用了这一点。

REST framework 提供的通用视图允许您快速构建紧密映射到数据库模型的 API 视图。

如果通用视图不符合需求,可以使用常规的 APIView 类,或者利用 mixin 特性和基类组合出可重用的视图。

举个栗子open in new window

通常,在使用通用视图时,您需要继承该视图,并设置几个类属性。

from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import generics
from rest_framework.permissions import IsAdminUser

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsAdminUser,)

对于更复杂的情况,您可能还想重写视图类中的各种方法,例如:

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsAdminUser,)

    def list(self, request):
        # 注意使用`get_queryset()`而不是`self.queryset`
        queryset = self.get_queryset()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

对于非常简单的情况,您可能想要使用 .as_view() 方法来传递类属性。例如,您的 URLconf 可能包含类似于以下条目的内容:

url(r'^/users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list')直接在 URLconf 中设置相关属性参数,这样连视图类都不用写了。

直接在 URLconf 中设置相关属性参数,这样连视图类都不用写了,不是很推荐。

API 参考open in new window

GenericAPIView总览

GenericAPIView 类继承于 REST framework 的 APIView 类,为标准列表和详细视图添加了常见的行为。

内置的每一个具体的通用视图都是通过将 GenericAPIView 类和一个或多个 minxin 类相互结合来构建的。

了解到 APIView 之后, 你可以尝试看下 GenericAPIView 的源码, 导入路径是:

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAdminUser

from models import User


class MedusaSerializer(serializers.Serializer):
    ...


class MedusaView(GenericAPIView):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

当然, 可能在 APIView 上拓展了一些方法放我们的 API 撰写变得简单, 并且富有规范性, 但是相比较而言, 他的存在更加推荐和 mixins 一起使用,

  • perform_create(self, serializer) - CreateModelMixin 保存新对象实例时调用
  • perform_update(self, serializer) - UpdateModelMixin 保存现有对象实例时调用
  • perform_destroy(self, instance) - DestroyModelMixin 删除对象实例时调用

例如你在新建实例对象进行保存处理的时候还需要做其他的处理, 你就可以在 perform_create(self, serializer) 中对它进行重写:

def perform_create(self, serializer):
    instance = serializer.save()
    
    # 你需要做的其他处理的函数, 或者代码块
    ...

你也可以对更新的数据进行再次验证, 引发 ValidationError() 异常:

def perform_update(self, serializer):
    if 1 == 1:
        raise ValidationError('我不管,我就是不让你更新。')
    serializer.save(user=self.request.user)

当然啦, 还有 ListModelMixin 来获取数据集合的, RetrieveModelMixin 来获取某一数据详情的, 大多数的请求方式和请求内容都已经概括了。

属性open in new window

基本设置open in new window

在源码中可以看到他是在 APIView 的一种拓展, 当你继承这个视图类撰写自定义视图的时候, 你可以获取以下几个属性:

  • queryset - 用于从此视图返回对象的查询集。通常,您必须设置此属性,或覆盖 get_queryset()方法。如果你重写了一个视图方法,在视图方法中,你应该调用 get_queryset() 而不是直接访问这个属性,这一点很重要!因为 REST 会在内部对 queryset 的结果进行缓存用于后续所有请求。
  • serializer_class - 用于验证和反序列化输入以及序列化输出的序列化类。通常,您必须设置此属性,或覆盖 get_serializer_class() 方法。
  • lookup_field - 用于执行各个模型实例的对象查找的模型字段。默认为 'pk'。请注意,使用 hyperlinked API 时,如果需要使用自定义值,则需要确保 API 视图和序列化类设置了 lookup field。
  • lookup_url_kwarg - 用于对象查找的 URL 关键字参数。URL conf 应该包含与此值相对应的关键字参数。如果未设置,则默认使用与 lookup_field 相同的值。

关于后两个参数,我这里附上对象查询的源码大家就应该了解了。
省略部分代码,方便理解

def get_object(self):
    # 先获取数据集
    queryset = self.filter_queryset(self.get_queryset())
    # 拿到查询参数的 key
    lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
    # 组装成 {key:value} 的形式
    filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
    # 查询
    obj = get_object_or_404(queryset, **filter_kwargs)
    # 最后返回
    return obj

分页open in new window

与列表视图一起使用时,以下属性用于控制分页。

  • pagination_class - 对列表进行分页时使用的分页类。默认值与 DEFAULT_PAGINATION_CLASS 设置相同,即 rest_framework.pagination.PageNumberPagination。设置 pagination_class = None 将禁用此视图的分页。

过滤open in new window

  • filter_backends - 用于过滤查询集的过滤器类的列表。默认值与 DEFAULT_FILTER_BACKENDS 设置的值相同。

方法open in new window

基本方法open in new window

get_queryset(self)

应该返回列表视图的查询集,并应该将其用作查看详细视图的基础。默认返回由 queryset 属性指定的查询集。

应该始终使用此方法, 而不是直接访问 self.queryset,因为 REST 会在内部对 self.queryset 的结果进行缓存用于后续所有请求。

可以覆盖以提供动态行为,例如针对不同用户的请求返回不同的数据。

举个栗子:

def get_queryset(self):
    user = self.request.user
    return user.accounts.all()

get_object(self)

应该返回详细视图的对象实例。默认使用 lookup_field 参数来过滤基本查询集。

可以覆盖以提供更复杂的行为,例如基于多个 URL kwarg 的对象查找。

举个栗子:

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

请注意,如果您的 API 不包含任何对象级权限,您可以选择排除 self.check_object_permissions,并简单地从 get_object_or_404 中查找返回对象。

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

get_serializer_class(self)

返回用于序列化的类。默认返回 serializer_class 属性。

可以被覆盖以提供动态行为,例如使用不同的序列化器进行读写操作,或为不同类型的用户提供不同的序列化器。

举个栗子:

def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer

保存和删除钩子(hook)open in new window

以下方法由 mixin 类提供,可以很轻松的重写对象的保存和删除行为。

  • perform_create(self, serializer) - 保存新对象实例时由 CreateModelMixin 调用。
  • perform_update(self, serializer) - 在保存现有对象实例时由 UpdateModelMixin 调用。
  • perform_destroy(self, instance) - 删除对象实例时由 DestroyModelMixin 调用。

这些钩子(hook)对设置请求中隐含的但不属于请求数据的属性特别有用。例如,您可以根据请求用户或基于 URL 关键字参数在对象上设置属性。

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.x pre_savepost_savepre_deletepost_delete 方法,这些方法不再可用。

其他方法open in new window

通常不需要重写以下方法,但如果使用 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
  • filter_queryset(self, queryset) - 给定一个查询集,使用过滤器进行过滤,返回一个新的查询集。

Mixinsopen in new window

mixin 类用于提供基本视图行为的操作。请注意,mixin 类提供了操作方法,而不是直接定义处理方法,如 .get().post()。这允许更灵活的行为组合。

mixin 类可以从 rest_framework.mixins 中导入。

ListModelMixin

提供了 .list(request, *args, **kwargs) 方法, 用于查询对象集合, 你也可以重写他的 list 方法实现你自己的返回体, 你也可以对他进行数据分页(分页 将会在以后讲解到), 当你查询成功了, 将会返回 200 OK 的状态码给你:

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAdminUser


class MedusaView(GenericAPIView, ListModelMixin):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

CreateModelMixin

提供 .create(request, *args, **kwargs) 方法, 该方法实现创建并保存模型的新实例对象, 如果成功创建对象则返回一个 201 Created 响应, 如果提供的用于创建对象的请求数据无效, 则会返回一个 400 Bad Request 响应, 并将错误详细信息作为响应的正文。

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from rest_framework.permissions import IsAdminUser


class MedusaView(GenericAPIView, CreateModelMixin):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

RetrieveModelMixin

提供 .retrieve(request, *args, **kwargs) 方法, 该方法实现在响应中返回现有模型实例的详情数据, 如果可以检索到对象, 则返回一个 200 OK 响应, 否则将返回 404 Not Found

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.permissions import IsAdminUser


class MedusaView(GenericAPIView, RetrieveModelMixin):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

UpdateModelMixin

提供 .update(request, *args, **kwargs) 方法, 该方法实现更新并保存现有模型的实例, 还提供了一种 .partial_update(request, *args, **kwargs) 方法, 该方法与该 update 方法类似, 但是用于更新的所有字段都是可选的, 这样可以支持 HTTP PATCH 请求(作者在实际开发中, 会对 UpdateModelsMixin 重新定义, 删除对 HTTP PATCH 请求方式的支持), 如果对象被更新, 它将返回一个 200 OK 响应, 如果提供的用于更新对象的请求数据无效, 则会返回 400 Bad Request 响应, 并将错误详细信息作为响应的主体。

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from rest_framework.permissions import IsAdminUser


class MedusaView(GenericAPIView, UpdateModelMixin):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

DestroyModelMixin

提供 .destroy(request, *args, **kwargs) 方法, 该方法实现删除现有模型实例, 如果删除对象成功则返回 204 No Content 响应, 否则将返回 404 Not Found

#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import DestroyModelMixin
from rest_framework.permissions import IsAdminUser


class MedusaView(GenericAPIView, DestroyModelMixin):
    queryset = User.objects.all()
    serializer_class = MedusaSerializer
    permission_classes = [IsAdminUser]

内置视图类列表

以下类是具体的通用视图。通常情况下,你应该都是使用的它们,除非需要高度的自定义行为。

这些视图类可以从 rest_framework.generics 中导入。

  • CreateAPIView

    用于仅创建新模型实例对象的视图

    提供 POST 请求方式

    依赖:GenericAPIView, CreateModelMixin

  • ListAPIView

    用于仅只读模型实例对象集合数据的视图

    提供 GET 请求方式

    依赖:GenericAPIView, ListModelMixin

  • RetrieveAPIView

    用于仅只读单个模型实例对象的视图

    提供 GET 请求方式

    依赖:GenericAPIView, RetrieveModelMixin

  • DestroyAPIView

    用于仅删除单个模型实例对象的视图

    提供 DELETE 请求方式

    依赖:GenericAPIView, DestroyModelMixin

  • UpdateAPIView

    用于仅对已有模型实例进行修改的视图

    提供 PUTPATCH 请求方式

    依赖:GenericAPIView, UpdateModelMixin

  • ListCreateAPIView

    用于对模型实例对象集读取和写入的视图

    提供 GETPOST 请求方式

    依赖:GenericAPIView, ListModelMixin, CreateModelMixin

  • RetrieveUpdateAPIView

    用于对单个模型实例的读取和更新的视图

    提供 GETPUTPATCH 请求方式

    依赖:GenericAPIView, RetrieveModelMixin, UpdateModelMixin

  • RetrieveDestroyAPIView

    用于对单个模型实例的读取和删除的视图

    提供 GETDELETE 请求方式

    依赖:GenericAPIView, RetrieveModelMixin, DestroyModelMixin

  • RetrieveUpdateDestroyAPIView

    用于对单个模型实例的读取、更新和删除的视图

    提供 GETPUTPATCHDELETE 请求方式

    依赖:GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin

自定义通用视图类open in new window

通常你会想使用现有的通用视图,然后稍微定制一下行为。如果您发现自己在多个地方重复使用了一些自定义行为,则可能需要将行为重构为普通类,然后根据需要将其应用于任何视图或视图集。

自定义 mixinsopen in new window

例如,如果您需要根据 URL conf 中的多个字段查找对象,则可以创建一个 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')

如果您需要使用自定义行为,那么使用自定义 mixins 是一个不错的选择。

自定义基类open in new window

如果您在多个视图中使用 mixin,您可以进一步创建自己的一组基本视图,然后在整个项目中使用它们。例如:

class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    pass


class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
    pass

如果自定义行为始终需要在整个项目中的大量视图中重复使用,那么使用自定义基类是一个不错的选择。