python-復(fù)盤-flask-數(shù)據(jù)庫一對(duì)一/一對(duì)多/多對(duì)多關(guān)系

Flask 數(shù)據(jù)庫多對(duì)多關(guān)系

實(shí)現(xiàn)學(xué)生與學(xué)院之間的多對(duì)多關(guān)系



這個(gè)例子中的關(guān)聯(lián)表是 registrations,表中的每一行都表示一個(gè)學(xué)生注冊(cè)的一個(gè)課程。查詢多對(duì)多關(guān)系要分成兩步。 若想知道某位學(xué)生選擇了哪些課程陵珍,你要先從學(xué)生和注冊(cè)之間的一對(duì)多關(guān)系開始眼坏, 獲取這位學(xué)生在 registrations 表中的所有記錄,然后再按照多到一的方向遍歷課程和注冊(cè)之間的一對(duì)多關(guān)系捏检, 找到這位學(xué)生在 registrations 表中各記錄所對(duì)應(yīng)的課程。 同樣不皆,若想找到選擇了某門課程的所有學(xué)生贯城,你要先從課程表中開始,獲取其在 registrations 表中的記錄霹娄,再獲取這些記錄聯(lián)接的學(xué)生能犯。通過遍歷兩個(gè)關(guān)系來獲取查詢結(jié)果的做法聽起來有難度, 不過像前例這種簡單關(guān)系犬耻,SQLAlchemy 就可以完成大部分操作踩晶。

registrations = db.Table('registrations',  
    db.Column('student_id', db.Integer, db.ForeignKey('students.id')),  
    db.Column('class_id', db.Integer, db.ForeignKey('classes.id'))  
)  
   
class Student(db.Model):  
    id = db.Column(db.Integer, primary_key=True)  
   name = db.Column(db.String)  
   classes = db.relationship('Class',secondary=registrations,  
                                    backref=db.backref('students', lazy='dynamic'),  
                                    lazy='dynamic')  
  
class Class(db.Model):  
    id = db.Column(db.Integer, primary_key = True)  
    name = db.Column(db.String) 

多對(duì)多關(guān)系仍使用定義一對(duì)多關(guān)系的 db.relationship() 方法進(jìn)行定義,但在多對(duì)多關(guān)系中枕磁,必須把 secondary 參數(shù)設(shè)為關(guān)聯(lián)表渡蜻。多對(duì)多關(guān)系可以在任何一個(gè)類中定義, backref 參數(shù)會(huì)處理好關(guān)系的另一側(cè)。關(guān)聯(lián)表就是一個(gè)簡單的表茸苇,不是模型排苍,SQLAlchemy 會(huì)自動(dòng)接管這個(gè)表。
這樣處理多對(duì)多關(guān)系特別簡單学密。假設(shè)學(xué)生是 s淘衙,課程是 c,學(xué)生注冊(cè)課程的代碼為:

>>> s.classes.append(c)
>>> db.session.add(s)

列出學(xué)生 s 注冊(cè)的課程以及注冊(cè)了課程 c 的學(xué)生也很簡單:

>>> s.classes.all()
>>> c.students.all()

Class 模型中的 students 關(guān)系由參數(shù) db.backref() 定義腻暮。注意彤守,這個(gè)關(guān)系中還指定了 lazy= 'dynamic' 參數(shù),所以關(guān)系兩側(cè)返回的查詢都可接受額外的過濾器哭靖。
如果后來學(xué)生 s 決定不選課程 c 了具垫,那么可使用下面的代碼更新數(shù)據(jù)庫:

>>> s.classes.remove(c)

下面來看一個(gè)實(shí)際的例子:因?yàn)樵谠O(shè)計(jì)中學(xué)生會(huì)轉(zhuǎn)學(xué)院,所以款青,學(xué)生與學(xué)院是多對(duì)多的關(guān)系

1. 定義模型

class User(UserMixin, db.Model):  
    __tablename__ = 'users'  
    id = db.Column(db.Integer, primary_key=True)  
    email = db.Column(db.String(100), unique=True, index=True)  
      
    .............省略其他字段  
    departments=db.relationship('Department', secondary=user_department, backref=db.backref('users',lazy='dynamic'), lazy='dynamic')  
class Department(db.Model):  
    __tablename__ = 'departments'  
    id = db.Column(db.Integer, primary_key=True)  
    department = db.Column(db.String(100))
user_department = db.Table('user_department',  
    db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),  
    db.Column('department_id', db.Integer, db.ForeignKey('departments.id'), primary_key=True)    
) 

2. 定義表單

class SmForm(Form):  
    name = StringField('真實(shí)姓名', validators=[Length(0, 64)])  
    ....................省略其他字段  
    is_departmentChange = BooleanField('是否轉(zhuǎn)過學(xué)院')  
    pre_department = SelectField('原學(xué)院:', coerce=int)  
    cut_department = SelectField('現(xiàn)學(xué)院:', coerce=int)  
      
    submit = SubmitField('Submit')  
  
   #下拉菜單初始化  
    def __init__(self, user, *args, **kwargs):  
        super(SmForm, self).__init__(*args, **kwargs)  
        <strong>self.pre_department.choices = [(pre_department.id, pre_department.department)  
                             for pre_department in Department.query.order_by(Department.department).all()]  
        self.cut_department.choices = [(cut_department.id, cut_department.department)  
                             for cut_department in Department.query.order_by(Department.department).all()]</strong>  
         
        self.user = user 

3. 定義路由

@main.route('/sm', methods=['GET', 'POST'])  
@login_required  
@main.errorhandler(404)  
def sm():  
    user = User.query.filter_by(email=current_user.email).first()  
    form = SmForm(user)  
    if  user.is_realname ==False:  
        if form.validate_on_submit():  
  
            # User的學(xué)院更新  刪除舊的數(shù)據(jù)做修,<strong>聯(lián)合刪除  
            usr = current_user._get_current_object()  
            deparment = user.departments.all()  
            for de in deparment:  
                de.users.remove(usr)</strong>  
  
       ........................省略其他  
  
              
            user.is_departmentChange = form.is_departmentChange.data  
          <strong>#向關(guān)系表中添加  
            user.departments.append(Department.query.get(form.pre_department.data))  
            user.departments.append(Department.query.get(form.cut_department.data))  
  
            db.session.add(user)  
            db.session.commit()</strong>  
            return redirect(url_for('.sm_success'))  
    return render_template('sm.html', form=form) 

4.渲染模板(省略)



SQLAlchemy_定義(一對(duì)一/一對(duì)多/多對(duì)多)關(guān)系

基本關(guān)系模式

from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

One To Many

表示一對(duì)多的關(guān)系時(shí)霍狰,在子表類中通過 foreign key (外鍵)引用父表類抡草。
然后,在父表類中通過 relationship() 方法來引用子表的類:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child")
   # 在父表類中通過 relationship() 方法來引用子表的類集合

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    # 在子表類中通過` foreign key` (外鍵)引用父表的參考字段

在一對(duì)多的關(guān)系中建立雙向的關(guān)系蔗坯,這樣的話在對(duì)方看來這就是一個(gè)多對(duì)一的關(guān)系
在子表類中附加一個(gè) relationship() 方法康震,并且在雙方的 relationship() 方法中使用relationship.back_populates 方法參數(shù):

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", back_populates="children")
    # 子表類中附加一個(gè) relationship() 方法
    # 并且在(父)子表類的 relationship() 方法中使用 relationship.back_populates 參數(shù)

這樣的話子表將會(huì)在多對(duì)一的關(guān)系中獲得父表的屬性
或者,可以在單一的 relationship() 方法中使用 backref 參數(shù)來代替 back_populates 參數(shù):

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

One To One

一對(duì)一是兩張表之間本質(zhì)上的雙向關(guān)系
要做到這一點(diǎn)宾濒,只需要在一對(duì)多關(guān)系基礎(chǔ)上的父表中使用 uselist 參數(shù)來表示
To convert one-to-many into one-to-one:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child = relationship("Child", uselist=False, back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", back_populates="child")

To convert many-to-one into one-to-one:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent = relationship("Parent", back_populates="child", uselist=False)

同樣的腿短,可以使用下面這種方式:

from sqlalchemy.orm import backref

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref=backref("parent", uselist=False))

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))


Many To Many

多對(duì)多關(guān)系會(huì)在兩個(gè)類之間增加一個(gè)關(guān)聯(lián)的表。這個(gè)關(guān)聯(lián)的表在 relationship() 方法中通過 secondary 參數(shù)來表示绘梦。
通常的橘忱,這個(gè)表會(huì)通過 MetaData 對(duì)象來與聲明基類關(guān)聯(lián),所以這個(gè) ForeignKey 指令會(huì)使用鏈接來定位到遠(yuǎn)程的表:

# 多對(duì)多關(guān)系中的兩個(gè)表之間的一個(gè)關(guān)聯(lián)表
association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=association_table)
    # 在父表中的 relationship() 方法傳入 secondary 參數(shù)卸奉,其值為關(guān)聯(lián)表的表名

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

雙向關(guān)系中钝诚,兩個(gè)表類都會(huì)包含這個(gè)集合。指定使用 relationship.back_populates參數(shù)榄棵,并且為每一個(gè) relationship()方法指定共用的關(guān)聯(lián)表:

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship(
        "Child",
        secondary=association_table,
        back_populates="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship(
        "Parent",
        secondary=association_table,
        back_populates="children")

當(dāng)在父表類的 relationship()方法中使用backref參數(shù)代替 relationship.back_populates 時(shí)凝颇,backref 會(huì)自動(dòng)的為子表類加載同樣的 secondary 參數(shù)。

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=association_table,
                    backref="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

secondary 參數(shù)還能夠接收一個(gè)可調(diào)函數(shù)的最終返回值疹鳄,

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=lambda: association_table,
                    backref="parents")
class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary="association",
                    backref="parents")

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拧略,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瘪弓,更是在濱河造成了極大的恐慌垫蛆,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異袱饭,居然都是意外死亡弛随,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門宁赤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舀透,“玉大人,你說我怎么就攤上這事决左°倒唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵佛猛,是天一觀的道長惑芭。 經(jīng)常有香客問我,道長继找,這世上最難降的妖魔是什么遂跟? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮婴渡,結(jié)果婚禮上幻锁,老公的妹妹穿的比我還像新娘。我一直安慰自己边臼,他們只是感情好哄尔,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柠并,像睡著了一般岭接。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臼予,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天鸣戴,我揣著相機(jī)與錄音,去河邊找鬼粘拾。 笑死窄锅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的半哟。 我是一名探鬼主播酬滤,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼寓涨!你這毒婦竟也來了盯串?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤戒良,失蹤者是張志新(化名)和其女友劉穎体捏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡几缭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年河泳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片年栓。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拆挥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出某抓,到底是詐尸還是另有隱情邑雅,我是刑警寧澤乌叶,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布塔鳍,位于F島的核電站愕乎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏备禀。R本人自食惡果不足惜洲拇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曲尸。 院中可真熱鬧赋续,春花似錦、人聲如沸队腐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柴淘。三九已至,卻和暖如春秘通,著一層夾襖步出監(jiān)牢的瞬間为严,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工肺稀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留第股,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓话原,卻偏偏與公主長得像夕吻,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子繁仁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理涉馅,服務(wù)發(fā)現(xiàn),斷路器黄虱,智...
    卡卡羅2017閱讀 134,711評(píng)論 18 139
  • 一稚矿、概念數(shù)據(jù)模型概述數(shù)據(jù)模型是現(xiàn)實(shí)世界中數(shù)據(jù)特征的抽象。數(shù)據(jù)模型應(yīng)該滿足三個(gè)方面的要求:1)能夠比較真實(shí)地模擬現(xiàn)實(shí)...
    AFinalStone閱讀 11,778評(píng)論 1 5
  • 今天是一個(gè)美好的一天桥爽,我們有個(gè)平臺(tái)叫做思涵創(chuàng)業(yè)平臺(tái),我們每天認(rèn)真參加三會(huì)昧识,讀書分享會(huì)钠四,碰頭會(huì),小組會(huì)跪楞,必須認(rèn)真去參...
    卓越成林愛編輯閱讀 193評(píng)論 2 1
  • 平遙古城的那一天一夜形导,我是最孤獨(dú)的,熱鬧的大街习霹,我是孤獨(dú)的朵耕,大街上的歡聲笑語,我是孤獨(dú)的淋叶,美麗的風(fēng)景阎曹,我是...
    王神馬閱讀 147評(píng)論 0 0