1. 程式人生 > >Flask-論壇開發-4-知識點補充

Flask-論壇開發-4-知識點補充

token day load() regex self 定義 文件類型 執行 rec

對Flask感興趣的,可以看下這個視頻教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002

1. WTForms 表單使用

WTForms 是一個支持多 web 框架的一個插件,主要功能有兩個:第一個是做表單的驗證,驗證用戶提交上來的信息是否合法,第二個是模板渲染。

1.1 WTForms 表單驗證的基本使用

使用 WTForms 進行表單驗證,會更好的管理我們的代碼和項目結構,還可以大大提高開發項目時的效率。WTForms 功能強大,將表單定義成一個類,可以實現對表單字段的豐富限制。

使用 WTForms 實現表單驗證的功能,主要有以下步驟:

  1. wtforms 中導入 Form 這個類,以及相關字段的數據類型

    from wtforms import From,StringField,IntegerField,FileField
    
    # Form 是一個基類,StringField 用來驗證 String 類型的數據
  2. wrforms.validators 導入一些限制對象(如長度限制)

    from wrforms.validators import Length,EqualTo
    
    # # wrforms.vaildators 是一個驗證器,包含 Length 在內的多種驗證限制,Length 則專門對參數的長度進行驗證,EqualTo 指定必須要和某個值相等
  3. 創建表單類並繼承自 Form,定義相關字段

    class RegistForm(Form):     # 該類用來驗證表單中傳遞的參數,屬性名和參數名必須一致
        username = StringField(validators=[Length(min=3,max=10,message=‘用戶名長度必須在3到10位之間‘)])   
        # StringField 必須傳入關鍵字參數 validators,且 validators 是一個 List 類型(此處僅對長度作驗證)
        password = StringField(validators=[Length(min=6,max=16)])
        password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo(‘password‘)])    # 驗證長度和相等
  4. 在視圖函數中使用該 RegistForm

    form = RegistForm(request.form)     # request.form 會拿到所有提交的表單信息
    if form.validate():     # form.validate() 方法會匹配表單信息並返回 True 或 False
        return ‘註冊成功!‘
    else:
        return ‘註冊失敗!‘

完整代碼如下:

# regist.html
<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>用戶名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密碼:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td>確認密碼:</td>
                <td><input type="password" name="password_repeat"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="點擊提交"></td>
            </tr>
        </tbody>
    </table>
</form>

# 後端程序
from wtforms import Form,StringField
from wtforms.validators import Length,EqualTo

class RegistForm(Form):
    username = StringField(validators=[Length(min=3,max=10,message=‘輸入的用戶名不符合長度規範‘)])
    password = StringField(validators=[Length(min=6,max=16)])
    password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo(‘password‘)])

@app.route(‘/regist/‘,methods=[‘GET‘,‘POST‘])
def regist():
    if request.method == ‘GET‘:
        return render_template(‘regist.html‘)
    else:
        form = RegistForm(request.form)
        if form.validate():
            return ‘註冊成功‘
        else:
            print(form.errors)
            for message in form.errors:
                return ‘註冊成功‘

1.2 WTForms 的相關驗證器

除了上面使用到的兩個驗證器(StringFieldEqualTo)外,WTForms 中還有很多常用的驗證器:

  1. Email:驗證上傳的數據是否為郵箱(格式)

    email = StringField(validators=[email()])
  2. EqualTo:驗證上傳的數據是否與另一個字段相等,常用在註冊時的兩次密碼輸入上

    password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo(‘password‘)])
  3. InputRequired:該字段必須輸入參數,且只要輸入了,那麽該字段就是 True。如果不是特數據情況,應該使用 InputRequired

    password = StringField(validators=[InputRequired()])    # 不管你的值是什麽,只要輸入了就是 True
  4. Length:長度限制,由 minmax 兩個值進行限制

    password = StringField(validators=[Length(6,16)])
  5. NumberRange:數字的區間,由 minmax 兩個值進行限制(包括 minmax)

    age = IntegerField(validators=[NumberRange(12,100)])
  6. Regexp:自定義正則表達式,比如手機號碼的匹配

    phone = StringField(validators=[Regexp(r‘1[34578]\d{9}‘)])
  7. URL:必須要是 URL 的形式

    homepage = StringField(validators=[URL()])
  8. UUID:驗證 UUID

    uuid = StringField(validators=[UUID()])

註意在使用驗證器的時候,後面要加上 ()

1.3 自定義驗證器

如果以上介紹的驗證器不滿足項目當中的需求,那麽還可以根據需求自定義相關的驗證器。如果想要對表單中的某個字段進行更加細致的驗證,那麽可以根據需求對該字段定進行單獨的驗證,步驟如下:

  1. 在表單驗證類中定義一個方法,方法的命名規則為:validate_字段名(self,field)
  2. 在方法中使用 field.data 獲取到用戶上傳到這個字段上的值。
  3. 對於驗證的判斷:若驗證成功,可以什麽都不做;若驗證失敗,則必須跑出 wtforms.validators.ValidationError 異常,並填入驗證失敗的原因。

示例代碼如下所示:

from wtforms import Form,StringField
from wtforms.validators import Length,ValidationError

class LoginForm(Form):
    captcha = StringField(validators=[Length(4,4)])
    def validate_captcha(self,field):   # 用 validate_captcha 來指定該驗證器是針對 captcha 字段的
        if field.data != ‘aw7e‘:
            raise ValidationError(‘驗證碼輸入錯誤!‘)

1.4 WTForms 渲染模板

這個功能可以讓我們的前端代碼少寫一點點,但是實際上用處不大。主要使用方法如下:

  1. forms 文件中定義一個表單類:

    class SettingsForms(Form):
        username = StringField(validators=[Length(4,10)])
  2. 在視圖函數中返回模板時傳遞相關參數:

    @app.route(‘/settings/‘,methods=[‘GET‘,‘POST‘])
    def Settings():
        if request.method == ‘GET‘:
            form = SettingsForms()
            return render_template(‘settings.html‘,my_form=form)
        else:
            pass
  3. 在前端模板中調用

    <form action="" method="post">
        <table>
            <tbody>
                <tr>
                    <td>{{ my_form.username.label }}</td>
                    <td>{{ my_form.username() }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </tbody>
        </table>
    </form>

    其中,第五第六兩行相當於:

    <td>用戶名:</td>
    <td><input type="text" name=‘username‘></td>

實際上,這個功能在生產環境中幾乎沒有任何作用,很雞肋。

2. 文件上傳和訪問

2.1 文件上傳

上傳文件時需要註意以下幾點:

  1. 在模板中,form 表單內,要指定 encotype=‘multipart/form-data‘ 才能實現文件的上傳:

    <form action="" method="post" enctype="multipart/form-data">
        ...
    </form>
  2. 在後臺獲取文件,需要使用 request.files.get(‘標簽名‘) 才能獲取到上傳的文件:

    avatar = request.files.get(‘avatar‘)
  3. 保存文件使用 avatar.save(路徑) 實現,推薦在保存文件時先對文件進行安全封裝:

    from werkzueg.utils import secure_filename
    import os
    
    UPLOAD_PATH = os.path.join(os.path.dirname(__file__),‘images‘)  # UPLOAD_PATH = 當前路徑/images
    
    avatar.save(UPLOAD_PATH,secure_filename(avatar.filename))
  4. 後臺完整代碼如下:

    from werkzeug.utils import secure_filename
    import os
    
    UPLOAD_PATH = os.path.join(os.path.dirname(__file__),‘images‘)  # 定義文件保存路徑:UPLOAD_PATH = 當前路徑/images
    
    @app.route(‘/upload/‘,methods=[‘GET‘,‘POST‘])
    def upload():
        if request.method == ‘GET‘:
            return render_template(‘upload.html‘)
        else:
            avatar = request.files.get(‘avatar‘)
            filename = secure_filename(avatar.filename)     # 對文件名進行安全過濾
            avatar.save(os.path.join(UPLOAD_PATH,filename))
            desc = request.form.get(‘desc‘)
            print(desc)
            return ‘上傳成功!‘

2.2 文件訪問

實現了文件上傳,那麽用戶肯定會需要對文件進行訪問。在 Flask 中,實現文件的訪問必須要定義一個單獨的 url 與視圖函數的映射,並且要借助 send_from_directory 方法返回文件給客戶端。

  1. flask 導入 send_from_directory

    from flask import send_from_directory
  2. 定義視圖函數並映射到文件的 url

    UPLOAD_PATH = os.path.join(os.path.dirname(__file__),‘images‘)
    
    @app.route(‘/getfile/<filename>/‘)
    def getfile(filename):
        return send_from_directory(UPLOAD_PATH,filename)    # send_from_directory 要傳入路徑和文件名
    
    # 用戶可以訪問 http://domainname/filename 對文件進行訪問

2.3 使用驗證器對驗證上傳的文件

在驗證文件的時候,同樣要定義一個驗證的類,然後用該驗證類去驗證上傳的文件。主要分為以下幾個步驟:

  1. 導入 FileField 和文件驗證器:FileRequiredFileAllowed

    from forms import FileField
    from flask_wtf.file import FileRequired,FileAllowed     # 註意這兩個針對文件的驗證器是從 flask_wtf_file 中導入的,而不是從之前的 wtforms.validators 中導入
  2. 定義表單類並繼承自 Form,然後定義相關字段

    class UpLoadForm(Form):
        avatar = FileField(validators=[FileRequired(),FileAllowed([‘jpg‘,‘png‘,‘gif‘])])        # FileRequired() 要求必須傳入文件,FileAllowed() 則指定了允許的文件類型
        desc = StringField(validators=[InputRequired()])
  3. 在主 app 文件中引用

    from werkzeug.datastructures import CombinedMultiDict   # CombinedMultiDict 用來合並兩個不可變的 dict
    form =UpLoadForm(CombinedMultiDict([request.form,request.files]))   # 傳入用戶提交的信息,其中 request.form 是表單中的信息,request.files 是上傳的文件
  4. 完整代碼如下:

    # forms.py 文件
    from wtforms import Form,StringField,FileField
    from flask_wtf.file import FileRequired,FileAllowed
    
    class UpLoadForm(Form):
        avatar = FileField(validators=[FileRequired(),FileAllowed([‘jpg‘,‘png‘,‘gif‘])])
        desc = StringField(validators=[InputRequired()])
    
    # 主 app 文件
    from forms import UpLoadForm
    from werkzeug.utils import secure_filename
    from werkzeug.datastructures import CombinedMultiDict
    import os
    
    UPLOAD_PATH = os.path.join(os.path.dirname(__file__),‘images‘)
    
    @app.route(‘/upload/‘,methods=[‘GET‘,‘POST‘])
    def upload():
        if request.method == ‘GET‘:
            return render_template(‘upload.html‘)
        else:
            form =UpLoadForm(CombinedMultiDict([request.form,request.files]))
            if form.validate():
                avatar = request.files.get(‘avatar‘)
                filename = secure_filename(avatar.filename)
                avatar.save(os.path.join(UPLOAD_PATH,filename))
                desc = request.form.get(‘desc‘)
                print(desc)
                return ‘上傳成功!‘
            else:
                return ‘上傳失敗!‘

設置 CookieResponse 類中有的方法,用法是:在視圖函數中

resp = Response(‘MYYD‘)     # 創建一個 Response 對象,傳入的字符串會被顯示在網頁中
resp.set_cookie(‘username‘,‘myyd‘)
return resp

其中,set_cookie() 中的參數有:

key         鍵
value       值
max_age     IE8 以下不支持,優先級比 expires 高
expires     幾乎所有瀏覽器都支持,必須傳入 datetime 的數據類型,並且默認加 8 個小時(因為我們是東八區)
path        生效的 URL,‘/‘ 代表該域名下所有 URL 都生效,一般默認就好
domian      域名,若沒設置,則只能在當前域名下使用
secure      默認 False,若改為 True 則只能在 https 協議下使用
httponly    默認 False,若改為 True 則只能被瀏覽器所讀取,不能被 JavaScript 讀取(JavaScript可以在前端處理一些簡單邏輯)

使用時依次傳入即可,如果有些選項要跳過則需要指定一下參數名。

完整代碼如下所示:

from flask import Flask,Response

app = Flask(__name__)


@app.route(‘/‘)
def hello_world():
    resp = Response(‘首頁‘)
    resp.set_cookie(‘username‘,‘MYYD‘)
    return resp

if __name__ == ‘__main__‘:
    app.run()

刪除 Cookie 時需要另外指定一條 URL 和視圖函數,也是使用 Response 來創建一個類,並使用 resp.delete_cookie() 來完成這個需求。代碼如下所示:

from flask import Flask,Response

app = Flask(__name__)

@app.route(‘/delCookie/‘)
def delete_cookie():
    resp = Response(‘刪除Cookie‘)
    resp.delete_cookie(‘username‘)
    return resp

if __name__ == ‘__main__‘:
    app.run()

設置 Cookie 的有效期,可以有兩種方法:使用 max_ageexpires

  1. 使用 max_age

    使用 max-age 時要註意,max-age 不支持 IE8 及以下版本的瀏覽器,並且只能相對於現在的時間往後進行推遲(單位是秒s),而不能指定具體的失效時間。使用方法如下代碼所示:

    resp.set_cookie(‘username‘,‘myyd‘,max_age=60)   # 設置該 cookie 60s 之後失效。
  2. 使用 expires

    使用 expires 時要註意,必須要使用格林尼治時間,因為最後會自動加上 8 小時(中國是東八區)。expires 的兼容性要比 max_age 要好,盡管在新版的 http 協議中指明了 expires 要被廢棄,但現在幾乎所有的瀏覽器都支持 expires

    expire 設置失效時間,可以針對當前時間往後推移,也可以指定某一個具體的失效時間。具體如下所示:

    1. 針對當前時間推移

      from datetime import datetime,timedelta
      
      expires = datetime.now() + timedelta(days=30,hours=16)  # 當下時間往後推移 31 天失效,註意這裏給的參數是減了 8 小時的
      resp.set_cookie(‘username‘,‘MYYD‘,expires=expires)
    2. 指定具體日期

      from datetime import datetime
      
      resp = Response(‘首頁‘)
      expires = datetime(year=2018,month=12,day=30,hour=10,minute=0,second=0) # 實際上的失效時間是 2018-12-30-18:0:0
      resp.set_cookie(‘username‘,‘MYYD‘,expires=expires)
      return resp
  3. 其他註意事項

    此外,還要註意幾點:

    1. 當同時使用 max_ageexpires 的時候,會優先使用 max_age 指定的失效時間
    2. 若同時不使用 max_ageexpires 的時候,默認的 cookie 失效時間為瀏覽器關閉的時間(而不是窗口關閉的時間)
    3. expires 要設置為格林尼治時間,同時導入 datetime.datetimedatetime.timedelta

4. RFCS

防範 CSRF 攻擊的措施:

實現:在返回一些危險操作的頁面時,同時返回一個 csrf_tokencookie 信息,並且在返回的頁面表單中也返回一個帶有 csrf_token 值的 input 標簽。

原理:當用戶提交該表單時,若表單中 input 標簽的 csrf_token 值存在並且和 cookie 中的 csrf_token 值相等則允許操作;若不滿足該條件,則操作不被允許。

原因:因為 csrf_token 這個值是在返回危險操作頁面時隨機生成的,黑客是無法偽造出相同的 csrf_token 值的,因為黑客不能操作非自己域名下的 cookie,即不知道 cookie 中的 csrf_token 值的內容。

具體實現:

主app文件:

  1. from flask_wtf import CSRFProtect
  2. CSRFProtect(app)

模板文件(表單中):

<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
註意這裏是要在所有危險操作頁面的表單內都需要加入。

瀏覽器:F12 -> Network -> Disable Cache

// 整個文檔加載完畢後才會執行這個函數
$(function () {
    $(‘#submit‘).click(function (event) {
        // 阻止默認的表單提交行為
        // event.preventDefault();
        var email = $(‘input[name=email]‘).val();
        var password = $(‘input[name=password]‘).val();
        var csrftoken = $(‘input[name=csrf_token]‘).val();

        // $.post() 方法用來提交表單
        $.post({
            ‘url‘:‘/login/‘,
            ‘data‘:{
                ‘email‘: email,
                ‘password‘: password,
                ‘csrftoken‘: csrftoken
            },
            ‘success‘:function (data) {
                console.log(data);
            },
            ‘fail‘:function (error) {
                console.log(error);
            }
        });
    })
});

5. Flask Restful

5.1 Restful API 介紹

Restful API 是用於在前端與後臺進行通信時使用的一套傳輸規範,這些規範可以使後臺開發變得更加輕松。

其采用的協議httphttps

傳輸數據格式采用 json 而不是 xml。使用 json 傳輸數據會變得更加簡單高效,而不是像 xml 那樣伴隨有眾多的固定代碼(類似於 html 的格式),即每次傳輸時 xml 占的資源更多。

並且其url 鏈接中,不能包含動詞,只能包含名詞;並且對於名詞,若出現復數,則必須加上 s

HTTP 的請求方法主要有以下 5 種,但實際上 getpost 就夠用了。

  1. get:獲取服務器上的一個資源
  2. post:在服務器上創建一個紫愛雲
  3. put:在服務器上更新資源(客戶端需要提交更新後的所有數據)
  4. patch:在服務器上更新資源(客戶端只需要提交所更新的數據)
  5. delete:在服務器上刪除一個資源

5.2 Flask-Restful 插件

  1. 安裝

    Flask-Restful 需要在 Flask 0.8 以上版本運行,在 python 2.6 以上版本運行,通過 pip install flask-restful 即可安裝。

  2. 使用

    使用之前必須從 flask_restful 中導入 ApiResource;然後用 Api 將初始化的 app 綁定起來;再定義一個類視圖,定義類視圖必須繼承自 Resource;最後用 add_resource 方法將接口(URL)與視圖綁定起來。完整代碼如下:

    from flask import Flask
    from flask_restful import Api,Resource  # Api 用來綁定 app,Resource 用來創建類視圖
    
    app = Flask(__name__)
    api = Api(app)
    
    class LoginView(Resource):
        def post(self):     # 定義了什麽樣的方法,才能用什麽樣的請求
            return {‘username‘:‘MYYD‘}  # 可以直接返回字典類型的數據(因為字典數據已經自動轉換成Json格式了)
    
    api.add_resource(LoginView,‘/login/‘,endpoint=‘login‘)  # 映射類視圖和接口,endpoint 用來指定 url_for 反轉到類視圖時的關鍵字
    
    if __name__ == ‘__main__‘:
        app.run()
  3. 註意事項:

    1. 映射類視圖和接口時不指定 endpoint,則進行 url_for 反轉時默認使用視圖名稱的小寫,即上例中的 loginview
    1. add_resource 方法的第二個參數,用來指定訪問這個類視圖的接口,與之前不同的是,這個地方可以傳入多個接口。

5.3 Flask-Restful 參數驗證

  1. 基本使用

    Flask-Restful 插件為我們提供了類似之前的 WTForm 表單驗證的包,可以用來驗證提交的數據是否合法,叫做 reqparse。基本用法如下(3步驟):

    parser = reqparse.RequestParser()   # 初始化一個 RequestParser 對象
    parser.add_argument(‘password‘,type=int,help=‘password input error‘)    # 指定驗證的參數名稱,類型以及驗證不通過時的提示信息
    args = parser.parse_args()  # 執行驗證

    完整代碼如下:

    from flask_restful import Api,Resource,reqparse
    
    class LoginView(Resource):
        def post(self):     # post 方法提交數據時傳入的 username 和 password,這裏不需要定義
            parser = reqparse.RequestParser()
            parser.add_argument(‘username‘,type=str,help=‘用戶名格式錯誤‘) # 如果提交數據時沒傳入,默認為 None
            parser.add_argument(‘age‘,type=int,help=‘密碼錯誤‘)
            args = parser.parse_args()
            print(args)
            return {‘username‘:‘MYYD‘}
  2. add_argument 解析

    在使用 add_argument 對上傳的數據進行驗證時,可以根據需求使用不同的選項進行驗證,常用的選項有:

    1. default:默認值,如果沒有傳入該參數,則使用 default 為該參數指定默認的值。
    2. required:置為 True 時(默認為 False),該參數必須傳入值,否則拋出異常。
    3. type:指定該參數的類型,並進行強制轉換,若強制轉換失敗則拋出異常。
    4. choices:相當於枚舉類型,即該傳入的參數只能為 choices 列表中指定的值。
    5. help:當驗證失敗時拋出的異常信息。
    6. trim:置為 True 時對上傳的數據進行去空格處理(只去掉字符串前後的空格,不去掉字符串之間的空格)。

    其中,type 選項除了可以指定 python 自帶的一些數據類型外,還可以指定 flask_restful.inputs 下的一些特定類型來進行強制轉換。常用的類型如下:

    1. url:會判斷上傳的這個參數是不是一個 url,若不是則拋出異常。
    2. regex:會判斷上傳的這個參數是否符合正則表達式中的格式,若不符合則拋出異常。
    3. date:將上傳的這個參數強制轉換成 datetime.date 類型,若轉換不成功則拋出異常。

    在使用 type 指定 flask_restful.inputs 數據類型時的用法如下:

    parser.add_argument(‘birthday‘,type=inputs.date,help=‘日期輸入錯誤‘)

5.4 Flask-Restful 類視圖返回內容

返回數據時候可以使用最原始的方法,返回一個字典。但是 Restful 推薦我們使用 Restful 方法,如下:

  1. 先定義一個字典,該字典定義所有要返回的參數
  2. 再使用 marshal_with(字典名) 傳入字典名稱
  3. 最後返回數據就行了,如下:

    from flask_restful import Api,Resource,fields,marshal_with
    api = Api(app)
    class Article(object):
        def __init__(self,title,content):
            self.title = title
            self.content = content
    artilce = Article(‘MYYD‘,‘wuba luba dub dub‘)
    class LoginView(Resource):
        resource_field = {
            ‘title‘: fields.String,
            ‘content‘: fields.String
        }
        @marshal_with(resource_field)
        def get(self):
            return artilce      # 可以直接返回 Article 的實例,會拿到 article 對象的兩個屬性並返回
    api.add_resource(LoginView,‘/login/‘,endpoint=‘login‘)

這樣做的好處是:

  1. 可以少寫代碼
  2. 可以規範輸出,即如果 article 對象只有 title 屬性而沒有 content 屬性,也會返回 content 的值,只不過該值被置為 None

5.5 Flask-Restful 標準返回

5.5.1 復雜結構

對於一個類視圖,可以指定好一些數據字段用於返回。指定的這些數據字段,在此後使用 ORM 模型或者自定義模型時,會自動獲取模型中的相應字段,生成 Json 數據,並返回給客戶端。對於擁有子屬性的字段而言,若想成功獲取其屬性並返回給客戶端,需要引用 fields.Nested 並在其中定義子屬性的字段。整個例子如下:

  1. 模型關系

    class User(db.Model):
        __tablename__ = ‘user‘
        id = db.Column(db.Integer,primary_key=True)
        username = db.Column(db.String(50),nullable=False)
        email = db.Column(db.String(50),nullable=False)
    
    article_tag_table = db.Table(
        ‘article_tag‘,
        db.Column(‘article_id‘,db.Integer,db.ForeignKey("article.id"),primary_key=True),
        db.Column(‘tag_id‘,db.Integer,db.ForeignKey("tag.id"),primary_key=True)
    )
    
    class Article(db.Model):
        __tablename__ = ‘article‘
        id = db.Column(db.Integer,primary_key=True)
        title = db.Column(db.String(50),nullable=False)
        content = db.Column(db.Text)
        author_id = db.Column(db.Integer,db.ForeignKey(‘user.id‘))
    
        author = db.relationship(‘User‘,backref=‘articles‘)
    
        tags = db.relationship(‘Tag‘,secondary=article_tag_table,backref=‘articles‘)
    
    class Tag(db.Model):
        __tablename__ = ‘tag‘
        id = db.Column(db.Integer,primary_key=True)
        name = db.Column(db.String(50),nullable=False)
  2. 返回時定義的數據字段

    註意這裏有三點必須實現:

    1. 導入相關包並初始化 app
    2. 定義返回數據的字段
    3. 使用裝飾器 marshal_with 傳入定義的數據字段
    from flask_restful import Api,Resource,fields,marshal_with
    api = Api(app)
    class ArticleView(Resource):
    
        article_detail = {
            ‘article_title‘: fields.String(attribute=‘title‘),
            ‘content‘: fields.String,
            ‘author‘: fields.Nested({       # 返回有子屬性的字段時要用 fields.Nested() 
                ‘username‘: fields.String,
                ‘email‘: fields.String,
                ‘age‘: fields.Integer(default=1)
            }),
            ‘tags‘: fields.Nested({         # 返回有子屬性的字段時要用 fields.Nested() 
                ‘name‘: fields.String
            })
        }
        @marshal_with(article_detail)
        def get(self,article_id):
            article = Article.query.filter_by(id=article_id).first()
            return article
5.5.2 重命名屬性

重命名屬性很簡單,就是返回的時候使用不同於模型本身的字段名稱,此操作需要借助 attribute 選項。如下所示代碼:

article_detail = {
    ‘article_title‘: fields.String(attribute=‘title‘)
}

Article 模型中的屬性原本是 title,但是要返回的字段想要命名為 article_title。如果不使用 attribute 選項,則在返回時會去 Article 模型中找 article_title 屬性,很明顯是找不到的,這樣以來要返回的 article_title 字段會被置為 Null。使用 attribute 選項後,當返回 article_title 字段時,會去 Article 模型中找 attribute 選項指定的 title 屬性,這樣就可以成功返回了。

5.5.3 默認值

當要返回的字段沒有值時,會被置為 Null,如果不想置為 Null,則需要指定一個默認的值,此操作需要借助 default 選項。如下代碼所示:

article_detail = {
    ‘article_title‘: fields.String(attribute=‘title‘)
    ‘readed_number‘: fields.Integer(default=0)
}

當想要返回一篇文章的閱讀量時,若沒有從模型中獲取到該字段的值,若不使用 default 選項則該字段會被置為 Null;若使用了該選項,則該字段會被置為 0

5.6 Flask-restful 細節

實際上,flask-restful 還可以嵌套在藍圖中使用,也能返回一個 html 模板文件。

  1. 嵌套藍圖使用

    搭配藍圖使用時,在註冊 api 時就不需要使用 app 了,而是使用藍圖的名稱,如下:

    article_bp = Blueprint(‘article‘,__name__,url_prefix=‘/article‘)
    api = Api(article_bp)

    其他的和之前一樣,不過要在主 app 文件中註冊一下藍圖。

  2. 渲染模板

    如果想使用 flask-restful 返回 html 模板,則必須使用 api.representation() 裝飾器來轉換返回數據的類型,並根據該裝飾器定義一個函數,用於返回該模板,如下:

    from flask import render_template,make_response
    
    @api.representation(‘text/html‘)
    def outPrintListForArticle(data,code,headers):  # 這裏要傳入這三個參數
        resp = make_response(data)  # 其中,data 就是模板的 html 代碼
        return resp
    
    class ListView(Resource):
        def get(self):
            return render_template(‘list.html‘)
    api.add_resource(ListView,‘/list/‘,endpoint=‘list‘)

Flask-論壇開發-4-知識點補充