美文网首页
rest_framework 序列化器

rest_framework 序列化器

作者: eeert2 | 来源:发表于2020-02-21 19:18 被阅读0次

    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/<int:pk>/', 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/<int:pk>/', 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/<int:pk>/', 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_<field_name>方法可以指定自定义字段级验证

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

    # 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()),
    ]
    

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


    validate_<field_name>函数约束与validators约束的区别:
    最主要的是validate_<field_name>只能给某一个字段使用,而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_<field_name>`的钩子埋在这
                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`中,多对一的反向字段都是以 `<fieldname>_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/<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`必须传入环境参数`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/<str:username>/', views.UserView.as_view(), name='user'),
    

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

    7.4 SlugRelatedField使用

    相关文章

      网友评论

          本文标题:rest_framework 序列化器

          本文链接:https://www.haomeiwen.com/subject/xcbtqhtx.html