From 3a536ad1bad3efc272ade2914b3418844fe46b57 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 21 Apr 2013 11:31:29 -0400 Subject: [PATCH] Fixed bug where many-to-many relationship with uselist=False would fail to delete the association row and raise an error if the scalar attribute were set to None. Also in 0.7.11. [ticket:2710] --- doc/build/changelog/changelog_07.rst | 8 ++++++ doc/build/changelog/changelog_08.rst | 8 ++++++ lib/sqlalchemy/orm/dependency.py | 10 ++++--- test/orm/test_manytomany.py | 39 ++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/doc/build/changelog/changelog_07.rst b/doc/build/changelog/changelog_07.rst index c650e769f5..08f5cd94c6 100644 --- a/doc/build/changelog/changelog_07.rst +++ b/doc/build/changelog/changelog_07.rst @@ -6,6 +6,14 @@ .. changelog:: :version: 0.7.11 + .. change:: + :tags: bug, orm + :tickets: 2710 + + Fixed bug where many-to-many relationship with uselist=False + would fail to delete the association row and raise an error + if the scalar attribute were set to None. + .. change:: :tags: bug, orm :tickets: 2699 diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 2a2925726c..eab1c2f9e2 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,14 @@ .. changelog:: :version: 0.8.1 + .. change:: + :tags: bug, orm + :tickets: 2710 + + Fixed bug where many-to-many relationship with uselist=False + would fail to delete the association row and raise an error + if the scalar attribute were set to None. Also in 0.7.11. + .. change:: :tags: bug, orm :tickets: 2708 diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 8236e69447..7bb1d97369 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -863,7 +863,7 @@ class DetectKeySwitch(DependencyProcessor): related = state.get_impl(self.key).get(state, dict_, passive=self._passive_update_flag) if related is not attributes.PASSIVE_NO_RESULT and \ - related is not None: + related is not None: related_state = attributes.instance_state(dict_[self.key]) if related_state in switchers: uowcommit.register_object(state, @@ -1127,11 +1127,15 @@ class ManyToManyDP(DependencyProcessor): def _synchronize(self, state, child, associationrow, clearkeys, uowcommit, operation): - if associationrow is None: - return + # this checks for None if uselist=True self._verify_canload(child) + # but if uselist=False we get here. If child is None, + # no association row can be generated, so return. + if child is None: + return False + if child is not None and not uowcommit.session._contains_state(child): if not child.deleted: util.warn( diff --git a/test/orm/test_manytomany.py b/test/orm/test_manytomany.py index d3609b4633..3135258930 100644 --- a/test/orm/test_manytomany.py +++ b/test/orm/test_manytomany.py @@ -316,8 +316,10 @@ class AssortedPersistenceTests(fixtures.MappedTest): ) Table('secondary', metadata, - Column('left_id', Integer, ForeignKey('left.id')), - Column('right_id', Integer, ForeignKey('right.id')), + Column('left_id', Integer, ForeignKey('left.id'), + primary_key=True), + Column('right_id', Integer, ForeignKey('right.id'), + primary_key=True), ) @classmethod @@ -337,6 +339,17 @@ class AssortedPersistenceTests(fixtures.MappedTest): }) mapper(B, right) + def _bidirectional_onescalar_fixture(self): + left, secondary, right = self.tables.left, \ + self.tables.secondary, self.tables.right + A, B = self.classes.A, self.classes.B + mapper(A, left, properties={ + 'bs': relationship(B, secondary=secondary, + backref=backref('a', uselist=False), + order_by=right.c.id) + }) + mapper(B, right) + def test_session_delete(self): self._standard_bidirectional_fixture() A, B = self.classes.A, self.classes.B @@ -359,3 +372,25 @@ class AssortedPersistenceTests(fixtures.MappedTest): sess.flush() eq_(sess.query(secondary).count(), 0) + def test_remove_scalar(self): + # test setting a uselist=False to None + self._bidirectional_onescalar_fixture() + A, B = self.classes.A, self.classes.B + secondary = self.tables.secondary + + sess = Session() + sess.add_all([ + A(data='a1', bs=[B(data='b1'), B(data='b2')]), + ]) + sess.commit() + + a1 = sess.query(A).filter_by(data='a1').one() + b2 = sess.query(B).filter_by(data='b2').one() + assert b2.a is a1 + + b2.a = None + sess.commit() + + eq_(a1.bs, [B(data='b1')]) + eq_(b2.a, None) + eq_(sess.query(secondary).count(), 1) -- 2.47.2