- 1.請求和響應
- 2.基於類的視圖
- 3.認證和權限
1.請求和響應
從現在開始,我們將真正開始接觸REST framework的核心。下面我們介紹幾個基本的模塊。
請求對象(request objects)
REST framework引入了一個擴展常規HTTPRequest和Request對象,並提供了更靈活的請求解析。request對象的核心功能是request.data屬性,它與request.POST類似,但對於使用Web API更加有用。
request.POST #只處理表單數據。 只適用於’POST’方法
request.data #處理任意數據。使用與’POST’, 'PUT’和’PATCH’方法
響應對象(response object)
REST framework還引入了一個Response對象,該對象是一種獲取未渲染內容的TemplateResponse類型,並使用內容協商來確定正確內容類型返回給客户端。
reture Response(data) # 渲染成客户端請求的內容類型
狀態碼
在你的視圖中使用數字HTTP狀態碼,並不是總利於閲讀,如果寫錯代碼,很容易被忽略。REST framework為每個狀態碼提供更明確的標識符,例如Status模塊中HTTP_400_BAD_REQUEST。
包裝API視圖(Wrapping API views)
REST framework提供了兩種編寫API視圖的封裝。
- 用於基於函數視圖的@api_view裝飾器
- 用於基於類視圖的APIView類
這些視圖封裝提供了一些功能,例如確保你的視圖能夠接收Request實例,並將上下文添加到Response對象,使得內容協商可以正常的運行。
視圖封裝還內置了一些行為。例如在適當的時候返回405 Method Not Allowed響應,並處理訪問錯誤的輸入request.data時候出發任何ParaseError異常。
組合視圖
我們不需要再view.py中JSONResponse類所以刪掉,然後我們可以重構我們的視圖。
# quickstart/view.py
from django.http import HttpResponse,JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from quickstart.models import Snippet
from quickstart.serializers import SnippetSerializer
from rest_framework.decorators import api_view #新增導入
from rest_framework.response import Response #新增導入
from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND,HTTP_204_NO_CONTENT #新增導入
# @csrf_exempt # 刪除
@api_view(['GET', 'POST']) # 新增
def snippet_list(request):
# 列出所有代碼 snippet, 或者創建一個新的snippet
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
# return JsonResponse(serializer.data, safe=False) # 刪除
return Response(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 Response(serializer.data, status=HTTP_201_CREATED) # 新增
# return JsonResponse(serializer.errors, status=400) # 刪除
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) # 新增
我們的實例視圖比前面的示例有所改進。它稍微簡潔一點,現在的代碼與我們使用 Forms API 時非常相似。我們還使用了指定的狀態碼,這使得響應更加明顯。
# @csrf_exempt # 刪除
@api_view(['GET', 'PUT', 'DELETE']) #新增
def snippet_detail(request, pk):
# 獲取、更新或者刪除一個代碼 snippet
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
# return HttpResponse(status=404) # 刪除
return Response(status=HTTP_404_NOT_FOUND) #新增
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
# return JsonResponse(serializer.data) # 刪除
return Response(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 Response(serializer.data) #新增
# return JsonResponse(serializer.errors, status=400) # 刪除
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) #新增
elif request.method == 'DELETE':
snippet.delete()
# return HttpResponse(status=204) # 刪除
return Response(status=HTTP_204_NO_CONTENT) #新增
這對我們來説應該都是非常熟悉的 - 它和正常 Django 視圖並沒有什麼不同。
注意,我們不再顯式地將請求或響應綁定到給定的內容類型。request.data 可以處理傳入的 json 請求,但它也可以處理其他格式。同樣,我們返回帶有數據的響應對象,但允許 REST framework 將響應渲染成正確的內容類型。
為我們的網址添加可選的格式後綴
為了利用我們的響應不再被硬鏈接到單個內容類型的事實。讓我們將格式後綴的之處添加我們的API端點。使用格式後綴,歐文提供了明確的只想給定的格式URL,這意味着我們的API可以處理一些URLs,類似這樣的格式:http://example.con/api/items/4.json.
像下面這樣在這兩個視圖中添加一個format關鍵字參數。
def snippet_list(request, format=None):
def snippet_detail(request,pk, format=None):
現在更新quickstart/urls.py 文件,在現在的urls基礎上追加一組format_suffix_patterns
# quickstart/urls.py
from django.conf.urls import url
from .views import snippet_list, snippet_detail
from rest_framework.urlpatterns import format_suffix_patterns #新增
urlpatterns = [
url('^quickstart/$', snippet_list),
url('^quickstart/(?P<pk>[0-9]+)/$', snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns) #新增
我們不一定需要添加額外的url模式。但他給我們一個簡單、清晰的方式來引用特定的格式。
測試API
啓動服務 python manage.py runserver
在瀏覽器中輸入 http://127.0.0.1:8000/quickstart/,結果如下
在瀏覽器輸入http://127.0.0.1:8000/quickstart.api,結果如下
在瀏覽器輸入http://127.0.0.1:8000/quickstart.json,結果如下
可視化
由於API是基於客户端發起請求的選擇來響應內容的格式,因此當接收到來之瀏覽器的請求是,會默認以HTML的格式來描述數據,這允許API返回網頁完全可瀏覽的HTML。
有關可瀏覽的API功能以及如何對其進行定製更多的信息,可參考可瀏覽的API主題。
2. 基於類的視圖
我們也可以使用基於類的視圖來編寫API視圖,而不是基於函數的視圖。正如我們看到的,這個模式足夠強大,可以讓我們重用通用功能,並幫助我們保持代碼DRY(Don’t repeat yourself)
使用基於類的視圖我們重寫我們的API
我們首先將基於類的視圖重寫根視圖。所有者寫都涉及對view.py的修改。
# quickstart/view.py
class SnippetList(APIView):
# 列出所有的代碼snippet,或者創建一個新的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=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
這個看起來和以前的非常相似,但是我們在不同的HTTP方法之間有更好的分離,還需要更新
view.py中的示例視圖。
# quickstart/view.py
class SnippetDetail(APIView):
# 獲取、更新或者刪除一個代碼 snippet
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise HTTP_404_NOT_FOUND
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=HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=HTTP_204_NO_CONTENT)
看起來,它仍然非常類似基於函數的視圖
使用基於列的視圖,我們現在還需要重構quickstart/urls.py文件內容:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail
urlpatterns = [
url('^quickstart/$', SnippetList.as_view()),
url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
重啓服務,如果你沒有搬錯磚,一切應該和以前一樣的。
##使用混合(mixins)
使用基於類的視圖的優勢之一,就是我們可以很容易撰寫可重複的行為。
到目前為止,我們使用創建、獲取、更新、刪除操作和我們創建的任何基於模型的API視圖非常相似。這些常見的行為是在REST framework的mixin類中實現的。
下面我們具體實踐如何通過mixin類編寫視圖。在quickstart/view.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構建視圖,並且使用了ListModelMixin和CreateModelMixin.
基類提供核心功能,而mixin類提供.list()和.create()操作.讓後我們可以明確的吧get和post綁定到適當的操作.
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類來提供核心功能,並添加mixins來提供.retrieve(),.update()和.destroy()操作。從新運行服務,你會發現沒有任何變化的。如果有,那就再運行一次。
使用通用的基於類的視圖
我們使用mixin類,使用了比以前更少的代碼重寫了視圖,但是我們可以更進一步。REST framework已經提供了一套已經混合的通用的視圖。我們可以更加簡化view.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
刷新瀏覽器,你會發現沒任何變化,如果有,老規矩:你搬錯磚了
3.認證和權限
目前,我們的api對誰都是開放的,誰都可以添加或者刪除代碼,沒有任何隱私和限制,我們需要更高行為來確保:
- snippet代碼始終與創建者關聯
- 只有經過身份驗證的用户才可以創建snippet
- 只有snippet的創建這個才可以更新或者刪除它
- 未經身份驗證的請求應具有隻讀的訪問權限
將信息添加到我們的模型裏面
我們對quickstart中的模塊要再修改一下,首先要添加幾個字段。其中一個字段將用於表示創建snippet代碼關聯的用户。
另外一個字段將用於存儲高亮顯示的,HTML內容表示的代碼。
添加字段到quickstart/model.py文件中的Snippet模型中:
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
# 提取出'pyment' 支持的所有語言的語法分析程序
LEXERS = [item for item in get_all_lexers() if item[1]]
# 提取除了'pyments' 支持的所有語言列表
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
# 提取出'pyment' 支持的所有格式化風格列表
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=120)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=120)
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) # 新增
highlighted = models.TextField() #新增
class Meta:
ordering = ('-created',)
我們還需要確保在保存模型時候,使用pygments代碼的高亮庫來填充highlighted字段
我們需要導入額外模塊,
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
from pygments.lexers import get_lexer_by_name
然後再類中添加一個方法.save()
def save(self, *args, **kwargs):
# 使用 pygment 庫創建一個高亮顯示html表示的Snippet代碼
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
做完這些工作,同創做法是會場街一個數據庫遷移來實現這一點,但是現在讓我們刪掉數據庫,重新開始。
(env) AdministratordeiMac:tutorial administrator$ ls
db.sqlite3 env manage.py quickstart tutorial
(env) AdministratordeiMac:tutorial administrator$ rm -f db.sqlite3
(env) AdministratordeiMac:tutorial administrator$ rm -r quickstart/migrations
(env) AdministratordeiMac:tutorial administrator$ python manage.py makemigrations quickstart
(env) AdministratordeiMac:tutorial administrator$ python manage.py migrate quickstart
你可以創建幾個不同的用户,用於測試不同的API,最快捷的方式就是createsuperuser命令。
pyhton manage.py createsuperuser #然後按照提示填寫用户名和密碼就可以了
為我們的用户模型添加端點
我們現在有一些用户可以使用,我們最好將這些用户添加到我們的API中。創建一個新的序列化器,在serializer.py我呢間中添加:
# quickstart/serializer.py
from rest_framework import serializers
from .models import Snippet
from django.contrib.auth.models import User
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
#新增
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
由於
snippets在用户模型上是反向關係,當使用ModelSerializer類時候,它將不會被默認包含,我們需要為他添加一個顯式字段。
我們還會在view.py中添加幾個視圖。我們想要使用用户,表示只讀視圖,所以我們將使用ListAPIView和RetrieveAPIView通用的基於類的視圖。
#quictstart/view.py
from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer, UserSerializer #新增導入
from django.contrib.auth.models import User
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
# 新增
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
最後我們需要通過URL conf中引用這些視圖來將這些視圖添加到API中,修改如下:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail, UserList, UserDetail #新增導入
urlpatterns = [
url('^quickstart/$', SnippetList.as_view()),
url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()),
url('^users/$',UserList.as_view()) # 新增
url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()), # 新增
]
urlpatterns = format_suffix_patterns(urlpatterns)
將Snippets和用户關聯
現在,如果我們創建了一個Snippet代碼,那麼無法將創建Snippet的用户與Snippet實例關聯起來。用户不是作為序列化表示的一部分發送的,而是作為請求的屬性傳入。
我們處理方式是,在我們的Snippet視圖上覆蓋,.perform_create()方法,這中管理方式,允許我們修改實例保存,並處理傳入請求,或者請求URL中隱含的任何信息。
在SnippetList視圖類中添加一下方法:
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
# 新增
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
現在我們的序列化器
create()方法將被傳遞一個owner字段以及來自請求的驗證數據。
更新序列化器
現在Snippet與創建他們的用户相關聯了,讓我們更新我們的SnippetSerializer以體現這一點。將下面內容添加到serializers.py文件中:
# quickstart/serializers.py
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username') # 新增
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')
注意:確保將
owner添加到了內部的Meta類的字段列表中。這個字段中,
source參數控制哪個屬性用於填充字段,並且可以指向序列化實例的任何屬性。它也可以採用如上所示的點符號(.),在這種情況下,他將與Django模板語言相似的方式遍歷給定的屬性。我們添加的字段是無類型的
ReadOnlyField類,與其他類型的字段相反,如CharField,BooleanField等無類型的,但ReadOnlyField始終是隻讀的,只能用於序列化表示,但不能用於在反序列化實時更新模型實例。我們這裏可以用CharField(read_only=True).
添加視圖所需的權限
現在,Snippet和用户相關聯,我們希望確保只要經過身份認證的用户才能創建、更新、和刪除snippets。
REST framework包含許多權限類,我們可以用來限制訪問權限。在這種情況下,我們需要的是IsAuthenticatedOrReadOnly類,它將確保身份驗證的請求獲得讀寫訪問權限。未經身份驗證的請求獲得只讀訪問權限。
在quickstart/view.py文件中,添加如下
# quickstart/view.py
from rest_framework import permissions #新增
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增
增加查看API權限,添加登錄
如果你現在打開瀏覽器,並導航到瀏覽器的到API,那麼你將發現無法再創建新的snippet。因為需要增加權限。
我們可以通過編輯url.py文件中的URLconf來添加可瀏覽API的登錄視圖。
在文件頂部,我們導入:
from django.conf.urls import include
並在文件末尾添加一個模式以包括可瀏覽的API的登錄或者註銷視圖。
urlpatterns = [
url('^quickstart/$', SnippetList.as_view()),
url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()),
url('^users/$',UserList.as_view()),
url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()),
url('^api-auth/', include('rest_framework.urls')), # 新增
]
模式
api-auth/部分實際上可以是你想使用的任何URL。
現在再次打開瀏覽器刷新頁面,則會在右上角看到一個登錄鏈接。如果你要用前面的創建用户登錄,就可以再次創建snippets。如下圖:
一旦創建了一些snippets後,導航到/users/端點,並注意到每個用户的snippets字段中包含與每個用户相關聯的snippet id列表。
對象級權限
實際上我們希望所有人都可以看到snippets,但是隻要創建snippet的用户才權限CRUD。
為此,我們需要創建一個自定義的權限。
在quickstart應用中,創建一個新的文件permission.py.
# quickstrat/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
# 自定義權限只允許隊形的所有者去編輯它
def has_object_permission(self, request, view, obj):
# 讀取權限被允許用於任何請求
# 所以我們始終允許GET, HEAD 或者OPTIONS請求。
if request.method in permissions.SAFE_METHODS:
return True
# 寫入權限只允許給snippet的所有者
return obj.owner == request.user
現在我們可以通過編輯SnippetDetail視圖中的permission_classes屬性將自定義權限添加到我們snippet實例中:
# quickstart/view.py
from .permissions import IsOwnerOrReadOnly # 新增
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly) # 新增
現在再次打開瀏覽器,你會發現"DELETE"和’PUT’操作只會出現在以創建者身份登錄的snippet實例端點上。
使用API進行身份驗證
因為現在我們在API上有一組權限,如果我們想要編輯任何snippet,我們需要先驗證我們的請求。我們還沒設置任何認證類,因此當前應用默認的SessionAuthentication和BasicAuthentication.
但我們通過web瀏覽器與API進行交互的時候,我們可以登錄,然後瀏覽器會話將為請求提供所需的身份驗證。
如果我們以百年城的方式與API進行交互,那麼我們需要在每個請求上明確提供身份驗證憑據。
如果我們嘗試建立一個沒身份驗證的snippet,我們會得到一個錯誤:
我們可以通過添加之前的用户名和密碼來發送成功請求:
我們已經在我們的webAPI上獲得了一套精細的權限組合,下一節我們將介紹如何通過為高亮顯示 snippet 創建 HTML 端點來將所有內容聯結在一起,並通過對系統內的關係使用超鏈接來提高 API 的凝聚力。