Django REST Framework 教程1

Tutorial 1: 序列化 Serialization

src

1. 设置一个新的环境

在我们开始之前, 我们首先使用virtualenv要创建一个新的虚拟环境,以使我们的配置和我们的其他项目配置彻底分开。

$mkdir ~/env
$virtualenv  ~/env/tutorial
$source ~/env/tutorial/bin/avtivate

现在我们处在一个虚拟的环境中,开始安装我们的依赖包

$pip install django
$pip install djangorestframework
$pip install pygments   ////使用这个包,做代码高亮显示

需要退出虚拟环境时,运行deactivate。更多信息,virtualenv document

2. 开始

环境准备好只好,我们开始创建我们的项目

$ cd ~
$ django-admin.py startproject tutorial
$ cd tutorial

项目创建好后,我们再创建一个简单的app

$python manage.py startapp snippets

我们使用sqlite3来运行我们的项目tutorial,编辑tutorial/settings.py, 将数据库的默认引擎engine改为sqlite3, 数据库的名字NAME改为tmp.db

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'tmp.db',
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

同时更改settings.py文件中的INSTALLD_APPS,添加我们的APP snippetsrest_framework

INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets',
)

tutorial/urls.py中,将snippets app的url包含进来

urlpatterns = patterns('',
    url(r'^', include('snippets.urls')),
)

3. 创建Model

这里我们创建一个简单的snippets model,目的是用来存储代码片段。

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, 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',)

完成model时,记得sync下数据库

python manage.py syncdb

4. 创建序列化类

我们要使用我们的web api,要做的第一件事就是序列化和反序列化, 以便snippets实例能转换为可表述的内容,例如json. 我们声明一个可有效工作的串行器serializer。在snippets目录下面,该串行器与django 的表单形式很类似。创建一个serializers.py ,并将下面内容拷贝到文件中。

from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet

class SnippetSerializer(serializers.Serializer):
    pk = serializers.Field()  # Note: `Field` is an untyped read-only field.
    title = serializers.CharField(required=False,
                                  max_length=100)
    code = serializers.CharField(widget=widgets.Textarea,
                                 max_length=100000)
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES,
                                       default='python')
    style = serializers.ChoiceField(choices=models.STYLE_CHOICES,
                                    default='friendly')

    def restore_object(self, attrs, instance=None):
        """
        Create or update a new snippet instance.
        """
        if instance:
            # Update existing instance
            instance.title = attrs['title']
            instance.code = attrs['code']
            instance.linenos = attrs['linenos']
            instance.language = attrs['language']
            instance.style = attrs['style']
            return instance

        # Create new instance
        return Snippet(**attrs)

该序列化类的前面部分,定义了要序列化和反序列化的类型,restore_object 方法定义了如何通过反序列化数据,生成正确的对象实例。

Notice that we can also use various attributes that would typically be used on form fields, such as widget=widgets.Textarea. These can be used to control how the serializer should render when displayed as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.

我们也可以使用ModelSerializer来快速生成,后面我们将节省如何使用它。

5. 使用 Serializers

在我们使用我们定义的SnippetsSerializers之前,我们先熟悉下Snippets.

$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='print "hello, world"\n')
snippet.save()

我们现在获得了一个Snippets的实例,现在我们对他进行以下序列化

serializer = SnippetSerializer(snippet)
serializer.data
# {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

这时,我们将该实例转成了python原生的数据类型。下面我们将该数据转换成json格式,以完成序列化:

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

反序列化也很简单,首先我们要将一个输入流(content),转换成python的原生数据类型

import StringIO

stream = StringIO.StringIO(content)
data = JSONParser().parse(stream)

然后我们将该原生数据类型,转换成对象实例

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.object
# <Snippet: Snippet object>

注意这些API和django表单的相似处。这些相似点, 在我们讲述在view中使用serializers时将更加明显。

We can also serialize querysets instead of model instances. To do so we simply add a many=True flag to the serializer arguments.

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}]

6. 使用 ModelSerializers

SnippetSerializer使用了许多和Snippet中相同的代码。如果我们能把这部分代码去掉,看上去将更佳简洁。

类似与django提供Form类和ModelForm类,Rest Framework也包含了Serializer 类和 ModelSerializer类。

打开snippets/serializers.py ,修改SnippetSerializer类:

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

7. 通过Serializer编写Django View

让我们来看一下,如何通过我们创建的serializer类编写django view。这里我们不使用rest framework的其他特性,仅编写正常的django view。

我们创建一个HttpResponse 子类,这样我们可以将我们返回的任何数据转换成json

snippet/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 it's content into JSON.
    """
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

我们API的目的是,可以通过view来列举全部的Snippet的内容,或者创建一个新的snippet

@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)
        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)
        else:
            return JSONResponse(serializer.errors, status=400)

注意,因为我们要通过client向该view post一个请求,所以我们要将该view 标注为csrf_exempt, 以说明不是一个CSRF事件。

Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as csrf_exempt. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.

我们也需要一个view来操作一个单独的Snippet,以便能更新/删除该对象。

@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)
        else:
            return JSONResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

将views.py保存,在Snippets目录下面创建urls.py,添加以下内容:

urlpatterns = patterns('snippets.views',
    url(r'^snippets/$', 'snippet_list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail'),
)

注意我们有些边缘事件没有处理,服务器可能会抛出500异常。

It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed json, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now.

8. 测试

现在我们启动server来测试我们的Snippet。

在python mange.py shell终端下执行(如果前面进入还没有退出)

quit()

执行下面的命令, 运行我们的server:

python manage.py runserver

Validating models...

0 errors found
Django version 1.4.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

新开一个terminal来测试我们的server

序列化:

curl http://127.0.0.1:8000/snippets/
>>[{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]

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

推荐阅读更多精彩内容