5. DRF 图书管理增删改查&分页器&多序列化器

1. 图书管理

增删改查四个接口, 使用分页器对数据进行分页返回.

1.1 准备环境

* 1. 新建项目, 不勾选模块层.

image-20220422201504688

* 2. 将rest_farmework添加到app应用列表中.
INSTALLED_APPS = [
	...
    'rest_framework',
]
* 3. 自定义响应模块
     1. 正常响应
     2. dispatch异常响应
# 1. 继承Response自定响应信息
from rest_framework.response import Response
from rest_framework import status


class CustomResponse(Response):
    def __init__(self, code=200, msg='访问成功!', data=None, status=status.HTTP_200_OK, **kwargs):
        back_info_dict = {
    
    'code': code, 'msg': msg}
        if data:
            back_info_dict.update(data=data)

        back_info_dict.update(kwargs)

        # 调用父类的方法生成对象
        super().__init__(data=back_info_dict, status=status)


# 2. 定义dispatch的异常的返回格式
from rest_framework.views import exception_handler


def exception_response(exc, context):
    # 执行drf的异常处理程序
    response = exception_handler(exc, context)

    # 返回值为空
    if not response:
        # 细分请求异常信息
        if isinstance(exc, Exception):
            return CustomResponse(501, f'访问不成功! {
      
      exc}', status=status.HTTP_501_NOT_IMPLEMENTED)

    # 三大认证异常
    return CustomResponse(501, f"访问不成功! {
      
      response.data}", status=status.HTTP_501_NOT_IMPLEMENTED)
* 4. 项目配置文件settings.py中, 全局配置dispatch异常响应.
     只能全局配置, 自定义的异常响应, 值就是一个字符串, 不能是列表['自定义的异常响应']
# DRF配置
REST_FRAMEWORK = {
    
    
    # dispatch异常响应, 值只能是一个字符串, 不要使用列表
    'EXCEPTION_HANDLER': 'utils.response.exception_response',
}
* 5. 测试路由, 路由分发模式
from django.conf.urls import url, include
from django.contrib import admin

# 导入子路由
from app01 import urls


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # books api
    url(r'^app01/api/', include(urls))
]

* 6. 子路由
from django.conf.urls import url, include
from django.contrib import admin

# 导入视图层
from app01 import views

urlpatterns = [
    # 主键有或没有
    url('^books/(?P<pk>(\d+)?)', views.BookAPI.as_view()),
]
* 7. BooksAPI视图
# BooksAPI视图
class BookAPI(GenericAPIView):
	...

1.2 表设计

外键字段参数:
on_delete: 删除关联数据与之关联的信息的行为
    当删除关联表中的数据数, 当前表与其关联表的行为.
    models.CASCADE 删除关联数据, 与之关联的信息也删除, 慎重使用.
    models.DOTHING 删除关联数据, 引发错误IntegerityError
    models.PROTECT 删除关联数据, 引发错误ProtectedError
    models.SET_NULL 删除关联数据, 与之关联的值设置为null, 前提条件: 该字段可为空.
    models.SET_DEFAULT 删除关联数据, 与之关联的值设置为默认值, 前提条件: 该字段可设置默认值.
    models.SET  删除关联数据,
        1. 与之关联的值设置为指定值, 设置: models.SET()
        2. 与之关联的值设置为可执行对象的放回值, 设置: models.SET(可执行对象)

db_constraint: 与外键建立的关系.
	db_constraint=True 建立真正的关联, 增删的操作受外键影响.
	db_constraint=False 建立逻辑关联, 增删的操作不受外键影响. ORM操作与之前一样.
	增删的操作.
表断关联
1. 表之间没有外键关联, 但是有外键逻辑关联(有充当外键的字段)
2. 断关联后不会影响数据库查询效率, 但是会极大提高数据库的增删改效率(不影响增删改查操作)
3. 段关联一定要通过编写逻辑代码保证表之间数据安全, 不要出现脏数据.
4. 级联关系:
	一对一, 作者没有, 作者详情页也跟随没有, on_delete=models.CASCADE
	一对多, 出版社没有了, 书还是存在, 出版的的id还在, on_delete=models.DO_NOTHING
	部门没了, 员工没有部分, on_delete=models.SET_NULLM, 该字段允许为空 null=true
	部门没了, 员工进入默认部门, on_delete=models.SET_DEFAULT, 该字段设置了默认值, default='z部门'
* 1. 基础表
删除数据不是真正的删除, 设置一个字段, 值为False代表数据被删除
创建数据时, 自动添加创建时间
修改数据时, 自动更新修改文件时间
from django.db import models


# Create your models here.

# 1. 继承表
class BaseModel(models.Model):
    # 删除标识
    is_delete = models.BooleanField(default=False)
    # 创建时间, auto_now_add=True 创建数据时, 自动添加当前时间
    create_time = models.DateTimeField(auto_now_add=True)
    # 修改数据, auto_now=True 修改文件时候, 自动添加当前时间
    last_update_time = models.DateTimeField(auto_now=True)

    # 定义Meta类
    class Meta:
        abstract = True  # 抽象类, 不在数据库中创建这张表
* 2. 书籍表
# 2. 书籍表
class Book(BaseModel):
    # 主键 verbose_name 详细名称, help_text 提示信息
    book_id = models.AutoField(primary_key=True, verbose_name='书籍主键', help_text='书籍主键')
    # 书名
    title = models.CharField(max_length=32, verbose_name='书名', help_text='书的名字')
    # 价格
    price = models.CharField(max_length=32, verbose_name='价格', help_text='书的价格')
    # 外键 出版社一对多书籍表, db_constraint=False, 不建立真正的关联
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE, db_constraint=False, verbose_name='外键,关联出版社')
    # 虚拟字段 书籍表多对多作者, 虚拟表不能设置on_delete
    author = models.ManyToManyField(to='Author', db_constraint=False, verbose_name='虚拟, 关联作者')

    # 定义Meta类
    class Meta:
        # 在后台管理显示的表名称
        verbose_name_plural = '书籍表'

    # 打印对象展示的信息
    def __str__(self):
        return f'{
      
      self.title}'
* 3. 出版社
# 3.出版社表
class Publish(BaseModel):
    # 主键
    publish_id = models.AutoField(primary_key=True, verbose_name='出版社主键')
    # 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字', help_text='出版社名字')
    # 邮箱
    email = models.EmailField(verbose_name='出版社邮箱')
    # 地址
    address = models.CharField(max_length=32, verbose_name='出版社地址')

    # 定义Meta类
    class Meta:
        # 在后台管理显示的表名称
        verbose_name_plural = '出版社表'

    # 打印对象展示的信息
    def __str__(self):
        return f'{
      
      self.name}'
* 4. 作者
# 4. 作者表
class Author(BaseModel):
    # 主键
    author_id = models.AutoField(primary_key=True, verbose_name='作者主键')
    # 名字
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 性别
    gender = models.IntegerField(choices=((1, '男'), (2, '女')), verbose_name='性别')
    # 外键
    detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE, db_constraint=False,
                                  verbose_name='外键, 关联详情')

    # 定义Meta类
    class Meta:
        # 在后台管理显示的表名称
        verbose_name_plural = '作者表'

    # 打印对象展示的信息
    def __str__(self):
        return f'{
      
      self.name}'
* 5. 作者详情
# 5. 作者详情表
class AuthorDetail(BaseModel):
    # 自动生成主键
    # 邮箱
    email = models.EmailField(verbose_name='作者邮箱')
    # 微信
    WeChat = models.CharField(max_length=32, verbose_name='微信')

    # 定义Meta类
    class Meta:
        # 在后台管理显示的表名称
        verbose_name_plural = '作者详情表'
        
    # 打印对象展示的信息
    def __str__(self):
        return f'邮箱: {
      
      self.email} - 微信: {
      
      self.WeChat}'
生成表记录, 数据库迁移
python3.6 manage.py makemigrations
python3.6 manage.py migrate     

1.3 注册表

使用admin的后台管理操作表, 在app应用的admin.py文件中注册表.
from django.contrib import admin

# Register your models here.
# 导入模型层
from app01 import models

# 注册表
admin.site.register(models.Book)
admin.site.register(models.Publish)
admin.site.register(models.Author)
admin.site.register(models.AuthorDetail)

1.4 创建超级用户

python3.6 manage.py createsuperuser
Username (leave blank to use '13600'):  root
Email address:  136@qq.com
Password:  zxc123456
Password (again):  zxc123456
Superuser created successfully.

1.5 登入后台

image-20220423001313914

* 1. 为作者详情添加表数据

image-20220423002117918

* 2. 为作者表添加数据

image-20220423002227738

image-20220423002214739

* 3. 为出版社添加数据

image-20220423002420043

image-20220423002452084

* 4. 为书籍表添加数据

image-20220423002605882

image-20220423002717870

1.6 数据查询

* 1. 模型序列化器
# 导入序列化器模块
from rest_framework import serializers
# 导入模型层
from app01 import models


# 图书序列化器
class BookModelSerializer(serializers.ModelSerializer):
    # 定义Meta类
    class Meta:
        # 关联的表模型
        model = models.Book
        # 序列化的字段
        fields = ('title', 'price', 'publish', 'author')
        # 跨记张表进行查询, 不推荐使用, 最大不推荐超过10
        depth = 2
* 2. BooksAPI 视图类
from django.shortcuts import render

# Create your views here.

# book api
from rest_framework.generics import GenericAPIView
from app01 import models
from utils import serializers
from utils.response import CustomResponse


class BookAPI(GenericAPIView):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer

    # 查
    def get(self, request, *args, **kwargs):
        # 判断是否有主键, 单数据查询
        pk = kwargs.get('pk')
        if pk:
            print(pk)
            book_obj = self.get_object()
            book_dict = self.get_serializer(instance=book_obj)

        # 查询所有数据
        else:
            book_obj = self.get_queryset().filter(is_delete=False)
            # 转换数据 queryset --> dict
            book_dict = self.get_serializer(instance=book_obj, many=True)

        return CustomResponse(data=book_dict.data)
* 3. 单条数据查询, depth = 2
get请求: 127.0.0.1:8000/app01/api/books/1/

2022-04-23_00312

* 4. 单条数据查询, depth = 0
get请求: 127.0.0.1:8000/app01/api/books/1/

2022-04-23_00313

* 5. 在序列化器中设置想要展示的值, 方式1:
     重写 publish字段的序列化, source = '表名.字段' 
# 图书序列化器
class BookModelSerializer(serializers.ModelSerializer):
    # 定义Meta类
    # 重写 publish字段的序列化, 在写入数据反序列化会有问题
    publish = serializers.CharField(source='publish.name')

    class Meta:
        # 关联的表模型
        model = models.Book
        # 序列化的字段
        fields = ('title', 'price', 'publish', 'author')
get请求: 127.0.0.1:8000/app01/api/books/1/

2022-04-23_00314

* 6. 在序列化器中设置想要展示的值, 方式2:
     在表模型类中添加静态方法, fields = ['静态方法', ]
    # 获取出版社的名字
    @property
    def publish_name(self):
        return self.publish.name

    # 获取作者的名字, 值可能有多个
    @property
    def author_name_list(self):
        authors = self.author.all()
        # [作者1, 作者2]
        return [author.name for author in authors]

image-20220423131854644

* 7. 设置原来的外键为只些模式, 读出来也没有用, 在写入数据时使用. 需要使用.
# 图书序列化器
class BookModelSerializer(serializers.ModelSerializer):
    # 定义Meta类
    # 重写 publish字段的序列化
    # publish = serializers.CharField(source='publish.name')

    class Meta:
        # 关联的表模型
        model = models.Book
        # 序列化的字段
        fields = ('title', 'price', 'publish', 'publish_name', 'author', 'author_name_list')
        # 跨记张表进行查询, 不推荐使用, 最大不推荐超过10
        depth = 0
        extra_kwargs = {
    
    
            # 原本的出版社外键 作者表虚拟字段 设置为只读
            'publish': {
    
    'write_only': True},
            'publish_name': {
    
    'read_only': True},
            'author': {
    
    'write_only': True},
            'author_name_list': {
    
    'read_only': True},
        }
get请求: 127.0.0.1:8000/app01/api/books/1/

image-20220423133244664

* 8. 查询所有数据
get请求: 127.0.0.1:8000/app01/api/books/

image-20220423153851287

1.7 新增数据

手动删除books表中的两条数据, 等下数据多了不好做实验.
*  1. 新增一条数据
post请求: 127.0.0.1:8000/app01/api/books/
携带的json数据:
{
    
    
    "title": "Django",
    "price": "123.00",
    "publish": 1,
    "author": [ 1, 2]
}

image-20220423185034068

* 2. 批量增加数据
    # 增, 单增, 多增
    def post(self, request, *args, **kwargs):
        # request.data 一条数据是字典, 多条数据是列表
        if isinstance(request.data, dict):
            # 调用模型序列化器
            book_obj = self.get_serializer(data=request.data)

        else:
            # 调用模型序列化器, 多个值
            book_obj = self.get_serializer(data=request.data, many=True)

        # 校验数据, raise_exception 数据不合格之后抛异常
        book_obj.is_valid(raise_exception=True)
        # 保存数据
        book_obj.save()

        # 返回新增的数据
        return CustomResponse(data=book_obj.data)
self.get_serializer(data=request.data,) 
对应的类型: <class 'utils.serializers.BookModelSerializer'>
    
    
self.get_serializer(data=request.data, many=True)
对应的类型: <class 'rest_framework.serializers.ListSerializer'>

from rest_framework.serializers import ListSerializer

2022-04-23_00323

从序列化对象中, 获取list_serializer_class 属性的值, 如果获取不到就是用ListSerializer

2022-04-23_00324

many不为True 使用的是 ModelSerializer的create()方法.
many为True ListSerializer 重复使用 ModelSerializer的create()方法创建数据.
ListSerializer 对象.create()

# 创建方法, 重复调用child的create方法将数据创建到数据库.
def create(self, validated_data):
    return [
        self.child.create(attrs) for attrs in validated_data
    ]

validated_data的数据:
[{
    
    'title': 'Python', 'price': '123.00',
  'publish': <Publish: 北京出版社>, 'author': [<Author: 北京出版社>]}, 
 {
    
    'title': 'Linux', 'price': '456.00', 
  'publish': <Publish: 上海出版社>, 'author': [<Author: 上海出版社>]}]
post请求: 127.0.0.1:8000/app01/api/books/
携带的json数据:
[{
    
    
    "title": "Python",
    "price": "123.00",
    "publish": 1,
    "author": [1]
},
{
    
    
    "title": "Linux",
    "price": "123.00",
    "publish": 2,
    "author": [2]
}]

image-20220423170451909

1.8 修改数据

* 1. 单条数据修改
    # 改, 单该
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        #
        if pk:
            book_obj = self.get_queryset().filter(pk=pk).first()
            book_dic = self.get_serializer(instance=book_obj, data=request.data)

            # 数据校验
            book_dic.is_valid(raise_exception=True)
            # 保存数据
            book_dic.save()

            return CustomResponse(data=book_dic.data)
put请求: 127.0.0.1:8000/app01/api/books/1/
携带的json数据:
{
    
    
    "title": "Linux123",
    "price": "456.00",
    "publish": 2,
    "author": [1, 2]
}

image-20220423185658330

* 2. 多条数据修改, 数据传入的格式是多条[{}, {}], 在使用序列化器之后many=True, 生成一个ListSerializer
     对象. 改对象中作者只写了create方法, update没有写, 我们可以继承ListSerializer, 重新该方法.

2022-04-23_00324

* 3. 继承ListSerializer重新update方法. 在自定义update方法中循环调用模型序列化类的update方法添加数据.
前端传递的数据需要携带主键.
# 导入序列化器模块
from rest_framework import serializers
# 导入模型层
from app01 import models


# 定义一个ListSerializer
class BookListSerializer(serializers.ListSerializer):
    # instance参数接收数据对象, data接收修改的数据, 
    def update(self, instance, validate_value):

        return [  # #          修改的对象 写入的数据
            self.child.update(instance[i], data) for i, data in enumerate(validate_value)
        ]


# 图书序列化器
class BookModelSerializer(serializers.ModelSerializer):
    # 定义Meta类
    # 重写 publish字段的序列化
    # publish = serializers.CharField(source='publish.name')

    class Meta:
        # 使用自定义的ListSerializer
        list_serializer_class = BookListSerializer
        # 关联的表模型
        model = models.Book
        # 序列化的字段
        fields = ('title', 'price', 'publish', 'publish_name', 'author', 'author_name_list')
        # 跨记张表进行查询, 不推荐使用, 最大不推荐超过10
        depth = 0
        extra_kwargs = {
    
    
            # 原本的出版社外键 作者表虚拟字段 设置为只读
            'publish': {
    
    'write_only': True},
            'publish_name': {
    
    'read_only': True},
            'author': {
    
    'write_only': True},
            'author_name_list': {
    
    'read_only': True},
        }
* 4. 批量修改代码
    # 改, 单该, 批量修改
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        # 单改
        if pk:
            # 获取修改的对象
            book_obj = self.get_queryset().filter(pk=pk).first()
            # 序列化处理
            book_dic = self.get_serializer(instance=book_obj, data=request.data)
        # 批量改
        else:
            # 一个数据对象列表, 一个提交的数据列表
            data_dict = []
            obj_list = []
            # 遍历提交的数据
            for items in request.data:
                # 获取book_id
                pk = items.pop('book_id')
                # 获取对应的数据对象
                book_obj = self.get_queryset().filter(pk=pk).first()
                # 将剔除了book_id的字典数据存储到列表中
                data_dict.append(items)
                # 将对应的数据对象条件到列表中
                obj_list.append(book_obj)

            # 多个数据序列化
            book_dic = self.get_serializer(instance=obj_list, data=data_dict, many=True)

        # 校验数据
        book_dic.is_valid(raise_exception=True)
        # 保存数据, get_serializer传递instance, data会 触发update方法
        """
        type(book_dic) 
        
        没有使用 many=True 序列化的是 模型序列化类. 
        调用轨迹 模型序列化类的父类的父类.save() 中self.update() self是 模型序列化类, 调用模型序列化类.update()方法
        
        使用 many=True 序列化的是 ListSerializer类
        调用轨迹 模型序列化类的父类的父类.save() 中self.update() self是 ListSerializer类, 调用ListSerializer类.update()方法
        ListSerializer类.update()方法 中 在遍历调用 调用模型序列化类.update()方法, 自己控制好传入的值就好了...
        """
        book_dic.save()

        return CustomResponse(data=book_dic.data)
put请求: 127.0.0.1:8000/app01/api/books/
携带的json格式数据:
[
    {
    
    
        "book_id": 1,
        "title": "小说1",
        "price": "111.00",
        "publish": 1,
        "author": [1]
    },
    {
    
    
        "book_id": 2,
        "title": "小说2",
        "price": "222.00",
        "publish": 2,
        "author": [2]
    }
]

image-20220423230158865

1.9 删除数据

修改数据对象的is_delete字段, 不是真正的删除数据...
    # 删 单除, 批量删除
    def delete(self, request, *args, **kwargs):
        # 定义一个列表, 存储需要删除的书籍主键
        del_list = []
        # 获取pk
        pk = kwargs.get('pk')
        if pk:
            del_list.append(pk)
        else:
            del_list = request.data.get('del_list')

        # 修改字段is_delete的值 __in [ , , ]  返回被影响的数目
        num = self.get_queryset().filter(pk__in=del_list, is_delete=False).update(is_delete=True)

        if num:
            return CustomResponse(data=f'删除了{
      
      num}条数据')

        return CustomResponse(data='没有删除数据, 删除的数据可能不存在, 或者已经被删除!')
* 1. 删除一条数据

image-20220423232023229

* 2. 批量删除数据

image-20220423232153779

1.10 局部修改

patch方法用来更新局部资源, 入数据有10个字段, 修改数据时自己只提供2个字段, 其他的字段信息都不提供.
put可以通通过修改一些参数来实现以上的功能, 就没必要在写patch请求方法了.
eg: 发送put请求, 序列化的字段就是必填的. 可以通过参数设置改字段不是必须填写的. 
	或者在使用模型序列化器时, 设置partial=True, 没有传递值的字段不是必填的.
put请求: 127.0.0.1:8000/app01/api/books/4/
携带的json数据:
{
    
    
    "title": "Django",
    "price": "123.00"
}

image-20220423234408563

# 单条数据修改, 添加partial参数
book_dic = self.get_serializer(instance=book_obj, data=request.data, partial=True)
再次访问, 实现局部修改...

image-20220423235118215

# 多条数据修改, 添加partial参数
book_dic = self.get_serializer(instance=obj_list, data=data_dict, many=True, partial=True)
put请求: 127.0.0.1:8000/app01/api/books/
携带的json数据:
[
{
    
       "book_id": 4,
    "title": "Django",
    "price": "123.00"
}]

image-20220423235511436

1.11 视图子类使用分页器

rest_framework 内置三种分页器
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
页码分页, 限制偏移分页, 光标分页
从视图子类开始不需要手定调用分类器的方法. 只需要配置好属性值即可使用.
在数据库中写入10条数据
post请求: 127.0.0.1:8000/app01/api/books/
携带的json数据:
[
    {
    
    
        "title": "小说1",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说2",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说3",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说4",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说5",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说6",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说7",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说8",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说9",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    },
    {
    
    
        "title": "小说10",
        "price": "123.00",
        "publish": 1,
        "author": [
            1
        ]
    }
]

image-20220424121645873

11.1 页码分页
DEFAULT_PAGINATION_CLASS 的默认值为无, 默认使用PageNumberPagination页面分页.
1. 全局配置
在项目配置文件中设置.
# DRF配置
REST_FRAMEWORK = {
    
    
	'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 每页展示的条数
    'PAGE_SIZE': 2,
}

image-20220424121745575

2. 局部配置
在需要设置页码的类中设置 pagination_class属性值.
# 导入ListAPIView
from rest_framework.generics import ListAPIView
# 带入分页器模块
from rest_framework.pagination import PageNumberPagination


# 定义一个ListAPIView
class BookListAPIView(ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer
    # 分页器配置
    pagination_class = PageNumberPagination
在项目配置文件中设置PAGE_SIZE属性.
# DRF配置
REST_FRAMEWORK = {
    
    
    # 每页展示的条数
    'PAGE_SIZE': 2,
}
3. 重新页码分页
继承PageNumberPagination类, 在该类中定义配置数据. 可以局部使用, 也可以全局使用.
参数:
page_size  每页的数目
page_query_param  页数关键字名, 默认为'page'
page_size_query_param  每页数目关键字, 默认为None
max_page_size  每页最大的显示条数
    # 视图子类分页器路由
    url('^books2/', views.BookListAPIView.as_view()),
# 定义一个ListAPIView
from rest_framework.generics import ListAPIView
from rest_framework.pagination import PageNumberPagination
class Pager(PageNumberPagination):
    # 每页的条数
    page_size = 2
    # 查询的key
    page_query_param = 'aa'
    # 指定一个关键字, 前端指定的关键字获取页多少条数据  page_size的值就会失效
    page_size_query_param = 'size'
    # 每页最大的显示条数
    max_page_size = 5


# 定义一个ListAPIView
class BookListAPIView(ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer
    # 分页器配置
    pagination_class = Pager
get请求: 127.0.0.1:8000/app01/api/books2/
每页的条数: page_size = 2

image-20220424142301868

get请求: 127.0.0.1:8000/app01/api/books2/?size=3
每页的条数: size=3 ==> page_size = 3

2022-04-24_00338

get请求: 127.0.0.1:8000/app01/api/books2/?size=10
限制每页最大的显示条数: max_page_size = 5

2022-04-24_00339

11.2 偏移分页
继承LimitOffsetPagination类, 在该类中定义配置数据. 可以局部使用, 也可以全局使用.
参数:
default_limit 每页的偏移数, 偏移一位获取一条数据. 
milit_query_pagam 指定每页偏移的关键字, 默认为limit.
offset_query_pagam 指定起始位的关键字, 默认为offset.
max_milit  最大偏移的位数

default_limit自己没有定义使用的配置文件的PAGE_SIZE.

from rest_framework.pagination import LimitOffsetPagination


class Offset(LimitOffsetPagination):
    # 每页显示3条数据
    default_limit = 3
    


# 定义一个ListAPIView
class BookListAPIView(ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer
    # 分页器配置
    pagination_class = Offset
    # 最偏移的位数
    max_limit = 5


第一页 127.0.0.1:8000/app01/api/books2/?limit=3
第二页 127.0.0.1:8000/app01/api/books2/?limit=3&offset=3  limit获取几条 offset 指定起始位

image-20220424145412807

127.0.0.1:8000/app01/api/books2/?limit=3&offset=5
从第五条的后面开始向后面获取3条数据

image-20220424150434451

127.0.0.1:8000/app01/api/books2/?limit=30&offset=1
limit=30
max_limit = 5 限制 limit最大的值为5

image-20220424150716564

11.3 光标分页
前两种方法可以指定位置开始查, 查的时候需要将数据都读取,效率低. 光标分页只能点击上下翻页, 查询效率最高.
继承CursorPagination类, 在该类中定义配置数据. 可以局部使用, 也可以全局使用.
参数:
page_size: 每页展示的数目
cursor_query_param: 页码的关键字参数, 默认为 'cursor'
ordering: 排序中的字段, 默认 ordering = '-created', 以创建时间排序,
	自己没有定义该属性会报错, 自己的表中没有改字段.
	 - 倒序, 在取值.
from rest_framework.pagination import CursorPagination


class Cursor(CursorPagination):
    cursor_query_param = 'page'
    page_size = 3
    ordering = '-book_id'



# 定义一个ListAPIView
class BookListAPIView(ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookModelSerializer
    # 分页器配置
    pagination_class = Cursor
get请求 127.0.0.1:8000/app01/api/books2/
ordering = '-book_id' 指定排序字段为book_id, -为倒序, 倒序之后取数据.

image-20220424152717401

只能通过 next previous 翻页

image-20220424152508995

1.12 视图基类使用分页器

从基础视图类需要手定调用分类器的方法. 使用方法按要求传递参数.

image-20220424155327585

    # 视图基类路由
    url('^books3/', views.BookAPIView.as_view()),
from rest_framework.pagination import PageNumberPagination


class Pager(PageNumberPagination):
    # 每页的条数
    page_size = 2
    # 查询的key
    page_query_param = 'aa'
    # 指定一个关键字, 前端指定的关键字获取页多少条数据  page_size的值就会失效
    page_size_query_param = 'size'
    # 每页最大的显示条数
    max_page_size = 5


# 定义一个APIView类
from rest_framework.views import APIView


class BookAPIView(APIView):
    def get(self, request):
        # 获取数据对象
        books_obj = models.Book.objects.all()
        # 分页器对象
        pager_obj = Pager()
        # 使用方法 paginate_queryset 分器, (书籍对象, request对象, 视图类对象)
        books_obj_list = pager_obj.paginate_queryset(books_obj, request, self)
        # 序列化模型对象
        books_dic = serializers.BookModelSerializer(instance=books_obj_list, many=True)

        # 获取上下页码
        next_link = pager_obj.get_next_link()
        previous_link = pager_obj.get_previous_link()
        # 将上下页码添加到页面中
        page_link = {
    
    'next_link': next_link, 'previous_link': previous_link}

        print(books_dic.data)
        # 将数据放回
        return CustomResponse(data=books_dic.data, page=page_link)
先对数据划分, 在对序列化数据. 上下页码通过自定义的Response组织放回.
get请求: 127.0.0.1:8000/app01/api/books3/?aa=2

image-20220424161631129

2. 多序列化练习

1. 继承AbstractUser自定义UserInfo表, 
   新增mobile_phone 手机号码字段, 唯一约束,
   新增icon用户头像字段, 设置默认头像.
2. 使用视图集GenericViewSet + 视图拓展类RetrieveModeMixin + 模型序列化器完成User表新增数据接口
   GenericViewSet: 继承ViewSetMixin和GenericAPIView
   CreateModelMixin: 创建数据模块, 内置create方法.
   ViewSetMixin: 重写的view函数内存地址, view函数中遍历actions字段, 为请求绑定方法.
3. 使用视图集GenericViewSet + 数图拓展类RetrieveModekMixin +  模型序列化器完成User表的查询接口
4. 使用视图集GenericViewSet + UpdateModelMixin + 模型序列化器完成User表修改头像的接口

2.1 准备环境

* 1. 新建项目

image-20220427171225071

* 2. 本地化设置
# DRF_test2/settings.py
# 中文简体
LANGUAGE_CODE = 'zh-hans'
# 时区 亚洲上海
TIME_ZONE = 'Asia/shanghai'

USE_I18N = True

USE_L10N = True
# UTC时间关闭
USE_TZ = False
* 3. 继承AbstractUser内置用户表, 创建UserInfo表模型
# api/models.py
from django.db import models

# Create your models here.
# 导入 AbstractUser
from django.contrib.auth.models import AbstractUser


# 定义用户表
class UserInfo(AbstractUser):
    # 新增手机号码字段, 字段信息唯一
    mobile_phone = models.CharField(max_length=11, unique=True, verbose_name='手机号码')
    # 头像, upload_to指定文件存放的位置, default设置默认头像
    icon = models.ImageField(upload_to='icon', default='default.png', verbose_name='头像')

image-20220427215352742

创基media媒体文件夹, 存放一张default.png 默认头像
配置使用的User表
# DRF_test2/settings.py
# 指定使用的User表, app应用名.表名
AUTH_USER_MODEL = 'api.UserInfo'
* 4. 配置媒体文件夹
     1. 项目目录下新建media文件夹
     2. 项目配置文件中配置媒体文件夹
# DRF_test2/settings.py
# 媒体文件夹
MEDIA_URL = '/media/'
# 媒体文件夹绝对路径
MEDIA_ROOT = Path(BASE_DIR, 'media')
* 5. 生成用户信息表
     1. 生成表操作记录 python manage.py makemigrations
     2. 数据集迁移 python manage.py migrate

2.2 创建用户

使用视图集GenericViewSet + 视图拓展类RetrieveModeMixin + 模型序列化器完成User表新增数据接口
GenericViewSet: 继承ViewSetMixin和GenericAPIView
CreateModelMixin: 创建数据模块, 内置create方法.
ViewSetMixin: 重写的view函数内存地址, view函数中遍历actions字段, 为请求绑定方法.
* 1. 继承ViewSetMixin后可以自定生成路由
# 总路由
from django.contrib import admin
# 导入模块
from django.urls import path, re_path, include
# 子路由
from api import urls
urlpatterns = [
    path('admin/', admin.site.urls),
    re_path('^api/', include(urls))
]
# 子路由, api文件夹中新建urls.py文件
from django.urls import re_path, include
from api import views
# 导入自动生成路由模块
from rest_framework.routers import SimpleRouter

# 生成对象
router = SimpleRouter()

# 生成路由
router.register('register', views.RegisterAPI)

# 路由列表
urlpatterns = [
    re_path('', include(router.urls))
]
* 2. 创建用户视图类
# 注册api
# 导入通用视图集
from rest_framework.viewsets import GenericViewSet
# 导入视图拓展类
from rest_framework.mixins import CreateModelMixin
# 导入模型层
from api import models
# 导入模型序列化器
from utils import serializers


class RegisterAPI(GenericViewSet, CreateModelMixin):
    # 获取表数据
    queryset = models.UserInfo.objects.all()

    # 定义使用的模型序列化器
    serializer_class = serializers.UserModelSerializer
* 4. 定义模型序列化器, 钩子函数与META类同级, 否则钩子函数失效!!!
# 定义模型序列化器
from rest_framework import serializers
# 导入模型层
from api import models
# 导入异常模块
from rest_framework.exceptions import ValidationError


# 定义用户模型序列化器
class UserModelSerializer(serializers.ModelSerializer):
    # 定义二次密码 required改字段必填, 最短6 最长12, 字段不存在必须设置为只写
    re_password = serializers.CharField(
        required=True, min_length=6, max_length=12, write_only=True, )

    # 定义Meta类
    class Meta:
        # 使用的表模型
        model = models.UserInfo
        # 需要序列化的字段
        fields = ['username', 'password', 're_password', 'mobile_phone', 'icon']
        extra_kwargs = {
    
    
            'password': {
    
    'write_only': True, 'min_length': 6, 'max_length': 12}}

    # 局部钩子 判断手机号码是否是11位, 不是11位直接抛出异常
    def validate_mobile_phone(self, data):
        if len(data) != 11:
            raise ValidationError('手机号码必须是11位!')

        # 将值返回
        return data

    # 全局钩子 检验两次密码是否一致
    def validate(self, attrs):
        if attrs.get('password') == attrs.get('re_password'):
            # 将所有的值返回
            return attrs

        raise ValidationError('两次密码不一致!')

    # 自己定义create方法, 如果使用ModelSerializer模型序列化类的create的方法创建的用户则是明文的
    def create(self, validated_data):
        # re_password是不存在的, 需要将该值pop掉
        validated_data.pop('re_password')
        # 将用户对象返回
        user_obj = models.UserInfo.objects.create_user(**validated_data)
        return user_obj
* 5. 将rest_framework 添加到app应用类别中
INSTALLED_APPS = [
    ...
    'rest_framework'
]

image-20220427213303719

* 6. 开发图片访问路径, 在主路由中配置	
from django.contrib import admin
from django.urls import path, re_path, include
from api import urls
# 导入访问媒体资源视图类
from django.conf import settings
from django.views.static import serve
urlpatterns = [
    path('admin/', admin.site.urls),
    # 主路由
    re_path('^media/(?P<path>.*)', serve, {
    
    'document_root': settings.MEDIA_ROOT}),
    # 图片访问
    re_path('^api/', include(urls))
]
get请求: http://127.0.0.1:8000/media/default.png

image-20220427215641339

serve是内置的视图类
def serve(request, path, document_root=None, show_indexes=False):
    第一个参数request 会自动传入, path是通过有名分组获取的, document_root文档的根路径.
    show_indexes 显示索引可以不开启

re_path('^media/(?P<path>.*)', serve, {
    
    'document_root': settings.MEDIA_ROOT}),
from django.conf import settings
from 项目 import settings
这两个方式都可以获取到settings.py的信息

项目的settings.py 是为开始者提供的配置, 配置少. (不推荐使用) 
	直接查找项目的settings.py数据
conf的settings.py 存放程序的默认配置, 配置多. (推荐使用)
    执行顺序: 同一个配置优先使用项目的settings.py 中的设置, 没有配置在使用conf的settings.py .
    在项目的settings.py中写的配置覆盖conf的settings.py 的配置.

2.3 查询信息

在视图类中使用拓展类RetrieveModelMixin +  另一个模型序列化器完成User表查询接口
直接为数图类添加继承RetrieveModelMixin类, 便拥有了通过主键访问数据的API接口.
主要是新增数据使用一个模型序列化器, 查询数据使用另一个序列化请求.
* 1. 定义一个模型序列化器
# 获取信息模型序列化器
class RetrieveModelSerializer(serializers.ModelSerializer):
    # 定义META类
    class Meta:
        model = models.UserInfo
        # 排除密码字段的转换, 其他的字段全部转换
        exclude = ['password', ]
* 2. 在视图类中定义 get_serializer_class 方法, 
     继承了ViewSetMixin的类使用自动生成路由, view函数中遍历actions字段, 为请求绑定方法.
     在get_serializer_class 方法中判断请求绑定的方法, 返回不同的模型序列化.
     self.action 在经过     
     def dispatch(self, request, *args, **kwargs):
	    ...
        request = self.initialize_request(request, *args, **kwargs)
   		之后self.action 便有了请求绑定的方法名字
# 注册api
# 导入通用视图集
from rest_framework.viewsets import GenericViewSet
# 导入视图拓展类
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
# 导入模型层
from api import models
# 导入模型序列化器
from utils import serializers


class RegisterAPI(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
    # 获取表数据
    queryset = models.UserInfo.objects.all()

    # 定义使用的模型序列化器

    def get_serializer_class(self):
        print(self.action)
        # 创建数据
        if self.action == 'create':
            return serializers.UserModelSerializer
        # 通过主键查询数据
        if self.action == 'retrieve':
            return serializers.RetrieveModelSerializer
内置的get_serializer_class是直接返回 类中定义serializer_class属性

image-20220427225402285

在类中自定义get_serializer_class, 通过对应的请求, 返回不同的序列化器.

2022-04-27_00375

2.4 修改头像

为视图类添加UpdateModelMixin类, + 另一个模型序列化器完成User表修改头像的接口.
# 注册api
# 导入通用视图集
from rest_framework.viewsets import GenericViewSet
# 导入视图拓展类
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
# 导入模型层
from api import models
# 导入模型序列化器
from utils import serializers


class RegisterAPI(GenericViewSet, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin):
    # 获取表数据
    queryset = models.UserInfo.objects.all()

    # 定义使用的模型序列化器

    def get_serializer_class(self):
        print(self.action)
        # 创建数据
        if self.action == 'create':
            return serializers.UserModelSerializer
        # 通过主键查询数据
        if self.action == 'retrieve':
            return serializers.RetrieveModelSerializer
        # 通过主键修改用户头像
        if self.action == 'update':
            return serializers.UpdateModelSerializer
# 定义模型序列化器
class UpdateModelSerializer(serializers.ModelSerializer):
    # 定义META类
    class Meta:
        model = models.UserInfo
        fields = ['icon']
PUT请求: 127.0.0.1:8000/api/register/4/
上传文件选择form-dat, 键为字段的中设置的upload_to值'icon'.

image-20220428000605455

猜你喜欢

转载自blog.csdn.net/qq_46137324/article/details/124373163
今日推荐