Drf官网教程(一) - 序列化
目录
- 环境搭建和项目初始化
- 创建Model和数据库迁移
- 创建Serializer
- 序列化与反序列化
- 使用ModelSerializer
- 完成View部分
- 完成Url部分
- 测试接口
0. 概述
本教程将完成一个pastebin web应用,通过应用对Drf进行综合的介绍,了解Drf中各个模块是如何组织在一起的。
1. 环境搭建和项目初始化
-
创建新的虚拟环境
- 安装虚拟环境管理包(win)
pip install virtualenvwrapper-win
- 创建新的虚拟环境
mkvirtualenv drf_tutorial
-
安装Django相关的包
安装pygments的原因是本教程是代码片段相关的web应用,需要这个包提供代码高亮功能。(建议安装ipython来增强Django的shell)
pip install django==1.11 djangorestframework pygments ipython
-
项目初始化
- 创建项目
django-admin.py startproject drf_tutorial
- 创建应用
python manage.py startapp snippets
- 在
tutorial/settings.py
配置INSTALLED_APPS
django从比较新的版本开始(起码是1.11开始),都推荐使用下面这种方式注册来注册app。
INSTALLED_APPS = [
...
'rest_framework',
'snippets.apps.SnippetsConfig'
]
- 在
tutorial/settings.py
修改语言和时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
2. 创建Model和数据库迁移
-
在
snippet/models.py
中创建model
- 不用在意pygments相关的代码,不是本文重点(与Drf无关)。
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)
# 个人认为官网default与blank重复了,default其实也可以不用写,default默认值就是''。
title = models.CharField(max_length=100, 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:
# 通过创建时间获取record
ordering = ('created',)
-
数据库迁移
python manage.py makemigrations snippets
python manage.py migrate
3. 创建Serializer
-
概述
序列化的操作与Django的Form类似。
-
完成序列化代码
在snippets/
创建serializers.py
,在文件中创建类(Model名 + Serializer, 一种Serializer类名的命名习惯)
from rest_framework import serializers
from .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):
return Snippet.objects.create(**validated_data)
def update(self, instance, 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
代码主要由两部分组成:
- 第一部分,由序列化/解序列化时的字段组成,字段一部分参数例如:
required
,max_length
与Django-Form相同。而另一部属性比如code
中的style
是用于Drf的bBrowserAPI(后面章节会使用到)。 - 第二部分,override
create
和update
方法(这是必须的),在POST和PUT将调用serizlizer.save()
,此时create和update会生效。 -
create
接受的是多个命名参数(每个参数都是model实例的一个字段),而validated_data是dict,所以对其进行了拆包。 -
update
会传入model的实例和序列化验证后的数据,由于部分字段不会被序列化检验(不在validated_data
中),所以将实例中字段的值作为默认值。最后要返回被保存的实例。
-
源码部分
- 如果不override
create
和update
方法,就会报错。下面是serializer.BaseSerializer
的源码。
- 什么时候调用
create
和update
方法,下面是serializer.save
的源码
由源码可知,如果初始化时传递了instance
就调用update
,否则调用create
。
-
后续
这些代码看起来是没有必要(大部分代码都是固定的,比如override
create
方法),但是目前我们先保持这种清晰的声明方式,在之后将使用ModelSerializer
减少代码量。
4. 序列化与反序列化
-
打开交互界面(建议安装ipython)
python manage.py shell
-
增加两个model实例
from snippets.models import Snippet
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
-
序列化
- 创建序列化实例
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 4, 'title': '', 'code': 'print"hello, world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
type(serializer.data)
# rest_framework.utils.serializer_helpers.ReturnDict
序列化的数据类型。
从上述测试以及源码可知,序列化实例中存放的数据仍是
dict
(仍是Python原生的数据结构),也就是说序列化仅仅完成了筛选,返回了一个筛选过后的dict
,下一步使用哪种数据格式(Json
还是Xml
或者其他类型。)进行渲染与序列化无关。
- 使用Json数据格式
from rest_framework.renderers import JSONRenderer
content = JSONRenderer().render(serializer.data)
content
# b'{"id":4,"title":"","code":"print\\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'
type(content)
# bytes
经过Json化后dict
变成了bytes
,但是可以看到所有字符串都被双引号包裹,即Json的格式。
-
反序列化
从bytes
再变回dict
。
- 先将字节变成可操作的字节流
- six是Py2和Py3的兼容库。django将可兼容的工具放入了
utils.six
中。 - 解析后字节流(Json格式的)又变回了
dict
。
from rest_framework.parsers import JSONParser
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parser(stream)
type(data) # dict
data
# {'id': 4,'title': '', 'code': 'print"hello, world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
-
转为序列化实例
- 通过从JSON中解析的data(类型是
dict
)构建一个序列化实例。 - 验证通过的情况下,使用
.save()
进行存储。
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>
-
序列化与反序列化的流程
-
序列化多个Model实例
增加参数many=True
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([...])...OrderedDict([...])]
5. 使用ModelSerializer
就像Django中Form有ModelForm一样,Serizlizer也有ModelSerilizer。
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
ModelSerializer主要完成了两件事情:
- 将Model中的field根据元类中的fields指定的字段名转为序列的字段。(下面shell中的测试显示了自动转化的field的具体代码)
- 帮助我们实现了create和update方法(不用再重复的完成同样的工作)。
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')...
6. 完成View部分
由于本章是序列化的章节,所以使用的是Django的view系统。
-
完成list(GET)+create(POST)部分
在snippet/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@csrf_exempt
def snippet_list(request):
"""list & create"""
# 获取所有snippets
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
# drf的request对django的request进行了封装,不需要使用request.POST
# 1. JSON -> dict
# 2. 序列化(form功能)
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
# 1. 如果验证成功,就存储数据,并返回数据+201(创建成功)
# 2. 验证失败,返回错误信息+400(语义有误)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
- 先使用装饰器(
@csrf_exempt
)取消防范csrf,因为前后端的web应用不再使用防范csrf。 - request在Drf中被封装了,增加了更多丰富的功能,比如不需要使用
request.POST
传递数据. - JsonResponse中只要第一个参数传递的不是
dict
,就需要增加参数safe=False
。(具体原因可看源码)
-
完成retrieve,update,delete部分
- 状态码204代表成功但不会返回数据
from django.http import HttpResponse
@csrf_exempt
def snippet_detail(request, pk):
"""retrieve, update, delete"""
# 获取对应id的snippet,没有就返回404(没找到)
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
# retrieve
serializer = SnippetSerializer(snippet)
return JsonResponse(serializer.data, status=200)
elif request.method == 'PUT':
# update
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=200)
return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
# 204不返回内容
return HttpResponse(status=204)
-
总结五种方法的操作流程
-
未完善的地方
没有考虑传入错误的Json格式或者访问了view无法处理的HTTP-METHOD,在这些情况实际上应该返回status-500("server error")。
7. 完成Url部分
这里的正则表达式的书写参照django中默认生成的admin的url配置方式:
- 在项目级的
urls.py
中,采用url(r'^应用名/', include('应用名.urls'))
。 - 应用的
urls.py
中,采用url(r'^$')
配置list
和create
,采用url(r'^(?P<pk>[0-9]+)/$')
配置retrive
,put
和delete
。
-
完成
snippets/urls.py
部分
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^$', views.snippet_list),
url(r'^(?P<pk>[0-9]+)/$', views.snippet_detail)
]
-
完成
drf_tutorial/urls.py
部分
from django.conf.urls import include
urlpatterns = [
url(r'^admin/', admin.site.urls), # 默认生成的
url(r'^snippets/', include('snippets.urls'))
]
8. 测试接口
- 运行
python manage.py runserver
- 主要测试下面两个接口:
http://127.0.0.1:8000/snippets/
-
http://127.0.0.1:8000/snippets/2/
(测试这个Url若有正则匹配报错需仔细检查url中配置是否错误)
- 可有两种供选择的测试方式:
- 在浏览器中测试
需要安装JsonView来更好的观察Json数据(Chrome)。 - 在终端中测试
pip install httpie
直接在终端中输入http 待测试的url
- 在浏览器中测试