13-flask部落格專案之restful api詳解1-概念
13-flask部落格專案之restful api詳解1-概念
Flask-RESTful學習網站
英文:https://flask-restful.readthedocs.io/en/latest/
中文:http://www.pythondoc.com/Flask-RESTful/quickstart.html
一個英文一箇中文
安裝
Flask-RESTful安裝
使用 pip
安裝 Flask-RESTful:
pip install flask-restful
開發的版本可以從 GitHub 上的頁面 下載
git clone https://github.com/twilio/flask-restful.git
cd flask-restful
python setup.py develop
Flask-RESTful 有如下的依賴包(如果你使用 pip
,依賴包會自動地安裝):
- Flask 版本 0.8 或者更高
Flask-RESTful 要求 Python 版本為 2.6, 2.7, 或者 3.3。
我這裡裝的0.3.8
postman安裝
除錯程式需要用到postman
postman使用:https://blog.csdn.net/fxbin123/article/details/80428216/
一、Postman背景介紹
使用者在開發或者除錯網路程式或者是網頁B/S模式的程式的時候是需要一些方法來跟蹤網頁請求的,使用者可以使用一些網路的監視工具比如著名的Firebug等網頁除錯工具。今天給大家介紹的這款網頁除錯工具不僅可以除錯簡單的css、html、指令碼等簡單的網頁基本資訊,它還可以傳送幾乎所有型別的HTTP請求!Postman在傳送網路HTTP請求方面可以說是Chrome外掛類產品中的代表產品之一。
1> 、postman下載地址:
https://www.getpostman.com/apps
postman基本使用
使用它訪問一下百度
我們看下面的請求地址,請求方式
請求結果
用post傳送geti請求,對這個url
上面請求200,我們還可以看請求頭
儲存是將請求結果儲存到一個檔案中
我們隨便填個資訊登入,可以看到是傳送了ajax的請求,型別是xhr。當滑動驗證的時候,也是傳送了verify的ajax請求
我們可以看他登入的請求地址和型別。我們可以看到,手機上提交的form表單都是post請求
我們可以看到表單資料攜帶郵箱密碼
第一個api
環境準備
我們這裡用的是英文的,匯入的模組路徑不同,應該是版本不同吧
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
我們需要將restful這個第三方元件加入到我們的專案中,它是跟db加進來是一樣的
例項化api物件
api物件初始化app
定義api藍圖,並新增api字首
註冊藍圖
建立並修改資料庫。其它不用變
app改成這個樣子的
我們匯入資源
匯入之後,我們點選resource進入檢視,它就有一個dispatch_request方法
Resource類繼承了MethodView類,我們點進去看下這個類
MethodView裡面也有dispatch_request這個方法。它有繼承了使用元類。我們可以看到下面寫著要定義get post等方法
藍圖總寫一個簡單不完善的api示例
from flask import Blueprint from flask_restful import Resource, marshal_with, fields from exts import api user_bp = Blueprint('user', __name__, url_prefix='/api') from apps.user.model import User user_fields = { 'id': fields.Integer, 'username': fields.String, 'password': fields.String, 'udatetime': fields.DateTime } # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users # post def post(self): return {'msg': '------>post'} # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} api.add_resource(UserResource, '/user')
我們在藍圖中如下操作:
從元件中匯入資源,定義檢視類,然後讓它繼承資源類,在自己定義的檢視類下面定義了get ,post,put,delete四個方法,分別對於查增改刪四個功能。
給我們的檢視類新增路由,透過呼叫之前在exts目錄init檔案中從元件匯入的api類生成的api物件,然後api物件點增加資源方法 。將我們定義的檢視類加進去,後面是路由字串。這樣就給我們定義的檢視類增添了路由對映了。
from flask import Blueprint from flask_restful import Resource from exts import api user_bp = Blueprint('user', __name__, url_prefix='/api') # 定義類檢視 class UserResource(Resource): # get 請求的處理 def get(self): return {'msg': '------>get'} # post def post(self): return {'msg': '------>post'} # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} api.add_resource(UserResource, '/user')
啟動程式,我們從瀏覽器訪問一下,當我們get請求路由的時候,找的這個檢視類,然後找到get請求,執行get方法,響應這個字典。前端接收到並在瀏覽器頁面顯示粗來。這裡新增了api字首,但是這裡不需要新增api字首,就能訪問到這裡的檢視類,如果新增上了反而無法訪問到,可能是因為沒有其它藍圖中有相同的路由的原因吧,所以用不上
我們在postman中也是可以訪問
我們四個請求方式都使用了,這裡目前都是可以請求到對應方式的t資料
當請求一個未在檢視類中定義的請求方法時,報錯
我們先定義一個模型,然後遷移資料
新增兩個使用者,
我們在使用者查詢方法裡面查出這個使用者物件列表,然後返回這個使用者物件列表。再看postman請求
此時,不能直接返回使用者物件列表。報錯沒有json序列化
我們改成這樣還是不行
這樣也不行
官網是這樣做的
當我們新增marshal_with裝飾器,裝飾器傳參使用者字典,使用者欄位字典裡面定義使用者表表中有的欄位,每個欄位的型別是什麼,這樣再遇到執行檢視裝飾器裝飾下的get方法時,返回使用者物件,就能將使用者物件的資訊像使用者欄位字典定義的格式查出來並響應給客戶端。這裡只響應一個使用者物件,返回的是個字典。
當我去掉所以,返回使用者物件列表後,響應結果是每一個使用者物件一個字典,所有使用者物件字典組成一個使用者物件列表。每個使用者物件返回的資訊都是安裝使用者欄位設定的格式返回的欄位資料,從表中查出資料。這裡欄位名字和表欄位名字保持一致的。欄位型別也需要設定。從資料庫中查詢出的資料,會渲染到這些相同欄位名稱下。也就是。使用者欄位前面自己定義,後面應該必須是_fields命名。然後需要返回資料庫物件的方法前面新增marshal_with裝飾器,並把要響應的格式使用者欄位傳進去,這樣裝飾器中會幫我們把使用者物件列表中的每個使用者物件按照user_fiels來生成響應資料。從而生成響應資料列表。只有一個使用者物件,請求結果是一個字典,有多個使用者時就是多個字典在一個列表裡面
from flask import Blueprint from flask_restful import Resource, marshal_with, fields from exts import api user_bp = Blueprint('user', __name__, url_prefix='/api') from apps.user.model import User user_fields = { 'id': fields.Integer, 'username': fields.String, 'password': fields.String, 'udatetime': fields.DateTime } # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users # post def post(self): return {'msg': '------>post'} # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} api.add_resource(UserResource, '/user')
如果在欄位裡面只定義了一個,那麼get獲取的資料也只有這一個。不會因為你響應的資料的欄位多而變多。我們的資料庫欄位是比較多的,當我們想要只返回一兩個欄位時,那麼只需要在xxfields裡面定義指定要返回的欄位就可以。
總結
什麼是RESTful架構: (1)每一個URI代表一種資源; (2)客戶端和伺服器之間,傳遞這種資源的某種表現層; (3)客戶端透過四個HTTP動詞(GET,POST,PUT,DELETE,[PATCH]),對伺服器端資源進行操作,實現"表現層狀態轉化"。 Postman 前後端分離: 前端: app,小程式,pc頁面 後端: 沒有頁面,mtv: 模型模板檢視 去掉了t模板。 mv:模型 檢視 模型的使用:跟原來的用法相同 檢視: api構建檢視 步驟: 1. pip3 install flask-restful 2.建立api物件 api = Api(app=app) api = Api(app=藍圖物件) 3. 定義類檢視: from flask_restful import Resource class xxxApi(Resource): def get(self): pass def post(self): pass def put(self): pass def delete(self): pass 4. 繫結 api.add_resource(xxxApi,'/user')
flask restful官網目錄彙總
中文的可以用來學習,可能包路徑是不一樣的,跟中文的
restful api資源路由
上面寫了一個介面,這個介面返回的就是一個json資料,提供前端請求使用,
一個檢視函式,按照繼承的類是資源,我們可以稱之為一個檢視函式就是一個資源吧,而檢視函式的請求路徑的字串對映,也就是路由,按官網翻譯就是資源路由
jquery使用地址(ajax使用地址) :https://jquery.cuishifeng.cn/
我們在前面的基礎上再新增一個類檢視。上面哪個是對所有使用者的操作,下面這個是對單個使用者的操作,對單個使用者的操作需要指定使用者的id。也就是說當是同一張表時,我們或許可以根據前端需求,給同一個表新增多個不同的類檢視,以返回不同的響應資料。這裡就是所有使用者和單個使用者是分開的,也就是還要單獨新增路由,這個路由是需要傳參的。至於是否可以合併 ,看情況考慮。
我們需要新增單個使用者的類檢視,新增單個使用者的增刪改查四個功能,新增單個使用者的類檢視的訪問路由。需要將單個 使用者資源類檢視作為新增路由的引數,然後後面填寫對映的路徑字串。因為單個使用者是需要傳遞使用者id引數的,這裡可以使用這種方式傳參,但是類檢視中每個方法需要定義接收這個傳參的形參的
我們根據傳進的使用者id,將使用者物件查出來然後響應回去,但是我們需要的響應的資料需要是json格式的。
因此我們需要給它新增marshal,將使用者物件轉成一個序列號物件,以之前定義的使用者欄位格式來響應。
然後 我們測試。可以發現,當我們不接引數的時候,訪問的是上面返回所有使用者的
當我們接上引數的時候,返回的是單個使用者的
from flask import Blueprint from flask_restful import Resource, marshal_with, fields from exts import api user_bp = Blueprint('user', __name__, url_prefix='/api') from apps.user.model import User user_fields = { 'id': fields.Integer, 'username': fields.String, 'password': fields.String, 'udatetime': fields.DateTime } # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users # post def post(self): return {'msg': '------>post'} # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} class UserSimpleResource(Resource): @marshal_with(user_fields) # user轉成一個序列化物件, def get(self, id): user = User.query.get(id) return user # 不是str,list,int,。。。 def put(self, id): pass def delete(self, id): pass api.add_resource(UserResource, '/user') api.add_resource(UserSimpleResource, '/user/<int:id>')
一個網址,後端是同樣的,但是前端可以有不同的,如pc端,小程式,手機app等等。前端需要什麼資料,後端需要前端傳什麼引數,需要前後端開發人員協商好
我們除了上面那種傳參,還可以用問號方式傳參
endpoints
我們在這裡列印一下url_map,就能列印出路由的資訊
當我們請求一條路由的時候,就列印出路由資訊
當我們不新增endpoint時,它用的時檢視類名稱小寫做的endpoint,
當我們新增了endpoint之後,就叫我們修改後的那個名稱,它指代的就是那條路由,可以使用endpoint來做反向解析
我們在單個使用者的檢視類put方法下列印一下all_user這個endpoint的反向解析,
postman請求正常
然後我們可以看到透過endpoint名稱,可以解析出它對應的路由。也就是在別的地方,我們需要使用這個路由,就可以使用endpoint去做反向解析出它的路由來了
引數解析
from flask import Blueprint, url_for from flask_restful import Resource, marshal_with, fields,reqparse from exts import api user_bp = Blueprint('user', __name__, url_prefix='/api') from apps.user.model import User from exts import db user_fields = { 'id': fields.Integer, 'username': fields.String, 'password': fields.String, 'udatetime': fields.DateTime } # 引數解析 parser = reqparse.RequestParser() # 解析物件 parser.add_argument('username', type=str, required=True, help='必須輸入使用者名稱') parser.add_argument('password', type=str , required=True, help='必須輸入密碼', location=['form']) parser.add_argument('phone', type=str) # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users @marshal_with(user_fields) def post(self): # 獲取資料 args = parser.parse_args() username = args.get('username') password = args.get('password') phone = args.get('phone') # 建立user物件 user = User() user.username = username user.password = password if phone: user.phone = phone db.session.add(user) db.session.commit() return user # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} class UserSimpleResource(Resource): @marshal_with(user_fields) # user轉成一個序列化物件, def get(self, id): user = User.query.get(id) return user # 不是str,list,int,。。。 def put(self, id): print('endpoint的使用:', url_for('all_user')) return {'msg': 'ok'} def delete(self, id): pass api.add_resource(UserResource, '/user',endpoint='all_user') api.add_resource(UserSimpleResource, '/user/<int:id>')
我們想要請求這個路徑的時候這個方式去攜帶很多個值
我們提交資料時帶著資料
需要用到
我們在使用者欄位下面建立解析物件,然後新增引數。我們可以對前端的傳參進行解析,進行校驗。相當於form類的作用
我們可以看到,新增引數是請求解析類中的一個方法。
我們看下請求解析這個類的init方法,裡面有些引數,我們沒有設定的時候用的是這些預設引數。trim就是預設不做空字元的去除,bundle_errors就是,如果enabled,當第一個錯誤出來的時候,不會中止。繼續往下校驗所有的欄位
給解析器新增引數
我們按照下面新增引數,如果前端有電話,後端沒有電話引數,是不可以的,是不能多給的
新增引數,限制傳參的型別,指明傳參型別必須是整型。預設是字串型別
比如我們的分頁,必須是整型,這時就需要前端傳遞過來的必須是整型,所以我們可以像上面那樣新增資料型別必須是整型才能成功提交,才能校驗透過
我們給模型新增一個電話欄位,遷移資料
然後新增引數,指定引數名稱,型別,必填,幫助資訊,就是報錯資訊
我們寫好之後,前端就可以傳資料了。後端檢視函式中需要取值,就從parse_args裡面取
我們匯入reqpares和資料庫
我們例項化解析物件,新增三個引數
當前端傳送post請求,將三個資料傳遞到後端,我們在post中從解析器裡面取資料
需要先呼叫解析引數方法,然後從這個物件中獲取請求過來的form資料,將他們儲存到資料庫,再將這個物件返回給前端,因為需要返回給前端,所以需要是json序列化過的,需要新增上marshal_with裝飾器。指定按照使用者欄位這個格式返回資料
我們在postman中新增資料,如果是文字的就用這個就行,如果是有檔案的,選擇form-data。這裡是post請求,我們應該在body裡面新增資料,而不是在params裡面新增資料。這裡新增鍵值對,就相當於你在form表單裡面寫入了資料。
當我們輸入鍵值對,點選傳送post請求之後。我們從解析器中獲取到使用者傳過來的鍵值對,然後儲存到資料庫中,資料庫中已經增加了這條資料了。post方法裡面返回這個新增的使用者,新增裝飾器和指定返回的欄位格式後,我們在postman中就接收到了儲存下來的資料資訊。
我們在params裡面新增鍵值對,這樣來傳送post請求。也是可以接收到資料的,從而往下執行儲存進入資料庫。畢竟有的地方form表單post請求就是可以接問號拼接傳參的,只要取得鍵值對對應上就行
我們在location引數裡面新增form時,這樣就限制了,表示form提交的資料裡必須有這些欄位,而我們是從params裡面寫的,那麼相當於每天在body裡面填寫form資料,所以請求到達解析器就對資料做了校驗,相當於沒有填寫username,就把錯誤資訊返回給請求客戶端了。這樣前端可以根據這個判斷,如果有錯誤資訊就在前端渲染,否則就是用使用者資訊做啥渲染的。或者其它操作等等
我們匯入input,裡面使用正則校驗,這樣沒有透過校驗的欄位,就會將幫助資訊,也就是未校驗透過的錯誤資訊響應給客戶端。其它欄位,包括密碼我們都可以設定校驗。除了正則校驗還可以使用其它校驗方法。密碼的正則校驗,可以透過正則限制個數,這裡是6到12位,我們提示資訊也修改完整一點,提示是需要6到12位的數字
我們將手機號讓它透過校驗,我們可以看到能成功提交資料。
我們再get請求5號使用者,將body鍵值對取消勾選,然後點選傳送。我們就能得到5號使用者的資料
類似於核取方塊功能的資料新增
核取方塊,我們需要新增action 是append
在post方法中我們get這個愛好。列印一下,這裡沒有新增資料庫這個欄位,只是看一下請求時這裡接收的資料是怎樣的
我們傳送post請求,新增上多個值,鍵是一樣的。
我們解析器定義接收的欄位行為是追加,所以我們從引數解析器裡面取的這個欄位,是postman裡面新增的多個值組成的一個列表。
換名字
前端location 檔案上傳,頭像上傳
新增頭像欄位,遷移資料
location有多種型別,
如果是檔案上傳,型別必須得填檔案儲存,
他們就對應我們從不同裡面取值是一樣的。像下面電話裡面,是可以填寫多個location的,這樣支援多種資料提交
看下檔案儲存類,它裡面也是用了之前我們使用的儲存驗證碼圖片用的位元組io物件。
因為我們需要上傳檔案了,所以需要將資料修改位formdata。我們點選後面bulk edit
複製貼上鍵值對到form-data中
再點選一下,貼上到formdata裡面鍵值對編輯。
這樣就將鍵值對複製過來了
選中所有,點選塊編輯
可以看到,沒有雙斜線//了,這說明沒有選中使用的鍵值對是//這種註釋,沒有註釋//的就是被選中需要使用的鍵值對。每個鍵值對都是冒號隔開,多個鍵值對換行分隔,這樣我們就能批次新增,修改和刪除鍵值對了。所以這個地方叫塊編輯。而返回到之前的狀態就是鍵值對編輯。描述資訊在這裡是怎麼定義的一會看,我試了一下,鍵值對描述資訊在塊編輯裡面不顯示
我們新增檔案儲存引數,
from werkzeug.datastructures import FileStorage
parser.add_argument('icon', type=FileStorage, location=['files'])
post請求裡面再獲取頭像檔案
我們在後端post請求邏輯中,根據頭像欄位名稱是從引數解析器裡面獲取頭像檔案,這是個檔案儲存物件。如果或者到頭像檔案物件,那麼就儲存在伺服器上,然後將圖片的相對路徑寫入到資料庫中。
而前端是用postman,使用form data方式新增欄位,傳送post請求。將key修改為file型別。然後點選value裡就可以將檔案加進來,點選傳送,就會請求到後端。最終走到post請求這個方法裡,然後儲存檔案,儲存檔案路徑到資料庫,並返回這個使用者物件的資訊,按照之前定好的使用者物件欄位格式。
因為是按照這裡定義的格式返回的資料,所以並沒有返回圖片欄位的資訊
引數繼承,新建一個繼承其它寫好的引數解析器,然後在原基礎上做增刪改等
引數繼承
from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('foo', type=int) parser_copy = parser.copy() parser_copy.add_argument('bar', type=int) # parser_copy has both 'foo' and 'bar' parser_copy.replace_argument('foo', required=True, location='json') # 'foo' is now a required str located in json, not an int as defined # by original parser parser_copy.remove_argument('foo') # parser_copy no longer has 'foo' argument
錯誤資訊處理
如果新增bundle_errors=True,那麼響應資料,會將所有欄位都校驗完,將校驗結果返回給客戶端。而如果沒有新增的話,那麼只會返回第一個校驗失敗的訊息,其它錯誤訊息不會返回,甚至可能都沒有往下進行校驗
from flask_restful import reqparse parser = reqparse.RequestParser(bundle_errors=True) parser.add_argument('foo', type=int, required=True) parser.add_argument('bar', type=int, required=True) # If a request comes in not containing both 'foo' and 'bar', the error that # will come back will look something like this. { "message": { "foo": "foo error message", "bar": "bar error message" } } # The default behavior would only return the first error parser = RequestParser() parser.add_argument('foo', type=int, required=True) parser.add_argument('bar', type=int, required=True) { "message": { "foo": "foo error message" } }
錯誤訊息
from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument( 'foo', choices=('one', 'two'), help='Bad choice: {error_msg}' ) # If a request comes in with a value of "three" for `foo`: { "message": { "foo": "Bad choice: three is not a valid choice", } }
輸出欄位
輸出欄位可以設定複雜結構
輸出欄位
Flask-RESTful 提供了一個簡單的方式來控制在你的響應中實際呈現什麼資料。使用 fields
模組,你可以使用在你的資源裡的任意物件(ORM 模型、定製的類等等)並且 fields
讓你格式化和過濾響應,因此您不必擔心暴露內部資料結構。
當查詢你的程式碼的時候,哪些資料會被呈現以及它們如何被格式化是很清楚的。
也就是我在這裡減少一個欄位,那麼使用者就看不到這個欄位,如果新增上那麼使用者就能看到這個欄位
基本用法
你可以定義一個字典或者 fields
的 OrderedDict 型別,OrderedDict 型別是指鍵名是要呈現的物件的屬性或鍵的名稱,鍵值是一個類,該類格式化和返回的該欄位的值。這個例子有三個欄位,兩個是字串(Strings)以及一個是日期時間(DateTime),格式為 RFC 822 日期字串(同樣也支援 ISO 8601)
from flask_restful import Resource, fields, marshal_with resource_fields = { 'name': fields.String, 'address': fields.String, 'date_updated': fields.DateTime(dt_format='rfc822'), } class Todo(Resource): @marshal_with(resource_fields, envelope='resource') def get(self, **kwargs): return db_get_todo() # Some function that queries the db
這個例子假設你有一個自定義的資料庫物件(todo
),它具有屬性:name
, address
, 以及 date_updated
。該物件上任何其它的屬性可以被認為是私有的不會在輸出中呈現出來。一個可選的 envelope
關鍵字引數被指定為封裝結果輸出。
裝飾器 marshal_with
是真正接受你的資料物件並且過濾欄位。marshal_with
能夠在單個物件,字典,或者列表物件上工作。
注意:marshal_with 是一個很便捷的裝飾器,在功能上等效於如下的 return marshal(db_get_todo(), resource_fields), 200
。這個明確的表示式能用於返回 200 以及其它的 HTTP 狀態碼作為成功響應(錯誤響應見 abort
)。
給欄位多加個括號,裡面加上東西,看著沒區別啊
重新命名屬性
很多時候你面向公眾的欄位名稱是不同於內部的屬性名。使用 attribute
可以配置這種對映。
fields = { 'name': fields.String(attribute='private_name'), 'address': fields.String, }
沒新增之前
新增之後
點進去
string和integer這些繼承Raw
raw裡面新增了attribute了,還有個預設,那麼我們新增一個預設
這樣實現了欄位值的隱匿性。也實現了重新命名欄位值
我們也可以修改前端展示的欄位名字。上面是沒有修改,欄位值跟模型的欄位值名字是一樣,所以前端展示的也是跟模型的欄位值一樣是username;下面我們就讓前端看到的和模型欄位名字不同,這樣客戶端請求後就不能獲取到我們資料庫真實的欄位名稱了,資料庫也相對更安全,做法如下:使用者欄位這裡修改為前端能看到的欄位名稱private_name,然後在字串物件裡面新增attribute屬性,屬性值使用這個欄位對應的模型中欄位的名稱,這樣將二者關聯起來,後面資料就會使用這個欄位的資料庫的值了,後面還有個預設值,也就是沒有值的話,就會用到這個預設值。
我們看了兩個欄位型別都是繼承Raw這個類,沒看過是否所有都繼承Raw,不過猜測大部分都是繼承Raw的,那麼繼承了的就會都可以新增attribute 和default兩個引數,那麼它們就都可以修改給前端的展示欄位,隱匿資料庫欄位名,給請求客戶端展示的都是我這裡定義的欄位值,而非資料庫欄位值。也就是前端看到的不一定就是資料庫欄位名
如下,如果不適應attribute來修改前端顯示欄位名,那麼欄位名預設是藥和資料庫欄位名是保持一致的。不然是找不到資料的,找不到就顯示null了
總結:
1.需要定義字典,字典的格式就是給客戶端看的格式
user_fields = {
'id': fields.Integer,
'username': fields.String(default='匿名'),
'pwd': fields.String(attribute='password'),
'udatetime': fields.DateTime(dt_format='rfc822')
}
客戶端能看到的是: id,username,pwd,udatetime這四個key
預設key的名字是跟model中的模型屬性名一致,如果不想讓前端看到命名,則可以修改
但是必須結合attribute='模型的欄位名'
lambda 也能在 attribute
中使用
fields = { 'name': fields.String(attribute=lambda x: x._private_name), 'address': fields.String, }
也可以使用屬性訪問巢狀屬性
fields = { 'name': fields.String(attribute='people_list.0.person_dictionary.name'), 'address': fields.String, }
預設值
如果由於某種原因你的資料物件中並沒有你定義的欄位列表中的屬性,你可以指定一個預設值而不是返回 None
。
fields = { 'name': fields.String(default='Anonymous User'), 'address': fields.String, }
自定義欄位&多個值
有時候你有你自己定義格式的需求。你可以繼承 fields.Raw
類並且實現格式化函式。當一個屬性儲存多條資訊的時候是特別有用的。例如,一個位域(bit-field)各位代表不同的值。你可以使用 fields
複用一個單一的屬性到多個輸出值(一個屬性在不同情況下輸出不同的結果)。
這個例子假設在 flags
屬性的第一位標誌著一個“正常”或者“迫切”項,第二位標誌著“讀”與“未讀”。這些項可能很容易儲存在一個位欄位,但是可讀性不高。轉換它們使得具有良好的可讀性是很容易的。
自定義欄位輸出,需要繼承Raw,然後重寫format方法
class UrgentItem(fields.Raw): def format(self, value): return "Urgent" if value & 0x01 else "Normal" class UnreadItem(fields.Raw): def format(self, value): return "Unread" if value & 0x02 else "Read" fields = { 'name': fields.String, 'priority': UrgentItem(attribute='flags'), 'status': UnreadItem(attribute='flags'), }
我們在模型裡面新增一個布林型別的資料
我們給1 5 7 設定為已經被刪除的,其它預設就是未刪除就是0狀態
我們新增一個查詢時將這個欄位展示給前端。我們可以看到資料庫中儲存的是布林值型別的,是0和1資料。客戶端請求時時顯示true和false。這裡給前端展示的是用大寫的D。我們想要讓前端展示的內容根據資料庫值不同顯示其它的可讀資訊。0展示未刪除,1展示已刪除。這樣我們需要新增一個判斷
我們如官網樣例,新增判斷。
所以我們可以自己定義輸出欄位 。我們前面用的string和integer這些欄位都是繼承Raw類的,我們自己建立一個自定義輸出欄位,也需要繼承Raw類,然後重寫format方法,裡面放一個value形參。然後在輸出欄位字典裡新增一個展示給客戶的欄位,也是讓它關聯上isdelete模型欄位。但是用的不是元件裡面的輸出欄位類,而是我們自己寫的isDelete類,因為它繼承Raw類,所以我們也可以傳參屬性attribute,讓我們定義的能跟isdelete模型欄位關聯起來。
再看看我們定義的輸出欄位類,裡面的value就是模型中關聯欄位的值,存入的是0 1 布林型別,所以列印出來是true和false,我們做了個判斷,如果true返回什麼字串,否則返回什麼字串,這樣就將輸出修改掉了。資料庫中的值是固定的兩個,這裡就能根據值來返回我們想要給前端展示的可讀性好的一個字串值。返回內容定製化
Url & 其它具體欄位
- 用於xx列表展示頁和某個xx詳情頁
- 需要定義兩個檢視類,一個是所有xx,響應所有xx欄位格式,裡面不包含xx所有欄位,但包含每個xx的詳情頁訪問地址,使用詳情頁路由的endpoint
- 另一個是單個xx詳情檢視類,路由和方法上都要有單個xx的模型id,路由上傳的id要和模型id名稱一致
Flask-RESTful 包含一個特別的欄位,fields.Url
,即為所請求的資源合成一個 uri。這也是一個好示例,它展示瞭如何新增並不真正在你的資料物件中存在的資料到你的響應中。
class RandomNumber(fields.Raw): def output(self, key, obj): return random.random() fields = { 'name': fields.String, # todo_resource is the endpoint name when you called api.add_resource() 'uri': fields.Url('todo_resource'), 'random': RandomNumber, }
預設情況下,fields.Url
返回一個相對的 uri。為了生成包含協議(scheme),主機名以及埠的絕對 uri,需要在欄位宣告的時候傳入 absolute=True
。傳入 scheme
關鍵字引數可以覆蓋預設的協議(scheme):
fields = { 'uri': fields.Url('todo_resource', absolute=True) 'https_uri': fields.Url('todo_resource', absolute=True, scheme='https') }
就是生成一個訪問地址,點選一下就能夠訪問它
from flask import Blueprint, url_for from flask_restful import Resource, marshal_with, fields,reqparse,inputs from exts import api from werkzeug.datastructures import FileStorage from settings import Config import os user_bp = Blueprint('user', __name__, url_prefix='/api') from apps.user.model import User from exts import db class IsDelete(fields.Raw): def format(self, value): print('------------------>', value) return '刪除' if value else '未刪除' user_fields_1 = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'uri': fields.Url('single_user', absolute=True) } user_fields = { 'id': fields.Integer, 'private_name': fields.String(attribute='username',default='匿名'), 'password': fields.String, 'isDelete': fields.Boolean(attribute='isdelete'), 'isDelete1': IsDelete(attribute='isdelete'), 'udatetime': fields.DateTime(dt_format='rfc822') } # 引數解析 parser = reqparse.RequestParser(bundle_errors=True) # 解析物件 # a783789893hf request.form.get() | request.args.get() | request.cookies.get() | request.headers.get() parser.add_argument('username', type=str, required=True, help='必須輸入使用者名稱', location=['form']) parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必須輸入6~12位數字密碼', location=['form']) parser.add_argument('phone', type=inputs.regex(r'^1[356789]\d{9}$'), location=['form'], help='手機號碼格式錯誤') parser.add_argument('hobby', action='append') # ['籃球', '遊戲', '旅遊'] parser.add_argument('icon', type=FileStorage, location=['files']) # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields_1) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users @marshal_with(user_fields) def post(self): # 獲取資料 args = parser.parse_args() username = args.get('username') password = args.get('password') phone = args.get('phone') bobby = args.get('hobby') print(bobby) icon = args.get('icon') print(icon) # 建立user物件 user = User() user.username = username user.password = password if icon: upload_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename) icon.save(upload_path) # 儲存路徑個 user.icon = os.path.join('upload/icon', icon.filename) if phone: user.phone = phone db.session.add(user) db.session.commit() return user # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} class UserSimpleResource(Resource): @marshal_with(user_fields) # user轉成一個序列化物件, def get(self, id): user = User.query.get(id) return user # 不是str,list,int,。。。 def put(self, id): print('endpoint的使用:', url_for('all_user')) return {'msg': 'ok'} def delete(self, id): pass api.add_resource(UserResource, '/user',endpoint='all_user') api.add_resource(UserSimpleResource, '/user/<int:id>', endpoint='single_user')
我們新增一個展示欄位的字典,前面的是展示所有使用者的,但是不展示使用者所有欄位,指包含使用者名稱和每個使用者詳情訪問地址,點選使用者訪問地址就能看到該使用者的詳情,所有欄位;後面的給前端展示單個使用者的,也就是使用者詳情,
檢視所有使用者和單個使用者的檢視類的get,要和對應欄位響應格式繫結上。
使用者詳情的要傳使用者id。傳參欄位名要和模型中使用者id欄位名保持一致,之前使用uid但是報錯了,名稱對不上和模型中,這個id也可能是響應欄位字典中對應的欄位名,也就是給前端看的那個欄位名,後面測試。傳參進去,才能查出並返回單個使用者物件
Url輸出欄位,第一個引數填字串,是放使用者詳情的endpoint。這裡使用absolute
網頁測試:
訪問所有使用者,響應所有使用者的,生成單個使用者的訪問地址。這個地址如何生成的呢?根據反向解析找的路徑,然後根據傳參id,在這裡的id還是在模型裡的id新增上的數字
點選id是1的地址開啟一個postman請求標籤頁,就是使用者1的訪問地址,點選傳送
成為走單個使用者,走響應詳情的欄位格式。這樣的話,像首頁部落格列表,每個部落格詳情頁需要地址 ;商品列表,每個商品詳情頁需要地址;產品列表,每個產品詳情頁需要地址等等類似的,都可以這樣來做
當我們將欄位改為uid時
fields.Url根據endpoint反向解析路徑,會找不到uid,因此路由新增的使用者id傳參和檢視名稱要和model裡面使用者id名稱要一致才行。model裡面是id,這裡傳參也是用id才能根據名字相同,在fields.Url反向解析時,將使用者id拼接到url訪問地址上,成為使用者詳情訪問地址
複雜結構以及第一第二張表是同一張表的表結構設計
你可以有一個扁平的結構,marshal_with 將會把它轉變為一個巢狀結構
>>> from flask_restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String} >>> resource_fields['address'] = {} >>> resource_fields['address']['line 1'] = fields.String(attribute='addr1') >>> resource_fields['address']['line 2'] = fields.String(attribute='addr2') >>> resource_fields['address']['city'] = fields.String >>> resource_fields['address']['state'] = fields.String >>> resource_fields['address']['zip'] = fields.String >>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> json.dumps(marshal(data, resource_fields)) '{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'
注意:address 欄位並不真正地存在於資料物件中,但是任何一個子欄位(sub-fields)可以直接地訪問物件的屬性,就像沒有巢狀一樣。
我們根據使用者表構建一個朋友表,使用者表是第一張表,第二張表還是使用者表,這個朋友表相當於第三張表。 因為朋友也是使用者,朋友的資訊也是在使用者表裡面,只是朋友表裡面存放的使用某個使用者和其它使用者的關係對映,是同一張表不同行之間的關係對映。這個應該是可以做成一對一和一對多 多對多的關係的,只要設定好下面那張表的外來鍵uid和Fid是否是唯一鍵就可以實現。
在原有使用者表上,新增朋友表和關係對映物件,遷移資料
我們給朋友表新增資料
我們新增使用者朋友表的路由和檢視類,這裡只是查詢,就只寫get就好了
我們再看下使用者朋友檢視類,我們要查使用者朋友,需要知道是查哪個使用者的朋友,所以路由上需要新增上使用者的id。檢視類方法中需要接收這個使用者id引數,因為使用者的朋友還是使用者,所以我們根據使用者id從使用者表中查到該使用者的朋友列表。然後構造資料結構響應給請求客戶端。需要響應的資料結構如下:要顯示是哪個使用者,有多少個朋友,然後是朋友列表裡面每個朋友的資訊,每個朋友就是一個使用者詳情的資訊 。然後將這個資料結構返回給客戶端。那麼restful api中想要響應這種複雜的資料結構,是需要如何實現呢
那麼朋友列表需要怎麼做呢?friends是我們根據傳進來的使用者id在朋友表中查詢來的該使用者對應的所有朋友的id。但是我們需要給客戶端傳朋友的詳情而不是朋友的id,所以需要根據所有朋友的id在使用者表中找的所有朋友的使用者物件。這裡是遍歷朋友id列表,然後在使用者表中查出每個朋友物件並追加到朋友列表中。這樣我們就有了朋友物件列表了,而返回客戶端詳情資訊,我們一般都是透過傳返回物件,然後根據輸出欄位格式將物件的所有欄位顯示給客戶端的
這時我們請求一下試試,我們可以看到報錯外來鍵錯誤
在後端報錯 foreign_keys,需要個外來鍵引數
File "D:\softwareinstall\python3\lib\site-packages\sqlalchemy\util\compat.py", line 208, in raise_ raise exception sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.friends - there are multiple foreign key paths linking the tables. Specify th e 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
我們新增到這裡試試
還是會報錯
我們把朋友列表先註釋試試
還是會報錯
將關係對映欄位先註釋掉
get這裡返回的是個字典,沒有model物件。沒有model物件的時候沒有使用裝飾器也是可以正常請求到的。如果有model物件返回,是需要新增裝飾器然後繫結輸出欄位格式,那麼這裡需要怎麼做呢,這裡其它的已經正常輸出給客戶端了,我們需要將朋友物件列表也返回給客戶端
如果我們直接返回朋友物件列表,是沒有序列化的。
我們可以使用marshal加工一下。將model物件列表放到marshl中,然後指定列表中每個物件需要按照哪個輸出欄位格式usre_fields展示給客戶端。這樣就會將包含model物件的資料按指定格式去展示給客戶端了。如果這裡有多個model物件展示,那麼我們可以構造含有多個model物件的資料結構,將每個model物件或物件列表都用一下marshal,並且定義好物件的返回格式。這裡不能用marshal_with這個裝飾器,因為marshal_with雖然能序列化model物件或者物件列表,但是不能格式化其它資料,這裡的資料還包含了username,nums等等。要使用裝飾器的方式需要另外做處理,後面講
from flask_restful import marshal
'friends': marshal(friend_list,user_fields)
我們用頁面訪問一下。正是我們需要的樣式
列表欄位
你也可以把欄位解組(unmarshal)成列表
>>> from flask_restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)} >>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']} >>> json.dumps(marshal(data, resource_fields)) >>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'
高階:巢狀欄位
儘管使用字典套入欄位能夠使得一個扁平的資料物件變成一個巢狀的響應,你可以使用 Nested
解組(unmarshal)巢狀資料結構並且合適地呈現它們。
>>> from flask_restful import fields, marshal >>> import json >>> >>> address_fields = {} >>> address_fields['line 1'] = fields.String(attribute='addr1') >>> address_fields['line 2'] = fields.String(attribute='addr2') >>> address_fields['city'] = fields.String(attribute='city') >>> address_fields['state'] = fields.String(attribute='state') >>> address_fields['zip'] = fields.String(attribute='zip') >>> >>> resource_fields = {} >>> resource_fields['name'] = fields.String >>> resource_fields['billing_address'] = fields.Nested(address_fields) >>> resource_fields['shipping_address'] = fields.Nested(address_fields) >>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2} >>> >>> json.dumps(marshal_with(data, resource_fields)) '{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'
此示例使用兩個巢狀欄位。Nested
建構函式把欄位的字典作為子欄位(sub-fields)來呈現。使用 Nested
和之前例子中的巢狀字典之間的重要區別就是屬性的上下文。在本例中 “billing_address” 是一個具有自己欄位的複雜的物件,傳遞給巢狀欄位的上下文是子物件(sub-object),而不是原來的“資料”物件。換句話說,data.billing_address.addr1
是在這裡的範圍(譯者:這裡是直譯),然而在之前例子中的 data.addr1
是位置屬性。記住:巢狀和列表物件建立一個新屬性的範圍。
如下,我們這裡使用巢狀欄位實現上面那個案例
user_fields = { 'id': fields.Integer(attribute='id'), 'private_name': fields.String(attribute='username',default='匿名'), 'password': fields.String, 'isDelete': fields.Boolean(attribute='isdelete'), 'isDelete1': IsDelete(attribute='isdelete'), 'udatetime': fields.DateTime(dt_format='rfc822') } user_friend_fields = { 'username': fields.String, 'nums': fields.Integer, 'friends': fields.List(fields.Nested(user_fields)) } class UserFriendResource(Resource): @marshal_with(user_friend_fields) def get(self, id): friends = Friend.query.filter(Friend.uid == id).all() user = User.query.get(id) friend_list = [] for friend in friends: u = User.query.get(friend.fid) friend_list.append(u) data = { 'username': user.username, 'nums': len(friends), 'friends': friend_list # [user,user,user] } return data
如下,我們不用marshal了,用marshal_with。我們的輸出資料還是data,data裡面有friend_list這個model物件列表。把data返回去,那麼需要將它交給marsh_with這個裝飾器處理,這個裝飾器裡面需要指定user_friend_fields這個我們定義的輸出欄位格式,這個輸出欄位格式和我們在get方法裡面定義的資料結構是一樣的,但是有model物件的地方它是做了處理的。處理的方式就是使用fields.List(),將朋友物件列表,解組成朋友資訊列表,然後指定user_fields這個輸出欄位格式來輸出給客戶端。而user_fields是之前定義的使用者詳情輸出欄位格式。
如果返回的資料中有多個model物件或者物件列表,那麼應該也是可以這樣做的,然後在外面定義一個同樣資料結構的欄位格式。將這個欄位格式有model物件或物件列表的地方再使用下面的方式來巢狀其它輸出欄位格式。這就完成了複雜的資料結構輸出。如果friends_list裡面還要更復雜的巢狀那就一層層往裡面巢狀,有時間去試試
這一章總結
知識點
什麼是RESTful架構: (1)每一個URI代表一種資源; (2)客戶端和伺服器之間,傳遞這種資源的某種表現層; (3)客戶端透過四個HTTP動詞(GET,POST,PUT,DELETE,[PATCH]),對伺服器端資源進行操作,實現"表現層狀態轉化"。 Postman 前後端分離: 前端: app,小程式,pc頁面 後端: 沒有頁面,mtv: 模型模板檢視 去掉了t模板。 mv:模型 檢視 模型的使用:跟原來的用法相同 檢視: api構建檢視 步驟: 1. pip3 install flask-restful 2.建立api物件 api = Api(app=app) api = Api(app=藍圖物件) 3. 定義類檢視: from flask_restful import Resource class xxxApi(Resource): def get(self): pass def post(self): pass def put(self): pass def delete(self): pass 4. 繫結 api.add_resource(xxxApi,'/user') 參照:http://www.pythondoc.com/Flask-RESTful/quickstart.html https://flask-restful.readthedocs.io/en/latest/ 路由: @app.route('/user') def user(): -------》檢視函式 ..... return response物件 增加 修改 刪除 查詢 按鈕動作 http://127.0.0.1:5000/user?id=1 http://127.0.0.1:5000/user/1 restful: ---->api ----> 介面 ---->資源 ----> url class xxx(Resource): -------> 類檢視 def get(self): pass .... http://127.0.0.1:5000/user get post put delete 增加 修改 刪除 查詢 是透過請求方式完成的 路徑產生: api.add_resource(Resource的子類,'/user') api.add_resource(Resource的子類,'/goods') api.add_resource(Resource的子類,'/order') endpoint: http://127.0.0.1:5000/user/1 http://127.0.0.1:5000/goods?type=xxx&page=1&sorted=price ----》get ----------------進:請求引數傳入------------------- 步驟: 1。建立RequestParser物件: # 引數解析 parser = reqparse.RequestParser(bundle_errors=True) # 解析物件 2。給解析器新增引數: 透過parser.add_argument('名字',type=型別,required=是否必須填寫,help=錯誤的提示資訊,location=表明獲取的位置form就是post表單提交) 注意在type的位置可以新增一些正則的驗證等。 例如: parser.add_argument('username', type=str, required=True, help='必須輸入使用者名稱', location=['form']) parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必須輸入6~12位數字密碼', location=['form']) parser.add_argument('phone', type=inputs.regex(r'^1[356789]\d{9}$'), location=['form'], help='手機號碼格式錯誤') parser.add_argument('hobby', action='append') # ['籃球', '遊戲', '旅遊'] parser.add_argument('icon', type=FileStorage, location=['files']) 只要新增上面的內容,就可以控制客戶端的提交,以及提交的格式。 3。在請求的函式中獲取資料: 可以在get,post,put等中獲取資料,透過parser物件.parse_args() # 獲取資料 args = parser.parse_args() args是一個字典底層的結構中,因此我們獲取具體的資料時可以透過get username = args.get('username') password = args.get('password') ------------輸出----------------- 1.需要定義字典,字典的格式就是給客戶端看的格式 user_fields = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'pwd': fields.String(attribute='password'), 'udatetime': fields.DateTime(dt_format='rfc822') } 客戶端能看到的是: id,username,pwd,udatetime這四個key 預設key的名字是跟model中的模型屬性名一致,如果不想讓前端看到命名,則可以修改 但是必須結合attribute='模型的欄位名' 自定義fields 1。必須繼承Raw 2。重寫方法: def format(self): return 結果 class IsDelete(fields.Raw): def format(self, value): print('------------------>', value) return '刪除' if value else '未刪除' user_fields = { 。。。 'isDelete1': IsDelete(attribute='isdelete'), 。。。 } URI: xxxlist ----->點選具體的一個獲取詳情 ------> 詳情 定義兩個user_fields, 1.用於獲取使用者的列表資訊結構的fields: user_fields_1 = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'uri': fields.Url('single_user', absolute=True) ----》引數使用的就是endpoint的值 } 2。具體使用者資訊展示的fields user_fields = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'pwd': fields.String(attribute='password'), 'isDelete': fields.Boolean(attribute='isdelete'), 'isDelete1': IsDelete(attribute='isdelete'), 'udatetime': fields.DateTime(dt_format='rfc822') } 涉及endpoint的定義: api.add_resource(UserSimpleResource, '/user/<int:id>', endpoint='single_user') 出: return data 注意:data必須是符合json格式 { 'aa':10, 'bb':[ { 'id':1, 'xxxs':[ {},{} ] }, { } ] } 如果直接返回不能有自定義的物件User,Friend,。。。。 如果有這種物件,需要:marchal(),marchal_with()幫助進行轉換。 1。marchal(物件,物件的fields格式) # 物件的fields格式是指字典的輸出格式 marchal([物件,物件],物件的fields格式) 2。marchal_with() 作為裝飾器修飾請求方法 @marshal_with(user_friend_fields) def get(self, id): 。。。。 return data 函式需要引數,引數就是最終資料輸出的格式 引數: user_friend_fields,型別是:dict型別 例如: user_friend_fields = { 'username': fields.String, 'nums': fields.Integer, 'friends': fields.List(fields.Nested(user_fields)) } fields.Nested(fields.String) ----> ['aaa','bbb','bbbc'] fields.Nested(user_fields) -----> user_fields是一個字典結構,將裡面的每一個物件轉成user_fields -----》[user,user,user]
程式
import os from flask import Blueprint, url_for from flask_restful import Resource, marshal_with, fields, reqparse, inputs, marshal from werkzeug.datastructures import FileStorage from apps.user.model import User, Friend from exts import api, db from settings import Config user_bp = Blueprint('user', __name__, url_prefix='/api') class IsDelete(fields.Raw): def format(self, value): # print('------------------>', value) return '刪除' if value else '未刪除' user_fields_1 = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'uri': fields.Url('single_user', absolute=True) } user_fields = { 'id': fields.Integer, 'username': fields.String(default='匿名'), 'pwd': fields.String(attribute='password'), 'isDelete': fields.Boolean(attribute='isdelete'), 'isDelete1': IsDelete(attribute='isdelete'), 'udatetime': fields.DateTime(dt_format='rfc822') } # 引數解析 parser = reqparse.RequestParser(bundle_errors=True) # 解析物件 # a783789893hf request.form.get() | request.args.get() | request.cookies.get() | request.headers.get() parser.add_argument('username', type=str, required=True, help='必須輸入使用者名稱', location=['form']) parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必須輸入6~12位數字密碼', location=['form']) parser.add_argument('phone', type=inputs.regex(r'^1[356789]\d{9}$'), location=['form'], help='手機號碼格式錯誤') parser.add_argument('hobby', action='append') # ['籃球', '遊戲', '旅遊'] parser.add_argument('icon', type=FileStorage, location=['files']) # 定義類檢視 class UserResource(Resource): # get 請求的處理 @marshal_with(user_fields_1) def get(self): users = User.query.all() # userList = [] # for user in users: # userList.append(user.__dict__) return users # post @marshal_with(user_fields) def post(self): # 獲取資料 args = parser.parse_args() username = args.get('username') password = args.get('password') phone = args.get('phone') bobby = args.get('hobby') print(bobby) icon = args.get('icon') print(icon) # 建立user物件 user = User() user.username = username user.password = password if icon: upload_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename) icon.save(upload_path) # 儲存路徑個 user.icon = os.path.join('upload/icon', icon.filename) if phone: user.phone = phone db.session.add(user) db.session.commit() return user # put def put(self): return {'msg': '------>put'} # delete def delete(self): return {'msg': '------>delete'} class UserSimpleResource(Resource): @marshal_with(user_fields) # user轉成一個序列化物件, def get(self, id): user = User.query.get(id) return user # 不是str,list,int,。。。 def put(self, id): print('endpoint的使用:', url_for('all_user')) return {'msg': 'ok'} def delete(self, id): pass user_friend_fields = { 'username': fields.String, 'nums': fields.Integer, 'friends': fields.List(fields.Nested(user_fields)) } class UserFriendResource(Resource): @marshal_with(user_friend_fields) def get(self, id): friends = Friend.query.filter(Friend.uid == id).all() user = User.query.get(id) friend_list = [] for friend in friends: u = User.query.get(friend.fid) friend_list.append(u) data = { 'username': user.username, 'nums': len(friends), 'friends': friend_list # [user,user,user] } return data api.add_resource(UserResource, '/user', endpoint='all_user') api.add_resource(UserSimpleResource, '/user/<int:id>', endpoint='single_user') api.add_resource(UserFriendResource, '/friend/<int:id>', endpoint='user_friend')
import datetime from exts import db class Friend(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) uid = db.Column(db.Integer, db.ForeignKey('user.id')) fid = db.Column(db.Integer, db.ForeignKey('user.id')) class User(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(15), nullable=False) password = db.Column(db.String(12), nullable=False) phone = db.Column(db.String(11)) icon = db.Column(db.String(150)) isdelete = db.Column(db.Boolean()) email = db.Column(db.String(100)) udatetime = db.Column(db.DateTime, default=datetime.datetime.now) friends = db.relationship('Friend', backref='user', foreign_keys=Friend.uid) def __str__(self): return self.username
藍圖中寫restful api 待新增