Repository: akvo/akvo-rsr

Issue:
Reordering RF items at lead project are reflected in contributing projects



Discussion:

Resolving patch:
diff --git a/akvo/rest/views/project_editor.py b/akvo/rest/views/project_editor.py
--- a/akvo/rest/views/project_editor.py
+++ b/akvo/rest/views/project_editor.py
@@ -25,6 +25,10 @@
 def project_editor_reorder_items(request, project_pk=None):
     """API call to reorder results or indicators"""
 
+    project = Project.objects.get(pk=project_pk)
+    if project.has_imported_results():
+        return Response(data={'errors': ['Reordering not allowed']}, status=http_status.HTTP_400_BAD_REQUEST)
+
     errors, item_list, item_selected, swap_id = [], [], None, -1
 
     item_type = request.POST.get('item_type', False)
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
@@ -1535,6 +1535,7 @@ def copy_result(self, source_result, set_parent=True):
             type=source_result.type,
             aggregation_status=source_result.aggregation_status,
             description=source_result.description,
+            order=source_result.order,
         )
         if not set_parent:
             data.pop('parent_result')
@@ -1565,6 +1566,7 @@ def copy_indicator(self, result, source_indicator, set_parent=True):
             type=source_indicator.type,
             export_to_iati=source_indicator.export_to_iati,
             scores=source_indicator.scores,
+            order=source_indicator.order,
         )
         if set_parent:
             indicator, created = Indicator.objects.update_or_create(
diff --git a/akvo/rsr/models/result/result.py b/akvo/rsr/models/result/result.py
--- a/akvo/rsr/models/result/result.py
+++ b/akvo/rsr/models/result/result.py
@@ -64,11 +64,19 @@ def save(self, *args, **kwargs):
 
         is_new_result = not self.pk
 
+        if is_new_result and Result.objects.filter(project_id=self.project.id).exists():
+            prev_result = Result.objects.filter(project_id=self.project.id).reverse()[0]
+            if prev_result.order:
+                self.order = prev_result.order + 1
+
+        super(Result, self).save(*args, **kwargs)
+
         for child_result in self.child_results.all():
-            # Always copy title, type and aggregation status. They should be the same as the parent.
+            # Always copy title, type, order and aggregation status. They should be the same as the parent.
             child_result.title = self.title
             child_result.type = self.type
             child_result.aggregation_status = self.aggregation_status
+            child_result.order = self.order
 
             # Only copy the description if the child has none (e.g. new)
             if not child_result.description and self.description:
@@ -76,13 +84,6 @@ def save(self, *args, **kwargs):
 
             child_result.save()
 
-        if is_new_result and Result.objects.filter(project_id=self.project.id).exists():
-            prev_result = Result.objects.filter(project_id=self.project.id).reverse()[0]
-            if prev_result.order:
-                self.order = prev_result.order + 1
-
-        super(Result, self).save(*args, **kwargs)
-
         if is_new_result:
             self.project.copy_result_to_children(self)
 


Test patch:
diff --git a/akvo/rsr/tests/rest/test_project_editor.py b/akvo/rsr/tests/rest/test_project_editor.py
--- a/akvo/rsr/tests/rest/test_project_editor.py
+++ b/akvo/rsr/tests/rest/test_project_editor.py
@@ -24,6 +24,7 @@
     RelatedProject, IndicatorPeriod, OrganisationLocation
 )
 from akvo.rsr.tests.base import BaseTestCase
+from akvo.rsr.tests.utils import ProjectFixtureBuilder
 from akvo.utils import check_auth_groups
 
 HERE = dirname(abspath(__file__))
@@ -305,6 +306,144 @@ def get_items(self):
         return Indicator.objects.filter(result_id=self.result.id).order_by('id')
 
 
+class ProjectEditorReorderImportedResultsFrameworkTestCase(BaseTestCase):
+
+    email = 'test@example.com'
+    password = 'password'
+
+    def test_should_not_be_able_to_reorder_result(self):
+        # Given
+        self.create_user(self.email, self.password, is_superuser=True)
+        self.c.login(username=self.email, password=self.password)
+        lead = ProjectFixtureBuilder()\
+            .with_title('Lead Project')\
+            .with_results([
+                {'title': 'Result #1'},
+                {'title': 'Result #2'},
+            ])\
+            .with_contributors([
+                {'title': 'Contrib Project'}
+            ])\
+            .build()
+        contrib = lead.get_contributor(title='Contrib Project')
+        results = contrib.results.all()
+
+        # When
+        url = '/rest/v1/project/{}/reorder_items/'.format(contrib.object.id)
+        data = {
+            'item_type': 'result',
+            'item_id': results[0].id,
+            'item_direction': 'down',
+        }
+        response = self.c.post(url, data=data, follow=True)
+
+        # Then
+        self.assertEqual(response.status_code, 400)
+
+    def test_should_not_be_able_to_reorder_indicator(self):
+        # Given
+        self.create_user(self.email, self.password, is_superuser=True)
+        self.c.login(username=self.email, password=self.password)
+        lead = ProjectFixtureBuilder()\
+            .with_title('Lead Project')\
+            .with_results([
+                {
+                    'title': 'Result #1',
+                    'indicators': [
+                        {'title': 'Indicator #1'},
+                        {'title': 'Indicator #2'},
+                    ]
+                },
+            ])\
+            .with_contributors([
+                {'title': 'Contrib Project'}
+            ])\
+            .build()
+        contrib = lead.get_contributor(title='Contrib Project')
+        indicators = contrib.indicators.all()
+
+        # When
+        url = '/rest/v1/project/{}/reorder_items/'.format(contrib.object.id)
+        data = {
+            'item_type': 'indicator',
+            'item_id': indicators[0].id,
+            'item_direction': 'down',
+        }
+        response = self.c.post(url, data=data, follow=True)
+
+        # Then
+        self.assertEqual(response.status_code, 400)
+
+    def test_reordering_result_at_lead_reflected_to_contributor(self):
+        # Given
+        self.create_user(self.email, self.password, is_superuser=True)
+        self.c.login(username=self.email, password=self.password)
+        lead = ProjectFixtureBuilder()\
+            .with_title('Lead Project')\
+            .with_results([
+                {'title': 'Result #1'},
+                {'title': 'Result #2'},
+            ])\
+            .with_contributors([
+                {'title': 'Contrib Project'}
+            ])\
+            .build()
+        contrib = lead.get_contributor(title='Contrib Project')
+        lead_result = lead.results.get(title='Result #2')
+
+        # When
+        url = '/rest/v1/project/{}/reorder_items/'.format(lead.object.id)
+        data = {
+            'item_type': 'result',
+            'item_id': lead_result.id,
+            'item_direction': 'up',
+        }
+        self.c.post(url, data=data, follow=True)
+
+        # Then
+        self.assertEqual(lead.results.get(title='Result #1').order, 1)
+        self.assertEqual(lead.results.get(title='Result #2').order, 0)
+        self.assertEqual(contrib.results.get(title='Result #1').order, 1)
+        self.assertEqual(contrib.results.get(title='Result #2').order, 0)
+
+    def test_reordering_indicator_at_lead_reflected_to_contributor(self):
+        # Given
+        self.create_user(self.email, self.password, is_superuser=True)
+        self.c.login(username=self.email, password=self.password)
+        lead = ProjectFixtureBuilder()\
+            .with_title('Lead Project')\
+            .with_results([
+                {
+                    'title': 'Result #1',
+                    'indicators': [
+                        {'title': 'Indicator #1'},
+                        {'title': 'Indicator #2'},
+                    ]
+                },
+            ])\
+            .with_contributors([
+                {'title': 'Contrib Project'}
+            ])\
+            .build()
+        contrib = lead.get_contributor(title='Contrib Project')
+        lead_indicator = lead.indicators.get(title='Indicator #2')
+
+        # When
+        url = '/rest/v1/project/{}/reorder_items/'.format(lead.object.id)
+        data = {
+            'item_type': 'indicator',
+            'item_id': lead_indicator.id,
+            'item_direction': 'up',
+        }
+        self.c.post(url, data=data, follow=True)
+
+        # Then
+        self.assertEqual(lead.indicators.get(title='Indicator #1').order, 1)
+        self.assertEqual(lead.indicators.get(title='Indicator #2').order, 0)
+        self.assertEqual(contrib.indicators.get(title='Indicator #1').order, 1)
+        self.assertEqual(contrib.indicators.get(title='Indicator #2').order, 0)
+
+
 class DefaultPeriodsTestCase(TestCase):
     """Test the adding and removal of default periods."""