[hooks] Add hook to synchronize file_category with mime_type / format_id
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 12 Oct 2017 09:39:54 +0200
changeset 2758 a9d8ac0426d6
parent 2757 f5069136d86d
child 2760 cc3b3e0f6d42
[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
MANIFEST.in
cubicweb_seda/hooks.py
test/data/file_categories.csv
test/test_dataimport.py
test/test_hooks.py
--- a/MANIFEST.in	Thu Oct 12 09:35:23 2017 +0200
+++ b/MANIFEST.in	Thu Oct 12 09:39:54 2017 +0200
@@ -5,7 +5,7 @@
 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
--- a/cubicweb_seda/hooks.py	Thu Oct 12 09:35:23 2017 +0200
+++ b/cubicweb_seda/hooks.py	Thu Oct 12 09:39:54 2017 +0200
@@ -486,6 +486,76 @@
                                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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/file_categories.csv	Thu Oct 12 09:39:54 2017 +0200
@@ -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;
--- a/test/test_dataimport.py	Thu Oct 12 09:35:23 2017 +0200
+++ b/test/test_dataimport.py	Thu Oct 12 09:39:54 2017 +0200
@@ -25,7 +25,7 @@
     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__':
--- a/test/test_hooks.py	Thu Oct 12 09:35:23 2017 +0200
+++ b/test/test_hooks.py	Thu Oct 12 09:39:54 2017 +0200
@@ -15,11 +15,12 @@
 # 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):
@@ -265,6 +266,57 @@
                 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()