rest_framework 序列化器

REST框架中的序列化器的工作方式與Django FormModelForm類非常相似之宿。
REST提供了一個Serializer類杰扫,可以將對象中的數(shù)據(jù)序列化成json,同時還可以將json數(shù)據(jù)反序列化成對象

第零篇、rest_framework的配置

# 1.創(chuàng)建一個 `django`項(xiàng)目屯阀,并生成一個`app`,命名為`my_app`
# 在 ALLOWED_HOSTS 允許所有
ALLOWED_HOSTS = ['*']

# 2. 安裝 djangorestframework
# 我這里安裝的 `restframework` 是 `3.11`版本,后續(xù)的用法以及源碼都基于此版本
pip install djangorestframework

# 3.將 restframework 配置到 `django`的`app`中
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'my_app.apps.MyAppConfig',
    'rest_framework',
]

# 4.數(shù)據(jù)初始化
python manage.py migrate

第一篇踱葛、rest_framework序列化反序列化流程與用法

這里以簡單的數(shù)據(jù)模型來講解土陪,只是為了弄明白 rest_framework的源碼流程,所以不涉及復(fù)雜關(guān)系笔喉,在第二篇中單獨(dú)講解 關(guān)系模型

1. REST序列化

注意:在 rest_framework中序列化器不僅適用于djangomodel數(shù)據(jù)而是對所有的對象都適用取视,在這里我們只以djangomodel作為數(shù)據(jù)對象。

使用 rest_framework的序列化的流程

  • 1.創(chuàng)建指定模型的序列化器(繼承serializers.Serializer)

    • 指明需要序列化的字段信息()
    id = serializers.IntegerField()
    name = serializers.CharField()
    size = serializers.IntegerField()
    
  • 2.在View類中常挚,生成序列化器對象

    # instance 就是我們要序列化的對象作谭,也可以傳入query_set
    book = Book.objects.first()
    book_serializer = BookSerializer(instance=book) 
    
    # 如果傳入的instance是復(fù)數(shù),則需要 many=True
    books = Book.objects.all()
    books_serializer = BookSerializer(instance=books, many=True) 
    
  • 3.從序列化器對象中取出序列化的數(shù)據(jù)

    data = book_serializer.data
    

example1. 簡單的數(shù)據(jù)序列化使用

# models.py
from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='書名')
    size = models.IntegerField(verbose_name='字?jǐn)?shù)')

# views.py

# 這里使用了 `rest_framework` 的 `Response`,也可以使用 django 提供的JsonResponse
# from django.http import JsonResponse

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book


class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    size = serializers.IntegerField()



class BookView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None) # 從 url 路徑中獲取 pk 字段信息
        book = Book.objects.filter(pk=pk).first()
        book_serializer = BookSerializer(instance=book)
        data = book_serializer.data
        return Response(data)
# urls.py

from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/book/<int:pk>/', views.BookView.as_view()),
]

使用postman,訪問http://127.0.0.1:8000/api/book/1/get方法
返回的結(jié)果如下:

{
    "id": 1,
    "name": "斗破蒼穹",
    "size": 3450000
}

example 2. 使用source更改字段名稱
source指定實(shí)際的數(shù)據(jù)獲取來源(字段或者函數(shù))

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(source='name') # 綁定 Book 中的 name 字段
    size = serializers.IntegerField()

指明title的數(shù)據(jù)來源name字段
返回結(jié)果如下

{
    "id": 1,
    "title": "斗破蒼穹",  // 這里  name 改成了 title
    "size": 3450000
}

example 3. 使用source獲取函數(shù)返回值
我們有時不僅需要 model字段數(shù)據(jù)奄毡,也需要函數(shù)數(shù)據(jù)折欠,借助source可以將函數(shù)進(jìn)行序列化

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='書名')
    size = models.IntegerField(verbose_name='字?jǐn)?shù)')

    category_choices = (
        (1, '仙俠修真'),
        (2, '歷史'),
        (3, '都市言情'),
    )
    # `django` 會自動生成`get_category_display()函數(shù)`
    category = models.IntegerField(choices=category_choices, default=1)

    def big_data(self):
        """是否大于300萬字"""
        return self.size > 3000000
class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()
    category_display = serializers.CharField(source='get_category_display')
    is_big_data = serializers.BooleanField(source='big_data')

序列化后的返回內(nèi)容:

{
    "id": 1,
    "name": "斗破蒼穹",
    "size": 3450000,
    "category": 1,
    "category_display": "仙俠修真",
    "is_big_data": true
}

2. REST反序列化簡單使用

反序列化就是將json數(shù)據(jù)轉(zhuǎn)成數(shù)據(jù)對象
反序列化使用流程:

  • 1.創(chuàng)建序列化器
    • 指定序列器的字段以及字段約束
    • 重寫update()create()方法
    1. 生成序列化器對象
    • 傳入 data參數(shù),instance參數(shù)可選,如果不傳入锐秦,則生成新的對象咪奖,如果傳入 instance參數(shù),則在傳入對象基進(jìn)行更新
    book_serializer = BookSerializer(instance=book, data=data)
    
  • 3.通過is_valid()對傳入的數(shù)據(jù)進(jìn)行驗(yàn)證

  • 4.執(zhí)行save()方法將數(shù)據(jù)反序列化成對象


example 1. 數(shù)據(jù)反序列化(創(chuàng)建對象)

# models.py
from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='書名')
    size = models.IntegerField(verbose_name='字?jǐn)?shù)')

    category_choices = (
        (1, '仙俠修真'),
        (2, '歷史'),
        (3, '都市言情'),
    )
    category = models.IntegerField(choices=category_choices, default=1)
# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book


class BookSerializer(serializers.Serializer):
    # 必須添加 read_only=True酱床,否則無法進(jìn)行`create`與`update`操作
    id = serializers.IntegerField(read_only=True) 
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()

    def create(self, validated_data):
        """
        返回一個 instance 實(shí)例對象
        """
        name = validated_data.get('name', None)
        size = validated_data.get('size', 0)
        category = validated_data.get('category', 2)
        book = Book.objects.create(
            name=name, size=size, category=category
        )
        book.save()  # 更新到數(shù)據(jù)庫中
        return book


class BookView(APIView):
    
    def post(self, request, *args, **kwargs):
        data = request.data  # 從 request 獲取POST傳過來的值

        # 沒有傳入 instance 參數(shù)赡艰,在 save() 中執(zhí)行 create(),
        book_serializer = BookSerializer(data=data)
        if book_serializer.is_valid(): # 驗(yàn)證數(shù)據(jù)是否合法
            # 將 json 數(shù)據(jù)轉(zhuǎn)化成 對象斤葱,實(shí)際調(diào)用我們重寫的 `create()`方法
            instance = book_serializer.save() 
            return Response(book_serializer.data)
        return Response(book_serializer.errors)

使用postman發(fā)送post請求慷垮,如下數(shù)據(jù):

{
    "name":"凡人修仙傳",
    "size":"4256732",
    "category":"1"
}

返回結(jié)果如下:

{
    "id": 4,
    "name": "凡人修仙傳",
    "size": 4256732,
    "category": 1
}

可以看出,我們創(chuàng)建了新的 book對象揍堕,在數(shù)據(jù)庫中的 id4


example 2. 數(shù)據(jù)反序列化(更新對象)

前端發(fā)送json數(shù)據(jù)料身,在數(shù)據(jù)庫中進(jìn)行更新。
全部更新對應(yīng) put請求衩茸,部分更新對應(yīng)patch請求芹血,這里我們以put 請求為例。

對對象進(jìn)行更新楞慈,需要實(shí)現(xiàn)序列化器的update()方法幔烛,并且在創(chuàng)建序列化器時,傳入instance實(shí)例對象囊蓝。

# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book


class BookSerializer(serializers.Serializer):
    # 必須添加 read_only=True饿悬,否則無法進(jìn)行`create`與`update`操作
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()

    def create(self, validated_data):
        """
        返回一個 instance 實(shí)例對象
        """
        name = validated_data.get('name', None)
        size = validated_data.get('size', 0)
        category = validated_data.get('category', 2)
        book = Book.objects.create(
            name=name, size=size, category=category
        )
        book.save()  # 更新到數(shù)據(jù)庫中
        return book

    def update(self, instance, validated_data):
        """
        對 instance 實(shí)例對象進(jìn)行數(shù)據(jù)更新
        """
        for name, value in validated_data.items():
            setattr(instance, name, value)
        instance.save() # 將數(shù)據(jù)更新到數(shù)據(jù)庫中
        return instance

class BookView(APIView):

    def post(self, request, *args, **kwargs):
        """用于創(chuàng)建對象"""
        data = request.data  # 從 request 獲取POST傳過來的值
        # 沒有傳入 instance 參數(shù),在 save() 中執(zhí)行 create()聚霜,
        book_serializer = BookSerializer(data=data)
        if book_serializer.is_valid():
            instance = book_serializer.save()
            return Response(book_serializer.data)
        return Response(book_serializer.errors)

    def put(self, request, *args, **kwargs):
        """用于更新資源的部分信息"""
        data = request.data  # 從 request 獲取傳過來的值
        pk = kwargs.get('pk', None) # 從 `url` 路徑中獲取`pk`值
        book = Book.objects.get(pk=pk)
        book_serializer = BookSerializer(instance=book, data=data)
        if book_serializer.is_valid():
            instance = book_serializer.save()  # 更新后的實(shí)例對象 instance
            return Response(book_serializer.data)  # 將更新后的實(shí)例進(jìn)行序列化返回的 json 數(shù)據(jù)
        return Response(book_serializer.errors)
# urls.py

from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/book/<int:pk>/', views.BookView.as_view()),
]

我們對 id4的book進(jìn)行更新狡恬,使用postmanhttp://127.0.0.1:8000/api/book/4/,發(fā)送put請求蝎宇,內(nèi)容如下:

{
    "name":"凡人修仙傳之都市篇",
    "size":"4256732",
    "category":3
}

由于我們在 BookSerializer中定義的id字段具有read_only=True約束弟劲,所有我們在發(fā)送的json數(shù)據(jù)中可以不填id信息,但是其它的信息必須填寫姥芥。

返回內(nèi)容:

{
    "id": 4,
    "name": "凡人修仙傳之都市篇",
    "size": 4256732,
    "category": 3
}

總結(jié):對反序列化而言兔乞,我們要做的就是實(shí)現(xiàn)序列化器的update()create()方法,然后調(diào)用save(),save()內(nèi)部會幫我們分發(fā)到update()或者create()

save()源碼流程:
查看源碼的技巧:對斷言assert部分不用太關(guān)注凉唐,抓住主要的邏輯

    def save(self, **kwargs):
        # 1.傳入自己補(bǔ)充的信息庸追,將補(bǔ)充的信息放入 `validated_data` 中
        validated_data = dict(
            list(self.validated_data.items()) +
            list(kwargs.items())
        )
        
        # 如果沒有傳入 `instance`,則調(diào)用 `update`方法
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)

        # 否則調(diào)用 `create` 方法
        else:
            self.instance = self.create(validated_data)
          
        return self.instance

3. REST反序列化熊榛,字段約束

反序列化數(shù)據(jù)時锚国,使用is_valid()對傳入的data數(shù)據(jù)進(jìn)行驗(yàn)證。
字段約束類似于 django的約束條件
如果發(fā)生任何驗(yàn)證錯誤玄坦,則.errors屬性將包含代表所得錯誤消息


example 1. 字段缺少展示
如果上述的update傳入時,size信息并沒有改動血筑,我們不傳入size

{
    "name":"凡人修仙傳之都市篇",
    "category":3
}

則返回信息:

{
    "size": [
        "This field is required."
    ]
}

這是因?yàn)槲覀兌x的序列化器,如果沒有指定requiredFalse,則必須傳入值绘沉,或者指定默認(rèn)值。
改成如下豺总,則不再報錯车伞,可以在更新或者生成時,不傳入size屬性

class BookSerializer(serializers.Serializer):
    # 必須添加 read_only=True喻喳,否則無法進(jìn)行`create`與`update`操作
    id = serializers.IntegerField(read_only=True) 
    name = serializers.CharField()
    size = serializers.IntegerField(required=False)
    category = serializers.IntegerField()

3.1 所有的字段共有的約束條件以及默認(rèn)值:read_only=False,write_only=False,required=None,allow_null=False,validators=None
  • read_only表示是否只讀屬性另玖,一般用于 id,表示只能看表伦,不能改谦去,就算我們傳入了id信息,如:
    {   
      "id":5,
      "name":"凡人修仙傳之都市篇",
      "category":3
    }
    
    返回的結(jié)果中蹦哼,id依然不會改變鳄哭,這是因?yàn)樵?code>create和update中,read_only=True的信息根本不會被封裝到validated_data

如果我們對某字段使用了read_only=True纲熏,還想要在 createupdate中對數(shù)據(jù)進(jìn)行修改妆丘,可以在 save()方法中,將信息傳入

pk = kwargs.get('pk',None)  # 從 url 中獲取`pk`信息
book_serializer.save(id=pk)

備注:這里只是為了演示save()從外部獲取數(shù)據(jù)局劲,但是不建議更改id字段

  • write_only表示只寫屬性勺拣,一般用于一些保密的信息,例如password鱼填,只能用于創(chuàng)建或更新药有,但是返回時,不對該字段信息進(jìn)行序列化剔氏。

例如我們對上述的name字段添加write_only約束塑猖,則更新后返回的信息中不再具有name

{
    "id": 4,
    "size": 4256732,
    "category": 3
}
  • required表示字段是否必填竹祷,如果required = False谈跛,則可以不傳入該字段信息。
  • allow_null 表示傳入的數(shù)據(jù)是否可以為空塑陵。
  • validators(驗(yàn)證器列表)感憾,可以傳入多個函數(shù)對象(callback),通過函數(shù)對字段進(jìn)行自定義約束令花。

example 2. 使用字段的validators阻桅,實(shí)現(xiàn)自定義約束

實(shí)現(xiàn)自定義約束函數(shù),返回結(jié)果只能有兩種

  • 返回一個驗(yàn)證后的數(shù)據(jù) 兼都,表示驗(yàn)證通過
  • 拋出 ValidationError 異常嫂沉,表示驗(yàn)證不通過
from rest_framework.exceptions import ValidationError

def sensitive_word_filter(value):
    """
    敏感字過濾
    返回驗(yàn)證通過的數(shù)據(jù),不通過則拋出 `ValidationError` 異常
    拋出 `ValidationError` 由 `is_valid()`接收
    """
    sensitive_words = ['*****', '***', '****']
    if value in sensitive_words:
        raise ValidationError(f'{value}是敏感詞')
    return value
class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)

    # 注意這里傳入的函數(shù)對象扮碧,而不是 sensitive_word_filter()
    name = serializers.CharField(validators=[sensitive_word_filter, ])
    size = serializers.IntegerField(required=False)
    category = serializers.IntegerField()

3.2 特殊字段的獨(dú)有約束條件

django一樣趟章,除了共有的約束信息杏糙,rest_framework中不同的字段,有不同的約束條件

  • CharFieldmin_length,max_length,trim_whitespace,allow_blank
  • EmailField: 自動驗(yàn)證是否符合郵箱格式
  • IntegerField, FloatField : max_value, min_value
  • DateTimeField : format序列化時的輸出格式 蚓土, input_formats 反序列化的輸入字符串格式列表宏侍,可以使用多個格式;default_timezone,設(shè)置時區(qū)

example 3. 使用DateTimeField的獨(dú)有約束蜀漆,實(shí)現(xiàn)自定義時間的輸入,輸出格式

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='書名')
    size = models.IntegerField(verbose_name='字?jǐn)?shù)')

    category_choices = (
        (1, '仙俠修真'),
        (2, '歷史'),
        (3, '都市言情'),
    )
    category = models.IntegerField(choices=category_choices, default=1)
    # 這里 created 是后續(xù)加上去的谅河,必須要有 null=True, blank=True 才能遷移成功 
    created = models.DateTimeField(null=True, blank=True) 
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book
from my_app.utils.validators import sensitive_word_filter

format = '%Y-%m-%d %H:%M:%S'

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)

    # 注意這里傳入的函數(shù)對象,而不是 sensitive_word_filter()
    name = serializers.CharField(validators=[sensitive_word_filter, ])
    size = serializers.IntegerField(required=False)
    category = serializers.IntegerField()

    # 指定輸入與輸出的字符串格式
    created = serializers.DateTimeField(format=format, input_formats=[format, ])

    def create(self, validated_data):
        """
        返回一個 instance 實(shí)例對象
        """
        name = validated_data.get('name', None)
        size = validated_data.get('size', 0)
        category = validated_data.get('category', 2)
        book = Book.objects.create(
            name=name, size=size, category=category
        )
        book.save()  # 更新到數(shù)據(jù)庫中
        return book

    def update(self, instance, validated_data):
        for name, value in validated_data.items():
            setattr(instance, name, value)
        instance.save()
        return instance


class BookView(APIView):

    def get(self, request, *args, **kwargs):
        """獲取指定 pk 的 `book` 信息"""
        pk = kwargs.get('pk', None)
        book = Book.objects.get(pk=pk)
        book_serializer = BookSerializer(instance=book)
        return Response(book_serializer.data)


    def put(self, request, *args, **kwargs):
        """用于更新資源信息"""
        data = request.data  # 從 request 獲取傳過來的值
        pk = kwargs.get('pk', None)
        book = Book.objects.get(pk=pk)
        # 傳入了instance 對象确丢,`.save()`執(zhí)行更新操作
        book_serializer = BookSerializer(instance=book, data=data)
        if book_serializer.is_valid():
            instance = book_serializer.save()  # 更新后的實(shí)例對象 instance
            return Response(book_serializer.data)  # 將更新后的實(shí)例進(jìn)行序列化返回的 json 數(shù)據(jù)
        return Response(book_serializer.errors)
# urls.py

from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/book/<int:pk>/', views.BookView.as_view()),
]

http://127.0.0.1:8000/api/book/3/ 發(fā)送put請求绷耍,請求內(nèi)容:

{   
    "name":"雪中悍刀行",
    "category":3,
    "created":"2013-11-04 05:23:49"
}

返回內(nèi)容:

{
    "id": 3,
    "name": "雪中悍刀行",
    "size": 4651209,
    "category": 3,
    "created": "2013-11-04 05:23:49"
}

如果請求輸入的datetime 格式可能有多種,我們也可以在input_formats設(shè)置多個


3.3 自定義驗(yàn)證函數(shù)

3.1 中使用在字段上validators可以實(shí)現(xiàn)自定義函數(shù)驗(yàn)證鲜侥,除了這一種方法外锨天,我們還可以使用另一種驗(yàn)證方式。
創(chuàng)建.validate_<field_name>方法可以指定自定義字段級驗(yàn)證

如下剃毒,為username綁定自定義函數(shù)約束/驗(yàn)證
example 4. .validate_<field_name>函數(shù)級驗(yàn)證

# models.py
from django.db import models

class UserInfo(models.Model):
    username = models.CharField(max_length=16)
    password = models.CharField(max_length=64)
    email = models.CharField(max_length=64)
    created_date = models.DateField()
    money = models.IntegerField()
from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from my_app.models import UserInfo

format = '%Y-%m-%d'
input_formats = ['%Y-%m-%d', '%Y %m %d', '%Y/%m/%d', '%Y.%m.%d']

class UserInfoSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    username = serializers.CharField(max_length=16)
    password = serializers.CharField(max_length=64, write_only=True)
    email = serializers.EmailField(max_length=64)
    created_date = serializers.DateField(format=format, input_formats=input_formats)
    money = serializers.FloatField(min_value=0)

    def validate_username(self, value):
        if not value.startswith('中國公民_'):
            raise ValidationError('用戶名稱必須以`中國公民_`開頭')
        return value

    def create(self, validated_data):
        user = UserInfo()
        for key, value in validated_data.items():
            setattr(user, key, value)
        user.save()
        return user

class UserView(APIView):

    def post(self, request, *args, **kwargs):
        data = request.data
        user_serializer = UserInfoSerializer(data=data)
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data)
        return Response(user_serializer.errors)
from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/book/<int:pk>/', views.BookView.as_view()),
    path('api/user/<int:pk>/', views.UserView.as_view()),
]

發(fā)送post請求病袄,創(chuàng)建時,username必須以中國公民_開頭'


validate_<field_name>函數(shù)約束與validators約束的區(qū)別:
最主要的是validate_<field_name>只能給某一個字段使用赘阀,而validators適用于多個字段益缠。
我們一般把通用型的字段驗(yàn)證放在validators中,以便供更多的字段適用基公。


3.4 對象級驗(yàn)證

3.1 - 3.3 都是對一個字段進(jìn)行驗(yàn)證幅慌,但在特殊的情況下,我們可能涉及到多個字段之間的關(guān)系驗(yàn)證轰豆,這就需要我們直接對 data 進(jìn)行驗(yàn)證胰伍。
重寫 Serializervalidate()方法,可以直接對data進(jìn)行處理酸休。

  • 拋出異常ValidationError說明驗(yàn)證失敗
  • 返回處理后的dict格式的數(shù)據(jù),說明驗(yàn)證成功
class MySerializer(serializers.Serializer):

    def validate(self, data):
        if data['start'] > data['end']:
            raise ValidationError('開始時間必須小于結(jié)束時間')
        return data

4. REST多個對象的序列化反序列化

前面的講解中骂租,我們都是使用的單個對象的獲取,但是在實(shí)際工作中斑司,我們更多的是一次獲取多個對象渗饮,并進(jìn)行分頁。

example 1.通過 get 獲取多個對象數(shù)據(jù) 以及通過post創(chuàng)建多個對象

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='書名')
    size = models.IntegerField(verbose_name='字?jǐn)?shù)')

    category_choices = (
        (1, '仙俠修真'),
        (2, '歷史'),
        (3, '都市言情'),
    )
    category = models.IntegerField(choices=category_choices, default=1)
    created = models.DateTimeField(null=True, blank=True)
# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book, UserInfo
from rest_framework.exceptions import ValidationError

format = '%Y-%m-%d %H:%M:%S'
input_formats = ['%Y-%m-%d %H:%M:%S', '%Y %m %d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y.%m.%d %H:%M:%S']

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()
    created = serializers.DateTimeField(format=format, input_formats=input_formats)

    def create(self, validated_data):
        """
        返回一個 instance 實(shí)例對象
        """
        book = Book(validated_data)
        book.save()  # 更新到數(shù)據(jù)庫中
        return book

    def validate_category(self, value):
        """對類別 category 進(jìn)行約束驗(yàn)證"""
        allow_category = [1, 2, 3]
        if value not in allow_category:
            raise ValidationError('類別不符合')
        return value

class BooksView(APIView):
    def get(self, request, *args, **kwargs):
        books = Book.objects.all()
        book_serializers = BookSerializer(instance=books, many=True)
        return Response(book_serializers.data)

    def post(self, request, *args, **kwargs):
        data = request.data
        book_serializers = BookSerializer(data=data, many=True) # 這里傳入的是多個對象宿刮,必須使 `many = True`
        if book_serializers.is_valid():
            book_serializers.save()
            return Response(book_serializers.data)
        return Response(book_serializers.errors)
# urls.py

from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/books/', views.BooksView.as_view()),
]

發(fā)送 get請求互站,獲取全部的books數(shù)據(jù)

[
    {
        "id": 1,
        "name": "斗破蒼穹",
        "size": 3450000,
        "category": 1,
        "created": null
    },
    {
        "id": 2,
        "name": "大主宰",
        "size": 3982365,
        "category": 1,
        "created": null
    },
    {
        "id": 3,
        "name": "雪中悍刀行",
        "size": 4651209,
        "category": 3,
        "created": "2013-11-04"
    },
    {
        "id": 4,
        "name": "凡人修仙傳之都市篇",
        "size": 4256732,
        "category": 3,
        "created": null
    }
]

發(fā)送 post請求進(jìn)行創(chuàng)建,如果數(shù)據(jù)中有一個對象的數(shù)據(jù)驗(yàn)證失敗僵缺,則本次創(chuàng)建失效

當(dāng) many = True時胡桃,我們創(chuàng)建的序列化器的實(shí)例對象并不是我們當(dāng)前的BookSerializer對象,而是 ListSerializer 對象磕潮,但是我們調(diào)用 ListSerializer 對象的 is_valid() 以及 save()方法時候翠胰,好像都是BookSerializer在起作用懊纳,這是因?yàn)?ListSerializer 內(nèi)部將傳入的集合對象進(jìn)行迭代遍歷,然后再交給
BookSerializer處理
如下是ListSerializer源碼

# ListSerializer 類
    def create(self, validated_data):
        # validated_data 是一個集合對象
        return [
            # 這里的 `child` 就是`BookSerializer`實(shí)例對象
            self.child.create(attrs) for attrs in validated_data
        ]

example 2.自定義ListSerializer的某些功能

  • 第一步 繼承ListSerializer亡容,并重寫部分功能
class MyListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        # do something
        return super().create(validated_data)
  • 第二步嗤疯,在使用序列化類時,指定 list_serializer_class屬性
class BookSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = BookListSerializer

這樣我們在執(zhí)行 save()方法時闺兢,會調(diào)用MyListSerializercreate()方法

5. REST序列化反序列化的源碼流程

5.1 序列化源碼流程


查看源碼的一些技巧

  • 1.找到主要的入口點(diǎn)茂缚,一步步理出脈絡(luò),而不是從import ……·一行行看起
  • 2.對于有繼承關(guān)系的類屋谭,每次執(zhí)行方法都從頂層類找起
class A:
    def step_1(self):
        print('A.step_1')
        self.step_2()

    def step_2(self):
        print('A.step_2')
        self.step_3()

    def step_3(self):
        print('A.step_3')


class B(A):

     def step_2(self):
        print('B.step_2')
        self.step_3()


class C(B):

    def step_3(self):
        print('C.step_3')


if __name__ == '__main__':
    c = C()
    c.step_1()

打印結(jié)果如下:

A.step_1
B.step_2
C.step_3

這里雖然第一步執(zhí)行了A.step_1,在A.step_1中執(zhí)行self.step_2()并不會直接執(zhí)行A.step_2脚囊,而是按照繼承關(guān)系,先從c對象查找step_2()方法桐磁,沒有的話就繼續(xù)往上查找悔耘,找到Bstep_2,所以先執(zhí)行B.step_2我擂,每一步都是如此衬以,從頂層查找,底層的方法只會被覆蓋校摩。

  • 3.抓住主要的邏輯 對于assert這些驗(yàn)證看峻,或者拋出異常的部分,可以不用太關(guān)注衙吩。
    例如完整的save()方法如下
    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

這里面最重要的就只有三段,我們主要理解這三段就好互妓,后面有時間再研究那些assert驗(yàn)證

  • 4.見名猜意,我們根據(jù)方法的名稱先假設(shè)作用,把整個走一下坤塞,如果能走的通冯勉,說明我們的假設(shè)應(yīng)該問題不大,然后再去驗(yàn)證我們的假設(shè)對不對摹芙。

備注:這里的源碼分析灼狰,我們也是將方法中的主要邏輯代碼片段貼出來,沒有全部展示瘫辩。


當(dāng)我們執(zhí)行序列化時伏嗜。通過book_serializer.data來獲取數(shù)據(jù)

  • 第一步: 執(zhí)行 SerializerdataSerializerdata調(diào)用父類BaseSerializerdata.
    BaseSerializerdata中伐厌,主要執(zhí)行以下片段
# 1.具有`instance`實(shí)例對象,則將實(shí)例對象序列化
if self.instance is not None and not getattr(self, '_errors', None):
     self._data = self.to_representation(self.instance)

# `_validated_data` 是我們傳入的`data`參數(shù)經(jīng)`is_valid()`驗(yàn)證后的數(shù)據(jù)
# 2.沒有`instance`實(shí)例裸影,但是傳入了`data`數(shù)據(jù)挣轨,并驗(yàn)證通過,將`data`數(shù)據(jù)序列化
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
     self._data = self.to_representation(self.validated_data)
  • 第二步:執(zhí)行to_representation()函數(shù)
    這里 Serializer具有to_representation()函數(shù)轩猩,所以直接調(diào)用
    注意:這里的參數(shù)instance既可以是數(shù)據(jù)對象卷扮,也可以是validated_data字典數(shù)據(jù)
    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        # 創(chuàng)建有序的 dict
        # 這樣返回的`json`數(shù)據(jù)順序就會和我們創(chuàng)建的`Serializer`中聲明的一致
        ret = OrderedDict() 

        # 獲取可讀的字段荡澎,如果字段約束條件中`write_only = True`,則在這里無法獲取晤锹,也就不會進(jìn)行序列化
        fields = self._readable_fields 
        
        # fields 就是我們聲明的字段 例如:name = serializers.CharField()
        for field in fields:
            try:
                # 如果`instance`中是數(shù)據(jù)對象
                # 從我們提供的屬性名稱/方法名稱摩幔,如:`name`,`get_choice_display`
                # 通過反射 獲取屬性值

                # 如果`instance`是 字典數(shù)據(jù),也就是`validated_data`
                # 則直接返回 instance[attr]
                attribute = field.get_attribute(instance)
            except SkipField:
                continue
            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                # 執(zhí)行各個字段的 `to_representation()`方法
                # 主要是將數(shù)據(jù)轉(zhuǎn)化為各個字段的格式
                # 例如 `IntegetField` 中,執(zhí)行 int(value)
                # 在 `CharField`中鞭铆,執(zhí)行 str(value)
                # 在 `DateTimeField`中或衡,則根據(jù)我們傳入的`format`參數(shù),對數(shù)據(jù)進(jìn)行字符串格式化
                ret[field.field_name] = field.to_representation(attribute)

        return ret

從上面看车遂,rest_framework的數(shù)據(jù)序列化主要都是在to_representation中完成封断,如果我們要自定義數(shù)據(jù)序列化的格式,則可以重寫to_representation

from collections import OrderedDict
from rest_framework import serializers


class UpperKeySerializer(serializers.Serializer):
    ……

    def to_representation(self, instance):
        """
        重寫了`to_representation`,返回結(jié)果中的`key`,全部返回`大寫`
        """
        origin_ret = super().to_representation(instance)
        ret = OrderedDict()
        for field_name, value in origin_ret.items():
            ret[field_name.upper] = value

        return ret

這里有一個注意點(diǎn)舶担,我們執(zhí)行post操作時坡疼,創(chuàng)建序列化器對象時,并沒有傳入instance衣陶,而是傳入了data = data柄瑰,并執(zhí)行is_valid()驗(yàn)證通過,但是序列化出來的結(jié)果中包含id屬性剪况,這明顯是直接將instance序列化了狱意。

這是因?yàn)槲覀儓?zhí)行save()操作,在save()中將create()創(chuàng)建的instance賦值給了self.所以self.instance為真拯欧,to_representation也就直接作用于該instance详囤,而不是我們傳入的data = data.


5.2反序列化源碼流程

執(zhí)行反序列化主要有兩步:

  • book_serailizer.is_valid()
  • book_serailizer.save()
    其中 save()方法已經(jīng)講解過,就是調(diào)用create或者update方法镐作。

反序列化的源碼主要在數(shù)據(jù)約束 / 驗(yàn)證上比較復(fù)雜藏姐,這里主要討論這一部分。

  • 第一步该贾,執(zhí)行Serializer類的is_valid()

    
    def is_valid(self, raise_exception=False):
    
        # 是否有`_validated_data` 是判斷是否執(zhí)行過`is_valid()`的依據(jù)
        # 如果存在`_validated_data` 羔杨,說明已經(jīng)驗(yàn)證過,直接返回杨蛋;防止多次調(diào)用`is_valid`
        if not hasattr(self, '_validated_data'):
            try:
                #  `is_valid`主要就是執(zhí)行這一行
                # `initial_data` 就是我們傳入的`data = data`參數(shù)兜材,在` __init__()`中被初始化為`initial_data`
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}
    
        if self._errors and raise_exception:
            raise ValidationError(self.errors)
    
        return not bool(self._errors)
    

    從這里可以看出,如果 is_valid()執(zhí)行成功逞力,則會產(chǎn)生_validated_data數(shù)據(jù)曙寡,在createupdate中傳入的 validated_data實(shí)際就是這里的_validated_data

  • 第二步,執(zhí)行 Serializer類的run_validation()

    def run_validation(self, data=empty):
        # 1.驗(yàn)證傳入的`data`是否為空
        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data

        # 2.將數(shù)據(jù)轉(zhuǎn)化為`內(nèi)部值`
        # 這里進(jìn)行了第一種驗(yàn)證 
        # 這里的 `data` 是我們傳入的字典寇荧,那么返回出來的 `value`應(yīng)該也是字典
        value = self.to_internal_value(data)
        try:
            # 這里進(jìn)行了第二種驗(yàn)證举庶,添加驗(yàn)證器
            self.run_validators(value)

            # 這里進(jìn)行了第三種驗(yàn)證,也就是我們前面`3.4`章節(jié)講的對象級驗(yàn)證揩抡,鉤子函數(shù)就埋在這
            value = self.validate(value)
            assert value is not None, '.validate() should return the validated data'
        except (ValidationError, DjangoValidationError) as exc:
            raise ValidationError(detail=as_serializer_error(exc))

        return value

上面的第三種驗(yàn)證户侥,在我們的3.4已經(jīng)講過镀琉,這里不再講解,第二種驗(yàn)證是自定義驗(yàn)證器類蕊唐,比較復(fù)雜屋摔,一般用不到,這里下一步著重講解to_internal_value

  • 第三步替梨, Serailizer類實(shí)現(xiàn)了to_internal_value()钓试,我們直接查看
    def to_internal_value(self, data):
        """
        這里主要執(zhí)行字段級別的驗(yàn)證
        對`data`字典的驗(yàn)證在`run_validators(value)`與 `validate(value)`完成
        """
        # 創(chuàng)建一個有序字典
        ret = OrderedDict()
        errors = OrderedDict()

        # 獲取可寫 / 可反序列化的字段對象
        # 例如我們將`id`設(shè)置為`read_only = True`,則這里不會獲取`id`字段
        fields = self._writable_fields
        
        for field in fields:
            # 第一種字段約束
            # 這里執(zhí)行自定義函數(shù)驗(yàn)證
            # 我們`3.3`章節(jié)`validate_<field_name>`的鉤子埋在這
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                # 第二種字段約束
                # 這里執(zhí)行字段的基礎(chǔ)驗(yàn)證
                # 對應(yīng)我們的`3.1`, `3.2`字段約束部分
                # 3.1中 `required ` , `validators ` ……
                # 3.2中`CharField`的 `max_length`耙替,`IntegerField`的`max_value`等都是在 `field.run_validation`中完成
                validated_value = field.run_validation(primitive_value)
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            except DjangoValidationError as exc:
                errors[field.field_name] = get_error_detail(exc)
            except SkipField:
                pass
            else:
                set_value(ret, field.source_attrs, validated_value)

        if errors:
            raise ValidationError(errors)

        return ret

6. REST ModelSerializer使用

對于 rest_framework的序列化器亚侠,所有的基礎(chǔ)功能都是基于Serializer來實(shí)現(xiàn)的,除此之外rest_framework還提供了與Django模型定義緊密映射的序列化程序類ModelSerializer俗扇。

ModelSerializer繼承了Serializer硝烂,它主要有以下三種新的功能:

  • 它將根據(jù)模型自動為您生成一組字段,這也是它最主要的作用铜幽。
  • 它將自動為序列化器生成驗(yàn)證器滞谢,例如unique_together驗(yàn)證器。
  • 它包括簡單的默認(rèn)實(shí)現(xiàn)create()update()

6.1 ModelSerializer 自動生成字段

# models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=16)
    password = models.CharField(max_length=32)
    money = models.FloatField()
    category_choices = (
        (1, '普通用戶'),
        (2, 'VIP'),
    )
    category = models.IntegerField(choices=category_choices)

example 1 使用fields自動生成所有字段

from rest_framework.serializers import ModelSerializer

class UserSerializer(ModelSerializer):
    class Meta:
        model = UserInfo
        fields = '__all__'

使用pycharm編輯器自帶的Python Console進(jìn)入shell界面
[ shell中自動導(dǎo)入了django環(huán)境除抛,可以直接使用django中的model對象狮杨,否則會報ImproperlyConfigured錯誤 ]
shell中執(zhí)行以下

>>> from my_app.models import UserSerializer
>>> user_serializer = us = UserSerializer()
>>> user_serializer

打印結(jié)果如下:

UserSerializer():
    id = IntegerField(label='ID', read_only=True)
    username = CharField(max_length=16)
    password = CharField(max_length=32)
    money = FloatField()
    category = ChoiceField(choices=((1, '普通用戶'), (2, 'VIP')))

可以看出,ModelSerializer自動為我們創(chuàng)建了字段到忽,并添加了字段約束橄教,像read_only=True會自定為 django的主鍵添加,而max_length也是從UserInfo中獲取的喘漏。這樣可以為我們節(jié)省很多時間护蝶。

生成的UserSerializer實(shí)際與我們自己前面所寫的Serializer功能是一模一樣


example 2 自動生成部分字段

class UserSerializer(ModelSerializer):
    class Meta:
        model = UserInfo
        fields = ['id', 'username', 'password'] # `fields` 可以為`__all__` 或者一個列表  

example 3 使用exclude屬性設(shè)置為要從序列化器中排除的字段列表。

class UserSerializer(ModelSerializer):
    class Meta:
        model = UserInfo
        exclude = ['category', 'money'] 

注意:從3.3.0版開始翩迈,必須提供屬性fieldsexclude之一持灰。


6.2 使用extra_kwargs設(shè)置關(guān)鍵字參數(shù)

使用 ModelSerializer可以方便的幫我們生成字段,并且根據(jù)model自動添加了約束,如:id字段的read_only = True负饲,CharFieldmax_length等堤魁。

但是有的時候我們還是需要自己設(shè)置關(guān)鍵字參數(shù),例如給password添加write_only = True,給money字段添加min_value = 0.0
一種方式是重寫這些字段

class UserSerializer(ModelSerializer):
    password = serializers.CharField(write_only=True, max_length=32)
    money = serializers.FloatField(min_value=0.0)

    class Meta:
        model = UserInfo
        fields = '__all__'

除了上面的方法返十,也可以通過extra_kwargs為指定的字段添加額外的關(guān)鍵字參數(shù)

class UserSerializer(ModelSerializer):

    class Meta:
        model = UserInfo
        fields = '__all__'
        extra_kwargs = {
            # `ModelSerializer` 生成的'password'已經(jīng)有`max_length = 32`妥泉,這里不需要再設(shè)置。
            'password': {'write_only': True}, 
            'money': {'min_value': 0.0}
        }

6.3 ModelSerializer設(shè)置關(guān)系深度depth

當(dāng)我們的model模型中涉及與其它表的關(guān)系時吧慢,使用ModelSerializer生成的關(guān)系一般使用主鍵表示

# models.py

from rest_framework.serializers import ModelSerializer
from django.db import models

class User(models.Model):
    username = models.CharField(max_length=16)
    detail = models.CharField(max_length=256)


class Article(models.Model):
    title = models.CharField(max_length=64)
    user = models.ForeignKey(User, on_delete=models.CASCADE)


class ArticleSerializer(ModelSerializer):
    class Meta:
        model = Article

Djangoshell 界面執(zhí)行以下:

>>> from my_app.models import ArticleSerializer
>>> article_ser = ArticleSerializer()
>>> article_ser

打印內(nèi)容如下

ArticleSerializer():
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=64)
    user = PrimaryKeyRelatedField(queryset=User.objects.all())

序列化后的數(shù)據(jù)如下:

[
    {
        "id": 1,
        "title": "梟臣",
        "user": 2
    },
    {
        "id": 2,
        "title": "斗破蒼穹",
        "user": 1
    }
]

這里的user以主鍵代替User
當(dāng)設(shè)置depth = 1

class ArticleSerializer(ModelSerializer):
    class Meta:
        model = Article
        depth = 1 # depth 是整數(shù)涛漂,表示向下關(guān)系深度,一般不建議在3層以上
[
    {
        "id": 1,
        "title": "梟臣",
        "user": {
            "id": 2,
            "username": "更俗",
            "detail": "更俗检诗,原為起點(diǎn)中文網(wǎng)白金作家匈仗,2010年轉(zhuǎn)投縱橫中文網(wǎng)成為其簽約作家。"
        }
    },
    {
        "id": 2,
        "title": "斗破蒼穹",
        "user": {
            "id": 1,
            "username": "天蠶土豆",
            "detail": "天蠶土豆逢慌,本名李虎悠轩。1989年12月28日出生于四川,中國內(nèi)地網(wǎng)絡(luò)小說作家攻泼、85后著名作家火架、浙江省網(wǎng)絡(luò)作家協(xié)會副主席..."
        }
    }
]

注意:使用depth后,關(guān)系嵌套為只讀忙菠。也就是這里的user為只讀屬性何鸡。

第二篇、rest_framework序列化器關(guān)系牛欢。

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=64)
    create = models.DateTimeField('發(fā)布時間')
    visitors = models.IntegerField('閱讀人數(shù)')
    user = models.ForeignKey(User, on_delete=models.CASCADE)

在上面的模型中骡男,對于簡單的字段,我們已經(jīng)知道如何序列化傍睹,但是對user知道關(guān)系字段隔盛,我們用專門的序列化字段可選。

  • PrimaryKeyRelatedField , 可以通過關(guān)系的主鍵來表示關(guān)系對象拾稳。推薦使用
  • StringRelatedField,使用__str__方法值來表示關(guān)系對象吮炕。
  • HyperlinkedRelatedField,通過超鏈接來表示關(guān)系對象。
  • SlugRelatedField,使用目標(biāo)上的字段來表示關(guān)系對象访得。

7.1 PrimaryKeyRelatedField使用

from django.db import models
from rest_framework.serializers import ModelSerializer

class User(models.Model):
    username = models.CharField(max_length=16)
    detail = models.CharField(max_length=256)


class Article(models.Model):
    title = models.CharField(max_length=64)
    user = models.ForeignKey(User, on_delete=models.CASCADE)


class ArticleSerializer(ModelSerializer):
    # 重寫 `user`字段
    user = PrimaryKeyRelatedField(queryset=User.objects.filter(pk__in=[1, 2]))

    class Meta:
        model = Article
        fields = '__all__'

序列化后的結(jié)果為:

[
    {
        "id": 1,
        "user": 2,
        "title": "楚臣"
    },
    {
        "id": 2,
        "user": 2,
        "title": "官場之風(fēng)流人生"
    },
    {
        "id": 3,
        "user": 2,
        "title": "梟臣"
    },
    {
        "id": 4,
        "user": 1,
        "title": "斗破蒼穹"
    },
    {
        "id": 5,
        "user": 1,
        "title": "武動乾坤"
    }
]

這里我們使用了PrimaryKeyRelatedField龙亲,所以就是使用user的主鍵來表示user對象。
注意:PrimaryKeyRelatedField關(guān)系字段的關(guān)鍵字參數(shù)悍抑。

  • 共有的基礎(chǔ)設(shè)置鳄炉, 3.1read_only=False, write_only=False, required=None, allow_null=False, validators=None
    這里比較常用的就是read_only = True传趾,將關(guān)系設(shè)置為只讀屬性迎膜。
  • many = False,如果我們的關(guān)系對象為多個浆兰,則需要設(shè)置many = True磕仅,這樣序列化的字段會表示為列表
class UserSerializer(ModelSerializer):
    # `article_set` 是`User` 的反向關(guān)系字段
    # 在 `Django`中,多對一的反向字段都是以 `<fieldname>_set`
    # 如果我們不明確指定簸呈,`ModelSerializer`不會自動創(chuàng)建
    # 我們在創(chuàng)建 / 更新`User`對象時榕订,不需要`article`參數(shù),所以`read_only = True`
    article_set = PrimaryKeyRelatedField(read_only = True,many = True)

    class Meta:
        model = User
        fields = '__all__'
[
    {
        "id": 1,
        "article_set": [
            5,
            6
        ],
        "username": "天蠶土豆",
        "detail": "天蠶土豆蜕便,本名李虎劫恒。1989年12月28日出生于四川,中國內(nèi)地網(wǎng)絡(luò)小說作家、85后著名作家两嘴、浙江省網(wǎng)絡(luò)作家協(xié)會副主席..."
    },
    {
        "id": 2,
        "article_set": [
            2,
            3,
            4
        ],
        "username": "更俗",
        "detail": "更俗丛楚,原為起點(diǎn)中文網(wǎng)白金作家,2010年轉(zhuǎn)投縱橫中文網(wǎng)成為其簽約作家憔辫。"
    }
]
  • queryset趣些,用于創(chuàng)建或者更新的字段約束,例如ArticleSerializer中贰您,除了常規(guī)的字段約束驗(yàn)證坏平,user必須在queryset中。
    例如以下數(shù)據(jù)就不通過:
{
        "user": 3,
        "title": "楚臣2"
}

querysetread_only = True必須二選一锦亦。


7.2 StringRelatedField使用

class User(models.Model):
    username = models.CharField(max_length=16)
    detail = models.CharField(max_length=256)

    def __str__(self):
        return self.username

class Article(models.Model):
    title = models.CharField(max_length=64)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

class ArticleSerializer(ModelSerializer):
    user = StringRelatedField(read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

序列化的結(jié)果:

[
    {
        "id": 1,
        "user": "更俗",
        "title": "楚臣"
    },
    {
        "id": 2,
        "user": "更俗",
        "title": "官場之風(fēng)流人生"
    },
    {
        "id": 3,
        "user": "更俗",
        "title": "梟臣"
    },
    {
        "id": 4,
        "user": "天蠶土豆",
        "title": "斗破蒼穹"
    },
    {
        "id": 5,
        "user": "天蠶土豆",
        "title": "武動乾坤"
    }
]

7.3 HyperlinkedRelatedField使用

# urls.py

from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/user/<int:pk>/', views.UserView.as_view(), name='user'),
    path('api/articles/', views.ArticlesView.as_view()),

]
class ArticleSerializer(ModelSerializer):
    user = HyperlinkedRelatedField(read_only=True, view_name='user')

    class Meta:
        model = Article
        fields = '__all__'


class ArticlesView(APIView):
    def get(self, request, *args, **kwargs):
        quertset = Article.objects.all()
        # 使用`HyperlinkedRelatedField`必須傳入環(huán)境參數(shù)`context={'request': request}`
        articles_serializer = ArticleSerializer(instance=quertset, many=True, context={'request': request})
        return Response(articles_serializer.data)

返回的結(jié)果如下

[
    {
        "id": 1,
        "user": "http://127.0.0.1:8000/api/user/2/",
        "title": "楚臣"
    },
    {
        "id": 2,
        "user": "http://127.0.0.1:8000/api/user/2/",
        "title": "官場之風(fēng)流人生"
    },
    {
        "id": 3,
        "user": "http://127.0.0.1:8000/api/user/2/",
        "title": "梟臣"
    },
    {
        "id": 4,
        "user": "http://127.0.0.1:8000/api/user/1/",
        "title": "斗破蒼穹"
    },
    {
        "id": 5,
        "user": "http://127.0.0.1:8000/api/user/1/",
        "title": "武動乾坤"
    }
]

使用 HyperlinkedRelatedField建議設(shè)置read_only = True
關(guān)鍵字參數(shù):

  • view_name : 視圖函數(shù)名稱
  • lookup_field : 生成url舶替,從關(guān)系對象中的取數(shù)據(jù)的字段,默認(rèn)值為pk
  • lookup_url_kwarg: 將取出的數(shù)據(jù)匹配url路徑的字段,默認(rèn)值與lookup_field相同杠园。

1??.當(dāng) url中的路徑字段不為pk時顾瞪,需要指定lookup_field

……
path('api/user/<str:username>/', views.UserView.as_view(), name='user'),

上面反向生成url需要填補(bǔ)username,而lookup_field 默認(rèn)為pk ,這個系統(tǒng)會從

7.4 SlugRelatedField使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末返劲,一起剝皮案震驚了整個濱河市玲昧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篮绿,老刑警劉巖孵延,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亲配,居然都是意外死亡尘应,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門吼虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬钢,“玉大人,你說我怎么就攤上這事思灰$栌蹋” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵洒疚,是天一觀的道長歹颓。 經(jīng)常有香客問我,道長油湖,這世上最難降的妖魔是什么巍扛? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮乏德,結(jié)果婚禮上撤奸,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好胧瓜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布矢棚。 她就那樣靜靜地躺著,像睡著了一般贷痪。 火紅的嫁衣襯著肌膚如雪幻妓。 梳的紋絲不亂的頭發(fā)上蹦误,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天劫拢,我揣著相機(jī)與錄音丸升,去河邊找鬼冒冬。 笑死,一個胖子當(dāng)著我的面吹牛攻锰,可吹牛的內(nèi)容都是我干的偶洋。 我是一名探鬼主播熟吏,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玄窝!你這毒婦竟也來了牵寺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恩脂,失蹤者是張志新(化名)和其女友劉穎帽氓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俩块,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黎休,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了玉凯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片势腮。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖漫仆,靈堂內(nèi)的尸體忽然破棺而出捎拯,到底是詐尸還是另有隱情,我是刑警寧澤盲厌,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布署照,位于F島的核電站,受9級特大地震影響藤树,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屡限,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一眶诈、第九天 我趴在偏房一處隱蔽的房頂上張望宪潮。 院中可真熱鬧尽棕,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翻伺。三九已至簿废,卻和暖如春埋凯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铸屉。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工钉蒲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顷啼,地道東北人偏陪。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓饥脑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刷钢。 傳聞我的和親對象是個殘疾皇子笋颤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

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