rest_framework 序列化器

REST框架中的序列化器的工作方式与Django FormModelForm类非常相似。
REST提供了一个Serializer类,可以将对象中的数据序列化成json,同时还可以将json数据反序列化成对象

第零篇、rest_framework的配置

# 1.创建一个 `django`项目,并生成一个`app`,命名为`my_app`
# 在 ALLOWED_HOSTS 允许所有
ALLOWED_HOSTS = ['*']

# 2. 安装 djangorestframework
# 我这里安装的 `restframework` 是 `3.11`版本,后续的用法以及源码都基于此版本
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.数据初始化
python manage.py migrate

第一篇、rest_framework序列化反序列化流程与用法

这里以简单的数据模型来讲解,只是为了弄明白 rest_framework的源码流程,所以不涉及复杂关系,在第二篇中单独讲解 关系模型

1. REST序列化

注意:在 rest_framework中序列化器不仅适用于djangomodel数据而是对所有的对象都适用,在这里我们只以djangomodel作为数据对象。

使用 rest_framework的序列化的流程

  • 1.创建指定模型的序列化器(继承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是复数,则需要 many=True
    books = Book.objects.all()
    books_serializer = BookSerializer(instance=books, many=True) 
    
  • 3.从序列化器对象中取出序列化的数据

    data = book_serializer.data
    

example1. 简单的数据序列化使用

# models.py
from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='书名')
    size = models.IntegerField(verbose_name='字数')

# 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//', views.BookView.as_view()),
]

使用postman,访问http://127.0.0.1:8000/api/book/1/get方法
返回的结果如下:

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

example 2. 使用source更改字段名称
source指定实际的数据获取来源(字段或者函数)

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

指明title的数据来源name字段
返回结果如下

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

example 3. 使用source获取函数返回值
我们有时不仅需要 model字段数据,也需要函数数据,借助source可以将函数进行序列化

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='书名')
    size = models.IntegerField(verbose_name='字数')

    category_choices = (
        (1, '仙侠修真'),
        (2, '历史'),
        (3, '都市言情'),
    )
    # `django` 会自动生成`get_category_display()函数`
    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')

序列化后的返回内容:

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

2. REST反序列化简单使用

反序列化就是将json数据转成数据对象
反序列化使用流程:

  • 1.创建序列化器
    • 指定序列器的字段以及字段约束
    • 重写update()create()方法
    1. 生成序列化器对象
    • 传入 data参数,instance参数可选,如果不传入,则生成新的对象,如果传入 instance参数,则在传入对象基进行更新
    book_serializer = BookSerializer(instance=book, data=data)
    
  • 3.通过is_valid()对传入的数据进行验证

  • 4.执行save()方法将数据反序列化成对象


example 1. 数据反序列化(创建对象)

# models.py
from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='书名')
    size = models.IntegerField(verbose_name='字数')

    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,否则无法进行`create`与`update`操作
    id = serializers.IntegerField(read_only=True) 
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()

    def create(self, validated_data):
        """
        返回一个 instance 实例对象
        """
        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()  # 更新到数据库中
        return book


class BookView(APIView):
    
    def post(self, request, *args, **kwargs):
        data = request.data  # 从 request 获取POST传过来的值

        # 没有传入 instance 参数,在 save() 中执行 create(),
        book_serializer = BookSerializer(data=data)
        if book_serializer.is_valid(): # 验证数据是否合法
            # 将 json 数据转化成 对象,实际调用我们重写的 `create()`方法
            instance = book_serializer.save() 
            return Response(book_serializer.data)
        return Response(book_serializer.errors)

使用postman发送post请求,如下数据:

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

返回结果如下:

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

可以看出,我们创建了新的 book对象,在数据库中的 id4


example 2. 数据反序列化(更新对象)

前端发送json数据,在数据库中进行更新。
全部更新对应 put请求,部分更新对应patch请求,这里我们以put 请求为例。

对对象进行更新,需要实现序列化器的update()方法,并且在创建序列化器时,传入instance实例对象。

# 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,否则无法进行`create`与`update`操作
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField()
    size = serializers.IntegerField()
    category = serializers.IntegerField()

    def create(self, validated_data):
        """
        返回一个 instance 实例对象
        """
        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()  # 更新到数据库中
        return book

    def update(self, instance, validated_data):
        """
        对 instance 实例对象进行数据更新
        """
        for name, value in validated_data.items():
            setattr(instance, name, value)
        instance.save() # 将数据更新到数据库中
        return instance

class BookView(APIView):

    def post(self, request, *args, **kwargs):
        """用于创建对象"""
        data = request.data  # 从 request 获取POST传过来的值
        # 没有传入 instance 参数,在 save() 中执行 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()  # 更新后的实例对象 instance
            return Response(book_serializer.data)  # 将更新后的实例进行序列化返回的 json 数据
        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//', views.BookView.as_view()),
]

我们对 id4的book进行更新,使用postmanhttp://127.0.0.1:8000/api/book/4/,发送put请求,内容如下:

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

由于我们在 BookSerializer中定义的id字段具有read_only=True约束,所有我们在发送的json数据中可以不填id信息,但是其它的信息必须填写。

返回内容:

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

总结:对反序列化而言,我们要做的就是实现序列化器的update()create()方法,然后调用save(),save()内部会帮我们分发到update()或者create()

save()源码流程:
查看源码的技巧:对断言assert部分不用太关注,抓住主要的逻辑

    def save(self, **kwargs):
        # 1.传入自己补充的信息,将补充的信息放入 `validated_data` 中
        validated_data = dict(
            list(self.validated_data.items()) +
            list(kwargs.items())
        )
        
        # 如果没有传入 `instance`,则调用 `update`方法
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)

        # 否则调用 `create` 方法
        else:
            self.instance = self.create(validated_data)
          
        return self.instance

3. REST反序列化,字段约束

反序列化数据时,使用is_valid()对传入的data数据进行验证。
字段约束类似于 django的约束条件
如果发生任何验证错误,则.errors属性将包含代表所得错误消息


example 1. 字段缺少展示
如果上述的update传入时,size信息并没有改动,我们不传入size

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

则返回信息:

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

这是因为我们定义的序列化器,如果没有指定requiredFalse,则必须传入值,或者指定默认值。
改成如下,则不再报错,可以在更新或者生成时,不传入size属性

class BookSerializer(serializers.Serializer):
    # 必须添加 read_only=True,否则无法进行`create`与`update`操作
    id = serializers.IntegerField(read_only=True) 
    name = serializers.CharField()
    size = serializers.IntegerField(required=False)
    category = serializers.IntegerField()

3.1 所有的字段共有的约束条件以及默认值:read_only=False,write_only=False,required=None,allow_null=False,validators=None
  • read_only表示是否只读属性,一般用于 id,表示只能看,不能改,就算我们传入了id信息,如:
    {   
      "id":5,
      "name":"凡人修仙传之都市篇",
      "category":3
    }
    
    返回的结果中,id依然不会改变,这是因为在createupdate中,read_only=True的信息根本不会被封装到validated_data

如果我们对某字段使用了read_only=True,还想要在 createupdate中对数据进行修改,可以在 save()方法中,将信息传入

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

备注:这里只是为了演示save()从外部获取数据,但是不建议更改id字段

  • write_only表示只写属性,一般用于一些保密的信息,例如password,只能用于创建或更新,但是返回时,不对该字段信息进行序列化。

例如我们对上述的name字段添加write_only约束,则更新后返回的信息中不再具有name

{
    "id": 4,
    "size": 4256732,
    "category": 3
}
  • required表示字段是否必填,如果required = False,则可以不传入该字段信息。
  • allow_null 表示传入的数据是否可以为空。
  • validators(验证器列表),可以传入多个函数对象(callback),通过函数对字段进行自定义约束。

example 2. 使用字段的validators,实现自定义约束

实现自定义约束函数,返回结果只能有两种

  • 返回一个验证后的数据 ,表示验证通过
  • 抛出 ValidationError 异常,表示验证不通过
from rest_framework.exceptions import ValidationError

def sensitive_word_filter(value):
    """
    敏感字过滤
    返回验证通过的数据,不通过则抛出 `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)

    # 注意这里传入的函数对象,而不是 sensitive_word_filter()
    name = serializers.CharField(validators=[sensitive_word_filter, ])
    size = serializers.IntegerField(required=False)
    category = serializers.IntegerField()

3.2 特殊字段的独有约束条件

django一样,除了共有的约束信息,rest_framework中不同的字段,有不同的约束条件

  • CharFieldmin_length,max_length,trim_whitespace,allow_blank
  • EmailField: 自动验证是否符合邮箱格式
  • IntegerField, FloatField : max_value, min_value
  • DateTimeField : format序列化时的输出格式 , input_formats 反序列化的输入字符串格式列表,可以使用多个格式;default_timezone,设置时区

example 3. 使用DateTimeField的独有约束,实现自定义时间的输入,输出格式

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='书名')
    size = models.IntegerField(verbose_name='字数')

    category_choices = (
        (1, '仙侠修真'),
        (2, '历史'),
        (3, '都市言情'),
    )
    category = models.IntegerField(choices=category_choices, default=1)
    # 这里 created 是后续加上去的,必须要有 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)

    # 注意这里传入的函数对象,而不是 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 实例对象
        """
        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()  # 更新到数据库中
        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()`执行更新操作
        book_serializer = BookSerializer(instance=book, data=data)
        if book_serializer.is_valid():
            instance = book_serializer.save()  # 更新后的实例对象 instance
            return Response(book_serializer.data)  # 将更新后的实例进行序列化返回的 json 数据
        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//', views.BookView.as_view()),
]

http://127.0.0.1:8000/api/book/3/ 发送put请求,请求内容:

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

返回内容:

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

如果请求输入的datetime 格式可能有多种,我们也可以在input_formats设置多个


3.3 自定义验证函数

3.1 中使用在字段上validators可以实现自定义函数验证,除了这一种方法外,我们还可以使用另一种验证方式。
创建.validate_方法可以指定自定义字段级验证

如下,为username绑定自定义函数约束/验证
example 4. .validate_函数级验证

# 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//', views.BookView.as_view()),
    path('api/user//', views.UserView.as_view()),
]

发送post请求,创建时,username必须以中国公民_开头'


validate_函数约束与validators约束的区别:
最主要的是validate_只能给某一个字段使用,而validators适用于多个字段。
我们一般把通用型的字段验证放在validators中,以便供更多的字段适用。


3.4 对象级验证

3.1 - 3.3 都是对一个字段进行验证,但在特殊的情况下,我们可能涉及到多个字段之间的关系验证,这就需要我们直接对 data 进行验证。
重写 Serializervalidate()方法,可以直接对data进行处理。

  • 抛出异常ValidationError说明验证失败
  • 返回处理后的dict格式的数据,说明验证成功
class MySerializer(serializers.Serializer):

    def validate(self, data):
        if data['start'] > data['end']:
            raise ValidationError('开始时间必须小于结束时间')
        return data

4. REST多个对象的序列化反序列化

前面的讲解中,我们都是使用的单个对象的获取,但是在实际工作中,我们更多的是一次获取多个对象,并进行分页。

example 1.通过 get 获取多个对象数据 以及通过post创建多个对象

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name='书名')
    size = models.IntegerField(verbose_name='字数')

    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 实例对象
        """
        book = Book(validated_data)
        book.save()  # 更新到数据库中
        return book

    def validate_category(self, value):
        """对类别 category 进行约束验证"""
        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()),
]

发送 get请求,获取全部的books数据

[
    {
        "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
    }
]

发送 post请求进行创建,如果数据中有一个对象的数据验证失败,则本次创建失效

many = True时,我们创建的序列化器的实例对象并不是我们当前的BookSerializer对象,而是 ListSerializer 对象,但是我们调用 ListSerializer 对象的 is_valid() 以及 save()方法时候,好像都是BookSerializer在起作用,这是因为 ListSerializer 内部将传入的集合对象进行迭代遍历,然后再交给
BookSerializer处理
如下是ListSerializer源码

# ListSerializer 类
    def create(self, validated_data):
        # validated_data 是一个集合对象
        return [
            # 这里的 `child` 就是`BookSerializer`实例对象
            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

这样我们在执行 save()方法时,会调用MyListSerializercreate()方法

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

5.1 序列化源码流程


查看源码的一些技巧

  • 1.找到主要的入口点,一步步理出脉络,而不是从import ……·一行行看起
  • 2.对于有继承关系的类,每次执行方法都从顶层类找起
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()

打印结果如下:

A.step_1
B.step_2
C.step_3

这里虽然第一步执行了A.step_1,在A.step_1中执行self.step_2()并不会直接执行A.step_2,而是按照继承关系,先从c对象查找step_2()方法,没有的话就继续往上查找,找到Bstep_2,所以先执行B.step_2,每一步都是如此,从顶层查找,底层的方法只会被覆盖。

  • 3.抓住主要的逻辑 对于assert这些验证,或者抛出异常的部分,可以不用太关注。
    例如完整的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验证

  • 4.见名猜意,我们根据方法的名称先假设作用,把整个走一下,如果能走的通,说明我们的假设应该问题不大,然后再去验证我们的假设对不对。

备注:这里的源码分析,我们也是将方法中的主要逻辑代码片段贴出来,没有全部展示。


当我们执行序列化时。通过book_serializer.data来获取数据

  • 第一步: 执行 SerializerdataSerializerdata调用父类BaseSerializerdata.
    BaseSerializerdata中,主要执行以下片段
# 1.具有`instance`实例对象,则将实例对象序列化
if self.instance is not None and not getattr(self, '_errors', None):
     self._data = self.to_representation(self.instance)

# `_validated_data` 是我们传入的`data`参数经`is_valid()`验证后的数据
# 2.没有`instance`实例,但是传入了`data`数据,并验证通过,将`data`数据序列化
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
     self._data = self.to_representation(self.validated_data)
  • 第二步:执行to_representation()函数
    这里 Serializer具有to_representation()函数,所以直接调用
    注意:这里的参数instance既可以是数据对象,也可以是validated_data字典数据
    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        # 创建有序的 dict
        # 这样返回的`json`数据顺序就会和我们创建的`Serializer`中声明的一致
        ret = OrderedDict() 

        # 获取可读的字段,如果字段约束条件中`write_only = True`,则在这里无法获取,也就不会进行序列化
        fields = self._readable_fields 
        
        # fields 就是我们声明的字段 例如:name = serializers.CharField()
        for field in fields:
            try:
                # 如果`instance`中是数据对象
                # 从我们提供的属性名称/方法名称,如:`name`,`get_choice_display`
                # 通过反射 获取属性值

                # 如果`instance`是 字典数据,也就是`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:
                # 执行各个字段的 `to_representation()`方法
                # 主要是将数据转化为各个字段的格式
                # 例如 `IntegetField` 中,执行 int(value)
                # 在 `CharField`中,执行 str(value)
                # 在 `DateTimeField`中,则根据我们传入的`format`参数,对数据进行字符串格式化
                ret[field.field_name] = field.to_representation(attribute)

        return ret

从上面看,rest_framework的数据序列化主要都是在to_representation中完成,如果我们要自定义数据序列化的格式,则可以重写to_representation

from collections import OrderedDict
from rest_framework import serializers


class UpperKeySerializer(serializers.Serializer):
    ……

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

        return ret

这里有一个注意点,我们执行post操作时,创建序列化器对象时,并没有传入instance,而是传入了data = data,并执行is_valid()验证通过,但是序列化出来的结果中包含id属性,这明显是直接将instance序列化了。

这是因为我们执行save()操作,在save()中将create()创建的instance赋值给了self.所以self.instance为真,to_representation也就直接作用于该instance,而不是我们传入的data = data.


5.2反序列化源码流程

执行反序列化主要有两步:

  • book_serailizer.is_valid()
  • book_serailizer.save()
    其中 save()方法已经讲解过,就是调用create或者update方法。

反序列化的源码主要在数据约束 / 验证上比较复杂,这里主要讨论这一部分。

  • 第一步,执行Serializer类的is_valid()

    
    def is_valid(self, raise_exception=False):
    
        # 是否有`_validated_data` 是判断是否执行过`is_valid()`的依据
        # 如果存在`_validated_data` ,说明已经验证过,直接返回;防止多次调用`is_valid`
        if not hasattr(self, '_validated_data'):
            try:
                #  `is_valid`主要就是执行这一行
                # `initial_data` 就是我们传入的`data = data`参数,在` __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()执行成功,则会产生_validated_data数据,在createupdate中传入的 validated_data实际就是这里的_validated_data

  • 第二步,执行 Serializer类的run_validation()

    def run_validation(self, data=empty):
        # 1.验证传入的`data`是否为空
        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data

        # 2.将数据转化为`内部值`
        # 这里进行了第一种验证 
        # 这里的 `data` 是我们传入的字典,那么返回出来的 `value`应该也是字典
        value = self.to_internal_value(data)
        try:
            # 这里进行了第二种验证,添加验证器
            self.run_validators(value)

            # 这里进行了第三种验证,也就是我们前面`3.4`章节讲的对象级验证,钩子函数就埋在这
            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

上面的第三种验证,在我们的3.4已经讲过,这里不再讲解,第二种验证是自定义验证器类,比较复杂,一般用不到,这里下一步着重讲解to_internal_value

  • 第三步, Serailizer类实现了to_internal_value(),我们直接查看
    def to_internal_value(self, data):
        """
        这里主要执行字段级别的验证
        对`data`字典的验证在`run_validators(value)`与 `validate(value)`完成
        """
        # 创建一个有序字典
        ret = OrderedDict()
        errors = OrderedDict()

        # 获取可写 / 可反序列化的字段对象
        # 例如我们将`id`设置为`read_only = True`,则这里不会获取`id`字段
        fields = self._writable_fields
        
        for field in fields:
            # 第一种字段约束
            # 这里执行自定义函数验证
            # 我们`3.3`章节`validate_`的钩子埋在这
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                # 第二种字段约束
                # 这里执行字段的基础验证
                # 对应我们的`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的序列化器,所有的基础功能都是基于Serializer来实现的,除此之外rest_framework还提供了与Django模型定义紧密映射的序列化程序类ModelSerializer

ModelSerializer继承了Serializer,它主要有以下三种新的功能:

  • 它将根据模型自动为您生成一组字段,这也是它最主要的作用。
  • 它将自动为序列化器生成验证器,例如unique_together验证器。
  • 它包括简单的默认实现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进入shell界面
[ shell中自动导入了django环境,可以直接使用django中的model对象,否则会报ImproperlyConfigured错误 ]
shell中执行以下

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

打印结果如下:

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自动为我们创建了字段,并添加了字段约束,像read_only=True会自定为 django的主键添加,而max_length也是从UserInfo中获取的。这样可以为我们节省很多时间。

生成的UserSerializer实际与我们自己前面所写的Serializer功能是一模一样


example 2 自动生成部分字段

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

example 3 使用exclude属性设置为要从序列化器中排除的字段列表。

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

注意:从3.3.0版开始,必须提供属性fieldsexclude之一。


6.2 使用extra_kwargs设置关键字参数

使用 ModelSerializer可以方便的帮我们生成字段,并且根据model自动添加了约束,如:id字段的read_only = TrueCharFieldmax_length等。

但是有的时候我们还是需要自己设置关键字参数,例如给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为指定的字段添加额外的关键字参数

class UserSerializer(ModelSerializer):

    class Meta:
        model = UserInfo
        fields = '__all__'
        extra_kwargs = {
            # `ModelSerializer` 生成的'password'已经有`max_length = 32`,这里不需要再设置。
            'password': {'write_only': True}, 
            'money': {'min_value': 0.0}
        }

6.3 ModelSerializer设置关系深度depth

当我们的model模型中涉及与其它表的关系时,使用ModelSerializer生成的关系一般使用主键表示

# 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 界面执行以下:

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

打印内容如下

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

序列化后的数据如下:

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

这里的user以主键代替User
当设置depth = 1

class ArticleSerializer(ModelSerializer):
    class Meta:
        model = Article
        depth = 1 # depth 是整数,表示向下关系深度,一般不建议在3层以上
[
    {
        "id": 1,
        "title": "枭臣",
        "user": {
            "id": 2,
            "username": "更俗",
            "detail": "更俗,原为起点中文网白金作家,2010年转投纵横中文网成为其签约作家。"
        }
    },
    {
        "id": 2,
        "title": "斗破苍穹",
        "user": {
            "id": 1,
            "username": "天蚕土豆",
            "detail": "天蚕土豆,本名李虎。1989年12月28日出生于四川,中国内地网络小说作家、85后著名作家、浙江省网络作家协会副主席..."
        }
    }
]

注意:使用depth后,关系嵌套为只读。也就是这里的user为只读属性。

第二篇、rest_framework序列化器关系

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=64)
    create = models.DateTimeField('发布时间')
    visitors = models.IntegerField('阅读人数')
    user = models.ForeignKey(User, on_delete=models.CASCADE)

在上面的模型中,对于简单的字段,我们已经知道如何序列化,但是对user知道关系字段,我们用专门的序列化字段可选。

  • PrimaryKeyRelatedField , 可以通过关系的主键来表示关系对象。推荐使用
  • StringRelatedField,使用__str__方法值来表示关系对象。
  • HyperlinkedRelatedField,通过超链接来表示关系对象。
  • SlugRelatedField,使用目标上的字段来表示关系对象。

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__'

序列化后的结果为:

[
    {
        "id": 1,
        "user": 2,
        "title": "楚臣"
    },
    {
        "id": 2,
        "user": 2,
        "title": "官场之风流人生"
    },
    {
        "id": 3,
        "user": 2,
        "title": "枭臣"
    },
    {
        "id": 4,
        "user": 1,
        "title": "斗破苍穹"
    },
    {
        "id": 5,
        "user": 1,
        "title": "武动乾坤"
    }
]

这里我们使用了PrimaryKeyRelatedField,所以就是使用user的主键来表示user对象。
注意:PrimaryKeyRelatedField关系字段的关键字参数。

  • 共有的基础设置, 3.1read_only=False, write_only=False, required=None, allow_null=False, validators=None
    这里比较常用的就是read_only = True,将关系设置为只读属性。
  • many = False,如果我们的关系对象为多个,则需要设置many = True,这样序列化的字段会表示为列表
class UserSerializer(ModelSerializer):
    # `article_set` 是`User` 的反向关系字段
    # 在 `Django`中,多对一的反向字段都是以 `_set`
    # 如果我们不明确指定,`ModelSerializer`不会自动创建
    # 我们在创建 / 更新`User`对象时,不需要`article`参数,所以`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日出生于四川,中国内地网络小说作家、85后著名作家、浙江省网络作家协会副主席..."
    },
    {
        "id": 2,
        "article_set": [
            2,
            3,
            4
        ],
        "username": "更俗",
        "detail": "更俗,原为起点中文网白金作家,2010年转投纵横中文网成为其签约作家。"
    }
]
  • queryset,用于创建或者更新的字段约束,例如ArticleSerializer中,除了常规的字段约束验证,user必须在queryset中。
    例如以下数据就不通过:
{
        "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__'

序列化的结果:

[
    {
        "id": 1,
        "user": "更俗",
        "title": "楚臣"
    },
    {
        "id": 2,
        "user": "更俗",
        "title": "官场之风流人生"
    },
    {
        "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//', 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`必须传入环境参数`context={'request': request}`
        articles_serializer = ArticleSerializer(instance=quertset, many=True, context={'request': request})
        return Response(articles_serializer.data)

返回的结果如下

[
    {
        "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": "官场之风流人生"
    },
    {
        "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建议设置read_only = True
关键字参数:

  • view_name : 视图函数名称
  • lookup_field : 生成url,从关系对象中的取数据的字段,默认值为pk
  • lookup_url_kwarg: 将取出的数据匹配url路径的字段,默认值与lookup_field相同。

1⃣️.当 url中的路径字段不为pk时,需要指定lookup_field

……
path('api/user//', views.UserView.as_view(), name='user'),

上面反向生成url需要填补username,而lookup_field 默认为pk ,这个系统会从

7.4 SlugRelatedField使用

你可能感兴趣的:(rest_framework 序列化器)