So I'm back to this, and I wonder about something you said: > > the main complication here is that those "dynamic" relationships > require that a query runs for everything, which means everything has > to be in the database, which means it flushes the session very > aggressively (and also disabling the flush, another thing I tried, > means it doesn't read the contents of the collections accurately), and > all of that makes an already tricky operation nearly impossible > without it barreling into itself. > > *If you are committed to using the "dynamic" relationships*, you can > always rely on emitting SQL to read from those association tables, and > there's a completely unorthodox way to do this which would be way more > efficient in most cases, and is extremely simple, just emit the DELETE > statements: >
So, I don't know actually if I have to be committed to one approach over the other. I was trying to leverage the orm layer for consistency, but if there is another approach to achieve the same thing I would like to know. Also, you mentioned a test you've added, do you mind to share it? BTW, the code bit you changed makes it work, thanks!. There are a lot of things I don't understand. But first, why does I need to use `session_object`? to expose the `execute` method? What is that I am "bypassing" by using this over the orm? Am Freitag, 4. Mai 2018 11:40:34 UTC-3 schrieb Diego Quintana: > > At the moment I've moved to other features, but I should be back to this > somewhere in the near future. I will let you know the results. > > I really appreciate your time, thanks again. > > Best, > > Am Donnerstag, 3. Mai 2018 10:10:47 UTC-3 schrieb Mike Bayer: >> >> On Thu, May 3, 2018 at 7:39 AM, Diego Quintana <daqui...@gmail.com> >> wrote: >> > Thanks again for your reply >> > >> >> at the core is that when you remove a child from the parent in the >> >> _remove_pets event, you want to prevent the _remove_children() event >> >> from actually happening, I think. >> > >> > >> > Yes, since it is a different usage case or flavour. I was trying to >> pass >> > kwargs to the event listener directly, but it does not work. >> > >> >> If I remove a pet from a parent, then we remove the child from the >> >> parent, and *only* that pet. we dont remove other pets that might be >> >> associated with that child. >> > >> > >> > Correct, this would be case A and the idea behind it is that, if a User >> has >> > *access* to a Child, it should also have access to all of the Child's >> pets. >> > If for some reason Parent does not have access to all of them, means it >> does >> > not have access to the Child either. >> > >> > Removing a pet that has a Child that is also present in user.children >> should >> > trigger this, leaving the parent with, as you say, only pets minus the >> one >> > that was removed. It would also remove the child from the >> parent/children >> > relationship in the association table. >> > >> >> if I remove a child from the parent, then we remove *all* pets >> >> associated with the child from that parent. >> > >> > >> > Correct, and this would be case B >> > >> >> This seems like it's a contradiction. I have parent p1, not referring >> >> to child c1, but it refers to pet p1 which *does* refer to child c1, >> >> and that is valid. There's basically two flavors of "remove child >> >> from parent", is that right? >> > >> > >> > Yes, both relationships -parent/pet and parent/child- are not seeing >> each >> > other, and the only binding is >> > their relationship between child and pets. >> > >> > Thus, the two flavors are depending on where is that listener being >> called >> > from. For the case A, this is called inside the if statement in >> > _remove_pets. Outside of this it should be always case B. >> > >> >> I tried to work on an implementation here which would also have to be >> >> extremely clever but I realized I don't actually understand what this >> >> is supposed to do. if "remove child from parent" has two different >> >> flavors then there needs to be all kinds of trickery to protect the >> >> events from each other. >> > >> > >> > I understand that it requires a lot of fiddling. I was trying to pass >> kwargs >> > to the listener directly, and parse them inside the other listener, but >> the >> > other listener is not receiving them. I was seeing this is on purpose >> here , >> > since accepting kwargs would pollute the API. Perhaps a custom event >> > implementation? >> >> I slowly realized it looked like you hoped that flag would pass >> through but there's too many layers of indirection for it to work that >> way. the main complication here is that those "dynamic" relationships >> require that a query runs for everything, which means everything has >> to be in the database, which means it flushes the session very >> aggressively (and also disabling the flush, another thing I tried, >> means it doesn't read the contents of the collections accurately), and >> all of that makes an already tricky operation nearly impossible >> without it barreling into itself. >> >> If you are committed to using the "dynamic" relationships, you can >> always rely on emitting SQL to read from those association tables, and >> there's a completely unorthodox way to do this which would be way more >> efficient in most cases, and is extremely simple, just emit the DELETE >> statements: >> >> from sqlalchemy.orm import object_session >> >> @sa.event.listens_for(Parent.children, 'remove') >> def _remove_children(parent, child, initiator, *args, **kwargs): >> object_session(parent).execute( >> "delete from parents_pets_relationship where " >> "parent_id=:parent_id and pet_id=:pet_id", >> [ >> {"parent_id": parent.id, "pet_id": pet.id} >> for pet in child.pets >> ] >> ) >> >> >> @sa.event.listens_for(Parent.pets, 'remove') >> def _remove_pets(parent, pet, initiator, *args, **kwargs): >> object_session(parent).execute( >> "delete from parents_children_relationship where " >> "parent_id=:parent_id and child_id=:child_id", >> {"parent_id": parent.id, "child_id": pet.child.id} >> ) >> >> I added a second test for the other case and this works as far as I've >> gotten it. since we are only dealing with these standalone >> association tables the above is pretty simple, and now you only emit >> one query (albeit a DML query, not a SELECT) rather than lots of >> SELECT statements. The above is at least very simple to work >> with since the ORM has no involvement past getting you those events. >> >> if the above does what you need then it's probably worth doing that way. >> >> >> >> > >> > Thanks again for your time >> > >> > >> > >> > Diego Alexandro Quintana Valenzuela >> > Ingeniero Civil Electricista >> > Universidad de la Frontera - Chile >> > +56 9 7965 3455 >> > >> > IEEE PES & CIS Member >> > diego.q...@ieee.org >> > LinkedIn >> > https://www.linkedin.com/in/diego-quintana-valenzuela/ >> > >> > 2018-05-02 18:46 GMT-03:00 Mike Bayer <mik...@zzzcomputing.com>: >> >> >> >> On Wed, May 2, 2018 at 12:22 PM, Diego Quintana <daqui...@gmail.com> >> >> wrote: >> >> > Hello, thanks again for your help. I'm not sure I understand what >> you >> >> > said >> >> > totally, and I believe this is the most simple MCVE I can provide. >> >> > >> >> > My local tests use postgresql, but I'm setting an in-memory sqlite3 >> >> > engine >> >> > here. I'm not fond of the differences between two backends, but the >> >> > tests >> >> > run without problems. >> >> >> >> So this is great, and shows the problem. but what you are trying to >> >> do here is deeply complicated. I was going to just type out >> >> everything I did to figure this out but this was way too long a >> >> process. >> >> >> >> at the core is that when you remove a child from the parent in the >> >> _remove_pets event, you want to prevent the _remove_children() event >> >> from actually happening, I think. >> >> >> >> If I remove a pet from a parent, then we remove the child from the >> >> parent, and *only* that pet. we dont remove other pets that might be >> >> associated with that child. >> >> >> >> if I remove a child from the parent, then we remove *all* pets >> >> associated with the child from that parent. >> >> >> >> This seems like it's a contradiction. I have parent p1, not referring >> >> to child c1, but it refers to pet p1 which *does* refer to child c1, >> >> and that is valid. There's basically two flavors of "remove child >> >> from parent", is that right? >> >> >> >> I tried to work on an implementation here which would also have to be >> >> extremely clever but I realized I don't actually understand what this >> >> is supposed to do. if "remove child from parent" has two different >> >> flavors then there needs to be all kinds of trickery to protect the >> >> events from each other. >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> > import sqlalchemy as sa >> >> > from sqlalchemy.ext.declarative import declarative_base >> >> > >> >> > Base = declarative_base() >> >> > >> >> > # many to many relationship between parents and children >> >> > parents_children_relationship = >> >> > sa.Table('parents_children_relationship', >> >> > Base.metadata, >> >> > sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')), >> >> >> > sa.Column('child_id', sa.Integer, sa.ForeignKey('children.id')), >> >> >> > sa.UniqueConstraint('parent_id', 'child_id')) >> >> > >> >> > # many to many relationship between User and Pet >> >> > parents_pets_relationship = sa.Table('parents_pets_relationship', >> >> > Base.metadata, >> >> > sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')), >> >> >> > sa.Column('pet_id', sa.Integer, sa.ForeignKey('pets.id')), >> >> > sa.UniqueConstraint('parent_id', 'pet_id')) >> >> > >> >> > class Parent(Base): >> >> > __tablename__ = 'parents' >> >> > >> >> > id = sa.Column(sa.Integer, primary_key=True) >> >> > name = sa.Column(sa.String(64)) >> >> > >> >> > # many to many relationship between parent and children >> >> > # my case allows for a children to have many parents. Don't ask. >> >> > children = sa.orm.relationship('Child', >> >> > secondary=parents_children_relationship, >> >> > backref=sa.orm.backref('parents', >> >> > lazy='dynamic'), >> >> > lazy='dynamic') >> >> > >> >> > # many to many relationship between parents and pets >> >> > pets = sa.orm.relationship('Pet', >> >> > secondary=parents_pets_relationship, >> >> > backref=sa.orm.backref('parents', >> >> > lazy='dynamic'), # >> >> > lazy='dynamic') >> >> > >> >> > >> >> > def __repr__(self): >> >> > return '<Parent (name=%r)>' % (self.name) >> >> > >> >> > class Child(Base): >> >> > __tablename__ = 'children' >> >> > id = sa.Column(sa.Integer, primary_key=True) >> >> > name = sa.Column(sa.String(64)) >> >> > # parents = <backref relationship with User model> >> >> > >> >> > # one to many relationship with pets >> >> > pets = sa.orm.relationship('Pet', backref='child', >> lazy='dynamic') >> >> > >> >> > def __repr__(self): >> >> > return '<Child (name=%r)>' % (self.name) >> >> > >> >> > class Pet(Base): >> >> > __tablename__ = 'pets' >> >> > id = sa.Column(sa.Integer, primary_key=True) >> >> > name = sa.Column(sa.String(64)) >> >> > # child = backref relationship with cities >> >> > child_id = sa.Column(sa.Integer, sa.ForeignKey('children.id'), >> >> > nullable=True) >> >> > # parents = <relationship backref from User> >> >> > >> >> > def __repr__(self): >> >> > return '<Pet (name=%r)>' % (self.name) >> >> > >> >> > >> >> > >> >> > @sa.event.listens_for(Parent.children, 'append') >> >> > def _append_children(parent, child, initiator): >> >> > """ >> >> > If a new child is appended to the parent, this listener >> >> > will also add the pets bound to the child being bound to the >> parent. >> >> > """ >> >> > # appends also the pets bound to the child that the >> >> > # parent is being appended to >> >> > parent.pets.extend(child.pets.all()) >> >> > >> >> > @sa.event.listens_for(Parent.children, 'remove') >> >> > def _remove_children(parent, child, initiator, *args, **kwargs): >> >> > """ >> >> > If a child is removed from the parent, this listener >> >> > will also remove only remove_single_pet --> <Pet> >> >> > """ >> >> > >> >> > remove_single_pet = kwargs.get('remove_single_pet', None) >> >> > >> >> > if remove_single_pet is not None: >> >> > parent.pets.remove(remove_single_pet) >> >> > else: # removes every pet >> >> > for pet in child.pets: >> >> > parent.pets.remove(pet) >> >> > >> >> > @sa.event.listens_for(Parent.pets, 'remove') >> >> > def _remove_pets(parent, pet, initiator, *args, **kwargs): >> >> > """ >> >> > If a pet is removed from the parent, and the parent also is >> related >> >> > to the child that has access to that pet, then >> >> > >> >> > * removes relationship with the child, and >> >> > * keeps relationship with the remaining pets, except the one >> that >> >> > was >> >> > removed >> >> > """ >> >> > >> >> > if pet.child in parent.children.all(): >> >> > remove_single_pet = pet >> >> > _remove_children(parent, pet.child, initiator, >> >> > remove_single_pet) >> >> > >> >> > >> >> > #### test ### >> >> > >> >> > import unittest >> >> > from sqlalchemy import create_engine >> >> > from sqlalchemy.orm import sessionmaker >> >> > >> >> > class BasicTestModelCase(unittest.TestCase): >> >> > >> >> > def setUp(self): >> >> > e = create_engine("sqlite://", echo=True) >> >> > Base.metadata.create_all(e) >> >> > >> >> > Session = sessionmaker(bind=e) >> >> > self.session = Session() >> >> > >> >> > >> >> > def tearDown(self): >> >> > # Base.metadata.drop_all() >> >> > pass >> >> > >> >> > def test_child_pet_relationship_on_parents_combined(self): >> >> > """ >> >> > Test that a parent can be hold children and pets that don't >> >> > belong necessary to the child, given the behaviour tested in >> the >> >> > previous test. >> >> > """ >> >> > >> >> > # create new parent >> >> > test_parent = Parent(name='test_parent') >> >> > >> >> > child1 = Child(id=1, >> >> > name='FakeChild1') >> >> > >> >> > child2 = Child(id=2, >> >> > name='FakeChild2') >> >> > >> >> > pet1 = Pet(id=1, >> >> > name='FakePet1', >> >> > child_id=1) >> >> > >> >> > pet2 = Pet(id=2, >> >> > name='FakePet2', >> >> > child_id=2) >> >> > >> >> > pet3 = Pet(id=3, >> >> > name='FakePet3', >> >> > child_id=1) >> >> > >> >> > self.session.add(test_parent) >> >> > self.session.add(child1) >> >> > self.session.add(child2) >> >> > self.session.add(pet1) >> >> > self.session.add(pet2) >> >> > self.session.add(pet3) >> >> > self.session.commit() >> >> > >> >> > # add parent to the child >> >> > child1.parents.append(test_parent) >> >> > self.session.add(child1) >> >> > self.session.commit() >> >> > >> >> > # add parent to the child >> >> > pet2.parents.append(test_parent) >> >> > >> >> > # persist changes in the db >> >> > self.session.add(pet2) >> >> > self.session.commit() >> >> > >> >> > print(test_parent.pets.all()) >> >> > print(child2.pets.all()) >> >> > >> >> > # check that previous relationships are intact >> >> > self.assertTrue(child1.pets.all() == [pet1, pet3]) >> >> > self.assertTrue(child2.pets.all() == [pet2]) >> >> > >> >> > # resultant elements should be only child1, its pets and the >> >> > single >> >> > Pet >> >> > self.assertTrue(test_parent.children.all() == [child1]) >> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2, >> pet3]) >> >> > >> >> > # remove child from parent >> >> > pet3.parents.remove(test_parent) ## ERROR here >> >> > >> >> > # resultant elements should be remaining pets, and no child >> >> > self.assertTrue(test_parent.children.all() == []) >> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2]) # >> pet2 >> >> > was >> >> > not touched, >> >> > # >> but >> >> > pet1 >> >> > should remain since only >> >> > # >> pet3 >> >> > was >> >> > removed >> >> > # >> child1 >> >> > should be also removed since >> >> > # >> >> > relationship is unbalanced, i.e. >> >> > # >> user >> >> > can't >> >> > have access to a child if it >> >> > # >> does >> >> > not >> >> > have access to all of the child's pets >> >> > >> >> > if __name__ == '__main__': >> >> > # run tests >> >> > unittest.main() >> >> > >> >> > Am Mittwoch, 2. Mai 2018 11:40:14 UTC-3 schrieb Mike Bayer: >> >> >> >> >> >> On Wed, May 2, 2018 at 10:14 AM, Diego Quintana <daqui...@gmail.com> >> >> >> >> wrote: >> >> >> > This worked. >> >> >> > >> >> >> > I'm trying to achieve some rather tricky behaviour, where >> >> >> > >> >> >> > Adding a children to some parent will also add the child's pets >> to >> >> >> > the >> >> >> > parent >> >> >> > Removing a children from some parent will also remove every >> current >> >> >> > relationship that the Parent has with such pet >> >> >> > If upon removal of a pet from a Parent, there is a Pet.child that >> is >> >> >> > also in >> >> >> > Parent.children, >> >> >> > >> >> >> > remove that Child from Parent, but keep existing relationships in >> >> >> > Parent.pets except the pet that is being removed >> >> >> > else only remove the pet from the parent >> >> >> > >> >> >> > >> >> >> > Some code I'm using for this is >> >> >> > >> >> >> > @db.event.listens_for(Parent.children, 'append') >> >> >> > def _append_children(parent, child, initiator): >> >> >> > """ >> >> >> > If a new child is appended to the parent, this listener >> >> >> > will also add the pets bound to the child being bound to the >> >> >> > parent. >> >> >> > """ >> >> >> > # appends also the pets bound to the child that the >> >> >> > # parent is being appended to >> >> >> > parent.pets.extend(child.pets.all()) >> >> >> > >> >> >> > @db.event.listens_for(Parent.children, 'remove') >> >> >> > def _remove_children(parent, child, initiator, *args, **kwargs): >> >> >> > """ >> >> >> > If a child is removed from the parent, this listener >> >> >> > will also remove only remove_single_pet --> <Pet> >> >> >> > """ >> >> >> > >> >> >> > remove_single_pet = kwargs.get('remove_single_pet', None) >> >> >> > >> >> >> > if remove_single_pet is not None: >> >> >> > parent.pets.remove(remove_single_pet) >> >> >> > else: # removes every pet >> >> >> > for pet in child.pets: >> >> >> > parent.pets.remove(pet) >> >> >> > >> >> >> > >> >> >> > @db.event.listens_for(Parent.pets, 'remove') >> >> >> > def _remove_pets(parent, pet, initiator, *args, **kwargs): >> >> >> > """ >> >> >> > If a pet is removed from the parent, and the parent also is >> >> >> > related >> >> >> > to the child that has access to that pet, then >> >> >> > >> >> >> > * removes relationship with the child, and >> >> >> > * keeps relationship with the remaining pets, except the one >> that >> >> >> > was >> >> >> > removed >> >> >> > """ >> >> >> > >> >> >> > if pet.child in parent.children.all(): >> >> >> > remove_single_pet = pet >> >> >> > _remove_children(parent, pet.child, initiator, >> >> >> > remove_single_pet) >> >> >> > >> >> >> > >> >> >> > #### test.py >> >> >> > >> >> >> > def test_child_pet_relationship_on_parents(self): >> >> >> > >> >> >> > >> >> >> > # create new parent >> >> >> > test_parent = Parent(name='test_parent') >> >> >> > >> >> >> > # commit parent to the database >> >> >> > db.session.add(test_parent) >> >> >> > db.session.commit() >> >> >> > >> >> >> > child1 = Child(id=1, >> >> >> > name='FakeChild1') >> >> >> > >> >> >> > child2 = Child(id=2, >> >> >> > name='FakeChild2') >> >> >> > >> >> >> > pet1 = Pet(id=1, >> >> >> > name='FakePet1', >> >> >> > child_id=1) >> >> >> > >> >> >> > pet2 = Pet(id=2, >> >> >> > name='FakePet2', >> >> >> > child_id=2) >> >> >> > >> >> >> > pet3 = Pet(id=3, >> >> >> > name='FakePet3', >> >> >> > child_id=1) >> >> >> > >> >> >> > db.session.add(child1) >> >> >> > db.session.add(child2) >> >> >> > db.session.add(pet1) >> >> >> > db.session.add(pet2) >> >> >> > db.session.add(pet3) >> >> >> > >> >> >> > db.session.commit() >> >> >> > >> >> >> > # add parent to the child >> >> >> > child1.parents.append(test_parent) >> >> >> > # add parent to the child >> >> >> > pet2.parents.append(test_parent) >> >> >> > >> >> >> > # persist changes in the db >> >> >> > db.session.add(child1) >> >> >> > db.session.add(pet2) >> >> >> > db.session.commit() >> >> >> > >> >> >> > # check that previous relationships are intact >> >> >> > self.assertTrue(child1.pets.all() == [pet1, pet3]) >> >> >> > self.assertTrue(child2.pets.all() == [pet2]) >> >> >> > >> >> >> > # resultant elements should be only child1, its pets and >> the >> >> >> > single >> >> >> > Pet >> >> >> > self.assertTrue(test_parent.children.all() == [child1]) >> >> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2, >> pet3]) >> >> >> > >> >> >> > # remove child from parent >> >> >> > pet3.parents.remove(test_parent) >> >> >> > >> >> >> > # resultant elements should be remaining pets, and no >> child >> >> >> > self.assertTrue(test_parent.children.all() == []) >> >> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2]) # >> >> >> > pet2 >> >> >> > was >> >> >> > not touched, >> >> >> > # >> but >> >> >> > pet1 >> >> >> > should remain since only >> >> >> > # >> >> >> > pet3 >> >> >> > was >> >> >> > removed >> >> >> > # >> >> >> > child1 >> >> >> > should be also removed since >> >> >> > # >> >> >> > relationship is unbalanced, i.e. >> >> >> > # >> >> >> > user >> >> >> > can't >> >> >> > have access to a child if it >> >> >> > # >> >> >> > does >> >> >> > not >> >> >> > have access to all of the child's pets >> >> >> > >> >> >> > >> >> >> > >> >> >> > I'm having errors >> >> >> > >> >> >> > sqlalchemy.orm.exc.StaleDataError: DELETE statement on table >> >> >> > 'parent_pets_relationship' expected to delete 1 row(s); Only 0 >> were >> >> >> > matched. >> >> >> >> >> >> it looks like you have an explicit association mapping on the >> >> >> secondary table as described in the green box in the section >> >> >> >> >> >> >> >> >> >> https://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#association-object, >> >> >> >> >> but I dont' have your complete mappings so I can't say for sure, >> you'd >> >> >> need to provide a complete MCVE. >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> > >> >> >> > and the logger says nothing much. I suspect I'm falling into some >> >> >> > weird >> >> >> > recursion, calling the listener from another listener, where the >> >> >> > second >> >> >> > call >> >> >> > can't find something that was already deleted. >> >> >> > >> >> >> > This might be a long shot, but I'm hoping this pattern might be >> >> >> > solved >> >> >> > already. >> >> >> > >> >> >> > >> >> >> > Am Donnerstag, 26. April 2018 13:00:45 UTC-3 schrieb Mike Bayer: >> >> >> >> >> >> >> >> On Thu, Apr 26, 2018 at 11:04 AM, Diego Quintana >> >> >> >> <daqui...@gmail.com> >> >> >> >> wrote: >> >> >> >> > Hello. >> >> >> >> > >> >> >> >> > Say I have three tables in a declarative fashion, `Parent`, >> >> >> >> > `Child`, >> >> >> >> > and >> >> >> >> > `Pet`, in such way that >> >> >> >> > >> >> >> >> > * `Parent` has a many-to-many relationship with both `Child` >> and >> >> >> >> > `Pet`, >> >> >> >> > meaning that a Parent can own a Child and its pets, and also a >> Pet >> >> >> >> > without >> >> >> >> > its Child. >> >> >> >> > * `Child` has a one-to-many relationship with `Pet` >> >> >> >> > >> >> >> >> > The code for them is (using Flask-SQLAlchemy, although I >> believe >> >> >> >> > the >> >> >> >> > solution lives in the realm of SQLAlchemy rather than in >> 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> >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > I would like to do something like this >> >> >> >> > >> >> >> >> > parent_a = Parent() >> >> >> >> > child_a = Child() >> >> >> >> > pet_a = Pet() >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > I can then do this >> >> >> >> > >> >> >> >> > parent_a.children.append(child_a) >> >> >> >> > # commit/persist data >> >> >> >> > parent_a.children.all() # [child_a] >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > I would like to achieve something like this >> >> >> >> > >> >> >> >> > 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. >> >> >> >> > >> >> >> >> > I can achieve this with a method in the Parent object like >> >> >> >> > add_child_and_its_pets(), but I would like to override the way >> >> >> >> > relationship >> >> >> >> > works, so I don't need to override other modules that may >> benefit >> >> >> >> > from >> >> >> >> > this >> >> >> >> > behaviour, like Flask-Admin for instance. >> >> >> >> > >> >> >> >> > >> >> >> >> > Basically how should I override the backref.append method or >> the >> >> >> >> > relationship.append method to also append other objects from >> other >> >> >> >> > relationships at call time i.e. on the python side? How should >> I >> >> >> >> > override >> >> >> >> > the remove methods as well? >> >> >> >> >> >> >> >> this seems like coordination of two separate relationships, so >> use >> >> >> >> attribute events for that, >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> http://docs.sqlalchemy.org/en/latest/orm/events.html?highlight=attributeevent#sqlalchemy.orm.events.AttributeEvents >> >> >> >> >> : >> >> >> >> >> >> >> >> @event.listens_for(Parent.children, "append") >> >> >> >> def _append_pets(parent, child, initiator): >> >> >> >> parent.pets.extend(child.pets) # or whatever it is you >> need >> >> >> >> >> >> >> >> you would need to look at the "append" and "remove" events. >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> > >> >> >> >> > >> >> >> >> > I have also posted this question in Stack Overflow, in case it >> >> >> >> > means >> >> >> >> > something. >> >> >> >> > >> >> >> >> > >> >> >> >> > Best! >> >> >> >> > >> >> >> >> > >> >> >> >> > >> >> >> >> > -- >> >> >> >> > SQLAlchemy - >> >> >> >> > The Python SQL Toolkit and Object Relational Mapper >> >> >> >> > >> >> >> >> > http://www.sqlalchemy.org/ >> >> >> >> > >> >> >> >> > To post example code, please provide an MCVE: Minimal, >> Complete, >> >> >> >> > and >> >> >> >> > Verifiable Example. See http://stackoverflow.com/help/mcve >> for a >> >> >> >> > full >> >> >> >> > description. >> >> >> >> > --- >> >> >> >> > You received this message because you are subscribed to the >> Google >> >> >> >> > Groups >> >> >> >> > "sqlalchemy" group. >> >> >> >> > To unsubscribe from this group and stop receiving emails from >> it, >> >> >> >> > send >> >> >> >> > an >> >> >> >> > email to sqlalchemy+...@googlegroups.com. >> >> >> >> > To post to this group, send email to sqlal...@googlegroups.com. >> >> >> >> >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> >> >> >> >> > For more options, visit https://groups.google.com/d/optout. >> >> >> > >> >> >> > -- >> >> >> > SQLAlchemy - >> >> >> > The Python SQL Toolkit and Object Relational Mapper >> >> >> > >> >> >> > http://www.sqlalchemy.org/ >> >> >> > >> >> >> > To post example code, please provide an MCVE: Minimal, Complete, >> and >> >> >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a >> full >> >> >> > description. >> >> >> > --- >> >> >> > You received this message because you are subscribed to the >> Google >> >> >> > Groups >> >> >> > "sqlalchemy" group. >> >> >> > To unsubscribe from this group and stop receiving emails from it, >> >> >> > send >> >> >> > an >> >> >> > email to sqlalchemy+...@googlegroups.com. >> >> >> > To post to this group, send email to sqlal...@googlegroups.com. >> >> >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> >> >> > For more options, visit https://groups.google.com/d/optout. >> >> > >> >> > -- >> >> > SQLAlchemy - >> >> > The Python SQL Toolkit and Object Relational Mapper >> >> > >> >> > http://www.sqlalchemy.org/ >> >> > >> >> > To post example code, please provide an MCVE: Minimal, Complete, and >> >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a >> full >> >> > description. >> >> > --- >> >> > You received this message because you are subscribed to the Google >> >> > Groups >> >> > "sqlalchemy" group. >> >> > To unsubscribe from this group and stop receiving emails from it, >> send >> >> > an >> >> > email to sqlalchemy+...@googlegroups.com. >> >> > To post to this group, send email to sqlal...@googlegroups.com. >> >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> >> > For more options, visit https://groups.google.com/d/optout. >> >> >> >> -- >> >> SQLAlchemy - >> >> The Python SQL Toolkit and Object Relational Mapper >> >> >> >> http://www.sqlalchemy.org/ >> >> >> >> To post example code, please provide an MCVE: Minimal, Complete, and >> >> Verifiable Example. See http://stackoverflow.com/help/mcve for a >> full >> >> description. >> >> --- >> >> You received this message because you are subscribed to a topic in the >> >> Google Groups "sqlalchemy" group. >> >> To unsubscribe from this topic, visit >> >> https://groups.google.com/d/topic/sqlalchemy/jgKgv5zQT7E/unsubscribe. >> >> To unsubscribe from this group and all its topics, send an email to >> >> sqlalchemy+...@googlegroups.com. >> >> To post to this group, send email to sqlal...@googlegroups.com. >> >> Visit this group at https://groups.google.com/group/sqlalchemy. >> >> For more options, visit https://groups.google.com/d/optout. >> > >> > >> > -- >> > SQLAlchemy - >> > The Python SQL Toolkit and Object Relational Mapper >> > >> > http://www.sqlalchemy.org/ >> > >> > To post example code, please provide an MCVE: Minimal, Complete, and >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full >> > description. >> > --- >> > You received this message because you are subscribed to the Google >> Groups >> > "sqlalchemy" group. >> > To unsubscribe from this group and stop receiving emails from it, send >> an >> > email to sqlalchemy+...@googlegroups.com. >> > To post to this group, send email to sqlal...@googlegroups.com. >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> > For more options, visit https://groups.google.com/d/optout. >> > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.