介绍

这个教程将创建一套简单的代码高亮的pastebin的Web API。一路上我们会介绍许多构建REST frmaework的组件,并且让你有一个全面的认识,这些组件是如何组建在一起的。

这个教程相当’深奥’,在开始学习之前你大概需要准备一些饼干和一杯你最爱的啤酒。你如果只是想快速了解,你可以查看之前的快速入门文档。

注意:教程中的代码可以访问github地址,完整的代码:这里

设置新环境

在我们开始之前,我们会使用virtualenv创建一个新的虚拟环境。这可以很好的将我们的项目与其他项目隔离开。

1
2
virtualenv env
source env/bin/activate

现在我们已经进入虚拟环境,我们可以安装一些依赖包了。

1
2
3
pip install django
pip install djangorestframework
pip install pygments # 我们将使用这个使代码高亮

注意:退出虚拟环境可以输入deactivate。更多信息,请看virtualenv文档

准备开始

我们已经准备开始敲代码了。开始前,让我们先创建一个新的项目。

1
2
3
cd ~
django-admin.py startproject tutorial
cd tutorial

一旦完成,我们可以创建一个app,我们将使用它来创建一套简单的Web API。

1
python manage.py startapp snippets

我们需要将snippetsapp和rest_frameworkapp添加到INSTALLED_APPS中。编辑tutorial/settings.py文件:

1
2
3
4
5
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)

Okay,我们已经准备好了~

创建一个模型

出于本教程的目的,我们首先创建一个简单的Snippet模块,用于存储代码片段。编辑snippets/models.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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',)

同步数据库

1
2
python manage.py makemigrations snippets
python manage.py migrate

创建一个序列化器

使用WebS API的第一件事就是提供一种将snippet实例序列化和反序列化成其他数据格式(如json)的方法。我们可以声明serializers(序列化器)来实现,这和Django的forms很像。在snippets的目录下创建一个serializers.py文件并添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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):
"""
创建并返回一个Snippet实例
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
更新并返回一个已经存在Snippet实例
"""
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()是一个实例是如何被创建或更新的。

序列化器类(serializer)很像Django的Form,同时在各个字段中也包含了类似的验证标志,如requiredmax_lengthdefault

在某些情况下,字段标志也可以控制序列化后如何显示,例如渲染成HTML。上面的{'base_template': 'textarea.html'}标志等同于在DjangoForm类中使用widget=widgets.Textarea。这对于控制如何显示browsable API特别有用,正如我们将在教程后面看到的。

事实上,我们可以通过使用ModelSerializer类节省开发时间,稍后我们会看到,但是现在我们仍把注意力放在序列化器的明确定义上。

使用序列化器

我们先熟悉一下我们的新序列化器如何使用。进入Django shell

1
python manage.py shell

Okay,导入一些必要的东西,让我们创建两个代码片段

1
2
3
4
5
6
7
8
9
10
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()

我们现在已经有两个可用的代码片端实例,将其中一个序列化试试看。

1
2
3
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。

1
2
3
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

反序列化也一样。我们将流解析成Python原生类型…

1
2
3
4
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)

…然后我们把原生类型还原成一个完整的对象实例

1
2
3
4
5
6
7
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是如何工作的。当我们使用序列化器写视图函数时,相似之处会变得更加明显。

我们也可以对一个querysets进行序列化。只需在序列化器的参数中添加一个many=True的标志。

1
2
3
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')])]

使用ModelSerializers

我们的SnippetSerializer类太臃肿了,要是能再简洁一点就更好了。

Django提供了FormModelForm类,同样的,RESTframework也有SerializerModelSerializer

现在让我们用ModelSerializer重构我们序列化器吧。再次打开snippets/serializer.py,将SnippetSetializer类替换为一下代码。

1
2
3
4
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

还有一个不错的特性就是你可以通过打印序列化器实例来查看所有字段信息。打开Django shell,输入一下命令:

1
2
3
4
5
6
7
8
9
10
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()方法

使用序列化器编写常规的Django视图

目前我们不会使用任何RESTframework的其他特性,我们只是编写Djang常规的视图函数。
编辑snippets/views.py文件,并添加以下代码。

1
2
3
4
5
6
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

首页是列出所有已存在的代码片段,或创建新的代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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令牌的客户端可以通过POST访问该视图,所以我们需要添加装饰器csrf_exempt。正常情况下你不该这么做,REST framework视图函数实际上还有比这更合理的解决方法,但目前这已经达到我们的目的了。

同样,我们创建一个显示单个代码片段的页面,允许搜索,更新和删除代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.urls import path
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>/', views.snippet_detail),
]
# Django-2.0以下版本
# 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),
# ]

将上面的url配置整合到根url配置文件中tutorial/urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('snippets.urls')),
]
# 2.0以下
# from django.conf.urls import url, include
#
# urlpatterns = [
# url(r'^', include('snippets.urls')),
# ]

目前,我们还有许多边界条件没有处理。如果我们发送的json格式不正确,或者请求一个视图无法处理的方法时,我们会返回一个500 server error。

测试我们在Web API上的第一次尝试

现在我们可以启动服务器运行我们服务了。
先退出刚刚的shell…

1
quit()

…接着启动我们的服务器

1
2
3
4
5
6
7
8
python manage.py runserver
Validating models...
0 errors found
Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

在另一个终端窗口,来测试我们的服务。

我们可以使用curl或者httpie来测试。Httpie是由python实现的一款的http客户端。

你可以通过pip来安装它:

1
pip install httpie

接着,我们尝试获取所有代码片段的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"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"
}
]

或者我们可以通过它的id来访问单个代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
http http://127.0.0.1:8000/snippets/2/
HTTP/1.1 200 OK
...
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}

同样地,你可以通过浏览器访问URLs来获得这些json数据。

我们到什么程度了

目前为止我们表现还不错,我们已经有了一个序列化的API,这感觉跟Django的表单API和一些常规的Django视图非常相似。

除了返回json数据,我们的视图没有做什么特别的事。还有一些边界条件的处理没有实现,但这仍是一个正常运行的Web API。

我们将在教程2开始改进它。


原文:http://www.django-rest-framework.org/tutorial/1-serialization/#working-with-serializers

留言

⬆︎TOP