Django REST Framework——5. 视图与路由


基于类的视图的一个关键好处是:它们允许您组合一些可重用的行为。而DRF通过提供大量预构建的视图来利用这一点,为我们提供了一些常用的模式,我们要做的就是选择合适的视图类进行继承即可。

一、APIView类

DRF提供的APIView类是Django的View类的子类。它和一般的View类有以下不同:

  • 被传入到处理方法的请求不是Django的HttpRequest类的实例,而是DRF的Request类的实例。
  • 处理方法可以返回DRF的Response,而不只是Django的HttpRequest。视图会管理内容协议,给响应设置正确的渲染器。
  • 任何APIException异常都会被捕获,并且传递给合适的响应。
  • 传入的请求将经过身份验证,并在将请求分派给处理方法之前运行适当的权限或限频检查。

使用APIView类和使用一般的View类非常相似,通常,传入的请求会被分发到合适的处理方法比如get()post()。我们之前一直的视图,都是继承了APIView

二、GenericAPIView类

此类是APIView的子类,对APIView类进行了扩展,添加了一些具体通用视图(后面会学到,每个视图都有具体的应用场景)都会用到的的方法。而每个具体通用视图则是通过将GenericAPIView与一个或多个mixin类组合来构建的,即GenericAPIView与这些mixin类是具体通用视图的父类。

2.1 常用属性

  • queryset:

    设置从该视图返回对象的查询集。 比如queryset = Book.objects.all(),之后就会从该查询集中获取模型对象。

    必须设置该属性或者重写get_queryset()方法

    如果要覆盖一个视图方法,那么就应该调用get_queryset()方法而不是直接访问该属性,这是很重要的。因为DRF第一次求出queryset的值后,就会缓存起来给所有后续的请求使用。

  • serializer_class:

    设置用于验证、反序列化输入以及序列化输出的Serializer类必须设置此属性或者重写get_serializer_class() 方法

  • lookup_field:查找单个模型对象时,参考的字段。默认是'pk'

    也就是说,从路由捕获到参数命名为pk后,传入视图中,在按照pk获取模型对象时,DRF会自动将pk作为查找条件找出目标对象。不需要我们再去写filter(pk=pk)了。

2.2 常用方法

  • get_object(self)

    queryset属性设置的基本查询集中获取实例,然后使用lookup_field属性过滤基本查询集,最后返回一个对象实例。

  • get_queryset(self)

    返回由queryset属性指定的模型类的查询集。

  • get_serializer_class(self)

    默认返回serializer_class属性的值,即指定的序列化器类。

  • get_serializer(self, instance=None, data=None, many=False, partial=False)

    返回serializer_class属性指定的序列化器类的实例。

我们还是通过一个书籍相关的例子来理解上面的方法(模型类和序列化器类不是重点,自己写啊?):

class BookDetailView(GenericAPIView):
    # 设置要用到的查询集
    queryset = Book.objects.all()
    # 设置要用到的序列化器
    serializer_class = BookSerializer
    # 设置查找模型对象用的字段,默认为“pk”
    # 因为我们在路由中捕获的参数命名为了“id”,所以这里也要改为“id”
    lookup_field = "id"

    def get(self, request, id):
        """返回单本书的信息"""
        # 获取单个对象,会自动通过id进行查找
        book = self.get_object()  # 相当于 book = Book.objects.filter(id=id).first()
        # 获取序列化器类并实例化
        book_ser = self.get_serializer(book)
        return Response(book_ser.data)

    def put(self, request, id):
        """修改单本书的信息"""
        book = self.get_object()
        # 获取序列化器类并实例化,并传入前端提交的数据
        book_ser = self.get_serializer(instance=book, data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status': 101, 'msg': '校验失败'})

    def delete(self, request, id):
        """删除单本书籍"""
        # 自动通过id删除对象
        self.get_object().delete()
        return Response({'status': 100, 'msg': '删除成功'})


class BooksView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        """获取所有书籍"""
        # 获取模型类的查询集
        book_list = self.get_queryset()
        book_ser = self.get_serializer(book_list, many=True)
        return Response(book_ser.data)

    def post(self, request):
        """添加一本书籍"""
        book_ser = self.get_serializer(data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status': 101, 'msg': '校验失败'})

三、Mixin类

mixin类为具体通用视图提供了一些方法,比如create()update()等。这些mixin类是专门用于组合构建具体通用视图的。

mixin类可以从rest_frame.mixins导入。

  • ListModelMixin

    提供一个list(request, *args, **kwargs)方法,返回查询集。

  • CreateModelMixin

    提供create(request, *args, **kwargs)方法,实现创建和保存一个新的model实例。

  • RetrieveModelMixin

    提供一个retrieve(request, *args, **kwargs) 方法,实现在响应中返回一个现有模型实例。

  • UpdateModelMixin

    提供一个update(request, *args, **kwargs)方法,该方法实现更新和保存现有模型实例。

    还提供了一个partial_update(request, *args, **kwargs)方法,它类似于update方法,只是用于更新的所有字段都是可选的。这允许支持HTTP PATCH请求。

  • DestroyModelMixin

    提供一个destroy(request, *args, **kwargs)方法,该方法实现删除现有模型实例。

四、通用的具体视图类

以下类是具体的通用视图。这些才是我们通常会用到的,除非需要深度定制,否则直接继承使用即可。

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

  • CreateAPIView

    用于创建模型实例的具体视图,提供一个post方法处理程序。

    比如,我们要创建一本书,就只需要继承CreateAPIView,它替我们实现了post方法和创建模型实例保存到数据库的操作:

    class BookDetailView(CreateAPIView):
        # 设置要用到的模型类
        queryset = Book.objects.all()
        # 设置要用到的序列化器
        serializer_class = BookSerializer
        # 设置查找模型对象用的字段,默认为”pk“, 这里改为了”id“
        lookup_field = "id"
    

    之后设置好路由,使用post方法提交书籍信息就可以直接创建书籍记录了。

下面各种类的使用方法与之类似,需要哪个继承哪个即可。

  • ListAPIView

    用于列出查询集的具体视图(获取全部),提供一个get方法处理程序。

  • RetrieveAPIView

    用于检索模型实例的具体视图(获取指定的单个),提供一个get方法处理程序。

  • DestroyAPIView

    用于删除模型实例的具体视图,提供一个delete方法处理程序。

  • UpdateAPIView

    用于更新模型实例的具体视图,提供一个put和patch方法处理程序。

  • ListCreateAPIView

    列出查询集或创建模型实例的具体视图,提供一个get和post方法的处理程序。

    也即是说,它是ListAPIViewCreateAPIView的结合。

下面的类也是如此,仅仅是为了书写方便,将多个类结合到了一起而已。

  • RetrieveUpdateAPIView

    用于检索、更新模型实例的具体视图,提供get, put和patch方法的处理程序。

  • RetrieveDestroyAPIView

    用于检索或删除模型实例的具体视图,提供 get 和 delete 方法的处理程序。

  • RetrieveUpdateDestroyAPIView

    用于检索、更新或删除模型实例的具体视图,提供get, put, patch 和delete方法的处理程序。

最后,我们来看看使用具体通用视图类后,之前的书籍例子成了什么样?

from rest_framework.generics import CreateAPIView,ListAPIView, RetrieveUpdateDestroyAPIView

from quickstart.models import Book
from quickstart.serializers import BookSerializer

# Create your views here.


class BookDetailView(CreateAPIView, RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = "id"


class BooksView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

没错,就是这么简洁。但其实还可以更加简洁。

上面的代码中querysetserializer_class等属性还是重复的,说到底,还是因为get()方法只有一个,但我们需要通过http get方法获取单本书籍或者获取书籍列表,二者发生了冲突,所以只能分成两个视图分别处理。这一点DRF也为我们考虑到了,因此提供了视图集这一功能。

五、视图集和路由器

5.1 视图集(ViewSet)

DRF允许我们将一组相关视图的逻辑合并到一个类中,这个类叫做ViewSet。

ViewSet只是一种基于类的View类型,它不提供任何方法处理程序,如get()post(),而是提供诸如list()create()等处理程序。因此,需要在路由中绑定http请求方法到相应的处理程序。

比如通过as_view()方法,get、post是http请求方法,list、create是对应的处理程序:

urlpatterns = [
    path('snippets/', BookViewSet.as_view(actions={
    'get': 'list',
    'post': 'create'})
    )]

但通常,我们通过路由器(Router类的实例)注册视图并绑定方法,而不是像以前一样,使用path()等方法显式地绑定视图。

5.2 路由器(Router)

只要我们使用的是ViewSet类(准确的说因该是ViewSetMixin类,因为as_view()方法是被它重写的)而不是View类,我们就不需要自己设计URL。我们需要做的就是使用路由器注册相应的视图集,然后让它执行其余操作。

5.2.1 使用方法

from django.urls import path, include
from rest_framework.routers import DefaultRouter

from quickstart import views

# 创建一个路由器并注册我们的视图集
router = DefaultRouter()
# 第一个参数是url的前缀,后一个是对应的视图
router.register(r'books', views.BooksViewSet)  # 前缀不用加斜杠

# API url现在由路由器自动确定
urlpatterns = [
    path('', include(router.urls))
]

# 也可以不使用include,直接加进去
# urlpatterns += router.urls

上面的示例将自动生成以下URL模式,保存在router.urls中:

  • URL pattern: ^books/$ Name: ‘book-list’
  • URL pattern: ^books/{pk}/$ Name: ‘book-detail’

DefaultRouter类也自动为我们创建了API ROOT视图,即根路径及其对应的视图。所以原来如果有API ROOT路由或视图,现在可以删掉了。

5.2.2 DefaultRouter类

URL 样式HTTP 方法动作URL 名称
[.format]GETautomatically generated root viewapi-root
{prefix}/[.format]GETlist{basename}-list
POSTcreate
{prefix}/{methodname}/[.format]GET, or as specified by `methods` argument`@list_route` decorated method{basename}-{methodname}
{prefix}/{lookup}/[.format]GETretrieve{basename}-detail
PUTupdate
PATCHpartial_update
DELETEdestroy
{prefix}/{lookup}/{methodname}/[.format]GET, or as specified by `methods` argument`@detail_route` decorated method{basename}-{methodname}
  • 生成后缀:

    DefaultRouter还可以生成可选的.json样式格式后缀的路由。

    比如从浏览器访问路径books.json,他就会以json格式返回数据,而不是默认的HTML页面。

  • 删除url最尾部的斜杠:

    # 在实例化时,传入以下参数
    router = DefaultRouter(trailing_slash=False)
    

5.2.3 action装饰器

视图集可以通过使用@action装饰器装饰一个方法,来标记用于路由的额外动作。这些额外的操作将包含在生成的路由中。

例如,给定UserViewSet类上的set_password方法:

from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import action

class UserViewSet(ModelViewSet):
    ...

    @action(methods=['post'], detail=True)
    def set_password(self, request, pk=None):
        ...

参数说明:

  • methods:此操作响应的HTTP方法名称列表,默认仅为GET。
  • detail:确定此操作是否适用于单个实例请求,False表示请求的是集合(多个实例)。

生成的路由如下:

  • URL 路径:^users/{pk}/set_password/$
  • URL 名称:'user-set-password'

默认情况下,URL路径基于方法名,URL名称则是“ViewSet.basename-方法名”。

如果您不想使用这两个值中的任何一个的默认值,您可以将url_pathurl_name参数提供给@action装饰器。

5.3 ModelViewSet类

学习了这么多视图类,各类之间又有着复杂的继承关系,现在大脑一片混乱?‍?,不知道该继承哪些类。但好在DRF已经提前考虑好了这个问题,为我们提供了ModelViewSet类。

ModelViewSet类继承自GenericAPIView,并继承了各种mixin类。因此,它包含了各种操作的实现。

所以,我们如果确定要使用视图集,那么就继承ModelViewSet类即可

ModelViewSet类提供的操作有list()retrieve()create()update()partial_update()destroy()

最后,看一眼书籍例子的最终形态:

视图:

from rest_framework.viewsets import ModelViewSet

from quickstart.models import Book
from quickstart.serializers import BookSerializer


class BookDetailView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = "id"

路由:

from django.urls import include, path
from rest_framework.routers import DefaultRouter

from quickstart import views


router = DefaultRouter()
router.register(r'books', views.BookDetailView)


urlpatterns = [
    path('', include(router.urls))
]

版权声明:本文为qq_39330486原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。