$ pip install flask-sqlalchemy
配置選項列表 :css
選項 | 說明 |
---|---|
SQLALCHEMY_DATABASE_URI | 用於鏈接的數據庫 URI 。例如:sqlite:////tmp/test.db 或 mysql://username:password@server/db |
SQLALCHEMY_BINDS | 一個映射 binds 到鏈接 URI 的字典。更多 binds 的信息見 用 Binds 操做多個數據庫 。 |
SQLALCHEMY_ECHO | 若是設置爲 Ture , SQLAlchemy 會記錄全部 發給 stderr 的語句,這對調試有用。 |
SQLALCHEMY_RECORD_QUERIES | 能夠用於顯式地禁用或啓用查詢記錄。查詢記錄 在調試或測試模式自動啓用。更多信息見 get_debug_queries() 。 |
SQLALCHEMY_TRACE_MODIFYCATIONS=False #是否追蹤對象的修改
SQLALCHEMY_NATIVE_UNICODE | 能夠用於顯式禁用原生 unicode 支持。當使用 不合適的指定無編碼的數據庫默認值時,這對於 一些數據庫適配器是必須的(好比 Ubuntu 上某些版本的 PostgreSQL )。|
| SQLALCHEMY_POOL_SIZE | 數據庫鏈接池的大小。默認是引擎默認值(一般 是 5 ) |
| SQLALCHEMY_POOL_TIMEOUT | 設定鏈接池的鏈接超時時間。默認是 10 。 |
| SQLALCHEMY_POOL_RECYCLE | 多少秒後自動回收鏈接。這對 MySQL 是必要的, 它默認移除閒置多於 8 小時的鏈接。注意若是 使用了 MySQL , Flask-SQLALchemy 自動設定這個值爲 2 小時。|html
模型 表示程序使用的持久化實體. 在 ORM 中, 模型通常是一個 Python 類, 類中的屬性對應數據庫中的表.python
Flaks-SQLAlchemy 建立的數據庫實例爲模型提供了一個基類以及一些列輔助類和輔助函數, 可用於定義模型的結構.mysql
模型屬性類型 :linux
類型名 | Python類型 | 說明 |
---|---|---|
Integer | int | 普通整數,通常是 32 位 |
SmallInteger | int | 取值範圍小的整數,通常是 16 位 |
Big Integer | int 或 long | 不限制精度的整數 |
Float | float | 浮點數 |
Numeric | decimal.Decimal | 定點數 |
String | str | 變長字符串 |
Text | str | 變長字符串,對較長或不限長度的字符串作了優化 |
Unicode | unicode | 變長 Unicode 字符串 |
Unicode Text | unicode | 變長 Unicode 字符串,對較長或不限長度的字符串作了優化 |
Boolean | bool | 布爾值 |
Date | datetime.date | 日期 |
Time | datetime.time | 時間 |
DateTime | datetime.datetime | 日期和時間 |
Interval | datetime.timedelta | 時間間隔 |
Enum | str | 一組字符串 |
PickleType | 任何 Python 對象 | 自動使用 Pickle 序列化 |
LargeBinary | str | 二進制文件 |
經常使用 SQLAlchemy 列選項sql
選項名 | 說明 |
---|---|
primary_key | 若是設爲 True,這列就是表的主鍵 |
unique | 若是設爲 True,這列不容許出現重複的值 |
index | 若是設爲 True,爲這列建立索引,提高查詢效率 |
nullable | 若是設爲 True,這列容許使用空值;若是設爲 False,這列不容許使用空值 |
default | 爲這列定義默認值 |
Flask-SQLAlchemy 要求每一個模型都要定義主鍵, 這一列一般命名爲 id .shell
示例 :數據庫
關係型數據庫使用關係把不一樣表中的行聯繫起來.flask
經常使用 SQLAlchemy 關係選項 :windows
選項名 | 說明 |
---|---|
backref | 在關係的另外一個模型中添加反向引用 |
primaryjoin | 明確指定兩個模型之間使用的聯結條件。只在模棱兩可的關係中須要指定. |
lazy | 指定如何加載相關記錄。可選值以下 : |
select(首次訪問時按需加載) | |
immediate(源對象加載後就加載) | |
joined(加載記錄,但使用聯結) | |
subquery(當即加載,但使用子查詢) | |
noload(永不加載) | |
dynamic(不加載記錄,但提供加載記錄的查詢) | |
uselist | 若是設爲 Fales,不使用列表,而使用標量值 |
order_by | 指定關係中記錄的排序方式 |
secondary | 指定多對多關係中關係表的名字 |
secondaryjoin | SQLAlchemy 沒法自行決定時,指定多對多關係中的二級聯結條件 |
原理 : 在 「多」 這一側加入一個外鍵, 指定 「一」 這一側聯結的記錄.
示例代碼 : 一個角色可屬於多個用戶, 而每一個用戶只能有一個角色.
最複雜的關係類型, 須要用到第三章表, 即 關聯表 , 這樣多對多關係能夠分解成原表和關聯表之間的兩個一對多關係.
查詢多對多關係分兩步 : 遍歷兩個關係來獲取查詢結果.
代碼示例:
多對多關係仍然使用定義一對多關係的 db.relationship() 方法進行定義, 但在多對多關係中, 必須把 secondary 參數設爲 關聯表.
多對多關係能夠在任何一個類中定義, backref 參數會處理好關係的另外一側.
關聯表就是一個簡單的表, 不是模型, SQLAlchemy 會自動接管這個表.
classes 關係使用列表語義, 這樣處理多對多關係比較簡單.
Class 模型的 students 關係有 參數 db.backref() 定義. 這個關係還指定了 lazy 參數, 因此, 關係兩側返回的查詢均可接受額外的過濾器.
自引用關係
自引用關係能夠理解爲 多對多關係的特殊形式 : 多對多關係的兩邊由兩個實體變爲 一個實體.
高級多對多關係
使用多對多關係時, 每每須要存儲所聯兩個實體之間的額外信息. 這種信息只能存儲在關聯表中. 對用戶之間的關注來講, 能夠存儲用戶關注另外一個用戶的日期, 這樣就能按照時間順序列出全部關注者.
爲了能在關係中處理自定義的數據, 必須提高關聯表的地位, 使其變成程序可訪問的模型.
關注關聯表模型實現:
使用兩個一對多關係實現的多對多關係:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class User(UserMixin, db.Model): # ... followd = db.relationship("Follow", foreign_keys=[Follow.follower_id], backref=db.backref("follower", lazy="joined"), lazy="dynamic", cascade="all, delete-orphan") followrs = db.relationship("Follow", foreign_keys=[Follow.followed_id], backref=db.backref("followed", lazy="joined"), lazy="dynamic", cascade="all, delete-orphan") # 這段代碼中, followed 和 follower 關係都定義爲 單獨的 一對多關係. # 注意: 爲了消除外鍵歧義, 定義關係是必須使用可選參數 foreign_keys 指定的外鍵. 並且 db.backref() 參數並非指定這兩個關係之間的引用關係, 而是回引 Follow 模型. 回引中的 lazy="joined" , 該模式能夠實現當即從鏈接查詢中加載相關對象. # 這兩個關係中, user 一側設定的 lazy 參數做用不同. lazy 參數都在 "一" 這一側設定, 返回的結果是 "多" 這一側中的記錄. dynamic 參數, 返回的是查詢對象. # cascade 參數配置在父對象上執行的操做相關對象的影響. 好比, 層疊對象可設定爲: 將用戶添加到數據庫會話後, 要自定把全部關係的對象都添加到會話中. 刪除對象時, 默認的層疊行爲是把對象聯結的全部相關對象的外鍵設爲空值. 但在關聯表中, 刪除記錄後正確的行爲是把執行該記錄的實體也刪除, 由於這樣纔能有效銷燬聯結. 這就是 層疊選項值 delete-orphan 的做用. 設爲 all, delete-orphan 的意思是啓動全部默認層疊選項, 而且還要刪除孤兒記錄. |
能夠看作特殊的 一對多 關係. 但調用 db.relationship() 時 要把 uselist 設置 False, 把 多變爲 一 .
將 一對多 關係,反過來便可, 也是 一對多關係.
建立數據庫
db.create_all()
示例 :
$ python myflask.py shell
> from myflask import db
> db.create_all()
若是使用 sqlite , 會在 SQLALCHEMY_DATABASE_URI 指定的目錄下 多一個文件, 文件名爲該配置中的文件名.
若是數據庫表已經存在於數據庫中, 那麼 db.create_all() 不會建立或更新這個表.
更新數據庫
方法一 :
先刪除, 在建立 –> 原有數據庫中的數據, 都會消失.
方法二 :
數據庫遷移框架 : 能夠跟自動數據庫模式的變化, 而後增量式的把變化應用到數據庫中.
SQLAlchemy 的主力開發人員編寫了一個 遷移框架 Alembic, 除了直接使用 Alembic wait, Flask 程序還可以使用 Flask-Migrate 擴展, 該擴展對 Alembic 作了輕量級包裝, 並集成到 Flask-Script 中, 全部操做都經過 Flaks-Script 命令完成.
① 安裝 Flask-Migrate
$ pip install flask-migrate
② 配置
③ 數據庫遷移
a. 使用 init 自命令建立遷移倉庫.
$ python myflask.py db init # 該命令會建立 migrations 文件夾, 全部遷移腳本都存在其中.
模型的構造函數, 接收的參數是使用關鍵字參數指定的模型屬性初始值. 注意, role 屬性也可以使用, 雖然他不是真正的數據庫列, 但倒是一對多關係的高級表示. 這些新建對象的 id 屬性並無明確設定, 由於主鍵是由 Flask-SQLAlchemy 管理的. 如今這些對象只存在於 Python 解釋器中, 還沒有寫入數據庫.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
>> from myflask import db, User, Role >> 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=mod_role) >> user_david = User(username="david", role=user_role) >> admin_role.name 'Admin' >> admin_role.id None --------- >> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david]) # 把對象添加到會話中. >> db.session.commit() # 把對象寫入數據庫, 使用 commit() 提交會話. |
Flask-SQLAlchemy 爲每一個模型類都提供了 query 對象.
獲取表中的全部記錄
查詢過濾器
filter_by() 等過濾器在 query 對象上調用, 返回一個更精確的 query 對象. 多個過濾器能夠一塊兒調用, 直到獲取到所需的結果.
filter() 對查詢結果過濾,比」filter_by()」方法更強大,參數是布爾表達式
查詢過濾器 :
過濾器 | 說明 |
---|---|
filter() | 把過濾器添加到原查詢上, 返回一個新查詢 |
filter_by() | 把等值過濾器添加到原查詢上, 返回一個新查詢 |
limit() | 使用是zing的值限制原查詢返回的結果數量, 返回一個新查詢 |
offset() | 偏移原查詢返回的結果, 返回一個新查詢 |
order_by() | 根據指定條件對原查詢結果進行排序, 返回一個新查詢 |
group_by() | 根據指定條件對原查詢結果進行分組, 返回一個新查詢 |
查詢執行函數 :
方法 | 說明 |
---|---|
all() | 以列表形式返回查詢的全部結果 |
first() | 返回查詢的第一個結果,若是沒有結果,則返回 None |
first_or_404() | 返回查詢的第一個結果,若是沒有結果,則終止請求,返回 404 錯誤響應 | |
| get() | 返回指定主鍵對應的行,若是沒有對應的行,則返回 None |
get_or_404() | 返回指定主鍵對應的行,若是沒找到指定的主鍵,則終止請求,返回 404 | |錯誤響應
| count() | 返回查詢結果的數量 |
| paginate() | 返回一個 Paginate 對象,它包含指定範圍內的結果 |
單個提交
多個提交
刪除會話
事務回滾 : 添加到數據庫會話中的全部對象都會還原到他們在數據庫時的狀態.
>> db.session.rollback()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@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: session["known"] = True session["name"] = form.name.data form.name.data = "" # why empty it ? return redirect(url_for("index")) return render_template("index.html", current_time=datetime.utcnow(), form=form, name=session.get("name"), known=session.get("known")) |
paginate() 方法的返回值是一個 Pagination 類對象, 該類在 Flask-SQLAlchemy 中定義, 用於在模板中生成分頁連接.
示例代碼:
1 2 3 4 5 6 7 8 |
@main.route("/", methods=["GET", "POST"]) def index(): # ... page = request.args.get('page', 1, type=int) # 渲染的頁數, 默認第一頁, type=int 保證參數沒法轉換成整數時, 返回默認值. pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=current_app.config["FLASKY_POSTS_PER_PAGE"], error_out=False) posts = pagination.items return render_template('index.html', form=form, posts=posts,pagination=pagination) |
Flask_SQLAlchemy 分頁對象的屬性:
屬性 | 說明 |
---|---|
items | 當前分頁中的記錄 |
query | 分頁的源查詢 |
page | 當前頁數 |
prev_num | 上一頁的頁數 |
next_num | 下一頁的頁數 |
has_next | 若是有下一頁, 返回 True |
has_prev | 若是有上一頁, 返回 True |
pages | 查詢獲得的總頁數 |
per_page | 每頁顯示的記錄數量 |
total | 查詢返回的記錄總數 |
在分頁對象可調用的方法:
方法 | 說明 |
---|---|
iter_pages(left_edge=2,left_current=2,right_current=5,right_edge=2) | 一個迭代器, 返回一個在分頁導航中顯示的頁數列表. 這個列表的最左邊顯示 left_edge 頁, 當前頁的左邊顯式 left_current 頁, 當前頁的右邊顯示 right_currnt 頁, 最右邊顯示 right_edge 頁. 如 在一個 100 頁的列表中, 當前頁爲 50 頁, 使用默認配置, 該方法返回如下頁數 : 1, 2, None, 48,49,50,51,52,53,54,55, None, 99 ,100. None 表示頁數之間的間隔. |
prev() | 上一頁的分頁對象 |
next() | 下一頁的分頁對象 |
使用 Flaks-SQLAlchemy 的分頁對象與 Bootstrap 中的分頁 CSS, 能夠輕鬆的構造出一個 分頁導航.
分頁模板宏 _macros.html : 建立一個 Bootstrap 分頁元素, 即一個有特殊樣式的無序列表.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
{% macro pagination_widget(pagination,endpoint) %} <ul class="pagination"> <li {% if not pagination.has_prev %} class="disabled" {% endif %}> <a href="{% if pagination.has_prev %}{{url_for(endpoint, page=paginatin.page - 1, **kwargs)}}{% else %}#{% endif %}"> « </a> </li> {% for p in pagination,.iter_pages() %} {% if p %} {% if p == pagination.page %} <li class="active"> <a href="{{ url_for(endpoint, page=p, **kwargs) }}">{{p}}</a> </li> {% else %} <li> <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{p}}</a> </li> {% endif %} {% else %} <li class="disabled"><a href="#">…</a> </li> {% endif %} {% endfor %} <li {% if not pagination.has_next %} class="disabled" {% endif%}> <a href="{% if paginatin.has_next %}{{ url_for(endpoint, page=pagination.page+1, **kwargs) }}{% else %}#{% endif %}"> » </a> </li> </ul> {% endmacro %} |
導入使用分頁導航
1 2 3 4 5 6 |
{% extends "base.html" %} {% import "_macros.html" as macros %} ... <div class="pagination"> {{ macro.pagination_widget(pagination, ".index")}} </div> |
示例代碼 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from markdown import markdown import bleach class Post(db.Model): # ... body = db.Colume(db.Text) body_html = db.Column(db.Text) # ... @staticmethod def on_changeed_body(target, value, oldvalue, initiator): allowed_tags = ["a", "abbr", "acronym", "b", "blockquote", "code", "em", "i", "li", "ol", "pre", "strong", "ul", "h1", "h2","h3","h4","p"] target.body_html = bleach.linkify(bleach.clean(markdown(value, output_format="html"), tags=allowed_tags, strip=True)) db.event.listen(Post.body, "set", Post.on_changeed_body) # on_changed_body 函數註冊在 body 字段上, 是 SQLIAlchemy "set" 事件的監聽程序, # 這意味着只要這個類實例的 body 字段設了新值, 函數就會自動被調用. # on_changed_body 函數把 body 字段中的文本渲染成 HTML 格式, # 結果保存在 body_html 中, 自動高效的完成 Markdown 文本到 HTML 的轉換. |
當 SQLIAlchemy ORM 從數據庫查詢數據時, 默認不調用__init__
方法, 其底層實現了 Python 類的 __new__()
方法, 直接實現 對象實例化, 而不是經過 __init__
來實例化對象.
若是須要在查詢時, 依舊但願實現一些初始化操做, 可使用 orm.reconstructor()
裝飾器或 實現 InstanceEvents.load()
監聽事件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# orm.reconstructor from sqlalchemy import orm class MyMappedClass(object): def __init__(self, data): self.data = data # we need stuff on all instances, but not in the database. self.stuff = [] @orm.reconstructor def init_on_load(self): self.stuff = [] # InstanceEvents.load() from sqlalchemy import event ## standard decorator style @event.listens_for(SomeClass, 'load') def receive_load(target, context): "listen for the 'load' event" # ... (event handling logic) ... |
若是隻是但願在從數據庫查詢生成的對象中包含某些屬性, 也可使用 property
實現:
1 2 3 4 5 6 7 8 9 10 11 |
class AwsRegions(db.Model): name=db.Column(db.String(64)) ... @property def zabbix_api(self): return ZabbixObj(zabbix_url) @zabbix_api.setter def zabbix_api(self): raise ValueError("zabbix can not be setted!") |
轉自:https://www.pyfdtic.com/2018/03/19/flaskExt--flask-sqlalchemy/