介紹
本教程將涵蓋一個簡單的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_length
和 default
害捕。
字段標識(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.
另起一個終端浸船,我們可以測試服務器。
我們可以使用curl或httpie來測試我們的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)绾伍_始改進事情君躺。