[PATCH 7 of 9 seda] [hooks] Add hook to synchronize file_category with mime_type / format_id

Sylvain Thenault sylvain.thenault at logilab.fr
Thu Oct 12 16:59:52 CEST 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1507793994 -7200
#      Thu Oct 12 09:39:54 2017 +0200
# Node ID a9d8ac0426d6e57e255be6c3b01ad5d108a86994
# Parent  f5069136d86d6775215362fbcf2ab7665dd233c3
# Available At https://hg.logilab.org/review/cubes/seda
#              hg pull https://hg.logilab.org/review/cubes/seda -r a9d8ac0426d6
[hooks] Add hook to synchronize file_category with mime_type / format_id

Upon modification of file_category relation, synchronize mime_type / format_id
values for the binary data-object, through intermediary SEDAMimeType /
SEDAFormatId entities which are kept for now but could be removed if the
automatic user cardinality system is validated by business people.

Synchronization is implemented to consider textual values of concept in the
category vocabulary, and to keep only those which have a match in the
vocabularies that are set on the transfer (code list version).

Because of that, don't attempt to synchronize values on archive unit component,
that should be done when it is imported into a transfer.

Related to #36331831

diff --git a/MANIFEST.in b/MANIFEST.in
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,11 +3,11 @@ recursive-include cubicweb_seda/data *.g
 recursive-include cubicweb_seda/xsd *.xsd
 recursive-include doc *.rst *.py
 recursive-include test *.py
 include cubicweb_seda/migration/data/*.csv
 include cubicweb_seda/i18n/*.po cubicweb_seda/i18n/*.pot
-include test/data/bootstrap_cubes test/data/*.xml test/data/*.xsd test/data/*.rng
+include test/data/bootstrap_cubes test/data/*.xml test/data/*.xsd test/data/*.rng test/data/*.csv
 include tox.ini dev-requirements.txt cubicweb_seda/makefile
 
 prune __pkginfo__.py
 prune cubicweb-seda.spec
 prune debian
diff --git a/cubicweb_seda/hooks.py b/cubicweb_seda/hooks.py
--- a/cubicweb_seda/hooks.py
+++ b/cubicweb_seda/hooks.py
@@ -484,10 +484,80 @@ class InitBDO(hook.Hook):
                                seda_mime_type_from=self.entity)
         self._cw.create_entity('SEDAFormatId', user_cardinality=u'0..1',
                                seda_format_id_from=self.entity)
 
 
+class SyncFileCategoryOp(hook.DataOperationMixIn, hook.LateOperation):
+    """On file category change, synchronize associated mime types and format ids
+    to the data object.
+
+    This is a late operation because we have to go back to the container to know
+    if it's a component archive unit or a transfer, so it has to be executed
+    once the 'container' relation is set.
+    """
+    # set possible mime types / format ids by joining related transfer's
+    # vocabularies to file category vocabulary using the concept's label
+    set_mime_type_rql = (
+        'SET X seda_mime_type_to MT WHERE X eid %(x)s, '
+        'MT in_scheme CS, CACLV seda_mime_type_code_list_version_from AT, '
+        'CACLV seda_mime_type_code_list_version_to CS, AT eid %(c)s, '
+        'MTL label_of MT, MTL label MTLL, '
+        'FCMTL label_of FCMT , FCMTL label MTLL, '
+        'EXISTS(FCMT broader_concept EXT1, EXT1 eid IN ({eids})) '
+        'OR EXISTS(FCMT broader_concept EXT2, EXT2 broader_concept CAT, '
+        '          CAT eid IN ({eids}))'
+    )
+    set_format_id_rql = (
+        'SET X seda_format_id_to FI WHERE X eid %(x)s, '
+        'FI in_scheme CS, CACLV seda_file_format_code_list_version_from AT, '
+        'CACLV seda_file_format_code_list_version_to CS, AT eid %(c)s, '
+        'FIL label_of FI, FIL label FILL, '
+        'FCFIL label_of FCFI , FCFIL label FILL, '
+        'EXISTS(FCFI broader_concept MT, MT broader_concept EXT1, EXT1 eid IN ({eids})) '
+        'OR EXISTS(FCFI broader_concept MT2, MT2 broader_concept EXT2, EXT2 broader_concept CAT, '
+        '          CAT eid IN ({eids}))'
+    )
+
+    def precommit_event(self):
+        for bdo_eid in self.get_data():
+            bdo = self.cnx.entity_from_eid(bdo_eid)
+            container = bdo.cw_adapt_to('IContained').container
+            if container.cw_etype != 'SEDAArchiveTransfer':
+                # don't afford doing this in components which are not bound to
+                # vocabularies, it will be done upon import in a transfer
+                continue
+            if not bdo.file_category:
+                # no related category
+                card = u'0..1'
+                eids = None
+            else:
+                card = u'1'
+                eids = ','.join(str(x.eid) for x in bdo.file_category)
+            bdo.mime_type.cw_set(user_cardinality=card,
+                                 seda_mime_type_to=None)
+            bdo.format_id.cw_set(user_cardinality=card,
+                                 seda_format_id_to=None)
+            if eids is not None:
+                self.cnx.execute(self.set_mime_type_rql.format(eids=eids),
+                                 {'x': bdo.mime_type.eid, 'c': container.eid})
+                self.cnx.execute(self.set_format_id_rql.format(eids=eids),
+                                 {'x': bdo.format_id.eid, 'c': container.eid})
+
+
+class SyncFileCategoryHook(hook.Hook):
+    """On file category change, instantiate an operation to synchronize
+    associated mime types and format ids to the data object.
+    """
+
+    __regid__ = 'seda.ux.filecategory'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('file_category')
+    events = ('after_add_relation', 'after_delete_relation')
+
+    def __call__(self):
+        SyncFileCategoryOp.get_instance(self._cw).add_data(self.eidfrom)
+
+
 def registration_callback(vreg):
     from cubicweb.server import ON_COMMIT_ADD_RELATIONS
     from cubicweb_seda import seda_profile_container_def, iter_all_rdefs
 
     vreg.register_all(globals().values(), __name__)
diff --git a/test/data/file_categories.csv b/test/data/file_categories.csv
new file mode 100644
--- /dev/null
+++ b/test/data/file_categories.csv
@@ -0,0 +1,12 @@
+#;$id;broader_concept;libellé;Description du titre du niveau;libellé alternatif;libellé alternatif
+lang;;;seda;fr;fr;fr
+url;skos:Concept;skos:broader;skos:prefLabel;skos:definition;skos:altLabel;skos:note
+type;;url;string;string;string;string
+;#3;;document;catégorie;;
+;#3-1;#3;doc;extension;;
+;#3-1-1;#3-1;application/msword;type_mime;;
+;#3-1-1-1;#3-1-1;fmt/37;format;version 1.0;
+;#3-1-1-2;#3-1-1;fmt/38;format;version 2.0;
+;#3-13;#3;pdf;extension;;
+;#3-13-1;#3-13;application/pdf;type_mime;;
+;#3-13-1-1;#3-13-1;fmt/14;format;version 1.0;
diff --git a/test/test_dataimport.py b/test/test_dataimport.py
--- a/test/test_dataimport.py
+++ b/test/test_dataimport.py
@@ -23,11 +23,11 @@ from cubicweb_seda import dataimport
 class ConcepSchemeImportTC(CubicWebTC):
 
     def test_import_seda_schemes(self):
         with self.admin_access.cnx() as cnx:
             dataimport.import_seda_schemes(cnx, lcsv_import=dataimport.lcsv_check)
-            self.assertEqual(len(cnx.find('ConceptScheme')), 18)
+            self.assertEqual(len(cnx.find('ConceptScheme')), 19)
 
 
 if __name__ == '__main__':
     import unittest
     unittest.main()
diff --git a/test/test_hooks.py b/test/test_hooks.py
--- a/test/test_hooks.py
+++ b/test/test_hooks.py
@@ -13,15 +13,16 @@
 #
 # 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 unit tests for hooks"""
 
+from os.path import dirname, join
 from itertools import chain, repeat
 
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb_seda import testutils
+from cubicweb_seda import testutils, dataimport
 
 
 class ValidationHooksTC(CubicWebTC):
 
     assertValidationError = testutils.assertValidationError
@@ -263,8 +264,59 @@ class CheckProfileTC(CubicWebTC):
 
             with self.assertValidationError(cnx):
                 access_rule_seq.cw_delete()
 
 
+class DispatchFileCategoryTC(CubicWebTC):
+
+    def test(self):
+        with self.admin_access.cnx() as cnx:
+            testutils.scheme_for_type(cnx, 'seda_format_id_to', None,
+                                      u'fmt/37', u'fmt/38', u'fmt/14')
+            testutils.scheme_for_type(cnx, 'seda_mime_type_to', None,
+                                      u'application/msword', u'application/pdf')
+            concepts = dict(cnx.execute('Any LL, C WHERE L label_of C, L label LL'))
+            dataimport.import_seda_schemes(cnx, lcsv_files=[
+                (u'Categories de fichier',
+                 'file_category', (),
+                 join(dirname(__file__), 'data', 'file_categories.csv'))])
+            categories = dict(cnx.execute('Any LL, C WHERE L label_of C, L label LL, '
+                                          'C in_scheme CS, CS title "Categories de fichier"'))
+
+            transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile',
+                                         simplified_profile=True)
+            unit, unit_alt, unit_alt_seq = testutils.create_archive_unit(transfer)
+            bdo = testutils.create_data_object(unit_alt_seq,
+                                               seda_binary_data_object=transfer)
+            cnx.commit()
+
+            bdo.cw_set(file_category=categories['document'])
+            cnx.commit()
+            self.assertFormatEqual(bdo, u'1',
+                                   [concepts['application/msword'], concepts['application/pdf']],
+                                   [concepts['fmt/37'], concepts['fmt/38'], concepts['fmt/14']])
+
+            bdo.cw_set(file_category=None)
+            cnx.commit()
+            self.assertFormatEqual(bdo, u'0..1', [], [])
+
+            bdo.cw_set(file_category=categories['doc'])
+            cnx.commit()
+            self.assertFormatEqual(bdo, u'1',
+                                   [concepts['application/msword']],
+                                   [concepts['fmt/37'], concepts['fmt/38']])
+
+    def assertFormatEqual(self, bdo, cardinality, mime_types, format_ids):
+        bdo.cw_clear_all_caches()
+        bdo.reverse_seda_mime_type_from[0].cw_clear_all_caches()
+        bdo.reverse_seda_format_id_from[0].cw_clear_all_caches()
+        self.assertEqual(bdo.reverse_seda_mime_type_from[0].user_cardinality, cardinality)
+        self.assertEqual(set(x.eid for x in bdo.reverse_seda_mime_type_from[0].seda_mime_type_to),
+                         set(mime_types))
+        self.assertEqual(bdo.reverse_seda_format_id_from[0].user_cardinality, cardinality)
+        self.assertEqual(set(x.eid for x in bdo.reverse_seda_format_id_from[0].seda_format_id_to),
+                         set(format_ids))
+
+
 if __name__ == '__main__':
     import unittest
     unittest.main()


More information about the saem-devel mailing list