1. 程式人生 > >08.DRF-反序列化

08.DRF-反序列化

## 三、反序列化使用 ### 3.1 驗證 使用序列化器進行反序列化時,需要對資料進行驗證後,才能獲取驗證成功的資料或儲存成模型類物件。 在獲取反序列化的資料前,必須呼叫**is_valid()**方法進行驗證,驗證成功返回True,否則返回False。 驗證失敗,可以通過序列化器物件的**errors**屬性獲取錯誤資訊,返回字典,包含了欄位和欄位的錯誤。如果是非欄位錯誤,可以通過修改REST framework配置中的**NON_FIELD_ERRORS_KEY**來控制錯誤字典中的鍵名。 驗證成功,可以通過序列化器物件的**validated_data**屬性獲取資料。 在定義序列化器時,指明每個欄位的序列化型別和選項引數,本身就是一種驗證行為。 如我們前面定義過的BookInfoSerializer ```python 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構造引數,進而進行驗證 ```python 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響應。 ```python # Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True) ``` 如果覺得這些還不夠,需要再補充定義驗證行為,可以使用以下三種方法: #### 3.1.1)validators 在欄位中新增validators選項引數,也可以補充驗證行為,如 ```python 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) ``` 測試: ```python 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_` 對``欄位進行驗證,如 ```python class BookInfoSerializer(serializers.Serializer): """圖書資料序列化器""" ... def validate_btitle(self, value): if 'django' not in value.lower(): raise serializers.ValidationError("圖書不是關於Django的") return value ``` 測試 ```python 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方法來驗證,如 ```python class BookInfoSerializer(serializers.Serializer): """圖書資料序列化器""" ... def validate(self, attrs): bread = attrs['bread'] bcomment = attrs['bcomment'] if bread < bcomment: raise serializers.ValidationError('閱讀量小於評論量') return attrs ``` 測試 ```python 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 單欄位唯一,如 ```python from rest_framework.validators import UniqueValidator slug = SlugField( max_length=100, validators=[UniqueValidator(queryset=BlogPost.objects.all())] ) ``` - UniqueTogetherValidation 聯合唯一,如 ```python 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()兩個方法來實現。 ```python 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 ``` 如果需要在返回資料物件的時候,也將資料儲存到資料庫中,則可以進行如下修改 ```python 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()方法返回一個數據物件例項了 ```python book = serializer.save() ``` 如果建立序列化器物件的時候,沒有傳遞instance例項,則呼叫save()方法的時候,create()被呼叫,相反,如果傳遞了instance例項,則呼叫save()方法的時候,update()被呼叫。 ```python from db.serializers import BookInfoSerializer data = {'btitle': '封神演義'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # True serializer.save() # from db.models import BookInfo book = BookInfo.objects.get(id=2) data = {'btitle': '倚天劍'} serializer = BookInfoSerializer(book, data=data) serializer.is_valid() # True serializer.save() # book.btitle # '倚天劍' ``` #### 兩點說明: 1) 在對序列化器進行save()儲存時,可以額外傳遞資料,這些資料可以在create()和update()中的validated_data引數獲取到 ```python serializer.save(owner=request.user) ``` 2)預設序列化器必須傳遞所有required的欄位,否則會丟擲驗證異常。但是我們可以使用partial引數來允許部分欄位更新 ```python # Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=Tr