[Django-REST-framework教程][01]序列化

介绍

本教程将涵盖一个简单的PasteBin1代码高亮的Web API。整个过程,将逐一介绍REST framework的各个组成部件,让你全面理解,组件之间是如何整合的。

本教程有点深度,所以在开始之前,你也许会需要几片曲奇饼,一杯你最爱的饮品边吃边看。 如果您只想快速浏览一下,则应该转到快速入门文档。

注意:本教程的代码可以在GitHub的tomchristie / rest-framework-tutorial存储库中找到。 完成的实现也在线作为沙箱版本进行测试,可以在这里找到。

搭建一个新的环境

在我们做任何事情之前,我们先使用virtualenv创建一个新的虚拟环境。 这将确保我们的软件包配置与我们正在进行的其他任何项目保持良好的隔离。

virtualenv

envsource env/bin/activate

现在我们进入了virtualenv环境,我们可以开始安装我们需要的软件支持包。

pip install django

pip install djangorestframework

pip install pygments  # 我们将使用这个让代码突出显示

注意:想要随时退出virtualenv环境,只需输入deactivate。欲了解更多信息,请参阅virtualenv文档

准备开始

好的,我们准备好了编码。 首先,让我们创建一个新的项目(project)来处理。

cd ~

django-admin.py startproject tutorial

cd tutorial

然后,我们可以创建一个app应用程序,来创建一个简单的Web API。

python manage.py startapp snippets

我们需要将我们的新建的snippets应用和rest_framework应用添加到INSTALLED_APPS。 我们来编辑tutorial/settings.py文件:

INSTALLED_APPS = (

    ...

    'rest_framework',

    'snippets.apps.SnippetsConfig',

)

请注意,如果你使用的Django <1.9,则需要更换snippets.apps.SnippetsConfig有snippets。

好的,我们准备好了。

创建一个可以使用的模型(model)

处于教程的设计考虑,我们首先创建一个简单Snippet模型。用来存储相关代码,然后编辑snippets/models.py文件。注意:良好的编程实践会有注释。 尽管您可以在本教程的示范代码中找到注释,但是我们在此省略注释。

Python

fromdjango.dbimportmodelsfrompygments.lexersimportget_all_lexersfrompygments.stylesimportget_all_stylesLEXERS=[itemforiteminget_all_lexers()ifitem[1]]LANGUAGE_CHOICES=sorted([(item[1][0],item[0])foriteminLEXERS])STYLE_CHOICES=sorted((item,item)foriteminget_all_styles())classSnippet(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)classMeta:ordering=('created',)本段代码来自 http://www.chenxm.cc/post/289.html

我们还需要为snippet模型创建数据的表,将模型同步到数据库中,实现初始的迁移(migration)。

python manage.py makemigrations snippets

python manage.py migrate

创建一个序列化Serializer类

我们的 Web API 将开始于,为代码片段的实例(instances)提供序列化和反序列化的途径,使之可以转化为,某种表现形式如 json 。我们可以借助声明序列器(serializer)来实现,类似于Django表单(form)的运作方式。在 snippets 路径下,创建文件 serializers.py 并以下内容。


fromrest_frameworkimportserializersfromsnippets.modelsimportSnippet,LANGUAGE_CHOICES,STYLE_CHOICESclassSnippetSerializer(serializers.Serializer):# 每一个表都可以建一个serializer,类似Django的Form  专门用于jsonid=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')defcreate(self,validated_data):"""

        传入验证过的数据, 创建并返回`Snippet`实例。

        """returnSnippet.objects.create(**validated_data)defupdate(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()returninstance本段代码来自 http://www.chenxm.cc/post/289.html

序列器(serializer)类的第一部分,告诉REST框架,哪些字段(field),需要被序列化/反序列化。create() 和 update() 方法,定义了如何创建和修改,一个有内容的实例对象。这两个方法会在运行 serializer.save() 时,被调用。

序列器类非常类似Django的 Form 类,在多个字段中,也包含了类似的验证标识(validation flags),如 required ,max_length 和 default。

字段标识(flag)也能,控制序列器,在特定情况下,是如何呈现(displayed)的,比如需要渲染(rendering)成HTML。上面的 {'base_template': 'textarea.html'} 标识,相当于在Django的 Form 类中使用 widget=widgets.Textarea。这尤其在控制可视化API如何来呈现时,特别有用。我们在后面的教程中,会看到这点。

事实上,一会我们可以看到,如何使用 ModelSerializer 类, 来节省一些时间。但现在,我们会保持序列器中,每个字段的清晰定义。

使用Serializer

在我们进一步了解之前,我们将熟悉使用我们的新的Serializer类。让我们进入Django shell。

python manage.py shell

进入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()

现在我们有几个,可用的代码片段实例了。让我们看看,如何来序列化,其中一个实例。

###

 该代码是把刚刚保存的数据snippet对象,经过序列化保存成一个字典

     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的原生数据类型(native datatypes)。要完成序列化的流程,我们将data渲染成json。

# 将字典转换成json格式

content = JSONRenderer().render(serializer.data)

content

# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

反序列化是相似的。 首先,我们将一个流解析为Python数据类型

# 将json转换成字典格式

from django.utils.six import BytesIO

stream = 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()    # 保存数据

注意API的工作形式是如此的相似。这种重复性的相似,会在我们的视图(view)中,用到序列器的时候,变得更加的明显。

除了模型实例,我们也可以将queryset序列化。只需在序列器的参数中加入 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')])]

使用ModelSerializers

在 SnippetSerializer 类中,重复了许多,在 Snippet 模型中的字段定义。如果我们能保持代码简洁,岂不是很好?

就像Django即提供了 Form 类,也提供了 ModelForm 类, REST framework也有 Serializer 类和 ModelSerializer类。

来看看如何,使用 ModelSerializer 类,重构我们的序列器。再次打开 snippets/serializers.py , 将SnippetSerializer 类替换为:

class SnippetSerializer(serializers.ModelSerializer):

    # ModelSerializer和Django中ModelForm功能相似

    # Serializer和Django中Form功能相似

    class Meta:

        model = Snippet

        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

序列化程序有个很好的特性,,您可以通过打印序列化的属性。查看序列器对象中所有的字段。在Django shell中(即 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')...

注意:ModelSerializer类不会做任何特别神奇的事情,它们只是创建序列化器类的快捷方式:

自动地声明了一套字段

默认的实现了create()和update()方法

使用我们的Serializer编写正常的Django视图

来看看如何使用新建的序列器(Serializer)类来编写一些API视图。到此为止,我们还没有使用过REST framework其他的特性,我们只是编写一个普通的Django视图。

我们将从,创建一个HttpResponse的子类开始,这个子类会将任何data渲染并返回为 json。

编辑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

我们API的根url,将会成为一个视图,显示所有现存的代码片段,或创建一个新的代码片段。

Python

@csrf_exemptdefsnippet_list(request):"""

    List all code snippets, or create a new snippet.

    """ifrequest.method=='GET':snippets=Snippet.objects.all()serializer=SnippetSerializer(snippets,many=True)returnJsonResponse(serializer.data,safe=False)elifrequest.method=='POST':data=JSONParser().parse(request)serializer=SnippetSerializer(data=data)ifserializer.is_valid():serializer.save()# serializer.data 数据创建成功后所有数据returnJsonResponse(serializer.data,status=201)# serializer.errors 错误信息returnJsonResponse(serializer.errors,status=400)本段代码来自 http://www.chenxm.cc/post/289.html

注意,因为我们需要POST数据,到这个视图的客户端,并没有CSRF令牌(token),所以我们需要为该视图标记为 csrf_exempt 。你平时不会做这种事,实际上,相比起这个,REST framework 的视图有着更加合理的行为,但现在我们会这么操作。

我们也需要一个视图,来响应某个单独的代码片段,并且可以获取,更新和删除这个片段。

Python

@csrf_exemptdefsnippet_detail(request,pk):"""

    Retrieve, update or delete a code snippet.

    """try:snippet=Snippet.objects.get(pk=pk)exceptSnippet.DoesNotExist:returnHttpResponse(status=404)ifrequest.method=='GET':serializer=SnippetSerializer(snippet)returnJsonResponse(serializer.data)elifrequest.method=='PUT':data=JSONParser().parse(request)serializer=SnippetSerializer(snippet,data=data)ifserializer.is_valid():serializer.save()returnJsonResponse(serializer.data)returnJsonResponse(serializer.errors,status=400)elifrequest.method=='DELETE':snippet.delete()returnHttpResponse(status=204)本段代码来自 http://www.chenxm.cc/post/289.html

最后,我们需要注册这些视图。创建snippets/urls.py文件:

from django.conf.urls import url

from snippets import views

urlpatterns = [

    url(r'^snippets/$', views.snippet_list),

    url(r'^snippets/(?P[0-9]+)/$', views.snippet_detail),

]

我们也需要,注册到 tutorial/urls.py 文件的根url配置(root urlconf)中,来包含我们的snippets app的URLs。

from django.conf.urls import url, include

urlpatterns = [

    url(r'^', include('snippets.urls')),

]

需要注意的是,此刻,有一些边缘事件(edge cases),我们没有相应的处理。如果我们发送杂乱的 json, 或一个请求使用了一种请求方法,是我们视图没有涵盖的(如modify),那么我们会出现500 “server error”的响应(response)。总之,现在我们暂时这么做。

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

现在我们可以启动一个服务我们的代码片段的示例服务器。

退出Django shell...

quit()

并启动Django的开发服务器。

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.

另起一个终端,我们可以测试服务器。

我们可以使用curlhttpie来测试我们的API 。Httpie是用Python编写的用户友好的http客户端。

您可以使用pip安装httpie:

pip install httpie

最后,我们可以得到所有片段的列表:

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来获取特定的代码段:

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"

}

同样,您可以通过在Web浏览器中访问这些URL来显示相同的json。

我们现在在哪

目前为止,我们做得还行,我们做的序列化API感觉跟Django的Form API 比较相似,并且我们做了一些普通的Django视图。

我们的API视图,现在还没做啥特别的事情。除了响应了json之外,还有一些没能处理的边缘事件,但至少还是个能用的Web API。

我们将在本教程的第2部分中看到我们如何开始改进事情。

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

推荐阅读更多精彩内容