Commit 40229bc6 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

Allow archive units not linked to a parent

This prepare the 'seda components' functionality from SAEM. For now, we don't
support data object as component, since most metadata moved to its related
archive unit with SEDA2.

We also have to change data object parent relation's cardinality since in case
of an archive unit as  component, we can't have this relation to the transfer
entity.
parent 5e1e74aab8ec
......@@ -38,6 +38,10 @@ def parent_and_container(entity):
# entity is expected to be a contained entity, not the container itself
container = entity.cw_adapt_to('IContained').container
parent = entity.cw_adapt_to('IContained').parent
if container is None:
# entity may be both container and contained, and in this case is a container
assert entity.cw_adapt_to('IContainer')
container = entity
else:
req = entity._cw
# but parent entity, retrieved through linkto, may be the container itself or a
......@@ -55,11 +59,13 @@ def parent_and_container(entity):
# unable to get parent eid for now :(
return None, None
parent = req.entity_from_eid(parent_eid)
icontainer = parent.cw_adapt_to('IContainer')
if icontainer is None:
container = parent.cw_adapt_to('IContained').container
else:
container = icontainer.container
# handle IContained first: in case entity support both interface we want to go to the
# uppermost parent
icontained = parent.cw_adapt_to('IContained')
if icontained is not None and icontained.container:
container = icontained.container
elif parent.cw_adapt_to('IContainer'):
container = parent
return parent, container
......@@ -77,14 +83,22 @@ def simplified_profile(cls, req, rset=None, entity=None, **kwargs):
entity = _transfer_from_context(rset, entity)
if entity is None:
return 0
return int(entity.simplified_profile)
if entity.cw_etype == 'SEDAArchiveUnit':
# XXX archive unit component, for now suppose it's "simplified"
return 1
return 1 if entity.simplified_profile else 0
@objectify_predicate
def full_seda2_profile(cls, req, rset=None, entity=None, **kwargs):
"""Predicate returning 1 score if context entity is within a full seda2 profile."""
entity = _transfer_from_context(rset, entity)
return int(not entity.simplified_profile)
if entity is None:
return 1
if entity.cw_etype == 'SEDAArchiveUnit':
# XXX archive unit component, for now suppose it's "simplified"
return 0
return 0 if entity.simplified_profile else 1
def rule_type_from_etype(etype):
......@@ -115,5 +129,6 @@ class DirectLinkIContained(IContained):
def registration_callback(vreg):
vreg.register(IContainer.build_class('SEDAArchiveTransfer'))
vreg.register(IContainer.build_class('SEDAArchiveUnit')) # archive unit may also be a container
for etype, parent_relations in sorted(seda_profile_container_def(vreg.schema)):
DirectLinkIContained.register_class(vreg, etype, parent_relations)
......@@ -79,6 +79,8 @@ class ITreeBaseArchiveUnitAdapter(IContainedToITreeBase):
def parent(self):
parent = self.entity.cw_adapt_to('IContained').parent
if parent is None:
return None
if parent.cw_etype == 'SEDAArchiveTransfer':
return parent
return parent_archive_unit(parent)
......
......@@ -46,11 +46,17 @@ class SetContainerOp(hook.DataOperationMixIn, hook.Operation):
# climb up to the uppermost parent
while peid in data:
peid = data[peid]
adapter = cnx.entity_from_eid(peid).cw_adapt_to('IContained')
entity = cnx.entity_from_eid(peid)
adapter = entity.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
if adapter.container is not None:
containers[eid] = adapter.container.eid
else:
assert entity.cw_adapt_to('IContainer'), \
"can't retrieve container for entity %s" % entity
containers[eid] = peid
# turn previous mapping into <container eid>: [<list of contained eids>]
contained = defaultdict(list)
for eid, ceid in containers.items():
......@@ -244,7 +250,7 @@ class CheckProfileSEDACompatiblityOp(hook.DataOperationMixIn, hook.LateOperation
profiles.add(entity)
else:
container = entity.cw_adapt_to('IContained').container
if container is not None:
if container is not None and container.cw_etype == 'SEDAArchiveTransfer':
profiles.add(container)
with cnx.deny_all_hooks_but():
for profile in profiles:
......
......@@ -129,11 +129,12 @@ if 'Agent' not in context.defined:
def post_build_callback(schema):
from cubes.seda import seda_profile_container_def, iter_all_rdefs
container_etype = 'SEDAArchiveTransfer'
container_etypes = ('SEDAArchiveTransfer', 'SEDAArchiveUnit')
for etype, parent_rdefs in seda_profile_container_def(schema):
# add relation to the container from every entity type within the compound graph
container_rdef = RelationDefinition(etype, 'container', container_etype)
schema.add_relation_def(container_rdef)
for container_etype in container_etypes:
container_rdef = RelationDefinition(etype, 'container', container_etype)
schema.add_relation_def(container_rdef)
eschema = schema[etype]
# set permissions on entity types from the compound graph according to permission on the
# container entity
......@@ -142,6 +143,13 @@ def post_build_callback(schema):
action, ('managers', ERQLExpression('U has_{action}_permission C, '
'X container C'.format(action=action)))
)
for action in ('update', 'delete'):
schema['SEDAArchiveUnit'].set_action_permissions(
action, ('managers',
ERQLExpression('U has_{action}_permission C, '
'X container C'.format(action=action)),
ERQLExpression('NOT EXISTS(X container C), U in_group G, '
'G name IN ("managers", "users")')))
# set permissions on all relation defs related to the compound graph according to permission on
# the container entity
for rdef, role in iter_all_rdefs(schema, 'SEDAArchiveTransfer'):
......@@ -149,7 +157,7 @@ def post_build_callback(schema):
target_etype, var = rdef.subject, 'S'
else:
target_etype, var = rdef.object, 'O'
if target_etype == container_etype:
if target_etype == 'SEDAArchiveTransfer':
expr = 'U has_update_permission {0}'.format(var)
else:
expr = 'U has_update_permission C, {0} container C'.format(var)
......
......@@ -431,7 +431,7 @@ class archive_transfer_binary_data_object(RelationDefinition):
name = 'seda_binary_data_object'
subject = 'SEDABinaryDataObject'
object = 'SEDAArchiveTransfer'
cardinality = '1*'
cardinality = '?*'
composite = fulltext_container = 'object'
inlined = True
constraints = []
......@@ -447,7 +447,7 @@ class archive_transfer_physical_data_object(RelationDefinition):
name = 'seda_physical_data_object'
subject = 'SEDAPhysicalDataObject'
object = 'SEDAArchiveTransfer'
cardinality = '1*'
cardinality = '?*'
composite = fulltext_container = 'object'
inlined = True
constraints = []
......@@ -542,7 +542,7 @@ class archive_transfer_archive_unit(RelationDefinition):
name = 'seda_archive_unit'
subject = 'SEDAArchiveUnit'
object = 'SEDAArchiveTransfer'
cardinality = '1*'
cardinality = '?*'
composite = fulltext_container = 'object'
inlined = True
constraints = []
......@@ -1364,7 +1364,7 @@ class seq_alt_archive_unit_archive_unit_ref_id_management_archive_unit(RelationD
name = 'seda_archive_unit'
subject = 'SEDAArchiveUnit'
object = 'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement'
cardinality = '1*'
cardinality = '?*'
composite = fulltext_container = 'object'
inlined = True
constraints = []
......
......@@ -69,6 +69,23 @@ class ContainerTC(CubicWebTC):
entity.cw_clear_all_caches()
self.assertEqual(entity.cw_adapt_to('IContained').container.eid, transfer.eid)
def test_archive_unit_container(self):
"""Functional test for SEDA component clone."""
with self.admin_access.repo_cnx() as cnx:
unit, unit_alt, unit_alt_seq = create_archive_unit(None, cnx=cnx)
bdo = create_data_object(None, cnx=cnx)
cnx.create_entity('SEDADataObjectReference', user_cardinality=u'0..n',
seda_data_object_reference=unit_alt_seq,
seda_data_object_reference_id=bdo)
cnx.commit()
unit.cw_clear_all_caches()
self.assertEqual(unit.container, ()) # XXX arguable
unit_alt_seq.cw_clear_all_caches()
self.assertEqual(unit_alt_seq.container[0].eid, unit.eid)
bdo.cw_clear_all_caches()
self.assertEqual(bdo.container[0].eid, unit.eid)
class FakeEntity(object):
cw_etype = 'Whatever'
......
......@@ -125,6 +125,19 @@ class SchemaTC(CubicWebTC):
self.assertEqual(rqlexpr.expression, 'U has_{action}_permission C, '
'X container C'.format(action=action))
def test_archive_unit_permissions(self):
"""Check that permissions are correctly set on archive unit that may be either container
or contained."""
for action in ('update', 'delete'):
etype = self.schema['SEDAArchiveUnit']
rqlexpr1, rqlexpr2 = [p for p in etype.permissions[action]
if isinstance(p, ERQLExpression)]
self.assertEqual(rqlexpr1.expression,
'U has_{action}_permission C, X container C'.format(action=action))
self.assertEqual(rqlexpr2.expression,
'NOT EXISTS(X container C), U in_group G, '
'G name IN("managers", "users")')
def test_rule_default_cardinality(self):
with self.admin_access.client_cnx() as cnx:
transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile')
......
......@@ -28,7 +28,7 @@ def create_transfer_to_bdo(cnx):
def create_archive_unit(parent, **kwargs):
cnx = parent._cw
cnx = kwargs.pop('cnx', getattr(parent, '_cw', None))
kwargs.setdefault('id', u'au1')
au = cnx.create_entity('SEDAArchiveUnit', seda_archive_unit=parent, **kwargs)
alt = cnx.create_entity('SEDAAltArchiveUnitArchiveUnitRefId',
......@@ -40,7 +40,8 @@ def create_archive_unit(parent, **kwargs):
def create_data_object(transfer, **kwargs):
create = transfer._cw.create_entity
cnx = kwargs.pop('cnx', getattr(transfer, '_cw', None))
create = cnx.create_entity
kwargs.setdefault('id', u'bdo1')
bdo = create('SEDABinaryDataObject', seda_binary_data_object=transfer, **kwargs)
choice = create('SEDAAltBinaryDataObjectAttachment',
......
......@@ -173,6 +173,9 @@ RTYPE_CARDS = {
'seda_description': '1?',
'seda_comment': '1?',
'seda_custodial_history_item': '1*',
'seda_archive_unit': '?*',
'seda_binary_data_object': '?*',
'seda_physical_data_object': '?*',
}
RTYPE_CARD = {
'seda_custodial_history_item': '*',
......
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