三、反序列化使用 3.1 驗證 使用序列化器進行反序列化時,需要對數據進行驗證後,才能獲取驗證成功的數據或保存成模型類對象。 在獲取反序列化的數據前,必須調用**is_valid()**方法進行驗證,驗證成功返回True,否則返回False。 驗證失敗,可以通過序列化器對象的errors屬性獲取錯誤 ...
三、反序列化使用
3.1 驗證
使用序列化器進行反序列化時,需要對數據進行驗證後,才能獲取驗證成功的數據或保存成模型類對象。
在獲取反序列化的數據前,必須調用is_valid()方法進行驗證,驗證成功返回True,否則返回False。
驗證失敗,可以通過序列化器對象的errors屬性獲取錯誤信息,返回字典,包含了欄位和欄位的錯誤。如果是非欄位錯誤,可以通過修改REST framework配置中的NON_FIELD_ERRORS_KEY來控制錯誤字典中的鍵名。
驗證成功,可以通過序列化器對象的validated_data屬性獲取數據。
在定義序列化器時,指明每個欄位的序列化類型和選項參數,本身就是一種驗證行為。
如我們前面定義過的BookInfoSerializer
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名稱', max_length=20)
bpub_date = serializers.DateField(label='發佈日期', required=False)
bread = serializers.IntegerField(label='閱讀量', required=False)
bcomment = serializers.IntegerField(label='評論量', required=False)
image = serializers.ImageField(label='圖片', required=False)
通過構造序列化器對象,並將要反序列化的數據傳遞給data構造參數,進而進行驗證
from booktest.serializers import BookInfoSerializer
data = {'bpub_date': 123}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # 返回False
serializer.errors
# {'btitle': [ErrorDetail(string='This field is required.', code='required')], 'bpub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')]}
serializer.validated_data # {}
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # True
serializer.errors # {}
serializer.validated_data # OrderedDict([('btitle', 'python')])
is_valid()方法還可以在驗證失敗時拋出異常serializers.ValidationError,可以通過傳遞raise_exception=True參數開啟,REST framework接收到此異常,會向前端返回HTTP 400 Bad Request響應。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
如果覺得這些還不夠,需要再補充定義驗證行為,可以使用以下三種方法:
3.1.1)validators
在欄位中添加validators選項參數,也可以補充驗證行為,如
def about_django(value):
if 'django' not in value.lower():
raise serializers.ValidationError("圖書不是關於Django的")
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django])
bpub_date = serializers.DateField(label='發佈日期', required=False)
bread = serializers.IntegerField(label='閱讀量', required=False)
bcomment = serializers.IntegerField(label='評論量', required=False)
image = serializers.ImageField(label='圖片', required=False)
測試:
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # False
serializer.errors
# {'btitle': [ErrorDetail(string='圖書不是關於Django的', code='invalid')]}
3.1.2)validate_<field_name>
對<field_name>
欄位進行驗證,如
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
...
def validate_btitle(self, value):
if 'django' not in value.lower():
raise serializers.ValidationError("圖書不是關於Django的")
return value
測試
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # False
serializer.errors
# {'btitle': [ErrorDetail(string='圖書不是關於Django的', code='invalid')]}
3.1.3)validate
在序列化器中需要同時對多個欄位進行比較驗證時,可以定義validate方法來驗證,如
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
...
def validate(self, attrs):
bread = attrs['bread']
bcomment = attrs['bcomment']
if bread < bcomment:
raise serializers.ValidationError('閱讀量小於評論量')
return attrs
測試
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'about django', 'bread': 10, 'bcomment': 20}
s = BookInfoSerializer(data=data)
s.is_valid() # False
s.errors
# {'non_field_errors': [ErrorDetail(string='閱讀量小於評論量', code='invalid')]}
REST framework提供的validators:
-
UniqueValidator
單欄位唯一,如
from rest_framework.validators import UniqueValidator slug = SlugField( max_length=100, validators=[UniqueValidator(queryset=BlogPost.objects.all())] )
-
UniqueTogetherValidation
聯合唯一,如
from rest_framework.validators import UniqueTogetherValidator class ExampleSerializer(serializers.Serializer): # ... class Meta: validators = [ UniqueTogetherValidator( queryset=ToDoItem.objects.all(), fields=('list', 'position') ) ]
3.2 保存
如果在驗證成功後,想要基於validated_data完成數據對象的創建,可以通過實現create()和update()兩個方法來實現。
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
...
def create(self, validated_data):
"""新建"""
return BookInfo(**validated_data)
def update(self, instance, validated_data):
"""更新,instance為要更新的對象實例"""
instance.btitle = validated_data.get('btitle', instance.btitle)
instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
instance.bread = validated_data.get('bread', instance.bread)
instance.bcomment = validated_data.get('bcomment', instance.bcomment)
return instance
如果需要在返回數據對象的時候,也將數據保存到資料庫中,則可以進行如下修改
class BookInfoSerializer(serializers.Serializer):
"""圖書數據序列化器"""
...
def create(self, validated_data):
"""新建"""
return BookInfo.objects.create(**validated_data)
def update(self, instance, validated_data):
"""更新,instance為要更新的對象實例"""
instance.btitle = validated_data.get('btitle', instance.btitle)
instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
instance.bread = validated_data.get('bread', instance.bread)
instance.bcomment = validated_data.get('bcomment', instance.bcomment)
instance.save()
return instance
實現了上述兩個方法後,在反序列化數據的時候,就可以通過save()方法返回一個數據對象實例了
book = serializer.save()
如果創建序列化器對象的時候,沒有傳遞instance實例,則調用save()方法的時候,create()被調用,相反,如果傳遞了instance實例,則調用save()方法的時候,update()被調用。
from db.serializers import BookInfoSerializer
data = {'btitle': '封神演義'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # True
serializer.save() # <BookInfo: 封神演義>
from db.models import BookInfo
book = BookInfo.objects.get(id=2)
data = {'btitle': '倚天劍'}
serializer = BookInfoSerializer(book, data=data)
serializer.is_valid() # True
serializer.save() # <BookInfo: 倚天劍>
book.btitle # '倚天劍'
兩點說明:
1) 在對序列化器進行save()保存時,可以額外傳遞數據,這些數據可以在create()和update()中的validated_data參數獲取到
serializer.save(owner=request.user)
2)預設序列化器必須傳遞所有required的欄位,否則會拋出驗證異常。但是我們可以使用partial參數來允許部分欄位更新
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)