--- a/entities/__init__.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entities/__init__.py Thu Mar 26 21:03:20 2009 +0100
@@ -23,36 +23,7 @@
"""an entity instance has e_schema automagically set on the class and
instances have access to their issuing cursor
"""
- id = 'Any'
- __rtags__ = {
- 'is' : ('generated', 'link'),
- 'is_instance_of' : ('generated', 'link'),
- 'identity' : ('generated', 'link'),
-
- # use primary and not generated for eid since it has to be an hidden
- # field in edition
- ('eid', '*', 'subject'): 'primary',
- ('creation_date', '*', 'subject'): 'generated',
- ('modification_date', '*', 'subject'): 'generated',
- ('has_text', '*', 'subject'): 'generated',
-
- ('require_permission', '*', 'subject') : ('generated', 'link'),
- ('owned_by', '*', 'subject') : ('generated', 'link'),
- ('created_by', '*', 'subject') : ('generated', 'link'),
-
- ('wf_info_for', '*', 'subject') : ('generated', 'link'),
- ('wf_info_for', '*', 'object') : ('generated', 'link'),
-
- ('description', '*', 'subject'): 'secondary',
-
- # XXX should be moved in their respective cubes
- ('filed_under', '*', 'subject') : ('generic', 'link'),
- ('filed_under', '*', 'object') : ('generic', 'create'),
- # generated since there is a componant to handle comments
- ('comments', '*', 'subject') : ('generated', 'link'),
- ('comments', '*', 'object') : ('generated', 'link'),
- }
-
+ id = 'Any'
__implements__ = (IBreadCrumbs, IFeed)
@classmethod
@@ -277,64 +248,11 @@
tschema = rschema.subjects(cls.e_schema)[0]
wdg = widget(cls.vreg, tschema, rschema, cls, 'object')
return wdg
-
+
+ @obsolete('use AutomaticEntityForm.relations_by_category')
def relations_by_category(self, categories=None, permission=None):
- if categories is not None:
- if not isinstance(categories, (list, tuple, set, frozenset)):
- categories = (categories,)
- if not isinstance(categories, (set, frozenset)):
- categories = frozenset(categories)
- eschema, rtags = self.e_schema, self.rtags
- if self.has_eid():
- eid = self.eid
- else:
- eid = None
- for rschema, targetschemas, role in eschema.relation_definitions(True):
- if rschema in ('identity', 'has_text'):
- continue
- # check category first, potentially lower cost than checking
- # permission which may imply rql queries
- if categories is not None:
- targetschemas = [tschema for tschema in targetschemas
- if rtags.get_tags(rschema.type, tschema.type, role).intersection(categories)]
- if not targetschemas:
- continue
- tags = rtags.get_tags(rschema.type, role=role)
- if permission is not None:
- # tag allowing to hijack the permission machinery when
- # permission is not verifiable until the entity is actually
- # created...
- if eid is None and ('%s_on_new' % permission) in tags:
- yield (rschema, targetschemas, role)
- continue
- if rschema.is_final():
- if not rschema.has_perm(self.req, permission, eid):
- continue
- elif role == 'subject':
- if not ((eid is None and rschema.has_local_role(permission)) or
- rschema.has_perm(self.req, permission, fromeid=eid)):
- continue
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (permission == 'add'
- and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
- and self.has_eid() and self.related(rschema.type, role)
- and not rschema.has_perm(self.req, 'delete', fromeid=eid,
- toeid=self.related(rschema.type, role)[0][0])):
- continue
- elif role == 'object':
- if not ((eid is None and rschema.has_local_role(permission)) or
- rschema.has_perm(self.req, permission, toeid=eid)):
- continue
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (permission == 'add'
- and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
- and self.has_eid() and self.related(rschema.type, role)
- and not rschema.has_perm(self.req, 'delete', toeid=eid,
- fromeid=self.related(rschema.type, role)[0][0])):
- continue
- yield (rschema, targetschemas, role)
+ from cubicweb.web.views.editform import AutomaticEntityForm
+ return AutomaticEntityForm.relations_by_category(entity, categories, permission)
def srelations_by_category(self, categories=None, permission=None):
result = []
--- a/entities/authobjs.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entities/authobjs.py Thu Mar 26 21:03:20 2009 +0100
@@ -13,7 +13,6 @@
class EGroup(AnyEntity):
id = 'EGroup'
fetch_attrs, fetch_order = fetch_config(['name'])
- __rtags__ = dict(in_group='create')
def db_key_name(self):
"""XXX goa specific"""
@@ -24,18 +23,6 @@
id = 'EUser'
fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname'])
- __rtags__ = { 'firstname' : 'secondary',
- 'surname' : 'secondary',
- 'last_login_time' : 'generated',
- 'todo_by' : 'create',
- 'use_email' : 'inlineview', # 'primary',
- 'in_state' : 'primary',
- 'in_group' : 'primary',
- ('owned_by', '*', 'object') : ('generated', 'link'),
- ('created_by','*','object') : ('generated', 'link'),
- ('bookmarked_by', '*', 'object'): ('generated', 'create'),
- }
-
# used by repository to check if the user can log in or not
AUTHENTICABLE_STATES = ('activated',)
--- a/entities/lib.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entities/lib.py Thu Mar 26 21:03:20 2009 +0100
@@ -25,10 +25,6 @@
id = 'EmailAddress'
fetch_attrs, fetch_order = fetch_config(['address', 'alias', 'canonical'])
- widgets = {
- 'address' : "EmailWidget",
- }
-
def dc_title(self):
if self.alias:
return '%s <%s>' % (self.alias, self.display_address())
@@ -94,13 +90,7 @@
class EProperty(AnyEntity):
id = 'EProperty'
- fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
-
- widgets = {
- 'pkey' : "PropertyKeyWidget",
- 'value' : "PropertyValueWidget",
- }
-
+ fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
rest_attr = 'pkey'
def typed_value(self):
@@ -120,10 +110,6 @@
"""customized class for Bookmark entities"""
id = 'Bookmark'
fetch_attrs, fetch_order = fetch_config(['title', 'path'])
- widgets = {
- 'path' : "StringWidget",
- }
- __rtags__ = {'path': 'primary'}
def actual_url(self):
url = self.req.build_url(self.path)
--- a/entities/schemaobjs.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entities/schemaobjs.py Thu Mar 26 21:03:20 2009 +0100
@@ -1,7 +1,7 @@
"""schema definition related entities
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -17,14 +17,7 @@
class EEType(AnyEntity):
id = 'EEType'
fetch_attrs, fetch_order = fetch_config(['name'])
- __rtags__ = {
- ('final', '*', 'subject'): 'generated',
-
- ('state_of', '*', 'object'): 'create',
- ('transition_of', '*', 'object'): 'create',
- ('from_entity', '*', 'object'): 'link',
- ('to_entity', '*', 'object'): 'link',
- }
+
def dc_title(self):
return self.req._(self.name)
@@ -47,11 +40,6 @@
class ERType(AnyEntity):
id = 'ERType'
fetch_attrs, fetch_order = fetch_config(['name'])
- __rtags__ = {
- ('final', '*', 'subject'): 'generated',
-
- ('relation_type', '*', 'object') : 'create',
- }
def dc_title(self):
return self.req._(self.name)
@@ -100,11 +88,6 @@
class ENFRDef(AnyEntity):
id = 'ENFRDef'
fetch_attrs = fetch_config(['cardinality'])[0]
- __rtags__ = {
- ('relation_type', 'ERType', 'subject') : 'inlineview',
- ('from_entity', 'EEType', 'subject') : 'inlineview',
- ('to_entity', 'EEType', 'subject') : 'inlineview',
- }
def dc_title(self):
return u'%s %s %s' % (
@@ -171,10 +154,6 @@
id = 'RQLExpression'
fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression'])
- widgets = {
- 'expression' : "StringWidget",
- }
-
def dc_title(self):
return '%s(%s)' % (self.exprtype, self.expression or u'')
@@ -209,11 +188,6 @@
id = 'EPermission'
fetch_attrs, fetch_order = fetch_config(['name', 'label'])
-
- __rtags__ = {
- 'require_group' : 'primary',
- }
-
def dc_title(self):
if self.label:
return '%s (%s)' % (self.req._(self.name), self.label)
--- a/entities/wfobjs.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entities/wfobjs.py Thu Mar 26 21:03:20 2009 +0100
@@ -1,14 +1,14 @@
"""workflow definition and history related entities
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from cubicweb.entities import AnyEntity, fetch_config
-
+
class Transition(AnyEntity):
"""customized class for Transition entities
@@ -17,9 +17,6 @@
"""
id = 'Transition'
fetch_attrs, fetch_order = fetch_config(['name'])
- __rtags__ = {('destination_state', '*', 'subject'): 'create',
- ('allowed_transition', '*', 'object') : 'create',
- }
def may_be_passed(self, eid, stateeid):
"""return true if the logged user may pass this transition
@@ -66,11 +63,7 @@
id = 'State'
fetch_attrs, fetch_order = fetch_config(['name'])
rest_attr = 'eid'
-
- __rtags__ = {'destination_state' : 'create',
- 'allowed_transition' : 'create'
- }
-
+
def transitions(self, entity, desteid=None):
rql = ('Any T,N,DS where S allowed_transition T, S eid %(x)s, '
'T name N, T destination_state DS, '
--- a/entity.py Thu Mar 26 20:54:40 2009 +0100
+++ b/entity.py Thu Mar 26 21:03:20 2009 +0100
@@ -6,6 +6,8 @@
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
+
from logilab.common import interface
from logilab.common.compat import all
from logilab.common.decorators import cached
@@ -39,164 +41,70 @@
return '1'
-class RelationTags(object):
-
- MODE_TAGS = frozenset(('link', 'create'))
- CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
- 'inlineview'))
+MODE_TAGS = set(('link', 'create'))
+CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
- def __init__(self, eclass, tagdefs):
- # XXX if a rtag is redefined in a subclass,
- # the rtag of the base class overwrite the rtag of the subclass
- self.eclass = eclass
- self._tagdefs = {}
- for relation, tags in tagdefs.iteritems():
- # tags must become a set
- if isinstance(tags, basestring):
- tags = set((tags,))
- elif not isinstance(tags, set):
- tags = set(tags)
- # relation must become a 3-uple (rtype, targettype, role)
- if isinstance(relation, basestring):
- self._tagdefs[(relation, '*', 'subject')] = tags
- self._tagdefs[(relation, '*', 'object')] = tags
- elif len(relation) == 1: # useful ?
- self._tagdefs[(relation[0], '*', 'subject')] = tags
- self._tagdefs[(relation[0], '*', 'object')] = tags
- elif len(relation) == 2:
- rtype, ttype = relation
- ttype = bw_normalize_etype(ttype) # XXX bw compat
- self._tagdefs[rtype, ttype, 'subject'] = tags
- self._tagdefs[rtype, ttype, 'object'] = tags
- elif len(relation) == 3:
- relation = list(relation) # XXX bw compat
- relation[1] = bw_normalize_etype(relation[1])
- self._tagdefs[tuple(relation)] = tags
- else:
- raise ValueError('bad rtag definition (%r)' % (relation,))
-
+try:
+ from cubicweb.web.views.editforms import AutomaticEntityForm
+ from cubicweb.web.views.boxes import EditBox
- def __initialize__(self):
- # eclass.[*]schema are only set when registering
- self.schema = self.eclass.schema
- eschema = self.eschema = self.eclass.e_schema
- rtags = self._tagdefs
- # expand wildcards in rtags and add automatic tags
- for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
- rtype = rschema.type
- star_tags = rtags.pop((rtype, '*', role), set())
- for tschema in tschemas:
- tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
- if role == 'subject':
- X, Y = eschema, tschema
- card = rschema.rproperty(X, Y, 'cardinality')[0]
- composed = rschema.rproperty(X, Y, 'composite') == 'object'
- else:
- X, Y = tschema, eschema
- card = rschema.rproperty(X, Y, 'cardinality')[1]
- composed = rschema.rproperty(X, Y, 'composite') == 'subject'
- # set default category tags if needed
- if not tags & self.CATEGORY_TAGS:
- if card in '1+':
- if not rschema.is_final() and composed:
- category = 'generated'
- elif rschema.is_final() and (
- rschema.type.endswith('_format')
- or rschema.type.endswith('_encoding')):
- category = 'generated'
- else:
- category = 'primary'
- elif rschema.is_final():
- if (rschema.type.endswith('_format')
- or rschema.type.endswith('_encoding')):
- category = 'generated'
- else:
- category = 'secondary'
- else:
- category = 'generic'
- tags.add(category)
- if not tags & self.MODE_TAGS:
- if card in '?1':
- # by default, suppose link mode if cardinality doesn't allow
- # more than one relation
- mode = 'link'
- elif rschema.rproperty(X, Y, 'composite') == role:
- # if self is composed of the target type, create mode
- mode = 'create'
- else:
- # link mode by default
- mode = 'link'
- tags.add(mode)
-
- def _default_target(self, rschema, role='subject'):
- eschema = self.eschema
- if role == 'subject':
- return eschema.subject_relation(rschema).objects(eschema)[0]
- else:
- return eschema.object_relation(rschema).subjects(eschema)[0]
-
- # dict compat
- def __getitem__(self, key):
- if isinstance(key, basestring):
- key = (key,)
- return self.get_tags(*key)
-
- __contains__ = __getitem__
+ def dispatch_rtags(tags, rtype, role, stype, otype):
+ for tag in tags:
+ if tag in MODE_TAGS:
+ EditBox.rmode.set_rtag(tag, rtype, role, stype, otype)
+ elif tag in CATEGORY_TAGS:
+ AutomaticEntityForm.rcategories.set_rtag(tag, rtype, role, stype, otype)
+ elif tag == 'inlined':
+ AutomaticEntityForm.rinline.set_rtag(True, rtype, role, stype, otype)
+ else:
+ raise ValueError(tag)
+
+except ImportError:
+ AutomaticEntityForm = None
- def get_tags(self, rtype, targettype=None, role='subject'):
- rschema = self.schema.rschema(rtype)
- if targettype is None:
- tschema = self._default_target(rschema, role)
- else:
- tschema = self.schema.eschema(targettype)
- return self._tagdefs[(rtype, tschema.type, role)]
-
- __call__ = get_tags
+ def dispatch_rtags(*args):
+ pass
- def get_mode(self, rtype, targettype=None, role='subject'):
- # XXX: should we make an assertion on rtype not being final ?
- # assert not rschema.is_final()
- tags = self.get_tags(rtype, targettype, role)
- # do not change the intersection order !
- modes = tags & self.MODE_TAGS
- assert len(modes) == 1
- return modes.pop()
-
- def get_category(self, rtype, targettype=None, role='subject'):
- tags = self.get_tags(rtype, targettype, role)
- categories = tags & self.CATEGORY_TAGS
- assert len(categories) == 1
- return categories.pop()
-
- def is_inlined(self, rtype, targettype=None, role='subject'):
- # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
- return 'inlineview' in self.get_tags(rtype, targettype, role)
-
-
class metaentity(type):
"""this metaclass sets the relation tags on the entity class
and deals with the `widgets` attribute
"""
def __new__(mcs, name, bases, classdict):
# collect baseclass' rtags
- tagdefs = {}
- widgets = {}
- for base in bases:
- tagdefs.update(getattr(base, '__rtags__', {}))
- widgets.update(getattr(base, 'widgets', {}))
- # update with the class' own rtgas
- tagdefs.update(classdict.get('__rtags__', {}))
- widgets.update(classdict.get('widgets', {}))
- # XXX decide whether or not it's a good idea to replace __rtags__
- # good point: transparent support for inheritance levels >= 2
- # bad point: we loose the information of which tags are specific
- # to this entity class
- classdict['__rtags__'] = tagdefs
- classdict['widgets'] = widgets
- eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
- # adds the "rtags" attribute
- eclass.rtags = RelationTags(eclass, tagdefs)
- return eclass
+ if '__rtags__' in classdict:
+ etype = classdict['id']
+ warn('%s: __rtags__ is deprecated' % name, DeprecationWarning)
+ for relation, tags in classdict.pop('__rtags__').iteritems():
+ # tags must become an iterable
+ if isinstance(tags, basestring):
+ tags = (tags,)
+ # relation must become a 3-uple (rtype, targettype, role)
+ if isinstance(relation, basestring):
+ dispatch_rtags(tags, relation, 'subject', etype, '*')
+ dispatch_rtags(tags, relation, 'object', '*', etype)
+ elif len(relation) == 1: # useful ?
+ dispatch_rtags(tags, relation[0], 'subject', etype, '*')
+ dispatch_rtags(tags, relation[0], 'object', '*', etype)
+ elif len(relation) == 2:
+ rtype, ttype = relation
+ ttype = bw_normalize_etype(ttype) # XXX bw compat
+ dispatch_rtags(tags, rtype, 'subject', etype, ttype)
+ dispatch_rtags(tags, rtype, 'object', ttype, etype)
+ elif len(relation) == 3:
+ rtype, ttype, role = relation
+ ttype = bw_normalize_etype(ttype)
+ if role == 'subject':
+ dispatch_rtags(tags, rtype, 'subject', etype, ttype)
+ else:
+ dispatch_rtags(tags, rtype, 'object', ttype, etype)
+ else:
+ raise ValueError('bad rtag definition (%r)' % (relation,))
+ if 'widgets' in classdict and AutomaticEntityForm is not None:
+ etype = classdict['id']
+ warn('%s: widgets is deprecated' % name, DeprecationWarning)
+ for relation, wdgname in classdict.pop('widgets').iteritems():
+ AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype)
+ return super(metaentity, mcs).__new__(mcs, name, bases, classdict)
class Entity(AppRsetObject, dict):
@@ -273,7 +181,6 @@
if mixins:
cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
cls.debug('plugged %s mixins on %s', mixins, etype)
- cls.rtags.__initialize__()
@classmethod
def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
--- a/web/views/baseforms.py Thu Mar 26 20:54:40 2009 +0100
+++ b/web/views/baseforms.py Thu Mar 26 21:03:20 2009 +0100
@@ -21,6 +21,7 @@
from cubicweb.web.controller import NAV_FORM_PARAMETERS
from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
from cubicweb.web.form import FormMixIn
+from cubicweb.web.views.editforms import AutomaticEntityForm
_ = unicode
@@ -326,7 +327,7 @@
# should_* method extracted to allow overriding
def should_inline_relation_form(self, entity, rschema, targettype, role):
- return entity.rtags.is_inlined(rschema, targettype, role)
+ return AutomaticForm.rinlined.etype_rtag(entity.id, role, rschema, targettype)
def should_display_inline_relation_form(self, rschema, existant, card):
return not existant and card in '1+'
@@ -428,7 +429,7 @@
def should_inline_relation_form(self, entity, rschema, targettype, role):
if rschema == self.rschema:
return False
- return entity.rtags.is_inlined(rschema, targettype, role)
+ return AutomaticForm.rinlined.etype_rtag(entity.id, role, rschema, targettype)
@cached
def keep_entity(self, entity):
--- a/web/views/editforms.py Thu Mar 26 20:54:40 2009 +0100
+++ b/web/views/editforms.py Thu Mar 26 21:03:20 2009 +0100
@@ -249,10 +249,11 @@
def editable_attributes(self):
"""return a list of (relation schema, role) to edit for the entity
"""
- return [(rschema, x) for rschema, _, x in self.relations_by_category(self.attrcategories, 'add')
- if rschema != 'eid']
-
- def relations_by_category(self, categories=None, permission=None):
+ return [(rschema, x) for rschema, _, x in self.relations_by_category(
+ self.entity, self.attrcategories, 'add') if rschema != 'eid']
+
+ @classmethod
+ def relations_by_category(cls, entity, categories=None, permission=None):
"""return a list of (relation schema, target schemas, role) matching
categories and permission
"""
@@ -261,11 +262,11 @@
categories = (categories,)
if not isinstance(categories, (set, frozenset)):
categories = frozenset(categories)
- eschema = self.edited_entity.e_schema
- rtags = self.rcategories
- permsoverrides = self.rpermissions_overrides
- if self.edited_entity.has_eid():
- eid = self.edited_entity.eid
+ eschema = entity.e_schema
+ rtags = cls.rcategories
+ permsoverrides = cls.rpermissions_overrides
+ if entity.has_eid():
+ eid = entity.eid
else:
eid = None
for rschema, targetschemas, role in eschema.relation_definitions(True):
@@ -286,31 +287,31 @@
yield (rschema, targetschemas, role)
continue
if rschema.is_final():
- if not rschema.has_perm(self.req, permission, eid):
+ if not rschema.has_perm(entity.req, permission, eid):
continue
elif role == 'subject':
if not ((eid is None and rschema.has_local_role(permission)) or
- rschema.has_perm(self.req, permission, fromeid=eid)):
+ rschema.has_perm(entity.req, permission, fromeid=eid)):
continue
# on relation with cardinality 1 or ?, we need delete perm as well
# if the relation is already set
if (permission == 'add'
and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
- and eid and self.edited_entity.related(rschema.type, role)
- and not rschema.has_perm(self.req, 'delete', fromeid=eid,
- toeid=self.edited_entity.related(rschema.type, role)[0][0])):
+ and eid and entity.related(rschema.type, role)
+ and not rschema.has_perm(entity.req, 'delete', fromeid=eid,
+ toeid=entity.related(rschema.type, role)[0][0])):
continue
elif role == 'object':
if not ((eid is None and rschema.has_local_role(permission)) or
- rschema.has_perm(self.req, permission, toeid=eid)):
+ rschema.has_perm(entity.req, permission, toeid=eid)):
continue
# on relation with cardinality 1 or ?, we need delete perm as well
# if the relation is already set
if (permission == 'add'
and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
- and eid and self.edited_entity.related(rschema.type, role)
- and not rschema.has_perm(self.req, 'delete', toeid=eid,
- fromeid=self.edited_entity.related(rschema.type, role)[0][0])):
+ and eid and entity.related(rschema.type, role)
+ and not rschema.has_perm(entity.req, 'delete', toeid=eid,
+ fromeid=entity.related(rschema.type, role)[0][0])):
continue
yield (rschema, targetschemas, role)
@@ -321,8 +322,8 @@
return a list of (relation's label, relation'schema, role)
"""
result = []
- for rschema, ttypes, role in self.relations_by_category(categories,
- permission):
+ for rschema, ttypes, role in self.relations_by_category(
+ self.entity, categories, permission):
if rschema.is_final():
continue
result.append( (rschema.display_name(self.req, role), rschema, role) )