Django REST framework 序列化

简介

使用序列化有四种方式

  • 使用json模块,完全手写
  • 使用django自带的序列化模块
    1,# from django.core import serializers
    2,# data=serializers.serialize(“json”,book_list)
  • 使用REST framework 带的序列化方法,但是自己写规则
    BookSerializers(serializers.Serializer)
  • 使用REST framework 带的序列化方法,不自定义,完全使用模块
    BookSerializers(serializers.ModelSerializer)

开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式。我们可以通过声明与Django forms非常相似的序列化器(serializers)来实现。

简单示例

models部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.db import models

# Create your models here.
class Book(models.Model):
title=models.CharField(max_length=32)
price=models.IntegerField()
pub_date=models.DateField()
publish=models.ForeignKey("Publish")
authors=models.ManyToManyField("Author")
def __str__(self):
return self.title

class Publish(models.Model):
name=models.CharField(max_length=32)
email=models.EmailField()
def __str__(self):
return self.name

class Author(models.Model):
name=models.CharField(max_length=32)
age=models.IntegerField()
def __str__(self):
return self.name

views部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from rest_framework.views import APIView
from rest_framework.response import Response


class BookViewSet(APIView):
def get(self,request,*args,**kwargs):
book_list=Book.objects.all()
# 序列化方式1 自己手写json:
# from django.forms.models import model_to_dict
# import json
# data=[]
# for obj in book_list:
# data.append(model_to_dict(obj))
# print(data)
# return HttpResponse("ok")

# 序列化方式2,使用django自带的序列化模块:
# from django.core import serializers
# data=serializers.serialize("json",book_list)
# return HttpResponse(data)

# 序列化方式3,使用REST framework 带的序列化方法,但是自己写规则:
bs=BookSerializers(book_list,many=True)
return Response(bs.data)

# 序列化方式4,使用REST framework 带的序列化方法,不自定义,完全使用模块,如下
bs=BookSerializers(book_list,many=True)
return Response(bs.data)

方式三:BookSerializers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.shortcuts import HttpResponse
from django.core import serializers
from rest_framework import serializers

class BookSerializers(serializers.Serializer): # 方式三,自己定义序列化的规则
title=serializers.CharField(max_length=32)
price=serializers.IntegerField()
pub_date=serializers.DateField()
publish=serializers.CharField(source="publish.name") # 一对多,序列化时如果想要指定字段,需要使用参数source,跨表用.
#authors=serializers.CharField(source="authors.all")
authors=serializers.SerializerMethodField() # 多对多时,自定义字段字段
def get_authors(self,obj): # 名字固定写法
temp=[]
for author in obj.authors.all():
temp.append(author.name)
return temp

class Meta:
model=Book
fields=['title','price','pub_date','authors']

# 钩子函数
def validate_title(self, value):
if '草' in value:
raise serializers.ValidationError('不符合社会主义核心价值观')
return value

方式四:ModelSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ModelSerializer 继承自 Serializer
class BookSerializers(serializers.ModelSerializer): # 不自定义,完全使用模块
class Meta: #固定写法
# 指定要序列化Book表
model = models.Book
#指定要序列化的字段
fields = ['nid','name']
#序列化所有字段
fileds ='__all__'
#要排除的字段(不能与fileds连用)
# exclude = ['name','price']
#深度判定
depth = 1
#如果要不按照父类的来,想要自己定义显示的字段的话,自己定义一个,覆盖掉父类的字段属性。
publish = serializers.SerializerMethodField() #一对多字段
def get_publish(self,obj):
return {'id':obj.publish.pk,'name':obj.publish.name}

序列化组件常用字段

常用字段类型

字段 字段构造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’)
format:
1) ‘hex_verbose’ 如”5ce0e9a5-5ffa-654b-cee0-1238041fb31a”
2) ‘hex’ 如 “5ce0e9a55ffa654bcee01238041fb31a”
3)’int’ - 如: “123456789012312313134124512351145145114”
4)’urn’ 如: “urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a”
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)
max_digits: 最多位数
decimal_palces: 小数点位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices)
choices与Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

选项参数:

名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最大值
min_value 最小值

通用参数

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息

source

在官方文档中是这样解释的:

将用于填充字段的属性的名称。可以是仅接受self参数的方法,例如URLField(source='get_absolute_url'),也可以使用点分符号遍历属性,例如EmailField(source='user.email')。当使用点分符号序列化字段时,default如果在属性遍历期间任何对象不存在或为空,则可能需要提供一个值。
该值source='*'具有特殊含义,用于指示应将整个对象传递给该字段。这对于创建嵌套表示或对需要访问完整对象才能确定输出表示的字段很有用。
默认为字段名称。

其中比较常用的用法:

  1. source=”get_field_name_display”如上所展示,在choice字段中可以展示选项对应的解释,这中用法代表的是官方解释说的可以是仅接受self参数的方法,也就是source中可以填serializer对象可以调用的方法(方法中需要传入self),内在逻辑是这个字段会展示此方法的返回的结果。
  2. source=’user.email’这种用法常在ModelSerializer的子类中,其中user为User对象,此中写法是指展示user的email属性,常用于我们想把外键对象user的属性和本对象的属性展示在同一层级,而不是下一级。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class UserSerializer(serializers.Serializer):
type = serializers.IntegerField(source='user_type')
user_type = serializers.CharField(source='get_user_type_display') # choices字段显示
username = serializers.CharField()
pwd = serializers.CharField(source='password') # 自定义serializer中的key值
group_title = serializers.CharField(source='group.title') # 关联对象属性
roles = serializers.CharField(source='roles.all') # 多对多关系
roles_info = serializers.SerializerMethodField() # 表示自定义方法,显示querytset对象详情

def get_roles_info(self, row):
roles = row.roles.all()
ret = []
for item in roles:
ret.append(
{
'id': item.id,
'title': item.title
}
)
return ret
  1. 如果没有指定在Filed中没有定义source参数的时候,就自动与数据库modles定义的字段进行匹配,如上面的userrname字段。在定义字段后,Serializer类中可以自定义属性如type
  2. 当models中是以choice定义时:需要定义source参数定义get_字段名_display才能获取数据,这与在模板语言中的用法一样,如上面的user_type
  3. 外键关联的时候,直接 外键字段名.属性 的方式定义传参给source参数即可,如上面的group.title
  4. 对于roles字段,想直接获取所有的对象,但是无法做到细粒度的将对象的所有属性展示出来,只能获取到QuerySet对象
  5. 自定义字段,处理数据,如roles_info获取所有的role对象的属性,处理数据可以定义方法,方法名格式为get_属性,并return值最终返回值

serializers.Serializer序列化

数据模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.db import models
__all__ = ['Book', 'Publisher', 'Author']
class Book(models.Model):
title = models.CharField(max_length=32)
CHOICES = (
(1, 'Python'),
(2, 'Linux'),
(3, 'Go')
)
category = models.IntegerField(choices=CHOICES)
pub_time = models.DateField()
publisher = models.ForeignKey(to = 'Publisher', on_delete=models.CASCADE)
authors = models.ManyToManyField(to = 'Author')

class Publisher(models.Model):
title = models.CharField(max_length=32)

class Author(models.Model):
name = models.CharField(max_length=32)

创建序列化器

在app目录下创建serializers.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from rest_framework import serializers
from .models import Book

class PublisherSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=32)

class AutherSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField(max_length=32)

class BookSerializer(serializers.Serializer):
# required=False 指明反序时(前端提交数据过来)不做校验
id = serializers.IntegerField(required=False)

title = serializers.CharField(max_length=32)
pub_time = serializers.DateField()

"""
# source 比较常用的用法:
1、source="get_field_name_display",在choice字段中可以展示选项对应的解释,这中用法代表的是官方解释说的可以是仅接受self参数的方法,也就是source中可以填serializer对象可以调用的方法(方法中需要传入self),内在逻辑是这个字段会展示此方法的返回的结果。
2、source='user.email'这种用法常在ModelSerializer的子类中,其中user为User对象,此中写法是指展示user的email属性,常用于我们想把外键对象user的属性和本对象的属性展示在同一层级,而不是下一级。
"""
category = serializers.CharField(source='get_category_display', read_only=True)

# 外键关系的序列化是嵌套的序列化对象;内部通过外键关系的id找到publisher对象,再使用PublisherSerializer(publisher对象)
publisher = PublisherSerializer(read_only=True) # 嵌套序列化器
authors = AutherSerializer(many=True, read_only=True) # 多对多关系需要加上参数:many = True

# read_only:指明该字段只做序列化,反序列化时不走该字段;那么反序列化时就必须重新指定字段名
# write_only:指明该字段只做反序列化,不做序列化,也就是从数据库读出来的数据,序列化返的时候,不显示某个字段
post_category = serializers.IntegerField(write_only=True)
publisher_id = serializers.IntegerField(write_only=True)
author_list = serializers.ListField(write_only=True)

authors=serializers.SerializerMethodField() # 多对多时,自定义字段字段
def get_authors(self,obj): # 名字固定写法
temp=[]
for author in obj.authors.all():
temp.append(author.name)
return temp


def create(self, validated_data):
# 调用 save 时执行
# validated_data 校验通过的数据
# 通过ORM操作给Book表添加数据
# {'title': 'Die Hard', 'pub_time': datetime.date(2020, 1, 12), 'post_category': 1, 'publisher_id': 1, 'author_list': [1, 2]}
book_obj = Book.objects.create(
title=validated_data['title'],
pub_time=validated_data['pub_time'],
category=validated_data['post_category'],
publisher_id=validated_data['publisher_id']
)
book_obj.authors.add(*validated_data['author_list'])
return book_obj

def update(self, instance, validated_data):
# 调用 save 时执行
# instance为需要更新的对象
instance.title = validated_data.get('title', instance.title)
instance.pub_time = validated_data.get('pub_time', instance.pub_time)
instance.category = validated_data.get('post_category', instance.category)
instance.publisher_id = validated_data.get('publisher_id', instance.publisher_id)
if validated_data.get('author_list'):
instance.authors.set(validated_data['author_list'])
instance.save()
return instance

序列化组件is_valid、validated_data

当我们定义好序列化器时,怎么校验传入的字段呢?那就是is_valid方法,可以根据定义序列化器的校验规则判断传入字段是否合法。

1
2
3
4
5
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}

然后serializer.validated_data就可以获取校验过后的数据字典。

序列化组件校验字段

序列化组件校验字段的方式常有三种:

1、首先是在字段的validators属性, 其中传入一个校验方法列表如:validators=(my_validator, )其中my_validator中定义校验规则。

1
2
3
4
5
6
def multiple_of_ten(value):
if value % 10 != 0:
raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
score = IntegerField(validators=[multiple_of_ten])

2、最常用的是定义一个validate_field_name(self, value)的函数(其中field_name指的是字段名),函数内是具体的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()

def validate_title(self, value):
"""
Check that the blog post is about Django.
单个字段校验;value就是title的值
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value

3、最后一种是定义一个validate(self, data)其中data是所有字段的键值对,所以这个校验方法是对象级别的校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from rest_framework import serializers

class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()

def validate(self, data):
"""
Check that start is before finish.
多个字段校验;attrs是个字典,包含前端传递过来的所有字段
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data

当然或许当你看到这里会问why?how? 只能说源码是最好的答案。

validate做数据校验错误返回的key问题:

1
2
3
4
5
6
7
8
9
10
from rest_framework import serializers

def validate(self, attrs):
password = attrs.get('password')
re_password = attrs.pop('re_password')
print(password, re_password)

if password != re_password:
raise serializers.ValidationError('两次密码不一致')
return attrs

校验如有错误,报错的格式如下:

1
2
3
4
5
6
7
8
{
"status": 1,
"message": {
"non_field_errors": [
"两次密码不一致"
]
}
}

message的key为non_field_errors,传递到前端不方便处理,可以自定义一个key

1
raise serializers.ValidationError({'password': '两次密码不一致'})

报错格式如下:

1
2
3
4
5
6
7
8
{
"status": 1,
"message": {
"password": [
"两次密码不一致"
]
}
}

类视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class BookView(APIView):

def get(self, request):
book_queryset = Book.objects.all()
ser_obj = BookSerializer(book_queryset, many = True)
return Response(ser_obj.data)

def post(self, request):
book_obj = request.data
# print(book_obj, type(book_obj))
ser_obj = BookSerializer(data = book_obj)
if ser_obj.is_valid():
# ser_obj.save()使用该方法时,序列化器中必须实例create()方法
ser_obj.save()
return Response(ser_obj.validated_data)
return Response(ser_obj.errors)

class BookEditView(APIView):

def get(self, request, id):
book_obj = Book.objects.filter(id = id).first()
ser_obj = BookSerializer(book_obj)
return Response(ser_obj.data)

def put(self, request, id):
book_obj = Book.objects.filter(id=id).first()
# partial=True 指明为部分字段做校验,即传入什么字段就校验什么字段,其他字段不做校验
ser_obj = BookSerializer(instance = book_obj, data = request.data, partial = True)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.validated_data)
return Response(ser_obj.errors)

serializers.Serializer序列总结

使用serializers.Serializer方法序列化时注意以下几点:

  1. 正序与反序字段不一致
  2. 字段中的参数 required=False 指明反序时(前端提交数据过来)不做数据校验
  3. read_only=True 参数指明该字段只做序列化,反序列化时不走该字段;那么反序列化时就必须重新指定字段名
  4. write_only 参数指明该字段只用做反序列化
  5. 序列化器中必须实现create()与update()方法,分别对应创建与更新
  6. 前端提交的参数校验与Django中的form表单校验数据一样

seriarizers.ModelSerializer序列化

示例

4.1 depth

1
2
3
4
5
# 只修改了book表的序列化器,其他文件不动
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'

序列化后的数据如下:

1
2
3
4
5
6
7
8
{
"id":2,
"title": "天龙八部",
"authors": [
1,
2
]
}

外键关联的字段只显示了id,添加一个depth参数

1
2
3
4
5
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
depth = 1

序列化后的数据如下:

1
2
3
4
5
6
7
8
{
"id":2,
"title": "天龙八部",
"authors": [
{"id":1 , "name": "a"},
{"id":2 , "name": "b"},
]
}

depth=1,意为找到一层关联的数据,即找到publisher关联表,如果publisher中有还有关联的表,不会再往下去找。

注意:

  1. depth最多不要超过四层
  2. depth会让所有的外键关系字段变成read_only=True
  3. 生产环境depth不常用

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BookSerializer(serializers.ModelSerializer):

class Meta:
model = Book
fields = '__all__'
# exclude = ['id']
# depth = 1 # 注意: depth会让所有的外键关系字段变成read_only=True
extra_kwargs = {
'publisher': {'write_only': True},
'authors': {'write_only': True},
}
# 重写正序字段
publisher_info = serializers.SerializerMethodField(read_only=True)
# SerializerMethodField是一个方法字段,即需要一个方法的返回值再赋给该字段
authors_info = serializers.SerializerMethodField(read_only=True)

def get_publisher_info(self, obj):
publisher_obj = obj.publisher
print(publisher_obj)
return {'id': publisher_obj.id, 'title': publisher_obj.title}
def get_authors_info(self, obj):
authors_queryset = obj.authors.all()
return [{'id': author.id, 'name': author.name} for author in authors_queryset]

外键关联的字段正序使用的是publisher_info与authors_info,那么反序还是使用的publisher与authors字段。字段的校验与上面的一样。

自定义序列字段的两种写法

这里所说的自定义序列字段可以另一张表中的字段,也可以自定要序列化的字段,也可以是外键关联的字段。
这种字段可以使用serializers.SerializerMethodField方法写在序列化器中
也可以在模型类中定义方法

1:写在序列化器中

1
2
3
4
5
6
7
8
9
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.SerializerMethodField()

def get_publisher(self, obj):
return obj.publisher.name

class Meta:
model = Book
fields = ['name', 'price', 'publisher']

注意:使用serializers.SerializerMethodField方法时,在fields列表中必须写上该字段,如上面代码中的publisher。如果fields列表中没有指定publiser字段,会报如下异常:

1
The field 'publisher' was declared on serializer BookSerializer, but has not been included in the 'fields' option.

2:写在模型类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2)
img = models.ImageField(upload_to='img', default='img/default.jpg')
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')

class Meta:
db_table = 'gin_book'

def __str__(self):
return self.name

@property
def publisher_name(self): # 定义成一个方法属性,方法名与属性名不能重复
return self.publisher.name

序列化类中的fields列表,可以指定publisher_name;不指定也不报异常

1
2
3
4
5
class BookSerializer(serializers.ModelSerializer):

class Meta:
model = Book
fields = ['name', 'price', 'publisher_name'] # 不指定publisher_name字段不会抛异常

使用该方法时反序列化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'publisher_name', 'img', 'author_list', 'publisher', 'authors']
extra_kwargs = {
'publisher': {'write_only': True},
'authors': {'write_only': True},
'name': {
'required': True,
'min_length': 3,
'error_messages': {
'required': '必填项',
'min_length': '至少3个字符'
}
}
}

视图类:

1
2
3
4
5
6
7
8
9
10
class BooksView(APIView):
def get(self, request):
queryset = Book.objects.filter(is_delete = False)
serializer = BookSerializer(queryset, many = True)
return Response(serializer.data)
def post(self, request):
ser_obj = BookSerializer(data = request.data)
ser_obj.is_valid(raise_exception=True) # 当校验失败会马上终止当前视图方法,抛出异常给前台
book_obj = ser_obj.save()
return Response({'status': 0, 'message': 'OK', 'result': BookSerializer(book_obj).data})

自定义联表深度(嵌套)

模型类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2)
img = models.ImageField(upload_to='img', default='img/default.jpg')
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
class Meta:
db_table = 'gin_book'
def __str__(self):
return self.name

class Publisher(BaseModel):
name = models.CharField(max_length=64)
address = models.CharField(max_length=64)
class Meta:
db_table = 'gin_publisher'
def __str__(self):
return self.name

需求:Book表序列化时,也要序列化Pulisher表中指定的字段

1
2
3
4
5
6
7
8
9
10
11
class PublisherSerializer(serializers.ModelSerializer):
class Meta:
model = Publisher
fields = ['name', 'address']

class BookSerializer(serializers.ModelSerializer):
# 自定义连表深度 - 子序列化方式
publisher = PublisherSerializer()
class Meta:
model = Book
fields = ['name', 'price', 'publisher']

Django Rest Framework之序列化器

单增群增

序列化器如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BookSerializer(serializers.ModelSerializer):

class Meta:
model = Book
fields = ['name', 'price', 'publisher_name', 'img', 'author_list', 'publisher', 'authors']
extra_kwargs = {
'publisher': {'write_only': True},
'authors': {'write_only': True},
'name': {
'required': True,
'min_length': 3,
'error_messages': {
'required': '必填项',
'min_length': '至少3个字符'
}
}
}

def validate_name(self, value):
if Book.objects.filter(name = value).exists():
raise serializers.ValidationError('书籍名已存在')
return value

def validate(self, attrs):
# todo: do something
return attrs

单增群增,序列化器不用改。但需要判断前台数据为字典时为单增,为列表时是群增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class BooksView(APIView):
def get(self, request):
queryset = Book.objects.filter(is_delete=False)
serializer = BookSerializer(queryset, many=True)
return Response(serializer.data)

def post(self, request):
if isinstance(request.data, dict):
many = False
elif isinstance(request.data, list):
many = True
else:
return Response({'status': 1, 'message': 'invalid data'})

ser_obj = BookSerializer(data=request.data, many=many)
ser_obj.is_valid(raise_exception=True) # 当校验失败会马上终止当前视图方法,抛出异常给前台
results = ser_obj.save()
return Response({'status': 0, 'message': 'OK', 'result': BookSerializer(results, many=many).data})

前台传递数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"name": "better man",
"price": 96.66,
"authors": [1,3],
"publisher": 1
},
{
"name": "tomorrow will be better",
"price": 100,
"authors": [2],
"publisher": 2
}
]

单删群删

1
2
3
4
5
6
7
8
9
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
pks = [pk] # 单删在URL中传递主键id
else:
pks = request.data.get('pks') # 群删传递JSON数据:{'pks': [2,4,5,6]}
if Book.objects.filter(pk__in = pks, is_delete = False).update(is_delete = True):
return Response({'status': 0, 'message': 'success'})
return Response({'status': 0, 'message': 'failed'})

修改数据

1:单整体修改
即修改数据时所传递的数据字段不能少

1
2
3
4
5
6
7
8
def put(self, request, pk):
book_obj = Book.objects.filter(pk = pk).first()
serializer = BookSerializer(instance = book_obj, data = request.data)
# 有 instance = book_obj 参数,当serializer.save()时会走update方法;如果没有instance = book_obj 参数会走create方法
# data = request.data 必须要有,因为下面的is_valid方法校验的就是data中的数据
serializer.is_valid(raise_exception=True)
book = serializer.save()
return Response(BookSerializer(book).data, status = status.HTTP_201_CREATED)

前端传递数据如下:

1
2
3
4
5
6
{
"name": "take me to you heart",
"price": 68.3,
"publisher": 1,
"authors": [1]
}

2:单局部修改
即前端只传递部分字段,传递过来的字段做数据校验,校验通过后更新;没传递过来的字段不做数据校验

1
2
3
4
5
6
7
def patch(self, request, pk):
book_obj = Book.objects.filter(pk=pk).first()
# 方法同上,只是多了个 partial=True 参数;即传递过来的字段做校验,没有传递过来的字段不做校验
serializer = BookSerializer(instance=book_obj, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
book = serializer.save()
return Response(BookSerializer(book).data, status=status.HTTP_201_CREATED)

批量修改数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class BooksAPIView(APIView):
........................
# 请求数据格式:[{pk:1, name: 123}, {}, {}]
def patch(self, request, *args, **kwargs):
request_data = request.data
pk = kwargs.get('pk')
if pk and isinstance(request_data, dict):
# 单改
pks = [pk]
request_data = [request.data]
elif not pk and isinstance(request_data, list):
# 群改
pks = []
for dic in request_data:
pk = dic.pop('pk', None)
pks.append(pk)
if not pks:
return Response({'status': 1, 'message': 'invalid data', 'result': ''})
else:
return Response({'status': 1, 'message': 'invalid data', 'result': ''})
objs = []
new_request_data = []

for index, pk in enumerate(pks):
try:
obj = Book.objects.get(pk = pk, is_delete = False)
objs.append(obj)
new_request_data.append(request_data[index])
except Exception as e:
continue
print(objs)
print(new_request_data)
book_ser = BookSerializer(instance = objs, data = new_request_data, partial = True, many = True)
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return Response({'status': 1, 'message': 'success', 'result': BookSerializer(book_obj, many = True).data})

注意:批量修改数据,序列化器中必须要有ListSerializer,如果没有会抛出异常:

1
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.

序列化类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ListSerializer与ModelSerializer建立关联的是:ModelSerializer的Meta类的 list_serializer_class
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
print(instance) # 要更新的对象们
print(self.child) # 服务的模型序列化类(BookSerializer)

for index, obj in enumerate(instance):
self.child.update(obj, validated_data[index])
return instance

class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'publisher', 'img', 'author_list', 'publisher_name', 'authors']
extra_kwargs = {
'name': {'required': True, 'min_length': 1, 'error_messages': {'required': '必填项', 'min_length': '至少3个字符'}},
'publisher': {'write_only': True},
'authors': {'write_only': True}
}
list_serializer_class = BookListSerializer

视图类与序列化类传参

在视图类中,可以通过request得到登陆用户request.user
在序列化类中,要完成数据库数据的校验与入库操作,可能会需要知道当前的登录用户,但序列化类无法访问request对象
在视图类中实例化序列化对象时,可以通过context参数将request对象传递进去

1
book_ser = BookSerializer(context = {'request': request}, instance = objs, data = new_request_data, partial = True, many = True)

序列化类中可以在钩子函数中接受该参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'publisher', 'img', 'author_list', 'publisher_name', 'authors']
extra_kwargs = {
'name': {'required': True, 'min_length': 1, 'error_messages': {'required': '必填项', 'min_length': '至少3个字符'}},
'publisher': {'write_only': True},
'authors': {'write_only': True}
}
list_serializer_class = BookListSerializer

def validate(self, attrs):
print(self.context.get('request').method)
return attrs

序列化组件修改返回值to_representation、to_internal_value

to_representation(self, instance):如果序列化器定义了此方法,可以改变序列化对象data的值,也就是serializer.data的值,你可以根据自己的业务场景去重新构造返回值。

1
2
3
4
5
def to_representation(self, instance):
"""Convert `username` to lowercase."""
ret = super().to_representation(instance)
ret['username'] = ret['username'].lower()
return ret

to_internal_value(self, data): data为未经校验的数据字段, 此方法可以实现校验和修改反序列化后的值,然后返回。如果不想修改反序列化后的值只是做校验的话,完全可以使用validate方法替代。

1
2
3
4
def to_internal_value(self, value):
if value == None:
return 0
return value

总而言之,这两个方法一个是用于重新构造validated_data并返回,一个用于重新构造serializer.data的值并返回。


Django REST framework 序列化
https://flepeng.github.io/021-Python-32-框架-Django-REST-framework-Django-REST-framework-序列化/
作者
Lepeng
发布于
2021年8月18日
许可协议