11 Book系列十大接口

一. APIView版本

1. models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.
# 自定义字段Char类型
class CustomChar(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=self.max_length, *args, **kwargs)

    def db_type(self, connection):
        return f'Char({self.max_length})'


# 定义所有的表共有的字段
class CommonModel(models.Model):
    is_delete = models.BooleanField(default=False, verbose_name='True标记被删除的数据, False标记正常使用的数据')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')

    class Meta:
        abstract = True


class Book(CommonModel):
    """
    参数拓展:
        blank:  指定布尔值. 用来表示admin后台管理该字段时候可以为空.
        help_text: 指定字符串. 用来表示admin后台管理的提示信息.

    外键关联:
        to_field: 指定关联的表的外键字段. 默认不写,关联到关联表的主键值.
        db_constraint:  逻辑上的关联,实质上没有外键练习,增删不会受外键影响,以及不影响orm查询.
        ForeignKey 与 OneToOneField
            由源码得知OneToOneField继承ForeignKey, 并且默认制定了unique=True参数
            ForeignKey(to='AuthorDetail', unique=True)
            OneToOneField(to='AuthorDetail')

    on_delete参数:
        1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
        2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
        3、断关联一定要通过逻辑保证表之间数据的安全,不要出现脏数据,代码控制
        4、断关联
        5、级联关系
              作者没了,详情也没:on_delete=models.CASCADE
              出版社没了,书还是那个出版社出版:on_delete=models.DO_NOTHING
              部门没了,员工没有部门(空不能):null=True, on_delete=models.SET_NULL
              部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT
    """
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32, verbose_name='书名', help_text=''
                                                                        '这里填写书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书价格')

    publish = models.ForeignKey(to='Publish', to_field='id', on_delete=models.DO_NOTHING, db_constraint=False)
    # 提示: to_field不能指定pk, 而是需要指定对应关联表的实际字段. 如果指定pk, 将会抛出如下异常:
    '''
    publish = models.ForeignKey(to='Publish', to_field='pk', on_delete=models.DO_NOTHING, db_constraint=False)
    ERRORS:
        api.Book.publish: (fields.E312) The to_field 'pk' doesn't exist on the related model 'api.Publish'.
    '''

    authors = models.ManyToManyField(to='Author', db_constraint=False)

    # 半自动表的创建db_constraint参数的存在就会抛出如下异常:
    '''
    TypeError: __init__() got an unexpected keyword argument 'db_contraint'
    '''

    # authors = models.ManyToManyField(to='Author', through_fields=('book', 'author'), through='Author2Book', db_constraint=False)

    class Meta:
        """
        参数拓展:
        db_table: 指定字符串. 用来修改表名
        abstract: 指定布尔值. 用来建立抽象表, 该表不会在数据库中创建
        unique_together: 指定容器. 用来建立多字段唯一
        index_together:  指定容器. 用来建立多字段之间的联合索引

        verbose_name: 指定字符串. 用来admin中显示表名, 默认加后缀s.
        verbose_name_plural: 指定字符串. 用来admin中显示表名, 默认不加后缀s.
        """
        verbose_name = '图书表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

    # @property   # 提示: property装饰器可以不指定
    # def publish_name(self):
    #     return self.publish.name

    # @property   # 提示: property装饰器可以不指定
    def authors_list(self):
        return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]


# class Author2Book(models.Model):
#     author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING, db_constraint=False)
#     book = models.ForeignKey(to='Book', on_delete=models.NOT_PROVIDED, db_constraint=False)


class Publish(CommonModel):
    name = models.CharField(max_length=32, verbose_name='出版社名')
    addr = models.CharField(max_length=64, verbose_name='出版社地址')

    class Meta:
        verbose_name = '出版社表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class Author(CommonModel):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    gender_choices = (
        (0, '女'),
        (1, '男'),
        (2, '保密'),
    )
    gender = models.IntegerField(choices=gender_choices, verbose_name='作者性别', default=2)

    author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE, db_constraint=False)

    class Meta:
        verbose_name = '作者表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class AuthorDetail(CommonModel):
    phone = CustomChar(max_length=11, verbose_name='作者手机号码')

    class Meta:
        verbose_name = '作者详情表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.phone

2. ser.py 自定义序列化.py文件

from rest_framework import serializers
from . import models


class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        print('instance:', instance)
        print('validated_data:', validated_data)
        print('self.child:', self.child)
        """
        return [
            self.child.create(attrs) for attrs in validated_data
        ]
        """
        return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)]


# 提示: 序列化操作的是数据库的表,推荐使用ModelSerializer
class BookModelSerializer(serializers.ModelSerializer):
    # 第一种方式: 通过指定参数read_only=True, 在反序列化的时候不需要传该字段指定的值
    publish_name = serializers.CharField(source='publish.name', read_only=True)

    # 第二种方式: 在模型类中写方法, 通过方法关联到这里的字段. 如authors_list字段
    '''
    def authors_list(self):
        return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
    '''

    class Meta:
        """
        depth: 指定整数. 表示跨表查询的深度.
            如果指定2, 查询的时候就会将本实例中Book表关联的表, 以及关联表的关联的表所有的数据获取出来.
        """
        model = models.Book
        fields = ('id', 'name', 'price', 'publish', 'authors', 'publish_name', 'authors_list')
        depth = 0
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
            'publish_name': {'read_only': True},
            'authors_list': {'read_only': True},
        }
        lis

3. views.py

from rest_framework.views import APIView

from . import models
from . import ser
from utils.exception import NonentityError
from utils.response import CommonResponse


class BookAPIView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            instance = models.Book.objects.filter(pk=pk, is_delete=False).first()
            serializer = ser.BookModelSerializer(instance=instance)
        else:
            instance = models.Book.objects.filter(is_delete=False)
            serializer = ser.BookModelSerializer(instance=instance, many=True)
        return CommonResponse(results=serializer.data)

    def post(self, request, *args, **kwargs):
        if isinstance(request.data, list):
            serializer = ser.BookModelSerializer(data=request.data, many=True)
        elif isinstance(request.data, dict):
            serializer = ser.BookModelSerializer(data=request.data)
        else:
            raise NonentityError('Add data through lists or dictionaries!')
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return CommonResponse(results=serializer.data)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            instance = models.Book.objects.get(pk=pk)
            serializer = ser.BookModelSerializer(instance=instance, data=request.data)
        elif isinstance(request.data, list):
            # request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
            pks = [dic.get('id') for dic in request.data]
            instance = models.Book.objects.filter(pk__in=pks)
            serializer = ser.BookModelSerializer(instance=instance, data=request.data, many=True)
        else:
            raise NonentityError(
                'Specifies that the keyword can be partially modified or that the dictionary \
                format of the list can be modified multiple times!')
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return CommonResponse(results=serializer.data)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        pks = []
        pks = pks.append(pk) if pk else request.data.get('pks')
        # {'pks': [1, 2, 3]}
        affected_rows = models.Book.objects.filter(pk__in=pks).update(
            is_delete=True)
        return CommonResponse(results={'affected_rows': affected_rows})

4. urls.py

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

urlpatterns = [
    url(r'books/$', views.BookAPIView.as_view()),
    url(r'books/(?P<pk>[^/.]+)', views.BookAPIView.as_view())
]

5. utils 自定义工具包

1) exception_handler.py 自定义异常处理

from rest_framework.views import exception_handler
from rest_framework import status
from .response import CommonResponse


def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if not response:
        obj = CommonResponse(code=2000, messages='失败', error=str(exc), results=str(context),
                            status=status.HTTP_403_FORBIDDEN)
    else:
        obj = CommonResponse(code=2001, messages='失败', error=str(response.data), results=str(context),
                            status=status.HTTP_403_FORBIDDEN)
    return obj

2) exception.py 自定义错误类型

class NonentityError(Exception):
    def __init__(self, value):
        self.value = value
        super().__init__()

    def __str__(self):
        return '< %s >' % self.value

3) response.py 自定义封装response对象

from rest_framework.response import Response


class CommonResponse(Response):
    def __init__(self, code=1000, messages='成功', results=None, error=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        data = {
            'code': code,
            'messages': messages,
        }
        data.update(kwargs)

        if results:
            data['results'] = results
        if error:
            data['error'] = error
        super().__init__(data=data, status=status,
                         template_name=template_name, headers=headers,
                         exception=exception, content_type=content_type)

4) throttle.py 自定义频率校验

from rest_framework.throttling import SimpleRateThrottle


class CustomSimpleRateThrottle(SimpleRateThrottle):
    scope = 'custom'

    def get_cache_key(self, request, view):
        # 'REMOTE_ADDR': '127.0.0.1',
        # print(request.META.get('REMOTE_ADDR'))
        return request.META.g

6. 总结

# 设计模型表的总结
    1. 所有的表应该都有三个基本字段: is_delete, create_time, update_time
        因此新增一个类继承Model, 之后的所有的类表继承它即可.
        注意: 新增的类表默认会在执行数据库迁移命令以后, 会在数据库中生成,
            因此应该新建Meat类, 指定abstract=True抽象表, 这样它就不会生成该表了.
    2. 外键关联字段需要注意如下几种问题
        1) 一对一关系要建立在查询次数较多的一行
        2) 一对多关系要建立在多的一方, 如果建立在一的一行, 那么以后一的一行该外键对应的值将会是一个'[1, 2, 3]'这种结构.
            这不是我们想要的. 我们最起码应该满足数据的数据设计存储规范.
        3) 级联关系: 建立外键关联的字段要着重考虑on_delete参数.
            如果一方没了, 那么另一方应该没有. 那么使用models.CASCADE(提示: 一对一关系表, 一般都是指定这个参数)
            如果一方没了, 那么另一方应该存在. 那么使用models.DO_NOTHING
            如果一方没了, 那么另一行应该设置为空. 那么使用models.SET_NULL
            如果一方没了, 那么另一行应该转移到设置的默认情况上. 那么使用models.SET_DEFAULT
        4) 断关联: 建立外键关联的字段最好使用db_constraint=False.
            使用它可以提升对数据的操作, 不用依照外键之间删除数据, 新增数据的限制, 就可以直接对数据进行操作,
            使用这种名义上的关联能最大化的提升数据库增删改的效率.
            缺点: 如果直接操作数据库会出现脏数据, 因此不要直接操作数据库, 代码层面对数据的操作, 可以进行有效的控制即可
    3. 疑问: 为什么创建半自动表使用db_constraint=False就会出现问题???

# 序列化器实现总结
    1. 序列化操作涉及到数据库的操作, 推荐使用ModelSerializer
    2. depth: 指定整数. 表示跨表查询的深度.  提示: 一般都不深度查询
    3. 重点: 关于外键关联, 序列化 与 反序列化 是有所不同的
        1) 专门用与序列化的字段设计:   新建一个外键字段指定只读
            2种方法:
                第一种: 序列化器中使用source
                第二种: 模型类中写方法, 在fields中声明
            注意: 该字段只读, 可在extra_kwargs中声明
        2) 专门用与反序列化的字段设计: 默认的外键字段指定只写.


# 视图方法实现总结
    # get
        # 如何区分请求过来是获取单条还是多条数据?
            先配置2条路由, 2条路由都指向同一个视图类. 再通过url传递过来kwargs中是时候有pk值.
            有: 单条数据  没有: 多条数据
        # 前提: 所有的获取都需要在过滤is_delete=True的字段, 获取的只是没有标记被删除的is_delete=False的数据
        # 获取单条数据: 直接获取到数据对象, 再使用自定义的序列化类序列话数据. 拿到序列化之后的结果
        # 获取多条数据: 直接获取到queryset对象, 序列化时需要指定many=True, 即可

    # post
        # 如何区分请求过来是新增单条还是多条数据?
            单条数据格式: {}
            多条数据格式: [{}, {}]
        # 新增单条数据 和 新增多条数据
            数据都是从body中获取, 反序列化时指定的参数是data, 还需要注意的就是多条数据需要指定many=True.
            提示: 新增多条数据, ListSerializer中定义了create方法
                本质就是通过for循环, 再调用ModelSerializer中的create方法.
                def create(self, validate_data):
                    # self.child就是当前视图中指行序列化类实例化得到的对象
                    return [self.child.create(attrs) for attrs in validate_data]

    # put
        # 如何区分请求过来是修改单条还是多条数据?
            判断: 通过路由的又名分组, 到kwargs中时候能获取pk值来判断
            单条数据格式: pk -> {}
            多条数据格式: [{'pk': ... }, {'pk': ... }]
        # 修改单条数据
            先获取传递过来的pk, 过滤出需要修改的数据对象
            再往序列化的类中传递需要修改的对象, 以及该对象修改的数据

        # 修改多条数据
            先将传过来的列表套字典格式的数据中的所有字典中pk获取, 再使用双下划线过滤出对应的所有对象, 返回一个queryset对象.
            再往序列换的类很重传递需要修改的queryset对象, 以及传递过来的要修改成什么样子的数据, 注意: 需要指定many=True
            提示: 上面指定了many=True, 序列化完毕以后返回的是一个由ListSerializer类. ListSerializer类中定义了create,
                但是没办法书写update方法, 因此需要我们重写.
            步骤:
                1) 新建一个类, 继承ListSerializer
                2) 重写create方法
                    def create(self, instance, validate_data):
                        return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validate_data)]
                3) 在当前视图中执行序化类中在其, Meta中声明处理many=True时的类. list_serializer_class = 新建类名

    # delete
        # 如何区分请求过来是修改单条还是多条数据?
            单条数据: 判断kwargs中是否有pk值
            多条数据: {'pks': [1, 2, 3]}
        # 删除单个 和 删除多个数据
            提示: 不是真正的删除, 而是修改对应数据中的is_delete字段等于True
            删除单个可

二. GenericAPIView版本

from rest_framework.generics import GenericAPIView
from . import models
from . import ser
from utils.response import CommonResponse
from utils.exception import NonentityError


class BookAPIView(GenericAPIView):
    queryset = models.Book.objects.all()
    serializer_class = ser.BookModelSerializer

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            instance = self.get_object()
            # is_delete=True表示数据是做了删除标记的
            if instance.is_delete:
                raise NonentityError('The data as well does not exist anymore!')
            else:
                serializer = self.get_serializer(instance=instance)
        else:
            instance = self.get_queryset().filter(is_delete=False)
            serializer = self.get_serializer(instance=instance, many=True)
        return CommonResponse(results=serializer.data)

    def post(self, request, *args, **kwargs):
        if isinstance(request.data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        elif isinstance(request.data, dict):
            serializer = self.get_serializer(data=request.data)
        else:
            raise NonentityError('Add data through lists or dictionaries!')
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return CommonResponse(results=serializer.data)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            instance = self.get_object()
            serializer = self.get_serializer(instance=instance, data=request.data, partial=True)
        elif isinstance(request.data, list):
            # request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
            pks = [dic.get('id') for dic in request.data]
            instance = self.get_queryset().filter(pk__in=pks)
            serializer = self.get_serializer(instance=instance, data=request.data, many=True)
        else:
            raise NonentityError(
                'Specifies that the keyword can be partially modified or that the dictionary \
                format of the list can be modified multiple times!')
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return CommonResponse(results=serializer.data)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        pks = []
        pks = pks.append(pk) if pk else request.date.get('pks')
        affected_rows = self.get_queryset().filter(pk__in=pks).update(is_delete=True)
        return CommonResponse(results={'affected_rows': affected_rows})

总结

主要要明白GenericAPIView中提供的三种主要的方法的返回值
self.get_object()      返回数据对象
self.get_queryset()    返回queryset对象. 因此这里就可以继续有filter(), update()等连点操作
self.get_serializer()  many=
posted @ 2020-07-19 20:11  给你加马桶唱疏通  阅读(207)  评论(0编辑  收藏  举报