利用 Django REST framework 編寫 RESTful API

利用 Django REST framework 編寫 RESTful API

自動生成符合 RESTful 規(guī)范的 API

支持 OPTION、HEAD滋将、POST邻悬、GET、PATCH耕渴、PUT拘悦、DELETE

根據(jù)Content-Type來動態(tài)的返回數(shù)據(jù)類型(如 text齿兔、json)

生成 browserable 的交互頁面(自動為 API 生成非常友好的瀏覽器頁面)

非常細粒度的權限管理(可以細粒度到 field 級別)

示意圖

安裝

$pipinstalldjangorestframework$pipinstallmarkdown

概述

Django Rest framework 的流程大概是這樣的

建立 Models

依靠 Serialiers 將數(shù)據(jù)庫取出的數(shù)據(jù) Parse 為 API 的數(shù)據(jù)(可用于返回給客戶端橱脸,也可用于瀏覽器顯示)

ViewSet 是一個 views 的集合,根據(jù)客戶端的請求(GET分苇、POST等)添诉,返回 Serialiers 處理的數(shù)據(jù)

權限 Premissions 也在這一步做處理

ViewSet 可在 Routers 進行注冊,注冊后會顯示在 Api Root 頁上

在 urls 里注冊 ViewSet 生成的 view医寿,指定監(jiān)聽的 url

希望全面細致了解的人請移步去看官方文檔栏赴,我這里就不一步步的細說了,而是分塊來進行介紹

準備工作 & Models

讓我們來寫個小項目練練手

先用manage.py startproject rest來生成一個項目

再用manage.py createsuperuser創(chuàng)建用戶(后面權限管理會用到)

初始化數(shù)據(jù)庫manage.py migrate

然后當然是編寫 models靖秩,為了展示 rest_framework 的強大之處须眷,我給 models 定義了一個自定義的 field

# myproject/myapp/models.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importimportcPickleaspicklefromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportUserclassSerializedField(models.TextField):"""序列化域用 pickle 來實現(xiàn)存儲 Python 對象"""__metaclass__=models.SubfieldBase# 必須指定該 metaclass 才能使用 to_pythondefvalidate(self,val):raiseisinstance(val,basestring)defto_python(self,val):"""從數(shù)據(jù)庫中取出字符串,解析為 python 對象"""ifvalandisinstance(val,unicode):returnpickle.loads(val.encode('utf-8'))returnvaldefget_prep_value(self,val):"""將 python object 存入數(shù)據(jù)庫"""returnpickle.dumps(val)classMyModel(models.Model):created_at=models.DateTimeField(auto_now_add=True)# 注意這里建立了一個外鍵owner=models.ForeignKey(User,related_name='mymodels')field=models.CharField(max_length=100)options=SerializedField(max_length=1000,default={})

Serializers

定義好了 Models沟突,我們可以開始寫 Serializers花颗,這個相當于 Django 的 Form

# myproject/myapp/serializers.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importimportjsonfromdjango.contrib.auth.modelsimportUserfromrest_frameworkimportserializersfrom..modelsimportMyModelfrom.fieldsimportMyCustFieldclassMyCustField(serializers.CharField):"""為 Model 中的自定義域額外寫的自定義 Serializer Field"""defto_representation(self,obj):"""將從 Model 取出的數(shù)據(jù) parse 給 Api"""returnobjdefto_internal_value(self,data):"""將客戶端傳來的 json 數(shù)據(jù) parse 給 Model"""returnjson.loads(data.encode('utf-8'))classUserSerializer(serializers.ModelSerializer):classMeta:model=User# 定義關聯(lián)的 Modelfields=('id','username','mymodels')# 指定返回的 fields# 這句話的作用是為 MyModel 中的外鍵建立超鏈接,依賴于 urls 中的 name 參數(shù)# 不想要這個功能的話完全可以注釋掉mymodels=serializers.HyperlinkedRelatedField(many=True,queryset=MyModel.objects.all(),view_name='model-detail')classMySerializer(serializers.ModelSerializer):options=MyCustField(max_length=1000,style={'base_template':'textarea.html'},)classMeta:model=MyModelfields=('id','owner','field','options')read_only_fields=('owner',)# 指定只讀的 fielddefcreate(self,validated_data):"""響應 POST 請求"""# 自動為用戶提交的 model 添加 ownervalidated_data['owner']=self.context['request'].userreturnMyModel.objects.create(**validated_data)defupdate(self,instance,validated_data):"""響應 PUT 請求"""instance.field=validated_data.get('field',instance.field)instance.save()returninstance

ViewSet

定義好了 Serializers惠拭,就可以開始寫 viewset 了

其實 viewset 反而是最簡單的部分扩劝,rest_framework 原生提供了四種 ViewSet

ViewSet

GenericViewSet

繼承于GenericAPIView

ModelViewSet

自身提供了六種方法

list

create

retrieve

update

partial_update

destroy

ReadOnlyModelViewSet

我比較喜歡用ModelViewSet,然后再用 Premissions 來管理權限

# myproject/myapp/views.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importfromdjango.contrib.auth.modelsimportUserfromrest_frameworkimportpermissions,viewsets,renderersfromrest_framework.decoratorsimport(permission_classes,detail_route)fromrest_framework.responseimportResponsefrom.serializersimportMySerializer,UserSerializerfrom.modelsimportMyModelclassUserViewSet(viewsets.ModelViewSet):queryset=User.objects.all()serializer_class=UserSerializer# 指定權限,下面馬上講到permission_classes=(permissions.IsAuthenticated,)classModelViewSet(viewsets.ModelViewSet):queryset=MyModel.objects.all()serializer_class=MySerializerpermission_classes=(permissions.IsAuthenticatedOrReadOnly,)@detail_route(renderer_classes=[renderers.StaticHTMLRenderer])defplaintext(self,request,*args,**kwargs):"""自定義 Api 方法"""model=self.get_object()returnResponse(repr(model))

我在 ModelViewSet 中自定義了方法 plaintext棒呛,rest_framework 中對于自定義的 viewset 方法提供了兩種裝飾器

list_route

detail_route

區(qū)別就是list_route的參數(shù)不包含pk(對應 list)聂示,而detail_route包含pk(對應 retrieve)

看一段代碼就懂了

@list_route(methods=['post','delete'])defcustom_handler(self,request):pass@detail_route(methods=['get'])defcustom_handler(self,request,pk=None):pass

Filters

前面根據(jù) serializers 和 viewset 我們已經(jīng)可以很好的提供數(shù)據(jù)接口和展示了。但是有時候我們需要通過 url參數(shù) 來對數(shù)據(jù)進行一些排序或過濾的操作簇秒,為此鱼喉,rest-framwork 提供了 filters 來滿足這一需求。

全局filter

可以在 settings 里指定應用到全局的 filter:

REST_FRAMEWORK={'DEFAULT_FILTER_BACKENDS':('rest_framework.filters.DjangoFilterBackend',)}

viewset 的 filter

也可以為 viewset 分別指定 filter趋观,方法就是在定義 viewset 的時候定義一個名為filter_backend的類變量:

classUserListView(generics.ListAPIView):queryset=User.objects.all()serializer=UserSerializerfilter_backends=(filters.DjangoFilterBackend,)

默認的 filter

rest-framework 提供了幾個原生的 filter:

SearchFilter

filter_backends=(filters.SearchFilter,)search_fields=('username','email')# 指定搜索的域

請求http://example.com/api/users?search=russell蒲凶。

OrderingFilter

filter_backends=(filters.OrderingFilter,)ordering_fields=('username','email')

請求http://example.com/api/users?ordering=account,-username。

自定義 filter

自定義 filter 非常簡單拆内,只需要定義filter_queryset(self, request, queryset, view)方法旋圆,并返回一個 queryset 即可。

直接貼一個我寫的例子:

classNodenameFilter(filters.BaseFilterBackend):"""根據(jù) nodename 來刪選[nodename]: NeiWang"""deffilter_queryset(self,request,queryset,view):nodename=request.QUERY_PARAMS.get('nodename')ifnodename:returnqueryset.filter(nodename=nodename)else:returnqueryset

如果參數(shù)匹配有誤麸恍,想要拋出異常的話灵巧,也可以自定義 APIError,舉個例子:

fromrest_framework.exceptionsimportAPIExceptionclassFilterError(APIException):status_code=406default_detail='Query arguments error!'

然后在 viewset 里直接拋出raise FilterError即可抹沪。

Premissions

顧名思義就是權限管理刻肄,用來給 ViewSet 設置權限,使用 premissions 可以方便的設置不同級別的權限:

全局權限控制

ViewSet 的權限控制

Method 的權限

Object 的權限

被 premission 攔截的請求會有如下的返回結果:

當用戶已登錄融欧,但是被 premissions 限制敏弃,會返回HTTP 403 Forbidden

當用戶未登錄,被 premissions 限制會返回HTTP 401 Unauthorized

默認的權限

rest_framework 中提供了七種權限

AllowAny# 無限制

IsAuthenticated# 登陸用戶

IsAdminUser# Admin 用戶

IsAuthenticatedOrReadOnly# 非登錄用戶只讀

DjangoModelPermissions# 以下都是根據(jù) Django 的 ModelPremissions

DjangoModelPermissionsOrAnonReadOnly

DjangoObjectPermissions

全局權限控制

在 settings.py 中可以設置全局默認權限

# settings.pyREST_FRAMEWORK={'DEFAULT_PERMISSION_CLASSES':('rest_framework.permissions.AllowAny',),}

ViewSet 的權限

可以設置permission_classes的類屬性來給 viewset 設定權限噪馏,restframework 會檢查元組內的每一個 premission麦到,必須要全部通過才行。

classUserViewSet(viewsets.ReadOnlyModelViewSet):queryset=User.objects.all()serializer_class=UserSerializer# 設置權限欠肾,是一個元組permission_classes=(permissions.IsAuthenticated,)

自定義權限

Premissions 可以非常方便的定制瓶颠,比如我就自己寫了一個只允許 owner 編輯的權限

# myproject/myapp/premissions.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importfromrest_frameworkimportpermissionsclassIsOwnerOrReadOnly(permissions.BasePermission):defhas_permission(self,request,view):"""針對每一次請求的權限檢查"""ifrequest.methodinpermissions.SAFE_METHODS:returnTruedefhas_object_permission(self,request,view,obj):"""針對數(shù)據(jù)庫條目的權限檢查,返回 True 表示允許"""# 允許訪問只讀方法ifrequest.methodinpermissions.SAFE_METHODS:returnTrue# 非安全方法需要檢查用戶是否是 ownerreturnobj.owner==request.user

urls & routers

# myproject/myapp/urls.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importfromdjango.conf.urlsimporturl,patterns,includefromrest_framework.routersimportDefaultRouterfrom.importviews# as_view 方法生成 view# 可以非常方便的指定 `{Http Method: View Method}`user_detail=views.UserViewSet.as_view({'get':'retrieve'})user_list=views.UserViewSet.as_view({'get':'list','post':'create'})# plaintext 是我的自定義方法刺桃,也可以非常方便的指定modal_plain=views.ModelViewSet.as_view({'get':'plaintext'})model_detail=views.ModelViewSet.as_view({'get':'retrieve','post':'create'})model_list=views.ModelViewSet.as_view({'get':'list','post':'create'})# router 的作用就是自動生成 Api Root 頁面router=DefaultRouter()router.register(r'models',views.ModelViewSet)router.register(r'users',views.UserViewSet)# 不要忘了把 views 注冊到 urls 中urlpatterns=patterns('',url(r'^',include(router.urls)),# Api Rooturl(r'^api-auth/',include('rest_framework.urls',namespace='rest_framework')),url(r'^models/(?P[0-9]+)/$',model_detail,name='model-detail'),url(r'^models/(?P[0-9]+)/plain/$',modal_plain,name='model-plain'),url(r'^models/$',model_list,name='model-list'),url(r'^users/$',user_list,name='user-list'),url(r'^users/(?P[0-9]+)/$',user_detail,name='user-detail'),)

時間倉促粹淋,就介紹這些,以后有空再介紹一下在 Django 用 JWT 作為身份憑證瑟慈。下面是一些效果圖

Api Root

Users

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末桃移,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葛碧,更是在濱河造成了極大的恐慌借杰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吹埠,死亡現(xiàn)場離奇詭異第步,居然都是意外死亡疮装,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門粘都,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廓推,“玉大人,你說我怎么就攤上這事翩隧》梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵堆生,是天一觀的道長专缠。 經(jīng)常有香客問我,道長淑仆,這世上最難降的妖魔是什么涝婉? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蔗怠,結果婚禮上墩弯,老公的妹妹穿的比我還像新娘。我一直安慰自己寞射,他們只是感情好渔工,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布虏两。 她就那樣靜靜地躺著厨剪,像睡著了一般澡为。 火紅的嫁衣襯著肌膚如雪烦粒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天戴卜,我揣著相機與錄音胶坠,去河邊找鬼杭跪。 笑死通惫,一個胖子當著我的面吹牛茂翔,可吹牛的內容都是我干的混蔼。 我是一名探鬼主播履腋,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼惭嚣!你這毒婦竟也來了遵湖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤晚吞,失蹤者是張志新(化名)和其女友劉穎延旧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體槽地,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡迁沫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年芦瘾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片集畅。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡近弟,死狀恐怖,靈堂內的尸體忽然破棺而出挺智,到底是詐尸還是另有隱情祷愉,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布赦颇,位于F島的核電站二鳄,受9級特大地震影響,放射性物質發(fā)生泄漏媒怯。R本人自食惡果不足惜订讼,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扇苞。 院中可真熱鬧躯嫉,春花似錦、人聲如沸杨拐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哄陶。三九已至帆阳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屋吨,已是汗流浹背蜒谤。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留至扰,地道東北人鳍徽。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像敢课,于是被迫代替她去往敵國和親阶祭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容