在上一節中,咱們編寫了抓取新聞的爬蟲腳本,天天早上八點定時抓取新聞保存到 MySQL數據庫中。直接用DataGrip連下數據庫,就能夠查看爬取到的新聞了。不過, 並非咱們想要的最終形態。我但願新聞篩選的工做能夠直接在手機上進行,而不是 每次都要打開電腦,打開DataGrip黑乎乎的頁面,篩選,複製粘貼。在寫這個APP以前, 要先學點web的東東,寫點接口。Python中有比較流行的兩個Flask和Django,筆者選擇 的比較輕量級的Flask。在寫《如何用Python投機倒把幾天「暴富」》,有些同窗私信我有 沒有Flask的教程推薦。索性本節就來過一過Flask,下一節再來利用Flask來寫API接口和 靜態頁面,以及直接生成公號文章樣式。內容較多,比較枯燥,建議先收藏後慢慢觀看~css
一個輕量級的「微內核」Web應用框架,基於Werkzeug實現的WSGI套件和Jinja2模板引擎, 內核精簡,易於擴展,花費較少的學習成本,便可能開發一個簡單的網站。html
相關文檔:python
- Flask官網:flask.pocoo.org/
- Flask Github倉庫:github.com/pallets/fla…
- Flask官方文檔:flask.pocoo.org/docs/1.0
- Flask官方文檔(中文版):dormousehole.readthedocs.io/en/latest/
- Flask學習資源:github.com/humiaozuzu/…
直接經過pip命令安裝便可:mysql
pip install flask
複製代碼
這裏要注意「全局安裝」和「虛擬環境安裝」的區別,以前有不少讀者都問過這樣的問題:jquery
我明明在命令行裏已經執行了pip install xxx庫,可是進去pycharm,仍是提示模塊找不到?nginx
對此,有兩種可選的解決方案:git
我的更傾向於第一種,爲了解決維護不一樣項目對應不一樣版本的問題,Python使用了虛擬環境的概念, 在虛擬環境中安裝第三方包只會做用到虛擬環境中,全局的Python解釋器不受影響。在Python3中, 虛擬環境已成爲一個內置模塊,建立一個帶虛擬環境的文件示例以下:github
mkdir Test
cd Test
python -m venv venv
複製代碼
執行完上述命令後,Python會運行venv包,建立一個venv的虛擬環境,上面的兩個venv參數分別爲:web
虛擬環境建立後,須要激活後才能進入,經過下述命令「激活虛擬環境」:正則表達式
source venv/bin/activate
複製代碼
執行完後會看到終端前綴多了個venv,激活虛擬環境後,終端會話的環境配置就會被修改, 此時鍵入Python或者pip,實際上調用的都是虛擬環境中的Python解釋器。一個應用場景: 打開多個終端調試多個應用,每一個終端窗口能夠激活不一樣的虛擬環境,且不相互干擾。
注:若是你使用的是Python2或者Windows系統,若是想使用虛擬環境,要先經過pip命令 安裝先安裝一波virtualenvwrapper:pip install virtualenvwrapper。而後建立虛擬環境: virtualenv venv,最後是激活虛擬環境:venv\Scripts\activate.bat。
新建一個hello.py的腳本,內容以下:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello Flask!"
if __name__ == "__main__":
app.run()
複製代碼
在終端鍵入下述命令執行腳本:
python hello.py
複製代碼
運行後能夠看到輸出以下信息:
瀏覽器打開:http://127.0.0.1:5000,能夠看到如圖所示的Hello Flask!
服務啓動後就會進入輪詢,等待並處理請求,知道程序被終止,也能夠直接按Ctrl+C停掉。 接着逐行解析一波代碼:
__name__
, 讓flask.helpers.get_root_path函數經過傳入這個名字肯定程序的根目錄,以便得到靜態文件和 模板文件的目錄。該模塊的做用是:經過命令行的形式來操做Flask,使用命令行傳參,而不只僅是經過 app.run()函數來傳。直接經過pip命令便可安裝此模塊:
pip install flask-script
複製代碼
新建一個manage.py的文件:
from flask_script import Manager
from flask import Flask
app = Flask(__name__)
manager = Manager(app)
if __name__ == '__main__':
manager.run()
複製代碼
接着命令行鍵入:
python manage.py runserver -h ip -p 端口
複製代碼
除此以外的參數還有:-d(開啓調試模式),-r(代碼修改後自動加載),--help(查看幫助信息)。
上面這種:
@app.route("/")
def hello():
return "Hello Flask!"
複製代碼
定義了URL到Python函數間的映射關係,這種映射關係就叫路由。
有時,可能有一些具備相同規則的URL,好比:
app.route("/login")
app.route("/register")
複製代碼
咱們能夠把這類URL抽象成一個URL模式,示例以下:
@app.route('/<do>')
def hello(do):
return "<h1>當前請求:%s </h1>" % do
複製代碼
Flask支持這種動態形式的路由,動態部分默認是字符串,你也能夠指定參數類型, 好比只接受整數:<int:id>,更多規則以下表所示:
字段標記 | 描述 | 示例 |
---|---|---|
string | 默認,接受任何沒有斜杆"/"的文本 | <string:name> |
int | 整數 | <in:id> |
float | 浮點數 | <float:price> |
path | 和string相似,但也接受斜槓 | <path:address> |
uuid | 只接受uuid字符串 | <string:uuid> |
any | 能夠指定多種路徑,但須要傳入參數 | <any(int,string):params> |
另外有一點要注意:惟一URL,好比下面表明的是兩個不一樣的URL:
CodingBoy.io/article/
CodingBoy.io/article
複製代碼
在Flask中,可使用url_for()函數來構造URL,接受一個視圖函數名做爲第一個參數, 也接受對應URL規則變量部分的命名參數,未知變量部分會被添加到URL末尾做爲查詢 參數。這裏務必注意一點:操做的是函數,不是路由裏的路徑!!!! 經過函數構建,而不是直接用字符串拼接,主要出於下面兩個目的:
使用示例以下:
with app.test_request_context():
print(url_for('hello', uid=1))
print(url_for('hello', uid=2, kind='測試'))
複製代碼
輸出結果以下:
/?uid=1
/?uid=2&kind=%E6%B5%8B%E8%AF%95
複製代碼
若是你想生成一個絕對路徑,能夠添加「_external=True」參數。
注:test_request_context能夠在交互模式下產生請求上下文,不用app.run() 來運行這個項目,直接執行也會有Falsk上下文
HTTP支持多種請求方法,默認狀況下,路由只響應GET請求,若是不信能夠自行用 PostMan對接口發起一個POST請求,結果如圖所示:
響應碼是405,表示不容許使用這種請求方法請求此URL,能夠直接在 app.route裝飾器中設置methods參數來修改。修改後的代碼示例以下:
@app.route("/", methods=['GET', 'POST'])
def hello():
return "Hello Flask!"
複製代碼
固然也支持其餘的請求方法:PUT,HEAD,DELETE,OPTIONS,PATCH,想支持多種方法用逗號隔開便可。
在Web開發中,咱們常常會用到模板引擎,什麼是模板?就是一個包含響應文本的文件, 用佔位符(變量)標識動態部分,告訴模板引擎,其具體值須要從使用的數據中獲取。
在前面的例子中,視圖函數的主要做用是生成請求的響應,而實際開發中,視圖函數有 兩個做用:「處理業務邏輯」和「返回響應內容」。在大型項目中,若是把業務邏輯 和表現內容放在一塊兒的話,會增長代碼的複雜度和維護成本。而模板的做用就是: 「承擔視圖函數返回響應內容這一部分的職責,從而使得代碼結構清晰,耦合度低。」 使用真實值替換變量,再(控制)返回最終獲得的字符串,這個過程稱做「渲染」。 Flask中默認使用「Jinja2」這個模板引擎來渲染模板。
經過一個簡單的例子,引入模板,下面是一個沒有使用模板的簡單程序:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/<user>")
def hello(user):
if user == 'admin':
return "<h1>管理員,您好!<h1>"
else:
return "<h1>%s, 您好!</h1>" % user
if __name__ == "__main__":
app.run()
複製代碼
接着咱們使用Jinja2模板進行改寫,默認狀況下,Flask會在「項目的templates子文件夾」 中尋找模板,咱們新建一個templates文件夾,而後新建一個index.html,內容以下:
"<h1>{{name}},您好!<h1>"
複製代碼
接着修改下hello.py文件,修改後的內容以下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/<user>")
def hello(user):
if user == 'admin':
return render_template("index.html", user="管理員")
else:
return render_template("index.html", user=user)
if __name__ == "__main__":
app.run()
複製代碼
接着運行hello.py,瀏覽器鍵入如下地址,對應輸出結果以下:
http://127.0.0.1:5000/admin # 輸出結果:管理員,您好!
http://127.0.0.1:5000/jay # 輸出結果:jay,您好!
複製代碼
以上就是一個簡單的模板使用示例,經過調用Flask提供的render_template函數, 生成了一個模板,第一個參數是模板的名稱,隨後的參數是鍵值對,表示模板中 變量對應的真實值。接着詳細講解一波Jinja2的語法:
Jinja2使用**{{變量名}}
**來表示一個變量,除了基本數據類型外還可使用列表,字段 或對象等複雜類型,示例以下:
<h1> 帳號:{{ user['name'] }},密碼:{{ user['passwd'] }}
複製代碼
Jinja2提供了多種控制結構,好比常見的判斷和循環結構,用於修改模板的渲染流程。 咱們把上面判斷的邏輯也放到模板裏,示例以下:
{# 註釋,不會輸出到瀏覽器中 #}
{% if user == 'admin' or user == '管理員' %}
<h1> 管理員,您好! </h1>
{% else %}
<h1> {{ user }},您好!</h1>
{% endif %}
{# 循環打印 #}
{% for num in num_list %}
<h2>{{ num }}</h2>
{% endfor %}
複製代碼
接着修改下hello.py文件:
@app.route("/<user>")
def hello(user):
return render_template("index.html", user=user, num_list=[1, 2, 3])
複製代碼
鍵入不一樣的URL,對應瀏覽器的輸出結果以下:
http://127.0.0.1:5000/admin
管理員,您好!
1
2
3
http://127.0.0.1:5000/jay
jay,您好!
1
2
3
複製代碼
在Python中若是有一些很經常使用的代碼,咱們會習慣性地抽取成一個函數,在Jinji2中, 可使用宏來實現。語法以下:
# 建立宏
{% macro 標籤名(key=value) %}
經常使用代碼
{% end macro %}
# 調用宏
{{ 標籤名(key=value) }}
複製代碼
若是宏比較多,能夠抽到單獨的HTML中,再import進來。
{% import 'xxx.html' as 別名 %}
{{ 別名.標籤名(key=value) }}
複製代碼
另外一種代碼複用的方式:模板繼承,和Python中類繼承同樣,須要一個基模板,用 {% block XXX %}{% endblock %}
標識一個代碼塊,能夠在子模塊中重載。 代碼示例以下:
# 基模板:base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block head %}
<title>{% block title %} Title {% endblock %} </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
# 子模板:son.html
{% extends "base.html" %}
{% block title %}Son{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block body %}
<h1>Hello Flask!</h1>
{% endblock %}
複製代碼
代碼簡述:
第一行代碼使用extends命令,代表該模板繼承自base.html,接着重寫了title,head和 body代碼塊,上面的super()函數用於獲取基模板原先的內容。
能夠用include語句包含一個模板,在渲染時會把include語句對應位置添加被包含的模板, 使用示例以下:
{% include 'header.html' %}
複製代碼
另外還可使用「ignore missing」標記,若是模板不存在,Jinja2會忽略此語句,示例以下:
{% include 'header.html' ignore missing %}
複製代碼
可使用set標籤來進行賦值,示例以下:
{% set a,b = 1,2}
<h1>{{ a }}{{ b }}</h1>
複製代碼
過濾器的本質就是函數,有時須要修改,格式化或者執行變量間的計算,而在模板中是不能 直接調用Python中的某些方法的,能夠用到過濾器。模板的過濾器支持鏈式調用:
{{ "hello flask" | reverse | upper}},
複製代碼
這串代碼就是反轉+轉換成大寫,常見的內建過濾器有字符串和列表兩種。 字符串操做過濾器以下表所示:
過濾器 | 描述 |
---|---|
safe | 禁用轉義 |
capitalize | 把變量值的首字母轉成大寫,其他字母轉小寫 |
lower | 把值轉成小寫 |
upper | 把值轉成大寫 |
title | 把值中的每一個單詞的首字母都轉成大寫 |
reverse | 字符串反轉 |
format | 格式化輸出 |
striptags | 渲染以前把值中全部的HTML標籤都刪掉 |
truncate | 字符串截斷 |
列表操做過濾器以下表所示:
過濾器 | 描述 |
---|---|
first | 取第一個元素 |
last | 取最後一個元素 |
length | 獲取列表長度 |
sum | 列表求和 |
sort | 列表排序 |
直接在py文件中編寫,代碼示例以下:
# 1.定義過濾器
def do_listreverse(li):
temp_li = list(li)
temp_li.reverse()
return temp_li
# 2.添加自定義過濾器
app.add_template_filter(do_listreverse, 'listreverse')
複製代碼
總結:
宏,繼承和包含都用實現代碼複用,宏功能相似於函數,能夠傳參,須要定義調用; 繼承本質是代碼替換,通常用來實現多個頁面中重複不變的區域;而包含是直接將 目標模板文件整個渲染出來。
在Flask中,HTTP請求被封裝成了Request對象,而HTTP響應則被封裝成了Response對象。 所以Flask應用開發的邏輯處理,都是基於這兩個對象。
Flask會將WSGI服務器轉發的http請求數據封裝爲一個Request對象,這個對象中包含了請求的 相關信息,能夠經過下表中的屬性來獲取對應的信息。
屬性 | 描述 | 數據類型 |
---|---|---|
form | 記錄請求中的表單數據。 | MultiDict |
args | 記錄請求中的查詢參數。 | MultiDict |
cookies | 記錄請求中的cookie。 | Dict |
headers | 記錄請求中的報文頭。 | EnvironHeaders |
method | 記錄請求使用的HTTP方法。 | string |
environ | 記錄WSGI服務器轉發的環境變量。 | Dict |
url | 記錄請求的URL地址。 | string |
瀏覽器以GET請求的方式提交的表單數據,Flask會將其存儲在request實例的args,也能夠用values屬性 來查詢,讀取代碼示例以下:
# coding=utf-8
from flask import Flask, request, json
app = Flask(__name__)
@app.route("/index")
def index():
return ''' <form method="GET" action="/login"> <input type="text" placeholder="帳號" name="user"> <br /> <input type="text" placeholder="密碼" name="pawd"> <br /> <input type="submit" value="登陸"> </form> '''
@app.route("/login")
def login():
msg = ""
if 'user' in request.args:
msg += request.args['user'] + ':'
msg += request.values.get('pawd','')
return msg
if __name__ == "__main__":
app.run(debug=True)
複製代碼
代碼運行後,打開:http://127.0.0.1:5000/index,瀏覽器:
輸入帳號密碼後,跳轉到:http://127.0.0.1:5000/login?user=zpj&pawd=123, 瀏覽器:
瀏覽器以GET請求的方式提交的表單數據,Flask會將其存儲在request實例的form中,可使用[]操做符 讀取指定鍵值。讀取代碼示例以下:
# coding=utf-8
from flask import Flask, request, json
app = Flask(__name__)
@app.route("/index")
def index():
return ''' <form method="POST" action="/login"> <input type="text" placeholder="帳號" name="user"> <br /> <input type="text" placeholder="密碼" name="pawd"> <br /> <input type="submit" value="登陸"> </form> '''
@app.route("/login", methods=['POST'])
def login():
msg = ""
msg += request.form['user'] + ':'
msg += request.form['pawd']
return msg
if __name__ == "__main__":
app.run(debug=True)
複製代碼
代碼運行後,打開:http://127.0.0.1:5000/index,瀏覽器:
輸入帳號密碼後,跳轉到:http://127.0.0.1:5000/login,瀏覽器:
和Request對應,Response用於給瀏覽器發送響應信息,根據視圖函數的返回結果。這個視圖函數 就是咱們路由下面的函數,視圖函數的返回值會自動轉換成一個響應對象,轉換邏輯以下:
def hello():
resp = make_response("Hello Flask!", 250)
return resp
複製代碼
另外,如今的API接口都是返回JSON格式的,能夠用jsonify包裝下,修改後的示例代碼以下:
# coding=utf-8
from flask import Flask, make_response, jsonify
from werkzeug.wrappers import Response
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def hello():
resp = make_response({'code': '200', 'msg': '請求成功', 'data': [{'data_1': ['數據', '數據'], 'data_2': ['數據', '數據']}]})
return resp
class JsonResponse(Response):
@classmethod
def force_type(cls, response, environ=None):
if isinstance(response, dict):
response = jsonify(response)
return super(JsonResponse, cls).force_type(response, environ)
app.response_class = JsonResponse
if __name__ == "__main__":
app.run(debug=True)
複製代碼
請求接口輸出以下:
{
"code": "200",
"data": [
{
"data_1": [
"數據",
"數據"
],
"data_2": [
"數據",
"數據"
]
}
],
"msg": "請求成功"
}
複製代碼
也能夠用Flask的json模塊的dumps()函數將數組或字典對象轉換爲JSON字符串,代碼示例以下:
data = {'code': '200', 'msg': '請求成功', 'data': [{'data_1': ['數據', '數據'], 'data_2': ['數據', '數據']}]}
return json.dumps(data),200,[('Content-Type','application/json;charset=utf-8')]
複製代碼
set_cookie(
key, //鍵
value='', //值
max_age=None, //秒爲單位的cookie壽命,None表示http-only
expires=None, //失效時間,datetime對象或unix時間戳
path='/', //cookie的有效路徑
domain=None, //cookie的有效域
secure=None,
httponly=False)
複製代碼
Web開發中常常須要處理重定向和會話,Flask中內建了「redirect」和「session」來對它們進行處理。
頁面重定向很是常見,最經常使用的就是登錄狀態判斷,若是沒登錄將網頁重定向到登陸頁,Flask中可使用redirect 對象對其進行處理,狀態碼默認爲302,能夠傳入code參數來修改,通常是:301,302,303,305和307, 簡單的代碼示例以下:
# coding=utf-8
from flask import Flask, redirect
app = Flask(__name__)
user_name = ''
@app.route('/article')
def article():
if user_name == '':
# 若是用戶名爲空,重定向跳轉到登陸頁
return redirect('/login')
else:
return "文章頁"
@app.route("/login")
def login():
global user_name
user_name = 'admin'
return "登陸成功!"
if __name__ == "__main__":
app.run(debug=True)
複製代碼
運行後操做流程以下:
瀏覽器鍵入:http://127.0.0.1:5000/article
自動跳轉到:http://127.0.0.1:5000/login,顯示登陸成功
再次訪問:http://127.0.0.1:5000/article,顯示文章頁
複製代碼
咱們能夠把數據存儲在用戶會話(session)中,用戶會話是一種私有存儲,默認狀況下保存在客戶端cookie中。 會話主要爲了解決兩個問題:「訪問者的標識和訪問者信息記錄」。瀏覽器第一次訪問服務器,服務器在其 cookie中設置一個惟一的會話ID,瀏覽器後續對服務器的訪問頭中將自動包含該信息,服務器經過這個ID號來 區分不一樣的訪問者。session依賴於cookie,通常存儲在服務器,Flask提供了session對象來操做用戶會話, 可使用[]操做符讀取或者設置指定鍵值,默認狀況下,Flask將會話對象加密後存儲在客戶端的cookie裏, 所以必需要應用實例的secret_key屬性配置一個加密種子才能使用session。用法示例以下:
# 設置session
session['name'] = 'jay'
# 讀取session
session.get('name')
# 配置加密種子(兩種方法二選一)
app.secret_key = '123456'
app.config['SECRET_KEY']='123456'
複製代碼
靜態文件就是那些不會被改變的文件,例如:圖片,CSS樣式文件,JavaScript腳本文件和字體文件等。 Flask默認會在根目錄中名爲static的子目錄中尋找靜態文件,因此若是須要用到靜態文件能夠建立一個 static的文件夾,而後把靜態文件丟裏面。能夠參考下面這樣的結構來組織項目:
static/
css/
lib/
bootstrap.css
style.css
home.css
js/
lib/
jquery.js
chart.js
home.js
img/
logo.svg
favicon.ico
複製代碼
另外,不要在模板中寫死靜態文件路徑,應該使用url_for函數生成路徑,示例以下:
url_for('static', filename='css/style.css')
複製代碼
固然,若是你想修改靜態文件的真實目錄,能夠在Flask構造函數中傳入參數:static_folder='文件夾名'。 另外,爲了得到更好的處理能力,建議使用Nginx或其餘Web服務器管理靜態文件,圖片這類資源能夠 託管到CDN平臺上。(好比七牛雲)
藍圖(Blueprint),定義了可用於單個應用的視圖,模板,靜態文件等等的集合。通俗點理解就是 一個實現應用模塊化的好工具,使用藍圖能使得項目層次更加清晰,更易於開發和維護,一般做用於 相同URL前綴的路由。先來看一個沒使用藍圖的示例:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/user/index')
def user_index():
return 'user_index'
@app.route('/user/show')
def user_show():
return 'user_show'
@app.route('/user/add')
def user_add():
return 'user_add'
@app.route('/admin/index')
def admin_index():
return 'admin_index'
@app.route('/admin/show')
def admin_show():
return 'admin_show'
@app.route('/admin/add')
def admin_add():
return 'admin_add'
if __name__ == "__main__":
app.run(debug=True)
複製代碼
上面的代碼挺整齊的,不過有幾個問題:
咱們使用藍圖來把user和admin拆分紅兩個不一樣的.py文件,admin.py 文件內容以下:
# coding=utf-8
from flask import Blueprint
admin = Blueprint('admin', __name__,url_prefix='/admin')
@admin.route('/index')
def admin_index():
return 'admin_index'
@admin.route('/show')
def admin_show():
return 'admin_show'
@admin.route('/add')
def admin_add():
return 'admin_add'
複製代碼
user.py 文件內容以下:
# coding=utf-8
from flask import Blueprint
user = Blueprint('user',__name__)
@user.route('/index')
def user_index():
return 'user_index'
@user.route('/show')
def user_show():
return 'user_show'
@user.route('/add')
def user_add():
return 'user_add'
複製代碼
註冊藍圖,hello.py的代碼內容以下:
# coding=utf-8
from flask import Flask
from admin import *
from user import *
app = Flask(__name__)
app.register_blueprint(admin)
app.register_blueprint(user, url_prefix='/user')
if __name__ == "__main__":
print(app.url_map)
app.run(debug=True)
複製代碼
利用app.url_map函數,查看全部的路由,打印結果以下:
Map([<Rule '/admin/index' (GET, HEAD, OPTIONS) -> admin.admin_index>,
<Rule '/admin/show' (GET, HEAD, OPTIONS) -> admin.admin_show>,
<Rule '/admin/add' (GET, HEAD, OPTIONS) -> admin.admin_add>,
<Rule '/user/index' (GET, HEAD, OPTIONS) -> user.user_index>,
<Rule '/user/show' (GET, HEAD, OPTIONS) -> user.user_show>,
<Rule '/user/add' (GET, HEAD, OPTIONS) -> user.user_add>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
複製代碼
url_prefix
這個參數用於設置request.url
中的url前綴,另外只有知足前綴的請求才會 經過註冊的藍圖的視圖方法處理請求並返回。能夠寫在子模塊中,也能夠在register_blueprint
註冊藍圖的時候傳入,只需傳入一次!以後打開http://127.0.0.1:5000/admin/index,能夠看到 如圖所示的結果:
有時在處理請求先後,執行某些特定代碼是很是有用的,這就用到了請求鉤子,好比:請求前建立db連接, 驗證用戶身份等,flask提供了註冊通用函數的功能,只須要寫一個請求鉤子——函數,整個程序實例全局都被應用, 好比請求前驗證用戶狀態的例子,沒登錄跳轉登陸頁面等。鉤子函數須要藉助Flask的全局變量g,g做爲中間變量, 在鉤子函數和視圖函數間傳遞數據。
g,global,g對象是專門用來保存用戶數據的,存儲的數據在全局均可以使用。代碼示例以下:
from flask import g, request
@app.route('/')
def index():
user = request.args.get('user')
g.user = user # 保存用戶數據
複製代碼
Flask提供下述四種鉤子函數:
寫一個程序來驗證下鉤子函數的執行流程(運行後,訪問兩次):
127.0.0.1 - - [30/Aug/2018 10:53:42] "GET / HTTP/1.1" 200 -
before_first_request
before_request
after_request
teardown_request
127.0.0.1 - - [30/Aug/2018 10:53:45] "GET / HTTP/1.1" 200 -
before_request
after_request
teardown_request
複製代碼
上下文至關於一個容器,保存了Flask程序運行過程當中的一些信息,根據管理機制分爲兩種:
請求上下文(RequestContext) Request:請求的對象,封裝了Http請求的內容; Session:根據請求中的cookie,重載訪問者相關的會話信息。
程序上下文(AppContext) g:處理請求時用做臨時存儲的對象,保存的是當前請求的全局變量,不一樣的請求會有不一樣的全局變量! current_app:當前運行程序的程序實例,保存的是應用程序的變量,好比可使用current_app.name獲取當前應用的名稱, 也能夠在current_app中存儲一些配置信息,變量等,使用示例:current_app.text = 'value'。
Flask中,而關於上下文的管理能夠分爲三個階段:
在開發中,後臺發生異常,但又不想把異常顯示給用戶看,或者須要同一處理的時候,可使用abort()函數 主動拋出異常,再捕獲異常,而後返回一個美化後的頁面,最多見的就是404了,代碼示例以下:
@app.route("/test")
def test():
abort(404)
@app.errorhandler(404)
def error(e):
return "一個精美的404頁面"
複製代碼
代碼執行後,瀏覽器鍵入:http://127.0.0.1:5000/test,能夠看到如圖所示的結果:
另外,你也能夠把全部異常處理些寫到一個藍圖中,代碼示例以下:
# coding=utf-8
from flask import Blueprint, abort
exception = Blueprint('exception', __name__)
@exception.errorhandler(404)
def error(e):
return "一個精美的404頁面"
# 註冊藍圖
from error import exception
app.register_blueprint(exception, url_prefix='/error')
複製代碼
使用對象映射關係(Object-Relational Mapper)ORM框架來操做數據庫。所謂的ORM框架就是:
「將底層的數據操做指令抽象成高層的面向對象操做」
不用再寫繁瑣的SQL操做語句,利用ORM框架能夠簡化成對Python對象的操做。
表映射成類
,行做爲實例
,字段做爲屬性
.
ORM在執行對象操做時會將對應操做轉換爲數據庫原生語句。Python中用得最普遍的ORM框架是SQLAlchemy。
直接使用pip命令安裝便可
pip install flask-sqlalchemy
複製代碼
另外SQLAlchemy自己沒法操做數據庫,依賴於pymysql等第三方庫,裏面有個Dialect模塊專門用於 和數據API交流,根據配置文件的不一樣而調用不一樣的數據庫API,從而實現對數據庫的操做。數據庫使用 URL限定,常見的數據庫引擎與其對應的URL以下表所示:
數據庫引擎 | URL |
---|---|
MySQL | mysql+pymysql://username:password@hostname/database |
Postgres | postgresql://username:password@hostname/database |
SQLite (Unix,開頭四個斜線) | sqlite:////absolute/path/to/database |
SQLite (Windows) | sqlite:///c:/absolute/path/to/database |
Postgres | postgresql://username:password@hostname/database |
Oracle | oracle://username:password@hostname/database |
參數簡述:
鏈接MySQL數據庫的代碼示例以下:
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
rs = con.execute("SELECT 1")
print(rs.fetchone())
複製代碼
輸出結果以下:
(1,)
複製代碼
另外還能夠經過下面這樣的方法來初始化SQLAlchemy,代碼示例以下:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
app = Flask(__name__)
db.init_app(app)
複製代碼
sqlalchemy支持直接執行原生的SQL語句,代碼示例以下:
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
con.execute("CREATE TABLE IF Not Exists note(_id INT AUTO_INCREMENT PRIMARY KEY, tran TEXT, status int)")
con.execute("INSERT INTO note(tran, status) VALUES ('吃飯', 1)")
con.execute("INSERT INTO note(tran, status) VALUES ('睡覺', 1)")
rs = con.execute("SELECT * FROM note")
for row in rs:
print(row)
複製代碼
代碼輸出結果以下:
(1, '吃飯', 1)
(2, '睡覺', 1)
複製代碼
演示一波Flask-SQLAlchemy的基本操做:
# models.py
from manager import db
class Note(db.Model):
__tablename__ = 'note'
_id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
tran = db.Column(db.TEXT)
status = db.Column(db.INT,default=0)
db.create_all() # 建立表
複製代碼
from model import Note
from manager import db
def create_note():
# 建立一個新對象
note1 = Note()
note1.tran = "吃飯"
note1.status = "1"
note2 = Note()
note2.tran = "睡覺"
note2.status = "2"
# 將新建筆記添加到數據庫會話中
db.session.add(note1)
db.session.add(note2)
# 將數據庫會話中的變更提交到數據庫中,若是不commit,數據庫內容是不會變化的
db.session.commit()
create_note()
複製代碼
def delete_note():
# 獲取筆記對象(這裏是獲取_id=1的記錄)
note = Note.query.filter_by(_id=1).first()
# 刪除筆記
db.session.delete(note)
# 提交數據庫會話
db.session.commit()
delete_note()
複製代碼
def update_note():
# 獲取筆記對象(這裏是獲取_id=2的記錄)
note = Note.query.filter_by(_id=2).first()
# 修改筆記內容
note.tran = "打豆豆"
# 提交數據庫會話
db.session.commit()
update_note()
複製代碼
說到查詢,必然有查詢條件,SQLAlchemy提供了以下表所示的經常使用查詢函數:
函數 | 描述 | 使用示例 |
---|---|---|
filter_by | 精確查詢 | filter_by(xxx='xxx') |
filter | 模糊查詢 | filter(xxx.endWith('xxx')) |
get(主鍵) | 根據主鍵查詢,通常爲id | get(1) |
not_() | 邏輯非,也能夠直接把==換成!= | not_(xxx='xxx') |
and_() | 邏輯與 | and_(xxx='xxx') |
or_() | 邏輯或 | or_(xxx='xxx') |
in_() | 在某個範圍裏 | XXX.xxx.in_((1,2,3)) |
notin_() | 不在某個範圍內 | XXX.xxx.notin_((1,2,3)) |
first() | 返回查詢到的一個對象 | XXX.query.first() |
all() | 返回查詢到的全部對象 | XXX.query.all() |
order_by() | 排序 | XXX.order_by(xxx.xxx.desc()) |
limit() | 限制返回條數 | XXX.limit(3) |
offset() | 設置偏移量 | XXX.offset() |
count() | 返回記錄的總條數 | xxx.count() |
代碼示例以下:
from sqlalchemy import not_, or_
def query_all():
notes = Note.query.all()
print("查詢所有數據:", notes)
note = Note.query.filter(not_(or_(Note.tran == '吃飯', Note.tran == '睡覺'))).first()
print(note._id, ":", note.tran, ":", note.status)
if __name__ == '__main__':
# 先插入幾條數據
create_note()
create_note()
query_all()
複製代碼
輸出結果以下:
查詢所有數據: [<Note 2>, <Note 3>, <Note 4>, <Note 5>, <Note 6>]
2 : 打豆豆 : 2
複製代碼
Flask中通常不會直接用原始表單,而是經過Flask-WTF擴展,它簡單繼承了WTForms,包括CSRF (跨域請求僞造),驗證表單數據的功能,文件上傳以及Google內嵌的驗證碼。
字段類型 | 說明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密碼文本字段 |
HiddenField | 隱藏文本字段 |
DateField | 文本字段,值爲datetime.date格式 |
DateTimeField | 文本字段,值爲datetime.datetime格式 |
IntegerField | 文本字段,值爲整數 |
DecimalField | 文本字段,值爲decimal.Decimal |
FloatField | 文本字段,值爲浮點數 |
BooleanField | 複選框,值爲True和False |
RadioField | 一組單選框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可選擇多個值 |
FileField | 文件上傳字段 |
SubmitField | 表單提交按鈕 |
FormField | 把表單做爲字段嵌入另外一個表單 |
FieldList | 一組指定類型的字段 |
驗證函數 | 說明 |
---|---|
驗證電子郵件地址 | |
EqualTo | 比較兩個字段的值,經常使用於要求輸入兩次密碼進行確認的狀況 |
IPAddress | 驗證IPv4網絡地址 |
Length | 驗證輸入字符串的長度 |
NumberRange | 驗證輸入的值在數字範圍內 |
Optional | 無輸入值時跳過其餘驗證函數 |
Required | 確保字段中有數據 |
Regexp | 使用正則表達式驗證輸入值 |
URL | 驗證URL |
AnyOf | 確保輸入值在可選值列表中 |
NoneOf | 確保輸入值不在可選列表中 |
接着咱們來編寫一個註冊表單的例子:
# coding=utf-8
from flask import Flask, request, render_template
from flask_wtf import FlaskForm
# 導入自定義表單須要的字段
from wtforms import SubmitField, StringField, PasswordField
# 導入表單驗證
from wtforms.validators import DataRequired, EqualTo
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'
# 自定義表單類
# StringField和PasswordField用於區分文本框類型
# 第一個參數是label值,第二個參數validators是要驗證的內容
class RegisterForm(FlaskForm):
username = StringField('用戶名:', validators=[DataRequired()])
password = PasswordField('密碼:', validators=[DataRequired()])
password2 = PasswordField('確認密碼:', validators=[DataRequired(), EqualTo('password', '兩次輸入的密碼不一致!')])
submit = SubmitField('註冊')
@app.route("/register", methods=['GET', 'POST'])
def register():
# 實例化註冊表單類
register_from = RegisterForm()
if request.method == 'POST':
# 獲取請求參數參數
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
# 調用validation_on_submit,一次性執行完全部驗證函數的邏輯
if register_from.validate_on_submit():
return '註冊成功!'
else:
return '先後密碼不一致!'
return render_template('register.html', form=register_from)
if __name__ == "__main__":
print(app.url_map)
app.run(debug=True)
複製代碼
# templates/register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{# 設置scrf_token #}
{{ form.csrf_token() }}
{{ form.username.label }} {{ form.username }}<br>
{{ form.password.label }} {{ form.password }}<br>
{{ form.password2.label }} {{ form.password2 }}<br>
<br>
{{ form.submit }}
</form>
</body>
</html>
複製代碼
輸入一波同樣的密碼和不同的密碼,瀏覽器的輸出結果以下所示:
另外有一點要注意:
使用Flask-WTF須要配置參數SECRET_KEY,CSRF_ENABLED是爲了**CSRF(跨站請求僞造)**保護。 SECRET_KEY用於生成加密令牌,當CSRF激活的時候,該設置會根據設置的密鑰生成加密令牌。
Flask基礎學得差很少了,接着咱們來規範下項目結構,一個比較簡單通用的項目結構如圖:
簡述下結構:
建立流程:
在__init__.py
中初始化app實例,代碼以下:
from flask import Flask
app = Flask(__name__)
複製代碼
views.py中寫個簡單的index路由:
from app import app
from flask import render_template
@app.route('/')
def index():
return render_template("index.html")
複製代碼
templates文件夾建立一個index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello Flask!</h1>
</body>
</html>
複製代碼
接着鍵入:python manage.py runserver,瀏覽器打開:http://127.0.0.1:5000/
項目結構基本就弄好了,接着咱們把項目丟到遠程服務器上。
有兩種可選的方法,最簡單的就是經過FTP/SFTP工具直接傳。另一種是把項目託管到 相似於Github這類代碼託管平臺,而後直接ssh連雲服務器,經過git clone命令把項目 拷貝到服務器上,這裏筆者直接用的第一種方法,把項目上傳到雲服務器上。
Nginx是一款輕量級、性能強、佔用資源少,能很好的處理高併發的反向代理軟件。 Flask自帶的Web Server只能開單線程,本身測試還行,放到線上就不行了,這裏 咱們用到Nginx,直接經過apt-get命令進行安裝,命令以下:
apt-get install nginx
複製代碼
安裝完後,外網訪問服務器的公網ip,出現下述頁面說明安裝成功:
Nginx安裝完,會默認建立一個目錄:/var/www/,直接經過命令把咱們的 項目移動到這個路徑下。
mv AutoPubNews /var/www/AutoPubNews
複製代碼
WSGI是一種WEB服務器,或者叫網關接口,Web服務器(如nginx)與應用服務器 (如uWSGI)通訊的一種規範(協議)。而uWSGI實現了WSGI的全部接口,是一個 快速、自我修復、開發人員和系統管理員友好的服務器。uWSGI代碼徹底用C編寫, 效率高、性能穩定。舉個例子:uWSGI把HTTP協議轉化成WSGI協議,讓Python能夠 直接使用。直接鍵入pip命令安裝:
pip install uwsgi
複製代碼
通常都是能直接安裝完成的,若是出錯了能夠試試先安裝libpython3.x-dev, 好比筆者的版本是3.5:
apt-get install libpython3.5-dev
複製代碼
接着配置一下uwsgi,在項目裏新建一個config.ini做爲配置文件:
vim config.ini
複製代碼
添加下述內容:
[uwsgi]
# uwsgi 啓動時所使用的地址與端口
socket = 127.0.0.1:8001 # 可使用其餘端口
# 指向網站目錄
chdir = /var/www/AutoPubNews
# python 啓動程序文件
wsgi-file = manage.py
# python 程序內用以啓動的 application 變量名
callable = app
# 處理器數
processes = 4
# 線程數
threads = 2
#狀態檢測地址
stats = 127.0.0.1:5000
複製代碼
接着執行下述命令:
uwsgi config.ini
複製代碼
出現: Stats server enabled on 127.0.0.1:5000,表明正常啓動。
接着配置下Nginx,不要去動默認的nginx.conf,直接將:/etc/nginx/sites-available/default
覆蓋掉,新建default文件,添加下述內容:
server {
listen 80;
server_name _;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8001; # 指向uwsgi 所應用的內部地址,全部請求將轉發給uwsgi 處理
uwsgi_param UWSGI_PYHOME /home/www/AutoPubNews/venv; # 指向虛擬環境目錄
uwsgi_param UWSGI_CHDIR /home/www/AutoPubNews; # 指向網站根目錄
uwsgi_param UWSGI_SCRIPT manager:app; #
}
}
複製代碼
接着鍵入下述命令重啓加載下nginx配置:
sudo service nginx restart
複製代碼
接着啓動uwsgi,而後就能夠經過服務器的公網ip直接訪問咱們的項目了:
每次訪問項目都用ip,顯得有些繁瑣,咱們能夠買個域名作下映射耍耍。 域名直接買就好,須要備案,搞定後打開域名管理頁,找到剛買的域名 點擊解析
而後點擊「添加記錄」會出現如圖所示的對話框,記錄值那裏填你的雲服務器公網ip便可。
此時就能夠直接經過域名來訪問咱們的項目了。
行吧,關於Flask速成就這麼都,下節咱們來利用Flask編寫API接口,動態生成頁面等。 有疑問的歡迎在評論區留言,謝謝~
Tips:公號目前只是堅持發早報,在慢慢完善,有點心虛,只敢貼個小圖,想看早報的能夠關注下~