django rest framework serializers解讀

serializers是什么?官網(wǎng)是這樣的”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. “翻譯出來就是闹击,將復(fù)雜的數(shù)據(jù)結(jié)構(gòu)變成json或者xml這個格式的踢故。

  • 將queryset與model實例等進行序列化烂完,轉(zhuǎn)化成json格式盅惜,返回給用戶(api接口)棋凳。
  • 將post與patch/put的上來的數(shù)據(jù)進行驗證。
  • 對post與patch/put數(shù)據(jù)進行處理妥箕。(后面的內(nèi)容,將用patch表示put/patch更新更舞,博主認(rèn)為patch更貼近更新的說法)
    簡單來說畦幢,針對get來說,serializers的作用體現(xiàn)在第一條缆蝉,但如果是其他請求宇葱,serializers能夠發(fā)揮2,3條的作用!


    image

serializers.fieild

我們知道在django中刊头,form也有許多field黍瞧,那serializers其實也是drf中發(fā)揮著這樣的功能。我們先簡單了解常用的幾個field芽偏。

常用的field

CharField、BooleanField弦讽、IntegerField污尉、DateTimeField這幾個用得比較多,我們把外鍵的field放到后面去說往产!

# 舉例子
mobile = serializers.CharField(max_length=11, min_length=11)
age = serializers.IntegerField(min_value=1, max_value=100)
# format可以設(shè)置時間的格式被碗,下面例子會輸出如:2018-1-24 12:10
pay_time = serializers.DateTimeField(read_only=True,format='%Y-%m-%d %H:%M')
is_hot = serializers.BooleanField()

不同的是,我們在django中仿村,form更強調(diào)對提交的表單進行一種驗證锐朴,而serializer的field不僅在進行數(shù)據(jù)驗證時起著至關(guān)重要的作用,在將數(shù)據(jù)進行序列化后返回也發(fā)揮著重要作用蔼囊!
我們可以看出焚志,不同的field可以用不同的關(guān)鍵參數(shù),除此之外畏鼓,還有一些十分重要有用的參數(shù)酱酬。

Core arguments參數(shù)

read_only:True表示不允許用戶自己上傳,只能用于api的輸出云矫。如果某個字段設(shè)置了read_only=True膳沽,那么就不需要進行數(shù)據(jù)驗證,只會在返回時让禀,將這個字段序列化后返回
  舉個簡單的例子:在用戶進行購物的時候挑社,用戶post訂單時,肯定會產(chǎn)生一個訂單號巡揍,而這個訂單號應(yīng)該由后臺邏輯完成痛阻,而不應(yīng)該由用戶post過來,如果不設(shè)置read_only=True腮敌,那么驗證的時候就會報錯录平。

order_sn = serializers.CharField(readonly=True)

write_only: 與read_only對應(yīng)
required: 顧名思義麻车,就是這個字段是否必填。
allow_null/allow_blank:是否允許為NULL/空 斗这。
error_messages:出錯時动猬,信息提示。

name = serializers.CharField(required=True, min_length=6,
                error_messages={
                    'min_length': '名字不能小于6個字符',
                    'required': '請?zhí)顚懨?})

label: 字段顯示設(shè)置表箭,如 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'})

這里面,還有一個十分有用的validators參數(shù)极舔,這個我們會在后面提及凤覆!

HiddenField

HiddenField的值不依靠輸入,而需要設(shè)置默認(rèn)的值拆魏,不需要用戶自己post數(shù)據(jù)過來盯桦,也不會顯式返回給用戶,最常用的就是user!!

我們在登錄情況下渤刃,進行一些操作拥峦,假設(shè)一個用戶去收藏了某一門課,那么后臺應(yīng)該自動識別這個用戶卖子,然后用戶只需要將課程的id post過來略号,那么這樣的功能,我們配合CurrentUserDefault()實現(xiàn)洋闽。

# 這樣就可以直接獲取到當(dāng)前用戶
user = serializers.HiddenField(
    default=serializers.CurrentUserDefault())

save instance
這個標(biāo)題是官方文檔的一個小標(biāo)題玄柠,我覺得用的很好,一眼看出诫舅,這是為post和patch所設(shè)置的随闪,沒錯,這一部分功能是專門為這兩種請求所設(shè)計的骚勘,如果只是簡單的get請求铐伴,那么在設(shè)置了前面的field可能就能夠滿足這個需求。
我們在mixins的博客中提及到俏讹,post請求對應(yīng)create方法当宴,而patch請求對應(yīng)update方法,這里提到的create方法與update方法泽疆,是指mixins中特定類中的方法户矢。我們看一下源代碼,源代碼具體分析可以看到另外一篇博客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):
# 略去一些稍微無關(guān)的內(nèi)容
    ···
    if self.instance is not None:
        self.instance = self.update(self.instance, validated_data)
            ···
    else:
        self.instance = self.create(validated_data)
            ···
    return self.instance

顯然,serializer.save的操作虏劲,它去調(diào)用了serializer的create或update方法托酸,不是mixins中的!F馕住励堡!我們看一下流程圖(以post為例)


image

講了那么多,我們到底需要干什么堡掏!重載這兩個方法S帷!
如果你的viewset含有post泉唁,那么你需要重載create方法鹅龄,如果含有patch,那么就需要重載update方法游两。

# 假設(shè)現(xiàn)在是個博客砾层,有一個創(chuàng)建文章漩绵,與修改文章的功能, 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):
    # 除了用戶,其他數(shù)據(jù)可以從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):
    # 更新的特別之處在于你已經(jīng)獲取到了這個對象instance
        instance.name = validated_data.get('name')
        instance.content = validated_data.get('content')
        instance.save()
        return instance

可能會有人好奇,系統(tǒng)是怎么知道碍扔,我們需要調(diào)用serializer的create方法瘩燥,還是update方法,我們從save( )方法可以看出不同,判斷的依據(jù)是:

if self.instance is not None:pass

那么我們的mixins的create與update也已經(jīng)在為開發(fā)者設(shè)置好了厉膀,

# 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再根據(jù)是否有傳遞instance來判斷來調(diào)用哪個方法!

Validation自定義驗證邏輯

單獨的validate

我們在上面提到field百新,它能起到一定的驗證作用企软,但很明顯,它存在很大的局限性饭望,舉個簡單的例子仗哨,我們要判斷我們手機號碼形庭,如果使用CharField(max_length=11, min_length=11),它只能確保我們輸入的是11個字符厌漂,那么我們需要自定義萨醒!

mobile_phone = serializers.CharField(max_length=11, min_length=11)

def validate_mobile_phone(self, mobile_phone):
    # 注意參數(shù),self以及字段名
    # 注意函數(shù)名寫法桩卵,validate_ + 字段名字
    if not re.match(REGEX_MOBILE, mobile):
    # REGEX_MOBILE表示手機的正則表達式
        raise serializers.ValidationError("手機號碼非法")
    return mobile_phone

當(dāng)然验靡,這里面還可以加入很多邏輯,例如雏节,還可以判斷手機是否原本就存在數(shù)據(jù)庫等等

聯(lián)合validate

上面驗證方式胜嗓,只能驗證一個字段,如果是兩個字段聯(lián)合在一起進行驗證钩乍,那么我們就可以重載validate( )方法辞州。

   start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, attrs):
    # 傳進來什么參數(shù),就返回什么參數(shù)寥粹,一般情況下用attrs
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return attrs

這個方法非常的有用变过,我們還可以再這里對一些read_only的字段進行操作,我們在read_only提及到一個例子涝涤,訂單號的生成媚狰,我們可以在這步生成一個訂單號,然后添加到attrs這個字典中阔拳。

order_sn = serializers.CharField(readonly=True)
def validate(self, attrs):
    # 調(diào)用一個方法生成order_sn
    attrs['order_sn'] = generate_order_sn()
    return attrs

這個方法運用在modelserializer中崭孤,可以剔除掉write_only的字段,這個字段只驗證糊肠,但不存在與指定的model當(dāng)中辨宠,即不能save( ),可以在這delete掉货裹!

Validators

validators可以直接作用于某個字段嗤形,這個時候,它與單獨的validate作用差不多
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])
當(dāng)然弧圆,drf提供的validators還有很好的功能:UniqueValidator赋兵,UniqueTogetherValidator等
UniqueValidator: 指定某一個對象是唯一的,如搔预,用戶名只能存在唯一:

username = serializers.CharField(
        max_length=11, 
        min_length=11,
        validators=[UniqueValidator(queryset=UserProfile.objects.all())
    )

UniqueTogetherValidator: 聯(lián)合唯一霹期,如用戶收藏某個課程,這個時候就不能單獨作用于某個字段斯撮,我們在Meta中設(shè)置经伙。

  class Meta:
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),
                fields=('user', 'course'),
                message='已經(jīng)收藏'
            )]

ModelSerializer

講了很多Serializer的,在這個時候,我還是強烈建議使用ModelSerializer帕膜,因為在大多數(shù)情況下枣氧,我們都是基于model字段去開發(fā)。

好處

ModelSerializer已經(jīng)重載了create與update方法垮刹,它能夠滿足將post或patch上來的數(shù)據(jù)進行進行直接地創(chuàng)建與更新达吞,除非有額外需求,那么就可以重載create與update方法荒典。
  ModelSerializer在Meta中設(shè)置fields字段酪劫,系統(tǒng)會自動進行映射,省去每個字段再寫一個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中沒有對應(yīng)麦箍,這個時候,我們需要重載validate陶珠!
如在用戶注冊時挟裂,我們需要填寫驗證碼,這個驗證碼只需要驗證揍诽,不需要保存到用戶這個Model中:

    def validate(self, attrs):
        del attrs["code"]
        return attrs

2诀蓉,某個字段不屬于指定model,它是read_only寝姿,只需要將它序列化傳遞給用戶交排,但是在這個model中划滋,沒有這個字段饵筑!我們需要用到SerializerMethodField。
  假設(shè)需要返回用戶加入這個網(wǎng)站多久了处坪,不可能維持這樣加入的天數(shù)這樣一個數(shù)據(jù)根资,一般會記錄用戶加入的時間點,然后當(dāng)用戶獲取這個數(shù)據(jù)同窘,我們再計算返回給它玄帕。

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

當(dāng)然,這個的SerializerMethodField用法還相對簡單一點想邦,后面還會有比較復(fù)雜的情況

關(guān)于外鍵的serializers

講了那么多裤纹,終于要研究一下外鍵啦~
其實,外鍵的field也比較簡單,如果我們直接使用serializers.Serializer鹰椒,那么直接用PrimaryKeyRelatedField就解決了锡移。
假設(shè)現(xiàn)在有一門課python入門教學(xué)(course),它的類別是python(catogory)漆际。

# 指定queryset
category = serializers.PrimaryKeyRelatedField(queryset=CourseCategory.objects.all(), required=True)

ModelSerializer就更簡單了淆珊,直接通過映射就好了
不過這樣只是用戶獲得的只是一個外鍵類別的id,并不能獲取到詳細的信息奸汇,如果想要獲取到具體信息施符,那需要嵌套serializer

category = CourseCategorySerializer()

注意:
上面兩種方式,外鍵都是正向取得擂找,下面介紹怎么反向去取戳吝,如,我們需要獲取python這個類別下贯涎,有什么課程骨坑。
首先,在課程course的model中柬采,需要在外鍵中設(shè)置related_name

class Course(model.Model):
    category = models.ForeignKey(CourseCategory, related_name='courses')
# 反向取課程欢唾,通過related_name
# 一對多,一個類別下有多個課程粉捻,一定要設(shè)定many=True
courses = CourseSerializer(many=True)

寫到這里礁遣,我們的外鍵就基本講完了!還有一個小問題:我們在上面提到ModelSerializer需要解決的第二個問題中肩刃,其實還有一種情況祟霍,就是某個字段屬于指定model,但不能獲取到相關(guān)數(shù)據(jù)盈包。
  假設(shè)現(xiàn)在是一個多級分類的課程沸呐,例如,編程語言–>python–>python入門學(xué)習(xí)課程呢燥,編程語言與python屬于類別崭添,另外一個屬于課程,編程語言類別是python類別的一個外鍵叛氨,而且屬于同一個model呼渣,實現(xiàn)方法:

parent_category = models.ForeignKey('self', null=True, blank=True, 
                    verbose_name='父類目別',
                    related_name='sub_cat')

現(xiàn)在獲取編程語言下的課程,顯然無法直接獲取到python入門學(xué)習(xí)這個課程寞埠,因為它們兩沒有外鍵關(guān)系屁置。SerializerMethodField( )也可以解決這個問題,只要在自定義的方法中實現(xiàn)相關(guān)的邏輯即可仁连!

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數(shù)據(jù)使鹅。
  可以看到傳遞的參數(shù)是分別是:queryset颇象,many=True多個對象,context上下文并徘。這個context十分關(guān)鍵遣钳,如果不將request傳遞給它,在序列化的時候麦乞,圖片與文件這些Field不會再前面加上域名蕴茴,也就是說,只會有/media/img…這樣的路徑姐直!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倦淀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子声畏,更是在濱河造成了極大的恐慌撞叽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件插龄,死亡現(xiàn)場離奇詭異愿棋,居然都是意外死亡,警方通過查閱死者的電腦和手機均牢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門糠雨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徘跪,你說我怎么就攤上這事甘邀。” “怎么了垮庐?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵松邪,是天一觀的道長。 經(jīng)常有香客問我哨查,道長逗抑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任解恰,我火速辦了婚禮锋八,結(jié)果婚禮上浙于,老公的妹妹穿的比我還像新娘护盈。我一直安慰自己,他們只是感情好羞酗,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布腐宋。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胸竞。 梳的紋絲不亂的頭發(fā)上欺嗤,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音卫枝,去河邊找鬼煎饼。 笑死,一個胖子當(dāng)著我的面吹牛校赤,可吹牛的內(nèi)容都是我干的吆玖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼马篮,長吁一口氣:“原來是場噩夢啊……” “哼沾乘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浑测,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤翅阵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后迁央,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掷匠,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年岖圈,在試婚紗的時候發(fā)現(xiàn)自己被綠了槐雾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡幅狮,死狀恐怖募强,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情崇摄,我是刑警寧澤擎值,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站逐抑,受9級特大地震影響鸠儿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陈醒。 院中可真熱鬧朴恳,春花似錦、人聲如沸镶蹋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贤徒。三九已至芹壕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間接奈,已是汗流浹背踢涌。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留序宦,地道東北人睁壁。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像互捌,于是被迫代替她去往敵國和親堡僻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容