Django drf 序列化

2022-03-02 14:33 更新

介紹

本教程將介紹如何創(chuàng)建一個(gè)簡(jiǎn)單的收錄代碼高亮展示的Web API。這個(gè)過程中, 將會(huì)介紹組成Rest框架的各個(gè)組件,并讓你全面了解各個(gè)組件是如何一起工作的。

這個(gè)教程是相當(dāng)深入的,所以你應(yīng)該在開始之前準(zhǔn)備些啤酒和花生毛豆什么的。如果你只是想來個(gè)快速預(yù)覽,那你應(yīng)該查看quickstart文檔。

注意:本教程的代碼可以在Github的tomchristie/rest-framework-tutorial庫(kù)中找到。完整的實(shí)現(xiàn)版本可以在線查看作為一個(gè)沙盒版本進(jìn)行測(cè)試。

創(chuàng)建一個(gè)新環(huán)境

在做其他事情之前,我們要用virtualenv創(chuàng)建一個(gè)新的虛擬環(huán)境。這樣就能確保我們的包配置與我們正在開展的任何其他項(xiàng)目保持良好的隔離。

virtualenv env
source env/bin/activate

現(xiàn)在我們?cè)谝粋€(gè)virtualenv環(huán)境中,我們可以安裝我們需要的包了。

pip install django
pip install djangorestframework
pip install pygments  # 代碼高亮插件

注意: 要隨時(shí)退出virtualenv環(huán)境,只需輸入deactivate。有關(guān)更多信息,請(qǐng)參閱virtualenv documentation文檔。

正式開始

好了,我們現(xiàn)在要開始寫代碼了。 首先,我們來創(chuàng)建一個(gè)新的項(xiàng)目。

cd ~
django-admin.py startproject tutorial
cd tutorial

完成上面的步驟以后我們要?jiǎng)?chuàng)建一個(gè)用于創(chuàng)建簡(jiǎn)單Web API的app。

python manage.py startapp snippets

我們需要將新建的snippetsapp和rest_frameworkapp添加到INSTALLED_APPS。讓我們編輯tutorial/settings.py文件:

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

請(qǐng)注意如果你使用的Django版本低于1.9,你需要使用snippets.apps.SnippetsConfig替換snippets。

好了,我們的準(zhǔn)備工作做完了。

創(chuàng)建一個(gè)model

為了實(shí)現(xiàn)本教程的目的,我們將開始創(chuàng)建一個(gè)用于存儲(chǔ)代碼片段的簡(jiǎn)單的Snippet model。然后繼續(xù)編輯snippets/models.py文件。注意:優(yōu)秀的編程實(shí)踐都會(huì)添加注釋。雖然你將在本教程代碼的存儲(chǔ)庫(kù)版本中找到它們,但我們?cè)诖撕雎粤怂鼈儯瑢W⒂诖a本身。

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

我們還需要為我們的代碼段模型創(chuàng)建初始遷移(initial migration),并首次同步數(shù)據(jù)庫(kù)(migrate)。

python manage.py makemigrations snippets
python manage.py migrate

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

開發(fā)我們的Web API的第一件事是為我們的Web API提供一種將代碼片段實(shí)例序列化和反序列化為諸如json之類的表示形式的方式。我們可以通過聲明與Django forms非常相似的序列化器(serializers)來實(shí)現(xiàn)。 在snippets的目錄下創(chuàng)建一個(gè)名為serializers.py文件,并添加以下內(nèi)容。

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):
        """
        根據(jù)提供的驗(yàn)證過的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Snippet`實(shí)例。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        根據(jù)提供的驗(yàn)證過的數(shù)據(jù)更新和返回一個(gè)已經(jīng)存在的`Snippet`實(shí)例。
        """
        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()方法定義了在調(diào)用serializer.save()時(shí)如何創(chuàng)建和修改完整的實(shí)例。

序列化器類與Django Form類非常相似,并在各種字段中包含類似的驗(yàn)證標(biāo)志,例如required,max_length和default。

字段標(biāo)志還可以控制serializer在某些情況下如何顯示,比如渲染HTML的時(shí)候。上面的{'base_template': 'textarea.html'}標(biāo)志等同于在Django Form類中使用widget=widgets.Textarea。這對(duì)于控制如何顯示可瀏覽器瀏覽的API特別有用,我們將在本教程的后面看到。

我們實(shí)際上也可以通過使用ModelSerializer類來節(jié)省時(shí)間,就像我們后面會(huì)用到的那樣。但是現(xiàn)在我們還繼續(xù)使用我們明確定義的serializer。

使用序列化類

在我們進(jìn)一步了解之前,我們先來熟悉使用我們新的Serializer類。輸入下面的命令進(jìn)入Django shell。

python manage.py shell

好的,像下面一樣導(dǎo)入幾個(gè)模塊,然后開始創(chuàng)建一些代碼片段來處理。

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

我們現(xiàn)在已經(jīng)有幾個(gè)片段實(shí)例了,讓我們看一下將其中一個(gè)實(shí)例序列化。

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

此時(shí),我們將模型實(shí)例轉(zhuǎn)換為Python原生數(shù)據(jù)類型。要完成序列化過程,我們將數(shù)據(jù)轉(zhuǎn)換成json。

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

反序列化是類似的。首先我們將一個(gè)流(stream)解析為Python原生數(shù)據(jù)類型...

from django.utils.six import BytesIO

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

...然后我們要將Python原生數(shù)據(jù)類型恢復(fù)成正常的對(duì)象實(shí)例。

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和表單(forms)是多么相似。當(dāng)我們開始使用我們的序列化類編寫視圖的時(shí)候,相似性會(huì)變得更加明顯。

我們也可以序列化查詢結(jié)果集(querysets)而不是模型實(shí)例。我們只需要為serializer添加一個(gè)many=True標(biāo)志。

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類中重復(fù)了很多包含在Snippet模型類(model)中的信息。如果能保證我們的代碼整潔,那就更好了。

就像Django提供了Form類和ModelForm類一樣,REST framework包括Serializer類和ModelSerializer類。

我們來看看使用ModelSerializer類重構(gòu)我們的序列化類。再次打開snippets/serializers.py文件,并將SnippetSerializer類替換為以下內(nèi)容。

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

序列一個(gè)非常棒的屬性就是可以通過打印序列化器類實(shí)例的結(jié)構(gòu)(representation)查看它的所有字段。

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類并不會(huì)做任何特別神奇的事情,它們只是創(chuàng)建序列化器類的快捷方式:

  • 一組自動(dòng)確定的字段。
  • 默認(rèn)簡(jiǎn)單實(shí)現(xiàn)的create()和update()方法。

使用我們的序列化(Serializer)來編寫常規(guī)的Django視圖(views)

讓我們看看如何使用我們新的Serializer類編寫一些API視圖。目前我們不會(huì)使用任何REST框架的其他功能,我們只需將視圖作為常規(guī)Django視圖編寫。

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

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的根視圖支持列出所有現(xiàn)有的snippet或創(chuàng)建一個(gè)新的snippet。

@csrf_exempt
def snippet_list(request):
    """
    列出所有的code snippet,或創(chuàng)建一個(gè)新的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)

請(qǐng)注意,因?yàn)槲覀兿M軌驈牟痪哂蠧SRF令牌的客戶端對(duì)此視圖進(jìn)行POST,因此我們需要將視圖標(biāo)記為csrf_exempt。這不是你通常想要做的事情,并且REST框架視圖實(shí)際上比這更實(shí)用的行為,但它現(xiàn)在足夠達(dá)到我們的目的。

我們也需要一個(gè)與單個(gè)snippet對(duì)象相應(yīng)的視圖,并且用于獲取,更新和刪除這個(gè)snippet。

@csrf_exempt
def snippet_detail(request, pk):
    """
    獲取,更新或刪除一個(gè) 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)建一個(gè)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),
]

在4.0以上版本的Django中,需要把url()換成path()

我們也需要在根URL配置tutorial/urls.py文件中,添加我們的snippet應(yīng)用的URL。

from django.conf.urls import url, include

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

值得注意的是,目前我們還沒有正確處理好幾個(gè)邊緣事項(xiàng)。如果我們發(fā)送格式錯(cuò)誤的json,或者如果使用視圖不處理的方法發(fā)出請(qǐng)求,那么我們最終會(huì)出現(xiàn)一個(gè)500“服務(wù)器錯(cuò)誤”響應(yīng)。不過,現(xiàn)在這樣做。

測(cè)試在我們Web API進(jìn)行第一次嘗試

現(xiàn)在為我們的snippets啟動(dòng)一個(gè)服務(wù)器。

退出所有的shell...

quit()

...啟動(dòng)Django的開發(fā)服務(wù)器。

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.

在另一個(gè)終端窗口中,我們可以測(cè)試服務(wù)器。

我們可以使用curlhttpie測(cè)試我們的服務(wù)器。Httpie是用Python編寫的用戶友好的http客戶端,我們安裝它。

你可以使用pip來安裝httpie:

pip install httpie

最后,我們可以得到所有snippet的列表:

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來獲取特定的snippet:

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"
}

同樣,你可以通過在網(wǎng)絡(luò)瀏覽器中訪問這些URL來顯示相同??的json。

我們現(xiàn)在的位置

我們到目前為止做得不錯(cuò),我們有一個(gè)與Django的Forms API非常相似序列化API,和一些常規(guī)的Django視圖。

現(xiàn)在,我們的API視圖除了服務(wù)json響應(yīng)外,不會(huì)做任何其他特別的東西,并且有一些錯(cuò)誤我們?nèi)匀恍枰逄幚?,但是它仍是一個(gè)可用的Web API。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)