DRF嚮導之序列化

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

# 即遷移數據庫
python manage,py migrate 

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 方法定義瞭如何通過反序列化數據,生成正確的對象實例。



注意,我們還可以使用通常用於表單字段的各種屬性,例如widget=widgets.textarea。這些可用於控制序列化程序在顯示爲HTML表單時應如何呈現。這對於控制可瀏覽api的顯示方式特別有用,我們將在本教程後面看到。



我們也可以使用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時將更加明顯。



我們還可以序列化queryset而不是模型實例。爲此,我們只需向序列化程序參數添加一個many=True。

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事件。

請注意,因爲我們希望能夠從沒有csrf token的客戶端發佈到此視圖,所以我們需要將該視圖標記爲“csrf_exempt”。這不是您通常想要做的事情,而rest框架視圖實際上使用了比這更合理的行爲,但是現在就可以達到我們的目的了。

我們也需要一個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"}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章