第一章 序列化
写在前面:
本文翻译于django rest framework官方文档,由于网上大多数django rest framework中文翻译文档都有较为多的删减行为,笔者在学习的时候就觉得不是太方便,故笔者将官方文档较为完善的为大家翻译下,仅供大家学习参考。由于笔者文笔有限,若有写得不当之处,敬请各位同仁指出;若涉及到侵权,请联系笔者,笔者将立即删除。
0. 介绍
本教程将为大家创建一个简单的代码高亮的Web API,在这个过程中,我们将为你介绍组成REST framework的各种组件,并且让你了解这些组件是如何配合工作的。
整篇教程是非常深入的,所以在开始的时候你最好准备好炸鸡和啤酒,以防你中途睡着,如果你只想要对这个框架有一个快速的大致的了解,你就可以来看这篇快速入门文档。
1. 开始一个新的环境(虚拟环境)
在开始之前我们还有很重要的一步,那就是创建虚拟环境,如果你对虚拟环境不太了解的话,笔者向你推荐这篇文章python virtualenv使用教程
virtualenv env
source env/bin/activate
现在我们在我们的虚拟环境中安装我们需要的包
pip install django
pip install djangorestframework
pip install pygments # 这个包我们将用来实现代码高亮
2.码代码前最后的准备
好的,我们从现在开始就要码代码了,期待麽,我们首先先创建一个项目吧
cd ~
django-admin.py startproject tutorial
cd tutorial
我们创建完项目之后再在项目目录下创建一个app
python manage.py startapp snippets
我们现在在settings文件中把我们刚才创建的snippets
和rest_framework
加入到INSTALLED_APPS
中,我们现在打开tutorial/settings.py
文件:
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
OK,我们的准备工作就做好啦(We are ready to roll)
3.开始创建我们的模型
我们打开我们的snippets/models.py
文件,创建一个简单的模型Snippet
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',)
下一步我们创建数据库迁移并执行,然后同步数据库。
python manage.py makemigrations snippets
python manage.py migrate
4.创建一个 序列化 类
在这一步,我们需要在我们的WEB Api中提供序列化和反序列化的代码实例,如JSON格式。我们可以通过声明序列化程序来完成这些,序列化的工作机制类似于Django
的表单forms
。我们现在在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):
"""
创建并返回一个Snippets实例化对象,并且给出验证数据
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
更新并且返回一个已存在的Snippets实例化对象,并给出验证数据
"""
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
这个类定义了我们需要序列化或者反序列化的字段, create()
和 update()
方法定义了在调用serializer.save()
时如何创建或者修改完整的实例。
这个序列化的类和Django的Forms类很类似,并且在各个字段上有一些类似的验证标志,如required
, max_length
,default
等
字段标志还可以控制在某些情况下如何显示序列化程序,例如在呈现为HTML时。 上面的{'base_template':'textarea.html'}
标志等同于在Django Form类上使用widget = widgets.Textarea
这对于控制如何显示可浏览的API特别有用,这里我们将在后面看到。
但事实上我们平时写项目的时候不会这样把一个一个的字段都列出来,我们通常使用ModelSerialzer
类
5. 操作序列化器
我们先进入Django shell
来熟悉下我们之前的代码。
在项目目录里输入
python manage.py shell
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'}
这时我们把实例化对象转换成了python的格式,如果我们需要完成序列化的过程,我们需要将它转化为json
格式。
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
反序列化也是类似的,首先我们将一个流解析为python的类型。
from django.utils.six import BytesIO # six模块是一个python2和python3的兼容性库
stream = BytesIO(content)
data = JSONParser().parse(stream)
然后我们再将python的数据类型转换成对象实例
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>
从这里我们可以注意到这个API工作起来和表单有多么的类似,当我们开始用我们的序列化写视图函数的时候我们会感觉的更加明显。
除此之外,我们还可以序列化查询集而不是模型实例,我们在代码中添加一个many=True
的标志到序列化程序的参数里就行了
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.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')])]
6.使用ModelSerailizer
我们之前写的SnippetSerializer
复制了大量在Snippet
模型里有的信息,我们可以用另一种方法来使得我们的代码更加简洁。
与Django
提供的Form
类和ModelForm
类相似,REST Framework
也为我们提供了Serializer
类和ModelSerializer
类。
我们现在使用ModelSerializer
类来重构我们的序列化器,我们再次打开serializer.py
,用以下的内容来代替之前写的SnippetSerializer
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
序列化器有一个很好的属性,就是我们可以打印它所有的字段在一个序列化的实例中,我们再次打开Django 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')...
神奇吧
但是,```ModelSerializer``并没有做什么神器的事情,它只是创建了序列化类的快捷方式。
- 自动确定了所有字段
-
create
()和update()
方法都是简单的默认实现
7.使用序列化器来编写一个常规的Django视图
我们看看我们如何使用新的serializer
类来编写API视图,目前我们不使用REST Framework
的其他功能,我们用Django
常规的方式来写。
首先我们渲染一个HttpResponse
的子类,我们可以使用他来渲染任何我们返回的json
数据。
打开snippets/views.py
文件,做如下操作
from django.http import HttpResponse
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
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
同时,我们在我们的这个API上创建列出所有已存在的实例,或者创建新的实例的视图
@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)
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令牌的客户端来向此视图POST
数据,我们需要将视图用装饰器@csrf_exempt
装饰,但这不是我们通常要做的,REST Framework
中的视图实际上会使用比这更加明智的行为,我们后面再说。
我们现在还需要一个与单个实例对应的视图,并可以用来检索,更新和删除操作。
@csrf_exempt
def snippet_detail(request, pk):
"""
检索,更新和删除
"""
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.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
并且在tutorial/urls.py
文件中连接根urlconf
,以及我们snippets app
的URL
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
虽然,目前这样做可能会出现一些状况,服务器会报500错误,但我们现在先这样做,其他的问题后面再来解决。
8. 测试我们做的第一个API
我们先退出shell
,然后开启本地服务器。
python manage.py runserver
Validating models...
0 errors found
Django version 1.8.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
然后我们使用浏览器跳转到http://127.0.0.1:8000/snippets/
,
我们将看到我们得到了一个所有的实例的列表
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
然后我们再跳转到http://127.0.0.1:8000/snippets/2/
,我们可以看到我们id=2
的实例
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
9. 我们现在走到哪了
我们现在拥有了第一个序列化API,然后写了一个Django
常规的视图,在有一些错误处理的情况下我们还需要改进,但是,但是,这确确实实是一个有功能的API了,为自己鼓掌吧!
下一阶段我们将来讲一讲如何改进,请关注我们第二章的内容。
谢谢各位阅读,并欢迎各位留下宝贵意见