Commits

Mike Bayer committed 1ef0324

insure that "parent" pointers are set up on objects that were lazily loaded

Comments (0)

Files changed (3)

 - added 'checkfirst' argument to table.create()/table.drop(), as 
 well as table.exists() [ticket:234]
 - some other ongoing fixes to inheritance [ticket:245]
+- attribute/backref/orphan/history-tracking tweaks as usual...
 
 0.2.5
 - fixed endless loop bug in select_by(), if the traversal hit

lib/sqlalchemy/attributes.py

                 if callable_ is not None:
                     if passive:
                         return InstrumentedAttribute.PASSIVE_NORESULT
-                    l = InstrumentedList(self, obj, self._adapt_list(callable_()), init=False)
+                    values = callable_()
+                    l = InstrumentedList(self, obj, self._adapt_list(values), init=False)
+                    if self.trackparent and values is not None:
+                        [self.sethasparent(v, True) for v in values if v is not None]
                     # if a callable was executed, then its part of the "committed state"
                     # if any, so commit the newly loaded data
                     orig = state.get('original', None)
                 if callable_ is not None:
                     if passive:
                         return InstrumentedAttribute.PASSIVE_NORESULT
-                    obj.__dict__[self.key] = callable_()
+                    value = callable_()
+                    obj.__dict__[self.key] = value
+                    if self.trackparent and value is not None:
+                        self.sethasparent(value, True)
                     # if a callable was executed, then its part of the "committed state"
                     # if any, so commit the newly loaded data
                     orig = state.get('original', None)

test/base/attributes.py

         class Post(object):pass
         class Blog(object):pass
         
-        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'))
-        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'))
+        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True)
+        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True)
         b = Blog()
         (p1, p2, p3) = (Post(), Post(), Post())
         b.posts.append(p1)
         j.port = None
         self.assert_(p.jack is None)
 
+    def testlazytrackparent(self):
+        """test that the "hasparent" flag works properly when lazy loaders and backrefs are used"""
+        manager = attributes.AttributeManager()
+
+        class Post(object):pass
+        class Blog(object):pass
+
+        # set up instrumented attributes with backrefs    
+        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True)
+        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True)
+
+        # create objects as if they'd been freshly loaded from the database (without history)
+        b = Blog()
+        p1 = Post()
+        Blog.posts.set_callable(b, lambda:[p1])
+        Post.blog.set_callable(p1, lambda:b)
+
+        # assert connections
+        assert p1.blog is b
+        assert p1 in b.posts
+
+        # no orphans
+        assert getattr(Blog, 'posts').hasparent(p1)
+        assert getattr(Post, 'blog').hasparent(b)
+        
+        
     def testinheritance(self):
         """tests that attributes are polymorphic"""
         class Foo(object):pass