【rest-framework】javascript
這是一個基於django才能發揮做用的組件,專門用於構造API的。html
說到API,以前在其餘項目中我也作過一些小API,不過那些都是玩票性質,結構十分簡單並且要求的設計強度並不高,要求的請求方式也不太規範。要想作一個規範的健壯的API,最好的辦法果真仍是站在巨人的肩膀上,學習一些現成組件的使用方法來構建本身的API最爲保險簡單。前端
下面就來從個人角度簡單說說rest-framework這個框架如何使用。java
本篇主要參考了 官方文檔,以及這個系列的翻譯(第一、第二、第三、剩餘做者還在繼續翻譯中)。python
安裝可使用pip install djangorestframeworkweb
■ 何爲RESTful的APIajax
首先有必要了解一下什麼是REST,REST的全稱是Representational State Transfer,是一種軟件設計和架構的風格。目的在於提高開發的效率,增長系統的可用性和可維護性。能夠把它當作是一種在開發網絡應用過程當中產生的最佳實踐。遵循REST風格的接口就是RESTful的API。這種API,考慮到網絡中客戶端和服務端的交互,其本質是CURD(Create,Update,Read,Delete)等各類操做和數據交互,將各類類型的數據交互和具象的HTTP方法聯繫起來,造成一套API的設計規則:sql
GET /resource 獲取全部resource對象的列表數據庫
GET /resource/ID 獲取指定resource對象django
POST /resource 根據POST數據中的參數新建一個resource對象
PUT /resource/ID 全量更新指定的一個resource對象
PATCH /resource/ID 部分更新指定的一個resource對象
DELETE /resource/ID 刪除一個指定的resource對象
有了上面這套規範,咱們固然能夠本身實現一個API了,不過工做量仍是偏大一些。rest_framework就是一個很好的遵循了這個規範的框架,咱們只須要編寫相對少的代碼就能夠獲得一個功能健全的API了。
■ rest_framework的基本使用
rest_framework框架是在django中使用的,爲了說明方便,咱們假設在一個django項目中咱們startapp album這個APP,而後在它的models.py文件中定義了以下一個「專輯」的模型:
class Album(models.Model): id = models.AutoField(primary_key=True) album_name = models.CharField(max_length=80) artist = models.CharField(max_length=100) def __unicode__(self): return '<Album> %s' % self.album_name
咱們暫時先定義這個簡單的模型,不涉及任何複雜關係。
■ 序列化器
而後咱們先來講說序列化器這個東西。在rest_framework中,序列化器是一個位於客戶端和後臺之間的中間層。這個中間層一個最基本的做用就是接受前端JSON字符串轉化爲後臺python能夠識別的對象;從後臺獲取python對象而後轉化爲給前端的JSON格式字符串。固然若是它僅僅是這個做用的話那用json.dumps和json.loads差很少了。一個亮點在於序列化器能夠定義一些字段,讓進出的數據能夠「一個蘿蔔一個坑」地填入序列化器,從而就能夠方便地進行格式轉化,順便還能夠作作數據校驗這種工做。序列化器的另外一個方便之處在於它能夠和django的模型層進行互動,從而大幅度減小了編碼量。下面咱們來看看具體的序列化器
序列化器的類在rest_framework.serializers中,最基本的一個序列化器類是Serializer。
咱們經過繼承Serializer類定義本身的序列化器類。一般在類中須要指出本序列化器全部須要進行處理的字段名和字段類型,看起來有點像在定義一個Model或者一個Form。
class AlbumSerializer(serializers.Serializer): id = serializers.IntegerField() album_name = serializers.CharField(max_length=80) artist = serializers.CharField(max_length=100)
因爲序列化器只是一箇中間層,用於轉換信息用,因此對於字段的條件校驗能夠設置得不那麼嚴格。好比serializer.IntergerField()並不能添加primary_key屬性,由於序列化根本不會管主鍵相關的邏輯,是否主鍵衝突或者怎麼樣都是最終存儲的時候ORM會幫咱們看的。
接下來咱們演示如何使用序列化器進行序列化做業:
# 建立一條測試用的記錄 album = (id=1,album_name='soundtrack',artist='oreimo') # 用序列化器序列化對象,得到序列化後的對象 serialized_album = AlbumSerializer(album) # 將序列化的對象轉換成JSON格式 from rest_framework.renderers import JSONRenderer JSONRenderer().render(serialized_album.data)
序列化器接收單個ORM對象,或者一個QuerySet對象,此時須要加上many=True這樣一個序列化參數。而其返回的serialized_album即所謂的「序列化後的對象」,有不少屬性。其中最重要的是data屬性,它是一個ReturnedDict類型(一種字典的亞種,當進行單個的序列化時)或者一個Orderdict構成的列表(當進行QuerySet的序列化時)。這些內容能夠被JSON化,這裏沒有直接使用json模塊來JSON化,而是使用了rest_framework自帶的JSONRenderer類來進行JSON化處理。除了data,這類「被序列化後的ORM對象」還有諸如errors,instance,is_valid等屬性和方法,這裏暫時都還用不到,後面繼續說。
除了序列化,序列化器還能夠作的就是反序列化:
raw = '{"id":"1","album_name":"soundtrack","artist":"oreimo"}' from django.utils.six import BytesIO from rest_framework.parsers import JSONParser streamed = BytesIO(raw) jsoned = JSONParser.parse(streamed) # 得到到預序列化對象,這個過程通常不出錯 predump = AlbumSerializer(data=jsoned) # 校驗此對象是否符合要求,好比raw是否是標準的JSON格式,內容中有無不符合定義的字段等 predump.is_valid() # 若是上句返回了True,那麼能夠查看被驗證是正確的數據,不然能夠查看驗證不經過的具體錯誤信息 predump.validated_data predump.errors
其實能夠看到,序列化器並無參加「反序列化」工做,那些事讓BytesIO和JSONParser作掉了,而這裏序列化器所作的,無非是利用其驗證功能,進行了反序列化數據的正確性驗證。只有當驗證經過(is_valid方法返回True),才能夠調用validated_data屬性。這個屬性就是Python內部的對象,能夠用來進行一些Python內部即web後臺的操做了。相反,若是驗證沒經過,那麼能夠看errors裏面的具體錯誤信息。errors也是一個符合JSON規範的對象。is_valid在被調用的時候能夠傳入raise_exception=True參數,從而使得驗證失敗時直接拋出錯誤,返回400應答。
上面驗證過程的is_valid方法是一刀切的,而且驗證規則有限。若是須要本身進行驗證邏輯的指定,那麼能夠在序列化器類中實現validate方法以進行object層面的數據驗證。此方法接受一個參數data(理想狀況應該是一個字典或列表)表示進行validate的時候,整個序列化器中的數據。若是驗證經過,則方法返回值應該是一個合法的和原值相似的結構;若驗證不經過那麼須要raise起serializer.ValidationError。此外Serializer類中定義class Meta也能夠進行一些object層面的驗證。具體不說了,能夠差文檔。
進一步的若是相對單個單個字段的驗證,而且本身規定驗證邏輯,那麼能夠在序列化器類中定義名爲validate_<field_name>的方法,field_name是你本身定義的字段名,這個方法接受一個參數value,表明進行validate時當前這個字段的值。對於驗證成功與否和object層面的驗證validate方法相似。對於單字段的驗證,還能夠經過在Serializer類聲明字段的時候指定validators=[xxx,yyy...],xxx和yyy都是定義好的函數名,
對於反序列化獲得的數據,咱們可能但願將其保存成一個ORM對象。此時序列化器對象提供了save方法,好比predump.save()返回的就能夠是一個實例。可是目前直接調用會報錯,由於save方法的調用前提條件是要實現類中的create和update方法:
def create(self, validated_data): return Student(**validated_data) def update(self, instance, validated_data): instance.id = validated_data.get('id',instance.id) instance.name = validated_data.get('name',instance.name) instance.phone = validated_data.get('phone',instance.phone) instance.comment = validated_data.get('comment',instance.comment) instance.save() return instance
這是在StudentSerailizer類中實現的兩個方法,create方法很簡單,就是講傳入的字典分散入參,建立一個對象返回。update方法略複雜一些,要把全部屬性都安裝上面的樣子寫出來。這主要是考慮到validated_data不全量的狀況。即update時可能validated_data並非全量信息,可能只提供了部分字段。,此時就要保證其他字段不被此次update所影響。那麼create方法和update方法(全量和部分)都是在什麼狀況下被調用的呢?
首先這兩個方法都是serializer對象調用save方法時使用。而這個serializer對象得來時,可能經過這幾種方法得來:
StudentSerializer(data=data) data是一個合法的Python結構好比一個字典,此時要求其實全字段的字典。這樣獲得的serializer對象調用save時至關於執行create方法。
StudentSerializer(student,data=data) 還能夠這樣獲得一個serializer對象。student是一個既存的Student類的ORM對象。此時調用update方法。傳入的data字典,若是是全字段字典,那麼天然就是全字段更新,不然就是部分字段更新。
須要注意的是serializer對象的save方法可不是ORM對象的save方法,它的save只是將字典中的數據充入到一個ORM對象中,然後者的save則是將ORM中的數據落地到數據庫中。
■ ModelSerializer,更加方便的序列化器
(這部份內容更多的擴展寫在了下面包含關係的序列化器一節中)
繼承Serializer雖然能夠實現一個序列化器,可是編碼量仍然比較多,由於仍是要一個個手工輸入字段以及字段相關性質。更方便的序列化器是ModelSerializer。這個就是以前說到的能夠和django模型層進行互動的序列化器。好比針對上面這個模型咱們能夠設計這麼一個序列化器:
#-*- coding:utf-8 -*- from rest_framework import serializers from album.models import Album class AlbumSerializer(serializers.ModelSerializer): class Meta: model = Album fields = ('album_name','artist')
一般序列化器是寫在項目主目錄下的serializers.py文件中。能夠看到,經過在序列化器類中定義Meta子類,能夠指定這個序列化器和哪一個模型關聯,而且指出引用模型類的哪些字段。若是fields參數寫'__all__'的話那麼就是引用全部字段了。
若是想要除外某些字段能夠設置exclude屬性好比exclue = ('a',),相似的,若是想要設置某些字段的只讀特徵(當視圖update這些字段的時候會被拒絕,create的時候也不須要提供這些字段)
■ 視圖設置
在寫完序列化器以後再看後臺的 路由設置 和 視圖設置。首先來講說視圖設置。在rest_framework框架下的API視圖有兩種實現方式。一種是基於函數的,另外一種基於類。基於函數的實現方式用到了rest_framework.decorators中的api_view裝飾器。經過爲一個函數加上 相似於 @api_view(['GET','POST'])的裝飾器能夠將這個函數做爲API的視圖函數。參數是和flask框架相似的,指出了該函數支持的方法類型。另外下面的函數應該帶有request這個參數。基於函數的視圖設置方法雖然和django原生的很像,不過有些僵硬的是它把原本應該有機一體的函數分紅了好幾塊,不太好。
另外一種基於類的視圖是我重點想說的。這個類也能夠寫在views.py中,其繼承自rest_framework.views.APIView這個類,在類中,應該要實現get,post等方法,而且給出相應的迴應。好比像這樣的一個類:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from rest_framework.views import APIView from rest_framework.response import Response from albumtest.serializers import AlbumSerializer from rest_framework import status from models import Album # Create your views here. class AlbumList(APIView): def get(self,request,format=None): serializer = AlbumSerializer(Album.objects.all(),many=True) data = serializer.data return Response(data,status=status.HTTP_200_OK) def post(self,request,format=None): serializer = AlbumSerializer(request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data,status=status.HTTP_200_OK) return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
基於RESTful的思想,其實對於一個模型,咱們能夠將各個操做涇渭分明地分別寫在兩個類裏,分別是AlbumList(就是上面這個)和AlbumDetail。前者用於訪問/resource的獲取全對象和新增對象兩個操做;後者用於訪問/resource/ID的查改刪單個對象的操做。更加詳細的類在下面給出,咱們先進入get方法看下具體代碼。get方法接受了一個request參數和format參數。實例化AlbumSerializer這個序列化器用到的是一個名爲instance的參數,能夠接受單個模型實例,也能夠接受一個queryset,當參數是一個集合的時候應該加上many=True參數來保證有效的序列化。
實例化以後的序列化器有不少屬性,最經常使用的就是data屬性,表示該序列化器對象序列化出的內容(字符串)是什麼,而後經過rest_framework框架提供的Response類返回結果字符串。另外status參數也是必要的,rest_framework也提供了status這個類,其中有一些常量供咱們使用。
再來看post方法,post方法是從客戶端接受數據解析後傳給後臺的,這樣子的話就用到了request參數中的data屬性。注意這個request參數有別於通常django視圖函數中的request參數,總之咱們調用其data屬性就能夠得到這個POST請求的參數了。以後是is_valid方法,這個方法是serializer自帶的一個方法,能夠根據定義時字段的控制來校驗獲得的參數格式等是否正確。若是不正確,沒法合法落庫,天然須要返回錯誤信息。此時調用了序列化器的另外一個屬性errors,它包含的就是序列化過程當中發生的錯誤信息了。
● 路由設置
rest_framework也爲咱們提供了方便的路由設置的辦法(經過rest_framework.routers),因爲路由設置即使是手寫也還好不過幾行代碼而已,因此這裏暫時用手動設置的辦法來設置路由。
此次咱們將路由設置在主目錄下的urls.py中,這裏已經預設了/admin的路由。咱們加上兩條:
from django.conf.urls import url,include from django.contrib import admin from album import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^albums/',views.AlbumList.as_view()), url(r'api-auth',include('rest_framework.urls')) ]
能夠看到,以前定義的視圖類,咱們經過調用as_views()方法來使其轉換成能夠被django的url方法接受的參數。前面配置的路徑是/albums,也就是說對於Album這個模型,咱們基於RESTful思想的API設計中的那個resource就是albums了。也就是說GET訪問/albums能夠獲取全部albums的列表這樣子。
第二條是一個rest_framework對於API驗證的支持。加上這項配置以後,咱們能夠對訪問API的當前用戶(匿名或者普通或者超級用戶等等)作出感知而且給出相關的迴應。這方面內容在後面細講。
爲了讓以上一大票rest_framework框架相關的內容生效,也別完了在settings.py的INSTALLED_APPS中加上'rest_framework'。
OK,至此這個API已經能夠正常運行了,別忘了同步數據庫信息等django通用步驟。而後咱們經過瀏覽器訪問目標地址的/albums路徑能夠看到這樣一個界面;
看到的是個界面而不是一個JSON串,這是由於經過瀏覽器訪問時,在請求頭中的Accept字段默認是html(這相似於javascript發起的ajax請求中的dataType字段寫什麼),若是一個請求的Accept指明是json或者是默認值的話,那麼就會返回json串了。
順便一提,這個界面是rest_framework爲了方便開發和調試加入進來的,確實很好用。可是在生產上這個界面確定是要關掉的。具體怎麼關我還要研究下。。。
● 關於AlbumDetail類
剛纔提到了AlbumList類是針對訪問/album的,那針對/album/<ID>的那個類給出以下:
class AlbumDetail(APIView): def get_object(self,pk): try: return Album.objects.get(id__exact=pk) except Album.DoesNotExist,e: raise Http404 def get(self,request,pk,format=None): album = self.get_object(pk) serializer = AlbumSerializer(album) return Response(serializer.data) def put(self,request,pk,format=None): album = self.get_object(pk) serializer = AlbumSerializer(album,data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data,status=status.HTTP_200_OK) return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST) def delete(self,request,pk,format=None): album = self.get_object(pk) album.delete() # 這個delete方法就是django模型對象自帶的那個,和rest_framework框架無關 return Response(status=status.HTTP_204_NO_CONTENT)
須要注意幾個點,get_object方法不是rest_framework要求的,可是有了這個方法的話可讓總體的耦合度升高。除了這個方法以外其他全部方法都還有參數pk,其意思是primary_key,即這個模型對象的主鍵,也是rest_framework從請求url中解析出來的那個標識符。通常狀況下是個unicode的字符串。在get_object中,以前的範文中寫的是get(pk),這會報錯,穩妥一點仍是加上id__exact=pk吧。
其餘全部方法也都是用到了get_object,另外的到沒什麼好說的了,反正都是相似的。
而後記得在urls.py中的urlpatterns中加上這條:
url(r'^album/(?P<pk>[0-9]+)/$', views.AlbumDetail.as_view())
需注意,以前若是r'^album/'最後沒加$,要加上,不然/album/1請求有可能會路由到那邊去。而後重啓應用就能夠訪問了。能夠嘗試用PUT方法改變一個Album對象或者用DELETE方法刪除一個對象。
關於正則url規則中pk的命名:通常而言這個變量名是能夠本身控制的,只要將相關視圖方法中的參數也更名便可。可是在繼承封裝度比較高的視圖類時,最好不要改,由於默認是識別pk這個變量名的。
■ 請求與響應
在上面的基本使用的說明以後,咱們再來看下rest_framework這個框架的各個細節部分。首先是在這個框架下的請求與響應。
在基於類的視圖中,咱們定義了各個API方法,在方法的參數中帶有request參數做爲一個請求的映射。實際上這個request是一個繼承自HttpRequest的東西,它更加適應RESTful風格的API。其核心要素時request.data屬性,它表明了通常視圖中request.POST,requset.PUT,request.PATCH三種可能的數據結構。
在API方法中咱們最終都回復了一個Response對象(即使是DELETE方法這種無需回覆信息的請求咱們也回覆了204),
另外在上面給出的,基於類的視圖中,其實已經能夠感受到,通用性是蠻大的。把Album換成另一個模型,彷佛這些代碼也不用怎麼改就能直接用。這就說明代碼還有更精簡的空間。實際上,這種分紅List和Detail兩個類的模式rest_framework確實也給出了這種模式方便的實現方式。名曰泛類實現(generic class):
from rest_framework import generics from serializers import AlbumSerializer from models import Album class AlbumList(generics.ListCreateAPIView): queryset = Album.objects.all() serializer_class = AlbumSerializer class AlbumDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Album.objects.all() serializer_class = AlbumSerializer
這裏使用泛類實現的基於類的視圖,和上面本身一個個寫API方法,在使用上是如出一轍的。不過泛類大大減少了編碼量。
■ 包含關係的序列化器
剛纔咱們給出的模型十分簡單和單一,沒有涉及到關係。天然,就會想當涉及到關係時,rest_framework是如何處理關係的。爲了說明關係,咱們改造了一下現有的Album模型並新增了Track模型:
class Album(models.Model): id = models.AutoField(primary_key=True) album_name = models.CharField(max_length=80) artist = models.CharField(max_length=100) def __unicode__(self): return '<Album> %s' % self.album_name class Track(models.Model): album = models.ForeignKey(Album,on_delete=models.CASCADE,related_name='tracks') order = models.IntegerField() title = models.CharField(max_length=100) duration = models.IntegerField() class Meta: unique_together = ('album','order') ordering = ['album'] def __unicode__(self): return '<Track %s> %s' % (self.order,self.title)
能夠看到Track中有album字段,是個Album模型的外鍵鏈接。而且設置了反向引用時的引用名是tracks(重要!)
在這種時候,理想狀況下,咱們GET /albums這個API時,返回的結果中最好有一個tracks字段,其包含了一些和這個album對象相關的track的信息。要達到這種效果,最關鍵的是要改造序列化器。
rest_framework中有一個relations.py文件,裏面包含了咱們可能在序列化器中用到的不少關於關係操做的字段。好比咱們將AlbumSerializer這個序列化器改形成這樣子:
class AlbumSerializer(serializers.ModelSerialzier): tracks = serailizers.StringRelatedField(many=True) class Meta: model = Album fields = ('album_name','artist','tracks')
雖然繼承了ModelSerializer,可是仍是重寫了tracks這個字段,將其指定爲一個StringRelatedField,經過這樣的改造,獲得的某一個album的tracks字段就是一個字符串的列表,每個成員都是track模型調用__unicode__方法返回的結果。
固然除了StringRelatedField以外,還有不少其餘的,好比
PrimaryKeyRelatedField 得出的列表是相關模型的id組成的列表
HyperlinkedRelatedField 返回的列表是相關模型詳細信息的url的列表,須要指定view_name,這個view_name應該和urls.py中/resource/ID這個路由指出的name一致,這樣纔可生成正確的url列表
SlugRelatedField 上述返回的列表內容的字段都是這個Field指定好的(主鍵或者相應的url等),而這個SlugRelatedField可讓咱們本身指定,只要在參數中寫上slug_field='字段名'便可。如SlugRelatedField(many=True,read_only=True,slug_field='title')時,返回的tracks的列表中就是各個tracks的title字段的值了。
相似功能的Field也還有一些,值得注意的是,除了最上面的StringRelatedField,其餘全部Field都要加上queryset參數或置參數read_only=True。這主要是由於當咱們爲API添加關係以後,若是咱們要經過一個模型的API去修改另外一個模型的值時,rest_framework框架須要queryset去校驗數據的合法性。好比這裏咱們能夠queryset=Track.objects.all(),不過這麼搞不是很符合RESTful思想(修改一個模型應該在這個模型本身的API中進行),因此通常設置成read_only會比較好。可是若是涉及到新增,那麼就必須設置一個queryset了,不然將沒法POST爭取的數據用於新增。
● 返回完整關係--序列化器之間互相調用
上面不管是用哪一個Field,返回的tracks字段的列表中都是相關track的部分信息,有沒有辦法返回完整的信息呢?答案是不用serializers提供的Field,而是直接使用本身的Serializer來實現。
首先改造序列化器:
from rest_framework import serializers from album.models import Album,Track class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ('order','title','duration') class AlbumSerializer(serializers.ModelSerializer): # tracks = serializers.StringRelatedField(many=True) # tracks = serializers.PrimaryKeyRelatedField(many=True,queryset=Track.objects.all()) 這些是上面提到的Field,順便作個示例,就不刪了 # tracks = serializers.SlugRelatedField(many=True,read_only=True,slug_field='title') tracks = TrackSerializer(many=True,read_only=True) class Meta: model = Album fields = ('album_name','artist','tracks')
tracks字段直接被定義成了目標模型的序列化器,因爲類之間的引用關係的規定,這致使TrackSerializer必須定義在AlbumSerializer以前。這樣子再去調用時,tracks字段就是一個字典的列表了,每個字典帶的就是每一個track的order,title,duration字段的信息。
● 可寫的完整關係
一般默認狀況下,上面這種直接調用別的Serializer做爲一個RelatedField而造成的關係是隻讀的。好比上面例子中,咱們能夠GET到某個Album中的一個Track對象的列表,可是在POST時不容許你也帶這麼一個Track對象的列表,後臺不會識別而且將這些tracks做爲新紀錄增長進數據庫。要實現可寫的完整關係,須要在AlbumSerializer這樣一個序列化器類中重載create方法好比:
class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ('order', 'title', 'duration') class AlbumSerializer(serializers.ModelSerializer): tracks = TrackSerializer(many=True) class Meta: model = Album fields = ('album_name', 'artist', 'tracks') def create(self, validated_data): tracks_data = validated_data.pop('tracks') album = Album.objects.create(**validated_data) for track_data in tracks_data: Track.objects.create(album=album, **track_data) return album
● 自定義RelatedField
雖然上面說了三四種RelatedField基本上能夠知足不少場景了。可是總仍是有些場景沒法知足,好比我想這個RelatedField造成的列表是目標對象類的其中的某幾個(若是是一個的話用SlugRelatedField便可)字段的值以必定格式造成的字符串,此時就須要咱們本身來實現一個RelatedField。常見的,咱們在目錄中建立一個fields.py文件,而後在裏面寫:
class TrackFormatField(serializers.RelatedField): def to_presentation(self, value): return 'Track %s: %s' % (value.order,value.title)
看到咱們重載了to_presentation方法,這個方法控制的就是序列化器是以何種格式序列化輸出信息的,它接受一個value參數,指的是被傳入這個字段的那個對象,返回一個字符串。而後在AlbumSerializer裏面就能夠令
tracks = TrackFormatField(many=True),這樣就使得最終出來JSON串中,tracks字段值的列表中每項的格式都是Track 1: xxx這樣子。
● 經過重載serializers.Field自定義Field
上面講的自定義RelatedField是針對一些有表外關聯的字段序列化時能夠用的辦法。若是對於有些字段單純只是想作一個翻譯或者其餘不涉及其餘模型的處理,那麼能夠考慮重載Field這個更加底層的類。
繼承Field類時,必需要進行重載的方法有to_presentation以及to_internal_value。二者分別是用來將Python對象轉化爲字符串以及字符串轉化爲Python對象的。下面給出一個例子
class EnvAdaptField(serializers.Field): def __init__(self,envDict,*args,**kwargs): self.envDict = {k:v for k,v in envDict} serializer.Field.__init__(self,*args,**kwargs) def to_presentation(self, value): return self.envDict.get(value,'未知'); def to_internal_value(self, data): return data env = [('0','生產'),('1','SIT'),('2','UAT')] # 在某個serializer中 environment = EnvAdaptField(env)
首先沒必要這麼死板,除了to_presentation和to_internal_value以外還能夠重載一些其餘方法。注意有時不是徹底重寫的話記得作一些相似於super的操做。好比這裏重載了__init__方法以後使得自定義Field還能夠接收一些額外的數據。從下面的env中不難能看出來,這個Field的目的在於將所屬環境這樣一個屬性,用0,1,2等標識存儲在數據庫中,可是客戶端獲取數據時給的數據又要是生產,SIT等具體的文字描述。
to_presentation方法作到了把標識翻譯成具體的文字。而to_internal_value的時候咱們能夠在前端作一些工做,好比select中的<option value="0">生產</option>,這樣能夠作到val()提交的時候原本提交的就是標識了,因此不作任何處理直接return 出來便可。
另外還有一些時候,可能須要在接受到一個請求以後,基於請求指向的那個對象作一些邏輯判斷,對於邏輯不過關的請求作出拒絕回覆。這應該怎麼作?若是這個判斷要放在field這個層面的話,那麼在Field類中咱們還能夠重載另一些方法好比get_attribute和get_value。這兩個方法的做用分別是做爲了to_presentation和to_internal_value的前處理方法。也就是說兩個方法的返回分別是to_presentation和to_internal_value的參數。
對於上面的這個需求,咱們能夠這麼幹:
class empty: pass class AccountInUseField(serializer.Field): def get_value(self, dictionary): # 校驗放在這個方法裏作 for_sever = dictionary.get('for_server') request = self.context.get('request') tobe_in_use = dictionary.get(self.field_name, empty) if request.method in ('POST','GET','DELETE'): return tobe_in_use if [row[0] for row in Server.objects.get(id=for_server).accounts.values_list('in_use')].count(True) > 0 and tobe_in_use: # 一個服務器的帳號最多隻能有一個的in_use字段處於True狀態 raise serailizer.ValidationError({'detail': 'In use account spotted'}) return tobe_in_use def to_internal_value(self,data): return data
這個類沒寫完整,在個人實踐中(DjangoORM+restframework構造的RESTful標準API),經過API發出針對帳號模型的請求時,就會經過這個類的代碼。首先能夠看到的是get_value方法接收了一個dictionary參數。其內容其實是發來的全部請求參數的鍵值對。好比PUT請求就會把指向的這個模型的全部字段都給寫出來,相對的PATCH請求可能就只會有一部分字段。self.field_name並非由Field決定,而是在模型的serializer中經過調用Field類的Bind方法來決定的。好比在這個示例中self.field_name應該等於'in_use'。不寫死成'in_use'也是考慮到了和源代碼的一個適配性。同理,若是發來請求中沒有in_use字段的話,dictionary中get也只能得到None,可是None對於一些輸入輸出標準來講是錯誤,源代碼(rest_framework/fields.py文件中)定義了空類empty來表明空值而非None,這裏也是模仿他。
再來看具體代碼,self.context是Field類工做的上下文,是一個字典包含了view和request兩個鍵。view的值天然就是請求由哪一個視圖處理,按照rest_framework標準,這個視圖會關聯一個serializer,而serializer中Bind了咱們這個Field。request嘛天然就是這個請求自己了,獲取到request以後咱們就能夠像在視圖函數裏獲取request對象那樣在這裏也作相似的操做。好比經過request.method對請求方法作了一個過濾。若是是POST,GET,DELETE等方法,不必作校驗就直接返回掉。而PUT,PATCH等方法,因爲是改數據,極可能會觸及一個服務器帳號只能有一個in_use是True的限制,因此要校驗。校驗條件涉及到我具體的模型設計,很少說了。總之當條件未經過時,咱們raise了一個ValidationError,此時API就會知道本次請求失敗了,從而返回失敗的HTTP Response。默認狀況下status_code是400。另外需注意ValidationError中的參數是一個字典不是一個簡單字符串(雖然官方文檔明明說簡單字符串就好了。。)。另外一種拋出錯誤的辦法是self.fail方法。fail方法接受固定的幾種錯誤模式做爲Key好比fail('incorrect_type'),fail('incorrect_format')等,並給出了默認的信息,若是有須要也能夠用。
最終若是經過了校驗,沒有raise起錯誤,那麼就返回要修改的in_use值,給to_internal_value方法處理便可。
■ API的權限控制
一般API是須要有權限控制的。rest_framework給咱們提供了大部分常見的權限模型,能夠當即使用。可是說到權限控制,也是分紅好多維度的。首先應該明確,控制的直接對象都是各類方法,即容許不容許某一個方法的執行。而控制的層級能夠分紅模型層面的和實例層面的。首先來講說前者。
● 模型層面的控制
模型層面的控制,只須要咱們在視圖類中直接添加便可。好比咱們想對於Album這個模型作權限控制,將Album的兩個視圖類改寫以下:
from rest_framework import permissions class AlbumList(generics.ListCreateAPIView): queryset = Album.objects.all() serializer_class = AlbumSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) def perform_create(self, serializer): serializer.save(owner=self.request.user) class AlbumDetail(generics.RetrieveUpdateDestroyAPIView) queryset = Album.objects.all() serializer_class = AlbumSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
rest_framework框架自帶的permissions類預約義了不少權限管控的模型,好比咱們用到的IsAuthenticatedOrReadOnly就是指出了對於這個視圖類包含的操做,要麼只能讀,涉及增刪改操做的只能在當前用戶經過驗證的狀況下進行。
● 對象層面的控制
對象層面的權限控制則稍微複雜一些。咱們須要從新定義權限類的樣子。咱們能夠在app中新建一個permissions.py文件。而後在這個permissions中自定義權限類。至於這個類怎麼定義回頭再說。首先咱們要明白,對象層面上的權限控制,勢必要求將咱們編寫的內容和django自帶的用戶模型(固然本身定義的用戶模型的話就不必了)關聯起來。因此須要對Album模型作如下改動:
class Album(models.Model): id = models.AutoField(primary_key=True) album_name = models.CharField(max_length=80) artist = models.CharField(max_length=100) owner = models.ForeignKey('auth.User',related_name='albums',on_delete=models.CASCADE) def __unicode__(self): return '<Album> %s' % self.album_name
經過一個外鍵,將django的用戶和本模型聯繫起來。順便,能夠不特意from django.contrib.auth.models import User而直接使用字符串形式的'auth.User'。而後爲了方便,咱們乾脆把整個數據庫清空,從新錄入數據。這裏須要額外提一句,若是從admin界面進行Album新增的話,天然是能夠選擇owner是誰。可是若是經過API的PUT方法來新增,API目前還沒法識別出owner是和當前用戶關聯在一塊兒的。爲了讓API可以自動將發送請求的當前用戶做爲owner字段的值保存下來咱們還須要作的一件事就是重載視圖類中的perform_create方法。如:
class AlbumList(generics.ListCreateAPIView): ... def perform_create(self, serializer): serializer.save(owner=self.request.user)
注意在視圖類中自帶了屬性self.request用於對請求中信息的提取(詳情見前面咱們從APIView開始繼承時每一個方法中有request參數這一點),perform_create大概就是這個類用於保存實例的方法。
好了,定義好了視圖以後,再回頭去編寫咱們須要的權限類。好比一個權限類能夠這樣寫:
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True return obj.owner == request.user
自定義的權限類繼承自rest_framework內建的權限類permissions.BasePermission,在這個類中咱們重載了has_object_permission方法,重載這個方法說明咱們要作的是對象層面的權限控制。這個方法自帶了request,view,obj三個參數,比較多的參數保證了咱們能夠實現很是靈活的對象層面權限控制。permissions.SAFE_METHODS其實就是('GET','HEAD','OPTION'),當請求的方法是這三個其中的一個的時候,性質都是隻讀的,不會對後臺數據作出改變,因此能夠放開,所以直接return True。當請求其餘方法的時候,只有當相關對象的owner字段和當前請求的用戶一致的時候纔開放權限,不然拒絕。
咱們還能夠將SAFE_METHODS那條判斷去掉,這樣的話就是全部對非本人建立的,單個對象的訪問都會被拒絕。
順便一提,被拒絕時給出的json回覆是{"detail": "Authentication credentials were not provided."}(沒有登陸時)或者{"detail": "You do not have permission to perform this action."}(登陸用戶非owner時)。
最後一步,就是如何把這個自定義的權限類和實際的視圖類結合起來了,其實和以前rest_framework自帶的IsAuthenticatedOrReadOnly同樣,將這個類賦予視圖類的permission_classes屬性中便可。
■ API的用戶驗證
與權限控制相輔相成的是驗證工做。上面的權限控制的重要一個維度就是當前是否有有效用戶登陸,而這個就取決於用戶驗證工做了。從概念上來講,驗證和權限控制應該分開來看,可是二者經常在一塊兒用。
和權限體如今視圖類的permissions_classes屬性中相似的,驗證體如今視圖類的authentication_classes中,代表這個視圖類涵蓋的方法只承認這幾種方式的驗證,經過其餘方式的驗證的用戶都視做沒有驗證;假如此時權限上對沒有驗證的用戶有訪問控制的話天然就訪問不到了。
下面來看一下rest_framework自帶的一些驗證類
● TokenAuthentication
經過Token驗證時B/S架構中常見的一種驗證方式。其要義是用戶先經過有效的帳號和密碼獲取到一個token字符串,以後全部的請求只須要經過這個token做爲一個標識,服務器承認token串就開放相關權限供客戶端操做。rest_framework自帶了一個rest_framework.autotoken的應用。咱們能夠經過它來較爲簡單地創建起一個token體系。
首先在settings.py的INSTALLED_APPS中假如rest_framework.autotoken應用。而後注意,這個應用定義了幾個token相關的模型,因此要python manage.py migrate及時同步數據庫。
接下來就是要構建一個帶有token獲取功能的url,這個url極其相關視圖函數咱們能夠手動寫,但rest_framework給咱們提供了一些預設好的東西。好比:
from rest_framework.authtoken import views as token_views # 在url中加入: url(r'^api-token-auth/',token_views.obtain_auth_token)
如此,經過POST方法訪問api-token-auth路徑,而且帶上POST參數{username:'xxx', password: 'xxx'},若是帳號密碼正確,就會返回一個token字符串了。
那麼獲得了這個Token以後怎麼樣才能把它用於實際請求中呢?只要在頭信息中加上兩條額外的記錄,好比經過python的requests模塊發起請求的話就像下面這樣:
#!/usr/bin/env python # -*- coding:utf-8 -*- import requests headers = { 'WWW-Authenticate': 'Token', 'Authorization': 'Token 506c12a0fc447918957ab7198edb7adae7f95f84' } res = requests.get('http://localhost:8000/albums/',headers=headers) print res.status_code print res.content
須要注意的是,Authorization字段的值不只僅是Token字符串,還包括了'Token '(最後帶一個空格),當頭信息中帶有這樣的記錄,再訪問authentication_classes中有TokenAuthentication的視圖類時,就能夠正常獲得數據了。
當一個視圖類中只有TokenAuthentication時,rest_framework提供給咱們調試用的頁面是沒法正常工做的,由於那個頁面上不管請求html或json,其請求的頭信息中都不會有Authrorization這個字段。即便有值也不是Token xxx形式。因此爲了這個頁面正常工做(同時有時候會須要向網站內部用戶提供訪問,只要當前訪問用戶是有效用戶就能夠在頁面上作一些增刪查改操做,而這個操做就能夠是調用API的動做),咱們應該在有TokenAuthentication類的視圖類中也加上SessionAuthentication這個類,確保比較友好的調用特性。
● SessionAuthentication
這種驗證法基於django自帶的會話機制。比較好理解,即一個會話只要經過了驗證,那麼這個會話就能夠發起一些帶有權限控制操做的請求了。
另外因爲django層面上的要求,當發起PUT,POST,DELETE等方法的請求時會要求請求帶有csrf_token字段的信息。然而這個csrftoken在這裏就是一個比較tricky的東西了。
是這樣的:當咱們指出多種認證機制的時候(好比SessionAuthentication和TokenAuthentication共用時),咱們知道SessionAuthentication是要求服務端發出一個token給客戶端做爲應用的,然而TokenAuthentication的場合並不須要,因此此時在發起請求時必須指出:「個人請求是要經過SessionAuthentication類來驗證的」(反過來,當咱們的請求要經過TokenAuthentication來驗證的時候也要在請求中指出,這個在上面提到了),不然服務端將不獲取請求中的token信息,這就會致使在發起不安全請求如POST,PUT,PATCH等的時候報錯:{"detail":"CSRF token not found"}之類的錯誤。
解決辦法:和TokenAuthentication相似,咱們在請求的header中添加一個字段的信息:X-CSRFTOKEN便可。不過請注意,這個字段的值並非咱們爲了泛泛解決django中POST之類請求須要csrftoken時,添加的ajax全局參數csrfmiddlewaretoken,而應該是一個保存在本地cookie中的一個csrftoken(由於Session的本質就是經過cookie實現的)。這也是我對比了API調試界面發起的請求和在本身開發的頁面上發起請求不一樣得出的結果。具體來講,咱們只要在發起的ajax請求中添加以下:
function getCookie(key){ // 這個函數是爲了方便取cookie值而寫的 var cookie = document.cookie; var kvs = cookie.split(';'); var i = 0; while (i < kvs.length){ var kv = kvs[i].split('='); if (kv[0] == key){ return kv[1]; } i++; } return null; } $.ajax({ //發起ajax請求,其餘參數省略 headers: { 'X-CSRFTOKEN': getCookie('csrftoken') //由於有'-'在裏面,因此X-CSRFTOKEN必須有引號引發來 } })
這樣就不會報錯啦!
● 自定義驗證類
和自定義權限類(上面實現對象層面的權限控制時提到的)同樣,驗證類也能夠本身定義。這個類須要繼承自BaseAuthentication
而後這個類要實現authenticate方法。在這個方法中實現驗證的邏輯,當用戶沒有給出驗證信息時應該返回None,當用戶給出錯誤的驗證信息時拋出AuthenticationFailed錯誤,當用戶驗證成功時返回一個元組:(user,auth),user是經過驗證的用戶對象,auth則是驗證用的關鍵信息。好比能夠參考下TokenAuthentication這個類的實現,看下源碼就會明白了。
除此以外,咱們還能夠重載authenticate_header方法,這個方法接受request做爲參數,而且返回一個字符串。當用戶沒有提供有效的驗證信息,服務端返回401錯誤時,這個字符串會被包含在迴應的頭信息的WWW-authenticate字段中,提示用戶提供何種驗證信息。
下面來具體實現一個類,這個類規定的驗證方式很簡單粗暴,就是要求請求頭信息中帶有用戶名,只要用戶名存在在咱們的系統中就算經過認證。這樣的:
from rest_framework import authentication from rest_framework import exceptions from django.contrib.auth.models import User class UsernameAuthentication(authentication.BaseAuthentication): def authenticate(self,request): username = request.META.get('X-USERNAME') if not username: return None try: user = User.objects.get(username__exact=username) except User.DoesNotExist: return exceptions.AuthenticationFailed('No Such User') return user,username
哦對了,這部分代碼能夠類比權限類,從新建一個authentications.py文件來存放。
包含這個權限類的視圖類,訪問時須要在頭信息中加入X-USERNAME的信息。順便一提,用python的requests庫加入頭信息時會發生一個神奇的事情,我設置的明明是X_USERNAME,可是實際發出的頭信息中字段名卻變成了HTTP_X_USERNAME...,須要注意。
■ 過濾
至此,咱們的API已經有了較爲完備的功能,可是實際使用仍是不行的,由於它只能傻傻地返回一個模型的全部記錄or指定的單個記錄。實際上,API一般會容許請求中包含一些所謂的過濾參數,來把整個結果集進行縮圈、排序,從而獲得一個更加漂亮乾淨的數據集合。
rest_framework框架整合了django-filters這個django組件,經過它能夠很方便地實現具備足夠用過濾功能的API。
首先要保證django-filters已經正確安裝了,pip install django-filter(注意沒有s,不然會裝上另外一個模塊的)便可。另外在settings.py的INSTALLED_APPS中也要包含上django-filters。(這步不要忘。。不然會在後續的訪問過程當中報找不到django-filters相關的模板)。另外,經過django-filters和rest_framework合做作出來的過濾器都是經過GET方法加上必定的參數請求特定的API的url,來獲取一個有限的結果集的。
接着找到咱們要進行過濾的一個視圖類,這個類一般是那種返回整個結果集的,也就是說是ListAPIView繼承出來的那種視圖類。若是想要給Detail的視圖類增長一個過濾器,不是不能夠,可是意義不大(畢竟url已經幫你縮圈到只有一個對象了),而Detail類視圖過濾器的條件如果和當前對象不符合的話就會爆出404錯誤。綜上,接下來的全部說明都基於一個完整結果集的那種視圖類,好比AlbumList這個類,咱們改爲這樣:
# 在views.py文件中 # from rest_framework import generics from rest_framework.permissions import IsAuthenticated from rest_framework.authentications import SessionAuthentication from models import Album from serializers import AlbumSerializer from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter class AlbumList(generics.ListCreateAPIView): queryset = Album.objects.all() serializer_class = AlbumSerializer permission_classes = (IsAuthenticated,) authentication_classes = (SessionAuthentication,) #下面幾行是新的 filter_backends = (DjangoFilterBackend,OrderingFilter) filter_class = AlbumFilter ordering_fields = ('artist',)
首先在視圖類中咱們引入了filter_backends屬性,指定這個屬性的值中有DjangoFilterBackend,至關因而把rest_framework和django-filters兩個組件聯繫了起來。OrderingFilter來自rest_framework.filters而非django-filters下面的某個包(切記切記,爲了找這個浪費好多時間。。),把它也加入filter_backends是爲了讓這個視圖類能夠支持排序。說到排序,ordering_fields指出了這個API的排序能夠指定哪些字段做爲排序基準。調用時加入ordering參數能夠拿來排序,像這樣:ordering=-artist(以artist爲字段desc排序),ordering=artist,album_name(以artist和album_name兩個字段asc排序)。
另外,沒有在上面的代碼中寫出來的是,能夠添加一個ordering屬性來指定默認的排序基準,好比ordering = ('artist',) 就是指默認狀況下以artist字段作升序排序,不帶任何參數請求時返回結果的排序就是這樣的。
而後再來看重頭戲,AlbumFilter是一個Filter類,是咱們本身實現的,它指出了這個API裝的過濾器支持哪些字段的什麼樣的過濾。AlbumFilter這個類咱們放在了同目錄下的filters.py文件中,這麼寫:
from django_filters import FilterSet from models import Album class AlbumFilter(FilterSet): class Meta: model = Album fields = { 'album_name': ['exact','contains'], 'artist': ['exact','contains'], 'tracks__title': ['exact'], }
仍是很好懂的,Meta中的model指出了這個過濾器是給哪一個模型用的,fields指出了該模型各個字段都支持怎麼樣的過濾方法,遵循djangoORM的規則。字段應該和給出的模型對應,若是有該模型沒有的字段則會報錯。另外強大的是能夠作關係查詢,即tracks__title代表在API的過濾器中咱們能夠進行關係的查詢了! 各個字段的值是一個列表,指出了匹配規則,很好理解就不說了。對於數字類型等也能夠加入lt,gt等規則。
構建了這樣一個視圖類以後,再訪問咱們的API調試界面,能夠看到會多出一個Filter的按鈕供咱們調試過濾器,按下按鈕彈出的界面是各類條件(包括過濾和排序等),嘗試着寫一個過濾一下,觀察下URL規則吧。其實和django中是同樣的。好比一個uri多是:/albums/?album_name__contains=肖邦&artist__contains=杰倫&ordering=-artist,這就把整個Album.objects.all()結果集縮圈到album_name字段中有「肖邦」二字,且artist值含有杰倫的結果集,而且給出的結果集按照artist字段的值降序排序。
● 關於filter和serializer之間的獨立性
在使用了一段時間的rest_framework框架以後,對這個方面有些認識了。在ORM設計性比較強的應用中,咱們能夠直接繼承ModelSerializer這個類來方便地基於一個模型建立出一個序列器類。另外一方面,也能夠繼承FilterSet,方便地基於一個模型來建立一個過濾器類。然後這個過濾器類每每還要被當作成一個屬性賦予序列器類。也就是說這裏面的serializer和filter兩個關聯起來了。在這個過程當中,能夠注意到,serializer和filter都基於了同一個模型因此老是下意識地以爲,filter定義的字段必須是和serializer相同或者是其一個子集。其實否則。二者是互相獨立的。
好比在serializer中咱們能夠在class Meta中寫出fields = "__all__",此時序列器類會自動讀取模型的全部字段而且基於此建立序列器。對於某些字段如外鍵或者要作一些額外處理的,咱們能夠在Meta前面寫上某個字段=某個Field來作特別處理。
在filter中,定義的字段則是相對自由的。好比通常filter中會指定model=xxx,而後後面的fields其實除了serializer裏面定義的那些字段以外,還能夠寫一些符合邏輯可是沒有被包括進的字段。這些字段就能夠在rest_framework構成的API中直接使用而不出錯的。好比filter的fields中指定for_client__clientno: ['exact']這樣一個字段。很明顯for_client是另外一個模型的外鍵,而for_client__clientno能夠通過一次外鍵關聯碰到那個模型的clientno字段。
● 重載初始結果集
上面的視圖類中明確指出了queryset=Album.objects.all(),也就是整個結果集。可是有時候咱們可能但願,能夠根據請求上下文在過濾以前的就進行一次「預過濾」,這能夠經過重載視圖類的get_queryset方法實現。好比不知道是否還記得,以前爲了測試對象層面的權限控制,咱們爲Album增長了一個owner字段並鏈接到了User模型上。其實這個權限也能夠在這裏經過重載初始結果集的辦法來實現:
def get_queryset(self): user = self.request.user return user.albums.all()
這樣子的話,好比我是admin登陸進來的,不論我怎麼過濾,最多也只能看到owner是admin的Album對象而不會越界看到其餘用戶的。
■ 分頁
有了過濾,分頁天然也是不能少的。咱們能夠經過重載視圖類的pagination_class屬性來指定一個分頁類實現分頁。分頁類能夠用rest_framework已經預設好的一些類也能夠本身實現。
分頁分紅不少種風格,首先來講下rest_framework爲咱們預設好的一些風格的分頁方式。好比:
● LimitOffsetPagination
rest_framework.pagination.LimitOffsetPagination這個分頁類幫助實現的是相似於Mysql中limit和offset方式的分頁。在API請求參數中加上GET參數limit=X&offset=Y的話,就能夠把當前結果集縮圈成第Y+1個到第Y+X個的那X個結果。好比咱們直接將其應用到某個ListAPIView中:
from rest_framework.pagination import LimitOffsetPagination from rest_framework import generics class ServerList(generics.ListCreateAPIView): queryset = Server.objects.all() serializer_class = ServerSerializer permission_classes = (IsAuthenticatedOrReadOnly,) authentication_classes = (SessionAuthentication,TokenAuthentication) filter_backends = (DjangoFilterBackend,OrderingFilter) filter_class = ServerFilter #設置pagination pagination_class = LimitOffsetPagination ordering = ('for_client__clientno',)
此時咱們訪問ServerList.as_view關聯的那個url時能夠加上limit和offset的參數了。順便,在rest_framework的可視化調試界面上,帶有分頁地訪問時會出現一個分頁按鈕,能夠點擊不一樣的頁碼跳到不一樣的頁。並且分頁比較智能,挺好用的。
須要注意的是,加入pagination_class以後,當請求帶有分頁參數limit時這個類的返回再也不是單純的[{...},{...}]這種形式的JSON了。而是相似下面這種:
{ "count": 5, "previous": "xxx", "next": "yyy", "results": [ {...}, {...} ] }
previous和next的值實際上是兩個url,分別指出當前分頁的上一頁和下一頁的請求url,很是貼心!results即此次請求獲得的結果,count是指在未分頁時結果集的總長度。
有些時候組件發出的請求自帶了limit(好比bootstrap-table在設置了pagination:true以後)參數,此時應該注意到返回結果的變化。
● PageNumberPagination
■ 去除web調試界面
在完成API的編寫以後,上生產環境以前,應該把web的調試界面去除。雖然經過ajax發起的請求能夠指定dataType參數爲json而不爲html,從而避免返回API調試界面html的問題,可是依然可能會存在好比表單提交,其餘請求等場景下返回了html的狀況。爲了一勞永逸地禁用web調試界面,應該在settings.py中加入以下配置:
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', #'rest_framework.renderers.BrowsableAPIRenderer' 這個就是調試界面的renderer ) }
只要指出咱們不要BrowsableAPIRenderer這個renderer那麼就可讓全部請求獲得的都是JSON格式的數據了。
■ 零散
● patch等方法老是沒法如願修改?
按照JSON傳輸的標準,檢查ajax請求參數是否包含了下面幾項:
traditional: true,
contentType: 'application/json; charset=utf-8'
以及data字段的object應該有JSON.stringify()包裹,如data: JSON.stringify({xxx})
同時檢查上面提到過的驗證字段如headers中應當添加{'X-CSRFTOKEN': getCookie('csrftoken')}這樣的
● 關於rest_framework有多層外鍵時取數據的原則
在經過rest_framework構建API的時候,對於List型的API(且不加任何filter參數條件)給出的數據,印象中感受就像是select * from xxx這樣的。但其實rest_framework會對DB中實際存儲的數據,根據ORM的規則判斷來進行一個「預過濾」。即不符合ORM邏輯的數據rest_framework並不會給予展現,即使數據真實存在於DB中。
好比針對以下這個模型:
class A(models.Model): for_B = models.ForeignKey( B, on_delete=models.CASCADE, related_name='has_A' ) # ...
因爲某個B對象被刪除後全部相關的B.has_A.all()中的A對象都會被一併刪除,因此若是正確地按照ORM的邏輯來,A表裏應該是不會存在某個記錄,其for_B_id字段的值在B表中不存在記錄。可是數據庫並未對這個邏輯作出限制。加入咱們在A表中插入一條INSERT INTO A VALUES(.....,'999'),即for_B_id是999的記錄。若是B表中存在某個B對象的id是999還好說,若是沒有,那麼這個A記錄就不會在經過rest_framework獲取A列表時出現。此時就會致使API得到的數據條數和實際表中的記錄條數不一致。
這一層控制是在rest_framework而不是ORM自己中作出的,由於對於這種記錄,若是在代碼中進行A.objects.all()的話仍是會被選擇出來的。
反過來,若是在模型定義中就設置了on_delete=models.SET_NULL,null=True的話,那麼手動插入這麼一條記錄後,rest_framework在獲取全部A對象的時候會給出這條記錄,而且其for_B字段的值是null