[entity fetch_attrs] remove ambiguous relations on different etypes from fetched attrs or it may produce wrong related results; closes #1700896
--- a/entity.py Tue May 24 10:33:43 2011 +0200
+++ b/entity.py Wed May 25 08:35:20 2011 +0200
@@ -62,6 +62,23 @@
return True
+def remove_ambiguous_rels(attr_set, subjtypes, schema):
+ '''remove from `attr_set` the relations of entity types `subjtypes` that have
+ different entity type sets as target'''
+ for attr in attr_set.copy():
+ rschema = schema.rschema(attr)
+ if rschema.final:
+ continue
+ ttypes = None
+ for subjtype in subjtypes:
+ cur_ttypes = rschema.objects(subjtype)
+ if ttypes is None:
+ ttypes = cur_ttypes
+ elif cur_ttypes != ttypes:
+ attr_set.remove(attr)
+ break
+
+
class Entity(AppObject):
"""an entity instance has e_schema automagically set on
the class and instances has access to their issuing cursor.
@@ -222,6 +239,7 @@
if len(targettypes) > 1:
# find fetch_attrs common to all destination types
fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes)
+ remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema)
else:
fetchattrs = etypecls.fetch_attrs
etypecls._fetch_restrictions(var, varmaker, fetchattrs,
@@ -739,6 +757,10 @@
etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0])
if len(targettypes) > 1:
fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes)
+ # XXX we should fetch ambiguous relation objects too but not
+ # recurse on them in _fetch_restrictions; it is easier to remove
+ # them completely for now, as it would require an deeper api rewrite
+ remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema)
else:
fetchattrs = etypecls.fetch_attrs
rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs,
--- a/test/data/schema.py Tue May 24 10:33:43 2011 +0200
+++ b/test/data/schema.py Wed May 25 08:35:20 2011 +0200
@@ -18,9 +18,11 @@
from yams.buildobjs import (EntityType, String, SubjectRelation,
RelationDefinition)
+
from cubicweb.schema import (WorkflowableEntityType,
RQLConstraint, RQLVocabularyConstraint)
+
class Personne(EntityType):
nom = String(required=True)
prenom = String()
@@ -36,22 +38,40 @@
RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'),
RQLVocabularyConstraint('S travaille P, P nom "tutu"')])
+
class Societe(EntityType):
nom = String()
evaluee = SubjectRelation('Note')
+ fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*')
+
+
+class Service(EntityType):
+ fabrique_par = SubjectRelation('Personne', cardinality='1*')
+
+
+class Produit(EntityType):
+ fabrique_par = SubjectRelation('Usine', cardinality='1*')
+
+
+class Usine(EntityType):
+ lieu = String(required=True)
+
class Note(EntityType):
type = String()
ecrit_par = SubjectRelation('Personne')
+
class SubNote(Note):
__specializes_schema__ = True
description = String()
+
class tags(RelationDefinition):
subject = 'Tag'
object = ('Personne', 'Note')
+
class evaluee(RelationDefinition):
subject = 'CWUser'
object = 'Note'
@@ -59,5 +79,3 @@
class StateFull(WorkflowableEntityType):
name = String()
-
-
--- a/test/unittest_entity.py Tue May 24 10:33:43 2011 +0200
+++ b/test/unittest_entity.py Wed May 25 08:35:20 2011 +0200
@@ -250,7 +250,7 @@
'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, '
'X modification_date AB')
- def test_related_rql_ambigous_cant_use_fetch_order(self):
+ def test_related_rql_ambiguous_cant_use_fetch_order(self):
tag = self.vreg['etypes'].etype_class('Tag')(self.request())
for ttype in self.schema['tags'].objects():
self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',)
@@ -258,6 +258,18 @@
'Any X,AA ORDERBY AA DESC '
'WHERE E eid %(x)s, E tags X, X modification_date AA')
+ def test_related_rql_cant_fetch_ambiguous_rtype(self):
+ soc_etype = self.vreg['etypes'].etype_class('Societe')
+ soc = soc_etype(self.request())
+ soc_etype.fetch_attrs = ('fournit',)
+ self.vreg['etypes'].etype_class('Service').fetch_attrs = ('fabrique_par',)
+ self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',)
+ self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',)
+ self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',)
+ # XXX should be improved: we could fetch fabrique_par object too
+ self.assertEqual(soc.cw_related_rql('fournit', 'subject'),
+ 'Any X WHERE E eid %(x)s, E fournit X')
+
def test_unrelated_rql_security_1_manager(self):
user = self.request().user
rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]