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. “翻譯出來(lái)就是,將復(fù)雜的數(shù)據(jù)結(jié)構(gòu)變成json或者xml這個(gè)格式的。

  • 將queryset與model實(shí)例等進(jìn)行序列化,轉(zhuǎn)化成json格式,返回給用戶(api接口)。
  • 將post與patch/put的上來(lái)的數(shù)據(jù)進(jìn)行驗(yàn)證。
  • 對(duì)post與patch/put數(shù)據(jù)進(jìn)行處理。(后面的內(nèi)容,將用patch表示put/patch更新,博主認(rèn)為patch更貼近更新的說(shuō)法)
    簡(jiǎn)單來(lái)說(shuō),針對(duì)get來(lái)說(shuō),serializers的作用體現(xiàn)在第一條,但如果是其他請(qǐng)求,serializers能夠發(fā)揮2,3條的作用!


    image

serializers.fieild

我們知道在django中,form也有許多field,那serializers其實(shí)也是drf中發(fā)揮著這樣的功能。我們先簡(jiǎn)單了解常用的幾個(gè)field。

常用的field

CharField、BooleanField、IntegerField、DateTimeField這幾個(gè)用得比較多,我們把外鍵的field放到后面去說(shuō)!

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

不同的是,我們?cè)赿jango中,form更強(qiáng)調(diào)對(duì)提交的表單進(jìn)行一種驗(yàn)證,而serializer的field不僅在進(jìn)行數(shù)據(jù)驗(yàn)證時(shí)起著至關(guān)重要的作用,在將數(shù)據(jù)進(jìn)行序列化后返回也發(fā)揮著重要作用!
我們可以看出,不同的field可以用不同的關(guān)鍵參數(shù),除此之外,還有一些十分重要有用的參數(shù)。

Core arguments參數(shù)

read_only:True表示不允許用戶自己上傳,只能用于api的輸出。如果某個(gè)字段設(shè)置了read_only=True,那么就不需要進(jìn)行數(shù)據(jù)驗(yàn)證,只會(huì)在返回時(shí),將這個(gè)字段序列化后返回
  舉個(gè)簡(jiǎn)單的例子:在用戶進(jìn)行購(gòu)物的時(shí)候,用戶post訂單時(shí),肯定會(huì)產(chǎn)生一個(gè)訂單號(hào),而這個(gè)訂單號(hào)應(yīng)該由后臺(tái)邏輯完成,而不應(yīng)該由用戶post過(guò)來(lái),如果不設(shè)置read_only=True,那么驗(yàn)證的時(shí)候就會(huì)報(bào)錯(cuò)。

order_sn = serializers.CharField(readonly=True)

write_only: 與read_only對(duì)應(yīng)
required: 顧名思義,就是這個(gè)字段是否必填。
allow_null/allow_blank:是否允許為NULL/空 。
error_messages:出錯(cuò)時(shí),信息提示。

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

label: 字段顯示設(shè)置,如 label=’驗(yàn)證碼’
help_text: 在指定字段增加一些提示文字,這兩個(gè)字段作用于api頁(yè)面比較有用
style: 說(shuō)明字段的類型,這樣看可能比較抽象,看下面例子:

# 在api頁(yè)面,輸入密碼就會(huì)以*顯示
password = serializers.CharField(
    style={'input_type': 'password'})
# 會(huì)顯示選項(xiàng)框
color_channel = serializers.ChoiceField(
    choices=['red', 'green', 'blue'],
    style={'base_template': 'radio.html'})

這里面,還有一個(gè)十分有用的validators參數(shù),這個(gè)我們會(huì)在后面提及!

HiddenField

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

我們?cè)诘卿浨闆r下,進(jìn)行一些操作,假設(shè)一個(gè)用戶去收藏了某一門課,那么后臺(tái)應(yīng)該自動(dòng)識(shí)別這個(gè)用戶,然后用戶只需要將課程的id post過(guò)來(lái),那么這樣的功能,我們配合CurrentUserDefault()實(shí)現(xiàn)。

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

save instance
這個(gè)標(biāo)題是官方文檔的一個(gè)小標(biāo)題,我覺(jué)得用的很好,一眼看出,這是為post和patch所設(shè)置的,沒(méi)錯(cuò),這一部分功能是專門為這兩種請(qǐng)求所設(shè)計(jì)的,如果只是簡(jiǎn)單的get請(qǐng)求,那么在設(shè)置了前面的field可能就能夠滿足這個(gè)需求。
我們?cè)趍ixins的博客中提及到,post請(qǐng)求對(duì)應(yīng)create方法,而patch請(qǐng)求對(duì)應(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()

可以看出,無(wú)論是create與update都寫了一行:serializer.save( ),那么,這一行,到底做了什么事情,分析一下源碼。

# serializer.py
def save(self, **kwargs):
# 略去一些稍微無(wú)關(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中的?。?!我們看一下流程圖(以post為例)


image

講了那么多,我們到底需要干什么!重載這兩個(gè)方法??!
如果你的viewset含有post,那么你需要重載create方法,如果含有patch,那么就需要重載update方法。

# 假設(shè)現(xiàn)在是個(gè)博客,有一個(gè)創(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這個(gè)字典中獲取
    # 注意,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)獲取到了這個(gè)對(duì)象instance
        instance.name = validated_data.get('name')
        instance.content = validated_data.get('content')
        instance.save()
        return instance

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

if self.instance is not None:pass

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

# CreateModelMixin
serializer = self.get_serializer(data=request.data)
# UpdateModelMixin
serializer = self.get_serializer(instance, data=request.data, partial=partial)

也就是說(shuō),在update通過(guò)get_object( )的方法獲取到了instance,然后傳遞給serializer,serializer再根據(jù)是否有傳遞instance來(lái)判斷來(lái)調(diào)用哪個(gè)方法!

Validation自定義驗(yàn)證邏輯

單獨(dú)的validate

我們?cè)谏厦嫣岬絝ield,它能起到一定的驗(yàn)證作用,但很明顯,它存在很大的局限性,舉個(gè)簡(jiǎn)單的例子,我們要判斷我們手機(jī)號(hào)碼,如果使用CharField(max_length=11, min_length=11),它只能確保我們輸入的是11個(gè)字符,那么我們需要自定義!

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表示手機(jī)的正則表達(dá)式
        raise serializers.ValidationError("手機(jī)號(hào)碼非法")
    return mobile_phone

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

聯(lián)合validate

上面驗(yàn)證方式,只能驗(yàn)證一個(gè)字段,如果是兩個(gè)字段聯(lián)合在一起進(jìn)行驗(yàn)證,那么我們就可以重載validate( )方法。

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

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

這個(gè)方法非常的有用,我們還可以再這里對(duì)一些read_only的字段進(jìn)行操作,我們?cè)趓ead_only提及到一個(gè)例子,訂單號(hào)的生成,我們可以在這步生成一個(gè)訂單號(hào),然后添加到attrs這個(gè)字典中。

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

這個(gè)方法運(yùn)用在modelserializer中,可以剔除掉write_only的字段,這個(gè)字段只驗(yàn)證,但不存在與指定的model當(dāng)中,即不能save( ),可以在這delete掉!

Validators

validators可以直接作用于某個(gè)字段,這個(gè)時(shí)候,它與單獨(dú)的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: 指定某一個(gè)對(duì)象是唯一的,如,用戶名只能存在唯一:

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

UniqueTogetherValidator: 聯(lián)合唯一,如用戶收藏某個(gè)課程,這個(gè)時(shí)候就不能單獨(dú)作用于某個(gè)字段,我們?cè)贛eta中設(shè)置。

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

ModelSerializer

講了很多Serializer的,在這個(gè)時(shí)候,我還是強(qiáng)烈建議使用ModelSerializer,因?yàn)樵诖蠖鄶?shù)情況下,我們都是基于model字段去開(kāi)發(fā)。

好處

ModelSerializer已經(jīng)重載了create與update方法,它能夠滿足將post或patch上來(lái)的數(shù)據(jù)進(jìn)行進(jìn)行直接地創(chuàng)建與更新,除非有額外需求,那么就可以重載create與update方法。
  ModelSerializer在Meta中設(shè)置fields字段,系統(tǒng)會(huì)自動(dòng)進(jìn)行映射,省去每個(gè)字段再寫一個(gè)field

class UserDetailSerializer(serializers.ModelSerializer):
    """
    用戶詳情序列化
    """

    class Meta:
        model = User
        fields = ("name", "gender", "birthday", "email", "mobile")
        # fields = '__all__': 表示所有字段
        # exclude = ('add_time',):  除去指定的某些字段
        # 這三種方式,存在一個(gè)即可
ModelSerializer需要解決的2個(gè)問(wèn)題:

1,某個(gè)字段不屬于指定model,它是write_only,需要用戶傳進(jìn)來(lái),但我們不能對(duì)它進(jìn)行save( ),因?yàn)镸odelSerializer是基于Model,這個(gè)字段在Model中沒(méi)有對(duì)應(yīng),這個(gè)時(shí)候,我們需要重載validate!
如在用戶注冊(cè)時(shí),我們需要填寫驗(yàn)證碼,這個(gè)驗(yàn)證碼只需要驗(yàn)證,不需要保存到用戶這個(gè)Model中:

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

2,某個(gè)字段不屬于指定model,它是read_only,只需要將它序列化傳遞給用戶,但是在這個(gè)model中,沒(méi)有這個(gè)字段!我們需要用到SerializerMethodField。
  假設(shè)需要返回用戶加入這個(gè)網(wǎng)站多久了,不可能維持這樣加入的天數(shù)這樣一個(gè)數(shù)據(jù),一般會(huì)記錄用戶加入的時(shí)間點(diǎn),然后當(dāng)用戶獲取這個(gè)數(shù)據(jù),我們?cè)儆?jì)算返回給它。

class UserSerializer(serializers.ModelSerializer):  
    days_since_joined = serializers.SerializerMethodField()
    # 方法寫法:get_ + 字段
    def get_days_since_joined(self, obj):
    # obj指這個(gè)model的對(duì)象
        return (now() - obj.date_joined).days

    class Meta:
        model = User

當(dāng)然,這個(gè)的SerializerMethodField用法還相對(duì)簡(jiǎn)單一點(diǎn),后面還會(huì)有比較復(fù)雜的情況

關(guān)于外鍵的serializers

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

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

ModelSerializer就更簡(jiǎn)單了,直接通過(guò)映射就好了
不過(guò)這樣只是用戶獲得的只是一個(gè)外鍵類別的id,并不能獲取到詳細(xì)的信息,如果想要獲取到具體信息,那需要嵌套serializer

category = CourseCategorySerializer()

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

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

寫到這里,我們的外鍵就基本講完了!還有一個(gè)小問(wèn)題:我們?cè)谏厦嫣岬組odelSerializer需要解決的第二個(gè)問(wèn)題中,其實(shí)還有一種情況,就是某個(gè)字段屬于指定model,但不能獲取到相關(guān)數(shù)據(jù)。
  假設(shè)現(xiàn)在是一個(gè)多級(jí)分類的課程,例如,編程語(yǔ)言–>python–>python入門學(xué)習(xí)課程,編程語(yǔ)言與python屬于類別,另外一個(gè)屬于課程,編程語(yǔ)言類別是python類別的一個(gè)外鍵,而且屬于同一個(gè)model,實(shí)現(xiàn)方法:

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

現(xiàn)在獲取編程語(yǔ)言下的課程,顯然無(wú)法直接獲取到python入門學(xué)習(xí)這個(gè)課程,因?yàn)樗鼈儍蓻](méi)有外鍵關(guān)系。SerializerMethodField( )也可以解決這個(gè)問(wèn)題,只要在自定義的方法中實(shí)現(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

上面的例子看起來(lái)有點(diǎn)奇怪,因?yàn)槲覀冊(cè)赟erializerMethodField()嵌套了serializer,就需要自己進(jìn)行序列化,然后再?gòu)膁ata就可以取出json數(shù)據(jù)。
  可以看到傳遞的參數(shù)是分別是:queryset,many=True多個(gè)對(duì)象,context上下文。這個(gè)context十分關(guān)鍵,如果不將request傳遞給它,在序列化的時(shí)候,圖片與文件這些Field不會(huì)再前面加上域名,也就是說(shuō),只會(huì)有/media/img…這樣的路徑!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容