переопределить поведение отношения в sqlalchemy - PullRequest
0 голосов
/ 26 апреля 2018

Скажем, у меня три таблицы декларативным образом: 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?

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Используя тот же ответ из списка рассылки sqlalchemy , этого можно достичь с помощью прослушивателей событий , которые вызываются до вызова 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)
0 голосов
/ 26 апреля 2018

Для parent.pets.all(), я думаю, вы могли бы использовать дочерние элементы в качестве условия вторичного соединения и рассматривать его как ассоциативную сущность или таблицу соединений .

Этозависит от ваших таблиц, но это будет выглядеть примерно так:

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.

...