Repository: datosgobar/pydatajson

Issue:
Agregar opción para mover `theme` a `keyword` en `push_dataset_to_ckan`
**Contexto**

Los temas específicos (`theme`) son temas que tienen sentido dentro del contexto y dominio temático bajo el cual cada nodo original opera. Sirven para clasificar a sus datasets según categorías temáticas específicas a la problemática que aborda ese nodo, pero que tienen una granularidad demasiado alta para la generalidad de los datos de toda la Administración Pública.

El `theme` de un dataset en su nodo original, es una extensión del `keyword` de ese dataset en el nodo integrador (son etiquetas).

**Implementar**

`pydatajson` tiene que hacer un _downgrade_ de los `theme` originales hacia el rol de etiquetas (`keyword`) en el nodo integrador.

Agregar a la función `push_dataset_to_ckan` un argumento opcional `theme_to_keyword` con default `True`.

Este flag mueve (los _elimina_ de un lado y los escribe en otro) todos los elementos originales del array `theme` en la metadata original de un dataset y los agrega al `keyword` del dataset que va a ser empujado a un CKAN.

**Notas**

Esto es una operación de transformación de los metadatos originales cuyo sentido es preservar el uso exclusivo de temas transversales (evitando el uso de temas específicos) de un dataset en un contexto de portal diferente (con un público y alcance temático transversal, mientras que el nodo original tiene un público y alcance temático específico).

**Entregable**

La nueva opción en `push_dataset_to_ckan`, junto con sus tests asociados  y las modificaciones correspondientes a la documentación en los docs de RTD.

Discussion:

Patch:
diff --git a/docs/MANUAL.md b/docs/MANUAL.md
index 556645c..2191b01 100644
--- a/docs/MANUAL.md
+++ b/docs/MANUAL.md
@@ -94,6 +94,8 @@ Toma los siguientes parámetros:
   - **portal_url**: URL del portal de CKAN de destino.
   - **apikey**: La apikey de un usuario del portal de destino con los permisos para crear el dataset bajo la
   organización pasada como parámetro.
+  - **demote_themes** (opcional, default: True): Si está en true, los labels de los themes del dataset, se escriben como
+  tags de CKAN; sino,se pasan como grupo.
   
   Retorna el id en el nodo de destino del dataset federado.
   
diff --git a/pydatajson/ckan_utils.py b/pydatajson/ckan_utils.py
index 65c4cb2..31f6737 100644
--- a/pydatajson/ckan_utils.py
+++ b/pydatajson/ckan_utils.py
@@ -14,7 +14,7 @@ def append_attribute_to_extra(package, dataset, attribute, serialize=False):
         package['extras'].append({'key': attribute, 'value': value})
 
 
-def map_dataset_to_package(dataset, catalog_id):
+def map_dataset_to_package(dataset, catalog_id, owner_org, theme_taxonomy, demote_themes=True):
     package = dict()
     package['extras'] = []
 #   Obligatorios
@@ -24,6 +24,7 @@ def map_dataset_to_package(dataset, catalog_id):
     package['private'] = False
     package['notes'] = dataset['description']
     package['author'] = dataset['publisher']['name']
+    package['owner_org'] = owner_org
 
     append_attribute_to_extra(package, dataset, 'issued')
     append_attribute_to_extra(package, dataset, 'accrualPeriodicity')
@@ -44,7 +45,6 @@ def map_dataset_to_package(dataset, catalog_id):
     append_attribute_to_extra(package, dataset, 'language', serialize=True)
 
     spatial = dataset.get('spatial')
-
     if spatial:
         serializable = type(spatial) is list
         append_attribute_to_extra(package, dataset, 'spatial', serializable)
@@ -58,6 +58,16 @@ def map_dataset_to_package(dataset, catalog_id):
     if keywords:
         package['tags'] = [{'name': keyword} for keyword in keywords]
 
+    # Move themes to keywords
+    themes = dataset.get('theme', [])
+    if themes and demote_themes:
+        package['tags'] = package.get('tags', [])
+        for theme in themes:
+            label = next(x['label'] for x in theme_taxonomy if x['id'] == theme)
+            package['tags'].append({'name': label})
+    else:
+        package['groups'] += [{'name': title_to_name(theme, decode=False)} for theme in themes]
+
     return package
 
 
diff --git a/pydatajson/federation.py b/pydatajson/federation.py
index a8f137f..f9a4f6b 100644
--- a/pydatajson/federation.py
+++ b/pydatajson/federation.py
@@ -9,7 +9,8 @@ from .ckan_utils import map_dataset_to_package
 from .search import get_datasets
 
 
-def push_dataset_to_ckan(catalog, catalog_id, owner_org, dataset_origin_identifier, portal_url, apikey):
+def push_dataset_to_ckan(catalog, catalog_id, owner_org, dataset_origin_identifier, portal_url, apikey,
+                         demote_themes=True):
     """Escribe la metadata de un dataset en el portal pasado por parámetro.
 
         Args:
@@ -19,15 +20,17 @@ def push_dataset_to_ckan(catalog, catalog_id, owner_org, dataset_origin_identifi
             dataset_origin_identifier (str): El id del dataset que se va a federar.
             portal_url (str): La URL del portal CKAN de destino.
             apikey (str): La apikey de un usuario con los permisos que le permitan crear o actualizar el dataset.
+            demote_themes(bool): Si está en true, los labels de los themes del dataset, pasan a ser tags. Sino,
+            se pasan como grupo.
 
         Returns:
             str: El id del dataset en el catálogo de destino.
     """
     dataset = catalog.get_dataset(dataset_origin_identifier)
     ckan_portal = RemoteCKAN(portal_url, apikey=apikey)
+    theme_taxonomy = catalog.themes
 
-    package = map_dataset_to_package(dataset, catalog_id)
-    package['owner_org'] = owner_org
+    package = map_dataset_to_package(dataset, catalog_id, owner_org, theme_taxonomy, demote_themes=demote_themes)
 
     # Get license id
     if dataset.get('license'):
@@ -42,15 +45,6 @@ def push_dataset_to_ckan(catalog, catalog_id, owner_org, dataset_origin_identifi
     else:
         package['license_id'] = 'notspecified'
 
-    # Move themes to keywords
-    themes = dataset.get('theme')
-    if themes:
-        package['tags'] = package.get('tags') or []
-        theme_taxonomy = catalog.themes
-        for theme in themes:
-            label = next(x['label'] for x in theme_taxonomy if x['id'] == theme)
-            package['tags'].append({'name': label})
-
     try:
         pushed_package = ckan_portal.call_action(
             'package_update', data_dict=package)


Test patch:
diff --git a/tests/test_ckan_utils.py b/tests/test_ckan_utils.py
index 942bd39..3884251 100644
--- a/tests/test_ckan_utils.py
+++ b/tests/test_ckan_utils.py
@@ -23,16 +23,17 @@ class DatasetConversionTestCase(unittest.TestCase):
         cls.distributions = cls.dataset['distribution']
 
     def test_replicated_plain_attributes_are_corrext(self):
-        package = map_dataset_to_package(self.dataset, self.catalog_id)
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes)
         plain_replicated_attributes = [('title', 'title'),
                                        ('notes', 'description'),
                                        ('url', 'landingPage')]
         for fst, snd in plain_replicated_attributes:
             self.assertEqual(self.dataset.get(snd), package.get(fst))
+        self.assertEqual('owner', package['owner_org'])
         self.assertEqual(self.catalog_id+'_'+self.dataset_id, package['id'])
 
     def test_dataset_nested_replicated_attributes_stay_the_same(self):
-        package = map_dataset_to_package(self.dataset, self.catalog_id)
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes)
         contact_point_nested = [('maintainer', 'fn'),
                                 ('maintainer_email', 'hasEmail')]
         for fst, snd in contact_point_nested:
@@ -43,7 +44,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertEqual(self.dataset.get('publisher').get(snd), package.get(fst))
 
     def test_dataset_array_attributes_are_correct(self):
-        package = map_dataset_to_package(self.dataset, self.catalog_id)
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes)
         groups = [group['name'] for group in package.get('groups', [])]
         super_themes = [title_to_name(s_theme.lower()) for s_theme in self.dataset.get('superTheme')]
         try:
@@ -53,13 +54,38 @@ class DatasetConversionTestCase(unittest.TestCase):
 
         tags = [tag['name'] for tag in package['tags']]
         keywords = self.dataset.get('keyword', [])
+
+        themes = self.dataset.get('theme', [])
+        theme_labels = []
+        for theme in themes:
+            label = next(x['label'] for x in self.catalog.themes if x['id'] == theme)
+            theme_labels.append(label)
+
+        try:
+            self.assertItemsEqual(keywords + theme_labels, tags)
+        except AttributeError:
+            self.assertCountEqual(keywords + theme_labels, tags)
+
+    def test_themes_are_preserved_if_not_demoted(self):
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes,
+                                         demote_themes=False)
+        groups = [group['name'] for group in package.get('groups', [])]
+        super_themes = [title_to_name(s_theme.lower()) for s_theme in self.dataset.get('superTheme')]
+        themes = self.dataset.get('theme', [])
+        tags = [tag['name'] for tag in package['tags']]
+        keywords = self.dataset.get('keyword', [])
+
+        try:
+            self.assertItemsEqual(super_themes + themes, groups)
+        except AttributeError:
+            self.assertCountEqual(super_themes + themes, groups)
         try:
             self.assertItemsEqual(keywords, tags)
         except AttributeError:
             self.assertCountEqual(keywords, tags)
 
     def test_dataset_extra_attributes_are_correct(self):
-        package = map_dataset_to_package(self.dataset, self.catalog_id)
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes)
 #       extras are included in dataset
         if package['extras']:
             for extra in package['extras']:
@@ -78,7 +104,7 @@ class DatasetConversionTestCase(unittest.TestCase):
                     self.assertEqual(dataset_value, extra_value)
 
     def test_dataset_extra_attributes_are_complete(self):
-        package = map_dataset_to_package(self.dataset, self.catalog_id)
+        package = map_dataset_to_package(self.dataset, self.catalog_id, 'owner', self.catalog.themes)
 #       dataset attributes are included in extras
         extra_attrs = ['issued', 'modified', 'accrualPeriodicity', 'temporal', 'language', 'spatial']
         for key in extra_attrs: