• 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

  • 相关阅读:
    HDU1879 kruscal 继续畅通工程
    poj1094 拓扑 Sorting It All Out
    (转)搞ACM的你伤不起
    (转)女生应该找一个玩ACM的男生
    poj3259 bellman——ford Wormholes解绝负权问题
    poj2253 最短路 floyd Frogger
    Leetcode 42. Trapping Rain Water
    Leetcode 41. First Missing Positive
    Leetcode 4. Median of Two Sorted Arrays(二分)
    Codeforces:Good Bye 2018(题解)
  • 原文地址:https://www.cnblogs.com/jiumo/p/10111250.html
Copyright © 2020-2023  润新知