【个人笔记】简单使用DRF框架

656 阅读14分钟

源自b站 BV1Sz4y1o7E8 的教学所作笔记

1.起始

DRF全称DjangoRestFramework,用于快速构建 Web RESTful API

1- 使用pip install djangorestframework下载DRF
2- 注册子应用以使用DRF

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',  # DRF应用
]

2.初步体验DRF

首先定义models.py中的BookInfo模型类

class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名称')
    bpub_date = models.DateField(verbose_name='发布日期')
    bread = models.IntegerField(default=0, verbose_name='阅读量')
    bcomment = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'tb_books'  # 指明数据库表名
        verbose_name = '图书'  # 在admin站点中显示的名称
        verbose_name_plural = verbose_name  # 显示的复数名称

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.btitle

第一步是构建序列化器
选择需要的子应用目录下新建py文件serializers.py

from rest_framework import serializers
from .models import BookInfo

class BookInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = BookInfo  # 指定序列化从哪个模型映射字段
         fields = "__all__" # 映射哪些字段,此处选择全部字段

第二步是创建类视图
在子应用下在views.py创建类

class BookInfoView(ModelViewSet):
    """ 定义类视图 """
    # 指定查询集
    queryset = BookInfo.objects.all()
    # 指定序列化器
    serializer_class = BookInfoSerializer

第三步是创建路由
在子应用下的urls.py文件中添加路由

from django.urls import re_path
from rest_framework.routers import DefaultRouter #需要引入DefaultRouter

from . import views

app_name = 'booktest'
    urlpatterns = [
    ...
]
router = DefaultRouter() # 创建路由器
router.register(r'restbooks',views.BookInfoView) # 注册路由
urlpatterns += router.urls  # 把生成好的代码拼接到 urlpatterns 中

第四步在浏览器中访问指定路径即可

3.序列化

3.1 创建模型

前提所使用的模型对象,即BookInfo

# models.py

from django.db import models

#定义图书模型类BookInfo
class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名称')
    bpub_date = models.DateField(verbose_name='发布日期')
    bread = models.IntegerField(default=0, verbose_name='阅读量')
    bcomment = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'tb_books'  # 指明数据库表名
        verbose_name = '图书'  # 在admin站点中显示的名称
        verbose_name_plural = verbose_name  # 显示的复数名称

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.btitle

其中数据表有四个数据:

image.png

第二个模型对象为HeroInfo

#定义英雄模型类HeroInfo
class HeroInfo(models.Model):
   GENDER_CHOICES = (
       (0, 'female'),
       (1, 'male')
   )
   hname = models.CharField(max_length=20, verbose_name='名称')
   hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
   hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
   hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')  # 外键
   is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

   class Meta:
       db_table = 'tb_heros'
       verbose_name = '英雄'
       verbose_name_plural = verbose_name

   def __str__(self):
       return self.hname

其表中有多个数据:

image.png

3.2 创建序列化器

根据模型对象BookInfo,创建对应的序列化器。需要在应用目录下新建一个py文件,这里将其命名为serializers.py,创建的序列化器类为BookInfoSerializer

from rest_framework import serializers
from .models import BookInfo

class BookInfoSerializer(serializers.Serializer):
    """
    参数
        lable 即运行完后在浏览器界面的DRF框架表单中显示的名称值
        read_only 即只读,不可更改,因此只做输出不做输入
        requires 即是否必填,默认为 required=True ,
    """
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名称', max_length=20)
    bpub_date = serializers.DateField(label='发布日期', required=True)
    bread = serializers.IntegerField(label='阅读量', required=False)
    bcomment = serializers.IntegerField(label='评论量', required=False)

3.3 在shell显示效果

在例子中使用Djago shell功能,关于Django shell详细使用方法可看Django shell 使用方法
另外可以使用ipython作为辅助,使用pip install ipython命令进行下载

准备工作
使用python manage.py shell进入终端命令行,在shell命令行中,引入所需的类

image.png

案例一 - 序列化多个

查所有: image.png 序列化结果:

[OrderedDict([('id', 1), ('btitle', '射雕英雄传'), ('bpub_date', '1980-05-01'), ('bread', 12), ('bcomment', 34)]), OrderedDict([('id', 2), ('btitle', '天龙八部'), ('bpub_date', '1986-07-24'), ('bread', 36), ('bcomment', 40)]), OrderedDict([('id', 3), ('btitle', '笑傲江湖'), ('bpub_date', '1995-12-24'), ('bread', 20),('bcomment', 80)]), OrderedDict([('id', 4), ('btitle', '雪山飞狐'), ('bpub_date', '1987-11-11'), ('bread', 58), ('bcomment', 24)])]

本例中,当查询到的是多个对象时,在实例化序列化器的时候参数需要加上many=True,若是使用book = BookInfo.objects.get(id = 1)则不用加上此参数

案例二 - 自定义添加序列化字段

退出shell命令行,更改序列化器中的内容,在【3.1 创建序列化器】中的序列化器类中添加一条字段

# 由于添加了一条字段,改变了序列化器,因此需要重启shell
hello  =serializers.CharField(label='你好',required=False)

在使用的时候给BookInfo的实例化类中添加hello属性,可以将hello属性进行序列化

image.png

序列化结果

{'id': 1, 'btitle': '射雕英雄传', 'bpub_date': '1980-05-01', 'bread': 12, 'bcomment': 34, 'hello': '11'}

案例三 - 关联序列化(多对一)

注意BookInfo对应的表为主表,HeroInfo对应的表为从表,HeroInfo的hbook为外键 在【3.1 创建序列化器】再添加一个序列化器类

class HeroInfoSerializer(serializers.Serializer):
    """英雄数据序列化器"""
    GENDER_CHOICES = (
        (0, 'female'),
        (1, 'male')
    )
    id = serializers.IntegerField(label='ID', read_only=True)
    hname = serializers.CharField(label='名字', max_length=20)
    hgender = serializers.ChoiceField(choices=GENDER_CHOICES, label='性别', required=False)
    hcomment = serializers.CharField(label='描述信息', max_length=200, required=False, allow_null=True)

    """ 
        写法一:默认将关联模型的 id 进行序列化
        hbook = serializers.PrimaryKeyRelatedField(label='书籍', read_only=True)
    """

    """ 
        写法二:默认是将关联模型的 __str__ 方法返回值序列化出来
        hbook = serializers.StringRelatedField(label='书籍',read_only=True)
    """
    

    """ 
        写法三:如果是这种写法,意味着反序列化时如果赋予的 hbook 不是 BookInfo 里面已经存在的 id 就会报错 
        hbook = serializers.PrimaryKeyRelatedField(label='书籍', queryset=BookInfo.objects.all()) 
    """
    

    """ 
        写法四:BookInfoSerializer的实例化,会将关联模型对象的序列化器中所有的字段全部序列化出来 
        hbook = BookInfoSerializer()
    """
    

写法一:过程与结果

image.png

写法二:过程与结果

image.png

写法三:不予展示

写法四:过程与结果

image.png

案例四 - 关联序列化(一对多)

注意BookInfo对应的表为主表,HeroInfo对应的表为从表,HeroInfo的hbook为外键

写法一:

在序列化器类BookInfoSerializer多添加一个字段

""" 写法规则是 关联的模型类名小_set"""
# 注意添加上去一个 many = True
heroinfo_set = HeroInfoSerializer(many = True)

过程与结果如下:

image.png

{'id': 1, 'btitle': '射雕英雄传', 'bpub_date': '1980-05-01', 'bread': 12, 'bcomment': 34, 'hello': '11', 'heroinfo_set': [OrderedDict([('id', 1), ('hname', '郭靖'), ('hgender', 1), ('hcomment', '降龙十八掌')]), OrderedDict([('id', 2), ('hname', '黄蓉'), ('hgender', 0), ('hcomment', '打狗棍法')]), OrderedDict([('id',3), ('hname', '黄药师'), ('hgender', 1), ('hcomment', '弹指神通')]), OrderedDict([('id', 4), ('hname', '欧阳锋'), ('hgender', 1), ('hcomment', '蛤蟆功')]), OrderedDict([('id', 5), ('hname', '梅超风'), ('hgender', 0), ('hcomment', '九阴白骨爪')]), OrderedDict([('id', 18), ('hname', '张三'), ('hgender', 0), ('hcomment', None)])]}

写法二:

# 注意添加上去一个 many = True
heroinfo_set = serializers.StringRelatedField(many=True)
# 或 hero_set = serializers.PrimaryKeyRelatedField(many=True)

结果如下:

{'id': 1, 'btitle': '射雕英雄传', 'bpub_date': '1980-05-01', 'bread': 12, 'bcomment': 34, 'hello': '11', 'heroinfo_set': ['郭靖', '黄蓉', '黄药师', '欧阳锋', '梅超风', '张三']}

4.反序列化

4.1 反序列化之添加

同上面的操作,在shell命令行中进行实验

1.导入需要的模块

from booktest.models import BookInfo,HeroInfo
from booktest.serializers import BookInfoSerializer,HeroInfoSerializer

2.首先给定一组数据(自定义一组前端传过来的json数据),名为data

data = {
    'btitle':'星三国',
    'bpub_date':'1991-3-15'
}

3.实例化,并且需要传入参数data

serializers = BookInfoSerializer(data=data)

4.进行验证 (失败或成功)

"""
    在获取反序列化的数据前,必须调用 is_valid() 方法进行验证,
    验证成功返回True,否则返回False。
"""
serializers.is_valid(raise_exception=True)

""" 
    验证失败,可以通过序列化器对象的errors属性获取错误信息,
    返回字典,包含了字段和字段的错误。如果是非字段错误,
    可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。
"""
# serializer.errors # 验证失败 

""" 验证成功,可以通过序列化器对象的**validated_data**属性获取数据。 """
# serializer.validated_data # 验证成功

5.进行保存

""" 
    本案例是添加操作,则会调用序列化器的create方法,
    如果是更新操作,则会调用序列化器的update方法
"""
serializers.save()

4.2 反序列化之更新

在本例中使用新的data以更新上一例中的添加操作

1.使用新的数据data

data = {
    'btitle':'超级三国',
    'bpub_date':'2003-10-15'
}

2.获取数据库中的某个对象 book

book = BookInfo.objects.get(id = 18)

3.实例化序列化器

""" 
    假如参数中有instance,则表明保存时调用序列化器的update函数
    反序列化id为18的书籍对象
"""
serializer = BookInfoSerializer(instance=book,data=data)

4.验证

serializer.is_valid(raise_exception=True)

5.保存

""" 
    本案例是更新操作,则会调用序列化器的update方法,
    如果是添加操作,则会调用序列化器的create方法
"""
serializer.save()

4.3 序列化器的create,update方法

BookInfoSerializer为例

from rest_framework import serializers
from .models import BookInfo
class BookInfoSerializer(serializers.Serializer):
    # 字段代码,忽略不看
    # ... ...
    
    """
    应该注意到,在4.1 中的实例化操作中,有以下代码:
        serializers = BookInfoSerializer(data=data)
    说明传入的data此时作为create函数中的validated_data参数,为字典格式
    """
    def create(self, validated_data):
        """新建"""
        # 使用 模型类.objects.create方法进行添加,
        # 由于是字典格式,因此作为参数时前面要加**
        return BookInfo.objects.create(**validated_data)
        
    """
        在4.2中的实例化操作中,有:
            serializer = BookInfoSerializer(instance=book,data=data)
        说明传入的book作为update的instance参数,传入的data作为validated_data参数
    """
    def update(self, instance, validated_data):
        """更新,instance为要更新的对象实例"""
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        instance.save()
        return instance

5.ModelSerializer

在上面的例子中,序列化器用的都是serializers中的Serializer类,但是使用ModelSerializer0会更加方便。在【2.初步体验DRF】中有使用

其优点在于:
    1.ModelSerializer可以根据模型自动生成序列化器中的字段
    2.ModelSerializer已经实现了create以及update方法

但是有的时候需要重写其中的create方法,比如说有的时候前端需要填写双重密码校验,这样可能会传过来类似{'password1':'123456','password2':'123456'}的数据,可是password2是校验密码,因此ModelSerializers中的create方法需要重写以删除password2

例子

序列化器:

class BookInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = BookInfo  # 指定序列化从哪个模型映射字段
         fields = "__all__" # 映射哪些字段,此处选择全部字段
         # fields = ['id','btitle','bpub_date'] # 映射id,btitle,bpub_date三个字段
         # exclude = ['bpub_date'] # 除了bpub_date不需要映射其他都要

在shell命令行运行查看:

image.png

image.png

因此映射完成后的字段是:

id = IntegerField(label='ID', read_only=True)
btitle = CharField(label='名称', max_length=20)
bpub_date = DateField(label='发布日期')
bread = IntegerField(label='阅读量', max_value=2147483647, min_value=-2147483648, required=False)
bcomment = IntegerField(label='评论量', max_value=2147483647, min_value=-2147483648, required=False)
is_delete = BooleanField(label='逻辑删除', required=False)

新增字段

由于ModelSerializer是Serializer的子类,因此也可以进行新增字段,假如新增good

class BookInfoSerializer(serializers.ModelSerializer):
     good = serializers.CharField(label='好评度', max_length=20)
     class Meta:
         model = BookInfo  # 指定序列化从哪个模型映射字段
         fields = "__all__"  
         # exclude = ['bpub_date']

如果使用列表形式,那么记得加上新增的good fields=['id','btitle','bpub_date','good']

修改选项参数

假如说对映射完后的bread字段的中min_value不满意,想将其变为0,对required字段不满意,想改为 True ,对那么可以:

class BookInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = BookInfo  # 指定序列化从哪个模型映射字段
         fields = "__all__"
         
         # 修改选项参数
         extra_kwargs = {
             'bread':{'min_value':0,'required':True}, # 将bread中的min_value改为0,required改为True
             'bcomment':{'write_only':True} #将bcomment改为只做反序列化(只能写进)
         }
         
         # 若想改变只读选项,则需要再另外定义 
         read_only_fields = ['bread'] # 将bread改为只读 

最终的bread以及bcomment结果:

# 注意,此时的bread中有read_only,因此自动取消掉requied选项
bread = IntegerField(label='阅读量', min_value=0, read_only=True)
bcomment = IntegerField(label='评论量', max_value=2147483647, min_value=-2147483648, required=False, write_only=True)

6.视图

6.1 Request和Response

Request

REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。

REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。

Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。

无论前端发送的哪种格式的数据,我们都可以以统一的方式读取数据。

常用属性

1. data

request.data 返回解析之后的请求体数据。类似于Django中标准的request.POST和 request.FILES属性,但提供如下特性:

  • 包含了解析之后的文件和非文件数据
  • 包含了对POST、PUT、PATCH请求方式解析后的数据
  • 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据

2. query_params

request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。

Response

rest_framework.response.Response

REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览API渲染器
    )
}

构造方式

Response(data, status=None, template_name=None, headers=None, content_type=None)

data数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data

data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

参数说明:

  • data: 为响应准备的序列化处理后的数据;
  • status: 状态码,默认200;
  • template_name: 模板名称,如果使用HTMLRenderer 时需指明;
  • headers: 用于存放响应头信息的字典;
  • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

常用属性:

data

传给response对象的序列化后,但尚未render处理的数据

status_code

状态码的数字

content

经过render处理后的响应数据

6.2 状态码

详见 【DRF】常见状态码

6.3 DRF中的视图

在此使用【3.序列化】中的BookInfo作为案例表

APIView 以及案例

首先创建序列化器

创建一个新的序列化器BookInfoModelSerialize

class BookInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = "__all__"

两个视图类的路由

re_path(r"^apibooks/$",views.BookListAPIView.as_view(),name = "BookListAPIView"),
re_path(r"^apibooks/(?P<id>\d+)/$",views.BookDetailAPIView.as_view(),name = "BookDetailAPIView")

类视图1:查询以及新增

创建一个类视图 BookListAPIView

from .models import BookInfo

""" APIView是REST framework提供的所有视图的基类,继承自Django的View父类。
    这个类会在后面介绍 """
from rest_framework.views import APIView,status
from .serializers import BookInfoModelSerializer

""" 引入DRF中的Response代替HttpResponse,JsonResponse等对象作为返回的对象
    它可以将数据内容转换(render渲染)成符合前端需求的类型。"""
from rest_framework.response import Response

class BookListAPIView(APIView):
    # 查所有书籍
    def get(self,request):
        # 获取所有书籍
        books = BookInfo.objects.all()
        # 进行序列化
        serializer = BookInfoModelSerializer(instance=books,many=True)
        """ 本来 serializer.data 的数据类型为OrderedDict(或由OrderedDict形成的列表)
            现在使用Response会将数据渲染成json数据并返回给前端 """
        return Response(serializer.data)
    #新增书籍
    def post(self,request):
        """ 当视图类继承DRF中的APIView时,其中的request成为DRF提供的扩展了  
                HttpRequest类的Request类的对象
            其中常用的属性为request.data以及request.query_params
            这里通过加强的request.data获取解析之后的数据 """
        data = request.data
        serializer = BookInfoModelSerializer(data=data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

类视图2:单一查询/修改/删除

创建类视图BookDetailAPIView


from .models import BookInfo
from rest_framework.views import APIView,status
from .serializers import BookInfoModelSerializer

class BookDetailAPIView(APIView):
    # 查单一书籍
    def get(self,request,id):
        try:
            book = BookInfo.objects.get(id = id)
        except BookInfo.DoesNotExist:
            """ 使用DRF中的status
                这里指定为404 """
            return Response(status = status.HTTP_404_NOT_FOUND)
        serializer = BookInfoModelSerializer(instance=book)
        return Response(serializer.data)
    # 修改单一书籍
    def put(self,request,id):
        try:
            book = BookInfo.objects.get(id = id)
        except BookInfo.DoesNotExist:
            return Response(status = status.HTTP_404_NOT_FOUND)
        serializer = BookInfoModelSerializer(instance=book,data=request.data)
        print(request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(request.data)
    # 删除单一书籍
    def delete(self, instance,id):
        try:
            book = BookInfo.objects.get(id = id)
        except BookInfo.DoesNotExist:
            return Response(status = status.HTTP_404_NOT_FOUND)
        book.delete()
        return Response(status = status.HTTP_204_NO_CONTENT)

GenericAPIView 以及案例

相比较APIViewGenericAPIView中可以赋予序列化器和查询集到"变量"中,相比较起来更加灵活
另外使用 GenericAPIView 查询单一书籍,须使用到 get_object(),其作用在于通过前端路由传过来的pk值获取对应id的书本对象,需要注意的是其路由参数的值应为 pk

# urls.py

""" (?P<pk>\d+) 中应为pk """
re_path(r"^genericbooks/(?P<pk>\d+)/$",views.BookDetailGenericView.as_view(),name = "BookDetailGenericView")

查询/新增 以及 单一查询/修改/删除

# views.py

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.views import status


class BookListGenericView(GenericAPIView):
    """ 指定的序列化器 """
    serializer_class = BookInfoModelSerializer
    """ 指定的查询集 """
    queryset = BookInfo.objects.all()
    
    """ 1.查询 """
    def get(self,request):
        """ 通过 GenericAPIView 中的 get_queryset() 方法 获取 指定的查询集"""
        books = self.get_queryset()
        """ 通过 GenericAPIView 中的 get_serializer 方法 获取 指定的序列化器"""
        serializer = self.get_serializer(instance = books,many = True)
        return Response(serializer.data)
        
    """ 2.新增 """
    def post(self,request):
        data = request.data
        serializer = self.get_serializer(data = data)
        serializer.is_valid(raise_exception = True)
        serializer.save()
        return Response(serializer.data)


class BookDetailGenericView(GenericAPIView):
    """ 指定的查询集 """
    queryset = BookInfo.objects.all()
    """ 指定的序列化器 """
    serializer_class = BookInfoModelSerializer
    
    """ 1.单一查询 """
    def get(self, request, pk):
        """ get_object()方法根据pk参数查找queryset中的数据对象 """
        book = self.get_object()  
        serializer = self.get_serializer(book)
        return Response(serializer.data)
        
    """ 2.单一修改 """
    def put(self,request,pk):
        book = self.get_object()
        serializer = self.get_serializer(instance = book,data = request.data)
        serializer.is_valid(raise_exception = True)
        serializer.save()
        return Response(serializer.data)
        
    """ 3.单一删除 """
    def delete(self,request,pk):
        book = self.get_object()
        book.delete()
        return Response(status = status.HTTP_204_NO_CONTENT)

五个拓展类 Mixin

五个拓展类分别为 ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,可以简略地写增删改查等操作

通过引入五个拓展类来缩短【GenericAPIView 以及案例】中的代码


from rest_framework.views import APIView,status
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
""" 引入五个拓展类 """
from rest_framework.mixins import 
ListModelMixin,CreateModelMixin,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin

""" 拓展类 ListModelMixin,CreateModelMixin 与GenericAPIView一同作为父类被继承 """
class BookListGenericView(GenericAPIView,ListModelMixin,CreateModelMixin):
    # 指定序列化器
    serializer_class = BookInfoModelSerializer
    # 指定查询集
    queryset = BookInfo.objects.all()
    """ 查询 """
    def get(self,request):
        return self.list(request) # 使用 ListModelMixin 中的 list 方法
    """ 新增 """
    def post(self,request):
        return self.create(request)  # 使用 CreateModelMixin 中的 create 方法

""" 拓展类 RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin 与GenericAPIView一同作为父类被继承 """
class BookDetailGenericView(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer
    
    """ 单一查询 """
    def get(self, request, pk):
        return self.retrieve(request) # 使用 RetrieveModelMixin 中的 retrieve 方法
        
    """ 单一修改 """
    def put(self,request,pk):
        return self.update(request) # 使用 UpdateModelMixin 中的 update 方法
        
    """ 单一删除 """
    def delete(self,request,pk):
        return self.destroy(request) # 使用 DestroyModelMixin 中的 destroy 方法

子类视图

CreateAPIView 提供 post 方法
继承自: GenericAPIView、CreateModelMixin

ListAPIView 提供 get 方法
继承自:GenericAPIView、ListModelMixin

RetrieveAPIView 提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin

DestroyAPIView 提供 delete 方法
继承自:GenericAPIView、DestroyModelMixin

UpdateAPIView 提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin

RetrieveUpdateAPIView 提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

RetrieveUpdateDestroyAPIView 提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin

使用子类视图将更加精简,只需指定序列化器以及查询集即可完成逻辑


from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView

""" 查询以及新增 """
class BookListGenericView(ListCreateAPIView):
    # 指定序列化器
    serializer_class = BookInfoModelSerializer
    # 指定查询集
    queryset = BookInfo.objects.all()
    
""" 单一查询/修改/删除 """
class BookDetailGenericView(RetrieveUpdateDestroyAPIView):
    # 指定序列化器
    serializer_class = BookInfoModelSerializer
    # 指定查询集
    queryset = BookInfo.objects.all()

6.4 使用视图集

视图集作用

  • 将所有的接口写在一个类视图中(类视图中的方法不再使用get,post... 而是使用 行为 action
  • 重写了as_view(),将改写的方法包装成字典,例如as_view({'get':'list'})

各种动作:

list() 提供一组数据
retrieve() 提供单个数据
create() 创建数据
update() 保存数据
destroy() 删除数据

ViewSet

继承自APIViewViewSetMixin,在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

案例如下,将查询接口以及查询单一写在一个类中:


from rest_framework.viewsets import ViewSet

""" ViewSet 继承 APIView ,因此序列化的方式与APIView的方式一样 """
class BookInfoViewSet(ViewSet):
    """ 查询 """
    def list(self,request):
        try:
            books = BookInfo.objects.all()
        except BookInfo.DoesNotExist:
            return Response(status = status.HTTP_404_NOT_FOUND)
        serializer = BookInfoModelSerializer(instance=books,many=True)
        return Response(serializer.data)
    """ 单一查询 """
    def retrieve(self,request,pk = None):
        try:
            book = BookInfo.objects.get(id = pk)
        except BookInfo.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = BookInfoModelSerializer(instance=book)
        return Response(serializer.data)

路由实现,可见两个路由均使用同一个类BookInfoViewSet,并且传递参数给as_view()

re_path(r"^viewsetbooks/$",views.BookInfoViewSet.as_view({'get':'list'})),
re_path(r"^viewsetbooks/(?P<pk>\d+)/$",views.BookInfoViewSet.as_view({'get':'retrieve'})),

GenericViewSet

使用ViewSet通常并不方便,因为list、retrieve、create、update、destroy等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView

GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIViewViewSetMixin,在实现了调用as_view()时传入字典(如{'get':'list'})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用。

实现 查询/单一查询 的案例如下:

from rest_framework.viewsets import GenericViewSet

class 
BookInfoGenericViewSet(ListModelMixin,RetrieveModelMixin,GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer
    """
    不用编写方法就可以实现查询/单一查询
    因为视图集本身调用list、retrieve、create等方法,
    而ListModelMixin,RetrieveModelMixin自己内置list,retrieve方法,会自动调用
    """

路由实现如下:

re_path(r"^geviewsetbooks/$", views.BookInfoGenericViewSet.as_view({'get': 'list'})),
re_path(r"^geviewsetbooks/(?P<pk>\d+)/$", views.BookInfoGenericViewSet.as_view({'get': 'retrieve'})),

ReadOnlyModelViewSet

另外,查询/单一查询 都是只读的,这样的话可以直接使用ReadOnlyModelViewSet,它继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin。那么 查询/单一查询可写成如下:

from rest_framework.viewsets import ReadOnlyModelViewSet

""" ReadOnlyModelViewSet继承了GenericViewSet、ListModelMixin、RetrieveModelMixin """
class BookInfoGenericViewSet(ReadOnlyModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer

ModelViewSet

如果想不止拥有查询功能,还想增删改查都有,那么可以使用最终版本的 ModelViewSet

它继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestroyModelMixin。

包含查询/新增,单一查询/修改/删除的视图如下。也可以直接查看【2.初体验DRF】

class BookInfoModelViewSet(ModelViewSet):
    """ 定义类视图 """
    # 指定查询集
    queryset = BookInfo.objects.all()
    # 指定序列化器
    serializer_class = BookInfoSerializer

路由实现如下:

re_path(r"^geviewsetbooks/$", views.BookInfoModelViewSet.as_view({'get': 'list'})),
re_path(r"^geviewsetbooks/(?P<pk>\d+)/$", views.BookInfoModelViewSet.as_view({'get': 'retrieve'})),

在视图集上新增动作

现在在上面的ModelViewSet实验中新增视图方法

class BookInfoModelViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer
    # 目前具有的基本增删改查动作之外,还可以自定义另外动作
    """ 现在自定义动作 latest ,查出最后一个id的具体数据 """
    def latest(self,request):
        book = BookInfo.objects.latest('id')
        serializer = self.get_serializer(instance = book)
        return Response(serializer.data)
    """ 现在自定义动作 update_read ,进行单独修改书本的阅读量 """
    def update_read(self,request,pk = None):
        book = self.get_object()
        book.bread = request.data.get('bread')
        book.save()
        serializer = BookInfoModelSerializer(instance=book)
        return Response(serializer.data)

自定义动作中的路由

""" ModelViewSet视图集的基本增删改查路由 """
re_path(r"^geviewsetbooks/$", views.BookInfoModelViewSet.as_view({'get': 'list','post':'create'})),
re_path(r"^geviewsetbooks/(?P<pk>\d+)/$", views.BookInfoModelViewSet.as_view({'get': 'retrieve','put':'update','delete':'destroy'})),

# 如果存在 增删改查 之外的额外行为 应该单独定义路由
# 如果此行为不需要pk,那么他就是列表视图,但是列表视图默认只有list,create

""" 在列表视图路由后缀加上latest,使用get方法访问此路由将显示最后一本书 """
re_path(r"^geviewsetbooks/latest$", views.BookInfoModelViewSet.as_view({'get': 'latest'})),
""" 后缀加上update_read,使用put方法访问此路由可以选择修改书本的阅读量 """
re_path(r"^geviewsetbooks/(?P<pk>\d+)/update_read$", views.BookInfoModelViewSet.as_view({'put':'update_read'})),

在视图集上使用路由器

只有在视图集中才能使用路由器
以上面的ModelViewSet新增动作实验为例,添加基本路由器

from rest_framework.decorators import action  

class BookInfoModelViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoModelSerializer
    # 目前具有的基本增删改查动作之外,还可以自定义另外动作
    
    """ 添加装饰器action
            methods 表示action对应的请求方式
            detail  True时表示对应的路由为   xxx/<pk>/action方法名/
                    False时表示对应的路由为  xxx/action方法名/  """
    @action(methods=['get'],detail=False)
    def latest(self,request):
        book = BookInfo.objects.latest('id')
        serializer = self.get_serializer(instance = book)
        return Response(serializer.data)
    """ """
    def update_read(self,request,pk = None):
        book = self.get_object()
        book.bread = request.data.get('bread')
        book.save()
        serializer = BookInfoModelSerializer(instance=book)
        return Response(serializer.data)

urls.py中添加路由器

from rest_framework.routers import DefaultRouter

urlpatterns = [
    ...
]

router = DefaultRouter() # 创建路由器
router.register(r"geviewsetbooks",views.BookInfoModelViewSet) # 注册路由
urlpatterns += router.urls  # 把生成好的代码拼接到 urlpatterns 中

""" 此时形成的代码如下
        ^geviewsetbooks/$ name: book-list 
        ^geviewsetbooks/{pk}/$ name: book-detail
        
        另外新增的action的路由看视图中的action装饰器而定 """