zoukankan      html  css  js  c++  java
  • Restful 2 --DRF解析器,序列化组件使用(GET/POST接口设计)

    DRF - 解析器

    1、解析器的引出

      我们知道,浏览器可以向django服务器发送json格式的数据,此时,django不会帮我们进行解析,只是将发送的原数据保存在request.body中,只有post请求发送urlencoded格式的数据时,django会帮我们将数据解析成字典放到reques.POST中,我们可直接获取并使用,下面是django对数据解析的相关源码:

    def _load_post_and_files(self):
        if self.method != 'POST':
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
            return
        if self._read_started and not hasattr(self, '_body'):
            self._mark_post_parse_error()
            return
        if self.content_type == 'multipart/form-data':
            if hasattr(self, '_body'):
                data = BytesIO(self._body)
            else:
                data = self
            try:
                self._post, self._files = self.parse_file_upload(self.META, data)
            except MultiPartParserError:
                self._mark_post_parse_error()
                raise
        elif self.content_type == 'application/x-www-form-urlencoded':
            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
        else:
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

      分析:有源码可见,django并没有解析json数据的操作,那么我们自己是否可以解析,当然可以,如下代码:

    class LoginView(View):
        def get(self, request):
            return render(request, 'login.html')
    
        def post(self, request):
            print(request.body)  # b'{"name":"alex","password":123}'
            origin_data = request.body.decode('utf-8')
            parsed_data = json.loads(origin_data)
            print(parsed_data)  # {'name': 'alex', 'password': 123}
            print(type(parsed_data))  # <class 'dict'>
            return HttpResponse("Ok")

      分析:上面代码可以看出,我们完全可以拿到用户发送的数据,然后进行解码和反序列化,那么问题来了,我们的项目中可能不止一次需要发送json格式数据,这是面临的问题就是拿到数据都要自己进行解析,有没有这样的一个工具可以为我们解析用户发送的json格式数据,答案当然有,DRF的APIView就为我们提供了这样的功能,看如下代码:

    from rest_framework.views import APIView
    class LoginView(APIView):
        def get(self, request):
            return render(request, 'login.html')
    
        def post(self, request):
            # request是被drf封装的新对象,基于django的request
            # request.data是一个被property装饰的属性方法
            # request.data最后会找到self.parser_classes中的解析器 
            # 来实现对数据进行解析
            print(request.data)   # {'name': 'alex', 'password': 123}
            print(type(request.data))  # <class 'dict'>
            return HttpResponse("Ok")

      分析:上面代码可以看出,我们通过使用APIView代替CBV中的View后,就可以通过request。data获取到经过解析后的用户发送的json格式数据。由此,我们可以猜测,DRF中的APIView继承了View并且对它进行了功能的丰富。接下来我们通过源码寻找答案。

    2、解析器源码解读

      APIView类中的dispatch方法实现View类中dispath的反射之外,还对request进行了封装,APIView类部分源码如下:

    class APIView(View):
       ...
       # api_settings是APISettings类的实例化对象,
       parser_classes = api_settings.DEFAULT_PARSER_CLASSES
       # APIView类加载时parser_classes已经有值,就是解析器,print(parser_classes)
       # 程序启动就能看见打印结果,结果如下
       # [<class 'rest_framework.parsers.JSONParser'>, 
       # <class 'rest_framework.parsers.FormParser'>, 
       # <class 'rest_framework.parsers.MultiPartParser'>]
       ...
        settings = api_settings
        schema = DefaultSchema()
    
        @classmethod
        def as_view(cls, **initkwargs):   # cls指LoginView
            if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
                ...
         # 下面一句表示去执行APIView父类(即View类)中的as_view方法
            view = super(APIView, cls).as_view(**initkwargs)
            view.cls = cls
            view.initkwargs = initkwargs
            return csrf_exempt(view)
    
      def dispatch(self, request, *args, **kwargs):
            ...
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            ...
    
            try:
                self.initial(request, *args, **kwargs)
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed
                response = handler(request, *args, **kwargs)
            except Exception as exc:
                response = self.handle_exception(exc)
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response

      使用initialize_request方法,对request进行加工,添加功能,APIView中initalize_request函数代码如下:

    def initialize_request(self, request, *args, **kwargs):
        parser_context = self.get_parser_context(request)
        # 返回Request的实例化对象
        return Request(
            request,
            parsers=self.get_parsers(),  # 这里的self指LoginView实例对象
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

      APIView类所在文件views.py中导入了Request和api_settings,如下:

      from rest_framework.request import Request
      from rest_framework.settings import api_settings

      Request类的部分代码如下:

    class Request(object):
        def __init__(self, request, parsers=None, authenticators=None,
                     negotiator=None, parser_context=None):
            assert isinstance(request, HttpRequest), (
                'The `request` argument must be an instance of '
                '`django.http.HttpRequest`, not `{}.{}`.'
                .format(request.__class__.__module__, request.__class__.__name__)
            )
    
            self._request = request
            self.parsers = parsers or ()
            self.authenticators = authenticators or ()
            self.negotiator = negotiator or self._default_negotiator()
            self.parser_context = parser_context
            self._data = Empty
            self._files = Empty
            self._full_data = Empty
            self._content_type = Empty
            self._stream = Empty
    
            if self.parser_context is None:
                self.parser_context = {}
            self.parser_context['request'] = self
            self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
    
            force_user = getattr(request, '_force_auth_user', None)
            force_token = getattr(request, '_force_auth_token', None)
            if force_user is not None or force_token is not None:
                forced_auth = ForcedAuthentication(force_user, force_token)
                self.authenticators = (forced_auth,)
    
      @property
      def data(self):
          if not _hasattr(self, '_full_data'):
              self._load_data_and_files()
          return self._full_data
    
      def _load_data_and_files(self):
          if not _hasattr(self, '_data'):
              # _parse()的执行结果是返回(parsed.data, parsed.files)
              self._data, self._files = self._parse()
              if self._files:
                  self._full_data = self._data.copy()
                  self._full_data.update(self._files)
              else:
                  self._full_data = self._data 
               # 此时self._full_data就是parsed.data,即解析后的数据
    
              if is_form_media_type(self.content_type):
                  self._request._post = self.POST
                  self._request._files = self.FILES
    
      def _parse(self):
          media_type = self.content_type
          try:
              stream = self.stream
          except RawPostDataException:
              if not hasattr(self._request, '_post'):
                  raise
              if self._supports_form_parsing():
                  return (self._request.POST, self._request.FILES)
              stream = None
    
          if stream is None or media_type is None:
              if media_type and is_form_media_type(media_type):
                  empty_data = QueryDict('', encoding=self._request._encoding)
              else:
                  empty_data = {}
              empty_files = MultiValueDict()
              return (empty_data, empty_files)
    
          parser = self.negotiator.select_parser(self, self.parsers)    # 这里的self.parsers就是解析类
    
          if not parser:
              raise exceptions.UnsupportedMediaType(media_type)
    
          try:
              parsed = parser.parse(stream, media_type, self.parser_context)
          except Exception:
              self._data = QueryDict('', encoding=self._request._encoding)
              self._files = MultiValueDict()
              self._full_data = self._data
              raise
    
          try:
              return (parsed.data, parsed.files)
          except AttributeError:
              empty_files = MultiValueDict()
              return (parsed, empty_files)

      api_settings所在的settings.py中部分相关代码如下:

    DEFAULTS = {
        ...,
        'DEFAULT_PARSER_CLASSES': (
            'rest_framework.parsers.JSONParser',
            'rest_framework.parsers.FormParser',
            'rest_framework.parsers.MultiPartParser'
        ),
        ...
    }
    
    class APISettings(object):
        def __init__(self, user_settings=None, defaults=None, import_strings=None):
            if user_settings:
                self._user_settings = self.__check_user_settings(user_settings)
            self.defaults = defaults or DEFAULTS
            self.import_strings = import_strings or IMPORT_STRINGS
            self._cached_attrs = set()
    
            @property
      def user_settings(self):
          if not hasattr(self, '_user_settings'):
              self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
          return self._user_settings
    
      def __getattr__(self, attr):   # 形参attr对应实参是DEFAULT_PARSER_CLASSES
          if attr not in self.defaults:
              raise AttributeError("Invalid API setting: '%s'" % attr)
    
          try:
              val = self.user_settings[attr]
          except KeyError:
              val = self.defaults[attr]
    
          if attr in self.import_strings:
              val = perform_import(val, attr)  # 参考动态import理解
    
          self._cached_attrs.add(attr)
          setattr(self, attr, val)
          return val
    
          api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
         # 注意:api_settings是APISettings类的实例化对象,因为对象api_settings没有DEFAULT_PARSER_CLASSES属性,所以api_settings.DEFAULT_PARSER_CLASSES时,会执行APISettings类的__getattr__方法,并且将DEFAULT_PARSER_CLASSES作为参数传入。

    3、自己指定解析数据类型

      知道了DRF的APIView封装了哪几个解析器类(JSONParser, FormParser,MultiPartParser)之后,我们可以根据需要自己定义解析器,如下:

    from rest_framework.views import APIView
    from rest_framework.parsers import JSONParser
    class LoginView(APIView):
       parser_classes = [JSONParser]   # 只需要解析JSON数据
       # parser_classes = [] 则不能解析任何数据类型
        def get(self, request):
            return render(request, 'login.html')
    
        def post(self, request):
            request.data    # 解析后的数据
            return HttpResponse("Ok")

    二、序列化组件的使用及接口设计

    1、django原生serializer(序列化)的使用

    from django.core.serializers import serialize    # 1.导入模块
    class CourseView(APIView):
        def get(self, request):
            course_list = Course.objects.all()    # 2.获取queryset
             # 3.对queryset进行序列化
            serialized_data = serialize('json', course_list) 
             # 4.返回序列化后的数据
            return HttpResponse(serialized_data)

    2、通过DRF的序列化组件进行接口设计

      1)参考图书管理系统的表结构,models.py如下:

    from django.db import models
    class Book(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=5, decimal_places=2)
        publish = models.ForeignKey(to='Publish', related_name='book', on_delete=models.CASCADE)
        authors = models.ManyToManyField(to='Author')
    
    
    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

      2)有如下几个接口

    GET       127.0.0.1:8000/books/        # 获取所有数据,返回值: [{}, {}]
    GET       127.0.0.1:8000/books/{id}    # 获取一条数据,返回值:{}
    POST      127.0.0.1:8000/books/        # 新增一条数据,返回值:{}
    PUT       127.0.0.1:8000/books/{id}    # 修改数据,返回值:{}
    DELETE    127.0.0.1:8000/books/{id}    # 删除数据,返回空

      3)通过序列化组件进行get接口(获取所有数据)设计,序列化组建使用步骤如下:

        - 导入序列化组件 from rest_feanmework import serializers

        - 定义序列化类,继承serializers.Serializer(建议单独创建一个模块存放所有序列化类);

        - 定义需要返回的字(字段类型可以与model中类型不一致,参数也可调整),字段名称要与model中一致,若不一直则通过source参数指定原始的字段名;

        - 在GET接口逻辑中,获取queryset;

        -  开始序列化: 奖queryset作为第一个参数传给序列化类,many默认为false,如果返回的数据是一个含多个对象的queryset,需要改many=True;

        - 返回:将序列化对象的data属性返回即可;

      4)为了解耦,我们新建一个名为app_serializers.py的模块,将所有的序列化的使用集中在这个模块中:

    from rest_framework import serializers  #  导入序列化模块
    
    from .models import Book
    
    # 创建序列化类
    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(max_length=32)
        authors = serializers.CharField(max_length=32)

      5)视图代码如下:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from .app_serializers import BookSerializer
    from .models import Book, Publish, Author
    
    class BookView(APIView):
        def get(self, request):
            origin_data = Book.objects.all()  # 获取queryset
            # 开始序列化(参数many=True表示有多条数据,默认为False)
            serialized_data = BookSerializer(origin_data, many=True)
            # 将序列化对象的data属性返回
            return Response(serialized_data.data)

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

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

    [
        {
            "nid": "1",
            "title": "python初级",
            "price": "188.00",
            "publish": "清华大学出版社",
            "authors": "serializer.Author.None"
        },
        {
            "nid": "2", 
            "title": "python中级",
            "price": "78.00",
            "publish": "清华大学出版社",
            "authors": "serializer.Author.None"
        },
    ]

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

    from rest_framework import serializers  # 导入序列化模块
    # 创建序列化类
    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(max_length=32)
        authors = serializers.SerializerMethodField()
    
        def get_authors(self, author_object):
            authors = list()
    
            for author in author_object.authors.all():
                authors.append(author.name)
    
            return authors

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

      6)通过序列化组件进行post接口(提交一条数据)设计,步骤如下:

        - 定义post方法:在视图类中定义post方法;

        - 开始序列化:通过上面定义的序列化类,创建一个序列化对象,传入参数data=request.data(application/json)数据;

        - 校验数据:通过实例对象的is_valid()方法,对请求数据的合法性进行校验;

        - 保存数据:调用save()方法,将数据插入数据库;

        - 插入数据到多对多关系表:如果有多对多字段,手动插入数据到多对多关系表;

        - 返回:将插入的对象返回;

      注意:因为多对多关系字段是我们自定义的,而且必须这样定义,返回的数据才有意义,而用户插入数据的时候,无法找到这个字段类型SerializerMethodField,所以,序列化类不能帮我们插入数据到多对多表,我们必须手动插入数据,因此序列化类要做如下修改:

    from rest_framework import serializers  # 1.导入序列化模块
    
    from .models import Book
    
    # 2.创建序列化类
    class BookSerializer(serializers.Serializer):
        # nid字段只需要传给客户端,用户提交不需要id,所以read_only=True
        nid = serializers.CharField(read_only=True, max_length=32)
        title = serializers.CharField(max_length=128)
        price  = serializers.DecimalField(max_digits=5, decimal_places=2)
        publish = serializers.CharField(max_length=32)
        # SerializerMethodField默认read_only=True
        authors = serializers.SerializerMethodField()
    
        def get_authors(self, author_object):
            authors = list()
            for author in author_object.authors.all():
                authors.append(author.name)
            print(authors)
    
            return authors
    
        # 必须手动插入数据,因此post方法提交数据必须有create方法
        def create(self, validated_data):
            print(validated_data) # validated_data为过滤之后的数据
                # {'title': '手册', 'price': Decimal('123.00'), 'publish': '3'}
                validated_data['publish_id'] = validated_data.pop('publish')
                book = Book.objects.create(**validated_data)
    
                return book

      根据接口规范,我们不需要新增url,只需要在上面视图类中定义一个post方法即可,代码如下:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from .app_serializers import BookSerializer
    from .models import Book, Publish, Author
    
    class BookView(APIView):
        def get(self, request):
            origin_data = Book.objects.all()
            serialized_data = BookSerializer(origin_data, many=True)
            return Response(serialized_data.data)
    
        def post(self, request):
            verfied_data = BookSerializer(data=request.data)
    
            if verfied_data.is_valid():
                book = verfied_data.save()
             # 手动绑定多对多关系,也可以放到create方法中去
                authors = Author.objects.filter(nid__in=request.data['authors'])
                book.authors.add(*authors)
                return Response(verfied_data.data)
            else:
                return Response(verfied_data.errors)

     分析:上面这种方法有两个问题:一个是需要手动插入数据(写序列化类中写create方法),另一个是如果字段很多,写序列化类的字段也会变成一种负担,那么有没有更简单的方式呢?当然,那就是用ModelSerializer。

      7)使用ModelSerializer序列化组件写上面的get和post接口,修改app_serializers.py代码如下:

    from rest_framework import serializers  
            from .models import Book
    
    class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = (
                'title',
                'price',
                'publish',
                'authors',
                'author_list',
                'pubName',
                'pubCity'
            )
            extra_kwargs = {
                'publish':{'write_only':True},
                'authors':{'write_only':True}
            }
    
        pubName = serializers.CharField(max_length=32, read_only=True, source='publish.name')
        pubCity = serializers.CharField(max_length=32, read_only=True, source='publish.city')
    
        # 多对多字段
        author_list = serializers.SerializerMethodField()
    
        def get_author_list(self, book_obj):
            authors = list()
    
            for author in book_obj.authors.all():
                authors.append(author.name)
    
            return authors

    三、补充知识点

    1、访问对象一个不存在的属性会执行类的__getattr__方法,如下:

    class Person(object):
        def __init__(self, name, age): 
            self.name = name
            self.age = age
    
        def __getattr__(self, item):
            print(item)
    
    jihong = Person("jihong", 20)
    print(jihong.name)  # jihong
    jihong.hobby   # hobby

    2、动态import

    # foo.py文件
    def foo():
        print('this is foo')
    
    # test.py文件
    import importlib
    module_path = input('请输入要导入的模块')  # 输入 foo
    module = importlib.import_module(module_path)
    print(module)
    # <module 'foo' from 'D:\@Lily\drfserver\classbasedview\foo.py'>
    module.foo()   # 执行foo.py模块中的foo函数
    # this is foo

    3、多继承(参考面向对象的对继承C3算法)

    class A(object):
        def foo(self):
            print('A.foo')
    
    class B(A):
        def foo(self):
            print('B.foo')
            super().foo()
    
    class C(A):
        def foo(self):
            print('C.foo')
            super().foo()
    
    class D(B, C):
        def foo(self):
            print('D.foo')
            super().foo()
    
    d = D()
    d.foo()

     执行结果如下:

      D.foo

      B.foo

      C.foo

      A.foo

    4、Django settings文件查找顺序

      我们在使用django的时候,经常会使用到它的settings文件,通过在settings文件中定义变量,

      我们可以在程序的任何地方使用这个变量,比如,假设在settings里边定义了一个变量NAME='Lily',虽然可以在项目的任何地方使用:

    1
    2
    >>> from drf_server import settings
    >>> print(settings.NAME)   # Lily

      但是,这种方式并不是被推荐和建议的,因为除了项目本身的settings文件之外,django程序本身也有许多配置信息,都存在django/conf/global_settings.py模块里面,包括缓存、数据库、密钥等,如果我们写from drf_server import settings,只是导入了项目本身的配置信息,当需要用到django默认的配置信息的时候,还需要再次导入,即from django.conf import settings,所以建议的导入方式是:

    1
    2
    >>> from django.conf import settings
    >>> print(setting.NAME)

      使用上面的方式,我们除了可以使用自定义的配置信息(NAME)外,还可以使用global_settings中的配置信息,不需要重复导入,django查找变量的顺序是先从用户的settings中查找,然后在global_settings中查找,如果用户的settings中找到了,则不会继续查找global_settings中的配置信息,假设我在用户的settings里面定义了NAME='Lily',在global_settings中定义了NAME='Alex',则请看下面的打印结果:

    >>> from django.conf import settings
    >>> print(settings.NAME)   # Lily

      可见,这种方式更加灵活高效,建议使用。

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

    from rest_framework import serializers  # 导入序列化模块
    
    # 创建序列化类
    class BookSerializer(serializers.Serializer):
        nid = serializers.CharField(max_length=32)
        bookTitle = serializers.CharField(max_length=128, source='title')
        price  = serializers.DecimalField(max_digits=5, decimal_places=2)
        # source也可以用于ForeignKey字段
        pubName = serializers.CharField(max_length=32, source='publish.name')
        pubCity = serializers.CharField(max_length=32, source='publish.city')
        # 多对多字段source参数为“authors.all”,则取出来的结果是QuerySet,不推荐
        authors = serializers.CharField(source='authors.all')

     

     

  • 相关阅读:
    WIN10安装python及numpy等第三方库以及卸载
    学习Python一年,基础忘记了,看看面试题回忆回议,Python面试题No3
    包含了 java环境,mysql,nginx,redis docker 镜像
    Docker的镜像制作与整套项目一键打包部署
    RedHat Enterprise Linux 5.8 升级openssl
    RedHat Enterprise Linux 5.8 升级openssl
    RedHat Enterprise Linux 5.8 升级openssl
    log4net进阶手札(二):基本用法
    log4net进阶手札(二):基本用法
    log4net进阶手札(二):基本用法
  • 原文地址:https://www.cnblogs.com/wxj1129549016/p/10078749.html
Copyright © 2011-2022 走看看