# HG changeset patch # User Florent Cayré # Date 1306305320 -7200 # Node ID 7e9d1d6fcba7ac40bb530bab9d4a216a1308cd84 # Parent 2c72bfbbf1a3d9c3d439668859e2f269aa77e02e [entity fetch_attrs] remove ambiguous relations on different etypes from fetched attrs or it may produce wrong related results; closes #1700896 diff -r 2c72bfbbf1a3 -r 7e9d1d6fcba7 entity.py --- 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, diff -r 2c72bfbbf1a3 -r 7e9d1d6fcba7 test/data/schema.py --- 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() - - diff -r 2c72bfbbf1a3 -r 7e9d1d6fcba7 test/unittest_entity.py --- 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]