diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-08 13:23:15 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-08 13:23:15 -0400 |
commit | 20d1e9c3fa8ccc99207988e27d89e18b1870bc2c (patch) | |
tree | 8e1584a5048fbbd6931118785890e5043cc9d5a8 /lib/sqlalchemy/ext/associationproxy.py | |
parent | d5363fca5400f6c4969c2756fcfcdae6b9703091 (diff) | |
download | sqlalchemy-20d1e9c3fa8ccc99207988e27d89e18b1870bc2c.tar.gz |
Added additional criterion to the ==, != comparators, used with
scalar values, for comparisons to None to also take into account
the association record itself being non-present, in addition to the
existing test for the scalar endpoint on the association record
being NULL. Previously, comparing ``Cls.scalar == None`` would return
records for which ``Cls.associated`` were present and
``Cls.associated.scalar`` is None, but not rows for which
``Cls.associated`` is non-present. More significantly, the
inverse operation ``Cls.scalar != None`` *would* return ``Cls``
rows for which ``Cls.associated`` was non-present.
Additionally, added a special use case where you
can call ``Cls.scalar.has()`` with no arguments,
when ``Cls.scalar`` is a column-based value - this returns whether or
not ``Cls.associated`` has any rows present, regardless of whether
or not ``Cls.associated.scalar`` is NULL or not.
[ticket:2751]
Diffstat (limited to 'lib/sqlalchemy/ext/associationproxy.py')
-rw-r--r-- | lib/sqlalchemy/ext/associationproxy.py | 30 |
1 files changed, 26 insertions, 4 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index 0482a9205..fca2f0008 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -17,7 +17,7 @@ import operator import weakref from .. import exc, orm, util from ..orm import collections, interfaces -from ..sql import not_ +from ..sql import not_, or_ def association_proxy(target_collection, attr, **kw): @@ -231,6 +231,10 @@ class AssociationProxy(interfaces._InspectionAttr): return not self._get_property().\ mapper.get_property(self.value_attr).uselist + @util.memoized_property + def _target_is_object(self): + return getattr(self.target_class, self.value_attr).impl.uses_objects + def __get__(self, obj, class_): if self.owning_class is None: self.owning_class = class_ and class_ or type(obj) @@ -388,10 +392,17 @@ class AssociationProxy(interfaces._InspectionAttr): """ - return self._comparator.has( + if self._target_is_object: + return self._comparator.has( getattr(self.target_class, self.value_attr).\ has(criterion, **kwargs) ) + else: + if criterion is not None or kwargs: + raise exc.ArgumentError( + "Non-empty has() not allowed for " + "column-targeted association proxy; use ==") + return self._comparator.has() def contains(self, obj): """Produce a proxied 'contains' expression using EXISTS. @@ -411,10 +422,21 @@ class AssociationProxy(interfaces._InspectionAttr): return self._comparator.any(**{self.value_attr: obj}) def __eq__(self, obj): - return self._comparator.has(**{self.value_attr: obj}) + # note the has() here will fail for collections; eq_() + # is only allowed with a scalar. + if obj is None: + return or_( + self._comparator.has(**{self.value_attr: obj}), + self._comparator == None + ) + else: + return self._comparator.has(**{self.value_attr: obj}) def __ne__(self, obj): - return not_(self.__eq__(obj)) + # note the has() here will fail for collections; eq_() + # is only allowed with a scalar. + return self._comparator.has( + getattr(self.target_class, self.value_attr) != obj) class _lazy_collection(object): |