python教程

1, 序列化 Serialization

建立一個新環境

在作其餘事以前,咱們會用virtualenv建立一個新的虛擬環境。這將確保咱們的包配置與咱們正在工做的其餘項目徹底隔離。html

virtualenv env          # 建立虛擬環境,命名: env
source env/bin/activate # 進入虛擬環境env

既然咱們已經在虛擬環境中,那麼咱們就能夠安裝咱們依賴的包了。python

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

開始

首先,咱們來建立一個新項目。sql

cd ~
django-admin.py startproject tutorial
cd tutorial

輸完以上命令,咱們就能夠建立一個應用,咱們將會用他來建立簡單的Web API。shell

python manage.py startapp snippets

咱們會添加一個新的snippets應用和rest_framework應用到INSTALLED_APPS。讓咱們編輯tutorial/settings.py文件:數據庫

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

Ok, 咱們準備下一步。django

建立一個 Model

爲了實現本教程的目的,咱們將建立一個簡單的Snippet模型,這個模型用來保存snippets代碼。開始編輯snippets/models.py文件。json

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模型建立一個初始遷移(initial migration),而後第一次同步數據庫。後端

python manage.py makemigrations snippets
python manage.py migrate

建立一個序列化類(Serializer class)

着手咱們的Web API,首先要作的是,提供一種將咱們的snippet實例序列化/反序列化成例如json這樣的表述形式。咱們能夠經過聲明序列來完成,這些序列與Django的表單(forms)工做類似。在snippets目錄建立一個新文件serializers.py,添加下列代碼。api

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):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        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 class)的第一部分定義了一些須要被序列化/反序列化字段。create()update()方法定義了在調用serializer.save()時成熟的實例是如何被建立和修改的。 序列化類(serializer class)與Django的表單類(Form class)很是類似,包括對各類字段有類似的確認標誌(flag),例如requiredmax_lengthdefault。 在某些狀況下,這些字段標誌也能控制序列應該怎麼表現,例如在將序列渲染成HTML時。{'base_template': 'textarea.html}'標誌至關於對Django表單(Form)類使用widget=widgets.Textarea。這對控制API的顯示尤爲有用,之後的教程將會看到。 事實上,之後咱們能夠經過使用ModelSerializer類來節約咱們的時間,可是如今爲了讓咱們序列化定義更清晰,咱們用Serializer類。瀏覽器

用序列化(Serializers)工做

在咱們深刻以前,咱們須要熟練使用新的序列化列(Serializer class)。然咱們開始使用Django命令行吧。

python manage.py shell

Okay,讓咱們寫一些snippets代碼來使序列化工做。

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

如今咱們已經有了一些snippet實例。讓咱們看看如何將其中一個實例序列化。

注: Model -> Serializer

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

如今,咱們已經將模型實例(model instance)轉化成Python原生數據類型。爲了完成實例化過程,咱們要將數據渲染成json。

注: Serializer -> JSON

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

反序列化也同樣。首先,咱們須要將流(stream)解析成Python原生數據類型...

注: stream -> json

from django.utils.six import BytesIO

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和表單(forms)是多麼類似啊。當咱們用咱們的序列寫視圖的時候,類似性會至關明顯。 除了將模型實例(model instance)序列化外,咱們也能序列化查詢集(querysets),只須要添加一個序列化參數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 框架包括了實例化(Serializer)類和模型實例化(ModelSerializer)類。 咱們來看看用ModelSerializer類建立的序列。再次打開snippets/serializers.py文件,用下面的代碼重寫SnippetSerializer類。

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

序列一個很是棒的屬性就是,你可以經過打印序列實例的結構(representation)查看它的全部字段。輸入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類並無作什麼有魔力的事情,它們僅僅是一個建立序列化類的快捷方式。

  • 一個自動決定的字段集合。
  • 簡單的默認create()update()方法的實現。

用咱們的序列化來寫常規的Django視圖

讓咱們看看,使用咱們新的序列化類,咱們怎麼寫一些API視圖。此刻,咱們不會使用REST框架的其餘特性,僅僅像寫常規Django視圖同樣。 經過建立HttpResponse的一個子類來開始,其中,咱們能夠用這個子類來渲染任何咱們返回的json數據。 編輯snippets/views.py文件,添加如下代碼。

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將是一個支持列出全部存在的snippets的視圖,或者建立一個新的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, 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()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

注意,由於咱們但願能夠從沒有CSRF token的客戶端POST數據到這個視圖,咱們須要標記這個視圖爲csrf_exempt。一般,你並不想這麼作,而且事實上REST框架視圖更實用的作法不是這樣的,可是目前來講,這足以到達咱們的目的。 咱們也須要一個與單個snippet對象相應的視圖,而且咱們使用這個視圖來讀取、更新或者刪除這個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)
        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),
]

咱們也須要在根url配置文件tutorial/urls.py中添加咱們的snippet應用URL。

from django.conf.urls import url, include

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

有一些當時咱們沒有正確處理的邊緣事件是沒有價值的。若是咱們發送不正確的json數據,或者若是咱們製造了一個視圖沒有寫處理的方法(method),那麼咱們會獲得500「服務器錯誤」的響應。固然,如今也會出現這個問題。

測試咱們Web API的第一次努力

如今咱們開始建立一個測試服務器來服務咱們的snippets應用。 退出命令行......

quit()

...而後啓動Django開發服務器。

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是一個面向用戶的很是友好的http客戶端,它是用Python寫的。讓咱們來安裝它。 你能夠經過pip來安裝httpie:

pip install httpie

最後,咱們來獲取一個包含全部snippets的列表:

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

類似地,你能夠經過在瀏覽器中訪問這些連接來得到相同的json數據。

咱們如今在哪

到目前爲止,咱們作的都很好,咱們已經得到一個序列化API,這和Django的表單API很是類似,而且咱們寫好了一些經常使用的Django視圖。 如今,咱們的API視圖除了服務於json外,不會作任何其餘特別的東西,而且有一些錯誤咱們仍然須要清理,可是它是一個可用的Web API。 咱們將會在本教程的第二部分改善這裏東西。

2, 請求與響應

從這開始,咱們將接觸REST框架的核心。讓咱們來介紹一系列必要的搭建模塊。

請求對象

REST框架介紹了一個請求(Request)對象,它擴展了常規的HttpResquest,而且,提供更靈活的請求解析。請求(Request)對象的核心功能是request.data屬性,這個屬性與request.POST類似,可是它對Web APIs更加有用。

request.POST # 只處理表單數據。只對'POST'方法起做用。
request.data # 能夠處理任意數據。對'POST','PUT'和'PATCH'方法起做用。

響應對象

REST 框架也介紹了Response對象,它是一類用未渲染內容和內容協商來決定正確的內容類型並把它返回給客戶端的模板響應(TemplateResponse)

return Response(data) # 根據客戶端的請求來渲染成指定的內容類型。

狀態碼

老是在你的視圖中用數字的HTTP狀態碼會更加容易理解,而且若是你用其餘錯誤代碼表示錯誤,就不太容易注意到了。REST框架爲每一個狀態碼`(status code)`提供更明確的標識符,例如在狀態`(status)`模型中的`HTTP_400_BAD_REQUEST`。用這些標識符代替純數字的HTTP狀態碼是很好的注意。

裝飾API視圖

REST框架提供兩個裝飾器,你能夠用它們來寫API視圖。

  • 1 @api_view裝飾器用在基於視圖的方法上。
  • 2 APIView類用在基於視圖的類上。 這些裝飾器提供一些功能,例如確保在你的視圖中接收Request對象,例如在你的Response對象中添加上下文,這樣咱們就能實現內容通訊。 這裏裝飾器也提供了一些行爲,例如在合適的時候返回405 Method Not Allowed響應,例如處理任何在訪問錯誤輸入的request.data時出現的解析錯誤(ParseError)異常。

結合在一塊兒

好了,讓咱們開始用這些新的組件寫一些視圖。 咱們再也不須要在咱們的視圖(views.py)中使用JSONResponse類,全部如今把它刪掉。一旦咱們這樣作了,咱們就能很快重建咱們的視圖。

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
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 Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

咱們的實例視圖是咱們以前例子的改良版。簡明瞭不少,而且目前的代碼和咱們使用Forms API很類似。咱們也用有意義的狀態碼標識符。 在views.py模塊中,有一個獨立的snippet視圖。

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

這對咱們來講應該很是熟悉,由於它與常規的Django視圖沒有什麼區別。 注意,咱們再也不明確打印咱們的對指定內容類型的請求或響應。request.data可以處理json請求,可是它也能處理其餘格式。類似地,雖然咱們能夠在響應對象中帶數據,但容許REST框架渲染響應成正確的內容類型。

在咱們的連接(URLs)後添加可選格式後綴

爲了利用咱們的響應內容再也不是單一格式的事實,咱們應該爲咱們的API尾部添加格式後綴。用格式後綴給咱們明確參考指定格式的URL,這意味着咱們的API可以處理像http://example.com/api/items/4/.json同樣的連接。 在視圖函數中添加一個format參數,像這樣:

def snippet_list(request, format=None):

def snippet_detail(request, pk, format=None):

如今能夠很快更新urls.py文件,在已經存在的URL中添加一個格式後綴模式(format_suffix_patterns)

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

咱們沒必要添加額外的URL模式,可是它給咱們簡單、清楚的方式渲染除特定的格式。

看看吧

和教程第一部分同樣,咱們要開始從命令行測試API。雖然咱們能在發送無效的請求時更穩當處理錯誤,可是如今一切都作的夠好了。 咱們能想以前同樣獲取全部的snippets列表。

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

咱們能控制咱們返回的響應格式,或者使用Accept響應頭。

http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

或者在URL後添加格式後綴:

http http://127.0.0.1:8000/snippets.json  # JSON 後綴
http http://127.0.0.1:8000/snippets.api   # 瀏覽用的 API 後綴

一樣地,咱們能夠控制咱們發送的請求格式,用Content-Type請求頭。

# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"

{
  "id": 3,
  "title": "",
  "code": "print 123",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"

{
    "id": 4,
    "title": "",
    "code": "print 456",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

你也能夠從瀏覽器打開API,經過訪問http://127.0.0.1:8000/snippets/。

Browsability

由於API是基於客戶端請求來選擇響應內容的類型,因此默認狀況下,在Web瀏覽器訪問資源時,API返回HTML格式的資源。這語序API返回徹底能夠網頁瀏覽的HTML。 有能夠網頁瀏覽API是很好的,這使開發和使用你的API更簡單,這也爲其餘想要查看和使用你的API的開發者大大下降了門檻。 關於可瀏覽API的特性和如何自定義可瀏覽API,請見可瀏覽API話題。

接下來要幹什麼?

在教程的第三部分,咱們基於視圖用類,而且看看普通的視圖咱們如何減小代碼。

3, 基於視圖的類(class based view)

除了能夠用基於視圖的函數(function based view)寫咱們的API,咱們也能夠用基於視圖的類。正如咱們所見,這是一個很是有利的模式,容許咱們重用一樣的功能,並幫助咱們使代碼緊湊。

用基於視圖的類重寫咱們的API

咱們將會想重寫一個基於視圖的類同樣重寫根視圖。這包括重構views.py文件。

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status


class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

到目前爲止,一切都很好。這和以前的狀況很類似,可是咱們已經很好地經過不一樣的HTTP方法區分。如今咱們也須要在views.py中更新實例視圖。

class SnippetDetail(APIView):
    """
    Retrieve, update or delete a snippet instance.
    """
    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

那看起來不錯。再次強調,這和基於視圖的函數很是類似。 咱們也須要用基於視圖的類重構咱們的urls.py

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.SnippetList.as_view()),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

好了,咱們作完了。若是你啓用開發服務器,那麼一切都和以前同樣。

使用混合(mixins)

使用基於視圖的類最大的一個好處是,它容許咱們快速建立可複用的行爲。咱們一直使用的create/retrieve/update/delete操做將和咱們建立的任何後端模型API視圖很是類似。這些廣泛的行爲是經過REST框架的混合類(mixin classes)實現的。 讓咱們看看如何經過混合類(mixin classes)組建視圖。下面是咱們的views.py模型。

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics

class SnippetList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

咱們會花一下子準確測試這裏發生了什麼。咱們使用GenericAPIView加上ListMOdelMixinCreatteModelMixin建立咱們的視圖。 基類提供核心功能,混合類提供.list().create()動做。而後咱們合適的動做綁定明確的getpost方法。到目前爲止,東西已經足夠簡單。

class SnippetDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

太像了。咱們用GenericAPIView類提供核心功能,添加混合(mixin),來提供.retrieve().update().destroy()動做。

使用基於視圖的通常類(generic class)

儘管咱們已經使用混合類(mixin classes)以比以前更少的代碼重寫了視圖,可是咱們能夠進一步深刻。REST框架提供一個已經混入通常視圖的集合,咱們能用他們來整理咱們的views.py模塊。

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

哇,如此簡潔。咱們的代碼看起來是如此簡潔、地道的Django。 接下來咱們要學習本教程的第四部分,在第四部分咱們會爲咱們的API處理受權(authentication)和權限(permissions)。

4, 受權(Authentication)與權限(Permissions)

當前,咱們的API沒有限制誰能編輯或刪除snippets代碼。咱們想要一些更高級的行爲以確保:

  • snippets數據老是與建立者聯繫在一塊兒。
  • 只有受權用戶才能建立snippets。
  • 只有snippet的建立者才能更新或者刪除它。
  • 沒有受權的請求應該只有只讀權限。

在咱們的模型中添加信息

咱們打算對咱們的Snippet模型類作些改變。首先,讓咱們添加幾個字段。其中一個字段將顯示出哪一個用戶建立裏snippet數據。另外一個字段將用於HTML代碼高亮。 將下面兩個字段添加到Snippet模型中,在snippets/models.py中。

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

咱們也須要確保當模型保存之後,咱們能夠看到高亮的字段。爲此咱們用pygments代碼高亮庫來造成高亮字段。 咱們須要一些額外的包:

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

而後給咱們的模型類添加.save()方法:

def save(self, *args, **kwargs):
    """
    Use the `pygments` library to create a highlighted HTML
    representation of the code snippet.
    """
    lexer = get_lexer_by_name(self.language)
    linenos = self.linenos and 'table' or False
    options = self.title and {'title': self.title} or {}
    formatter = HtmlFormatter(style=self.style, linenos=linenos,
                              full=True, **options)
    self.highlighted = highlight(self.code, lexer, formatter)
    super(Snippet, self).save(*args, **kwargs)

而後,咱們須要更細咱們的數據庫表。爲此,正常狀況下,咱們會建立數據庫遷移(database migration),可是就本教程來講,咱們只須要刪除原來的數據庫,而後從新建立便可。

rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

你可能也想要建立不一樣的用戶來測試API。最快的方式就是用createsuperuser命令。

python manage.py createsuperuser

爲咱們的用戶模型添加端點

既然咱們已經建立了多個用戶,那麼咱們最好將用戶添加到咱們的API。很容易建立一個新的序列。在serializers.py中添加;

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

由於'snippets'在用戶模型中是一個相反的關係,默認狀況下在使用ModelSerializer類時咱們不會包括,因此咱們須要手動爲用戶序列添加這個字段。 咱們須要添加在views.py中添加一些視圖。咱們想要爲用戶添加只讀視圖,因此咱們會使用基於視圖的通常類ListAPIViewRetrieveAPIView

from django.contrib.auth.models import User


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

確保文件中引入了UserSerializer類。

from snippets.serializers import UserSerializer

最後,咱們須要經過修改URL配置,將這些視圖添加進API。添加如下urls.py中。

url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

將用戶和Snippets鏈接起來

如今,若是咱們建立snippet數據,咱們沒辦法將用戶和snippet實例聯繫起來。雖然用戶不是序列表示的部分,可是它是請求的一個屬性。 咱們經過重寫snippet視圖的.perform_create()方法來作到,這個方法容許咱們修改如何保存實例,修改任何請求對象或者請求鏈接裏的信息。 在SnippetList視圖類中添加如下方法;

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

如今,咱們序列的create()方法將會另外傳入一個來自有效的請求數據的'owner'字段。

更新咱們的序列

既然已經將snippets和建立它們的用戶聯繫在一塊兒了,那麼咱們須要更新對應的SnippetSerializer。在serializers.py的序列定義(serializer definition)中添加如下字段:

owner = serializers.ReadOnlyField(source='owner.username')

注意;確保你將'owner'字段添加到內部類Meta的字段列表裏。 這個字段頗有趣。source參數控制哪一個屬性被用於構成一個字段,而且可以指出序列實例的任何屬性。它也能像上面同樣使用點標記(.),這種狀況下他會橫貫給定的屬性,就是咱們使用Django模板語言同樣。 咱們添加的字段是隱式ReadOnly類,與其餘類相反,如CharField,BooleanField,隱式ReadOnlyField老是隻讀的,用於序列化表示,但在數據非序列化時不能用於更新實例。這裏咱們也能夠用CharField(read_only=True)。

爲視圖添加須要的權限

snippets數據已經和用戶聯繫在一塊兒,咱們想確保只有受權的用戶能夠建立、更新和刪除snippet數據。 REST框架包括許多權限類(permission classes),咱們可使用這些權限類來如今視圖的訪問權限。這種狀況下,其中咱們須要IsAuthenticatedOrReadOnly,這個類確保受權請求有讀寫權限,而沒有受權的用戶只有只讀權限。 首先,在視圖模塊中引入如下代碼:

from rest_framework import permissions

接下來,將如下屬性添加到SnippetListSnippetDetail的視圖類中。

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

爲可瀏覽API(Browsable API)添加登陸

若是你打開瀏覽器並瀏覽API,你將會發現你沒法再建立新的snippets code,若是想要有建立的權限,須要登陸。 咱們能夠添加一個登陸視圖,經過編輯咱們的根路由urls.py文件。 將下列包導入到文件上方:

from django.conf.urls import include

在文件的末尾,將login和logout的路由配好。

urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls')),
]

url樣式的r'^api-auth/'部分實際上能夠是任何你想要的URL。惟一的限制就是include的連接必須使用'rest_framework'名字空間。如今若是你刷新瀏覽器頁面,你會看到右上角的'Login'連接。若是你用以前建立的用戶登陸,你就能夠再次寫snippets數據了。 一旦你建立snippets數據,瀏覽'/users/',而後你會發如今每一個用戶的'snippets'字段,顯示的內容包括與每一個用戶相關的snippets主鍵。

對象等級權限

雖然咱們真的想任何人都和一看見snippets數據,但也要確保只有建立snippet的用戶能夠修改或刪除他的snippet。 爲此,咱們須要建立自定義權限。 在snippets app中,建立一個新文件permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

而後編輯SnippetDetail視圖類中的permission_classes屬性,添加自定義權限。

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

確保引入了IsOwnerOrReadOnly類。

from snippets.permissions import IsOwnerOrReadOnly

如今,若是你再次打開瀏覽器,你會發現只有你登入,你才能刪除(DELETE)或更新(PUT)屬於你的snippet數據。

受權API

由於咱們的API有一系列權限,因此若是咱們想編輯任何snippets,咱們須要受權咱們的請求。咱們如今尚未任何受權類(authenticaions classes),因此默認狀況下只有SessionAuthenticationBasicAuthentication。 當咱們經過Web瀏覽器與API交互時,咱們能夠登陸,而後瀏覽器會話(session)將會提供必須的請求受權。 若是咱們經過程序與API交互,咱們須要爲每一個請求提供明確的受權證實。 若是咱們在沒有受權的狀況下建立一個snippet,那麼咱們會獲得下面的錯誤:

http POST http://127.0.0.1:8000/snippets/ code="print 123"

{
    "detail": "Authentication credentials were not provided."
}

爲了請求成功,咱們須要包含用戶名和密碼。

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"

{
    "id": 1,
    "owner": "admin",
    "title": "foo",
    "code": "print 789",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

總結

如今咱們已經在咱們的Web API上,爲咱們的系統用戶和snippet的建立者,添加了不少權限和端點。 在第五部分,咱們將會看怎麼咱們能夠經過爲咱們的高亮snippets建立HTML端點來將全部東西聯繫在一塊兒,而後在系統內用超連接將咱們的API聯繫起來。

5, 關係(Relationships)與超連接API(Hyperlinked APIs)

如今,用主鍵表明咱們API之間的關係。在這部分教程,咱們會用超連接改善API之間的關係。

爲咱們的API根建立一個端點

如今,咱們已經爲'snippets''users'設置了端點,可是咱們沒有爲咱們的API設置單獨的入口點。所以,咱們會一個基於方法的常規視圖和@api_view裝飾器來建立一個入口點。在你的snippets/views.py中添加:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

咱們會注意到兩件事:第一,咱們用了REST框架的reverse方法爲了返回高質量的URL;第二,URL格式是方便的名字標識符,咱們會在以後會在snippets/urls.py中聲明。

建立一個高亮的snippets端點

另外一件明顯的事就是,咱們的API缺少代碼高亮端點。 和咱們全部的API端點不同,咱們不想用JSON,而只是想用HTML顯示。REST框架提供兩種HTML渲染樣式,一種是用模板渲染處理HTML,另外一種是用預渲染HTML。第二種是咱們想要用的方式。 在建立代碼時,咱們須要考慮的是,高亮視圖在咱們使用的普通視圖中是不存在的。咱們不會返回一個對象實例,而是對象實例的一個屬性。 咱們會是使用基類表明實例,並建立咱們本身的.get()方法,而不是用普通的視圖。在你的snippets/views.py添加:

from rest_framework import renderers
from rest_framework.response import Response

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = (renderers.StaticHTMLRenderer,)

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

一般,咱們須要添加新的視圖到咱們的URL配置。而後,在snippest/urls.py中添加一個連接:

url(r'^$', views.api_root),

而後,爲高亮snippet添加一個url樣式:

url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),

爲咱們的API添加超連接

處理好實體之間的關係是Web API設計中極具挑戰性的方面之一。表明一種關係能夠有不少種方式:

  • 使用主鍵。
  • 在實體之間使用超連接。
  • 在相關的實體上使用獨一無二的slug。
  • 使用相關的實體的默認字符串。
  • 在父表述使用嵌套的實體。
  • 一些自定義的表述。 REST框架支持以上全部方式,都能適應正向或者反向關係,或者就行使用通常的外鍵同樣使用自定義的管理方式。 這種狀況下,咱們想要在實體之間使用超連接方式。爲了達到目的,咱們須要修改咱們的序列(serializers),以拓展HyperlinkedModelSerializer,不是使用已經存在的ModelSerializer。 如下是HyperlinkedModelSerializer不一樣於ModelSerializer的地方:
  • HyperlinkedModelSerializer默認不包括pk字段。
  • 它只包括一個url字段,使用HyperlinkedIndentityField
  • 關係使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField。 咱們能使用超連接快速重寫現存的序列。在snippets/serializers.py中添加:
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

    class Meta:
        model = Snippet
        fields = ('url', 'id', 'highlight', 'owner',
                  'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.HyperlinkedModelSerializer):
    snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)

    class Meta:
        model = User
        fields = ('url', 'id', 'username', 'snippets')

注意,咱們已經添加了一個新字段highlight。這個字段類型是和url同樣的,只是它指向snippet-highlighturl模式,而不是snippet-detailurl模式。 由於咱們已經包含了格式後綴的URL,如.json,因此咱們也須要在highlight字段指明,任何格式後綴超連接應該用.html後綴。

確保咱們的URL模式是有名字的

若是咱們想要超連接的API,那麼咱們要保證咱們給URL起了名字。讓咱們看看咱們須要命名哪一個連接。

  • 咱們API根指向user-listsnippet-list
  • 咱們的snippet序列包括一個指向snippet-highlight的字段。
  • 咱們的用戶血烈包括一個指向snippet-detail的字段。
  • 咱們的snippet和用戶序列包括url字段,這個字段默認指向'{model_name}-detail',這種狀況下,它是snippet-detailuser-detail。 在將那些名字加入咱們的URL配置(URLconf)後,咱們的snippets/urls.py應該是下面的樣子:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    url(r'^$', views.api_root),
    url(r'^snippets/$',
        views.SnippetList.as_view(),
        name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    url(r'^users/$',
        views.UserList.as_view(),
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        views.UserDetail.as_view(),
        name='user-detail')
])

添加分頁

用戶和snippet的列表視圖會返回不少實例,因此咱們想要給這些結果分頁,分頁後容許API客戶端訪問每一個單頁。 咱們能夠用分頁改變默認的列表風格,只要稍微修改tutorial/settings.py文件。添加下面設置:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

注意:REST框架的分頁設置(settings)是一個單獨的字典,叫'REST_FRAMEWORK',它能夠幫咱們區分項目中的其餘配置。 若是咱們須要,咱們能夠自定義分頁樣式,可是如今咱們只是用默認的。

瀏覽API

若是咱們打開瀏覽器訪問API,那麼你會發現你能夠經過下面的連接使用API。 你也能夠看見snippet實例的高亮(highlight)連接,這些連接會返回高亮HTML代碼。 在本教程的第六部分,咱們會用ViewSetsRouters來減小咱們API的代碼量。

視圖集(ViewSets)和路由(Routers)

REST框架包括對ViewSets的簡短描述,這可讓開發者把精力集中在構建狀態和交互的API模型,並且它能夠基於通常規範自動構建URL。 ViewSet類幾乎和View類同樣,除了他們提供像readupdate的操做,而不是像getput的方法。 目前,一個ViewSet類只綁定一個方法的集合,當它初始化一個視圖的集合時,通常使用爲你處理複雜的URL定義的Router類。

使用視圖集(ViewSets)重構

讓咱們來用視圖集重寫當前視圖。 首先,咱們要把咱們的UserListUserDetail視圖重寫成單個UserViewSet。咱們能夠UserViewSet代替UserListUserDetail

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    This viewset automatically provides `list` and `detail` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

這裏咱們使用ReadOnlyModelViewSet類自動提供默認的'只讀'操做。黨咱們使用常規視圖的時候,咱們仍然須要設置準確設置querysetserializer_class屬性,可是咱們不在須要爲兩個分開的類提供一樣的信息。 接下來,咱們將用SnippetHighlight視圖類來代替SnippetListSnippetDetail。咱們能夠用一個類代替以前的三個類。

from rest_framework.decorators import detail_route
from rest_framework.response import Response

class SnippetViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.

    Additionally we also provide an extra `highlight` action.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

此次咱們使用ModelViewSet類是爲了得到完整的默認讀寫操做的集合。 注意:咱們也用了@detail_route裝飾器來建立自定義動做,命名爲highlight。這個裝飾器用於添加任何自定義的端點,這些端點不符合標準的create/update/delete方式。 使用@detail_route裝飾器的自定義動做會響應GET請求。若是咱們讓動做響應POST請求,咱們可使用methods參數。 自定義動做的URL在默認狀況下是依賴於方法自己。若是你想改變url原本建立的方式,你能夠將url_path包含在裝飾器關鍵參數中。

明確綁定視圖集到URL

咱們定義URLConf的時候,處理方法只綁定了動做。爲了看看發生了什麼,咱們必須從咱們的視圖集(ViewSets)建立一個視圖集合。 在urls.py文件中,咱們將ViewSet類綁定到具體視圖的集合。

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

注意咱們如何經過綁定http方法到每一個視圖須要的動做來從ViewSet類建立多視圖。 既然咱們已經綁定了咱們的資源和具體視圖,咱們就能夠和之前同樣將咱們的視圖註冊到URL配置中。

urlpatterns = format_suffix_patterns([
    url(r'^$', api_root),
    url(r'^snippets/$', snippet_list, name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
    url(r'^users/$', user_list, name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])

使用路由

由於咱們使用ViewSet類而不是View類,因此實際上咱們不須要本身設計URL配置。按慣例,使用Router類就能夠自動將資源與視圖(views)、連接(urls)聯繫起來。咱們須要作的只是用一個路由註冊合適的視圖集合。如今,咱們把剩下的作完。 咱們重寫了urls.py文件。

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from snippets import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    url(r'^', include(router.urls))
]

用路由註冊視圖和提供一個urlpattern是類似的,包括兩個參數--視圖的URL前綴和視圖自己。 咱們使用的默認路由(DefaultRouter)類會自動爲咱們建立API根視圖,因此咱們就能夠從咱們的views模塊刪除api_root方法。

views和viewsets的比較

使用視圖集(viewsets)真的頗有用。它保證URL規範存在你的API中,讓你寫最少的代碼,容許你把注意力集中在你的API提供的交互和表現上而不須要特定的URL配置。 這並不意味着這樣作老是正確的。在使用基於類的視圖代替基於函數的視圖時,咱們總會發現viewsviewsets有類似的地方。使用視圖集(viewsets)沒有比你本身的視圖更清晰。

回顧

難以置信,用這麼少的代碼,咱們已經完成了一個Web API,它是徹底可瀏覽的,擁有完整的受權(authentication)、每一個對象權限(per-object permissions)和多重渲染格式(multiple renderer formats)。 咱們已經經歷了設計過程的每一步,看到了若是咱們只是使用常規的Django視圖自定義任何東西。

相關文章
相關標籤/搜索