django rest framework快速入门第一章 序列化

第一章 序列化

写在前面:
本文翻译于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文件中把我们刚才创建的snippetsrest_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()时如何创建或者修改完整的实例。

这个序列化的类和DjangoForms类很类似,并且在各个字段上有一些类似的验证标志,如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了,为自己鼓掌吧!

下一阶段我们将来讲一讲如何改进,请关注我们第二章的内容。

谢谢各位阅读,并欢迎各位留下宝贵意见

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容