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")