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类中dispatch的反射之外,还对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中initialize_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
)

  在实例化Request对象的时候,传入第二个参数是parsers,执行get_parsers函数,APIView类中get_parsers函数如下:

  def get_parsers(self):    # 获取所有解析器,结果返回给Request类实例化对象时参数
    # 这里的parser_classes是在APIView中定义的类变量
    return [parser() for parser in self.parser_classes]

  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_framework import serializers

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

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

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

    - 开始序列化:将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": "",
"title": "python初级",
"price": "188.00",
"publish": "清华大学出版社",
"authors": "serializer.Author.None"
},
{
"nid": "",
"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',虽然可以在项目的任何地方使用:

>>> 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,所以建议的导入方式是:

>>> 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')

最新文章

  1. 《图形学》实验五:改进的Bresenham算法画直线
  2. golang使用 mongo
  3. Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)
  4. qqzoneQQ空间漏洞扫描器的设计attilax总结
  5. 学习 java命令
  6. 【BZOJ 1007】【HNOI 2008】水平可见直线 解析几何
  7. java循环遍历map
  8. JAVA字节码解析
  9. mac下pmset的使用方法
  10. 纯原生js移动端日期选择插件
  11. 浅谈负载均衡之【tomcat分布式session共享】
  12. JMeter数据库性能测试
  13. 使用BufferedReader的时候出现的问题
  14. 【python之路12】三元运算符(if)
  15. macos系统下共语言gopath变量的设置
  16. make和makefile简明基础
  17. react 监听页面滚动
  18. springboot+jpa+mysql+redis+swagger整合步骤
  19. python接口自动化测试六:时间戳,防重复处理
  20. 【C/C++】1~20的阶乘之和

热门文章

  1. 从0到1,Java Web网站架构搭建的技术演进
  2. Makefile 自动变量之 $(@D),$(@F)
  3. JS面试题目
  4. Windows 内核(WRK)简介
  5. ORACLE修改列名与列类型
  6. poj1182食物链--并查集
  7. .net mvc 下实现移动架构display mode
  8. Android插件化开发之OpenAtlas生成插件信息列表
  9. C#获取CPU编号
  10. ICO图标的制作与应用