第一章 序列化
寫在前面:
本文翻譯于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文件中把我們剛才創建的snippets
和rest_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()
時如何創建或者修改完整的實例。
這個序列化的類和Django的Forms類很類似,并且在各個字段上有一些類似的驗證標志,如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了,為自己鼓掌吧!
下一階段我們將來講一講如何改進,請關注我們第二章的內容。
謝謝各位閱讀,并歡迎各位留下寶貴意見