使用Flask-SQLAlchemy

使用Flask-SQLAlchemy

來自專欄 Python程序員

本文是我在學習Python過程中做的筆記,部分內容參考了《Flask Web開發》。更多筆記在個人博客精神書屋,歡迎訪問,我們一起成長: )

兩種初始化Flask-SQLAlchemy的方式

先給出SQLAlchemy的參數:

flask.ext.sqlalchemy.SQLAlchemy(app=None, use_native_unicode=True, session_options=None, metadata=None, query_class=<class flask_sqlalchemy.BaseQuery>, model_class=<class flask_sqlalchemy.Model>)

有兩種方式可以初始化Flask-SQLAlchemy對象:立即使用或根據需要添加Flask應用程序:

from flask import Flaskfrom flask_sqlalchemy import SQLAlchemy"""以下兩種方法都可以"""# 方法一:直接給SQLAlchemy傳遞app實例app = Flask(__name__)db = SQLAlchemy(app)# 方法二:創建對象,在稍後的配置工廠函數里支持它db = SQLAlchemy()def create_app(): app = Flask(__name__) db.init_app(app) ...

這兩種方式的區別在於:第一種方式的create_all()drop_all()總是可用的,而第二種方法只有當flask.Flask.app_context()存在時才可以用create_all()drop_all()

實例對象一旦創建,這個對象會包含sqlalchemy和sqlalchemy.orm中的所有函數和助手。此外,它還提供了一個名為 Model 的類,用於作為聲明模型時的delarative基類

要創建表,可以在shell中導入db實例然後調用create_all()方法:

>>> from app import db>>> db.create_all()

這裡要注意,此處的python shell必須已經把db相關的model導入context中,否則缺少相關模型create_all()不會成功。原因前面已經解釋過了。

解決方法有兩個:

每次啟動shell時將db與app這兩個Flask運行相關的運行context載入到shell上下文。

from flask_script import Manager, shellfrom flask_sqlalchemy import SLQAlchemyfrom app import create_app, dbapp = create_app(Develop)app.config[SECRET_KEY] = a secret stringapp.config[SQLALCHEMY_DATABASE_URI] = mysql://username:password@hostname:port/databasenamemanager = Manager(app)@app.shell_context_processordef make_shell_context(): return dict(app=current_app, db=db)manager.add_command("shell", Shell(make_context=make_shell_context))if __name__ == __main__: manage.run()

  1. 在manage文件里導入相關db模型,然後flask-script添加一個創建數據表的命令

# 導入所有db模型from modle import *from flask_sqlalchemy import SLQAlchemyfrom app import create_app, dbapp = create_app(Develop)app.config[SECRET_KEY] = a secret stringapp.config[SQLALCHEMY_DATABASE_URI] = mysql://username:password@hostname:port/databasenamemanager = Manager(app)@manager.add_commanddef create_all(): db.create_all()if __name__ == __main__: manage.run()

查詢方法

對於一個BaseQuery類,SQLAlchemy提供了一些查詢方法用於獲得你想要的查詢對象。

1. all() 會以列表的形式返回所有的查詢結果。

2. order_by(field[,DESC]) 根據某個欄位進行排序,然後返回所有結果。默認按升序排列,如果添加DESC參數則以降序排列

3. limit(num) 限制返回查詢結果的數量

4. offset(num) 根據偏移量返回查詢結果。如:總共查詢到10個人,偏移量是2,那麼就只返回後8個人。

5. first() 返回第一個查詢結果

6. first_or_404() 返回第一個查詢結果,如果不存在就拋出404錯誤

7. get(ident) 通過主鍵查詢

8. get_or_404(ident) 通過主鍵查詢,如果不存在就拋出404錯誤

9. paginate(page=None, per_page=None, error_out=True)。page指定當前頁數,per_page指定每一頁有多少查詢對象,error_out如果設為True,當請求的頁數超出了範圍時會拋出404錯誤。

paginate方法返回的是一個pagination類對象。這個類在Flask-SQLAlchemy中定義,這個對象包含很多屬性,用於在模板中生成分頁鏈接,下面介紹幾個常用屬性:

  • items 當前頁面中的記錄
  • page 當前頁數
  • prev_num 上一頁的頁數
  • next_num 下一頁的頁數
  • has_next 如果有下一頁返回True
  • has_prev 如果有上一頁返回True
  • pages 查詢得到的總頁數
  • per_page 每頁顯示的記錄數量
  • total 查詢返回的記錄總數

pagination類對象上還可以調用一些方法:

  • prev(error_out=False) 上一頁的分類對象
  • next(error_out=False) 下一頁的分類對象
  • iter_pages(left_edge=2, left_current=2, right_current=5, right_edge=2) 一個迭代器。返回一個在分頁導航中顯示的頁數列表。這個列表的最左邊顯示left_edge頁,最右邊顯示right_edge頁。當前頁的左邊顯示left_current頁,右邊顯示right_current頁。如一個100頁的列表中,當前頁為第50頁。則pagination.iter_pages()返回的列表為[1, 2, None, 48, 49, 50, 51, 52, 53, 54, 55, None, 99, 100]。None表示頁數之間的間隔。

關係型資料庫中表的關係

一對多

考慮下面的代碼

# 定義模型class Writer(db.Model): __tablename__ = writers id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(64),unique=True) posts = db.relationship(Article,backref=writer)class Article(db.Model): __tablename__ = articles id = db.Column(db.Integer,primary_key) title = db.Column(db.String(64)) body = db.Column(db.String(2018)) writer_id = db.Colum(db.Integer,db.ForeignKey(writers.id))

  1. 在Writer模型中,relationship()定義了兩張表之間的關係:第一個參數是子表模型名,backref屬性相當於在另一張表中也定義了一個相關關係,用於訪問父表的模型。這樣Writer實例通過posts屬性可以訪問所有與之相關的Article模型,返回一個關聯Article組成的列表。Article實例通過writer屬性可以訪問對應的Writer模型。
  2. 在Article模型中,writer_id屬性被定義為外鍵,ForeignKey()函數的含義是其所在的列的值域應當被限制在另一個表的指定列的取值範圍之內。

    這裡要說明:ForeignKey()函數的參數形式應為表名.欄位名而不是Model名.欄位名
  3. 一對多關係中,在父表模型中定義db.relationship(),用於指出和子表的關係,在子表模型中定義db.ForeignKey指向父表。

如下例,在實例化Article模型時,由relationship反向定義的writer屬性要傳入與love_python對應的的Writer實例susan。這樣就可以讓love_python與susan兩個實例關聯起來。

susan = Writer(name=susan)love_python = Article(title=love python,body=python is easy and elegantwriter=susan)

關係型資料庫是通過主鍵與外鍵確定兩張表的關係的。比如

w1 = Writer(Jack)w2 = Writer(Mike)a = Article(hello,world,writer=w1)b = Article(hi,i like python,writer=w1)c = Article(haha,i like flask,writer=w2)db.session.add_all([w1,w2,a,b,c])db.session.commit()print(w.posts) # 會訪問a和b,而不訪問c

當w查詢posts時,sqlalchemy會從Article模型中尋找外鍵與w.id相同的實例並返回這些對象。

多對一

多對一關係中模型的定義與一對多類似,但是要將ForeignKey定義在父表中,即多的一方。

class Writer(db.Model): __tablename__ = writers id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(64),unique=True) article_id = db.Column(db.Integer,db.ForeignKey(articles.id) posts = db.relationship(Article,backref=writer)class Article(db.model): __tablename__ = articles id = db.Column(db.Integer,primary_key=True) title = db.Column(db.String(64)) body = db.Column(db.String(2018))

一對一

要讓兩張表是一對一關係,定義模型方式類似於一對多。只需要在的模型中其relationship()的uselist參數設為False。

對於多對一關係,要改成一對一關係也很簡單,只要用backref()函數在的一方定義一個關係,並且將urslist設為False即可。

class Writer(db.Model): __tablename__ = writers id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(64),unique=True) article_id = db.Column(db.Integer,db.ForeignKey(articles.id) posts = db.relationship(Article,backref=db.backref(writer,uselist=False)

多對多

要定義兩張表的多對多關係,這時候光用兩張表就不夠了。要引入第三張表。

舉個例子,現在要定義課程與學生之間的關係。由於一個課程對應多個學生,一個學生也對應對個課程,這時候就不再是簡單的一對多或者多對一而是多對多關係了。為了解決這個問題,我們引入第三張表Reflection,這張表定義了學生的id,對應的課程id與學生選這門課程的時間。這樣一來,如果我們想要知道小明選了什麼課,只需要在Reflection中根據小明的id找出對應的課程id,再通過課程id在Class中找到對應課程就OK啦~同時,通過Reflection我們還可以知道小明在什麼時候選了這門課。

class Reflection(db.Model): __tablename__ = reflections id = db.Column(db.Integer,primary_key=True) student_id = db.Column(db.Integer,db.ForeignKey(students.id)) class_id = db.Column(db.Integer,db.ForeignKey(classes.id)) timestamp = db.Column(db.DateTime,default=datetime.utcnow)class Student(db.Model): __tablename__ = students id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(64)) age = db.Column(db.Integer) classes = db.relationship(Reflection,backref=db.backref(students,lazy=joined),lazy=dynamic)class Class(db.Model): __tablename__ = classes id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(64)) student = db.relationship(Reflection,backref=db.backref(classes,lazy=joined),lazy=dynamic)

值得一提的是,在定義Reflection中回引模型的屬性時用了backref()方法,並且將回引屬性定義為joined載入。joined載入會在調用Reflection的同時找出對應的Student模型和Class模型,換言之,此時Reflection中的student和class直接指向對應的實例。這樣就避免了selected載入導致的僅在調用Reflection.student或Reflection.class才載入相應模型。因為從資料庫中載入模型是很耗費時間的,用joined一次就把所有模型都調用出來了,而selected需要調用多次,換言之,這樣提高了效率。

定義好的模型的關係,我們就可以試著進行操作了:

先創建學生和課程實例

Mike = Student(name=Mike,age=18)English = Class(name=English)db.session.add(Mike)db.session.add(English)db.session.commit()

將學生與課程之間的關係添加到Reflection中

f = Reflection(students=Mike,classes=English)db.session.add(f)db.session.commit()

當我們想要查詢學生的課程時,通過Student.classes獲取到Reflection對象,再通過Reflection.class就可以查到該學生的課了。

student = Student.query.filter_by(name=Mike).first()classes = student.classes.class.all()

聯結查詢

在上面的例子中,我們為了獲取學生的課程,執行了多次查詢,這樣的效率太低了,最好是一次就直接把結果查詢出來。我們可以考慮把Class和Reflection結合起來,然後分別過濾Reflection中student_id與class_id,這樣就得到一張包含了學生和相應課程的表。這種操作就叫做聯結查詢。

class Student(db.Model): ...... @property def getclass(self): return Reflection.query.join(Class,Class.id==Reflection.class_id).filter(Reflection.student_id==self.id)

下面對這行代碼進行分析:

1. Reflection.query返回Reflection對象

2. join(Class,Class.id==Reflection.class_id) 聯結Class與Reflection表,並且將Class.id與Reflection.class_id的值對應起來,相當於sql語句中的on

3. filter(Reflection.student_id==self.id) 對這張臨時表進行過濾:只有Reflection.student_id與當前學生實例id相同的會留下來

這樣,我們就可以使用這樣臨時的表來獲取想要的內容了。

推薦閱讀:

sqlalchemy中用db.create_all()無法建表?
如何理解python的sqlalchemy這種orm框架?
遇到一個問題,請各位給講解一下sqlalchemy中的backref?

TAG:Python | Flask | SQLAlchemy |