IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 1. DRF 序列化组件 -> 正文阅读

[Python知识库]1. DRF 序列化组件

0. 环境创建

* 1. 新建一个项目

image-20220414211329127

* 2. 修改 tempaltes路径问题
# 修改模板文件路径拼接问题
'DIRS': [BASE_DIR, 'templates']
* 3. 在app01下创建表模型
from django.db import models


# Create your models here.
class Book(models.Model):
    # 主键
    id = models.AutoField(primary_key=True, verbose_name='主键')
    # 书名
    title = models.CharField(max_length=32, verbose_name='书名')
    # 价格
    price = models.DecimalField(max_digits=5, decimal_places=3)
    # 作者
    author = models.CharField(max_length=32, verbose_name='作者')
    # 出版社
    publish = models.CharField(max_length=32, verbose_name='出版社')

* 4. 生成表
	生成操作记录 python manage.py makemigrations
	数据库迁移 python manage.py migrate

image-20220414212642798

* 5. 在项目app01下的tests.py中添加两条数据
# Create your tests here.
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DRF1_serializers.settings")
    import django

    django.setup()

    # 导入模型层
    from app01.models import Book

    book_list = [
        {
            'title': '穿越诸天万界',
            'price': 12.3,
            'author': 'aa',
            'publish': '飞卢小说网'
        },

        {
            'title': '洪荒道尊',
            'price': 32.1,
            'author': 'bb',
            'publish': '飞卢小说网'
        }]

    # 生成对象并添加到列表中
    queryset_list = []
    for dic in book_list:
        queryset_list.append(Book(**dic))

    # 批量插入
    Book.objects.bulk_create(queryset_list)

image-20220414214521913

1. 序列化组件

序列化组件Serializer是rest_framework的.
导入序列化组件:
from rest_framework.serializers import Serializer
1. 序列化: 序列化器会把模型对象转为字典, 经过response之后变成json格式字符串, 发送给客户端.
2. 反序列化: 序列化器可以将字典转为模型对象, 客户端发送的数据, 经过request以后变成字典.
3. 反序列化完成数据校验功能.

1.1 序列化的过程

1. 写一个序列化类, 这个类需要继承Serializer
2. 在类中写需要序列化的字段, 字段 = serializers.转换的类型(), 将表字段对应的数据转为指定格式数据
3. 在视图中导入序列化类, 实例化得到一个序列化器对象, 需要转化的数据对象作为第一个参数, 传入序列化类中 
4. 序列化类实例化得到一个对象, 序列列化的数据是一个字典, 被封装到对象的.data方法中
5. 字典数据使用rest_framework提供的Response函数放回
rest_framework提供的Response, 会根据不同的客户端返回不同的信息, 如浏览器访问, 返回一个页面.
使用JsonResponse返回的数据就都是json格式的字符串.

1.2 字段类型

对于DRF框架中序列化器所有的字段类型,我们可以到 rest_framework.fields 模块中进行查看
序列化器类定义字段类型和Django框架中ORM的字段类型是一样.
序列化器中常用的字段类型如下
字段字段构造方式
BooleanFieldBooleanField()
CharFieldCharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailFieldEmailField(max_length=None, min_length=None, allow_blank=False)
IntegerFieldIntegerField(max_value=None, min_value=None)
FloatFieldFloatField(max_value=None, min_value=None)
DecimalFieldDecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)max_digits: 最多位数decimal_palces: 小数点位置
DateTimeFieldDateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateFieldDateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeFieldTimeField(format=api_settings.TIME_FORMAT, input_formats=None)
ChoiceFieldChoiceField(choices)choices与Django的用法相同
ImageFieldImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)

1.3 获取数据(序列化操作)

* 1. 在app01项目下新建一个res.py文件
# 导入序列化器
from rest_framework import serializers  # 导入serializers组件
# 导入序列化中的串行器
from rest_framework.serializers import Serializer  # Serializer是一个类


# 定义序列化组件的类, 继承串行器
class BookSerializer(Serializer):
    # 定义需要序列化的字段, 没有定义的不会处理
    # 主键
    id = serializers.CharField()
    # 书名
    title = serializers.CharField()
    # 价格 浮点型的值也转为字符串
    price = serializers.CharField()
    # 作者
    author = serializers.CharField()
* 2. 定义一个路由
    # Book视图类的  定义一个有名分组
    url(r'^book/(?P<pk>\d+)/', views.Book.as_view())
* 3. 定义视图类 使用APIView
from django.shortcuts import render, redirect, HttpResponse

# 0. 导入 APIView, 使用rest_framework模块一定要去app应用列表中注册
from rest_framework.views import APIView


# 1. 定义视图类
class BookView(APIView):
    # 1.1 定义get方法, 接收request对象 与有名分组匹配到的的主键
    def get(self, request, pk):
        # 1.3 导入 模型层
        from app01.models import Book
        # 1.4 通过 主键获取对应的数据对象
        book_obj = Book.objects.filter(pk=pk).first()
        print(book_obj)

        # 1.5 导入图书序列化器
        from app01.res import BookSerializer
        # 1.6 使用序列化将模型对象序列化成字典 BookSerializer是一个类, 加括号实例化执行__init__方法
        book_dic = BookSerializer(book_obj)

        # 1.7 序列化成字典的数据被封装到对象的data方法中, 通过.data获取序列化之后的数据
        print(book_dic.data)

        # 1.8 导入rest_framework的 Response, Response继承了Django的HttpResponse, 返回的数据也是HttpResponse格式
        from rest_framework.response import Response

        # 1.9 返回字典数据
        return Response(book_dic.data)

将导入模块提出
from django.shortcuts import render, redirect, HttpResponse

# 0. 导入 APIView, 使用rest_framework模块一定要去app应用列表中注册
from rest_framework.views import APIView

# 1.3 导入 模型层
from app01.models import Book

# 1.5 导入图书序列化器
from app01.res import BookSerializer

# 1.8 导入rest_framework的 Response, Response继承了Django的HttpResponse, 返回的数据也是HttpResponse格式
from rest_framework.response import Response


# 1. 定义视图类
class BookView(APIView):
    # 1.1 定义get方法, 接收request对象 与有名分组匹配到的的主键
    def get(self, request, pk):
        # 1.4 通过 主键获取对应的数据对象
        book_obj = Book.objects.filter(pk=pk).first()
        print(book_obj)

        # 1.6 使用序列化将模型对象序列化成字典 BookSerializer是一个类, 加括号实例化执行__init__方法
        book_dic = BookSerializer(book_obj)

        # 1.7 序列化成字典的数据被封装到对象的data方法中, 通过.data获取序列化之后的数据
        print(book_dic.data)

        # 1.9 返回字典数据
        return Response(book_dic.data)

* 4. 启动项目
* 5. 在Postman中测试
	 GET 127.0.0.1:8000/api/books/v1/1/
	 定义的字段都处理了, 作者字段没有处理

image-20220415010213562

* 6. 在浏览器中访问: 127.0.0.1:8000/api/books/v1/1/
使用srest_framework的Response返回数据的时候, 针对浏览器, 会渲染一个页面返回,
这个页面使用通过srest_framework模块的app去生成的, 需要对这个app进行注册.
出现这个错误之后, 去app列表中注册rest_framework即可.

2022-04-15_00146

* 7. 应用列表中注册 rest_framework app
INSTALLED_APPS = [
    ...
    'rest_framework',
]
* 8. 重新访问

image-20220415005427655

1.4 反序列化的过程

1. 写一个序列化类, 这个类需要继承Serializer
2. 在类中写需要反序列化的字段, 字段 = serializers.转换的类型(限制条件)
   2.1 将请求的数据转为指定类型数据, json格式字符串(字典) --> Python字典
   2.2 转换数据的时候做数据的校验, 默认的参数达不到要求, 可以自定义钩子函数
参数名称作用
max_length字符串最大长度
min_length字符串最小长度
max_value数字最大值
min_value数字最小值
allow_blank是否允许为空
trim_whitespace是否截切空白字符
3. 在视图中导入序列化类, 实例化得到一个序列化器对象, 
   需要转化的数据对象作为第一个参数, 修改的数据作为第二个参数, 传入序列化类中.
       对象 = xxserializer(数据对象, request.data)
       推荐 对象 xxserializer(instance=数据对象, data=request.data)
4. 数据校验
      对象.is_valid, 判断数据是否合法
      is_valid(raise_exception=True):  数据校验不通过直接return返回错误提示
      对象.errors 校验不通过的异常信息
      对象.error_messages 异常的提示信息
5. 检验成功就保存数据
          对象.sava(), 需要在序列化类重新updata方法.
6. 检验不成功, 写错误的逻辑	  

1.5 修改数据(反序列化+数据校验)

修改的数据的请求put  patch 虽然被区分为局部修改和全局修改, 倒是在使用时, 没人严格区分, 使用那个都行.
1. 修改app01下的res.py 中的序列化类, 添加校验条件
# 定义序列化组件的类, 继承串行器
class BookSerializer(Serializer):
    # 定义需要序列化的字段, 这些字段是必须要传递的
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField()
    # 书名
    title = serializers.CharField(min_length=3, max_length=5)
    # 价格 浮点型的值也转为字符串
    price = serializers.CharField()
    # 作者
    author = serializers.CharField()
* 2. 写视图类的put方法
    # 定义put方法 接收request对象, 与有名分组匹配到的的主键
    def put(self, request, pk):
        """
        对数据修改需要对数据做检验
        """

        # 1. 通过组件获取对应的数据对象
        book_obj = Book.objects.filter(pk=pk).first()

        # 2. 反序列化 字段-->模型, 序列化类(数数据对象, 修改的数据)
        book_dic = BookSerializer(instance=book_obj, data=request.data)

        # 3. 判断数据校验结果
        if book_dic.is_valid():
            # 数据校验合格之后将数据存储
            book_dic.save()  # NotImplementedError:必须实现`update()`。

            back_dic = {
                'code': 200,
                'msg': '修改成功!',
                # 将修改后的数据返回 单个数据返回的是对象
                'book_obj': book_dic.data
            }
        else:
            back_dic = {
                'code': 404,
                'msg': f'修改失败: {book_dic.errors}'
            }

        return Response(back_dic)

2022-04-15_00150

序列化器的对象, 自己是没有save方法的, 父类中定义了该方法. 它判断当前需要更新数据就会调用update方法
update方法利用Python面向对象的多态特性, 在父类中限制子类必须有某个方法, 实现某个功能.
* 3. 在序列化类定义update方法.
	 每个表的数据都是不一样的, 写这个模板的作者刚脆不提供数据更新到数据库的方法, 
	 在父类中限制该方法必须实现, 让使用者自己写update, 将数据更新到数据库
# 定义序列化组件的类, 继承串行器
class BookSerializer(Serializer):
    # 定义需要序列化的字段
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField()
    # 书名
    title = serializers.CharField(min_length=3, max_length=5)
    # 价格 浮点型的值也转为字符串
    price = serializers.CharField()
    # 作者
    author = serializers.CharField()

    # 定义update方法  book的数据对象  检验后的数据-->字典
    def update(self, instance, validated_data):
        # 将book_obj对象的数据 数据逐个修改
        for key in validated_data:
            # 放射   对象      属性    值
            setattr(instance, key, validated_data.get(key))

        # 保存数据, 写入数据库  Django ORM的save()
        instance.save()
        # 修改之后将数据返回
        return instance
* 5. 在Postman中测试: (* 序列化器类中定义的字段必须传数据, 不然, 反序列化该字段就会报错, 不能为空)
	 请求方式: put
	 请求地址: 127.0.0.1:8000/api/books/v1/1/
	 请求格式: raw-JSON
	 请求数据:
{	
    "id": "1",
    "title": "穿越诸天",
    "price": "12.30",
    "author": "aaa",
}

image-20220415140722522

* 6. 检验测试
     tilte字段的修改限制条件: 字符串 最长5, 最少3
     请求数据:
{
    "title": "穿越",
    "price": "12.30",
    "author": "aaa",
}

image-20220415141506010

# 3. 判断数据校验结果, 如有数据不合格就直接return返回
book_dic.is_valid(raise_exception=True)
# 后面的代码全部注释

image-20220416133805848

1.6 局部钩子检验

校验的顺序, 是一个一个字段验证的, 先验证参数规则, 马上检查自己对应的局部钩子.
在局部钩子中使用 raise ValidationError('xx'), 抛出的异常被捕获, 并封装到对象.errors中.
# 导入抛异常模板
from rest_framework.exceptions import ValidationError
检验书的价格不能超过300.00
* 1. 在序列化类中写局部钩子函数, 局部钩子的函数名 validdate_需要校验的字段名称
	 必须接收一个date参数, 这个参数就是, 该字段提交的值, 在经过第一层serializers.xx(x)校验之后的数据.
# 导入序列化器
from rest_framework import serializers  # 导入serializers组件
# 导入序列化中的串行器
from rest_framework.serializers import Serializer  # Serializer是一个类

# 导入抛异常模板
from rest_framework.exceptions import ValidationError


# 定义序列化组件的类, 继承串行器
class BookSerializer(Serializer):
    # 定义需要序列化的字段
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField()
    # 书名
    title = serializers.CharField(min_length=3, max_length=5)
    # 价格 浮点型的值也转为字符串
    price = serializers.CharField()
    # 作者
    author = serializers.CharField()

    # 定义update方法  book的数据对象  检验后的数据-->字典
    def update(self, instance, validated_data):
        # 将数据逐个修改
        for key in validated_data:
            # 放射   对象      属性    值
            setattr(instance, key, validated_data.get(key))

        # 保存, Django ORM的save()
        instance.save()
        # 修改之后将数据返回
        return instance

    # 定义一个局部钩子, 判断修改的价格不能超过300.00
    def validate_price(self, data):
        print(data, type(data))
        # 满足条件就将值返回
        if float(data) < 300.00:
            return data
        # 抛出异常, 使用rest_framework的  ValidationError 验证错误封装异常提示信息
        else:
            return ValidationError('价格过高')
* 2. 在Postman中测试: 
	 请求方式: put
	 请求地址: 127.0.0.1:8000/api/books/v1/1/
	 请求格式: raw-JSON
	 请求数据:
{   
    "id": "1",
    "title": "穿越诸天",
    "price": "301.00",
    "author": "aaa"
}

image-20220415145106825

1.7 全局钩子

校验的顺序, 等所有的字段检验了参数规则和局部钩子之后, 得到了所有校验合格的数据, 才执行全局钩子.
字段多了, 少了, 没有经过检验的都会被排除.
检验书名不能与作者社重名
* 2. 在序列化类中写全局钩子函数, 全局钩子的函数名 validdate
	 必须接收一个validdate_tate参数, 这个参数就是, 经过第一层serializers.xx(x)校验后所有合格的数据.
    # 定义全局钩子
    def validate(self, validate_data):
        print(validate_data, type(validate_data))  # 有序字典

        # 取值
        title = validate_data.get('title')
        author = validate_data.get('author')

        # 满足条件就将值返回
        if author != title:
            return validate_data
        # 抛出异常, 使用rest_framework的  ValidationError 验证错误封装异常提示信息
        else:
            raise ValidationError('书名与作者名重名!')
* 2. 在Postman中测试: 
	 请求方式: put
	 请求地址: 127.0.0.1:8000/api/books/v1/1/
	 请求格式: raw-JSON
	 请求数据:
{   
    "id": "1",
    "title": "穿越诸天",
    "price": "30.00",
    "author": "穿越诸天"
}
 第一层数据检验之后合格之后被封装成一个有序字典存, 在定义全局钩子的时候, 会被作为一个参数进行传递.
 OrderedDict([
 ('id', '1'), 
 ('title', '穿越诸天'),
 ('price', '30.00'),
 ('author', 'aaa')]) 
 <class 'collections.OrderedDict'>

image-20220415150731201

1.8 valifaters参数检验

使用使用字段的validators指定一个校验函数, xxx.CharField(validators=[函数名1, 函数名2])
可以通过函数的形式去检验数据, 使用和局部钩子一样, 写成函数形式, 不多见.
定义多个序列化器类的时候, 校验多个表中的字段时使用.
可以指定多个检验的函数.
# 导入序列化器
from rest_framework import serializers  # 导入serializers组件
# 导入序列化中的串行器
from rest_framework.serializers import Serializer  # Serializer是一个类

# 导入抛异常模板
from rest_framework.exceptions import ValidationError


def check_keyword(data):
    # 不能以xx开头
    if data.startswith('xx'):
        raise ValidationError('作者名称不能以xx开头!')
    else:
        return data
# 定义序列化组件的类, 继承串行器
class BookSerializer(Serializer):
    # 定义需要序列化的字段
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField()
    # 书名
    title = serializers.CharField(min_length=3, max_length=5)
    # 价格 浮点型的值也转为字符串
    price = serializers.CharField()
    # 作者
    author = serializers.CharField(validators=[check_keyword])
    
    # 定义update方法  book的数据对象  检验后的数据-->字典
    def update(self, instance, validated_data):
        # 将数据逐个修改
        for key in validated_data:
            # 放射   对象      属性    值
            setattr(instance, key, validated_data.get(key))

        # 保存, Django ORM的save()
        instance.save()
        # 修改之后将数据返回
        return instance

image-20220415154315523

1.8 通用参数

参数名称说明
read_only表明该字段仅用于序列化数据, 默认为False
write_only表明该字段仅用于反序列化数据, 默认为False
required表明该字段在反序列化时必须输入, 默认为True,
default反序列化时使用设置的默认值
allow_null表明该字段是否允许传入None, 默认为False
validators该字段可以指定使用的验证器, [func]
error_messages包含错误编号与错误信息的字典
在序列化字段的时候, 读写都是同一个序列化类, 这样就会出现某个字段在读的时候需要, 某写字段在写的时候需要.
read_only=True   在查询到数据序列化成字典的时候, 该字段会展示, 在传数据的时候不需要写该字段的值.
write_only=True  在写入数据反序列化的成模型对象的时候, 该字段需要传值, 在查询该数据的时候不会展示.
class BookSerializer(Serializer):
    # 定义需要序列化的字段
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField(read_only=True)  # 只查不传
    ...

image-20220415161506903

class BookSerializer(Serializer):
    # 定义需要序列化的字段
    # 主键   括号校验 在反序列化 字典转模型的时候会触发
    id = serializers.CharField(write_only=True)  # 只传不查
    ...

image-20220415161602555

1.9 查询所有数据

查询所有可以新建一个路由, 也可以在原来的get请求上, 对pk的值进行判断,
有值则是单条数据查询, 没值则是查询全部.
多个数据序列化的时候序列化类(需要设置many=True)
    # 查询所有表数据
    url(r'^api/books/v1/$', views.BooksView.as_view()),
class BooksView(APIView):
    def get(self, request):
        # 获取所有数据
        all_books_obj = Book.objects.all()

        # 序列化成字段
        all_books = BookSerializer(instance=all_books_obj, many=True)
        back_dic = {
            'code': 200,
            'msg': '查询成功!',
            'book_obj': all_books.data
        }
        return Response(back_dic)

1.10 添加数据

    # 查询所有表数据/新增表数据
    url(r'^api/books/v1/$', views.BooksView.as_view()),
添加数据是没有数据对象的值, 只需要将request.data的值创建即可, 需要使用关键的形式传递.
序列化类的第一个位置形参是instance, 如果以位置参数进行传递就出错了.
    # post请求新增数据
    def post(self, request):
        # 将request.data 的数据传递给序列化类, 以关键字的形式传
        book_obj = BookSerializer(data=request.data)
        # 检验数据
        if book_obj.is_valid():
            # 校验成功, 将数据保存
            book_obj.save()
            back_dic = {
                'code': 201,
                'msg': '创建成功!',
                'book_obj': book_obj.data
            }

        # 数据校验不成功
        else:
            back_dic = {
                'code': 101,
                'msg': f'数据检验不通过{book_obj.errors}'
            }

        return Response(back_dic)

2022-04-15_00153

序列化器的对象, 自己是没有save方法的, 父类中定义了该方法, 它判断当前需要创建数据就会调用create方法
create方法利用Python面向对象的多态特性, 在父类中限制子类必须有某个方法, 实现某个功能.
在序列化类定义create方法.
每个表的数据都是不一样的, 写这个模板的作者刚脆不提供创建数据到数据库的方法, 
在父类中限制该方法必须实现, 让使用者自己写create, 创建数据到数据库.
# 导入模型层的表
from app01.models import Book

# 接收校验之后的数据
def create(self, validated_data):
    # 创建数据到数据库
    instance = Book.objects.create(**validated_data)
    # 将数据返回, 封装序列化对象.data方法需要这个参数, 不然.data就没有值了, 不返回会抛出异常
    # AssertionError: `create()` did not return an object instance.
    return instance
* 2. 在Postman中测试: 
	 请求方式: put
	 请求地址: 127.0.0.1:8000/api/books/v1/
	 请求格式: raw-JSON
	 请求数据: (id设置为只读模式, 在写入数据的时候, 该字段不要参数)
{   
    "title": "这本书很酷",
    "price": "18.00",
    "author": "NN",
    "publish": "起点大说网"
}

image-20220415203458942

1.11 删除

删除在有名分组获取主键的的视图类中写,
删除数据之间过滤数据删除即可, 不需要使用序列化器, 不需要返回什么数据, 返回响应信息即可.
    # Book视图类的路由, 路由中有api字眼, 路由即资源 名称使用名字 推荐是复数 携带版本号 定义一个有名分组
    url(r'^api/books/v1/(?P<pk>\d+)/', views.BookView.as_view()),
# 接收request对象 与有名分组匹配到的主键值
def delete(self, request, pk):
    # 过滤删除, 删除数据会返回一个影响的行数
    num = Book.objects.filter(pk=pk).delete()
    print(num)  # (1, {'app01.Book': 1})
    # 返回响应状态码
    if num[0]:
        back_dic = {
            'code': 100,
            'msg': '删除成功'
        }
    else:
        back_dic = {
            'code': 101,
            'msg': '需要删除的数据不存在!'
        }
        return Response(back_dic)

image-20220415204506529

再次删除

image-20220415204811943

1.12 封装响应状态信息

每个接口都需要返回响应信息, 可以加响应信息利用类来实现.
* 1. 在app001下创建一个request_msg.py
* 2. 写一个MyResponse类
class MyResponse():
    def __init__(self):
        self.code = 200
        self.msg = '成功'
        
    @property
    def get_dict(self):
        return self.__dict__
* 3. 在接口中调用MyResponse生成响应信息
	 如果有数据对象需要返回, 对象.book_obj = 序列化对象.data 即可
# 接收request对象 与有名分组匹配到的主键值
def delete(self, request, pk):
    # 过滤删除, 删除数据会返回一个影响的行数
    num = Book.objects.filter(pk=pk).delete()
    print(num)
    # 返回响应状态码
    if num[0]:
        # 生成对象
        res = MyResponse()
        # 获取所有数型的数据 是一个字段格式数据
        back_dic = res.get_dict
    else:
        # 生成对象
        res = MyResponse()
        # 修改数据值
        res.code = 101
        res.msg = '需要删除的数据不存在!'
        # 获取所有数型的数据 是一个字段格式数据
        back_dic = res.get_dict
    return Response(back_dic)

2. 模型序列化组件

模型序列化组件可以省去定义字段的转换的过程. 模型序列化器需要继承ModelSerializer类.
它的使用方法与序列化器是一样的. 
* 模型序列化组件对应上了模型表, create与update方法就不需要自己写了.
# 导入模型序列化类
from rest_framework.serializers import ModelSerializer
模型类中必须定义Meta类, 在Meta类中必须定义field属性或exclude属性, 两者只能出现一个. 
field = '__all__' 所有字段都转换
field = ['字段1', '字段2', ...] 指定转换的字段
exclude = ['字段1', '字段2', ...]  排除指定字段
extra_kwargs = {'字段1': {'read_only': True}} 为字段添加条件, 设置id字段默认开启了只读!
局部钩子和全局钩子直接后面添加即可, 使用方法与序列化器时一模一样的.
	    class Meta:
	    	model = xxx
	    # 定义局部钩子
	    ...
	    # 定义全局钩子
	    ...
* 1. 定义一个模型序列化类
# 导入模型序列化类
from rest_framework.serializers import ModelSerializer


# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):
    # 定义元类
    # 必有有 Meta类 AssertionError: Class BookModelsSerializer missing "Meta" attribute
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book
        # 必须有modes属性 AssertionError: Class BookModelsSerializer missing "Meta.model" attribute
        # 指定需要转换的字段  '__all__' 表示所有字段
        fields = '__all__'

* 2. 新建一个路由, 查询所有的数据
    # 模型序列化器, 查询所有数据
    url(r'api/books2/v1/', views.Book2View.as_view())
* 3. 定义视图类中, 在请求的方法中使用模型序列化器.
class Book2View(APIView):
    # 查询所有数据, 没有主键
    def get(self, request):
        # 查询表中所有的数据
        all_book_obj = Book.objects.all()
        # 导入模型序列类, 使用模型序列化器, 将数据作为参数进行传递
        from app01.res import BookModelsSerializer
        book_dic = BookModelsSerializer(instance=all_book_obj)
        
        print(book_dic)
        # 返回响应信息
        res = MyResponse()
        # 添加属性
        back_dic = res.get_dict
        # 所有的属性通过__dict__转为字段
        back_dic.book_obj = book_dic.data
        
        # 返回
        return Response(back_dic)
* 5. 在Postman中测试: 
	 请求方式: get
	 请求地址: 127.0.0.1:8000/api/books2/v1/

image-20220415222058914

* 6. 指定字段转换
# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):
    # 定义元类
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book

        # 指定需要转换的字段  
        fields = ['id', 'title']

image-20220415223230399

* 7. 排除字段
# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):
    # 定义元类
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book
        # 排除不需要转换的字段  
        exclude = ['id', 'title']

image-20220415224345546

* 8. 给字段添加限制条件
	 老版本是可以 write_only_fields = ['字段1', '字段2'] 现在已经被禁用了, 设置了不生效
id字段默认是开启了只读, 就是在传给数据的时候id字段不需要传递参数.
AssertionError: 不能同时设置 `read_only`  `write_only`
# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):
    # 定义元类
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book

        # 指定需要转换的字段  '__all__' 表示所有字段
        fields = '__all__'

        # 添加限制条件, 只写, 不读
        extra_kwargs = {
            'title': {'write_only': True},
            'price': {'write_only': True}
        }

image-20220415225447127

* 9. 混合使用
# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):

    # 在这里定义的字段会将下面的覆盖后面的设置
    title = serializers.CharField(write_only=True)
    # 定义元类
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book
        fields = '__all__'
        extra_kwargs = {
            'title': {'write_only': False}}  # 关闭失败

image-20220416143344049

3. many关键字参数分析

序列化组件的many关键字参数分析
* 1. 修改视图类, 分类查单条数据和所有数据
class Book2View(APIView):
    # 查询所有数据, 没有主键
    def get(self, request):
        # 查询表中所有的数据
        all_book_obj = Book.objects.all()

        # 导入模型序列类, 使用模型序列化器, 将数据作为参数进行传递
        from app01.res import BookModelsSerializer
        all_dic = BookModelsSerializer(instance=all_book_obj, many=True)

        # 查询单条数据
        only_obj = Book.objects.first()
        only_dic = BookModelsSerializer(instance=only_obj)
        print(type(all_dic), type(only_dic))
        # <class 'rest_framework.serializers.ListSerializer'> <class 'app01.res.BookModelsSerializer'>

        # 返回响应信息
        res = MyResponse()
        res.book_obj = all_dic.data
        back_dic = res.get_dict

        return Response(back_dic)

mant=True  <class 'rest_framework.serializers.ListSerializer'> 
mant=False <class 'app01.res.BookModelsSerializer'>
类名加()
1. 先调用__net__方法, 生成空对象,
2. 在调用__init__方法, 实例化为空对象添加属性
* 类的__net__方法控制了对象的生成
追溯__net__,  
BaseSerializer有__new__
Serializer(BaseSerializer) 
ModelsSerializer(Serializer)
BookModelsSerializer(ModelsSerializer)

2022-04-15_00158

    # cls 是当前所在位置的类
    def __new__(cls, *args, **kwargs):
    # We override this method in order to automagically create
        # `ListSerializer` classes instead when `many=True` is set.
        # 从关键字总取many的值
        if kwargs.pop('many', False):
            # 如果many为True 则走当前类的绑定方法many_init
            return cls.many_init(*args, **kwargs)
        # 没有传many为True的, 正常序列化得到 app01.res.BookModelsSerializer 
        return super().__new__(cls, *args, **kwargs)

2022-04-16_00159

4. Serializer高级用法

4.1 创建环境

* 1. 创建app02
 python manage.py startapp app02
* 2. 注册app02
    INSTALLED_APPS = [
    ...
    'app02.apps.App02Config']
* 3. 路由分发
# 项目名文件夹下urls.py 总路由
from django.conf.urls import url
from django.contrib import admin
# 导入视图层
from app01 import views

# 导入include
from django.conf.urls import include
from app02 import urls
urlpatterns = [
	...
    # app02的路由
    url(r'^api/app2/(?P<pk>\d+)/', include(urls))
]

* 4. app02项目下创建urls.py
from django.shortcuts import render

# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response


class App2Book(APIView):
    # get方法
    def get(self, request, pk):
        return Response({'code': 200})
* 5 . 创建表模型
from django.db import models


# Create your models here.

# 0. 书籍表
class Book(models.Model):
    # 0.1 id 自动创建
    # 0.2 书名
    title = models.CharField(max_length=32, verbose_name='书名')
    # 0.3 价格
    price = models.CharField(max_length=32, verbose_name='价格')
    # 0.4 出版时间
    pub_date = models.DateField()
    # 0.5 外键字段 on_delete 级联删除
    publish = models.ForeignKey('Publish', on_delete=models.CASCADE,
                                null=True, verbose_name='关联出版社表')
    # 0.6 外键字段
    author = models.ManyToManyField('Author', verbose_name='关联作者表')

    def __str__(self):
        return self.title


# 1.定义出版社
class Publish(models.Model):
    # 1.0 id字段自动增加
    # 1.1 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字')
    # 1.2 邮箱
    email = models.EmailField(verbose_name='出版社邮箱')

    def __str__(self):
        return self.name


# 2. 作者表
class Author(models.Model):
    # 2.1 id自动增加
    # 2.2 作者名字
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 2.3 作者邮箱
    email = models.EmailField(verbose_name='作者邮箱')
* 6. 生成表
    生成表记录 python3.6 manage.py makemigrations app02
    数据库迁移 python3.6 manage.py migrate app02       
* 7. 在app02的texts.py中为表条件数据
from django.test import TestCase

# Create your tests here.
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DRF1_serializers.settings")
    import django

    django.setup()

    # 0. 导入模型层
    from app02 import models

    # 1. 出版社表数据
    models.Publish.objects.create(name='起点小说网', email='110@qq.com')
    models.Publish.objects.create(name='飞卢小说网', email='120@qq.com')

    # 2. 作者表数据
    models.Author.objects.create(name='kid', email='130@qq.com')
    models.Author.objects.create(name='qq', email='140@qq.com')

    # 3. 书籍表数据
    import datetime

    date = datetime.date(2021, 4, 2)
    models.Book.objects.create(title='开局签到荒古圣体', price=123, pub_date=date, publish_id=1)
    models.Book.objects.create(title='开局签到荒古圣体', price=123, pub_date=date, publish_id=2)

    # 书籍表与作者的第三张表
    book1_obj = models.Book.objects.filter(pk=1).first()
    # 第一本书绑定两个作者
    book1_obj.author.add(1, 2)

    book2_obj = models.Book.objects.filter(pk=2).first()
    # 第二本书绑定一个作者
    book1_obj.author.add(1)

2022-04-16_00160

* 8. 创建序列化器
# 0. 导入序列化组件模板
from rest_framework import serializers
# 1. 导入序列化类
from rest_framework.serializers import Serializer


# 2. 定义序列化器
class BookSerializer(Serializer):
    # 写需要转换的字段
    id = serializers.CharField()
    title = serializers.CharField()
    price = serializers.CharField()
    pub_date = serializers.CharField()
    # 外键字段, 触发了出版社的__str__方法
    publish = serializers.CharField()
* 9. 使用序列化器
from django.shortcuts import render

# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response

# 导入自定义的序列化类
from app02.res import BookSerializer
# 导入模型层
from app02 import models
# 导入响应信息
from app01.request_msg import MyResponse


class App2Book(APIView):
    # get方法 查询pk对应的书籍数据
    def get(self, request, pk):
        # 通过主键查询数据
        book_obj = models.Book.objects.filter(pk=pk).first()
        print(book_obj)
        # 序列化模型数据
        book_dic = BookSerializer(instance=book_obj)

        # 返回信息
        res = MyResponse()
        res.book_obj = book_dic.data
        back_dic = res.get_dict
        print(back_dic)
        return Response(back_dic)
* 10. 在Postman中测试: 
	  请求方式: get
	  请求地址: 127.0.0.1:8000/api/app2/books/v1/1/

image-20220416114053947

转换Publish外键字段触发了Publisk的__str__方法

4.2 source字段

source字段作用:
1. 起别名: 返回的数据key值与数据库的不一致.
2. 跨表查询: 通过外键字段查询关联表的数据
3. 使用模型表的方法: 使用表模型的方法, 会自动加括号执行
自己返回的字段名不能和数据的字段名一样, 这样很容易被被人攻击获取到数据.
需要一种方式来处理, 在字段类型后面的括号中设置source属性, 
eg: 别名 = serializer.CharField(source='数据库存在的字段名')
# 修改序列化类中的id字段名
uid = serializers.CharField(source='id')

image-20220416115748616

跨表查询, eg: pbulsh = serializers.CharField(source='外键字段.关联表的字段')
# 查询关联表的邮箱信息
publish = serializers.CharField(source='publish.email')

image-20220416121048659

在模型层的Book表中定义一个方法
# 0. 书籍表
class Book(models.Model):
	...

    # 定义一个方法
    def get_title(self):
        return f'<<{self.title}>>'
# 自动加括号调用
title = serializers.CharField(source='get_title')

image-20220416124854359

4.3 SerializerMethodField

SerializerMethodField的使用配套一个方法, 在该方法定义返回的数据.
eg: 别名 = serializers.SerializerMethodField(source='外键字段/虚拟字段')
绑定方法名字: get_字段名(self, instance接收数据对象), 字段名可以是别名.
返回的数据: return 该字段展示的信息.
# 0. 导入序列化组件模板
from rest_framework import serializers
# 1. 导入序列化类
from rest_framework.serializers import Serializer


# 2. 定义序列化器
class BookSerializer(Serializer):
    # 写需要转换的字段
    uid = serializers.CharField(source='id')
    title = serializers.CharField()
    price = serializers.CharField()
    pub_date = serializers.CharField()

    # 自定义放回数据 SerializerMethodField 绑定get_字段方法
    author_msg = serializers.SerializerMethodField(source='author')

    # get_字段名, 接收instance, 当前表的对象, instance是在是序列化器时传入的.
    def get_author_msg(self, instance):
        # instance == 数据对象
        # 查询书籍所有的作者对象
        all_author_obj = instance.author.all()

        # 封装成一个列表套字段的格式
        l1 = []
        for author_obj in all_author_obj:
            l1.append({'作者名字': author_obj.name, '作者邮箱:': author_obj.email})

            # 返回什么, 获取的数据就是什么
        return l1

image-20220416122853794

4.4 模型序列化中使用

修改app01中res.py的模型序列化类.
单独对字段经常操作, 在上面定义字段之后, 将Meta中的字段覆盖.
# 定义一个模型类序列化器
class BookModelsSerializer(ModelSerializer):
    # 定义元类
	# 覆盖下面的设置 title字段的设置
    title1 = serializers.CharField(source='author')
    class Meta:
        # 指定序列化的模型表, models.py 的变
        model = Book
        fields = '__all__'

image-20220416141547346

5. 练习

* 1.新建项目编写查增改删四个接口.
* 2. 表模型 & 创建表
# 书籍表
class Book(models.Model):
    # 1. id
    id = models.AutoField(primary_key=True, verbose_name='主键')
    # 2. 书名
    title = models.CharField(max_length=32, verbose_name='书名')
    # 3. 作者
    author = models.CharField(max_length=32, verbose_name='作者')
 python3.6 manage.py makemigrations
 python3.6 manage.py migrate   

5.1 路由层

, , 查单个数据都需要一个携带一个主键, 查全部,增不需要.
,查全部使用一个路由, 删改查单条数据使用一个路由.
from django.conf.urls import url
from django.contrib import admin

from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 增,查全部
    url(r'^api/books/v1/$', views.BookAPI1.as_view()),
    # 删改查单条数据
    url(r'^api/books/v1/(?P<pk>\d+)/', views.BookAPI2.as_view()),
]

5.2定义响应信息类

在app01下创建一个 response_msg.py文件
# 定义响应信息
class BackMsg(object):
    def __init__(self, code, msg, data):
        self.code = code
        self.msg = msg
        self.data = data

    @property
    def get_data(self):
        return self.__dict__

5.3 视图层

from django.shortcuts import render

# Create your views here.

# 导入 rest_framework
from rest_framework.views import APIView
# 导入 响应函数
from rest_framework.response import Response
# 导入 响应信息
from app01.response_msg import BackMsg


# 增, 查全部
class BookAPI1(APIView):
    # 查全部
    def get(self, request):
        back_msg = BackMsg(200, '查询成功', None)
        return Response(back_msg.get_data)

    # 增
    def post(self, request):
        back_msg = BackMsg(200, '增加成功', None)
        return Response(back_msg.get_data)


# 改, 查, 查单个
class BookAPI2(APIView):
    # 查单个
    def get(self, request, pk):
        back_msg = BackMsg(200, '查询成功', None)
        return Response(back_msg.get_data)

    # 改
    def put(self, request, pk):
        back_msg = BackMsg(200, '修改成功', None)
        return Response(back_msg.get_data)

    # 删
    def delete(self, request, pk):
        back_msg = BackMsg(200, '删除成功', None)
        return Response(back_msg.get_data)
127.0.0.1:8000/api/books/v1/

5.4 定义序列化器

在app01下创建一个 my_serialize.py文件
# 导入序列化器组件
from rest_framework import serializers


# 定义序列化器
class BookSerializer(serializers.Serializer):
    # 定义转换的字段
    book_id = serializers.IntegerField(source='id')
    # 书名
    book_title = serializers.CharField(source='title')
    # 作者
    author_name = serializers.CharField(source='author')

5.5 增加数据

获取用户提交的数据, 将用户提交的数据反序列化检验数据.
0. 数据不能为空, 默认的
1. 局部钩子函数检验 已经存在的书籍不能添加. 
1. 全局钩子函数检验 书名不能与作者名一样. 
局部钩子的名字以别名为主, 
全局函数的接收检验合格是数据是一个有序字段, 字段的键还是表的字段. 
	OrderedDict([('字段名1', '值'), ('字段名2', '字段')])
在写creat方法与update方法, 检验合格是数据一个普通的字典
	{'字段名1': '值', '字段名2': '值'}
最后返回数据的时候序列化器对象的.data数据就是一个普通的字典.
	{'字段名1': '值', '字段名2': '值'}
如果是创建值, 或修改值, 是需要返回数据对象的, 这个值被列化器对象的.data接收,
在没有写这两个方法且没有返回数据对象会报错, .save之前不能使用.data

使用序列化器时:
序列化类(data=request.data) 会触发校验. 必须执行is_vaild()方法, 
在使用.save()会触发父类规范子类的行为, 序列化类中必须实现create方法.

序列化类(instance=obj, data=request.data) 会触发校验. 必须执行is_vaild()方法, 
在使用.save()会触发父类规范子类的行为, 序列化类中必须实现update方法.
* 1. 视图函数
# 增, 查全部
class BookAPI1(APIView):
	...
    # 增
    def post(self, request):
        # 使用序列化器校验数据
        post_dic = BookSerializer(data=request.data)

        # 判断提交数据是否合法
        if post_dic.is_valid():
            # 保存用户的数据到数据库, 需要自己在序列化器中定义create方法.
            post_dic.save()

            # 返回正常的响应数据
            back_msg = BackMsg(200, '增加成功', post_dic.data)
        else:
            # 返回错误的响应数据
            error_msg = post_dic.errors
            back_msg = BackMsg(100, error_msg, None)

        return Response(back_msg.get_data)
# 导入序列化器组件
from rest_framework import serializers
# 导入异常信息模块
from rest_framework.exceptions import ValidationError
# 导入模型层
from app01 import models


# 定义序列化器
class BookSerializer(serializers.Serializer):
    # 定义转换的字段
    book_id = serializers.IntegerField(source='id', read_only=True)
    # 书名
    book_title = serializers.CharField(source='title')
    # 作者
    author_name = serializers.CharField(source='author')

    # 局部钩子, 校验书名是否存在(validate_别名为主)
    def validate_book_title(self, data):
        book_obj = models.Book.objects.filter(title=data).first()
        # 书名存在
        if book_obj:
            raise ValidationError('图书已经存在!')
        return data

    # 全局钩子, 校验书名是否与作者名一样
    def validate(self, validate_data):
        print(validate_data)  # OrderedDict([('title', '小说2'), ('author', '作者2')])
        # 取出值做检验, 值还是字段中的值, 不是别名
        title = validate_data.get('title')
        author = validate_data.get('author')
        if title != author:
            return validate_data

        # 抛出异常, 使用rest_framework的  ValidationError 验证错误封装异常提示信息
        else:
            raise ValidationError('书名与作者名同名')

    # 定义create方法, 将创建成功数据的数据返回
    def create(self, instance):

        print(instance)  # {'title': '小说2', 'author': '作者2'}
        book_obj = models.Book.objects.create(**instance)
        return book_obj
在Postman中测试: 
请求方式: POST
请求地址: 127.0.0.1:8000/api/books/v1/
数据格式: raw-JSON
提交数据:
{
    "book_title": "小说2",
    "author_name": "作者2"
}

2022-04-16_00169

重复提交

image-20220416203012897

5.6 查询数据

查所有
# 导入 模型层
from app01.models import Book

# 增, 查全部
class BookAPI1(APIView):
    # 查全部
    def get(self, request):
        # 查询所有的数据
        all_obj = Book.objects.all()

        # 判断是否有值
        if all_obj:
            # 使用序列化器
            get_dic = BookSerializer(instance=all_obj, many=True)
            back_msg = BackMsg(200, '查询成功', get_dic.data)
        else:
            back_msg = BackMsg(200, '查询成功', '没有值')
        return Response(back_msg.get_data)
在Postman中测试: 
请求方式: GET
请求地址: 127.0.0.1:8000/api/books/v1/

image-20220416205458711

单独查
# 改, 查, 查单个
class BookAPI2(APIView):
    # 查单个
    def get(self, request, pk):
        # 通过pk查询对应的数据值
        book_obj = Book.objects.filter(pk=pk).first()

        # 判断是否为空
        if book_obj:
            # 使用序列化
            book_dic = BookSerializer(instance=book_obj)

            back_msg = BackMsg(200, '查询成功', book_dic.data)
        else:
            back_msg = BackMsg(101, '数据不存在', None)
        return Response(back_msg.get_data)
在Postman中测试: 
请求方式: GET
请求地址: 127.0.0.1:8000/api/books/v1/1/

image-20220416211053669

查询不存在的书籍

image-20220416211032347

5.7 修改数据

    # 改
    def put(self, request, pk):
        # 通过主键获取数据对象
        book_obj = Book.objects.filter(pk=pk).first()
        # 判断是否为空
        if book_obj:
            # 使用序列化器
            book_dic = BookSerializer(instance=book_obj, data=request.data)
            # 判断检验是否合格
            if book_dic.is_valid():
                book_dic.save()
                back_msg = BackMsg(200, '修改成功', book_dic.data)
            # 数据不合法
            else:
                back_msg = BackMsg(100, book_dic.errors, None)
        else:
            back_msg = BackMsg(100, '修改的数据不存在!', None)
        return Response(back_msg.get_data)
def update(self, instance, validated_data):
    # 修改对象的值, data校验之后的值
    print(instance, validated_data)
    # 修改属性
    for key, value in validated_data.items():
        setattr(instance, key, value)
    # 保存
    instance.save()
    return instance
在Postman中测试: 
请求方式: put
请求地址: 127.0.0.1:8000/api/books/v1/5/
数据格式: raw-JSON
提交数据:
{
    "book_title": "开局签到荒古圣体",
    "author_name": "kid"
}

image-20220416213720677

请求地址: 127.0.0.1:8000/api/books/v1/100/

image-20220416214008542

5.8 删除数据

# 删
def delete(self, request, pk):
    # 直接删除数据
    del_num = Book.objects.filter(pk=pk).delete()
    # 判断是都删除了值
    if del_num[0]:
        back_msg = BackMsg(200, '删除成功', None)
    else:
        back_msg = BackMsg(100, '删除的值不存在!', None)
    return Response(back_msg.get_data)
在Postman中测试: 
请求方式: delete
请求地址: 127.0.0.1:8000/api/books/v1/1/

image-20220416214400304

删除不存在的值

image-20220416214420429

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:36:48  更:2022-04-18 17:38:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 17:43:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码