flask核心知識

repo: github.com/alphardex/p…javascript

flask是一個Python語言開發的web「微框架」,和django不一樣的是,它既沒有數據庫、也沒有表單驗證等工具它自己僅僅提供了一個WSGI的橋樑,其餘東西通通靠你來定製,具備很大的靈活性css

腳手架

爲了迅速搭建一個像樣的flask網站,咱們可使用腳手架html

以前在Github上看到cookiecutter-flask,是個不錯的選擇,可是新手可能會看不懂裏面代碼是如何封裝的java

因而本人作出了一個更user-friendly的腳手架——cookiecutter-flask-bootstrappython

這個腳手架的功能大體和上個腳手架差很少,不過更加輕量化,並且結構更加清晰明瞭,best practice也基本都作到了,但願你們用的開心:dgit

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

路由

路由是將特定的業務代碼(即視圖函數)綁定到某個url上,以實現某個功能web

註冊

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

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

能夠爲路由傳遞變量,變量左邊能夠帶轉換器用來轉換變量的類型mongodb

@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
複製代碼
相關文章
相關標籤/搜索