zoukankan      html  css  js  c++  java
  • DRF 解析器组件

    Django无法处理application/json协议请求的数据,即,如果用户通过application/json协议发送请求数据到达Django服务器,我们通过request.POST获取到的是一个空对象。

    引入

    Django RestFramework帮助我们实现了处理application/json协议请求的数据,另外,我们也提到,如果不使用DRF,直接从request.body里面拿到原始的客户端请求的字节数据,经过decode,然后json反序列化之后,也可以得到一个Python字典类型的数据。

    但是,这种方式并不被推荐,因为已经有了非常优秀的第三方工具,那就是Django RestFramework的解析器组件。

    解析器组件

    解析器组件的使用:

    from django.http import JsonResponse
    
    from rest_framework.views import APIView
    from rest_framework.parsers import JSONParser, FormParser
    # Create your views here.
    
    
    class LoginView(APIView):
        parser_classes = [FormParser]
    
        def get(self, request):
            return render(request, 'parserver/login.html')
    
        def post(self, request):
            # request是被drf封装的新对象,基于django的request
            # request.data是一个property,用于对数据进行校验
            # request.data最后会找到self.parser_classes中的解析器
            # 来实现对数据进行解析
            
            print(request.data)  # {'username': 'alex', 'password': 123}
    
            return JsonResponse({"status_code": 200, "code": "OK"})

    使用方式非常简单,分为如下两步:

    • from rest_framework.views import APIView
    • 继承APIView
    • 直接使用request.data就可以获取Json数据

    如果你只需要解析Json数据,不允许任何其他类型的数据请求,可以这样做:

    • from rest_framework.parsers import JsonParser
    • 给视图类定义一个parser_classes变量,值为列表类型[JsonParser]
    • 如果parser_classes = [], 那就不处理任何数据类型的请求了

    源码:

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r. as_view "
                                    "only accepts arguments that are already "
                                    "attributes of the class." % (cls.__name__, key))
    
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs
    
        # take name and docstring from class
        update_wrapper(view, cls, updated=())
    
        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    请求到来,根据url查找映射表,找到视图函数,然后执行view函数并传入request对象

    解析器组件源码剖析

    序列化组件
    序列化组件的使用

    定义几个 model:

    from django.db import models
    
    # Create your models here.
    
    
    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)
        publishDate = models.DateField()
        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接口设计
    from django.urls import re_path
    
    from serializers import views
    
    urlpatterns = [
        re_path(r'books/$', views.BookView.as_view())
    ]

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

    # -*- coding: utf-8 -*-
    from rest_framework import serializers
    
    from .models import Book
    
    
    class BookSerializer(serializers.Serializer):
        title = serializers.CharField(max_length=128)
        publish_date = serializers.DateTimeField()
        price = serializers.DecimalField(max_digits=5, decimal_places=2)
        publish = serializers.CharField(max_length=32)
        authors = serializers.CharField(max_length=32)

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

    # -*- coding: utf-8 -*-
    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):
            origin_books = Book.objects.all()
            serialized_books = BookSerializer(origin_books, many=True)
    
            return Response(serialized_books.data)

    定义好model和url后,使用序列化组件的步骤如下:

    • 导入序列化组件:from rest_framework import serializers
    • 定义序列化类,继承serializers.Serializer(建议单独创建一个专用的模块用来存放所有的序列化类):class BookSerializer(serializers.Serializer):pass
    • 定义需要返回的字段(字段类型可以与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=128, source="title")
        publishDate = serializers.DateTimeField()
        price = serializers.DecimalField(max_digits=5, decimal_places=2)
        # source也可以用于ForeignKey字段
        publish = serializers.CharField(max_length=32, source="publish.name")
        authors = serializers.CharField(max_length=32)

    下面是通过POSTMAN请求该接口后的返回数据:

    [
        {
            "title": "Python入门",
            "publishDate": null,
            "price": "119.00",
            "publish": "浙江大学出版社",
            "authors": "serializers.Author.None"
        },
        {
            "title": "Python进阶",
            "publishDate": null,
            "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)
        publishDate = serializers.DateField()
        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方法即可,序列化类不需要修改,如下:

    # -*- coding: utf-8 -*-
    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):
            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()
                # 可写字段通过序列化添加成功之后需要手动添加只读字段
                authors = Author.objects.filter(nid__in=request.data['authors'])
                book.authors.add(*authors)
    
                return Response(verified_data.data)
            else:
                return Response(verified_data.errors)

    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=128)
        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
    
        def create(self, validated_data):
            # {'title': 'Python666', 'price': Decimal('66.00'), 'publish': '2'}
            validated_data['publish_id'] = validated_data.pop('publish')
            book = Book.objects.create(**validated_data)
    
            return book
    
        def update(self, instance, validated_data):
            # 更新数据会调用该方法
            instance.title = validated_data.get('title', instance.title)
            instance.publishDate = validated_data.get('publishDate', instance.publishDate)
            instance.price = validated_data.get('price', instance.price)
            instance.publish_id = validated_data.get('publish', instance.publish.nid)
    
            instance.save()
    
            return 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

    步骤如下:

    • 继承ModelSerializer:不再继承Serializer
    • 添加extra_kwargs类变量:extra_kwargs = {‘publish’: {‘write_only’: True}}
  • 相关阅读:
    outlook express 发不出邮件问题
    当您更改为一个值该值不是有效的启动参数对于群集实例的 SQL Server 2000 或 SQL Server 2005 的 SQL Server 服务不能启动
    (转)为gridview“删除”列添加确认对话框
    关于开心网
    Windows 群集(一)
    你们公司有软件实验室吗?
    数据安全性小结
    请教:如何限制C++.net托管组件在设计时不能运行?
    test:请不要访问
    将WDL(华康)等电子文件转换为PDF后转换其它格式文件的方法
  • 原文地址:https://www.cnblogs.com/zbw582922417/p/10120134.html
Copyright © 2011-2022 走看看