Commits

Mike Bayer committed ad42240

- Class-bound attributes sent as arguments to
relation()'s remote_side and foreign_keys parameters
are now accepted, allowing them to be used with
declarative (and therefore self-referential many-to-one
relations); merged from 0.5.

  • Participants
  • Parent commits 5b196f3
  • Branches rel_0_4

Comments (0)

Files changed (3)

       variable indicating their creation order, which 
       declarative_base() maintains when generating
       Table constructs.
+
+    - Class-bound attributes sent as arguments to 
+      relation()'s remote_side and foreign_keys parameters 
+      are now accepted, allowing them to be used with 
+      declarative (and therefore self-referential many-to-one
+      relations); merged from 0.5.
       
 0.4.6
 =====

File lib/sqlalchemy/orm/properties.py

 
 from sqlalchemy import sql, schema, util, exceptions, logging
 from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs, find_columns
-from sqlalchemy.sql import visitors, operators, ColumnElement
+from sqlalchemy.sql import visitors, operators, ColumnElement, expression
 from sqlalchemy.orm import mapper, sync, strategies, attributes, dependency, object_mapper
 from sqlalchemy.orm import session as sessionlib
 from sqlalchemy.orm.mapper import _class_to_mapper
         if self._legacy_foreignkey and not self._refers_to_parent_table():
             self.foreign_keys = self._legacy_foreignkey
 
-        arg_foreign_keys = self.foreign_keys
+        arg_foreign_keys = set(expression._literal_as_column(x) for x in util.to_set(self.foreign_keys))
 
         if self._arg_local_remote_pairs:
             if not arg_foreign_keys:
             else:
                 eq_pairs = self._arg_local_remote_pairs
         elif self.remote_side:
+            remote_side = set(expression._literal_as_column(x) for x in util.to_set(self.remote_side))
+            
             if self.direction is MANYTOONE:
-                eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_referenced_keys=self.remote_side, any_operator=True)
+                eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_referenced_keys=remote_side, any_operator=True)
             else:
-                eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self.remote_side, any_operator=True)
+                eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=remote_side, any_operator=True)
         else:
             if self.viewonly:
                 eq_pairs = self.synchronize_pairs

File test/ext/declarative.py

         assert User.addresses
         assert mapperlib._new_mappers is False
 
+    def test_uncompiled_attributes_in_relation(self):
+        class Address(Base, Fixture):
+            __tablename__ = 'addresses'
+            id = Column(Integer, primary_key=True)
+            email = Column(String(50))
+            user_id = Column(Integer, ForeignKey('users.id'))
+
+        class User(Base, Fixture):
+            __tablename__ = 'users'
+            id = Column(Integer, primary_key=True)
+            name = Column(String(50))
+            addresses = relation("Address", order_by=Address.email, 
+                foreign_keys=Address.user_id, 
+                remote_side=Address.user_id,
+                )
+
+        # get the mapper for User.   User mapper will compile,
+        # "addresses" relation will call upon Address.user_id for
+        # its clause element.  Address.user_id is a _CompileOnAttr,
+        # which then calls class_mapper(Address).  But !  We're already
+        # "in compilation", but class_mapper(Address) needs to initialize
+        # regardless, or COA's assertion fails
+        # and things generally go downhill from there.
+        class_mapper(User)
+
+        Base.metadata.create_all()
+
+        sess = create_session()
+        u1 = User(name='ed', addresses=[Address(email='abc'), Address(email='xyz'), Address(email='def')])
+        sess.save(u1)
+        sess.flush()
+        sess.clear()
+        self.assertEquals(sess.query(User).filter(User.name == 'ed').one(),
+            User(name='ed', addresses=[Address(email='abc'), Address(email='def'), Address(email='xyz')])
+        )
+
     def test_nice_dependency_error(self):
         class User(Base):
             __tablename__ = 'users'