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
複製程式碼