知識點:Serializer(偏底層)、ModelSerializer(重點)、ListModelSerializer(輔助羣改)前端
爲何要使用序列化組件?
視圖中查詢到的對象和queryset類型不能直接做爲數據返回給前臺,因此要使用序列化組件python
路由層 urls.pygit
from django.urls import path, re_path from .views import users urlpatterns = [ path('bookinfo/', users.BookInfo.as_view()), re_path('bookinfo/(?P<pk>.*)/$', users.BookInfo.as_view()), ]
模型層:models.py數據庫
class BookInfo(models.Model): PUB_CHOICES = [ (0, '商務印書館'), (1, '人民出版社'), (2, '人民文學出版社 '), (3, '做家出版社') ] pwd = models.CharField(max_length=32, verbose_name='密碼') publisher = models.IntegerField(choices=PUB_CHOICES, default=0, verbose_name='出版社') btitle = models.CharField(max_length=20, verbose_name='名稱') bpub_date = models.DateField(verbose_name='發佈日期', null=True) created_time = models.DateTimeField(auto_now_add=True, verbose_name="建立時間", help_text='建立時間') bread = models.IntegerField(default=0, verbose_name='閱讀量') bcomment = models.IntegerField(default=0, verbose_name='評論量') image = models.ImageField(upload_to='icon', verbose_name='圖片', default='icon/default.jpg') class Meta: db_table = 'BookInfo' verbose_name = '書籍信息' verbose_name_plural = verbose_name def __str__(self): return '%s' % self.btitle
爲這個模型類提供一個序列化器django
from rest_framework import serializers class BookInfoSerializer(serializers.Serializer): """圖書數據序列化器""" id = serializers.IntegerField(label='ID', read_only=True) pwd = serializers.CharField(label='密碼', required=True) publisher = serializers.IntegerField(label='出版社') btitle = serializers.CharField(label='名稱', max_length=20) bpub_date = serializers.DateField(label='發佈日期', required=False) created_time = serializers.DateTimeField(label='建立時間', required=False) bread = serializers.IntegerField(label='閱讀量', required=False) bcomment = serializers.IntegerField(label='評論量', required=False) image = serializers.ImageField(label='圖片', required=False) """ 自定義序列化屬性 格式: 屬性名隨意,值由固定的命名規範方法提供 def get_屬性名(self, 參與序列化的model對象): 返回值就是自定義序列化屬性的值 """ # 出版社顯示名稱,而不是0,1。。。 publisher_name = serializers.SerializerMethodField() def get_publisher_name(self, obj): # choice類型的解釋型值 get_字段_display() 來訪問 return obj.get_publisher_display() # 圖片顯示全路徑 image_path = serializers.SerializerMethodField() def get_image_path(self, obj): # settings.MEDIA_URL: 本身配置的 /media/,給後面高級序列化與視圖類準備的 # obj.icon不能直接做爲數據返回,由於內容雖然是字符串,可是類型是ImageFieldFile類型 return '%s%s%s' % (r'http://127.0.0.1:8000', settings.MEDIA_URL, str(obj.image)) # 自定義虛擬閱讀量,原基礎增長10 fictitious_bread = serializers.SerializerMethodField() def get_fictitious_bread(self, obj): return obj.bread + 10
注意:serializer不是隻能爲數據庫模型類定義,也能夠爲非數據庫模型類的數據定義。serializer是獨立於數據庫以外的存在。json
經常使用字段類型:api
字段 | 字段構造方式 |
---|---|
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頁面時,顯示的字段幫助提示信息 |
定義好Serializer類後,就能夠建立Serializer對象了。服務器
Serializer的構造方法爲:restful
Serializer(instance=None, data=empty, **kwarg)
說明:
1)用於序列化時,將模型類對象傳入instance參數
2)用於反序列化時,將要被反序列化的數據傳入data參數
3)除了instance和data參數外,在構造Serializer對象時,還可經過context參數額外添加數據,如
serializer = AccountSerializer(account, context={'request': request})
經過context參數附加的數據,能夠經過Serializer對象的context屬性獲取。
使用序列化器的時候必定要注意,序列化器聲明瞭之後,不會自動執行,須要咱們在視圖中進行調用才能夠
序列化器沒法直接接收數據,須要咱們在視圖中建立序列化器對象時把使用的數據傳遞過來。(data,instance傳參)
序列化是:數據對象從數據庫中查出,經過instance傳入序列化器中,必須經過data屬性才能將序列化後的數據傳給前端,不能直接傳序列化對象
反序列化是:數據是經過request.data從前端獲取到數據,經過data傳入序列化器中進行校驗,保存到數據庫中
序列化器的字段聲明相似於咱們前面使用過的表單系統
開發restful api時,序列化器會幫咱們把模型數據轉換成字典。
drf提供的視圖會幫咱們把字典轉換成json,或者把客戶端發過來的數據轉換成字典
序列化器的使用分兩個階段:
from .. import models from ..serializers import BookInfoSerializer class BookInfo(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: try: # 1) 查詢出圖書對象 book_obj = models.BookInfo.objects.get(pk=pk) # 2) 構造序列化器 book_ser = BookInfoSerializer(book_obj) # 3) 獲取序列化數據 return Response({ 'status': 200, 'msg': 0, # 在序列化的時候沒有.data,那麼在傳給前端的時候必需要.data 'results': book_ser.data # 經過data屬性能夠獲取序列化後的數據 }) except: return Response({ 'status': 201, 'msg': '書籍信息不存在', }) else: # 1) 查詢出圖書對象:對象列表(queryset)不能直接做爲數據返回給前臺 book_obj_list = models.BookInfo.objects.all() # 2) 構造序列化器 PS:將對象交給序列化處理,產生序列化對象,若是序列化的數據是由[]嵌套,必定要設置many=True book_ser_data = BookInfoSerializer(book_obj_list, many=True) # 3) 獲取序列化數據 return Response({ 'status': 200, 'msg': 0, 'results': book_ser_data.data # 經過data屬性能夠獲取序列化後的數據 })
class BookInfoDeSerializer(serializers.Serializer): """圖書序列化器""" id = serializers.IntegerField(label='ID', read_only=True) pwd = serializers.CharField(label='密碼', required=True) publisher = serializers.IntegerField(label='出版社', required=False) btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django]) bpub_date = serializers.DateField(label='發佈日期', required=False) created_time = serializers.DateTimeField(label='建立時間', required=False) bread = serializers.IntegerField(label='閱讀量', required=True) bcomment = serializers.IntegerField(label='評論量', required=True) image = serializers.ImageField(label='圖片', required=False) # 自定義有校驗規則的反序列化字段,例如確認密碼字段re_pwd re_pwd = serializers.CharField(required=True)
經過構造序列化器對象,並將要反序列化的數據傳遞給data構造參數,進而進行驗證
class BookInfo(APIView): # 單增 def post(self, request, *args, **kwargs): request_data = request.data if not isinstance(request_data, dict) or request_data == {}: return Response({ 'status': 1, 'msg': '數據有誤', }) book_ser = BookInfoDeSerializer(data=request_data) # 序列化對象調用is_valid()完成校驗,校驗失敗的失敗信息都會被存儲在 序列化對象.errors # 檢驗是否合格 raise_exception=True必填的 book_ser.is_valid(raise_exception=True) # book_result是對象<class 'app01.models.Book'>,羣增就是列表套一個個對象 book_obj = book_ser.save() return Response({ 'status': 200, 'msg': 'ok', 'results': BookInfoSerializer(instance=book_obj).data }) 上面兩句和下面是同樣的 # if book_ser.is_valid(): # # 校驗經過,完成新增 # book_obj = book_ser.save() # return Response({ # 'status': 0, # 'msg': 'ok', # 'results': BookInfoSerializer(instance=book_obj).data # }) # else: # # 校驗失敗 # return Response({ # 'status': 1, # 'msg': book_ser.errors, # })
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)
若是還不夠,須要在補充定義驗證行爲,可使用一下三種方法
validate_<field_name>
-局部鉤子對<field_name>
字段進行驗證
局部鉤子:validate_要校驗的字段名(self, 當前要校驗字段的值)
校驗規則:校驗經過返回原值,校驗失敗,拋出異常
class BookInfoSerializer(serializers.Serializer): """圖書數據序列化器""" ... # 局部鉤子:validate_要校驗的字段名(self, 當前要校驗字段的值) # 校驗規則:校驗經過返回原值,校驗失敗,拋出異常 def validate_btitle(self, value): if 'django' not in value.lower(): raise exceptions.ValidationError('圖書不是關於Django的') return value
測試
http://127.0.0.1:8000/bookinfo/ { "btitle":"紅樓夢", "bpub_date":"2020-10-07" } { "btitle": [ "圖書不是關於Django的" ] }
在序列化器中須要同時對多個字段進行比較驗證時,能夠定義validate方法來驗證
全局鉤子:validate(self, 經過系統與局部鉤子校驗以後的全部數據)
class BookInfoSerializer(serializers.Serializer): """圖書數據序列化器""" ... # 全局鉤子:validate(self, 經過系統與局部鉤子校驗以後的全部數據) def validate(self, attrs): # attrs是字典格式 pwd = attrs.get('pwd') re_pwd = attrs.pop('re_pwd') # 由於re_pwd不須要存入數據庫,因此在全局鉤子校驗中刪除掉這個字段 bread = attrs['bread'] bcomment = attrs['bcomment'] if pwd != re_pwd: raise exceptions.ValidationError({'pwd&re_pwd': '兩次密碼不一致'}) if bread < bcomment: raise serializers.ValidationError('閱讀量小於評論量') return attrs
def about_django(value): if 'django' not in value.lower(): raise serializers.ValidationError("validators-圖書不是關於Django的") class BookInfoDeSerializer(serializers.Serializer): id = serializers.IntegerField(label='ID', read_only=True) pwd = serializers.CharField(label='密碼', required=True) publisher = serializers.IntegerField(label='出版社', required=False) btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django]) bpub_date = serializers.DateField(label='發佈日期', required=False) created_time = serializers.DateTimeField(label='建立時間', required=False) bread = serializers.IntegerField(label='閱讀量', required=True) bcomment = serializers.IntegerField(label='評論量', required=True) image = serializers.ImageField(label='圖片', required=False)
測試:
Copyfrom booktest.serializers import BookInfoSerializer data = {'btitle': 'python'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # False serializer.errors # {'btitle': [ErrorDetail(string='圖書不是關於Django的', code='invalid')]}
validators--->validate_<field_name>(局部)----->validate(全局)
若是在驗證成功後,想要基於validated_data完成數據對象的建立,能夠經過實現create()和update()兩個方法來實現。
class BookInfoSerializer(serializers.Serializer): """圖書數據序列化器""" ... # 要完成新增,必須重寫create方法,validated_data是校驗的數據 def create(self, validated_data): # 儘可能在全部校驗規則完畢以後,數據能夠直接入庫 return models.User.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.pwd = validated_data.get('pwd', instance.pwd) 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()被調用。
models.py
from django.db import models class BookInfo(models.Model): PUB_CHOICES = [ (0, '商務印書館'), (1, '人民出版社'), (2, '人民文學出版社 '), (3, '做家出版社') ] pwd = models.CharField(max_length=32, verbose_name='密碼') publisher = models.IntegerField(choices=PUB_CHOICES, default=0, verbose_name='出版社') btitle = models.CharField(max_length=20, verbose_name='名稱') bpub_date = models.DateField(verbose_name='發佈日期', null=True) created_time = models.DateTimeField(auto_now_add=True, verbose_name="建立時間", help_text='建立時間') bread = models.IntegerField(default=0, verbose_name='閱讀量') bcomment = models.IntegerField(default=0, verbose_name='評論量') image = models.ImageField(upload_to='icon', verbose_name='圖片', default='icon/default.jpg') class Meta: db_table = 'BookInfo' verbose_name = '書籍信息' verbose_name_plural = verbose_name def __str__(self): return '%s' % self.btitle
路由:urls
from django.urls import path, re_path from .views import test, users, books, v2books urlpatterns = [ path('bookinfo/', users.BookInfo.as_view()), re_path('bookinfo/(?P<pk>.*)/$', users.BookInfo.as_view()), ]
views視圖
rom rest_framework.views import APIView from rest_framework.response import Response from .. import models from ..serializers import UserSerializer, UserDeserializer, BookInfoSerializer, BookInfoDeSerializer class BookInfo(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: try: # 1) 查詢出圖書對象 book_obj = models.BookInfo.objects.get(pk=pk) # 2) 構造序列化器 book_ser = BookInfoSerializer(instance=book_obj) # 3) 獲取序列化數據 return Response({ 'status': 200, 'msg': 0, 'results': book_ser.data # 經過data屬性能夠獲取序列化後的數據 }) except: return Response({ 'status': 201, 'msg': '書籍信息不存在', }) else: # 1) 查詢出圖書對象 book_obj_list = models.BookInfo.objects.all() # 2) 構造序列化器 PS:若是要被序列化的是包含多條數據的查詢集QuerySet,能夠經過添加many=True參數補充說明 book_ser_data = BookInfoSerializer(instance=book_obj_list, many=True) # 3) 獲取序列化數據 return Response({ 'status': 200, 'msg': 0, 'results': book_ser_data.data # 經過data屬性能夠獲取序列化後的數據 }) def post(self, request, *args, **kwargs): request_data = request.data if not isinstance(request_data, dict) or request_data == {}: return Response({ 'status': 1, 'msg': '數據有誤', }) book_ser = BookInfoDeSerializer(data=request_data) # 序列化對象調用is_valid()完成校驗,校驗失敗的失敗信息都會被存儲在 序列化對象.errors # 檢驗是否合格 raise_exception=True必填的 book_ser.is_valid(raise_exception=True) # book_result是對象<class 'app01.models.Book'>,羣增就是列表套一個個對象 book_obj = book_ser.save() return Response({ 'status': 200, 'msg': 'ok', 'results': BookInfoSerializer(instance=book_obj).data }) # if book_ser.is_valid(): # # 校驗經過,完成新增 # book_obj = book_ser.save() # return Response({ # 'status': 0, # 'msg': 'ok', # 'results': BookInfoSerializer(instance=book_obj).data # }) # else: # # 校驗失敗 # return Response({ # 'status': 1, # 'msg': book_ser.errors, # })
serializers.py
from rest_framework import serializers from django.conf import settings from rest_framework.exceptions import ValidationError from . import models # 序列化 class BookInfoSerializer(serializers.Serializer): """圖書數據序列化器""" id = serializers.IntegerField(label='ID', read_only=True) pwd = serializers.CharField(label='密碼', required=True) publisher = serializers.IntegerField(label='出版社', required=False) btitle = serializers.CharField(label='名稱', max_length=20) bpub_date = serializers.DateField(label='發佈日期', required=False) created_time = serializers.DateTimeField(label='建立時間', required=False) bread = serializers.IntegerField(label='閱讀量', required=False) bcomment = serializers.IntegerField(label='評論量', required=False) image = serializers.ImageField(label='圖片', required=False) """ 自定義序列化屬性 格式: 屬性名隨意,值由固定的命名規範方法提供 def get_屬性名(self, 參與序列化的model對象): 返回值就是自定義序列化屬性的值 """ # 出版社顯示名稱,而不是0,1。。。 publisher_name = serializers.SerializerMethodField() def get_publisher_name(self, obj): # choice類型的解釋型值 get_字段_display() 來訪問 return obj.get_publisher_display() # 圖片顯示全路徑 image_path = serializers.SerializerMethodField() def get_image_path(self, obj): # settings.MEDIA_URL: 本身配置的 /media/,給後面高級序列化與視圖類準備的 # obj.icon不能直接做爲數據返回,由於內容雖然是字符串,可是類型是ImageFieldFile類型 return '%s%s%s' % (r'http://127.0.0.1:8000', settings.MEDIA_URL, str(obj.image)) # 自定義虛擬閱讀量,原基礎增長10 fictitious_bread = serializers.SerializerMethodField() def get_fictitious_bread(self, obj): return obj.bread + 10 def about_django(value): if 'django' not in value.lower(): raise serializers.ValidationError("validators-圖書不是關於Django的") # 反序列化 class BookInfoDeSerializer(serializers.Serializer): id = serializers.IntegerField(label='ID', read_only=True) pwd = serializers.CharField(label='密碼', required=True) publisher = serializers.IntegerField(label='出版社', required=False) btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django]) bpub_date = serializers.DateField(label='發佈日期', required=False) created_time = serializers.DateTimeField(label='建立時間', required=False) bread = serializers.IntegerField(label='閱讀量', required=True) bcomment = serializers.IntegerField(label='評論量', required=True) image = serializers.ImageField(label='圖片', required=False) # 自定義有校驗規則的反序列化字段,例如確認密碼字段re_pwd re_pwd = serializers.CharField(required=True) # 局部鉤子:validate_要校驗的字段名(self, 當前要校驗字段的值) # 校驗規則:校驗經過返回原值,校驗失敗,拋出異常 def validate_btitle(self, value): if 'django' not in value.lower(): raise exceptions.ValidationError('validate_btitle-圖書不是關於Django的') return value # 全局鉤子:validate(self, 經過系統與局部鉤子校驗以後的全部數據) def validate(self, attrs): # attrs是字典格式 pwd = attrs.get('pwd') re_pwd = attrs.pop('re_pwd') # 由於re_pwd不須要存入數據庫,因此在全局鉤子校驗中刪除掉這個字段 bread = attrs['bread'] bcomment = attrs['bcomment'] if pwd != re_pwd: raise exceptions.ValidationError({'pwd&re_pwd': '兩次密碼不一致'}) if bread < bcomment: raise serializers.ValidationError('閱讀量小於評論量') return attrs # 要完成新增,必須重寫create方法,validated_data是校驗的數據 def create(self, validated_data): # 儘可能在全部校驗規則完畢以後,數據能夠直接入庫 return models.User.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.pwd = validated_data.get('pwd', instance.pwd) instance.bread = validated_data.get('bread', instance.bread) instance.bcomment = validated_data.get('bcomment', instance.bcomment) return instance
1) 在對序列化器進行save()保存時,能夠額外傳遞數據,這些數據能夠在create()和update()中的validated_data參數獲取到
# request.user 是django中記錄當前登陸用戶的模型對象 serializer.save(owner=request.user)
2)默認序列化器必須傳遞全部required的字段,不然會拋出驗證異常。可是咱們可使用partial參數來容許部分字段更新
# Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)