diff --git a/entities.py b/entities.py index 3f7468af1f56adc97afa61a23720fa86db2702bb_ZW50aXRpZXMucHk=..b46c3f860ccb67718e867908ca273255d4018b3f_ZW50aXRpZXMucHk= 100644 --- a/entities.py +++ b/entities.py @@ -173,6 +173,14 @@ else: clones[child] = copy_entity(child, **kwargs) + @property + def clone(self): + """The entity being cloned from this entity (the original) or None.""" + rset = self.entity.related(self.rtype, role=self.role, limit=1) + if rset: + return rset.get_entity(0, 0) + return None + class IContained(EntityAdapter): """Abstract adapter for entities which are part of a container. diff --git a/hooks.py b/hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..b46c3f860ccb67718e867908ca273255d4018b3f_aG9va3MucHk= --- /dev/null +++ b/hooks.py @@ -0,0 +1,58 @@ +# copyright 2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr -- mailto:contact@logilab.fr +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +"""cubicweb-compound specific hooks and operations""" + + +from cubicweb.predicates import objectify_predicate +from cubicweb.server import hook + + +@objectify_predicate +def match_clone_rtype(cls, req, *args, **kwargs): + """Return 1 if `rtype` found in context matches with the `rtype` attribute + of a registered IClonable adapter bound to any of the entities + corresponding to `eidfrom` or `eidto` eids found in context. + """ + rtype = kwargs.get('rtype') + if rtype is None: + return 0 + for eidrole in ('eidfrom', 'eidto'): + entity = req.entity_from_eid(kwargs[eidrole]) + adapted = entity.cw_adapt_to('IClonable') + if adapted and adapted.rtype == rtype: + return 1 + return 0 + + +class CloneEntityHook(hook.Hook): + """Trigger cloning of an entity upon addition of a <clone_of> relation. + """ + __regid__ = 'compound.clone_entity_hook' + __select__ = hook.Hook.__select__ & match_clone_rtype() + events = ('after_add_relation', ) + + def __call__(self): + for eid in (self.eidfrom, self.eidto): + original = self._cw.entity_from_eid(eid) + adapted = original.cw_adapt_to('IClonable') + if adapted: + clone = adapted.clone + if clone: + break + else: + raise AssertionError( + '{} unexpectedly selected'.format(self.__regid__)) + adapted.clone_into(clone) diff --git a/test/unittest_entities.py b/test/unittest_entities.py index 3f7468af1f56adc97afa61a23720fa86db2702bb_dGVzdC91bml0dGVzdF9lbnRpdGllcy5weQ==..b46c3f860ccb67718e867908ca273255d4018b3f_dGVzdC91bml0dGVzdF9lbnRpdGllcy5weQ== 100644 --- a/test/unittest_entities.py +++ b/test/unittest_entities.py @@ -185,6 +185,17 @@ self.assertEqual(alice2.name, u'alice') self.assertEqual([x.eid for x in alice2.knows], [bob.eid]) + def test_clone_property(self): + """Test .clone property of IClonableAdapter.""" + with self.admin_access.repo_cnx() as cnx: + bob = cnx.create_entity('Agent', name=u'bob') + cnx.commit() + self.assertIsNone(bob.cw_adapt_to('IClonable').clone) + bobby = cnx.create_entity('Agent', name=u'bobby', clone_of=bob) + cnx.commit() + self.assertEqual(bob.cw_adapt_to('IClonable').clone, bobby) + self.assertIsNone(bobby.cw_adapt_to('IClonable').clone) + def test_clone_simple(self): with self.admin_access.repo_cnx() as cnx: bob = cnx.create_entity('Agent', name=u'bob') @@ -238,6 +249,26 @@ rset=entity.as_rset()) self.assertIsInstance(action, CloneAction) + def test_clone_hook(self): + with self.admin_access.repo_cnx() as cnx: + agent = cnx.create_entity('Agent', name=u'bob') + account = cnx.create_entity('OnlineAccount', reverse_account=agent) + bio = cnx.create_entity('Biography', reverse_biography=agent) + event = cnx.create_entity('Event', reverse_event=bio) + anecdote1 = cnx.create_entity('Anecdote', reverse_event=bio) + anecdote2 = cnx.create_entity('Anecdote', reverse_event=bio, narrated_by=agent) + 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', clone_of=bob) + cnx.commit() + clone.cw_clear_all_caches() + # Ensure all relation (to new entities) are present on the clone. + rset = cnx.execute( + 'Any X WHERE X name "bobby", X account AC, X biography B,' + ' B event E, E is Event, B event A, A narrated_by X') + self.assertTrue(rset) + if __name__ == '__main__': from logilab.common.testlib import unittest_main