DRF04-视图


视图共分三级

他们之间的联系如下

一级是APIView,二级是GenericAPIView,三级则是CreateAPIView等,下面就对这三级视图的使用分别进行讲解

一级视图

APIView之request

  • 目的: 知道APIView的特点, 并且可以通过request获取参数

  • 特点:

    • 1, 继承自View
    • 2, 提供了自己的request对象
      • get参数: request.query_params
      • post参数: request.data
    • 3, 提供了自己的response对象
    • 4, 并且提供了认证, 权限, 限流等功能

操作流程:

#1,定义类,集成APIView
class BookAPIView(APIView):

    def get(self,request):
        """
        View获取数据方式:
            GET:
                request.GET
            POST:
                request.POST
                request.body

        APIView获取数据方式
            GET:
                reqeust.query_params
            POST:
                request.data

        :param request:
        :return:
        """
        #1,获取APIVIew中的get请求参数
        # print(request.query_params)

        return http.HttpResponse("get")

    def post(self,request):

        # 2,获取APIView中的post的参数
        print(request.data)

        return http.HttpResponse("post")

APIView之Response

  • 目的: 可以使用response响应各种数据和状态

  • 好处:

    • 1,使用一个类, 就可以替代以前View中的各种类型的Response(HttpResponse,JsonResponse….)
    • 2, 可以配合状态码status使用
from rest_framework.views import APIView
from django import http
from rest_framework.response import Response
from rest_framework import status

#1,定义类,集成APIView
class BookAPIView(APIView):

    def get(self,request):
        ...

	return Response([{"name":"zhangsan"},	{"age":13}],status=status.HTTP_404_NOT_FOUND)

常用的web请求有五大方法,分别是get获取所有,post添加一个,get获取单个,put修改一个,delete删除一个,其中前两个又叫列表视图,后三个叫详情视图。


APIView实现列表视图

可以使用序列化器和APIView对列表视图进行改写

子路由

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^books/$',views.BookListAPIView.as_view())
]

视图类

class BookListAPIView(APIView):

    def get(self,request):
        #1,查询所有的书籍
        books = BookInfo.objects.all()

        #2,将对象列表转成字典列表
        serializr = BookInfoModelSerializer(instance=books,many=True)

        #3,返回响应
        return Response(serializr.data)


    def post(self,request):
        #1,获取参数
        data_dict = request.data

        #2,创建序列化器
        serializer = BookInfoModelSerializer(data=data_dict)

        #3,校验,入库
        serializer.is_valid(raise_exception=True)
        serializer.save()

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_201_CREATED)

序列化器

from rest_framework import serializers
from booktest.models import BookInfo

#1,定义书籍模型类序列化器
class BookInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = "__all__"

APIView实现详情视图

可以使用模型类序列化器和APVIew改写详情视图

子路由

from django.conf.urls import url
from . import views

urlpatterns = [
    ...
  
    url(r'^books/(?P\d+)/$',views.BookDetailAPIView.as_view()),
]

类视图

#3,序列化器和APIView实现详情视图
class BookDetailAPIView(APIView):
    def get(self,request,book_id):

        #1,获取书籍
        book = BookInfo.objects.get(id=book_id)

        #2,创建序列化器对象
        serializer = BookInfoModelSerializer(instance=book)

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_200_OK)

    def put(self,request,book_id):

        #1,获取数据,获取对象
        data_dict = request.data
        book = BookInfo.objects.get(id=book_id)

        #2,创建序列化器对象
        serializer = BookInfoModelSerializer(instance=book,data=data_dict)

        #3,校验,入库
        serializer.is_valid(raise_exception=True)
        serializer.save()

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_201_CREATED)

    def delete(self,request,book_id):

        #1,删除书籍
        BookInfo.objects.get(id=book_id).delete()

        #2,返回响应
        return Response(status=status.HTTP_204_NO_CONTENT)

二级视图

二级视图,实现列表视图

子路由

url(r'^generic_apiview_books/$',views.BookListGenericAPIView.as_view()),

类视图

#4,二级视图GenericAPIView特点
"""
特点: 
1, GenericAPIView,继承自APIView类,为列表视图, 和详情视图,添加了常用的行为和属性。
    行为(方法)
        get_queryset
        get_serializer
    
    属性
        queryset
        serializer_class

2, 可以和一个或多个mixin类配合使用。
"""

#5,使用二级视图GenericAPIView实现, 列表视图
class BookListGenericAPIView(GenericAPIView):

    #1,提供公共的属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer


    def get(self,request):
        #1,查询所有的书籍
        # books = self.queryset
        books = self.get_queryset()

        #2,将对象列表转成字典列表
        # serializr = BookInfoModelSerializer(instance=books,many=True)
        # serializr = self.serializer_class(instance=books,many=True)
        # serializr = self.get_serializer_class()(instance=books,many=True)
        serializr = self.get_serializer(instance=books,many=True)

        #3,返回响应
        return Response(serializr.data)


    def post(self,request):
        #1,获取参数
        data_dict = request.data

        #2,创建序列化器
        # serializer = BookInfoModelSerializer(data=data_dict)
        serializer = self.get_serializer(data=data_dict)

        #3,校验,入库
        serializer.is_valid(raise_exception=True)
        serializer.save()

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_201_CREATED)

二级视图,实现详情视图

子路由

url(r'^generic_apiview_books/(?P\d+)/$',views.BookDetailGenericAPIView.as_view()),

类视图

class BookDetailGenericAPIView(GenericAPIView):

    #1,提供通用属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

    def get(self,request,pk):

        #1,获取书籍
        # book = BookInfo.objects.get(id=book_id)
        book = self.get_object() #根据book_id到queryset中取出书籍对象

        #2,创建序列化器对象
        serializer = self.get_serializer(instance=book)

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_200_OK)

    def put(self,request,pk):

        #1,获取数据,获取对象
        data_dict = request.data
        book = self.get_object()

        #2,创建序列化器对象
        serializer = self.get_serializer(instance=book,data=data_dict)

        #3,校验,入库
        serializer.is_valid(raise_exception=True)
        serializer.save()

        #4,返回响应
        return Response(serializer.data,status=status.HTTP_201_CREATED)

    def delete(self,request,pk):

        #1,删除书籍
        self.get_object().delete()

        #2,返回响应
        return Response(status=status.HTTP_204_NO_CONTENT)

get_object方法

理解get_object如何根据pk在queryset获取的单个对象

二级视图GenericAPIView属性方法总结

1, GenericAPIView,继承自APIView类,为列表视图, 和详情视图,添加了常用的行为和属性。
    行为(方法)
        get_queryset:  获取queryset的数据集
        get_serializer: 获取serializer_class序列化器对象
        get_object:    根据lookup_field获取单个对象
    
    属性
        queryset:   通用的数据集
        serializer_class: 通用的序列化器
        lookup_field:   默认是pk,可以手动修改id

2, 可以和一个或多个mixin类配合使用。

在上面的案例代码中我们可以看到获取单个对象是如何做的?使用了book = self.get_object(),很明显这个是个父类方法,因此我们区父类GenericAPIView中看看,get_object是如何获取单个实例对象的。

get_object()源码解析

以下的代码来自DRF源码,经过我的删减,只保留跟get_object()最相关的东西

  1. 这里就是我们继承GenericAPIView的时候必须指定的数据集和序列化器
  2. 这两个字段先记住,后面就用到了
  3. 如果lookup_url_kwarg这个字段为空,就用lookup_field,第二步中也看到了,lookup_url_kwarg为空,因此lookup_url_kwarg就等于lookup_field的默认值也就是pk
  4. 断言,如果pk不在self.kwargs中,就会报错,括号里是不满足断言条件时会打印的信息。那self.kwagrs又是哪里来的,点击self.kwagrs跳到了5
  5. dispatch方法是用来解析请求形式的方法。我们知道,GenericAPIView继承自APIView。在这里,正则解析路由,会将对应的参数放到args或者kwargs,因此7中的pk就放到了self.kwargs中,也就跟前面的lookup_field = 'pk'对应起来了。也就是说get_object()方法默认使用pk主键获取对象。
  6. 形成过滤条件字典,并且依据此字典从数据集中获取数据对象
  7. 路由,pk来自这里
  8. 使用反射获取对应的请求方法,也就是两个get,post,put,delete
  9. 执行请求方法

MiXin

Mixin可以和二级视图配合使用

先看一下如何使用

路由

url(r'^mixin_generic_apiview_books/$',
      views.BookListMixinGenericAPIView.as_view()),
  
url(r'^mixin_generic_apiview_books/(?P\d+)/$',
    views.BookDetailMixinGenericAPIView.as_view()),

类视图

from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
#8,mixin和二级视图GenericAPIView, 实现列表视图, 详情视图
class BookListMixinGenericAPIView(GenericAPIView,ListModelMixin,CreateModelMixin):

    #1,提供公共的属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

    def get(self,request):
        return self.list(request)


    def post(self,request):
        return self.create(request)


class BookDetailMixinGenericAPIView(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):

    #1,提供通用属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer
    # lookup_field = "id"
    lookup_url_kwarg = "book_id"

    def get(self,request,book_id):
        return self.retrieve(request)

    def put(self,request,book_id):
        return self.update(request)

    def delete(self,request,book_id):
        return self.destroy(request)

我们自己的视图类必须继承二级视图,mixin类则根据请求选择性的继承。可以看到,使用mixin和二级视图的组合使用,代码更加精简了,我们只需要执行数据集和序列化类就行了。那他是如何做到的?

下面以ListModelMixin为例,查看一下ListModelMixin的源码

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

ListModelMixin只有一个list方法,而方法体内部的代码跟我们直接使用二级视图来写的几乎一样。剩下的jigemixin类都是如此,每个mixin类都只有一个方法,方法体的代码就是我们之前自己去写的代码。

mixin类有如下几种

类名称 提供方法 功能
ListModelMixin list 查询所有的数据
CreateModelMixin create 创建单个对象
RetrieveModelMixin retrieve 获取单个对象
UpdateModelMixin update 更新单个对象
DestroyModelMixin destroy 删除单个对象

三级视图

可以看到,二级视图还需要包装增删查改五个方法,自己书写对应的方法来调用父类方法,DRF连这一步都省了,做到了进一步的简练,那就是三级视图

三级视图有如下几种:

类名称 父类 提供方法 功能
CreateAPIView GenericAPIView,CreateModelMixin post 创建单个对象
ListAPIView GenericAPIView, ListModelMixin get 查询所有的数据
RetrieveAPIView GenericAPIView, RetrieveModelMixin get 获取单个对象
DestroyAPIView GenericAPIView, DestroyModelMixin delete 删除单个对象
UpdateAPIView GenericAPIView, UpdateModelMixin put 更新单个对象

三级视图,实现列表,详情视图

子路由

url(r'^third_view/$',views.BookListThirdView.as_view()),
url(r'^third_view/(?P\d+)/$',views.BookDetailThirdView.as_view()),

视图类

from rest_framework.generics import ListAPIView,CreateAPIView
class BookListThirdView(ListAPIView,CreateAPIView):

    #1,提供公共的属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

from rest_framework.generics import RetrieveAPIView,UpdateAPIView,DestroyAPIView
class BookDetailThirdView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):

    #1,提供通用属性
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

那这么精简内部是如何做的呢?

下面以ListAPIView来举例,其余四个类似

ListAPIView源码

class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

可以看到,ListAPIView源码中只有一个方法,并且他帮我们已经继承了二级视图类和对应的mixin类,然后编写了一个函数get调用父类的list方法,这些都是我们以前要做的活儿,现在我们只需要按照要求继承对应的三级视图,给出数据集和序列化器即可,精炼至此,一步留神可能就看不懂了。

视图集

前面的二级视图,三级视图都封装的太好了,尤其是三级视图,方法名都给我们想好了,对于我们而言自由度低了点,视图集可以帮我们映射请求与方法

下面就几个常用的视图集来进行讲解

ViewSet

一个最简单的视图集,提供请求映射的功能

子路由

url(r'^viewset/$',views.BooksViewSet.as_view({'get': 'list'})),
url(r'^viewset/(?P\d+)/$',views.BooksViewSet.as_view({'get': 'retrieve'}))

类视图

from django.shortcuts import get_object_or_404
from booktest.serializer import BookInfoModelSerializer
from rest_framework import viewsets

class BooksViewSet(viewsets.ViewSet):
    """
    A simple ViewSet for listing or retrieving books.
    """
    def list(self, request):
        queryset = BookInfo.objects.all()
        serializer = BookInfoModelSerializer(instance=queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = BookInfo.objects.all()
        book = get_object_or_404(queryset, pk=pk)
        serializer = BookInfoModelSerializer(instance=book)
        return Response(serializer.data)

看一下,ViewSet做了什么

ViewSet源码

class ViewSet(ViewSetMixin, views.APIView):
    """
    The base ViewSet class does not provide any actions by default.
    """
    pass

ViewSet只做了个继承操作,APIView已经学过了,那么as_view这个方法定然来自ViewSetMixin,且看一下as_view做了什么?不过在此之前我们需要再看一段代码,这段代码有助于我们了解as_view是怎么做的

这段代码中,as_view是ViewSetMixin中定义的方法,Tmp继承了as_view,使用Tmp调用as_view,as_view中的cls指代的就是Tmp,这个和self是一个意思,他们指代的都是最外层调用的那个类或者对象,而不是中间继承的某个类或者对象。

ViewSetMixin修剪过的代码

这整的映射就是这么形成的。

当我们的请求触发请求所有的数据的时候,走的是第一个路由,第一个路由执行的是BooksViewSet.as_view({'get': 'list'})

当我们的请求触发请求单个数据对象的时候,走的是第二个路由,第二个路由执行的是BooksViewSet.as_view({'get': 'retrieve'})

因此虽然两个都是get请求,但因为触发的路由不一样,执行的as_view也不一样,实际执行的函数不一样,因此不会行成冲突。而在以前,列表请求和详情请求我们需要定义两个视图类,现在可以写在一个视图类里面,函数名字我们也可以随便取,在路由中制定好请求函数映射关系就好了。

ReadOnlyModelViewSet

可以使用ReadOnlyModelViewSet获取所有, 和单个数据

子路由

url(r'^readonly_viewset/$', views.BooksReadOnlyModelViewSet.as_view({'get': 'list'})),
url(r'^readonly_viewset/(?P\d+)/$', views.BooksReadOnlyModelViewSet.as_view({'get': 'retrieve'})),

类视图

from rest_framework.viewsets import ReadOnlyModelViewSet
class BooksReadOnlyModelViewSet(ReadOnlyModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

ReadOnlyModelViewSet源码探寻

也就是说ReadOnlyModelViewSet其实就是三级视图加基础视图集ViewSetMixin的合并,不过如此。

ModelViewSet

使用ModelViewSet实现列表视图+详情视图功能

子路由

url(r'^model_viewset/$', 
    views.BookModelViewSet.as_view({'get': 'list',"post":"create"})),

url(r'^model_viewset/(?P\d+)/$', 
    views.BookModelViewSet.as_view({'get': 'retrieve','put':'update','delete':'destroy'})),

类视图

from rest_framework.viewsets import ModelViewSet
class BookModelViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

ModelViewSet源码探寻

可以看到ModelViewSet只是五个三级视图和基础视图集ViewSetMixin的结合

至此,DRF的视图算是告一段落了。