前言
在DRF-Serialization这篇文章中简单介绍了Serialization的使用,更多详细内容请参看官方文档。
本文主要对实际开发过程遇到的问题及处理进行总结。
字段处理
只对指定的字段进行数据处理
比如django自带的User models有许多字段,但我们只想对其中的username
, email
, password
进行处理。
这个很简单,通过filed指定即可:
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
fields = ('username', 'email', 'password')
如果设置fields = '__all__'
,代表处理User model的全部字段。
与fields
相对的是exclude
,exclude代表这里面的字段我都不处理。比如我不想处理User
的email
字段,而除它之外的其他字段都要进行处理:
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
exclude = ('email')
字段只读/只写
只读
我们知道ModelSerializer
和ViewSet
结合使用,能够迅速地实现一个表数据的增删查改。
但,有时我们希望修改表数据实例时不能对某些指定字段进行修改,可设置字段为只读。
比如,我们希望修改用户表实例数据时,不能修改用户的密码。
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
fields = ('username', 'email', 'password')
extra_kwargs = {'password': {'read_only': True},}
这样的话,当发送PUT请求`http//127.0.0.1/user/
{
"username": "test",
"password": "test_password",
"email": "[email protected]"
}
,我们的UserSerializer并不会对password
字段进行数据处理,所以并不会去修改用户的密码。
当然当我们使用GET请求查询数据时,返回的数据仍然会携带password
字段。
只写
与read_only
对应的就是write_only
了。
设置了'write_only': True
,代表该字段只可写,而不可读。
同样以password
为例,为了安全,当返回用户列表的时候,我们并不想返回用户的password
字段。
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
fields = ('username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True},}
字段必需
又比如某些字段在model定义为不能为空,对应到serializer中即required=True
,但在实际生活中,可能不希望修改此字段。
比如一些系统要求用户名固定,那么用户修改界面,不希望用户能够修改自己的用户名,是不是使用上面介绍的只读就可以了呢?
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
fields = ('username', 'email', 'password')
extra_kwargs = {'username': {'read_only': True},}
当我们用body参数
{
"email": "[email protected]"
}
向我们的viewset发送PUT请求后,会收到系统返回类似field username is required
的错误返回。也就是希望我们在body中携带"username"参数,但实际上因为限制了read_only: True
,就算携带了username
也毫无意义,那么怎么规避掉这个错误返回呢?这个时候就可以设置required: False
class UserSerializer(serializers.ModelSerializer):
'''
user类数据解析
'''
class Meta:
model = User # 定义关联的 Model
fields = ('username', 'email', 'password')
extra_kwargs = {
'username': {
'read_only': True,
'required': False
}
}
自定义字段验证
通过在searializer类中自定义.validate_<field_name>
方法或者指定字段的Validators
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.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value
或者
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])
...
字段间的验证或者说对象级别的验证
通过在searializer类中自定义.validate()
方法
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.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data
context
我们定义serializer
是用来做数据的序列化和反序列化的,定义的serializer
类通常在view
视图中调用,如果我们需要serializer
调用view
视图中的某些属性应该怎么做呢?
比如我们需要在serializer
中调用view
视图中的request
请求?这时候context
就派上用场了
class UserSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
request = self.context['request']
那么content
中有哪些参数呢?这些参数又是怎么传进去的呢?
让我们来看看源码:
首先在APIView
或者ViewSet
中是通过request.data生成具体的serializer
对象的呢?找到rest_framework/generics.py
通用的基类文件
# generics.py
class GenericAPIView(views.APIView):
...
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
...
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
可以看到在封装好的get_serializer_context
函数中将request
等参数传给了content
参数中,而在get_serializer
函数中构造serializer
类对象时又传入content了,使得我们能在serializer
类中调用self.context['request']
而不报错。
看到这里,我们很容易也就知道通过在我们定义的的view
中重载get_serializer_context
,可以自定义我们想要传到serializer
中的参数。
Serializer relations
关系字段用于表示模型关系。它们可以应用于ForeignKey,ManyToManyField和OneToOneField关系,以及反向关系和自定义关系,例如GenericForeignKey。
from django.contrib.auth.models import User
from django.db import models
class UserProfile(models.Model):
name = models.CharField(null=True, max_length=30)
gender = models.IntegerField(null=True, verbose_name="性别")
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True)
比如像这种带有关系字段user
的,是怎么用serializer
进行处理user
的呢?
# serializers.py
class ProfileSerializer(serializers.ModelSerializer):
'''
用户扩展类数据解析
'''
class Meta:
model = UserProfile
fields = '__all__'
(.venv) ➜ Cube git:(dev) ✗ python cube/manage.py shell
Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from users.serializers import ProfileSerializer
In [2]: print(ProfileSerializer)
<class 'users.serializers.ProfileSerializer'>
In [3]: serializer = ProfileSerializer()
In [4]: serializer
Out[4]:
ProfileSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_null=True, max_length=30, required=False)
gender = IntegerField(allow_null=True, label='性别', max_value=2147483647, min_value=-2147483648, required=False)
user = PrimaryKeyRelatedField(allow_null=True, queryset=User.objects.all(), required=False, validators=[<UniqueValidator(queryset=UserProfile.objects.all())>])
可以看到,serializer
默认使用PrimaryKeyRelatedField
字段来定义model中外键(包括一对一,一对多),其序列化的数据格式为:
{
"id": 2,
"name": "admin",
"gender": 1,
"user": 1
}
当然我们也可以使用其他field来定义,类似StringRelatedField
。StringRelatedField
可用于定义了__str__
方法的model。
user = StringRelatedField()
user返回的数据则是__str__
方法中定义的数据。这里的user
对应的是model中外键的字段名
类似的字段还有HyperlinkedRelatedField
、SlugRelatedField
、HyperlinkedIdentityField
,具体可参看官方文档。
serializer 嵌套
当我们对UserProfile
进行数据校验和序列化的时候,还想对UserProfile
中外键关联的User
model进行校验和序列化,该怎么做呢?
serializer
支持嵌套,也就是可以在serializer
中定义serializer
,比如:
# serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email')
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = '__all__'
那么序列化后数据类似:
{
"id": 2,
"name": "admin",
"gender": 1,
"user": {
"username": "admin",
"email": "[email protected]"
}
}
当然上面的User
和UserProfile
的关联是通过外键实现的,若没有外键,则需要自己去建立这一层关系。
举个🌰,将user
外键改成一个普通的int字段:
class UserProfile(models.Model):
name = models.CharField(null=True, max_length=30)
gender = models.IntegerField(null=True, verbose_name="性别")
user = models.IntegerField(null=True, verbose_name="user id")
然后再来实现我们的serializer
:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
try:
user = User.objects.get(id=instance.user)
except User.DoesNotExist:
ret['user'] = None
else:
serializer = UserSerializer(user)
if serializer.is_valid:
ret['profile'] = serializer.data
return ret
-> instance = UserProfile.objects.get(id=2)
-> serializer = ProfileSerializer(instance)
-> print(serializer.data)
-> {
-> "id": 2,
-> "name": "admin",
-> "gender": 1,
-> "user": {
-> "username": "admin",
-> "email": "[email protected]"
-> }
-> }