重写 sqlalchemy 中的关系行为
override relationship behaviour in sqlalchemy
假设我以声明方式拥有三个表,Parent
、Child
和 Pet
,这样
Parent
与 Child
和 Pet
都存在多对多关系
Child
与Pet
是一对多关系
它们的代码是(使用 Flask-SQLAlchemy,尽管我相信解决方案存在于 SQLAlchemy 领域而不是 Flask 中)。
class Parent(db.Model):
__tablename__ = 'parents'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# many to many relationship between parent and children
# my case allows for a children to have many parents. Don't ask.
children = db.relationship('Child',
secondary=parents_children_relationship,
backref=db.backref('parents', lazy='dynamic'),
lazy='dynamic')
# many to many relationship between parents and pets
pets = db.relationship('Pet',
secondary=users_pets_relationship,
backref=db.backref('parents', lazy='dynamic'), #
lazy='dynamic')
# many to many relationship between parents and children
parents_children_relationship = db.Table('parents_children_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('parents.id')),
db.Column('child_id', db.Integer, db.ForeignKey('children.id')),
UniqueConstraint('parent_id', 'child_id'))
# many to many relationship between User and Pet
users_pets_relationship = db.Table('users_pets_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('parents.id')),
db.Column('pet_id', db.Integer, db.ForeignKey('pets.id')),
UniqueConstraint('parent_id', 'pet_id'))
class Child(db.Model):
__tablename__ = 'children'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# parents = <backref relationship with User model>
# one to many relationship with pets
pets = db.relationship('Pet', backref='child', lazy='dynamic')
class Pet(db.Model):
__tablename__ = 'pets'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# child = backref relationship with cities
child_id = db.Column(db.Integer, db.ForeignKey('children.id'), nullable=True)
# parents = <relationship backref from User>
我想做这样的事情
parent_a = Parent()
child_a = Child()
pet_a = Pet()
然后我可以这样做
parent_a.children.append(child_a)
# commit/persist data
parent_a.children.all() # [child_a]
我想实现这样的目标
child_a.pets.append(pet_a)
parent_a.children.append(child_a)
# commit/persist data
parent_a.children.all() # [child_a]
parent_a.pets.all() # [pet_a], because pet_a gets
# automatically added to parent using some sorcery
# like for child in parent_a.children.all():
# parent.pets.append(child.pets.all())
# or something like that.
我可以使用 Parent
对象中的方法实现此目的,例如 add_child_and_its_pets()
,但我想重写关系的工作方式,因此我不需要重写其他可能的模块从这种行为中受益,例如 Flask-Admin
。
基本上我应该如何重写 backref.append
方法或 relationship.append
方法以在调用时也附加来自其他关系的其他对象,即在 python 端?我还应该如何覆盖 remove
方法?
对于 parent.pets.all()
,我认为您可以将 children 用作 secondary join condition, and treat it as an associative entity or junction table。
这取决于您的表格,但看起来像:
Parent.pets = relationship(
Pet,
backref='parent'
primaryjoin=Pet.child_id == Child.id,
secondaryjoin=Child.parent_id == Parent.id
)
如果您愿意,您也可以相当合理地制作一个反向引用 parent
- 这样您就可以同时访问 parent_a.pets
和 pet_a.parent
.
使用来自 sqlalchemy mailing list, this can be achieved using event listeners 的相同答案,它们在第一个参数中的对象上调用 append
或 remove
之前调用。
@db.event.listens_for(Parent.children, 'append')
def _append_children(parent, child, initiator):
# appends also the pets bound to the child that the
# is being appended to the Parent
parent.pets.extend(child.pets.all())
# at the end of this call, executes
# parent.children.append(child)
假设我以声明方式拥有三个表,Parent
、Child
和 Pet
,这样
Parent
与Child
和Pet
都存在多对多关系
Child
与Pet
是一对多关系
它们的代码是(使用 Flask-SQLAlchemy,尽管我相信解决方案存在于 SQLAlchemy 领域而不是 Flask 中)。
class Parent(db.Model):
__tablename__ = 'parents'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# many to many relationship between parent and children
# my case allows for a children to have many parents. Don't ask.
children = db.relationship('Child',
secondary=parents_children_relationship,
backref=db.backref('parents', lazy='dynamic'),
lazy='dynamic')
# many to many relationship between parents and pets
pets = db.relationship('Pet',
secondary=users_pets_relationship,
backref=db.backref('parents', lazy='dynamic'), #
lazy='dynamic')
# many to many relationship between parents and children
parents_children_relationship = db.Table('parents_children_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('parents.id')),
db.Column('child_id', db.Integer, db.ForeignKey('children.id')),
UniqueConstraint('parent_id', 'child_id'))
# many to many relationship between User and Pet
users_pets_relationship = db.Table('users_pets_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('parents.id')),
db.Column('pet_id', db.Integer, db.ForeignKey('pets.id')),
UniqueConstraint('parent_id', 'pet_id'))
class Child(db.Model):
__tablename__ = 'children'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# parents = <backref relationship with User model>
# one to many relationship with pets
pets = db.relationship('Pet', backref='child', lazy='dynamic')
class Pet(db.Model):
__tablename__ = 'pets'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
# child = backref relationship with cities
child_id = db.Column(db.Integer, db.ForeignKey('children.id'), nullable=True)
# parents = <relationship backref from User>
我想做这样的事情
parent_a = Parent()
child_a = Child()
pet_a = Pet()
然后我可以这样做
parent_a.children.append(child_a)
# commit/persist data
parent_a.children.all() # [child_a]
我想实现这样的目标
child_a.pets.append(pet_a)
parent_a.children.append(child_a)
# commit/persist data
parent_a.children.all() # [child_a]
parent_a.pets.all() # [pet_a], because pet_a gets
# automatically added to parent using some sorcery
# like for child in parent_a.children.all():
# parent.pets.append(child.pets.all())
# or something like that.
我可以使用 Parent
对象中的方法实现此目的,例如 add_child_and_its_pets()
,但我想重写关系的工作方式,因此我不需要重写其他可能的模块从这种行为中受益,例如 Flask-Admin
。
基本上我应该如何重写 backref.append
方法或 relationship.append
方法以在调用时也附加来自其他关系的其他对象,即在 python 端?我还应该如何覆盖 remove
方法?
对于 parent.pets.all()
,我认为您可以将 children 用作 secondary join condition, and treat it as an associative entity or junction table。
这取决于您的表格,但看起来像:
Parent.pets = relationship(
Pet,
backref='parent'
primaryjoin=Pet.child_id == Child.id,
secondaryjoin=Child.parent_id == Parent.id
)
如果您愿意,您也可以相当合理地制作一个反向引用 parent
- 这样您就可以同时访问 parent_a.pets
和 pet_a.parent
.
使用来自 sqlalchemy mailing list, this can be achieved using event listeners 的相同答案,它们在第一个参数中的对象上调用 append
或 remove
之前调用。
@db.event.listens_for(Parent.children, 'append')
def _append_children(parent, child, initiator):
# appends also the pets bound to the child that the
# is being appended to the Parent
parent.pets.extend(child.pets.all())
# at the end of this call, executes
# parent.children.append(child)