day 76 周总结

Response封装

经过前面的学习我们可以发现, 我们每次成功响应了前端的请求, 都要按照固定的格式写一遍Response内部包含的数据,并将其返回给前端, 每返回一次就要完整的写一次, 是不是有点麻烦?

我们

可以通过对Response进行简单封装, 来简化我们的代码

Copy# 封装前
return Response({
    'status': 0,
    'msg': 'ok',
    'results': serializer_obj.data
})

# 封装后
return APIResponse(results=serializer_obj.data)
Copy# 在app文件下新建一个response.py文件
from rest_framework.response import Response


# 定义APIResponse继承Response
class APIResponse(Response):
    # 重写__init__方法
    def __init__(self, status=0, msg='ok', results=None, http_status=None,
                 headers=None, exception=False, content_type=None, **kwargs):
        # 将status, msg, results, kwargs放到data当中
        data = {
            'status': status,
            'msg': msg
        }
        if results is not None:
            data['results'] = results

        data.update(**kwargs)

        # 调用父类Response的__init__方法
        super().__init__(data=data, status=http_status, headers=headers, exception=exception, content_type=content_type)

深度查询之depth

  • depth是深度查询的一种实现方式
  • 在序列化类中的配置类中设置depth
  • 会根据对应深度的外键字段的主键值, 获取对应的记录
Copyclass PressModerSerializer(serializers.ModelSerializer):
  
    class Meta:
        model = models.Press
        fields = ['name', 'addr', 'books']
        # 设置查询深度为1
        depth = 1
        
        
-----------------------------------------------------------------------------------------------------


{
    "status": 0,
    "msg": "ok",
    "results": [
        {
            "name": "东方出版社",
            "addr": "上海",
            # 不设置查询深度, 显示的是主键值; 设置查询深度, 显示是主键值对应的记录
            "books": [
                {
                    "id": 1,
                    "is_delete": false,
                    "created_time": "2019-12-26T18:40:09",
                    "name": "三体",
                    "price": "49.90",
                    # 如果设置depth=2, 那下面id=1的press也会被查出来 
                    "press": 1,
                    "authors": [
                        1
                    ]
                },
                {
                    "id": 3,
                    "is_delete": false,
                    "created_time": "2019-12-26T18:42:08",
                    "name": "球状闪电",
                    "price": "36.60",
                    "press": 1,
                    "authors": [
                        1
                    ]
                }
            ]
        },
        

深度查询之自定义@property方法

  • 在模型类中自定义@property属性方法获取外键字段对应的数据, 也可以实现深度查询
  • 外键字段值只对应一条记录的深度查询: 自定义方法只需要返回一个值
  • 外键字段值对应多条记录的深度查询: 自定义方法需要返回多个值
Copy# models.py
class Book(Base):
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    press = models.ForeignKey(to='Press', related_name='books', db_constraint=False, on_delete=models.SET_NULL,
                              null=True)
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

    # 外键字段只对应一条数据时
    @property
    def book_press(self):
        # 这里也可以利用序列化类
        # from serializers import PressModelSerializer
        # return  PressModerSerializer(self.press).data
        
        return {
            'name': self.press.name,
            'addr': self.press.addr
        }

    # 外键字段对应多条数据时
    @property
    def book_authors(self):
        authors = []
        for author in self.authors.all():

            author_dic = {
                'name': author.name,
            }

            # 如果作者没有详情, 进行异常捕获
            try:
                author_dic['mobile'] = author.detail.moblie
            except:
                author_dic['mobile'] = '无'

            authors.append(author_dic)

        return authors
        
    
-----------------------------------------------------------------------------------------------------


# serializers.py
class BookMethodSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        # 将自定义的@property属性方法在fields中配置
        fields = ['name', 'price', 'book_press', 'book_authors']

delete请求实现单删群删

  • 将单删变为群删一条数据: pks = [pk, ]
  • 对涉及数据库内部操作的代码进行异常处理
Copy # 单删群删
    def delete(self, request, *args, **kwargs):
        """
        单删
            接口: /book/(pk)/, 数据: 空
        群删
            接口: /book/, 数据: [pk1, pk2, ...]
        """
        pk = kwargs.get('pk')

        if pk:
            # 将单删变为群删一条数据
            pks = [pk, ]
        else:
            pks = request.data

        # 数据有误, 数据库执行会报错
        try:
            rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
        except:
            return APIResponse(1, 'invalid data', http_status=400)

        if rows:
            return APIResponse(0, 'delete ok')
        else:
            return APIResponse(1, 'delete fail')

post请求实现单增群增

  • 单增群增的接口都是 /book/
  • 单增请求携带数据的格式是字典, 群增请求携带数据的格式是列表套字典
  • 通过判断请求携带数据的数据类型, 来确定many=True or False
Copy# 单增群增
    def post(self, request, *args, **kwargs):
        """
        单增
            接口: /book/, 数据: {...}
        群增
            接口: /book/, 数据: [{},{}, ...]
        """
        if isinstance(request.data, dict):
            is_many = False
        elif isinstance(request.data, list):
            is_many = True
        else:
            return APIResponse(0, 'invalid data', http_status=400)

        serializer_obj = serializers.BookModelSerializer(data=request.data, many=is_many)
        serializer_obj.is_valid(raise_exception=True)
        book_obj_or_list = serializer_obj.save()

        return APIResponse(results=serializers.BookModelSerializer(book_obj_or_list, many=is_many).data)

ListSerializer

  • ModelSerializer的create的方法只能进行单增操作
  • ModelSerializer默认配置了ListSerializer来辅助其完成群增操作
  • ListSerialzer下面create的方法只是对群增数据进行了遍历, 然后调用ModelSerializer的create方法进行数据的入库
Copy# ModelSerializer下面的create的方法只能实现单增
def create(self, validated_data):
    (...)    
    return instance
Copy# ListSerializer下面的create方法
def create(self, validated_data):
    return [
        # 对群增数据进行遍历, 遍历一个, 就调用ModelSerializer的create方法来增加一个
        # self.child就是ModelSerializer对象
        self.child.create(attrs) for attrs in validated_data
        ]
  • 如果只是进行群增操作, 我们是没有必要自定义ListSerializer子类, 重写create方法的 (当然确实可以写, 但没必要)
  • 如果进行群改操作, 就需要我们自定义ListSerializer子类, 重写update方法
Copy# ListSerializer的update方法
def update(self, instance, validated_data):
    raise NotImplementedError(
        "Serializers with many=True do not support multiple update by "
        "default, only multiple create. For updates it is unclear how to "
        "deal with insertions and deletions. If you need to support "
        "multiple update, use a `ListSerializer` class and override "
        "`.update()` so you can specify the behavior exactly."
    )

put请求实现整体单改和整体群改

  • 群改请求携带数据的数据格式是列表套字典, 且每个字典都必需包含pk
  • 如果有一个字典没有包含pk, 或者pk没有对应的数据, 就直接整体报错
  • 需要借助自定义的ListSerializer类, 重新update方法来实现群改操作
Copy# views.py
# 整体单改群改 
def put(self, request, *args, **kwargs):
    """
    单改
        接口: /book/(pk)/, 数据: {...}
    群改
        接口: /book/, 数据: [{'pk':1,..},{'pk':2,..}, ...]
    """
    pk = kwargs.get('pk')
    # 单改
    if pk:
        try:
            # 注意这里我们使用get方法, 没有的话就报错, 进行异常处理
            book_instance = models.Book.objects.get(is_delete=False, pk=pk)

        except:
            return APIResponse(1, 'invalid data', http_status=400)

        serializer_obj = serializers.BookModelSerializer(instance=book_instance, data=request.data)
        serializer_obj.is_valid(raise_exception=True)
        book_obj = serializer_obj.save()

        return APIResponse(results=serializers.BookModelSerializer(book_obj).data)

    # 群改
    else:
        try:
            pks = []
            for dic in request.data:
                pk = dic.pop('pk')
                pks.append(pk)

            book_query = models.Book.objects.filter(is_delete=False, pk__in=pks).all()

            if not len(book_query) == len(book_query):
                raise Exception('pk error')

        except Exception as e:
            return APIResponse(1, msg=f'{e} error', http_status=400)

        serializer_obj = serializers.BookModelSerializer(instance=book_query, data=request.data, many=True)
        serializer_obj.is_valid(raise_exception=True)
        book_list = serializer_obj.save()

        return APIResponse(results=serializers.BookModelSerializer(book_list, many=True).data)
Copy# serializers.py
# 自定义ListSerializer子类实现群改操作
class BookListSerializer(serializers.ListSerializer):
    def update(self, instance_list, validated_data_list):
        return [
            self.child.update(instance_list[index], attrs) for index, attrs in enumerate(validated_data_list)
        ]


class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        # 配置自定义的ListSerializer类
        list_serializer_class = BookListSerializer

        model = models.Book
        fields = ['name', 'price', 'book_press', 'book_authors', 'press', 'authors']
        extra_kwargs = {
            'press': {
                'write_only': True
            },
            'authors': {
                'write_only': True
            }
        }

patch请求实现局部单改和局部群改

  • 局部改就是在实例化serializer对象的时候加一个

    partial=True
    

    参数就行

    • 某个字段被提供了值, 则该字段修改
    • 某个字段没有被提供值, 则保留原有的值
  • 实例化serializer对象时设置context参数, 可以将视图类中的数据传递给序列化类下面的钩子函数

  • 局部改是兼容整体改的, 因此我们以后用patch请求进行修改操作就好了

Copy# views.py
# 局部单改群改 
def put(self, request, *args, **kwargs):
    """
    单改
        接口: /book/(pk)/, 数据: {...}
    群改
        接口: /book/, 数据: [{'pk':1,..},{'pk':2,..}, ...]
    """
    pk = kwargs.get('pk')
    # 单改
    if pk:
        try:
            # 注意这里我们使用get方法, 没有的话就报错, 进行异常处理
            book_instance = models.Book.objects.get(is_delete=False, pk=pk)

        except:
            return APIResponse(1, 'invalid data', http_status=400)
        
        # 实例化serializer对象时添加partial=True
        serializer_obj = serializers.BookModelSerializer(instance=book_instance, data=request.data, partial=True)
        serializer_obj.is_valid(raise_exception=True)
        book_obj = serializer_obj.save()

        return APIResponse(results=serializers.BookModelSerializer(book_obj).data)

    # 群改
    else:
        try:
            pks = []
            for dic in request.data:
                pk = dic.pop('pk')
                pks.append(pk)

            book_query = models.Book.objects.filter(is_delete=False, pk__in=pks).all()

            if not len(book_query) == len(book_query):
                raise Exception('pk error')

        except Exception as e:
            return APIResponse(1, msg=f'{e} error', http_status=400)
        
        # 实例化serializer对象时添加partial=True
        serializer_obj = serializers.BookModelSerializer(instance=book_query, data=request.data, many=True, partial=True)
        serializer_obj.is_valid(raise_exception=True)
        book_list = serializer_obj.save()

        return APIResponse(results=serializers.BookModelSerializer(book_list, many=True).data)
posted @ 2019-12-30 00:33  colacheng  阅读(117)  评论(0编辑  收藏  举报
Live2D