[PATCH 5 of 6 seda] [diag] Add a new warning about entities with 1 cardinality being followed by others

Sylvain Thenault sylvain.thenault at logilab.fr
Mon Dec 11 16:51:36 CET 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1512573868 -3600
#      Wed Dec 06 16:24:28 2017 +0100
# Node ID cdb433d3ff6824af3f828a04bf06d64228ddf18a
# Parent  9457420d2e21c5cab937d12b6ee0c5653cbaf67b
[diag] Add a new warning about entities with 1 cardinality being followed by others

This will be desired in following csets since we'll stop ordering RNG export
using cardinality to follow user enforced ordering, hence consistently with the
UI.

diff --git a/cubicweb_seda/entities/diag.py b/cubicweb_seda/entities/diag.py
--- a/cubicweb_seda/entities/diag.py
+++ b/cubicweb_seda/entities/diag.py
@@ -113,10 +113,16 @@ RULES = {
         set(['RNG']),
         _("More than one children with cardinality not equal to '1', "
           "this may cause RNG validation problems."),
         None,  # generated at run time
         []),  # have it's own hook
+    'rng_mandatory_not_first': Rule(
+        set(['RNG']),
+        _("Children with cardinality equal to '1' should appear before other "
+          "to avoid potential RNG validation problems."),
+        None,  # generated at run time
+        []),  # have it's own hook
 }
 
 
 class CompatError(namedtuple('_CompatError', [
         'impacted_formats', 'message', 'tab_id', 'entity', 'rule_id'])):
@@ -232,22 +238,23 @@ class ISEDACompatAnalyzer(EntityAdapter)
                 'NOT EXISTS(ALT seda_seq_alt_archive_unit_archive_unit_ref_id_management SEQ),'
                 'X container C, C eid %(c)s', {'c': self.entity.eid}).entities():
             yield 'use_archive_unit_ref', archive_unit
 
     def _check_rng_ambiguities(self):
-        for eid, rtype in self._cw.execute(_AMBIGUOUS_RQL,
-                                           {'c': self.entity.eid}):
-            # if the container is a simplified profile, allow to have several
-            # entities with cardinality != 1 under the seda_binary_data_object
-            # relation because data objects are all linked to the transfer
-            # through this relation, but this is only relevant for SEDA 2 export
-            # and we don't want to prevent this in profiles for SEDA 0.2 and 1.0
-            # export.
-            if rtype == 'seda_binary_data_object' and self.entity.simplified_profile:
-                continue
-            tab_id = _RTYPE_TO_TAB.get(rtype)
-            yield 'rng_ambiguity', _parent(self._cw.entity_from_eid(eid)), {'tab_id': tab_id}
+        for rql, error_id in [(_AMBIGUOUS_RQL, 'rng_ambiguity'),
+                              (_MANDATORY_NOT_LAST_AMBIGUOUS_RQL, 'rng_mandatory_not_first')]:
+            for eid, rtype in self._cw.execute(rql, {'c': self.entity.eid}):
+                # if the container is a simplified profile, allow to have several
+                # entities with cardinality != 1 under the seda_binary_data_object
+                # relation because data objects are all linked to the transfer
+                # through this relation, but this is only relevant for SEDA 2 export
+                # and we don't want to prevent this in profiles for SEDA 0.2 and 1.0
+                # export.
+                if rtype == 'seda_binary_data_object' and self.entity.simplified_profile:
+                    continue
+                tab_id = _RTYPE_TO_TAB.get(rtype)
+                yield error_id, _parent(self._cw.entity_from_eid(eid)), {'tab_id': tab_id}
 
 
 def _parent(entity):
     """Return the first encountered parent which is an ArchiveUnit"""
     while entity.cw_etype not in ('SEDAArchiveTransfer', 'SEDAArchiveUnit',
@@ -294,5 +301,23 @@ def _parent(entity):
      'NOT X user_cardinality "1" HAVING COUNT(X) > 1)'.format(rtype=rtype)
      for rtype in ('seda_archive_unit',
                    'seda_binary_data_object', 'seda_physical_data_object',
                    'seda_related_transfer_reference')),
 ))
+
+_MANDATORY_NOT_LAST_AMBIGUOUS_RQL = ' UNION '.join(chain(
+    ('(Any P, "{rtype}" WHERE EXISTS(X1 {rtype} P, X2 {rtype} P, '
+     'P container C, C eid %(c)s, '
+     'X1 user_cardinality "1", NOT X2 user_cardinality "1", '
+     'X1 ordering ORD, X2 ordering < ORD))'.format(rtype=rtype)
+     for rtype in _RTYPE_TO_TAB
+     if rtype not in ('seda_binary_data_object',
+                      'seda_physical_data_object',
+                      'seda_related_transfer_reference')),
+    # we need another RQL for top level entities without container relation
+    ('(Any P, "{rtype}" WHERE EXISTS(X1 {rtype} P, X2 {rtype} P, P eid %(c)s, '
+     'X1 user_cardinality "1", NOT X2 user_cardinality "1", '
+     'X1 ordering ORD, X2 ordering < ORD))'.format(rtype=rtype)
+     for rtype in ('seda_archive_unit',
+                   'seda_binary_data_object', 'seda_physical_data_object',
+                   'seda_related_transfer_reference')),
+))
diff --git a/cubicweb_seda/i18n/en.po b/cubicweb_seda/i18n/en.po
--- a/cubicweb_seda/i18n/en.po
+++ b/cubicweb_seda/i18n/en.po
@@ -77,10 +77,15 @@ msgstr ""
 
 msgid "Archive unit reference are only supported by SEDA 2."
 msgstr ""
 
 msgid ""
+"Children with cardinality equal to '1' should appear before other to avoid "
+"potential RNG validation problems."
+msgstr ""
+
+msgid ""
 "Custodial history is text with SEDA 0.2, hence date information isn't "
 "considered."
 msgstr ""
 
 msgid ""
diff --git a/cubicweb_seda/i18n/fr.po b/cubicweb_seda/i18n/fr.po
--- a/cubicweb_seda/i18n/fr.po
+++ b/cubicweb_seda/i18n/fr.po
@@ -77,10 +77,17 @@ msgstr "arbre de l'unité d'archive"
 
 msgid "Archive unit reference are only supported by SEDA 2."
 msgstr "Les références ne sont supportées qu'en SEDA 2."
 
 msgid ""
+"Children with cardinality equal to '1' should appear before other to avoid "
+"potential RNG validation problems."
+msgstr ""
+"Les sous-éléments avec une cardinalité égale à '1' devrait apparaître avant "
+"les autres pour éviter des potentiels problèmes lors de la validation RNG"
+
+msgid ""
 "Custodial history is text with SEDA 0.2, hence date information isn't "
 "considered."
 msgstr ""
 "L'historique de conservation est du texte en SEDA 0.2, l'information de date "
 "est donc ignorée."
diff --git a/test/test_diag.py b/test/test_diag.py
--- a/test/test_diag.py
+++ b/test/test_diag.py
@@ -149,12 +149,12 @@ class CompatAnalyzerTC(CubicWebTC):
     def test_multiple_child_unhandled_cardinality_archive_unit(self):
         with self.admin_access.cnx() as cnx:
             transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile')
             doctor = transfer.cw_adapt_to('ISEDACompatAnalyzer')
             testutils.create_archive_unit(transfer)
-            testutils.create_archive_unit(transfer, user_cardinality=u'0..1')
             unit2 = testutils.create_archive_unit(transfer, user_cardinality=u'0..1')[0]
+            testutils.create_archive_unit(transfer, user_cardinality=u'1..n')
             cnx.commit()
 
             self.assertIsRNGAmbiguous(transfer, True)
             self.assertErrorOn(doctor, 'rng_ambiguity', [(transfer, 'seda_archive_units_tab')])
 
@@ -232,10 +232,25 @@ class CompatAnalyzerTC(CubicWebTC):
                               user_cardinality=u'1..n')
             cnx.commit()
 
             self.assertErrorOn(doctor, 'rng_ambiguity', [(unit, 'seda_indexation_tab')])
 
+    def test_single_cardinality_not_first(self):
+        with self.admin_access.cnx() as cnx:
+            transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile')
+            doctor = transfer.cw_adapt_to('ISEDACompatAnalyzer')
+            u1 = testutils.create_archive_unit(transfer, user_cardinality=u'1..n')
+            u2 = testutils.create_archive_unit(transfer, user_cardinality=u'1')
+            cnx.commit()
+            self.assertEqual(u1[0].ordering, 1)
+            self.assertEqual(u2[0].ordering, 2)
+            self.assertEqual(u1[0].user_cardinality, '1..n')
+            self.assertEqual(u2[0].user_cardinality, '1')
+            self.assertIsRNGAmbiguous(transfer, True)
+            self.assertErrorOn(doctor, 'rng_mandatory_not_first',
+                               [(transfer, 'seda_archive_units_tab')])
+
     def assertErrorOn(self, doctor, error_id, expected_errors):
         errors = set()
         for error in doctor.detect_problems():
             print('assert diag error', error.rule_id)
             if error.rule_id == error_id:


More information about the saem-devel mailing list