Commit 99fd3503 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

Define a compound graph for SEDA profiles

This patch defines a compound graph using the compound cube API. It implements a
custom IContainer adapter which relies on a 'container' relation linking any
contained entity to its container (transitive closure). This relation allow to
reference the parent profile in rql expressions as needed by later cset. It's
dynamically set in hooks, using raw SQL for performance reason.

As we need to use seda_profile_container_def from schema, move it from entities
to package's __init__, introducing a dependancy on to-be-release compound 0.3.
parent 35f800ebb71a
......@@ -2,3 +2,10 @@
Data Exchange Standard for Archival
"""
from cubes.compound import structure_def
def seda_profile_container_def(schema):
"""Define container for SEDAProfile"""
return structure_def(schema, 'SEDAArchiveTransfer').items()
# copyright 2016 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/>.
"""Custom logic (eg entities and adapters) for cubicweb-seda."""
from cubes.compound.entities import IContainer, IContained
from cubes.seda import seda_profile_container_def
class DirectLinkIContained(IContained):
"""IContained implementation using a relation that link every contained entities to its parent
container.
"""
@property
def container(self):
"""Return the container to which this entity belongs, or None."""
container = self.entity.related('container', entities=True)
return container and container[0] or None
def registration_callback(vreg):
vreg.register(IContainer.build_class('SEDAArchiveTransfer'))
for etype, parent_relations in sorted(seda_profile_container_def(vreg.schema)):
DirectLinkIContained.register_class(vreg, etype, parent_relations)
# copyright 2016 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-seda hooks"""
from collections import defaultdict
from cubicweb.server import hook
SEDA_PARENT_RTYPES = {}
class SetContainerOp(hook.DataOperationMixIn, hook.Operation):
def precommit_event(self):
cnx = self.cnx
# build a mapping <eid>: <container eid>
containers = {}
data = dict(self.get_data()) # data is a 2-uple (eid, parent eid)
for eid in data:
# search for the container from the parent
peid = data[eid]
# climb up to the uppermost parent
while peid in data:
peid = data[peid]
adapter = cnx.entity_from_eid(peid).cw_adapt_to('IContained')
if adapter is None: # we reached the container
containers[eid] = peid
else: # the adapter must know the container at this point
containers[eid] = adapter.container.eid
# turn previous mapping into <container eid>: [<list of contained eids>]
contained = defaultdict(list)
for eid, ceid in containers.items():
contained[ceid].append(eid)
# and uses it to insert data using raw SQL for performance
cursor = cnx.cnxset.cu
for ceid, eids in contained.items():
args = [{'x': eid} for eid in eids]
cursor.executemany('INSERT INTO container_relation(eid_from, eid_to) '
'VALUES (%%(x)s, %s)' % ceid, args)
class SetContainerHook(hook.Hook):
__regid__ = 'seda.graph.updated'
__select__ = hook.Hook.__select__ & hook.match_rtype_sets(SEDA_PARENT_RTYPES)
events = ('before_add_relation',)
def __call__(self):
if SEDA_PARENT_RTYPES[self.rtype] == 'subject':
SetContainerOp.get_instance(self._cw).add_data((self.eidfrom, self.eidto))
else:
SetContainerOp.get_instance(self._cw).add_data((self.eidto, self.eidfrom))
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__)
from cubes.seda.entities import seda_profile_container_def
for _, parent_rdefs in seda_profile_container_def(vreg.schema):
for rtype, role in parent_rdefs:
if SEDA_PARENT_RTYPES.setdefault(rtype, role) != role:
raise Exception('inconsistent relation', rtype, role)
......@@ -15,7 +15,7 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""cubicweb-seda schema"""
from yams.buildobjs import EntityType, RelationDefinition
from yams.buildobjs import EntityType, RelationType, RelationDefinition
from yams.buildobjs import String
......@@ -65,3 +65,16 @@ class Agent(EntityType):
agent's activity.
"""
name = String(required=True, fulltextindexed=True)
class container(RelationType):
__permissions__ = {'add': (), 'delete': (), 'read': ()}
cardinality = '1*'
inlined = False
def post_build_callback(schema):
from cubes.seda import seda_profile_container_def
for etype, _ in seda_profile_container_def(schema):
rdef = RelationDefinition(etype, 'container', 'SEDAArchiveTransfer')
schema.add_relation_def(rdef)
# copyright 2016 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-saem_ref unit tests for entities.container"""
from cubicweb.devtools.testlib import CubicWebTC
from cubes.seda.entities import seda_profile_container_def
def sort_container(container_def):
for k, v in container_def:
yield k, sorted(v)
class ContainerTC(CubicWebTC):
def test_seda_profile_container(self):
# line below should be copied from entities.container.registration_callback
container_def = seda_profile_container_def(self.schema)
container_def = dict(sort_container(container_def))
self.assertEqual(container_def['SEDADataObjectPackage'],
[('seda_data_object_package', 'subject')])
self.assertEqual(container_def['SEDAMimeType'],
[('seda_mime_type_from', 'subject')])
self.assertNotIn('ConceptScheme', container_def)
self.assertNotIn('Concept', container_def)
self.assertNotIn('Agent', container_def)
entity = self.vreg['etypes'].etype_class('SEDAArchiveTransfer')(self)
self.assertIsNotNone(entity.cw_adapt_to('IContainer'))
self.assertIsNone(entity.cw_adapt_to('IContained'))
def test_container_relation(self):
with self.admin_access.client_cnx() as cnx:
create = cnx.create_entity
transfer = create('SEDAArchiveTransfer')
clv = create('SEDACodeListVersions', reverse_seda_code_list_versions=transfer)
mtclv = create('SEDAMimeTypeCodeListVersion', seda_mime_type_code_list_version_from=clv)
package = create('SEDADataObjectPackage', seda_data_object_package=transfer)
access_rule = create('SEDAAccessRule', seda_access_rule=package)
cnx.commit()
for entity in (clv, mtclv, package, access_rule):
entity.cw_clear_all_caches()
self.assertEqual(entity.cw_adapt_to('IContained').container.eid, transfer.eid)
access_rule_seq = create('SEDASeqAccessRuleRule',
reverse_seda_seq_access_rule_rule=access_rule)
rule = create('SEDARule', reverse_seda_rule=access_rule_seq)
cnx.commit()
for entity in (access_rule_seq, rule):
entity.cw_clear_all_caches()
self.assertEqual(entity.cw_adapt_to('IContained').container.eid, transfer.eid)
if __name__ == '__main__':
import unittest
unittest.main()
......@@ -7,6 +7,7 @@ envlist = py27, flake8-jenkins
[testenv]
sitepackages = True
deps =
hg+https://hg.logilab.org/review/cubes/compound@649282ea05fb#egg=cubicweb-compound
pytest
commands = {envpython} -m pytest {posargs:{toxinidir}/test}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment