DRF - 序列化组件

序列化组件
引入:

视图的功能:说白了就是接收前端请求,进行数据处理(这里的处理包括:如果前端是GET请求,则构造查询集,将结果返回,这个过程为序列化;如果前端是POST请求,假如要对数据库进行改动,则需要拿到前端发来的数据,进行校验,将数据写入数据库,这个过程称为反序列化)

最原始的视图可以实现这样的逻辑处理,但是针对不同的请求,需要在类视图中定义多个方法实现各自的处理,这样是可以解决问题,但是存在一个缺陷,那就是每个函数中一般的逻辑都差不多:读请求,从数据库拿数据,写东西到数据库,返回结果给前端。这样就会产生大量的重复代码。

在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:

增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回

删:判断要删除的数据是否存在 -> 执行数据库删除

改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回

查:查询数据库 -> 将数据序列化并返回

序列化组件的使用

定义model:

from django.db import models

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")

    def __str__(self):
        return self.title

通过序列化组件进行GET接口设计

首先,设计url,本次我们只设计GET和POST两种接口:

from django.urls import re_path

from serializers import views

urlpatterns = [
    re_path(r'books/$', views.BookView.as_view())
]

我们新建一个名为app_serializers.py的模块,将所有的序列化的使用集中在这个模块里面,对程序进行解耦:

from rest_framework import serializers

from .models import Book

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=128)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    authors = serializers.CharField(max_length=32)

接着,使用序列化组件,开始写视图类:

from rest_framework.views import APIView
from rest_framework.response import Response

# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer

# Create your views here.

class BookView(APIView):
    def get(self, request):
        # 获取查询集 queryset
        origin_books = Book.objects.all()
        # 进行校验
        # 当为queryset时,many=True.当是model类型时,many=False

        serialized_books = BookSerializer(origin_books, many=True)
	   # 通过序列化对象.data将数据读出来
        return Response(serialized_books.data)

如此简单,我们就已经,通过序列化组件定义了一个符合标准的接口,定义好model和url后,使用序列化组件的步骤如下:

  • 导入序列化组件:from rest_framework import serializers
  • 定义序列化类,继承serializers.Serializer(建议单独创建一个专用的模块用来存放所有的序列化类)
  • 定义需要返回的字段(字段类型可以与model中的类型不一致,参数也可以调整),字段名称必须与model中的一致
  • 在GET接口逻辑中,获取QuerySet
  • 开始序列化:将QuerySet作业第一个参数传给序列化类,many默认为False,如果返回的数据是一个列表嵌套字典的多个对象集合,需要改为many=True
  • 返回:将序列化对象的data属性返回即可

上面的接口逻辑中,我们使用了Response对象,它是DRF重新封装的响应对象。该对象在返回响应数据时会判断客户端类型(浏览器或POSTMAN),如果是浏览器,它会以web页面的形式返回,如果是POSTMAN这类工具,就直接返回Json类型的数据。

此外,序列化类中的字段名也可以与model中的不一致,但是需要使用source参数来告诉组件原始的字段名,如下:

class BookSerializer(serializers.Serializer):
    BookTitle = serializers.CharField(max_length=32)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    # source也可以用于ForeignKey字段
    # source:指定显示的字段
    publish = serializers.CharField(max_length=32, source="publish.name")
    authors = serializers.CharField(max_length=32)
    # 在一对多:在序列化书籍信息时,同时序列化出书籍关联的任务信息
    # StringRelatedField : 也是适用的,显示汉字字段而不是指定的PrimaryKey,这样更直观
    # many=True :指定heroinfo_set是多的那一方
    # read_only:该字段只能进行序列化,反序列化时直接忽略该字段

下面是通过POSTMAN请求该接口后的返回数据,大家可以看到,除ManyToManyField字段不是我们想要的外,其他的都没有任何问题:

[
    {
        "title": "Python入门",
        "price": "119.00",
        "publish": "浙江大学出版社",
        "authors": "serializers.Author.None"
    },
    {
        "title": "Python进阶",
        "price": "128.00",
        "publish": "清华大学出版社",
        "authors": "serializers.Author.None"
    }
]

那么,多对多字段如何处理呢?如果将source参数定义为”authors.all”,那么取出来的结果将是一个QuerySet,对于前端来说,这样的数据并不是特别友好,我们可以使用如下方式:

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=32)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField()
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_email = serializers.CharField(max_length=32, read_only=True, source='publish.email')
    # authors = serializers.CharField(max_length=32, source='authors.all')
    # 多对多字段需要自己手动获取数据通过调用下面的方法
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, authors_obj):
        authors = list()
        for author in authors_obj.authors.all():
            authors.append(author.name)

        return authors

请注意,get_必须与字段名称一致,否则会报错。

通过序列化组件进行POST接口设计

接下来,我们设计POST接口,根据接口规范,我们不需要新增url,只需要在视图类中定义一个POST方法即可,序列化类不需要修改,如下:

from rest_framework.views import APIView
from rest_framework.response import Response

# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer

class BookView(APIView):
    
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

    def post(self, request):
        # 将查询集以及前端传过来的数据绑定给序列化器
        verified_data = BookSerializer(data=request.data)

        if verified_data.is_valid():
            #这个的地方的save()会调用create()方法,但源码里面没有,因此,我们需要自己创建
            book = verified_data.save()
            # 可写字段通过序列化添加成功之后需要手动添加只读字段
            authors = Author.objects.filter(nid__in=request.data['authors'])
            # Book表对应的多对多表,在这里添加,先查询author表的用户,在添加到book_author的对应关系中
            book.authors.add(*authors)

            return Response(verified_data.data)
        else:
            return Response(verified_data.errors)
    def delete(self, request, nid):
        book_obj = Book.objects.get(pk=nid).delete()

        return Response()

POST接口的实现方式,如下:

  • url定义:需要为post新增url,因为根据规范,url定位资源,http请求方式定义用户行为
  • 定义post方法:在视图类中定义post方法
  • 开始序列化:通过我们上面定义的序列化类,创建一个序列化对象,传入参数data=request.data(application/json)数据
  • 校验数据:通过实例对象的is_valid()方法,对请求数据的合法性进行校验
  • 保存数据:调用save()方法,将数据插入数据库(复习:数据库增加数据见末尾)
  • 插入数据到多对多关系表:如果有多对多字段,手动插入数据到多对多关系表
  • 返回:将插入的对象返回

请注意,因为多对多关系字段是我们自定义的,而且必须这样定义,返回的数据才有意义,而用户插入数据的时候,serializers.Serializer没有实现create,我们必须手动插入数据,就像这样:

# 第二步, 创建一个序列化类,字段类型不一定要跟models的字段一致
class BookSerializer(serializers.Serializer):
    # nid = serializers.CharField(max_length=32)
    title = serializers.CharField(max_length=32)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField()
    # 外键字段, 显示__str__方法的返回值
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
    # authors = serializers.CharField(max_length=32) 
    # book_obj.authors.all()

    # 多对多字段需要自己手动获取数据,SerializerMethodField()
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, book_obj):
        author_list = list()

        for author in book_obj.authors.all():
            author_list.append(author.name)

        return author_list
	# validated_data是经过校验之后的数据,已经是标准的字典
    def create(self, validated_data):
        # validated_data: {'title': 'Python666', 'price': Decimal('66.00'), 'publish': '2'}
         # 因为Book表里没有publish字段,因此,我们需要删除publish
        validated_data['publish_id'] = validated_data.pop('publish')
        # 对字典进行拆包 把数据增加到Book表里
        book = Book.objects.create(**validated_data)

        return book

    def update(self, instance, validated_data):
        # 更新数据会调用该方法
        # 取不到就取默认值
        instance.title = validated_data.get('title', instance.title)
        instance.price = validated_data.get('price', instance.price)
        instance.publish_id = validated_data.get('publish', instance.publish.nid)

        instance.save()

        return instance

这样就会非常复杂化程序,如果我希望序列化类自动插入数据呢?

​ 问题一:如何让序列化类自动插入数据?

​ 问题二:如果字段很多,那么显然,写序列化类也会变成一种负担,有没有更加简单的方式呢?

ModelSerializer

ModelSerializer与常规的Serializer相同,但提供了:

  • 基于模型类自动生成一系列字段
  • 基于模型类自动为Serializer生成validators,比如unique_together
  • 包含默认的create()和update()的实现
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ('title',
                  'price',
                  'publish',
                  'authors',
                  'author_list',
                  'publish_name',
                  'publish_city'
                  )
       # model 指明参照哪个模型类
       # fields 指明为模型类的哪些字段生成

        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True}
        }
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book_obj):
        # 拿到queryset开始循环 [{}, {}, {}, {}]
        authors = list()

        for author in book_obj.authors.all():
            authors.append(author.name)

        return authors

步骤如下:

  1. 定义:继承ModelSerializer

    1. model 指明参照哪个模型类
    2. fields 指明为模型类的哪些字段生成

    我们可以在python manage.py shell中查看自动生成的BookInfoSerializer的具体实现

  2. 指定字段

    1. 使用fields来明确字段,__all__表名包含所有字段,也可以写明具体哪些字段,如上。
    2. 使用exclude可以明确排除掉哪些字段,不能跟fields同时用
    3. 默认ModelSerializer使用主键作为关联字段,但是我们可以使用depth来简单的生成嵌套表示,depth应该是整数,表明嵌套的层级数量。深度控制,写几往里拿几层,层数越多,响应越慢,官方建议0--10之间,个人建议最多3层
    4. 指明只读字段:通过read_only_fields指明只读字段,即仅用于序列化输出的字段。
  3. 添加额外参数:我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数

使用serializer进行put接口设计

根据规范,PUT接口用来定义用户对数据修改的逻辑,也就是update,它的url是这样的, 127.0.0.1/books/1/,请求方式是PUT,1代表的是具体的数据,使用户动态传递的,所以我们的url应该是这样的:

re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),

此时我们应该重新定义一个视图类,因为url不一样了,所以在views.py中,需新增一个视图类:

from rest_framework.views import APIView
from app_serializer import BookSerializer

class BookFilterView(APIView):

    def put(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(data=request.data, instance=book_obj)

        if serialized_data.is_valid():
            serialized_data.save()
        else:
            return Response(serialized_data.errors)

请注意,在序列化时,我们除了传入data参数外,还需告诉序列化组件,我们需要更新哪条数据,也就是instance,另外,我们使用的序列化类还是之前那个:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ('title',
                  'price',
                  'publish',
                  'authors',
                  'author_list',
                  'publish_name',
                  'publish_city'
                  )
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True}
        }
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book_obj):
        # 拿到queryset开始循环 [{}, {}, {}, {}]
        authors = list()

        for author in book_obj.authors.all():
            authors.append(author.name)

        return authors

使用POSTMAN工具发送一个PUT请求修改数据:

请注意,此时会报错:RuntimeError: You called this URL via PUT, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:9001/serializers/books/1/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.

因为,如果是GET请求,Django的全局APPEND_SLASH参数为True,所以会在url后面加上/(如果没有),但是如果是PUT或者DELETE请求,APPEND_SLASH不会添加 / 到url末尾。而我们上面定义的url是明确以 / 结尾的,所以,我们应该在url后面加上反斜线 / ,或者把url修改为不以斜线结尾。

加上之后,再次发送请求修改数据:查看数据库,发现,数据已经被修改了。

这就是PUT接口逻辑的设计,分为如下几个步骤:

  • url设计:re_path(r’books/(\d+)/$’, views.BookFilterView.as_view())
  • 视图类:重新定义一个视图类
  • put方法:在视图类中定义一个put方法
  • 序列化:在序列化的过程中,需要传入当前修改的数据行,参数名为instance
  • 序列化类:不需要修改
  • url路径:请求时,发送的url必须与urls.py中定义的url完全匹配

使用serializer进行delete接口设计

接下来,继续设计delete接口,根据规范,delete接口的url为:127.0.0.1/books/1/,请求方式是DELETE,与put是一致的,都是对用户指定的某行数据进行操作,数字1是动态的,所以我们的url不变:

re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),

同样的,视图类和序列化类都不需要重新定义,只需要在视图类中定义一个delete方法即可,如下代码所示:

class BookFilterView(APIView):

    def delete(self, request, nid):
        book_obj = Book.objects.get(pk=nid).delete()

        return Response("")

    def put(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(data=request.data, instance=book_obj)

        if serialized_data.is_valid():
            serialized_data.save()
            return Response(serialized_data.data)
        else:
            return Response(serialized_data.errors)

用POSTMAN来试试DELETE请求,我们将刚刚添加的数据删除,操作成功,同样的,请注意,请求url必须完全匹配urls.py中定义的url。

使用serializer进行单条数据的接口设计

最后一个接口的设计,是对单条数据进行获取,根据规范url为:127.0.0.1/books/1/,请求方式为GET,根据url和前面两个接口的经验,这次仍然使用之前的视图类,因为根据REST规范,url唯一定位资源,127.0.0.1/books/1/和127.0.0.1/books/是不同的资源,所以,我们不能使用之前那个获取全部数据的视图类。这里肯定不能重用之前的那个get方法,必须重新定义一个get方法。

urls.py不变,新增三个接口逻辑后的视图类如下:

class BookFilterView(APIView):
    def get(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(book_obj, many=False)

        return Response(serialized_data.data)

    def delete(self, request, nid):
        book_obj = Book.objects.get(pk=nid).delete()

        return Response("")

    def put(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(data=request.data, instance=book_obj)

        if serialized_data.is_valid():
            serialized_data.save()
            return Response(serialized_data.data)
        else:
            return Response(serialized_data.errors)

many=False, 当然,也可以不传这个参数,因为默认是False。通过POSTMAN发送请求,成功。三个接口定义完成了,加上上一节课的get和post,两个视图类的接口逻辑如下:

class BookView(APIView):
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

    def post(self, request):
        verified_data = BookSerializer(data=request.data)

        if verified_data.is_valid():
            book = verified_data.save()
            return Response(verified_data.data)
        else:
            return Response(verified_data.errors)


class BookFilterView(APIView):
    def get(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(book_obj, many=False)

        return Response(serialized_data.data)

    def delete(self, request, nid):
        book_obj = Book.objects.get(pk=nid).delete()

        return Response("")

    def put(self, request, nid):
        book_obj = Book.objects.get(pk=nid)

        serialized_data = BookSerializer(data=request.data, instance=book_obj)

        if serialized_data.is_valid():
            serialized_data.save()
            return Response(serialized_data.data)
        else:
            return Response(serialized_data.errors)
复习:数据库增加数据
# 方式1
book = Book(
    btitle='无问',
    bput_date=date(1988,1,1),
    bread=10,
    bcomment=10
)
book.save()

# 方式2:
book.create(
    btitle='无问',
    bput_date=date(1988,1,1),
    bread=10,
    bcomment=10
)

参考资料:https://blog.csdn.net/qq_25068917/article/details/81077145

posted @ 2018-12-12 21:52  久末丶  阅读(310)  评论(0编辑  收藏  举报