Repository: akvo/akvo-rsr

Issue:
Allow import results even if some disaggregation categories are already present
Disaggregation categories may be already created manually on some projects and this causes the results import to fail. It is quite easy to work around this, and we should allow for that. 


Discussion:

Resolving patch:
diff --git a/akvo/rsr/models/project.py b/akvo/rsr/models/project.py
--- a/akvo/rsr/models/project.py
+++ b/akvo/rsr/models/project.py
@@ -1299,27 +1299,33 @@ def copy_results(self, source_project):
             self.copy_result(result, set_parent=False)
 
     def copy_dimension_name(self, source_dimension_name, set_parent=True):
-        data = dict(
-            project=self,
-            name=source_dimension_name.name,
-            parent_dimension_name=source_dimension_name
-        )
+        defaults = dict(parent_dimension_name=source_dimension_name)
+        data = dict(project=self, name=source_dimension_name.name, defaults=defaults)
         if not set_parent:
-            data.pop('parent_dimension_name')
+            defaults.pop('parent_dimension_name')
 
         IndicatorDimensionName = apps.get_model('rsr', 'IndicatorDimensionName')
-        dimension_name = IndicatorDimensionName.objects.create(**data)
+        dimension_name, created = IndicatorDimensionName.objects.get_or_create(**data)
+        if not created and set_parent:
+            dimension_name.parent_dimension_name = source_dimension_name
+            dimension_name.save(update_fields=['parent_dimension_name'])
 
         for dimension_value in source_dimension_name.dimension_values.all():
-            self.copy_dimension_value(dimension_name, dimension_value)
+            self.copy_dimension_value(dimension_name, dimension_value, set_parent=set_parent)
 
-    def copy_dimension_value(self, dimension_name, source_dimension_value):
+    def copy_dimension_value(self, dimension_name, source_dimension_value, set_parent=True):
         IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')
-        IndicatorDimensionValue.objects.create(
+        defaults = dict(parent_dimension_value=source_dimension_value)
+        data = dict(
             name=dimension_name,
             value=source_dimension_value.value,
-            parent_dimension_value=source_dimension_value,
-        )
+            defaults=defaults)
+        if not set_parent:
+            defaults.pop('parent_dimension_value')
+        dimension_value, created = IndicatorDimensionValue.objects.get_or_create(**data)
+        if not created and set_parent:
+            dimension_value.parent_dimension_value = source_dimension_value
+            dimension_value.save(update_fields=['parent_dimension_value'])
 
     def copy_result(self, source_result, set_parent=True):
         """Copy the source_result to this project, setting it as parent if specified."""


Test patch:
diff --git a/akvo/rsr/tests/results_framework/test_results_framework.py b/akvo/rsr/tests/results_framework/test_results_framework.py
--- a/akvo/rsr/tests/results_framework/test_results_framework.py
+++ b/akvo/rsr/tests/results_framework/test_results_framework.py
@@ -432,6 +432,25 @@ def test_dimension_update_does_not_create_deleted_dimension(self):
         # Then
         self.assertEqual(1, child_indicator.dimensions.count())
 
+    def test_manually_created_dimension_names_should_not_break_import(self):
+        # Given
+        parent_dimension_name = IndicatorDimensionName.objects.create(
+            name='Colour', project=self.parent_project)
+        child_dimension_name = IndicatorDimensionName.objects.create(
+            name='Colour', project=self.child_project)
+        for colour in {'Red', 'Blue', 'Green'}:
+            IndicatorDimensionValue.objects.create(value=colour, name=parent_dimension_name)
+            IndicatorDimensionValue.objects.create(value=colour, name=child_dimension_name)
+
+        # When
+        self.child_project.import_results()
+
+        # Then
+        child_dimension_name.refresh_from_db()
+        self.assertEqual(child_dimension_name.parent_dimension_name, parent_dimension_name)
+        for dimension_value in child_dimension_name.dimension_values.all():
+            self.assertIsNotNone(dimension_value.parent_dimension_value)
+
     def test_child_dimension_name_state_updates_after_change(self):
         """Test that updating dimension name propagates to children."""
         # Given