Skip to content
Snippets Groups Projects
Commit c6fa146ef147 authored by Denis Laxalde's avatar Denis Laxalde
Browse files

Make it possible to explicitly follow some relations while cloning

For instance, following non-composite relations.

Closes #15770380.
parent d37b90ee4885
No related branches found
No related tags found
No related merge requests found
......@@ -68,10 +68,10 @@
self.skipetypes = skipetypes
def parent_relations(self, etype):
"""Yield composite relation information items walking the graph
upstream from `etype`.
"""Yield graph relation information items walking the graph upstream
from `etype`.
These items are `(rtype, role), parents` where `parents` is a list of
possible parent entity types reachable through `(rtype, role)`
relation.
"""
......@@ -73,8 +73,8 @@
These items are `(rtype, role), parents` where `parents` is a list of
possible parent entity types reachable through `(rtype, role)`
relation.
"""
return self._composite_relations(etype, topdown=False)
return self._graph_relations(etype, topdown=False)
def child_relations(self, etype):
......@@ -79,8 +79,8 @@
def child_relations(self, etype):
"""Yield composite relation information items walking the graph
downstream from `etype`.
"""Yield graph relation information items walking the graph downstream
from `etype`.
These items are `(rtype, role), children` where `children` is a list of
possible child entity types reachable through `(rtype, role)` relation.
"""
......@@ -83,6 +83,6 @@
These items are `(rtype, role), children` where `children` is a list of
possible child entity types reachable through `(rtype, role)` relation.
"""
return self._composite_relations(etype, topdown=True)
return self._graph_relations(etype, topdown=True)
......@@ -88,3 +88,3 @@
def _composite_relations(self, etype, topdown):
def _graph_relations(self, etype, topdown, follow_relations=()):
"""Yield `(rtype, role), etypes` values corresponding to arcs of the
......@@ -90,6 +90,7 @@
"""Yield `(rtype, role), etypes` values corresponding to arcs of the
graph of compositely-related entity types reachable from a `etype`
entity type. `etypes` is a list of possible entity types reachable
graph of entity types reachable from a `etype` by following composite
relations and relations selected by `follow_relations` parameter at
initialization. `etypes` is a list of possible entity types reachable
through `(rtype, role)` relation "upstream" (resp. "downstream") if
`topdown` is True (resp. False).
"""
......@@ -100,9 +101,9 @@
for rschema, teschemas, role in eschema.relation_definitions():
if rschema.meta or rschema in self.skiprtypes:
continue
composite_role = role if topdown else neg_role(role)
target_role = role if topdown else neg_role(role)
relation, children = (rschema.type, role), []
for target in teschemas:
if target in self.skipetypes:
continue
rdef = rschema.role_rdef(eschema, target, role)
......@@ -104,9 +105,10 @@
relation, children = (rschema.type, role), []
for target in teschemas:
if target in self.skipetypes:
continue
rdef = rschema.role_rdef(eschema, target, role)
if rdef.composite != composite_role:
if ((rschema.type, target_role) not in follow_relations
and rdef.composite != target_role):
continue
children.append(target.type)
if children:
......@@ -145,5 +147,5 @@
These items are tuples `(child, (rtype, role), parent)` where `role` is
the role of `child` entity in `rtype` relation with `parent`.
"""
return self._composite_related(entity, False)
return self._graph_related(entity, False)
......@@ -149,8 +151,8 @@
def child_related(self, entity):
def child_related(self, entity, follow_relations=()):
"""Yield information items on entities related to `entity` through
composite relations walking the graph downstream from `entity`.
These items are tuples `(parent, (rtype, role), child)` where `role` is
the role of `parent` entity in `rtype` relation with `child`.
"""
......@@ -151,8 +153,9 @@
"""Yield information items on entities related to `entity` through
composite relations walking the graph downstream from `entity`.
These items are tuples `(parent, (rtype, role), child)` where `role` is
the role of `parent` entity in `rtype` relation with `child`.
"""
return self._composite_related(entity, True)
return self._graph_related(entity, True,
follow_relations=follow_relations)
......@@ -158,7 +161,9 @@
def _composite_related(self, entity, topdown, _visited=None):
"""Yield arcs of the graph of compositely-related entities reachable
from `entity`.
def _graph_related(self, entity, topdown, follow_relations=(),
_visited=None):
"""Yield arcs of the graph of made from entities reachable from
`entity` through composite relations or relations specified in
`follow_relations`.
An "arc" is a tuple `(l_entity, (rtype, role), r_entity)` where
`l_entity` is a "parent" (resp. "child") entity when `topdown` is True
......@@ -168,11 +173,11 @@
"""
if _visited is None:
_visited = set()
for (rtype, role), targettypes in self._composite_relations(
entity.cw_etype, topdown):
for (rtype, role), targettypes in self._graph_relations(
entity.cw_etype, topdown, follow_relations=follow_relations):
rset = entity.related(rtype, role=role, targettypes=targettypes)
for target in rset.entities():
yield entity, (rtype, role), target
if target.eid in _visited:
continue
_visited.add(target.eid)
......@@ -173,8 +178,10 @@
rset = entity.related(rtype, role=role, targettypes=targettypes)
for target in rset.entities():
yield entity, (rtype, role), target
if target.eid in _visited:
continue
_visited.add(target.eid)
for x in self._composite_related(target, topdown, _visited):
for x in self._graph_related(
target, topdown, follow_relations=follow_relations,
_visited=_visited):
yield x
......@@ -168,6 +168,8 @@
rtype, role = None, 'object' # relation between the clone and the original.
skiprtypes = ()
skipetypes = ()
# non-composite relations' (rtype, role) to explicitly follow.
follow_relations = ()
clone_relations = {} # registered relation type and role of the original entity.
# hooks categories that should be activated during cloning
enabled_hook_categories = ['metadata']
......@@ -196,6 +198,8 @@
assert clone.cw_etype == self.entity.cw_etype, \
"clone entity type {} does not match with original's {}".format(
clone.cw_etype, self.entity.cw_etype)
related = self.graph.child_related(
self.entity, follow_relations=self.follow_relations)
with self._cw.deny_all_hooks_but(*self.enabled_hook_categories):
clone.copy_relations(self.entity.eid)
clones = {self.entity: clone}
......@@ -199,7 +203,7 @@
with self._cw.deny_all_hooks_but(*self.enabled_hook_categories):
clone.copy_relations(self.entity.eid)
clones = {self.entity: clone}
for parent, (rtype, parent_role), child in self.graph.child_related(self.entity):
for parent, (rtype, parent_role), child in related:
rel = rtype if parent_role == 'object' else 'reverse_' + rtype
kwargs = {rel: clones[parent]}
clone = clones.get(child)
......
from cubicweb.predicates import has_related_entities
from cubes.compound.entities import (IClonableAdapter, IContained, IContainer,
structure_def)
......@@ -10,6 +12,16 @@
rtype = 'clone_of'
class AgentInGroupIClonableAdapter(IClonableAdapter):
"""IClonable for Agent member of a Group, following `member` relation for
cloning.
"""
__select__ = (IClonableAdapter.__select__
& has_related_entities('member', role='object'))
rtype = 'clone_of'
follow_relations = [('member', 'object')]
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__)
vreg.register(IContainer.build_class('Agent'))
......
......@@ -319,6 +319,28 @@
{'original': bob.eid, 'clone': clone.eid})
self.assertTrue(rset)
def test_clone_follow_relations(self):
with self.admin_access.repo_cnx() as cnx:
bob = cnx.create_entity('Agent', name=u'bob')
group = cnx.create_entity('Group', member=bob)
cnx.create_entity('OnlineAccount', reverse_account=bob)
cnx.commit()
with self.new_access(u'georges').repo_cnx() as cnx:
bob = cnx.find('Agent', name=u'bob').one()
clone = cnx.create_entity('Agent', name=u'bobby')
adapted = bob.cw_adapt_to('IClonable')
self.assertEqual(adapted.__class__.__name__,
'AgentInGroupIClonableAdapter')
adapted.clone_into(clone)
clone.cw_clear_all_caches()
cnx.commit()
rset = cnx.execute(
'Any G WHERE G member A, A name "bobby", OG eid %(og)s,'
' NOT G identity OG',
{'og': group.eid})
self.assertEqual(len(rset), 1)
self.assertEqual(clone.reverse_member[0].eid, rset[0][0])
def test_clone_full(self):
with self.admin_access.repo_cnx() as cnx:
agent = cnx.create_entity('Agent', name=u'bob')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment