flask核心知識

alphardex發表於2019-04-27

repo: github.com/alphardex/p…

flask是一個Python語言開發的web“微框架”,和django不同的是,它既沒有資料庫、也沒有表單驗證等工具它本身僅僅提供了一個WSGI的橋樑,其他東西統統靠你來定製,具有很大的靈活性

腳手架

為了迅速搭建一個像樣的flask網站,我們可以使用腳手架

之前在Github上看到cookiecutter-flask,是個不錯的選擇,但是新手可能會看不懂裡面程式碼是如何封裝的

於是本人做出了一個更user-friendly的腳手架——cookiecutter-flask-bootstrap

這個腳手架的功能大致和上個腳手架差不多,不過更加輕量化,而且結構更加清晰明瞭,best practice也基本都做到了,希望大家用的開心:d

最後還要感謝李輝大大的狼書,給了我很大的幫助

路由

路由是將特定的業務程式碼(即檢視函式)繫結到某個url上,以實現某個功能

註冊

flask中使用裝飾器來註冊路由

@app.route('/')
def index():
    return 'Hello world.'
複製程式碼

可以為路由傳遞變數,變數左邊可以帶轉換器用來轉換變數的型別

@app.route('/user/<string:username>')
def user_profile(username):
    return f'User {username}'
複製程式碼

常用的轉換器有6種:string, int, float, path, any, uuid

比較特殊的是any,格式如下(var變數只接受a, b其中的任意一值)

@app.route('/<any(a, b):var>/')
複製程式碼

如果想通過路由直接訪問檔案呢?用path轉換器和send_from_directory就行了

@app.route('/uploads/<path:filename>')
def get_image(filename):
    return send_from_directory(current_app.config['UPLOAD_PATH'], filename)
複製程式碼

構造url

使用url_for函式可以反向構建訪問路由的url

url_for('index')  # '/'
url_for('user_profile', username='alphardex')  # '/user/alphardex'
url_for('index', _external=True)  # 'http://localhost:5000/' 絕對路徑
複製程式碼

HTTP

路由預設支援GET方法,如果需要POST方法則需在route的methods中傳入

HTTP methods的用處如下:

  • GET:獲取資源
  • POST:建立資源
  • PUT:更新資源
  • DELETE:刪除資源
@app.route('/login', methods=['GET', 'POST'])
複製程式碼

如果想更改HTTP請求頭內容,那就要用到make_response

比如製作網站的RSS,就需要把響應的mimetype設定為application/xml

@app.route('/rss')
def rss():
    articles = Article.query.order_by(Article.date.desc).limit(10)
    rss = render_template('rss.xml', articles=articles)
    response = make_response(rss)
    response.mimetype = 'application/xml'
    return response
複製程式碼

模板

渲染一個模板,簡言之就是通過上下文變數來生成HTML

from flask import render_template

@app.route('/')
def index():
    greetings = 'Hello world.'
    return render_template('index.html', greetings=greetings)
複製程式碼

render_template中第一個引數是要渲染的模板檔名,其餘引數則是上下文變數

通過mustache語法將上下文變數傳入模板並渲染,同時也支援if、for等控制流語句語法,更高階的有過濾器、模板繼承、巨集等

提幾個常用的過濾器:

  • safe: 避免HTML的自動轉義,本質上是個Markup物件
  • length: 獲取變數長度
  • default: 為變數設定預設值
  • trim: 去除變數前後的空格
  • tojson: 將變數轉化為json
  • truncate: 截斷字串,常用於顯示文章摘要

網站的靜態檔案放在static資料夾中,通過反向構造url訪問

url_for('static', filename='style.css')
複製程式碼

上下文全域性變數

  • current_app:指向處理請求的app例項
  • g:global的簡寫,以object的方式儲存資訊(比如使用者登入後的使用者物件 g.user)
  • request:以dict形式儲存HTTP請求相關變數
  • session:以dict的方式儲存會話資訊(比如使用者登入後的使用者id session['user_id'])

以下是request所封裝的幾個最常用的引數,全部引數請點這裡

request.args        # GET請求的查詢字串
request.cookies     # cookies資訊
request.files       # 請求上傳的檔案
request.form        # POST請求的表單資料
request.headers     # 請求頭
request.method      # 請求型別
request.path        # 請求路徑
request.referrer    # 請求發源地址
request.remote_addr # 使用者的ip地址
request.get_json()  # 獲取api的json資料
複製程式碼

工具函式

  • abort:放棄請求
  • flash:閃現資訊,可以附帶類別
  • jsonify:將資料序列化為json,常用於設計restful api
  • redirect:重定向

工廠模式

工廠模式使得app在建立之時能同時完成以下步驟:載入配置,初始化擴充套件,註冊藍本,註冊shell上下文,以及註冊錯誤處理函式等

def create_app(config_name=None):
    if config_name is None:
        config_name = os.getenv('FLASK_CONFIG', 'development')

    app = Flask(__name__)
    app.config.from_object(config[config_name])

    register_blueprints(app)
    register_extensions(app)
    register_shell_context(app)
    register_errors(app)

    return app

def register_extensions(app):
    debugtoolbar.init_app(app)
    ...

def register_blueprints(app):
    app.register_blueprint(main_bp)
    ...

def register_shell_context(app):
    @app.shell_context_processor
    def make_shell_context():
        return {'db': db, ...}

def register_errors(app):
    @app.errorhandler(400)
    def bad_request(e):
        return render_template('errors/400.html'), 400
    ...
複製程式碼

藍本

用來實現模組化程式設計

例如一個app(名字叫flasky)通常會有以下的資料夾結構

├─flasky
│  ├─blueprints
│  ├─static
│  │  └─css
│  └─templates
│      ├─auth
│      ├─errors
│      ├─main
│      └─user
└─tests
複製程式碼

其中blueprints就是藍本資料夾,裡面存放著和templates對應的4個藍本

├─blueprints
│      auth.py
│      main.py
│      user.py
│      __init__.py
複製程式碼

這4個藍本中__init__.py負責Python的包結構,其餘三個則是app的3個功能模組:認證、主頁、使用者

以auth.py為例

from flask import Blueprint, ...
...(此處省略了一堆import)

bp = Blueprint('auth', __name__)

@bp.route('/login', methods=['GET', 'POST'])
def login():
    ...

@bp.route('/register', methods=['GET', 'POST'])
def register():
    ...

@bp.route('/logout')
def log_out():
    ...
複製程式碼

藍本也可以註冊路由,如果反向構造url的話就得加上藍本名,比如url_for('auth.login')

藍本的註冊應在工廠函式中執行,並且每個藍本可以通過url_prefix來給url新增字首

常用外掛

  • flask-admin:提供admin管理後臺
  • flask-avatars:生成使用者頭像
  • flask-ckeditor:整合富文字編輯器ekeditor
  • flask-cors:提供跨域支援
  • flask-dropzone:整合檔案上傳外掛dropzone
  • flask-login:處理使用者登陸認證邏輯
  • flask-mail:郵箱服務
  • flask-migrate:提供資料庫遷移支援
  • flask-moment:提供時間規範化支援
  • flask-mongoengine:整合mongoengine——面向mongodb的ORM
  • flask-restful:RESTful API支援
  • flask-socketio:整合socketio,常用於編寫聊天室
  • flask-sqlalchemy:整合sqlalchemy——面向標準SQL的ORM
  • flask-weasyprint:提供PDF列印功能
  • flask-whooshee:整合whooshee——全文搜尋引擎
  • flask-wtf:整合wtforms——表單支援
  • bootstrap-flask:整合bootstrap,並提供一些有用的巨集
  • faker:能生成假資料,用於測試

高階玩法

強制響應格式

API返回的一般都是json,故在每個檢視函式中呼叫jsonify將dict序列化為json

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    return jsonify({'message': 'Hello World!'})

@app.route('/foo')
def foo():
    return jsonify({'message': 'Hello foo!'})
複製程式碼

但其實沒必要這麼做,因為flask的Response是可以定製的

flask的app例項提供了response_class屬性,預設是Response

繼續查照定義,發現Response其實繼承了werkzeug裡的BaseResponse

通過查閱BaseResponse,我們可以過載Response的force_type類方法,將型別為dict的response直接jsonify,並且無需在每個檢視函式中都顯式呼叫jsonify函式了

from flask import Flask, jsonify, Response

class JSONResponse(Response):
    @classmethod
    def force_type(cls, response, environ=None):
        if isinstance(response, dict):
            response = jsonify(response)
        return super().force_type(response, environ)

app = Flask(__name__)
app.response_class = JSONResponse

@app.route('/')
def index():
    return {'message': 'Hello World!'}

@app.route('/foo')
def foo():
    return {'message': 'Hello foo!'}
複製程式碼

當然,你也可以類似地強制響應格式為xml,RSSHub就是這麼實現的

from flask import Flask, Response

class XMLResponse(Response):
    def __init__(self, response, **kwargs):
        if 'mimetype' not in kwargs and 'contenttype' not in kwargs:
            if response.startswith('<?xml'):
                kwargs['mimetype'] = 'application/xml'
        return super().__init__(response, **kwargs)

app = Flask(__name__)
app.response_class = XMLResponse

@bp.route('/feed')
def rss_feed():
    from rsshub.spiders.feed import ctx
    return render_template('main/atom.xml', ctx())
複製程式碼

全域性模板函式

有的時候你希望把某種邏輯抽象成一個函式,使得多個頁面能共用,那麼就要定義全域性模板函式了

有的網站的排序功能是這麼實現的:通過在url的查詢字串中追加sort(要排序的欄位)和order(升序還是降序)引數,在檢視函式中獲取這2個引數並進行相應的排序處理

要實現這個功能,可以編寫2個全域性函式:1個負責修改url的查詢字串,另一個負責處理給查詢排序

@bp.app_template_global()
def modify_querystring(**new_values):
    args = request.args.copy()
    for k, v in new_values.items():
        args[k] = v
    return f'{request.path}?{url_encode(args)}'

@bp.app_template_global()
def get_article_query():
    sort_key = request.args.get('s', 'date')
    order = request.args.get('o', 'desc')
    article_query = Article.query
    if sort_key:
        if order == 'asc':
            article_query = article_query.order_by(db.asc(sort_key))
        else:
            article_query = article_query.order_by(db.desc(sort_key))
    return article_query
複製程式碼

自定義路由轉換器

我們都知道flask的路由對映是儲存在app.url_map中的,因此查閱官方文件有關url_map的部分,就能輕鬆實現

from flask import Flask
from urllib.parse import unquote
from werkzeug.routing import BaseConverter

class ListConverter(BaseConverter):
    def __init__(self, url_map, separator='+'):
        super().__init__(url_map)
        self.separator = unquote(separator)

    def to_python(self, value): # 把路徑轉換成一個Python物件,比如['python', 'javascript', 'sql']
        return value.split(self.separator)

    def to_url(self, values): # 把引數轉換成符合URL的形式,比如/python+javascript+sql/
        return self.separator.join(BaseConverter.to_url(self, value) for value in values)

app = Flask(__name__)
app.url_map.converters['list'] = ListConverter

@app.route('/list1/<list:var>/')
def list1(var):
    return f'Separator: + {var}'


@app.route('/list2/<list(separator=u"|"):var>/')
def list2(var):
    return f'Separator: | {var}'
複製程式碼

官方文件僅僅用了逗號分隔符,而在這裡我們通過新增了separator屬性來實現了自定義分隔符 訪問如下連結體驗下效果

http://localhost:5000/list1/python+javascript+sql
http://localhost:5000/list2/python|javascript|sql
複製程式碼

相關文章