Flask 数据库多对多关系
实现学生与学院之间的多对多关系
这个例子中的关联表是 registrations,表中的每一行都表示一个学生注册的一个课程。查询多对多关系要分成两步。 若想知道某位学生选择了哪些课程,你要先从学生和注册之间的一对多关系开始, 获取这位学生在 registrations 表中的所有记录,然后再按照多到一的方向遍历课程和注册之间的一对多关系, 找到这位学生在 registrations 表中各记录所对应的课程。 同样,若想找到选择了某门课程的所有学生,你要先从课程表中开始,获取其在 registrations 表中的记录,再获取这些记录联接的学生。通过遍历两个关系来获取查询结果的做法听起来有难度, 不过像前例这种简单关系,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)
多对多关系仍使用定义一对多关系的 db.relationship() 方法进行定义,但在多对多关系中,必须把 secondary 参数设为关联表。多对多关系可以在任何一个类中定义, backref 参数会处理好关系的另一侧。关联表就是一个简单的表,不是模型,SQLAlchemy 会自动接管这个表。
这样处理多对多关系特别简单。假设学生是 s,课程是 c,学生注册课程的代码为:
>>> s.classes.append(c)
>>> db.session.add(s)
列出学生 s 注册的课程以及注册了课程 c 的学生也很简单:
>>> s.classes.all()
>>> c.students.all()
Class 模型中的 students 关系由参数 db.backref() 定义。注意,这个关系中还指定了 lazy= 'dynamic' 参数,所以关系两侧返回的查询都可接受额外的过滤器。
如果后来学生 s 决定不选课程 c 了,那么可使用下面的代码更新数据库:
>>> s.classes.remove(c)
下面来看一个实际的例子:因为在设计中学生会转学院,所以,学生与学院是多对多的关系
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('真实姓名', validators=[Length(0, 64)])
....................省略其他字段
is_departmentChange = BooleanField('是否转过学院')
pre_department = SelectField('原学院:', coerce=int)
cut_department = SelectField('现学院:', 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的学院更新 删除旧的数据,<strong>联合删除
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>#向关系表中添加
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_定义(一对一/一对多/多对多)关系
基本关系模式
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
表示一对多的关系时,在子表类中通过 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` (外键)引用父表的参考字段
在一对多的关系中建立双向的关系,这样的话在对方看来这就是一个多对一的关系
在子表类中附加一个 relationship()
方法,并且在双方的 relationship()
方法中使用relationship.back_populates
方法参数:
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")
# 子表类中附加一个 relationship() 方法
# 并且在(父)子表类的 relationship() 方法中使用 relationship.back_populates 参数
这样的话子表将会在多对一的关系中获得父表的属性
或者,可以在单一的 relationship() 方法中使用 backref
参数来代替 back_populates
参数:
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
一对一是两张表之间本质上的双向关系
要做到这一点,只需要在一对多关系基础上的父表中使用 uselist 参数来表示
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
多对多关系会在两个类之间增加一个关联的表。这个关联的表在 relationship() 方法中通过 secondary 参数来表示。
通常的,这个表会通过 MetaData 对象来与声明基类关联,所以这个 ForeignKey 指令会使用链接来定位到远程的表:
# 多对多关系中的两个表之间的一个关联表
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 参数,其值为关联表的表名
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
双向关系中,两个表类都会包含这个集合。指定使用 relationship.back_populates
参数,并且为每一个 relationship()
方法指定共用的关联表:
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")
当在父表类的 relationship()
方法中使用backref
参数代替 relationship.back_populates
时,backref
会自动的为子表类加载同样的 secondary
参数。
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 参数还能够接收一个可调函数的最终返回值,
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")