[hooks] Trigger CKAN resource creation upon addition of the link to dataset relation
authorDenis Laxalde <denis.laxalde@logilab.fr>
Thu, 18 Dec 2014 11:03:00 +0100
changeset 44 359eb926e733
parent 43 681487d37369
child 45 5e3ef3b319cf
[hooks] Trigger CKAN resource creation upon addition of the link to dataset relation Related to #4753964.
README
hooks.py
test/data/hooks.py
test/unittest_hooks.py
--- a/README	Thu Dec 18 09:32:05 2014 +0100
+++ b/README	Thu Dec 18 11:03:00 2014 +0100
@@ -18,4 +18,9 @@
 *   ``ICKANResource`` adapter relies on a ``ckan_resource_id`` attribute. It
     is used to related some CubicWeb entity type to a CKAN resource.
 
+The push of a resource on CKAN instance should be triggered by the
+addition of a relation between the dataset-like entity and a resource-like
+entity in CubicWeb. This is handled by the `LinkResourceToDatasetHook` hook,
+which is abstract and should thus subclass by setting its `rtype` attribute.
+
 See ``test/data`` for a minimal example.
--- a/hooks.py	Thu Dec 18 09:32:05 2014 +0100
+++ b/hooks.py	Thu Dec 18 11:03:00 2014 +0100
@@ -18,8 +18,8 @@
 
 from requests.exceptions import RequestException
 
-from cubicweb import ValidationError
-from cubicweb.predicates import adaptable, score_entity
+from cubicweb import ValidationError, role
+from cubicweb.predicates import adaptable, score_entity, PartialPredicateMixIn
 from cubicweb.server import hook
 
 from cubes.ckanpublish.utils import (ckan_post, CKANPostError,
@@ -135,12 +135,48 @@
         CKANResourceOp.get_instance(self._cw).add_data(self.entity.eid)
 
 
-class AddOrUpdateCKANResourceHook(hook.Hook):
-    """Add or update a CKAN resource upon addition or update of an entity"""
-    __regid__ = 'ckanpublish.add-update-ckan-resource'
+class partial_match_rtype(PartialPredicateMixIn, hook.match_rtype):
+    """Same as :class:~`cubicweb.server.hook.match_rtype`, but will look for
+    attributes `rtype`, `role`, `frometypes` and `toetypes` on the selected
+    class to get information which is otherwise expected by the initializer.
+    """
+    def __init__(self, *expected, **more):
+        super(partial_match_rtype, self).__init__()
+
+    def complete(self, cls):
+        self.expected = (cls.rtype, )
+        self.role = role(cls)
+        self.frometypes = getattr(cls, 'frometypes', None)
+        self.toetypes = getattr(cls, 'toetypes', None)
+
+
+class LinkResourceToDatasetHook(hook.Hook):
+    """Create a CKAN dataset upon link of a resource-like entity to a
+    dataset-like entity.
+
+    Actual implementations should at least fill the `rtype` attribute.
+    """
+    __regid__ = 'ckanpublish.link-ckan-resource-to-ckan-dataset'
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+                  partial_match_rtype())
+    __abstract__ = True
+    events = ('after_add_relation', )
+    rtype = None  # Use to fill the `expected` argument of match_rtype.
+    role  = 'object'
+    frometypes = None
+    toetypes = None
+
+    def __call__(self):
+        eid = {'subject': self.eidfrom, 'object': self.eidto}[self.role]
+        CKANResourceOp.get_instance(self._cw).add_data(eid)
+
+
+class UpdateCKANResourceHook(hook.Hook):
+    """Update a CKAN resource upon update of an resource-like entity"""
+    __regid__ = 'ckanpublish.update-ckan-resource'
     __select__ = (hook.Hook.__select__ & ckan_instance_configured &
                   adaptable('ICKANResource'))
-    events = ('after_add_entity', 'after_update_entity', )
+    events = ('after_update_entity', )
 
     def __call__(self):
         CKANResourceOp.get_instance(self._cw).add_data(self.entity.eid)
--- a/test/data/hooks.py	Thu Dec 18 09:32:05 2014 +0100
+++ b/test/data/hooks.py	Thu Dec 18 11:03:00 2014 +0100
@@ -1,6 +1,6 @@
 from cubicweb.server import hook
 
-from cubes.ckanpublish.hooks import CKANDatasetOp
+from cubes.ckanpublish.hooks import CKANDatasetOp, LinkResourceToDatasetHook
 
 
 class AddUpdateMaintainerHook(hook.Hook):
@@ -13,11 +13,10 @@
         CKANDatasetOp.get_instance(self._cw).add_data(self.eidfrom)
 
 
-class AddDeleteFileResourceHook(hook.Hook):
-    __regid__ = 'ckanpublish-tests.add-delete-file-resource'
-    __select__ = (hook.Hook.__select__ &
-                  hook.match_rtype('resources', frometypes=('CWDataSet')))
-    events = ('after_add_relation', 'after_delete_relation')
-
-    def __call__(self):
-        CKANDatasetOp.get_instance(self._cw).add_data(self.eidfrom)
+class MyLinkResourceToDatasetHook(LinkResourceToDatasetHook):
+    """Implementation of `resource` relation hook."""
+    rtype = 'resources'
+    role = 'object'
+    # frometypes/toetypes are not actually required.
+    frometypes = 'CWDataSet'
+    toetypes = 'File'
--- a/test/unittest_hooks.py	Thu Dec 18 09:32:05 2014 +0100
+++ b/test/unittest_hooks.py	Thu Dec 18 11:03:00 2014 +0100
@@ -90,8 +90,7 @@
                                         description=u'flop')
             resource = cnx.create_entity('File', data=Binary('yui'),
                                          data_format=u'text/plain',
-                                         data_name=u'blurp',
-                                         reverse_resources=dataset)
+                                         data_name=u'blurp')
             cnx.commit()
             yield self._check_resource_creation, cnx, dataset, resource
             yield self._check_resource_update, cnx, resource
@@ -99,6 +98,9 @@
 
     def _check_resource_creation(self, cnx, dataset, resource):
         self.set_description('resource creation')
+        dataset.cw_set(resources=resource)
+        cnx.commit()
+        resource.cw_clear_all_caches()
         self.assertIsNotNone(resource.ckan_resource_id)
         result = ckan_post(self.ckan_config, 'package_show',
                            {'id': dataset.ckan_dataset_id})