flask 定义数据库关系(一对多) --
定義關(guān)系
在關(guān)系型數(shù)據(jù)庫中,我們可以通過關(guān)系讓不同表之間的字段建立聯(lián)系。一般來說,定義關(guān)系需要兩步,分別是創(chuàng)建外鍵和定義關(guān)系屬性。在更復(fù)雜的多對多關(guān)系中,我們還需要定義關(guān)聯(lián)表來管理關(guān)系。下面我們學(xué)習(xí)用SQLAlchemy在模型之間建立幾種基礎(chǔ)的關(guān)系模式。
?
配置python shell上下文
在上面的操作中,每一次使用flask shell命令啟動python shell后都要從app模塊里導(dǎo)入db對象和相應(yīng)的模型類。為什么不能把他們自動集成到python shell上下文里呢?就像flask內(nèi)置的app對象一樣。這當然可以實現(xiàn),我們可以使用app.shell_context_processor裝飾器注冊一個shell上下文處理函數(shù)。它和模板上下文處理函數(shù)一樣,也需要返回包含變量和變量值字典。
?
app.py: 注冊shell上下文處理函數(shù)
?
@app.shell_context_processor def make_shell_context():return dict(db=db, Note=Note) # 等同于{'db': db, 'Note': Note}?
當你使用flask shell命令啟動python shell時,所有使用app.shell_context_processor裝飾器注冊的shell上下文處理函數(shù)都會被自動執(zhí)行,這會將db和Note對象推送到python shell上下文里:
>>> db <SQLAlchemy engine=sqlite:///D:\flask\FLASK_PRACTICE\DataBase\data.db> >>> Note <class 'app.Note'>?
在下面演示各種數(shù)據(jù)庫關(guān)系時,將編寫更多的模型類。在示例程序中,都使用shell上下文處理函數(shù)添加到shell上下文中,因此你可以直接在python shell使用,不用手動導(dǎo)入。
?
?
一對多
我們將以作者和文章來演示一對多關(guān)系:一個作者可以寫多篇文章。一對多關(guān)系如下圖:
?
?
在示例程序中,Author類用來表示作者,Article類用來表示文章。
app.py: 一對多關(guān)系示例
? class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(70), unique=True) class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)body = db.Column(db.Text)?
我們將在這兩個模型之間建立一個一對對關(guān)系,建立這個一對多關(guān)系的目的是在表示作者的Author類中添加一個關(guān)系屬性articles,作為集合(collection)屬性,當我們對特定的Author對象調(diào)用articles屬性會返回所有的Article對象。下面來介紹如何一步步定義這個一對多關(guān)系。
1、定義外鍵
定義關(guān)系的第一步是創(chuàng)建外鍵。外鍵是(foreign key)用來在A表存儲B表的主鍵值以便和B表建立聯(lián)系的關(guān)聯(lián)字段。因為外鍵只能存儲單一數(shù)據(jù)(標量),所以外鍵總是在“多”這一側(cè)定義,多篇文章屬于同一個作者,所以我們需要為每篇文章添加外鍵存儲作者的主鍵值以指向?qū)?yīng)的作者。在Article模型中,我們定義一個author_id字段作為外鍵:
class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)body = db.Column(db.Text)author_id = db.Column(db.Integer, db.ForeignKey('authod.id'))#指定顯示內(nèi)容,否則默認顯示<表名 主鍵id>def __repr__(self):return '<Article %r>' % self.title?
這個字段使用db.ForeignKey類定義為外鍵,傳入關(guān)系另一側(cè)的表名和主鍵字段名,即author.id。實際的效果是將article表的authod_id的值限制為author表的id列的值。它將用來存儲author表中記錄的主鍵值,如下圖:
?
?
外鍵字段的命名沒有限制,因為要連接的目標字段是author表的id列,所以為了便于區(qū)別而將這個外鍵字段的名稱命名為author_id。
?
傳入ForeignKey類的參數(shù)author.id,其中author指的是Author模型對應(yīng)的表名稱,而id指的是字段名,即“表名.字段名”。模型類對應(yīng)的表名由Flask-SQLAlchemy生成,默認為類名稱的小寫形式,多個單詞通過下劃線分隔,你也可以顯示地通過__tablename__屬性自己指定。
?
2、定義關(guān)系屬性
定義關(guān)系的第二步是使用關(guān)系函數(shù)定義關(guān)系屬性。關(guān)系屬性在關(guān)系的出發(fā)側(cè)定義,即一對多關(guān)系的“一”這一側(cè)。一個作者擁有多篇文章,在Author模型中,定義一個articles屬性來表示對應(yīng)的多篇文章:
class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(70), unique=True)articles = db.relationShip('Article')#指定顯示內(nèi)容def __repr__(self):return '<Author %r>' %self.name關(guān)系屬性的名稱沒有限制,你可以自由修改。它相當于一個快捷查詢,不會作為字段寫入數(shù)據(jù)庫中。
?
這個屬性并沒有使用column類聲明為列,而是使用了db.relationship()關(guān)系函數(shù)定義為關(guān)系屬性,因為這個關(guān)系屬性返回多個記錄,我們稱之為集合關(guān)系屬性。relationship()函數(shù)的第一個參數(shù)為關(guān)系另一側(cè)的模型名稱,它會告訴SQLAlchemy將Author類與Article類建立關(guān)系。當這個關(guān)系屬性被調(diào)用時,SQLAlchemy會找到關(guān)系的另一側(cè)(即article表)的外鍵字段(即author_id),然后反向查詢article表中所有author_id值為當前表主鍵值(即author.id)的記錄,返回包含這些記錄的列表,也就是返回某個作者對應(yīng)的多篇文章記錄。
?
>>> from app import Author, Article >>> from app import db >>> foo = Author(name = 'Foo') >>> spam = Article(title = 'Spam') >>> ham = Article(title = 'Ham') >>> db.session.add(foo) >>> db.session.add(spam) >>> db.session.add(ham)?
3、建立關(guān)系
建立關(guān)系有兩種方式,第一種方式為外鍵字段賦值,比如:
?
>>> spam.author_id = 1 >>> db.session.commit()我們將spam對象的author_id字段的值設(shè)為1,這會和id值為1的Author對象建立關(guān)系。提交數(shù)據(jù)庫改動后,如果我們隊id為1的foo對象調(diào)用articles關(guān)系屬性,會看到spam對象包括在返回的Article對象列表中:
?
>>> foo=Author.query.first() >>> foo <Author u'F00'> >>> foo.articles [<Article u'Spam'>, <Article u'Ham'>]?
另一種方式是通過操作關(guān)系屬性,將關(guān)系屬性付給實際的對象即可建立關(guān)系。集合關(guān)系屬性可以像列表一樣操作,調(diào)用append()方法來與一個Article對象建立關(guān)系:
>>> foo.articles [<Article u'Spam'>, <Article u'Ham'>] >>> A1 = Article.query.get(3) >>> A1 <Article u'A1'> >>> foo.articles.append(A1) >>> db.session.commit() >>> foo.articles [<Article u'Spam'>, <Article u'Ham'>, <Article u'A1'>]?
我們也可以直接將關(guān)系屬性賦值給一個包含Article對象的列表。
>>> foo.articles [<Article u'Spam'>, <Article u'Ham'>, <Article u'A1'>] >>> foo.articles = foo.articles[1:2] >>> foo.articles [<Article u'Ham'>]?
和前面的第一種方式類似,為了讓改動生效,我們需要調(diào)用db.session.commit()方法提交數(shù)據(jù)庫會話。
?
建立關(guān)系后,存儲外鍵的author_id字段會自動獲得正確的值,而調(diào)用Author實例的關(guān)系屬性articles時,會獲得所有建立關(guān)系的Article對象:
?
>>> foo.id 2 >>> A1.author_id 2 >>> foo.articles [<Article u'Spam'>, <Article u'Ham'>, <Article u'A1'>]?
和主鍵類似,外鍵字段有SQLAlchemy管理,我們不需要手動設(shè)置。當通過關(guān)系屬性建立關(guān)系后,外鍵字段會自動獲得正確的值。
?
和append()相對,對關(guān)系屬性調(diào)用remove()方法可以與對應(yīng)的Article對象接觸關(guān)系:
?
>>> soo.articles [<Article u'Ham'>, <Article u'A1'>] >>> soo.articles.remove(A1) >>> soo.articles [<Article u'Ham'>] >>> db.session.commit() >>> soo.articles [<Article u'Ham'>]?
你也可以使用pop()方法操作關(guān)系屬性,它會與關(guān)系屬性對應(yīng)的列表的最后一個Article對象接觸關(guān)系并返回改對象。
不要忘記在操作結(jié)束后需要調(diào)用commit()方法提交數(shù)據(jù)庫會話,這樣才可以把數(shù)據(jù)寫入數(shù)據(jù)庫。
是用關(guān)系函數(shù)定義的屬性不是數(shù)據(jù)庫字段,而是類似于特定的查詢函數(shù)。當某個Article對象被刪除時,在對應(yīng)Author對象的articles屬性調(diào)用時返回的列表也不會包含該對象。
?
在關(guān)系函數(shù)中,有很多參數(shù)可以用來設(shè)置調(diào)用關(guān)系屬性進行查詢時的具體行為。常用的SQLAlchemy關(guān)系函數(shù)參數(shù)如下所示:
?
當關(guān)系屬性被調(diào)用時,關(guān)系函數(shù)會加載相應(yīng)的記錄,下表列出了控制關(guān)系記錄加載方式的lazy參數(shù)的常用選項。
常用的SQLAlchemy關(guān)系記錄加載方式(lazy參數(shù)可選值):
?
dynamic選項僅用于集合關(guān)系屬性,不可用于多對一、一對一或是在關(guān)系函數(shù)中將uselist參數(shù)設(shè)為False的情況。
?
要避免使用dynamic來動態(tài)加載所有集合關(guān)系屬性對應(yīng)的記錄,使用dynamic加載方式以為這每次操作關(guān)系都會執(zhí)行一次SQL查詢,這會造成潛在的性能問題。大多數(shù)情況下我們只需要使用默認值(select),只有在調(diào)用關(guān)系屬性會返回大量記錄,并且總是需要對關(guān)系屬性返回的結(jié)果附加額外的查詢時才需要動態(tài)加載(lazy=’dynamic’)。
?
4、建立雙向關(guān)系
我們在Author類中定義了集合關(guān)系屬性articles,用來獲取某個作者擁有的多篇文章記錄。在某些情況下,你也許希望能在Article類中定義一個類似的author關(guān)系屬性,當被調(diào)用時返回對應(yīng)的作者記錄,這類返回單個值的關(guān)系屬性被稱為標量關(guān)系屬性。而這種兩側(cè)都添加關(guān)系屬性獲取對方記錄的關(guān)系我們稱之為雙向關(guān)系(bidirectional relationship)
雙向關(guān)系并不是必須的,但在某些情況下會非常方便。雙向關(guān)系的建立很簡單,通過在關(guān)系的另一側(cè)也創(chuàng)建一個relationship()函數(shù),我們就可以在兩個表之間建立雙向關(guān)系。我們使用作家(Writer)和書(Book)的一對多關(guān)系來進行演示,建立雙向關(guān)系后的Writer和Book類如下所示:
app.py: 基于一對多關(guān)系的雙向關(guān)系
class Writer(db.Model):id = db.Column(db.Integer,primary_key=True)name = db.Column(db.String(70), unique = True)#back_populates, 定義雙向關(guān)系 # back_populates參數(shù)的值需要設(shè)為關(guān)系另一側(cè)的關(guān)系屬性名books = db.relationship('Book', back_populates='writer')def __repr__(self):return '<Writer %r>' % self.nameclass Book(db.Model):id = db.Column(db.Integer, primary_key = True)title = db.Column(db.String(50), index = True)writer_id = db.Column(db.Integer, db.ForeignKey('writer.id'))writer = db.relationship('Writer', back_populates = 'books')def __repr__(self):return '<Book %r>' % self.title在“多”這一側(cè)的Book(書)類中,我們新創(chuàng)建了一個writer關(guān)系屬性,這是一個標量關(guān)系屬性,調(diào)用它會獲取對應(yīng)的Writer(作者)記錄;而在Writer(作者)類中的books屬性則用來獲取對應(yīng)的多Book(書)記錄。在關(guān)系函數(shù)中,我們使用back_populates參數(shù)來連接對方,back_populates參數(shù)的值需要設(shè)為關(guān)系另一側(cè)的關(guān)系屬性名。
?
我們先創(chuàng)建1個Writer和2個Book記錄,并添加到數(shù)據(jù)庫中:
?
>>> from app import Writer >>> from app import Book, db >>> king = Writer(name = 'Stephen KKing') >>> carrie = Book(title = 'Carrie') >>> it = Book(title = 'IT') >>> db.session.add(king) >>> db.session.add(carrie) >>> db.session.add(it) >>> db.session.commit() #當一個book對象修改了writer屬性,對應(yīng)的writer對象的books屬性會跟著修改(刪去),#修改后的writer屬性對應(yīng)的writer對象的books屬性會增加 >>> king.books [<Book u'IT'>, <Book 'Marry'>] >>> job = Writer(name = 'job') >>> db.session.add(job) >>> db.session.commit() >>> job.id 2 >>> marry = Book(title = 'Marry') >>> marry.writer = job >>> marry.writer <Writer u'job'> >>> king.books [<Book u'IT'>] >>> job.books [<Book u'Marry'>]?
設(shè)置雙向關(guān)系后,除了通過集合屬性books(多個)來操作關(guān)系,我們也可以使用標量屬性writer來進行關(guān)系操作。比如,將一個Writer對象賦值給某個Book對象的writer屬性,就會和這個Book對象建立關(guān)系:
>>> king <Writer u'Stephen KKing'> >>> carrie <Book u'Carrie'> >>> carrie.writer = king >>> carrie.writer <Writer u'Stephen KKing'> #carrie這條數(shù)據(jù)的關(guān)系writer指向king,同時king這條數(shù)據(jù)的books屬性也指向了carrie,這是雙向的 >>> king.books [<Book u'Carrie'>] >>> it <Book u'IT'> >>> it.writer = king #同上 >>> king.books [<Book u'Carrie'>, <Book u'IT'>]?
相對的,將某個book的writer屬性設(shè)為None,就會解除與對應(yīng)Writer對象的關(guān)系:
>>> carrie.writer = None >>> king.books [<Book u'IT'>] >>> db.session.commit()?
需要注意的是,我們只需要在關(guān)系的一側(cè)操作關(guān)系。當為Book對象的writer屬性賦值后,對應(yīng)Writer對象的books屬性的返回值也會自動包含這個Book對象。反之,當某個Writer對象被刪除時,對應(yīng)的Book對象的writer屬性被調(diào)用時的返回值也會被置為空(即NULL,會返回None)。
?
其他關(guān)系模式建立雙向關(guān)系的方式完全相同。
?
?
5、使用backref簡化關(guān)系定義
在介紹關(guān)系函數(shù)的參數(shù)時,我們曾提到過,使用關(guān)系函數(shù)中的backref參數(shù)可以簡化雙向關(guān)系的定義。以一對多關(guān)系為例,backref參數(shù)用來自動為關(guān)系另一側(cè)添加關(guān)系屬性,作為反向引用(back reference),賦予的值會作為關(guān)系另一側(cè)的關(guān)系屬性名稱。比如,我們在Author一側(cè)的關(guān)系函數(shù)中將backref參數(shù)設(shè)為author,SQLAlchemy會自動為Article類添加一個author屬性。為了避免和前面的實例命名沖突,我們使用歌手(Singer)和歌曲(Song)的一對多關(guān)系作為演示,分別創(chuàng)建Singer和Song類,如下所示:
?
class Singer(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(70), unique=True)songs = db.relationship('Song', backref='singer')def __repr__(self):return "<Singer %r>" % self.nameclass Song(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(50), index=True)singer_id = db.Column(db.Integer, db.ForeignKey('singer.id'))def __repr__(self):return '<Song %r>' % self.name?
在定義集合屬性songs的關(guān)系函數(shù)中,我們將backref參數(shù)設(shè)置singer,這會同時在Song類中添加一個singer標量屬性。我們僅需要定義這一個關(guān)系函數(shù),雖然singer是一個“看不見的關(guān)系屬性”,但是使用上和定義兩個關(guān)系函數(shù)并使用back_poplulates參數(shù)的效果完全相同。
?
需要注意的是,使用backref允許我們僅在關(guān)系一側(cè)定義另一側(cè)的關(guān)系屬性,但是在某些情況下,我們希望可以對在關(guān)系另一側(cè)的關(guān)系屬性進行設(shè)置,這時就需要使用backref()函數(shù)。backref()函數(shù)接收第一個參數(shù)作為在關(guān)系另一側(cè)添加的關(guān)系屬性名,其他關(guān)鍵字參數(shù)會作為關(guān)系另一側(cè)關(guān)系函數(shù)的參數(shù)傳入。比如,我們要在關(guān)系另一側(cè)“看不見的relationship()函數(shù)”中將uselist參數(shù)設(shè)為False,可以這樣:
class Singer(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(70), unique=True)songs = relationship('Song', backref=backref('singer', uselist=False))>>> from app import Singer >>> from app import Song >>> singer1 = Singer(name = 'Huyanbin') >>> song1 = Song(name = 'HongYan') >>> singer1 <Singer 'Huyanbin'> >>> song1 <Song 'HongYan'> >>> song1.singer = singer1 >>> song1.singer <Singer 'Huyanbin'> >>> singer1.songs [<Song 'HongYan'>] >>> song2 = Song(name = 'manman') >>> song2.singer = singer1 >>> song2 <Song 'manman'> >>> singer1.songs [<Song 'HongYan'>, <Song 'manman'>] #提交數(shù)據(jù)庫會話,落表 >>> db.session.add(singer1) >>> db.session.add(song1) >>> db.session.add(song2) >>> singer1.id >>> db.session.commit() >>> singer1.id 2 >>> song1.id 1 >>> song2.id 2 ?盡管使用backref非常方便,但通常來說“顯示好過隱式”,所以我們應(yīng)該盡量使用back_populates定義雙向關(guān)系。
轉(zhuǎn)載于:https://www.cnblogs.com/xiaxiaoxu/p/10597561.html
總結(jié)
以上是生活随笔為你收集整理的flask 定义数据库关系(一对多) --的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL填充字符串函数 LPAD(st
- 下一篇: springcloud(五) Hyst