文章出處 http://www.javashuo.com/article/p-xrhcbbrv-hm.htmlhtml
【01】前言前端
serializers是什麼?官網是這樣的」Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. 「翻譯出來就是,將複雜的數據結構,例如ORM中的QuerySet或者Model實例對象轉換成Python內置的數據類型,從而進一步方便數據和json,xml等格式的數據進行交互。python
根據實際的工做經驗,我來總結下serializers的做用: django
1.將queryset與model實例等進行序列化,轉化成json格式,返回給用戶(api接口)。
2.將post與patch/put的上來的數據進行驗證。
3.對post與patch/put數據進行處理。(後面的內容,將用patch表示put/patch更新,博主認爲patch更貼近更新的說法)
簡單來講,針對get來講,serializers的做用體如今第一條,但若是是其餘請求,serializers可以發揮2,3條的做用!編程
用一張圖來講明下它的做用:json
serializers.fieild:咱們知道在django中,form也有許多field,那serializers其實也是drf中發揮着這樣的功能。咱們先簡單瞭解經常使用的幾個field。後端
1. 經常使用的field
CharField、BooleanField、IntegerField、DateTimeField這幾個用得比較多,咱們把外鍵的field放到後面去說!api
參考以下的例子:服務器
# 舉例子 mobile = serializers.CharField(max_length=11, min_length=11) age = serializers.IntegerField(min_value=1, max_value=100) # format能夠設置時間的格式,下面例子會輸出如:2018-3-20 12:10 pay_time = serializers.DateTimeField(read_only=True,format='%Y-%m-%d %H:%M') is_hot = serializers.BooleanField() # 例如設置商品是否熱銷
2. Core arguments參數
read_only:True表示不容許用戶本身上傳,只能用於api的輸出。若是某個字段設置了read_only=True,那麼就不須要進行數據驗證,只會在返回時,將這個字段序列化後返回
舉個簡單的例子:在用戶進行購物的時候,用戶post訂單時,確定會產生一個訂單號,而這個訂單號應該由後臺邏輯完成,而不該該由用戶post過來,若是不設置read_only=True,那麼驗證的時候就會報錯。再例如,咱們在網上購物時,支付時一般會產生支付狀態,交易號,訂單號,支付時間等字段,這些字段都應該設置爲read_only=True,即這些字段都應該由後臺產生而後返回給客戶端;舉例以下:數據結構
pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True) """ 在用戶提交訂單的時候,咱們在這裏給用戶新增一個字段,就是支付寶支付的URL 要設置爲read_only=True,這樣的話,就不能讓用戶端提交了,而是服務器端生成 返回給用戶的 """ alipay_url = serializers.SerializerMethodField(read_only=True)
write_only:與read_only對應;就是用戶post過來的數據,後臺服務器處理後不會再通過序列化後返回給客戶端;最多見的就是咱們在使用手機註冊的驗證碼和填寫的密碼。
required: 顧名思義,就是這個字段是否必填,例如要求:用戶名,密碼等是必須填寫的;不填寫就直接報錯
allow_null/allow_blank:是否容許爲NULL/空 。
error_messages:出錯時,信息提示。
例如咱們在定義用戶註冊序列化類時:
code = serializers.CharField(required=True, write_only=True, label="驗證碼", max_length=6, min_length=6, error_messages={ "blank": "請輸入驗證碼", "required": "請輸入驗證碼", #當用戶不輸入時提示的錯誤信息 "max_length": "驗證碼格式錯誤", "min_length": "驗證碼格式錯誤" }, help_text="驗證碼") """ 驗證用戶名是否存在,allow_blank 不能爲空;表示不容許用戶名爲空 """ username = serializers.CharField(required=True, allow_blank=False, label="用戶名", validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")]) # 同理咱們也應該將password設置write_only參數,不讓它返回回去,這樣容易被截獲 password = serializers.CharField( style={'input_type': 'password'}, help_text="密碼", label="密碼", write_only=True)
label: 字段顯示設置,如 label=’驗證碼’
help_text: 在指定字段增長一些提示文字,這兩個字段做用於api頁面比較有用
style: 說明字段的類型,這樣看可能比較抽象,看下面例子:
# 在api頁面,輸入密碼就會以*顯示 password = serializers.CharField( style={'input_type': 'password'}) # 會顯示選項框 color_channel = serializers.ChoiceField( choices=['red', 'green', 'blue'], style={'base_template': 'radio.html'})
3. HiddenField
HiddenField的值不依靠輸入,而須要設置默認的值,不須要用戶本身post數據過來,也不會顯式返回給用戶,最經常使用的就是user!!
咱們在登陸狀況下,進行一些操做,假設一個用戶去收藏了某一門課,那麼後臺應該自動識別這個用戶,而後用戶只須要將課程的id post過來,那麼這樣的功能,咱們配合CurrentUserDefault()實現。
# 這樣就能夠直接獲取到當前用戶 user = serializers.HiddenField( default=serializers.CurrentUserDefault())
再例如,咱們在收藏某件商品的時候,後臺要獲取到當前的用戶;至關於前臺只須要傳遞過來一個商品的ID便可;那麼在後臺我根據當前的登入用戶和當前的商品ID便可判斷用戶是否收藏過該商品;這就是一個聯合惟一主鍵的判斷;這一樣須要使用HiddenField。
save instance
這個標題是官方文檔的一個小標題,我以爲用的很好,一眼看出,這是爲post和patch所設置的,沒錯,這一部分功能是專門爲這兩種請求所設計的,若是隻是簡單的get請求,那麼在設置了前面的field可能就可以知足這個需求。
咱們在view以及mixins的博客中說起到,post請求對應create方法,而patch請求對應update方法,這裏提到的create方法與update方法,是指mixins中特定類中的方法。咱們看一下源代碼,源代碼具體分析能夠參考個人另一篇博客:
# 只截取一部分 class CreateModelMixin(object): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): serializer.save() class UpdateModelMixin(object): def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer): serializer.save()
能夠看出,不管是create與update都寫了一行:serializer.save( ),那麼,這一行,到底作了什麼事情,分析一下源碼。
在serializer.py文件中:
def save(self, **kwargs): assert not hasattr(self, 'save_object'), ( 'Serializer `%s.%s` has old-style version 2 `.save_object()` '
'that is no longer compatible with REST framework 3. '
'Use the new-style `.create()` and `.update()` methods instead.' % (self.__class__.__module__, self.__class__.__name__) ) assert hasattr(self, '_errors'), ( 'You must call `.is_valid()` before calling `.save()`.' ) assert not self.errors, ( 'You cannot call `.save()` on a serializer with invalid data.' ) # Guard against incorrect use of `serializer.save(commit=False)` assert 'commit' not in kwargs, ( "'commit' is not a valid keyword argument to the 'save()' method. "
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. "
"You can also pass additional keyword arguments to 'save()' if you "
"need to set extra attributes on the saved model instance. "
"For example: 'serializer.save(owner=request.user)'.'" ) assert not hasattr(self, '_data'), ( "You cannot call `.save()` after accessing `serializer.data`."
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. " ) validated_data = dict( list(self.validated_data.items()) + list(kwargs.items()) ) if self.instance is not None: self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( '`update()` did not return an object instance.' ) else: self.instance = self.create(validated_data) assert self.instance is not None, ( '`create()` did not return an object instance.' ) return self.instance
顯然,serializer.save的操做,它去調用了serializer的create或update方法,不是mixins中的!!!咱們看一下流程圖(以post爲例):
講了那麼多,咱們到底須要幹什麼!重載這兩個方法!!
若是你的viewset含有post,那麼你須要重載create方法,若是含有patch,那麼就須要重載update方法。
# 假設如今是個博客,有一個建立文章,與修改文章的功能, model爲Article。 class ArticleSerializer(serializers.Serializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault()) name = serializers.CharField(max_length=20) content = serializers.CharField() def create(self, validated_data): # 除了用戶,其餘數據能夠從validated_data這個字典中獲取 # 注意,users在這裏是放在上下文中的request,而不是直接的request user = self.context['request'].user name = validated_data['name '] content = validated_data['content '] return Article.objects.create(**validated_data) def update(self, instance, validated_data): # 更新的特別之處在於你已經獲取到了這個對象instance instance.name = validated_data.get('name') instance.content = validated_data.get('content') instance.save() return instance
可能會有人好奇,系統是怎麼知道,咱們須要調用serializer的create方法,仍是update方法,咱們從save( )方法能夠看出,判斷的依據是:
if self.instance is not None:pass
那麼咱們的mixins的create與update也已經在爲開發者設置好了:
# CreateModelMixin serializer = self.get_serializer(data=request.data) # UpdateModelMixin serializer = self.get_serializer(instance, data=request.data, partial=partial)
也就是說,在update經過get_object( )的方法獲取到了instance,而後傳遞給serializer,serializer再根據是否有傳遞instance來判斷來調用哪一個方法!
Validation自定義驗證邏輯
單獨的validate (這個就相似於Django中Form中的局部鉤子函數)
咱們在上面提到field,它能起到必定的驗證做用,但很明顯,它存在很大的侷限性,舉個簡單的例子,咱們要判斷咱們手機號碼,若是使用CharField(max_length=11, min_length=11),它只能確保咱們輸入的是11個字符,那麼咱們須要自定義!就拿筆者在實際生產環境下的例子來講,光針對用戶輸入的手機號碼,咱們在後端就須要進行驗證,例如該手機號碼是否註冊,手機號碼是否合法(例如知足手機號碼的zhengze表達式,以及要驗證該手機號碼向後臺發送請求驗證短信的頻率等等)
class SmsSerializer(serializers.Serializer): """ 爲什不用ModelSerializer來完成手機號碼的驗證呢? 由於VerifyCode中還有一個字段是手機驗證碼字段,直接使用ModelSerializer來驗證會報錯 由於ModelSerializer會自動生成VerifyCode模型中的全部字段;可是 咱們傳遞過來的就是一個手機號,判斷它是不是合法的 所以使用Serializer來自定義合法性校驗規則,至關於就是鉤子函數 單獨對手機號碼進行驗證 """ mobile = serializers.CharField(max_length=11) # 使用validate_字段名(self, 字段名):要麼返回字段,要麼拋出異常 def validate_mobile(self, mobile): """ 驗證手機號碼,記住這裏的validate_字段名必定要是數據模型中的字段 :param attrs: :return: """ # 手機號碼是否註冊,查詢UserProfile表便可 if User.objects.filter(mobile=mobile).exists(): raise serializers.ValidationError("用戶已經存在") # 驗證手機號碼是否合法,這部分應該是在前端作的,固然後臺也須要進行驗證 if not re.match(REGEX_MOBILE, mobile): raise serializers.ValidationError("手機號碼格式不正確") # 驗證發送頻率,若是不作,用戶能夠一直向後臺發送,請求驗證碼; # 會形成很大的壓力,限制一分鐘只能發送一次 7-8 09:00 one_minute_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0) """ 若是添加時間在一分鐘之內,它確定是大於你一分鐘以前的時間的 若是這條記錄存在 """ if VerifyCode.objects.filter(add_time__gt=one_minute_ago, mobile=mobile): raise serializers.ValidationError("抱歉,一分鐘只能發送一次") # 若是驗證經過,我就將這個mobile返回去,這裏必定要有一個返回 return mobile
聯合validate 這個就相似於全局鉤子函數
上面驗證方式,只能驗證一個字段,若是是兩個字段聯合在一塊兒進行驗證,那麼咱們就能夠重載validate( )方法。
start = serializers.DateTimeField() finish = serializers.DateTimeField() def validate(self, attrs): # 傳進來什麼參數,就返回什麼參數,通常狀況下用attrs if data['start'] > data['finish']: raise serializers.ValidationError("finish must occur after start") return attrs
這個方法很是的有用,咱們還能夠再這裏對一些read_only的字段進行操做,咱們在read_only說起到一個例子,訂單號的生成,咱們能夠在這步生成一個訂單號,而後添加到attrs這個字典中。看以下的代碼:
class OrderSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() ) pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True) """ 在用戶提交訂單的時候,咱們在這裏給用戶新增一個字段,就是支付寶支付的URL 要設置爲read_only=True,這樣的話,就不能讓用戶端提交了,而是服務器端生成 返回給用戶的 """ alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): # obj就是OrderSerializer對象 alipay = AliPay( appid="2016091200490227", # 沙箱環境中能夠找到 app_notify_url="http://47.92.87.172:8000/alipay/return/", app_private_key_path=private_key_path, # 我的私鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你本身的公鑰, debug=True, # 默認False,上線的時候修改成False便可 return_url="http://47.92.87.172:8000/alipay/return/" ) url = alipay.direct_pay( # 一個訂單裏面可能有多個商品,所以subject # 不適合使用商品名稱 subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url def generate_order_sn(self): """ 後臺系統生成訂單號 這個是系統後臺生成的: 當前時間+userId+隨機數 """ random_ins = Random() order_sn = "{time_str}{user_id}{random_num}".format(time_str=time.strftime("%Y%m%d%H%M%S"), user_id=self.context["request"].user.id, random_num=random_ins.randint(1000, 9999)) return order_sn def validate(self, attrs): # 全局鉤子函數 attrs["order_sn"] = self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__"
這個方法運用在modelserializer中,能夠剔除掉write_only的字段,這個字段只驗證,但不存在於指定的model當中,即不能save( ),能夠在這delete掉;例如短信驗證碼驗證完畢後就能夠刪除了:
def validate(self, attrs): """ 判斷完畢後刪除驗證碼,由於沒有什麼用了 """ attrs["mobile"] = attrs["username"] del attrs["code"] return attrs
Validators
validators能夠直接做用於某個字段,這個時候,它與單獨的validate做用差很少;固然,drf提供的validators還有很好的功能:UniqueValidator,UniqueTogetherValidator等;UniqueValidator: 指定某一個對象是惟一的,如,用戶名只能存在惟一:
username = serializers.CharField(required=True, allow_blank=False, label="用戶名", max_length=16, min_length=6, validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")], error_messages={ "blank": "用戶名不容許爲空", "required": "請輸入用戶名", "max_length": "用戶名長度最長爲16位", "min_length": "用戶名長度至少爲6位" })
UniqueTogetherValidator: 聯合惟一,例如咱們須要判斷用戶是否收藏了某個商品,前端只須要傳遞過來一個商品ID便可。這個時候就不是像上面那樣單獨做用於某個字段,而是須要進行聯合惟一的判斷,即用戶ID和商品ID;此時咱們須要在Meta中設置。
class UserFavSerializer(serializers.ModelSerializer): # 獲取到當前用戶 user = serializers.HiddenField( default=serializers.CurrentUserDefault() ) class Meta: model = UserFav """ 咱們須要獲取的是當前登入的user 之後要取消收藏,只須要獲取這裏的id便可 UniqueTogetherValidator做用在多個字段之上 由於是聯合惟一主鍵 """ validators = [ UniqueTogetherValidator( queryset=UserFav.objects.all(), fields=('user', 'goods'), message="已經收藏" ) ] fields = ("user", "goods", "id")
ModelSerializer
講了不少Serializer的,在這個時候,我仍是強烈建議使用ModelSerializer,由於在大多數狀況下,咱們都是基於model字段去開發。
好處:
ModelSerializer已經重載了create與update方法,它可以知足將post或patch上來的數據進行進行直接地建立與更新,除非有額外需求,那麼就能夠重載create與update方法。
ModelSerializer在Meta中設置fields字段,系統會自動進行映射,省去每一個字段再寫一個field。
class UserDetailSerializer(serializers.ModelSerializer): """ 用戶詳情序列化 """
class Meta: model = User fields = ("name", "gender", "birthday", "email", "mobile") # fields = '__all__': 表示全部字段 # exclude = ('add_time',): 除去指定的某些字段 # 這三種方式,存在一個便可
ModelSerializer須要解決的2個問題:
1,某個字段不屬於指定model,它是write_only,須要用戶傳進來,但咱們不能對它進行save( ),由於ModelSerializer是基於Model,這個字段在Model中沒有對應,這個時候,咱們須要重載validate!
如在用戶註冊時,咱們須要填寫驗證碼,這個驗證碼只須要驗證,不須要保存到用戶這個Model中:
def validate(self, attrs): del attrs["code"] return attrs
2,某個字段不屬於指定model,它是read_only,只須要將它序列化傳遞給用戶,可是在這個model中,沒有這個字段!咱們須要用到SerializerMethodField。
假設須要返回用戶加入這個網站多久了,不可能維持這樣加入的天數這樣一個數據,通常會記錄用戶加入的時間點,而後當用戶獲取這個數據,咱們再計算返回給它。
class UserSerializer(serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() # 方法寫法:get_ + 字段 def get_days_since_joined(self, obj): # obj指這個model的對象 return (now() - obj.date_joined).days class Meta: model = User
固然,這個的SerializerMethodField用法還相對簡單一點,後面還會有比較複雜的狀況。
關於外鍵的serializers
講了那麼多,終於要研究一下外鍵啦~
其實,外鍵的field也比較簡單,若是咱們直接使用serializers.Serializer,那麼直接用PrimaryKeyRelatedField就解決了。
假設如今有一門課python入門教學(course),它的類別是python(catogory)。
# 指定queryset category = serializers.PrimaryKeyRelatedField(queryset=CourseCategory.objects.all(), required=True)
ModelSerializer就更簡單了,直接經過映射就行了
不過這樣只是用戶得到的只是一個外鍵類別的id,並不能獲取到詳細的信息,若是想要獲取到具體信息,那須要嵌套serializer:
category = CourseCategorySerializer()
注意:上面兩種方式,外鍵都是正向取得,下面介紹怎麼反向去取,如,咱們須要獲取python這個類別下,有什麼課程。
首先,在課程course的model中,須要在外鍵中設置related_name:
class Course(model.Model): category = models.ForeignKey(CourseCategory, related_name='courses')
# 反向取課程,經過related_name # 一對多,一個類別下有多個課程,必定要設定many=True courses = CourseSerializer(many=True)
寫到這裏,咱們的外鍵就基本講完了!還有一個小問題:咱們在上面提到ModelSerializer須要解決的第二個問題中,其實還有一種狀況,就是某個字段屬於指定model,但不能獲取到相關數據。
假設如今是一個多級分類的課程,例如,編程語言–>python–>python入門學習課程,編程語言與python屬於類別,另一個屬於課程,編程語言類別是python類別的一個外鍵,並且屬於同一個model,實現方法:
parent_category = models.ForeignKey('self', null=True, blank=True, verbose_name='父類目別', related_name='sub_cat')
如今獲取編程語言下的課程,顯然沒法直接獲取到python入門學習這個課程,由於它們兩沒有外鍵關係。SerializerMethodField( )也能夠解決這個問題,只要在自定義的方法中實現相關的邏輯便可!
courses = SerializerMethodField() def get_courses(self, obj): all_courses = Course.objects.filter(category__parent_category_id=obj.id) courses_serializer = CourseSerializer(all_course, many=True, context={'request': self.context['request']}) return courses_serializer.data
上面的例子看起來有點奇怪,由於咱們在SerializerMethodField()嵌套了serializer,就須要本身進行序列化,而後再從data就能夠取出json數據。
能夠看到傳遞的參數是分別是:queryset,many=True多個對象,context上下文。這個context十分關鍵,若是不將request傳遞給它,在序列化的時候,圖片與文件這些Field不會再前面加上域名,也就是說,只會有/media/img…這樣的路徑!
以上就是關於DRF的Serializer的小結,若是有錯漏,煩請指正,後面我將把本身工做中遇到的坑分享出來,但願對你們有幫助!