從如今開始,咱們將真正開始接觸REST framework的核心。下面咱們介紹幾個基本的模塊。html
REST framework引入了一個擴展常規HTTPRequest
和Request
對象,並提供了更靈活的請求解析。request
對象的核心功能是request.data
屬性,它與request.POST
相似,但對於使用Web API更加有用。python
request.POST #只處理表單數據。 只適用於'POST'方法web
request.data #處理任意數據。使用與'POST', 'PUT'和'PATCH'方法sql
REST framework還引入了一個Response對象,該對象是一種獲取未渲染內容的TemplateResponse
類型,並使用內容協商來肯定正確內容類型返回給客戶端。數據庫
reture Response(data) # 渲染成客戶端請求的內容類型
在你的視圖中使用數字HTTP狀態碼,並非總利於閱讀,若是寫錯代碼,很容易被忽略。REST framework爲每一個狀態碼提供更明確的標識符,例如Status模塊中HTTP_400_BAD_REQUEST
。django
REST framework提供了兩種編寫API視圖的封裝。json
這些視圖封裝提供了一些功能,例如確保你的視圖可以接收Request
實例,並將上下文添加到Response
對象,使得內容協商能夠正常的運行。api
視圖封裝還內置了一些行爲。例如在適當的時候返回405 Method Not Allowed
響應,並處理訪問錯誤的輸入request.data
時候出發任何ParaseError
異常。瀏覽器
咱們不須要再view.py
中JSONResponse類因此刪掉,而後咱們能夠重構咱們的視圖。app
# 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模式。但他給咱們一個簡單、清晰的方式來引用特定的格式。
啓動服務 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主題。
咱們也可使用基於類的視圖來編寫API視圖,而不是基於函數的視圖。正如咱們看到的,這個模式足夠強大,可讓咱們重用通用功能,並幫助咱們保持代碼DRY(Don't repeat yourself)
咱們首先將基於類的視圖重寫根視圖。全部者寫都涉及對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)
重啓服務,若是你沒有搬錯磚,一切應該和之前同樣的。
使用基於類的視圖的優點之一,就是咱們能夠很容易撰寫可重複的行爲。
到目前爲止,咱們使用建立、獲取、更新、刪除操做和咱們建立的任何基於模型的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
刷新瀏覽器,你會發現沒任何變化,若是有,老規矩:你搬錯磚了
目前,咱們的api對誰都是開放的,誰均可以添加或者刪除代碼,沒有任何隱私和限制,咱們須要更高行爲來確保:
咱們對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)
如今,若是咱們建立了一個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,那麼你將發現沒法再建立新的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上有一組權限,若是咱們想要編輯任何snippet,咱們須要先驗證咱們的請求。咱們還沒設置任何認證類,所以當前應用默認的SessionAuthentication
和BasicAuthentication
.
但咱們經過web瀏覽器與API進行交互的時候,咱們能夠登陸,而後瀏覽器會話將爲請求提供所需的身份驗證。
若是咱們以百年城的方式與API進行交互,那麼咱們須要在每一個請求上明確提供身份驗證憑據。
若是咱們嘗試創建一個沒身份驗證的snippet,咱們會獲得一個錯誤:
咱們能夠經過添加以前的用戶名和密碼來發送成功請求:
咱們已經在咱們的webAPI上得到了一套精細的權限組合,下一節咱們將介紹如何經過爲高亮顯示 snippet 建立 HTML 端點來將全部內容聯結在一塊兒,並經過對系統內的關係使用超連接來提升 API 的凝聚力。