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)