DRF-Serialization
按照上一章内容的架构图自下向上,本章介绍Serialization。关于Model层,在原先的Django基础上未做修改,故不做介绍。若想了解可参看官方文档或可参看我之前写的Django模型简介
Introduction
首先思考一下,若不使用DRF框架,我们生成一个API接口应该会怎么做呢?比如传入一个用户名,需要返回用户的相关信息。大概会这么实现:
def get_user_info(request):
if request.method == 'GET':
username = request.GET.get('username', None)
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
result = {}
if not user:
result = {
'name': user.username,
'email': user.email,
'is_superuser': user.is_superuser,
}
return JsonResponse(result)
我们需要做的,首先是解析从客户端传来的参数,然后按照要求获取相关信息,最后组成需要返回的数据。
如果客户端传入的参数,以及需要返回的数据特别多,还按照上述方式进行处理的话,一个视图函数将会变得十分臃肿。而且一般我们的api接口大部分是对数据库表文件进行增删查改,每个功能都只用这么一个视图函数来实现的话,会出现许多重复性代码。
DRF在models层增加一层Serialization,主要用于解析客户端传入的json参数将其转化为django可用的数据。以及将models的数据序列化为json数据,用于返回给客户端。
接下来,让我们用官方文档中的例子,具体看看Serialization
Creating a model to work with
因为Serialization一个重要的功能就是序列化models数据,甚至Serialization可以直接与model绑定。所以首先,让我们来创建一个model:
snippets/models.py
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
Serialization
Creating a Serializer class
snippets/serializers.py
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
可以看到serializer
类的第一部分定义了我们需要序列化或反序列化的字段,非常类似于Django 的Form
类,并且每个字段中都包含了相似的认证标志,包括required
, max_length
和 default
。
第二部分定义两个类函数:create
和update
,分别对应着Snippet
model实例的创建和更新。当调用serializer.save()
时,会调用这两个函数。让我们来看看DRF源码中serializers.py
文件中对应的代码就会明白:
def save(self, **kwargs):
……
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
可以看到,当我们调用serializer.save()
时,若serializer
中对应的model实例不存在时创建实例,若存在即更新实例。
接下来,让我们看看我们定义的SnippetSerializer
类的功能
序列化
创建实例
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
序列化
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
serializer_list = SnippetSerializer(Snippet.objects.all(), many=True)
serializer_list.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
可以看到serializer
类序列化后的字符串格式为Unicode格式,可用JSONRenderer()
将其渲染成普通字符串。
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
反序列化
格式转换
import io
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
反序列化
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
注意:当对数据进行反序列化时,必须对数据进行可靠性检验,即调用serializer.is_valid()
。还可以重载此函数,加入自己的检验逻辑。
还是继续看看源码,首先是serializer
类的构造函数,可追溯到其父类BaseSerializer
类的构造函数:
class BaseSerializer(Field):
'''
In particular, if a `data=` argument is passed then:
.is_valid() - Available.
.initial_data - Available.
.validated_data - Only available after calling `is_valid()`
.errors - Only available after calling `is_valid()`
.data - Only available after calling `is_valid()`
If a `data=` argument is not passed then:
.is_valid() - Not available.
.initial_data - Not available.
.validated_data - Not available.
.errors - Not available.
.data - Available.
'''
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
if data is not empty:
self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
super(BaseSerializer, self).__init__(**kwargs)
……
可以看到当传入model实例,对实例进行序列化;当传入data时,对data进行反序列化。
并且当传入data与否,函数应按照源码注释中所言的限制来使用。
ModelSerializers
SnippetSerializer
类与Snippet
类仍然有许多重复的字段的信息,如果能够再简洁一些就更好了。于是DRF 封装了ModelSerializers
类,直接与Model相绑定。于是SnippetSerializer
类变成了:
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
使用命令python manage.py shell
,尝试如下操作:
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', read_only=True)
# title = CharField(allow_blank=True, max_length=100, required=False)
# code = CharField(style={'base_template': 'textarea.html'})
# linenos = BooleanField(required=False)
# language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
# style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
ModelSerializers
类只是对原来的Serializers
类进行了封装,内部实现了字段列表及create
、update
等函数的实现。从而使得,当继承ModelSerializers
类时,代码变得更加简洁。但实际上两者完成的功能一致。
Writing regular Django views using our Serializer
如上, Serializers
基本已介绍完毕。接下来用我们实现的SnippetSerializer
,完成部分API接口。
snippets/views.py
:
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
@csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
snippets/urls.py
:
from django.urls import path
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>/', views.snippet_detail),
]