.. seealso::
:ref:`change_3934`
+
+ .. change:: 3607
+ :tags: bug, orm
+ :tickets: 3607
+
+ Fixed bug where :meth:`.Query.with_parent` would not work if the
+ :class:`.Query` were against an :func:`.aliased` construct rather than
+ a regular mapped class. Also adds a new parameter
+ :paramref:`.util.with_parent.from_entity` to the standalone
+ :func:`.util.with_parent` function as well as
+ :meth:`.Query.with_parent`.
"""
self._invoke_all_eagers = value
- def with_parent(self, instance, property=None):
+ def with_parent(self, instance, property=None, from_entity=None):
"""Add filtering criterion that relates the given instance
to a child object or collection, using its attribute state
as well as an established :func:`.relationship()`
that the given property can be None, in which case a search is
performed against this :class:`.Query` object's target mapper.
+ :param instance:
+ An instance which has some :func:`.relationship`.
+
+ :param property:
+ String property name, or class-bound attribute, which indicates
+ what relationship from the instance should be used to reconcile the
+ parent/child relationship.
+
+ :param from_entity:
+ Entity in which to consider as the left side. This defaults to the
+ "zero" entity of the :class:`.Query` itself.
+
"""
+ if from_entity:
+ entity_zero = inspect(from_entity)
+ else:
+ entity_zero = self._entity_zero()
if property is None:
- mapper_zero = self._mapper_zero()
mapper = object_mapper(instance)
for prop in mapper.iterate_properties:
if isinstance(prop, properties.RelationshipProperty) and \
- prop.mapper is mapper_zero:
+ prop.mapper is entity_zero.mapper:
property = prop
break
else:
"Could not locate a property which relates instances "
"of class '%s' to instances of class '%s'" %
(
- self._mapper_zero().class_.__name__,
+ entity_zero.mapper.class_.__name__,
instance.__class__.__name__)
)
- return self.filter(with_parent(instance, property))
+ return self.filter(with_parent(instance, property, entity_zero.entity))
@_generative()
def add_entity(self, entity, alias=None):
mapperlib.Mapper._configure_all()
return self.prop
- def _with_parent(self, instance, alias_secondary=True):
+ def _with_parent(self, instance, alias_secondary=True, from_entity=None):
assert instance is not None
+ adapt_source = None
+ if from_entity is not None:
+ insp = inspect(from_entity)
+ if insp.is_aliased_class:
+ adapt_source = insp._adapter.adapt_clause
return self._optimized_compare(
- instance, value_is_parent=True, alias_secondary=alias_secondary)
+ instance, value_is_parent=True, adapt_source=adapt_source,
+ alias_secondary=alias_secondary)
def _optimized_compare(self, state, value_is_parent=False,
adapt_source=None,
return _ORMJoin(left, right, onclause, True, full)
-def with_parent(instance, prop):
+def with_parent(instance, prop, from_entity=None):
"""Create filtering criterion that relates this query's primary entity
to the given related instance, using established :func:`.relationship()`
configuration.
Python without the need to render joins to the parent table
in the rendered statement.
- .. versionchanged:: 0.6.4
- This method accepts parent instances in all
- persistence states, including transient, persistent, and detached.
- Only the requisite primary key/foreign key attributes need to
- be populated. Previous versions didn't work with transient
- instances.
-
:param instance:
An instance which has some :func:`.relationship`.
what relationship from the instance should be used to reconcile the
parent/child relationship.
+ :param from_entity:
+ Entity in which to consider as the left side. This defaults to the
+ "zero" entity of the :class:`.Query` itself.
+
+ .. versionadded:: 1.2
+
"""
if isinstance(prop, util.string_types):
mapper = object_mapper(instance)
elif isinstance(prop, attributes.QueryableAttribute):
prop = prop.property
- return prop._with_parent(instance)
+ return prop._with_parent(instance, from_entity=from_entity)
def has_identity(object):
{'param_1': 7}
)
- @testing.fails("issue #3607")
+ def test_from_entity_standalone_fn(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+ u1 = sess.query(User).get(7)
+ q = sess.query(User, Address).filter(
+ with_parent(u1, "addresses", from_entity=Address))
+ self.assert_compile(
+ q,
+ "SELECT users.id AS users_id, users.name AS users_name, "
+ "addresses.id AS addresses_id, addresses.user_id "
+ "AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM users, addresses "
+ "WHERE :param_1 = addresses.user_id",
+ {'param_1': 7}
+ )
+
+ def test_from_entity_query_entity(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+ u1 = sess.query(User).get(7)
+ q = sess.query(User, Address).with_parent(
+ u1, "addresses", from_entity=Address)
+ self.assert_compile(
+ q,
+ "SELECT users.id AS users_id, users.name AS users_name, "
+ "addresses.id AS addresses_id, addresses.user_id "
+ "AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM users, addresses "
+ "WHERE :param_1 = addresses.user_id",
+ {'param_1': 7}
+ )
+
def test_select_from_alias(self):
User, Address = self.classes.User, self.classes.Address
{'param_1': 7}
)
+ def test_select_from_alias_explicit_prop(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+ u1 = sess.query(User).get(7)
+ a1 = aliased(Address)
+ q = sess.query(a1).with_parent(u1, "addresses")
+ self.assert_compile(
+ q,
+ "SELECT addresses_1.id AS addresses_1_id, "
+ "addresses_1.user_id AS addresses_1_user_id, "
+ "addresses_1.email_address AS addresses_1_email_address "
+ "FROM addresses AS addresses_1 "
+ "WHERE :param_1 = addresses_1.user_id",
+ {'param_1': 7}
+ )
+
def test_noparent(self):
Item, User = self.classes.Item, self.classes.User