[PATCH 5 of 6 saem_ref] [schema] Use functions backported to the compound cube

Sylvain Thenault sylvain.thenault at logilab.fr
Wed Mar 8 13:59:03 CET 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1488971916 -3600
#      Wed Mar 08 12:18:36 2017 +0100
# Node ID 87c611021e511cee3e6cb00cea92c00f8a2bb54a
# Parent  95b2cca2860d312cf6e4692a3301778bf465a150
[schema] Use functions backported to the compound cube

diff --git a/cubicweb_saem_ref/__init__.py b/cubicweb_saem_ref/__init__.py
--- a/cubicweb_saem_ref/__init__.py
+++ b/cubicweb_saem_ref/__init__.py
@@ -17,60 +17,14 @@
 """cubicweb-saem_ref application package
 
 Référenciel de Système d'Archivage Électronique Mutualisé
 """
 
-from functools import partial
-
 from six import text_type
 
 from logilab.common.registry import objectify_predicate
 
-from cubicweb import neg_role
-from cubicweb_compound import CompositeGraph
-
-
-# Composite graph definitions used in permissions setup.
-PERMISSIONS_GRAPHS = dict.fromkeys(
-    ('AuthorityRecord', 'ConceptScheme', 'SEDAArchiveTransfer'),
-    partial(CompositeGraph, skiprtypes=('generated', 'used')))
-
-
-def optional_relations(schema, graph_structure):
-    """Return a dict with optional relations information in a CompositeGraph.
-
-    Keys are names of entity types in the graph for which a relation type has
-    no mandatory (cardinality in '1+') relation definitions and values is a
-    set of respective `(rtype, role)` tuples.
-    """
-    optionals = dict()
-    for etype, relations in graph_structure.iteritems():
-        for (rtype, role), targets in relations.iteritems():
-            for target in targets:
-                rdef = schema[rtype].role_rdef(etype, target, role)
-                if rdef.role_cardinality(role) in '1+':
-                    break
-            else:
-                optionals.setdefault(etype, set()).add((rtype, role))
-    return optionals
-
-
-def mandatory_rdefs(schema, graph_structure):
-    """Yield non-optional relation definitions (and the role of the parent in
-    the relation) in a graph structure.
-    """
-    visited = set()
-    for etype, relations in graph_structure.iteritems():
-        for (rtype, role), targets in relations.iteritems():
-            for target in targets:
-                rdef = schema[rtype].role_rdef(etype, target, role)
-                if rdef in visited:
-                    continue
-                visited.add(rdef)
-                if rdef.role_cardinality(role) in '1+':
-                    yield rdef, neg_role(role)
-
 
 def cwuri_url(entity):
     """Return an absolute URL for entity's cwuri, handling case where ark is directly used, and so
     URL should be generated from it.
     """
diff --git a/cubicweb_saem_ref/hooks.py b/cubicweb_saem_ref/hooks.py
--- a/cubicweb_saem_ref/hooks.py
+++ b/cubicweb_saem_ref/hooks.py
@@ -456,14 +456,15 @@ class AuthorityRecordUsedBySetDefault(ho
 
 def registration_callback(vreg):
     vreg.register_all(globals().values(), __name__)
 
     from cubicweb.server import ON_COMMIT_ADD_RELATIONS
-    from . import PERMISSIONS_GRAPHS, mandatory_rdefs
+    from . import PERMISSIONS_GRAPHS
+    from cubicweb_compound import utils
 
     # Add relations involved in a composite graph with security setup to "on
     # commit" check step.
     schema = vreg.schema
     for etype, graph_def in PERMISSIONS_GRAPHS.iteritems():
         graph = graph_def(schema)
-        for rdef, __ in mandatory_rdefs(schema, graph.parent_structure(etype)):
+        for rdef, __ in utils.mandatory_rdefs(schema, graph.parent_structure(etype)):
             ON_COMMIT_ADD_RELATIONS.add(rdef.rtype)
diff --git a/cubicweb_saem_ref/schema.py b/cubicweb_saem_ref/schema.py
--- a/cubicweb_saem_ref/schema.py
+++ b/cubicweb_saem_ref/schema.py
@@ -27,12 +27,12 @@ from cubicweb.schemas.base import Extern
 
 from cubes.skos.schema import ConceptScheme
 from cubicweb_eac.schema import AuthorityRecord, NameEntry
 from cubicweb_seda.schema import simplified_profile
 from cubicweb_seda.schema.seda2 import SEDAArchiveTransfer
-
-from . import PERMISSIONS_GRAPHS, optional_relations, mandatory_rdefs
+from . import PERMISSIONS_GRAPHS
+from cubicweb_compound import utils
 
 
 def publication_permissions(cls):
     """Set __permissions__ of `cls` entity type class preventing modification
     when not in state "draft".
@@ -58,65 +58,10 @@ def groups_permissions(cls):
         ERQLExpression('U in_group G, G name IN ("managers", "users")', 'U'),
     )
     return cls
 
 
-def _rel_expr(rtype, role):
-    return {'subject': 'X {rtype} A',
-            'object': 'A {rtype} X'}[role].format(rtype=rtype)
-
-
-def graph_set_etypes_update_permissions(schema, graph, etype):
-    """Add `action` permissions for all entity types in the composite `graph`
-    with root `etype`. Respective permissions that are inserted on each
-    entity type are relative to the "parent" in the relation from this
-    entity type walking up to the graph root.
-
-    So for instance, calling `set_etype_permissions('R', 'update')`
-    on a schema where `A related_to B` and `R root_of B` one will get:
-
-    * "U has_update_permission R, R root_of X" for `B` entity type and,
-    * "U has_update_permission P, X related_to P" for `A` entity type.
-
-    If an entity type in the graph is reachable through multiple relations, a
-    permission for each of this relation will be inserted so that if any of
-    these match, the permission check will succeed.
-    """
-    structure = graph.parent_structure(etype)
-    optionals = optional_relations(schema, structure)
-    for child, relations in structure.iteritems():
-        skiprels = optionals.get(child, set())
-        exprs = []
-        for rtype, role in relations:
-            if (rtype, role) in skiprels:
-                continue
-            relexpr = _rel_expr(rtype, role)
-            exprs.append('{relexpr}, U has_update_permission A'.format(relexpr=relexpr))
-        if exprs:
-            for action in ('update', 'delete'):
-                schema[child].set_action_permissions(action,
-                                                     tuple(ERQLExpression(e) for e in exprs))
-
-
-def graph_set_write_rdefs_permissions(schema, graph, etype):
-    """Set 'add' and 'delete' permissions for all mandatory relation definitions in the composite
-    `graph` with root `etype`.
-
-    Respective permissions that are inserted on each relation definition are relative to the
-    "parent" in the relation from this entity type walking up to the graph root.
-
-    Relations which are not mandatory or which are not part of the graph structure should be handled
-    manually.
-    """
-    structure = graph.parent_structure(etype)
-    for rdef, parent_role in mandatory_rdefs(schema, structure):
-        var = {'object': 'O', 'subject': 'S'}[parent_role]
-        expr = 'U has_update_permission {0}'.format(var)
-        for action in ('add', 'delete'):
-            rdef.set_action_permissions(action, (RRQLExpression(expr), ))
-
-
 # Disable "update" for ExternalUri as these should only come from imported data
 # and are meant to only be created or deleted.
 ExternalUri.__permissions__ = ExternalUri.__permissions__.copy()
 ExternalUri.__permissions__['update'] = ()
 
@@ -447,12 +392,12 @@ def post_build_callback(schema):
     schema['simplified_profile'].rdefs['SEDAArchiveTransfer', 'Boolean'].default = True  # XXX
     for etype, graph in PERMISSIONS_GRAPHS.iteritems():
         if etype == 'SEDAArchiveTransfer':
             # this compound graph as a generic 'container' relation on which security is based
             continue
-        graph_set_etypes_update_permissions(schema, graph(schema), etype)
-        graph_set_write_rdefs_permissions(schema, graph(schema), etype)
+        utils.graph_set_etypes_update_permissions(schema, graph(schema), etype)
+        utils.graph_set_write_rdefs_permissions(schema, graph(schema), etype)
 
     # permissions override
     schema['Label'].set_action_permissions('delete', ('managers', 'users'))
     for rtype in ('in_scheme', 'broader_concept', 'label_of'):
         for rdef in schema[rtype].rdefs.values():
diff --git a/dev-requirements.txt b/dev-requirements.txt
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,4 +1,5 @@
 pytest
 webtest
 http://hg.logilab.org/master/cubes/eac/archive/tip.tar.bz2#egg=cubicweb-eac
+http://hg.logilab.org/review/cubes/compound/archive/tip.tar.bz2#egg=cubicweb-compound
 http://hg.logilab.org/master/cubes/relationwidget/archive/tip.tar.bz2#egg=cubicweb-relationwidget
diff --git a/test/unittest_schema.py b/test/unittest_schema.py
--- a/test/unittest_schema.py
+++ b/test/unittest_schema.py
@@ -17,43 +17,19 @@
 
 import unittest
 from datetime import date
 from contextlib import contextmanager
 
-from six.moves import reduce
-
-from cubicweb import ValidationError, Unauthorized, neg_role
+from cubicweb import ValidationError, Unauthorized
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb_saem_ref import (PERMISSIONS_GRAPHS, mandatory_rdefs,
-                               optional_relations)
+from cubicweb_saem_ref import PERMISSIONS_GRAPHS
+from cubicweb_compound.utils import optional_relations, graph_relations
 
 import testutils
 
 
-def graph_relations(schema, parent_structure):
-    """Given a parent structure of a composite graph (and a schema object),
-    return relation information `(rtype, role)` sets where `role` is the role
-    of the child in the relation for the following kinds of relations:
-
-    * structural relations,
-    * optional relations (cardinality of the child not in '1*'),
-    * mandatory relations (cardinality of the child in '1*').
-    """
-    def concat_sets(sets):
-        """Concatenate sets"""
-        return reduce(lambda x, y: x | y, sets, set())
-
-    optionals = concat_sets(
-        optional_relations(schema, parent_structure).values())
-    mandatories = set([
-        (rdef.rtype, neg_role(role))
-        for rdef, role in mandatory_rdefs(schema, parent_structure)])
-    structurals = concat_sets(map(set, parent_structure.values()))
-    return structurals, optionals, mandatories
-
-
 @contextmanager
 def assertValidationError(self, cnx):
     with self.assertRaises(ValidationError) as cm:
         yield cm
         cnx.commit()


More information about the saem-devel mailing list