request.from
能獲得 POST 要求中提交的表單數據
Flask-WTF 擴大可以把處理 Web 表單的進程變成1種愉悅的體驗
默許情況下,Flask-WTF 能保護所有表單免受跨站要求捏造的攻擊,為了實現 CSRF 保護,Flask-WTF 需要程序設置1個密鑰,會使用這個密鑰生成加密令牌,再用令牌驗證要求中表單數據的真偽
設置密鑰的方法如示例所示
app = Flask(__name__)
app.config['SECRET_KEY'] = 'This is a secret key'
app.config
字典可用來存儲框架、擴大和程序本身的配置變量,這個對象還提供了1些方法可以從文件或環境中導入配置值。SECRTET_KEY 配置變量是通用密鑰,可在 Flask 和多個第3方擴大中使用。
為了增強安全性,密鑰不應當直接寫入代碼,而要保存在環境變量中
使用 Flask-WTF 時,每一個 Web 表單都由1個繼承自 Form 的類表示,這個類定義表單中的1組字段,每一個字段都用對象表示,字段對象可附屬1個或多個驗證函數
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('What is your name?', validators=[Required()])
submit = SubmitField('Submit')
表單的每一個屬性都定義為類的屬性,類變量的值是相應字段類型的對象,StringField 類表示屬性為 type="text"
的 元素,SubmitField 類表示屬性為 type="submit"
的 元素
字段構造函數的第1個參數是把表單渲染成 HTML 時使用的標號,可選參數 validators
指定1個由驗證函數組成的列表,在接受用戶提交的數據之前驗證數據,驗證函數Required()
確保提交的字段不為空
Form 基類由 Flask-WTF 擴大定義,所以從 flask.ext.wtf 中導入,字段和驗證函數卻可以直接從 WTForms 包中導入
字段類型 | 說明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密碼文本字段 |
HiddenField | 隱藏文本字段 |
DateField | 值為datatime.date格式 |
DateTimeField | 值為datatime.datetime格式 |
IntegerField | 值為整數 |
DecimalField | 值為decimal.Decimal |
FloatField | 值為浮點數 |
BooleanField | 復選框,值為 True 和 False |
RadioField | 1組單選框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表可以多選 |
FileField | 文件上傳字段 |
SubmitField | 表單提交按鈕 |
FormField | 把表單作為字段嵌入另外一個表單 |
FieldList | 1組指定類型的字段 |
WTForms 內建的驗證函數以下表所示
驗證函數 | 說明 |
---|---|
驗證電子郵件地址 | |
EqualTo | 比較兩個字段的值,經常使用于密碼確認 |
IPAddress | 驗證IPv4網絡地址 |
Length | 驗證輸入字符串的長度 |
NumberRange | 驗證輸入的值在數字范圍內 |
Optional | 無輸入值時跳過其他驗證函數 |
Required | 確保字段中有數據 |
Regexp | 使用正則表達式驗證輸入值 |
URL | 驗證URL |
AnyOf | 確保輸入值在可選值列表中 |
NoneOf | 確保輸入值不在可選值列表中 |
在 index.html
中
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
import
允許導入模版中的元素并用在多個模版中,導入的 bootstrap/wtf.html
文件中定義了1個使用 Bootstrap 渲染 Flask-WTF 表單對象的輔助函數。
wtf.quick_form()
函數的參數為 Flask-WTF 表單對象,使用 Bootstrap 的默許樣式渲染傳入的表單
該文件中使用了模版條件語句,在 Jinja2 中的條件語句格式為{% if condition %}...{% else %}...{% endif %}
,如果條件的計算結果為 True,那末渲染 if 和 else 指令之間的值,如果為 False,則渲染 else 和 endif 指令之間的值。此處,如果沒有模版變量 name 就渲染字符串 Stranger。
下面使用 wtf.quick_form()
函數渲染 NameForm 對象
在使用了 manager.run()
以后,啟動的方式有所變化,必須附加參數 runserver
,如上圖所示
from flask import Flask, render_template
from flask.ext.script import Manager
from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
manager = Manager(app)
bootstrap = Bootstrap(app)
moment = Moment(app)
class NameForm(Form):
name = StringField('What is your name?', validators=[Required()])
submit = SubmitField('Submit')
![icone.png-22.2kB][1]
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
@app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name)
if __name__ == '__main__':
manager.run()
index.html
沿用上1小節的便可,運行后可以得到以下頁面
methods
通知 Flask 在 URL 映照中把這個視圖函數注冊為 GET 和 POST 要求的處理程序,如果沒指定 methods
參數,只會注冊為 GET 要求的處理程序
在視圖函數中創建1個 NameForm 類實例用于表示表單,如果數據能被所有的驗證函數接受,那末form.validate_on_submit()
的返回值為 True,這個函數的返回值決定是重新渲染表單還是處理表單提交的數據
1個要求的詳解:**用戶提交表單后,1個 POST 要求過來,validate_on_submit()
會調用 name 字段上附屬的 Required() 驗證函數,如果名字不為空就可以通過驗證,函數返回 True。
用戶輸入的名字通過字段的 data 屬性獲得,在 if 語句中把名字賦值給局部變量 name,再把 data 屬性置為空字符串,從而清空表單字段**
如果提交表單時,沒有填入內容, Required 驗證函數會捕獲毛病并顯示提示,良好的擴大讓程序具有更加強大的功能
當我過1段時間后點擊了刷新按鈕時,閱讀器會彈出提示詢問我是不是想要重新提交表單,由于閱讀器發出的最后1個要求是 POST 要求。
使用重定向作為 POST 要求的響應,而不是使用常規響應可以規避這個問題,但是如此這般又引入了新問題。使用 form.name.data
獲得用戶輸入的名字,在要求結束時數據丟失。這里我們需要用到用戶會話,把數據存儲在用戶會話中,在要求之間記住數據
默許情況下,用戶會話保存在客戶端 Cookie 中,使用設置的 SECRET_KEY 進行加密簽名,如果篡改了 Cookie 中的內容,簽名就會失效,會話也就失效了
def index():
form = NameForm()
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
修改其視圖函數就能夠完成功能,之前的名字被保存在局部變量中,現在保存在用戶會話里,在需要的時候可以取出使用
redirect()
是個輔助函數,用來生成 HTTP 重定向響應,重定向可以寫根地址也能夠使用 URL 生成函數 url_for()
,推薦使用生成函數生成 URL,這樣保證了 URL 和定義的路由兼容,而且修改路由名字后仍然可用
url_for()
函數的唯逐一個必須指定的參數是端點名,即路由的內部名字,默許情況下,路由的端點是相應視圖函數的名字
提示消息使用flash()
函數實現
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
將視圖函數修改成上面這樣便可
僅調用flash()
函數其實不能把消息顯示出來,程序使用的模版要渲染這些消息,最好在基模版中渲染 Flash 消息,這樣所有頁面都能使用這些消息, Flask 把get_flashed_messages()
函數開放給模版,用來獲得并渲染消息,將base.html
修改以下
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
NoSQL 數據庫1般使用集合代替表,使用文檔代替記錄
易用性
數據庫抽象層,又被稱作object-relational mappers (ORMs) 或 object-document mappers (ODMs),提供了從高級對象到低級數據庫實體的直接轉換,使用起來固然會更加方便。
性能
ORMs和ODMs把對象轉化為數據庫實體會有1些開消,但多數時候這個開消可以疏忽不計的。通常來講,使用ORMs和ODMs帶來的工作效能提升常常大于所帶來的性能消耗,因此我們沒有甚么理由謝絕ORMs和ODMs。通常比較公道的做法是用數據庫抽象層來做經常使用的操作,而只在某些需要特別優化的地方使用原生的數據庫操作。
可移植性
所選擇的數據庫在生產和部署環境下是不是可用是1個必須斟酌的因素,比如你想要把你的利用部署在云平臺上,你固然應當首先知道該平臺支持哪些數據庫。
ORMs和ODMs還能帶來的其他1個便利。雖然多數數據庫提供了1個抽象層,但是還有1些更高階的抽象層提供了只用1套接口便可操作多種數據庫的功能。最好的例子就是SQLAlchemy ORM,它提供了1系列關系型數據庫引擎的支持,比如MySQL、Postgres 和 SQLite。
本書選擇了 Flask-SQLAlchemy
使用 pip install flask-sqlalchemy
來安裝 Flask-SQLAlchemy
Flask-SQLAlchemy 數據庫使用 URL 指定,流行的數據庫 URL 指定表以下
數據庫引擎 | URL |
---|---|
MySQL | mysql://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 |
hostname 是數據庫服務所在的主機,database 指定使用的數據庫名,username 和 password 表示數據庫密令
程序使用的數據庫 URL 被在配置在 Flask 的 config 對象的 SQLALCHEMY_DATABASE_URI 中,還有另外一個重要的屬性被配置在 SQLALCHEMY_COMMIT_ON_TEARDOWN 中用來在每次要求結束時候自動提交數據庫改動,Flask-SQLAlchemy 官方文檔提供了更多配置選項。以下是1個配置 SQLite 數據庫的例子:
from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = \
'sqlite:///C:\\Users\\Avenger\\Desktop\\test\\database.db'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
千萬設置好['SQLALCHEMY_DATABASE_URI']
這1行,否則會報錯,注意 Windows 下是3個/
模型是指那些在程序中被持久化的對象,在 ORM 的環境下,1個模型是1個典型的 Python 類,類中的屬性對應數據庫表中的列。
Flask-SQLAlchemy 數據庫的實例提供了1個 model 的基類和1系列的工具方法方法來定義他們的結構,在前面示例中的 roles 和 users 表可以定義成以下的 Role 和 User models
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return '<User %r>' % self.username
類變量 __tablename__
定義在數據庫中使用的表名,如果沒有定義 __tablename__
會被指定1個默許的名字,推薦自己來命名。其余的類變量都是該模型的屬性,被定義為 db.Column
db.Column
類構造函數的第1個參數是數據庫列和模型屬性的類型,其余的參數指定屬性的配置選項,以下表所示
這兩個模型都定義了 __repr()__
方法,返回1個具有可讀性的字符串表示模型,可在調試時使用
最經常使用的列類型
類型名 | Python 類型 | 說明 |
---|---|---|
Integer | int | 普通整數,1般是32位 |
SmallInteger | int | 取值范圍小的整數,1般是16位 |
BigInteger | int or long | 不限制精度的整數 |
Float | float | 浮點數 |
Numeric | decimal.Decimal | 定點數 |
String | str | 變長字符串 |
Text | str | 變長字符串,對較長或不限長度的字符串做了優化 |
Unicode | unicode | 變長 Unicode 字符串 |
UnicodeText | unicode | 變長 Unicode 字符串,對較長或不限長度的字符串做了優化 |
Boolean | bool | 布爾值 |
Date | datetime.date | 日期 |
Time | datetime.time | 時間 |
DateTime | datetime.datetime | 日期和時間 |
Interval | datetime.timedelta | 時間間隔 |
Enum | str | 1組字符串 |
PickleType | Any Python object | 自動使用 Pickle 序列化 |
LargeBinary | str | 2進制文件 |
最經常使用的列選項
選項名 | 說明 |
---|---|
primary_key | 如果設為 True,這列就是表的主鍵 |
unique | 如果設為 True,這列不允許出現重復值 |
index | 如果設為 True,為這列創建索引,提升查詢效力 |
nullable | 如果設為 True,這列允許使用空值,如果設為 False,這列不允許使用空值 |
default | 為這列設定默許值 |
Flask-SQLAlchemy 要求每一個模型都要定義主鍵,這1列常常命名為 id
1對多關系在模型類中的表示方法以下
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role')
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return '<User %r>' % self.username
關系使用 users 屬性代表這個關系的面向對象視角,對1個 Role 類的實例,其 users 屬性將返回
"users = db.relationship('User', backref='role')"
代表這個關系的面向對象的視圖。能返回指定角色的相干人員列表。當外鍵由多個組成時,還需要斟酌1些其他參數:
選項名 | 說明 |
---|---|
backref | 在關系的另外一個模型中添加反向援用 |
primaryjoin | 明確指定兩個模型之間使用的聯結條件,只在模棱兩可的關系中需要指定 |
lazy | 指定如何加載相干記錄,詳細見附表 |
uselist | 如果設為 False 不使用列表,而使用標量值 |
order_by | 指定關系中記錄的排序方式 |
secondary | 指定多對多關系中關系表的名字 |
secondaryjoin | SQLAlchemy 沒法自行決定時,指定多對多關系中的2級聯結條件 |
附表
可選值 | 說明 |
---|---|
select | 首次訪問時按需加載 |
immediate | 源對象加載后就加載 |
joined | 加載記錄,使用聯結 |
subquery | 立即加載,使用子查詢 |
noload | 永不加載 |
dynamic | 不加載記錄,但提供加載記錄的查詢 |
if __name__ == '__main__':
db.create_all()
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User(username='john', role=admin_role)
user_susan = User(username='susan', role=user_role)
user_david = User(username='david', role=user_role)
manager.run()
db.create_all()
用來創建數據庫,以后的語句都是插入行(在 Role 表和 users 表中)
我們在 SQLiteStudio 來連接這個數據庫看是不是創建成功
可以發現這個表和列都已創建OK了,但是此時這些對象其實不是真實的數據庫內容,只在 Python 中,還未寫入數據庫,因此 id 等都還沒有賦值
db.session.add(admin_role)
db.session.add(mod_role)
db.session.commit()
可使用這類方法來寫入數據庫,commit()
之前都是將對象添加到會話中,commit()
來提交這個會話就能夠寫入數據庫了。添加對象到會話時,我們可簡寫為以下
db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])
db.session.commit()
可以直接看出,相干數據已寫入了數據庫中
使用把相干改動放在會話中提交,在寫入會話時產生毛病,全部會話都會失效,這樣可以免由于部份更新而致使數據庫不1致
數據庫會話也能夠回滾,db.session.rollback()
可以把添加到數據庫會話中的所有對象都還原到初始狀態
在數據庫會話中調用 add()
方法也能更新模型,使用 delete()
可以刪除
if __name__ == '__main__':
db.create_all()
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User(username='john', role=admin_role)
user_susan = User(username='susan', role=user_role)
user_david = User(username='david', role=user_role)
db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
db.session.commit()
admin_role.name = 'Administrator'
db.session.add(admin_role)
db.session.delete(mod_role)
db.session.commit()
manager.run()
在原有項目上測試,會遇到問題,不能重新更新原來的值,重新創建1個空文件連接數據庫寫入就行了,簡單粗魯的辦法
可以看出,更新和刪除操作都已可以履行了!插入、刪除、更新都必須提交數據庫會話才能履行。
Role.query.all()
User.query.all()
User.query.filter_by(role=user_role).all()
str(User.query.filter_by(role=user_role))
user_role = Role.query.filter_by(role=user_role)
每一個模型都有 query 對象,使用過濾器可以配置 query 對象進行更精確的數據庫查詢,第3個就是查詢角色為“user”的所有用戶,第4條是查看SQLAlchemy
為查詢生成的原生 SQL 語句
user_role = Role.query.filter_by(name='User').first()
可以查詢名為 User 的用戶角色。多個過濾器可以1起調用來獲得所需結果
經常使用過濾器:
過濾器 | 說明 |
---|---|
filer() | 把過濾器添加到原查詢上,返回1個新查詢 |
filter_by() | 把等值過濾器添加到原查詢上,返回1個新查詢 |
limit() | 使用指定的值限制原查詢結果返回的結果數量,返回1個新查詢 |
offset() | 偏移原查詢返回的結果,返回1個新查詢 |
order_by() | 根據指定條件對原查詢結果進行排序,返回1個新查詢 |
group_by() | 根據指定條件對原查詢結果進行分組,返回1個新查詢 |
在查詢上利用了過濾器后,通過調用all()
履行查詢,以列表的情勢返回結果,全部查詢方法以下
過濾器 | 說明 |
---|---|
all() | 以列表情勢返回查詢的所有結果 |
first() | 返回查詢的第1個結果,如果沒有結果,返回 None |
first_or_404() | 返回查詢的第1個結果,如果沒有結果,終止要求,返回 404 響應毛病 |
get() | 返回指定主鍵對應的行,如果沒有對應的行,返回 None |
get_or_404() | 返回指定主鍵對應的行,如果沒有指定的主鍵,終止要求,返回 404 響應毛病 |
count() | 返回查詢結果的數量 |
paginate() | 返回1個 Paginate 對象,它包括指定范圍內的結果 |
下面這個例子分別從關系的兩端查詢角色和用戶之間的1對多關系
users = user_role.users
在履行 user_role.users 時,隱含的查詢會調用 all() 返回1個用戶列表, query 對象是隱藏的,因此沒法指定更精確的查詢過濾器,改進以下
我們可以通過修改關系的設置,加入lazy = 'dynamic'
參數,從而制止自動履行查詢
class Role(db.Model)
#...
users = db.relationship('User', backref='role', lazy='dynamic')
#...
這樣配置關系后,users = user_role.users
會返回1個還沒有履行的查詢,因此可以在其上添加過濾器了
user_role.users.order_by(User.username).all()
user_role.users.count()
修改視圖函數以下
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
# 對提交上上來的表單查看數據庫中是不是已存在
user = User.query.filter_by(username=form.name.data).first()
if user is None:
# 如果在數據庫中不存在,提交進數據庫會話中
user = User(username=form.name.data)
db.session.add(user)
session['known'] = False
else:
# 存在的用戶,在網站會話中給出已知,用于在 index.html 中進行判斷
session['known'] = True
# 用表單中的名字刷新網站會話
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'),
known=session.get('known', False))
在啟動這個 app 之前,需要初始化這個數據庫嘛,在 manager.run()
之前,
if __name__ == '__main__':
db.create_all()
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User(username='john', role=admin_role)
user_susan = User(username='susan', role=user_role)
user_david = User(username='david', role=user_role)
db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
db.session.commit()
admin_role.name = 'Administrator'
db.session.add(admin_role)
db.session.delete(mod_role)
db.session.commit()
print(Role.query.all())
print(User.query.all())
print(User.query.filter_by(role=user_role).all())
print(str(User.query.filter_by(role=user_role)))
print(user_role.users.order_by(User.username).all())
manager.run()
index.html
也需要修改,以下
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
運行以后,在首頁提交表單,可以看到提交的名字成功被插入到了數據庫中
再次提交這個名字的話,就會看到Happy to see you again!
的歡迎提示
from flask.ext.script import Manager, Shelldef make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))
在 Python 腳本中,增加上述語句,可讓 Flask-Script 的 shell 命令自動導入特定的對象,若想把對象添加到導入列表中,我們要為 shell 命令注冊1個 make_context 回調函數
make_shell_context()
函數注冊了程序、數據庫實例和模型,因此這些對象能直接導入 shell
履行 python hello.py shell
就引入了這些對象
更新表的唯1方法就是刪除舊表 db.drop_all()
更新表的更好辦法是使用數據庫遷移框架, SQLAlchemy 的研發了1個叫做 Alembic,也能夠使用 Flask-Migrate 擴大,這個擴大輕量級包裝了 Alembic
首先進行安裝 pip install flask-migrate
再進行初始化:
導入:from flask.ext.migrate import Migrate, MigrateCommand
初始化:
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
為了導出數據庫遷移指令,Flask-Migrate 提供了1個 MigrateCommand 類,可附加到 Flask-Script 的 manager 對象中
修改腳本參數如上如所示,運行可得
在腳本的目錄下創建了 migrations 文件夾,所有遷移腳本都寄存在其中
數據庫遷移倉庫中的文件要和程序的其他文件1起納入版本控制
在 Alembic 中,數據庫遷移用遷移腳本表示。腳本中有兩個函數,分別是 upgrade()
和 downgrade()
。 upgrade() 函數把遷移中的改動利用到數據庫中, downgrade()
函數則將改動刪除。 Alembic 具有添加和刪除改動的能力,因此數據庫可重設到修改歷史的任意1點。
我們可使用 revision
命令手動創建 Alembic 遷移,也可以使用 migrate
命令自動創建。手動創建的遷移只是1個骨架, upgrade()
和 downgrade()
函數都是空的,開發者要使用Alembic 提供的 Operations
對象指令實現具體操作。自動創建的遷移會根據模型定義和數據庫當前狀態之間的差異生成 upgrade()
和 downgrade()
函數的內容。
自動創建的遷移不1定總是正確的,自動生成遷移腳本后1定要進行檢查
python hello.py db migrate -m "initial migration"
自動創建遷移腳本的 migrate
命令
檢查并修正好遷移腳本后,我們可使用 db upgrade
命令把遷移利用到數據庫中
第1個遷移的作用和調用 db.create_all()
方法1樣,但在后面的遷移中 upgrade
可以把改動利用到數據庫中,且不影響其中保存的數據
書中說數據庫的名字是 data.sqlite
但是我沒有成功,我的數據庫的名字是 database.db
應當沒有區分,只是提起
上一篇 如何添加網絡打印機