zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    《统计学习方法》
    《赤裸裸的统计学》
    a[i]=i++
    正态分布(normal distribution)
    可视化
    数据转换
    beLazy
    水到渠成
    数据过滤
    数据合并
  • 原文地址:https://www.cnblogs.com/jiumo/p/10111250.html
Copyright © 2011-2022 走看看