Django REST FrameWork中文教程1:序列化

介紹

本教程將涵蓋一個簡單的PasteBin1代碼高亮的Web API。整個過程,將逐一介紹REST framework的各個組成部件管呵,讓你全面理解,組件之間是如何整合的哺窄。

本教程有點深度捐下,所以在開始之前,你也許會需要幾片曲奇餅萌业,一杯你最愛的飲品邊吃邊看坷襟。 如果您只想快速瀏覽一下,則應該轉(zhuǎn)到快速入門文檔生年。

注意:本教程的代碼可以在GitHub的tomchristie / rest-framework-tutorial存儲庫中找到婴程。 完成的實現(xiàn)也在線作為沙箱版本進行測試,可以在這里找到抱婉。

搭建一個新的環(huán)境

在我們做任何事情之前档叔,我們先使用virtualenv創(chuàng)建一個新的虛擬環(huán)境。 這將確保我們的軟件包配置與我們正在進行的其他任何項目保持良好的隔離蒸绩。

virtualenv
envsource env/bin/activate

現(xiàn)在我們進入了virtualenv環(huán)境衙四,我們可以開始安裝我們需要的軟件支持包。

pip install django
pip install djangorestframework
pip install pygments  # 我們將使用這個讓代碼突出顯示</pre>

注意:想要隨時退出virtualenv環(huán)境患亿,只需輸入deactivate传蹈。欲了解更多信息,請參閱virtualenv文檔窍育。

準備開始

好的卡睦,我們準備好了編碼。 首先漱抓,讓我們創(chuàng)建一個新的項目(project)來處理表锻。

cd ~
django-admin.py startproject tutorial
cd tutorial

然后,我們可以創(chuàng)建一個app應用程序乞娄,來創(chuàng)建一個簡單的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士骤。

好的,我們準備好了蕾域。

創(chuàng)建一個可以使用的模型(model)

處于教程的設計考慮拷肌,我們首先創(chuàng)建一個簡單Snippet模型。用來存儲相關(guān)代碼旨巷,然后編輯snippets/models.py文件巨缘。注意:良好的編程實踐會有注釋。 盡管您可以在本教程的示范代碼中找到注釋采呐,但是我們在此省略注釋若锁。

Python

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

我們還需要為snippet模型創(chuàng)建數(shù)據(jù)的表,將模型同步到數(shù)據(jù)庫中斧吐,實現(xiàn)初始的遷移(migration)又固。

python manage.py makemigrations snippets
python manage.py migrate

創(chuàng)建一個序列化Serializer類

我們的 Web API 將開始于,為代碼片段的實例(instances)提供序列化和反序列化的途徑煤率,使之可以轉(zhuǎn)化為仰冠,某種表現(xiàn)形式如json 。我們可以借助聲明序列器(serializer)來實現(xiàn)涕侈,類似于Django表單(form)的運作方式沪停。在snippets路徑下,創(chuàng)建文件serializers.py 并以下內(nèi)容裳涛。

Python

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES

class SnippetSerializer(serializers.Serializer):
    # 每一個表都可以建一個serializer木张,類似Django的Form  專門用于json
    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):
        """
        傳入驗證過的數(shù)據(jù), 創(chuàng)建并返回`Snippet`實例。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        傳入驗證過的數(shù)據(jù), 更新并返回已有的`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

序列器(serializer)類的第一部分舷礼,告訴REST框架,哪些字段(field)郊闯,需要被序列化/反序列化妻献。create()update() 方法,定義了如何創(chuàng)建和修改团赁,一個有內(nèi)容的實例對象育拨。這兩個方法會在運行serializer.save()時,被調(diào)用欢摄。

序列器類非常類似Django的 Form 類熬丧,在多個字段中,也包含了類似的驗證標識(validation flags)怀挠,如 required 析蝴,max_lengthdefault害捕。

字段標識(flag)也能,控制序列器闷畸,在特定情況下尝盼,是如何呈現(xiàn)(displayed)的,比如需要渲染(rendering)成HTML佑菩。上面的 {'base_template': 'textarea.html'}標識盾沫,相當于在Django的 Form 類中使用 widget=widgets.Textarea。這尤其在控制可視化API如何來呈現(xiàn)時殿漠,特別有用疮跑。我們在后面的教程中,會看到這點凸舵。

事實上,一會我們可以看到失尖,如何使用 ModelSerializer 類啊奄, 來節(jié)省一些時間。但現(xiàn)在掀潮,我們會保持序列器中菇夸,每個字段的清晰定義。

使用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

# 創(chuàng)建數(shù)據(jù)
snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print "hello, world"\n')
snippet.save()

現(xiàn)在我們有幾個择诈,可用的代碼片段實例了。讓我們看看出皇,如何來序列化羞芍,其中一個實例。

###
該代碼是把剛剛保存的數(shù)據(jù)snippet對象郊艘,經(jīng)過序列化保存成一個字典
     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'}

此刻荷科,我們將模型實例,轉(zhuǎn)化成了Python的原生數(shù)據(jù)類型(native datatypes)纱注。要完成序列化的流程畏浆,我們將data渲染成json

# 將字典轉(zhuǎn)換成json格式
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

反序列化是相似的狞贱。 首先刻获,我們將一個流解析為Python數(shù)據(jù)類型

# 將json轉(zhuǎn)換成字典格式
from django.utils.six import BytesIO

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

然后我們將該原生數(shù)據(jù)類型,轉(zhuǎn)換成對象實例斥滤。

serializer = SnippetSerializer(data=data)
serializer.is_valid()    # 驗證數(shù)據(jù)是否符合要求
# True
serializer.validated_data    # 驗證后的數(shù)據(jù)
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()    # 保存數(shù)據(jù)
# <Snippet: Snippet object>

注意API的工作形式是如此的相似将鸵。這種重復性的相似勉盅,會在我們的視圖(view)中,用到序列器的時候顶掉,變得更加的明顯草娜。

除了模型實例,我們也可以將queryset序列化痒筒。只需在序列器的參數(shù)中加入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 類,重構(gòu)我們的序列器巷嚣。再次打開 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類不會做任何特別神奇的事情坝茎,它們只是創(chuàng)建序列化器類的快捷方式:

  • 自動地聲明了一套字段

  • 默認的實現(xiàn)了create()update()方法

使用我們的Serializer編寫正常的Django視圖

來看看如何使用新建的序列器(Serializer)類來編寫一些API視圖涤姊。到此為止,我們還沒有使用過REST framework其他的特性景东,我們只是編寫一個普通的Django視圖砂轻。

我們將從,創(chuàng)建一個HttpResponse的子類開始斤吐,這個子類會將任何data渲染并返回為json搔涝。

編輯snippets/views.py文件,并添加以下內(nèi)容和措。

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庄呈,將會成為一個視圖,顯示所有現(xiàn)存的代碼片段派阱,或創(chuàng)建一個新的代碼片段诬留。

Python

@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()
            # serializer.data 數(shù)據(jù)創(chuàng)建成功后所有數(shù)據(jù)
            return JsonResponse(serializer.data, status=201)
        # serializer.errors 錯誤信息
        return JsonResponse(serializer.errors, status=400)

注意,因為我們需要POST數(shù)據(jù),到這個視圖的客戶端文兑,并沒有CSRF令牌(token)盒刚,所以我們需要為該視圖標記為csrf_exempt 。你平時不會做這種事绿贞,實際上因块,相比起這個,REST framework 的視圖有著更加合理的行為籍铁,但現(xiàn)在我們會這么操作涡上。

我們也需要一個視圖,來響應某個單獨的代碼片段拒名,并且可以獲取吩愧,更新和刪除這個片段。

Python

@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)

最后增显,我們需要注冊這些視圖雁佳。創(chuàng)建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 文件的根url配置(root urlconf)中同云,來包含我們的snippets app的URLs甘穿。

from django.conf.urls import url, include

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

需要注意的是,此刻梢杭,有一些邊緣事件(edge cases),我們沒有相應的處理秸滴。如果我們發(fā)送雜亂的 json武契, 或一個請求使用了一種請求方法,是我們視圖沒有涵蓋的(如modify)荡含,那么我們會出現(xiàn)500 “server error”的響應(response)咒唆。總之释液,現(xiàn)在我們暫時這么做全释。

測試我們在Web API上的第一次訪問

現(xiàn)在我們可以啟動一個服務我們的代碼片段的示例服務器。

退出Django shell...

quit()

并啟動Django的開發(fā)服務器误债。

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封字。

我們現(xiàn)在在哪

目前為止,我們做得還行,我們做的序列化API感覺跟Django的Form API 比較相似阔籽,并且我們做了一些普通的Django視圖流妻。

我們的API視圖,現(xiàn)在還沒做啥特別的事情笆制。除了響應了json之外绅这,還有一些沒能處理的邊緣事件,但至少還是個能用的Web API项贺。

我們將在本教程的第2部分中看到我們?nèi)绾伍_始改進事情君躺。

原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市开缎,隨后出現(xiàn)的幾起案子棕叫,更是在濱河造成了極大的恐慌,老刑警劉巖奕删,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俺泣,死亡現(xiàn)場離奇詭異,居然都是意外死亡完残,警方通過查閱死者的電腦和手機伏钠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谨设,“玉大人熟掂,你說我怎么就攤上這事≡穑” “怎么了赴肚?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長二蓝。 經(jīng)常有香客問我誉券,道長,這世上最難降的妖魔是什么刊愚? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任踊跟,我火速辦了婚禮,結(jié)果婚禮上鸥诽,老公的妹妹穿的比我還像新娘商玫。我一直安慰自己,他們只是感情好牡借,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布决帖。 她就那樣靜靜地躺著,像睡著了一般蓖捶。 火紅的嫁衣襯著肌膚如雪地回。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音刻像,去河邊找鬼畅买。 笑死,一個胖子當著我的面吹牛细睡,可吹牛的內(nèi)容都是我干的谷羞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼溜徙,長吁一口氣:“原來是場噩夢啊……” “哼湃缎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢壹,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤嗓违,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后图贸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹂季,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年疏日,在試婚紗的時候發(fā)現(xiàn)自己被綠了偿洁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡沟优,死狀恐怖涕滋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠阁,我是刑警寧澤何吝,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站鹃唯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓣喊。R本人自食惡果不足惜坡慌,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藻三。 院中可真熱鬧洪橘,春花似錦、人聲如沸棵帽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逗概。三九已至弟晚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卿城。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工枚钓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瑟押。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓搀捷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親多望。 傳聞我的和親對象是個殘疾皇子嫩舟,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容