Repository: akvo/akvo-rsr

Issue:
PE API flag
Flag all requests made from PE with `project_editor_change: true`


Discussion:

Resolving patch:
diff --git a/akvo/rest/viewsets.py b/akvo/rest/viewsets.py
--- a/akvo/rest/viewsets.py
+++ b/akvo/rest/viewsets.py
@@ -14,7 +14,7 @@
 from akvo.rest.models import TastyTokenAuthentication
 from akvo.utils import log_project_changes
 
-from rest_framework import authentication, filters, permissions, viewsets
+from rest_framework import authentication, exceptions, filters, permissions, viewsets
 
 from .filters import RSRGenericFilterBackend
 from .pagination import TastypieOffsetPagination
@@ -171,30 +171,36 @@ def get_queryset(self):
         return queryset.distinct()
 
     def create(self, request, *args, **kwargs):
+        project_editor_change = is_project_editor_change(request)
         response = super(PublicProjectViewSet, self).create(request, *args, **kwargs)
         obj = self.queryset.model.objects.get(pk=response.data['id'])
         project = self.get_project(obj)
         if project is not None:
-            project.update_iati_checks()
             log_project_changes(request.user, project, obj, {}, 'added')
+            if project_editor_change:
+                project.update_iati_checks()
         return response
 
     def destroy(self, request, *args, **kwargs):
+        project_editor_change = is_project_editor_change(request)
         obj = self.get_object()
         project = self.get_project(obj)
         response = super(PublicProjectViewSet, self).destroy(request, *args, **kwargs)
         if project is not None:
-            project.update_iati_checks()
             log_project_changes(request.user, project, obj, {}, 'deleted')
+            if project_editor_change:
+                project.update_iati_checks()
         return response
 
     def update(self, request, *args, **kwargs):
+        project_editor_change = is_project_editor_change(request)
         response = super(PublicProjectViewSet, self).update(request, *args, **kwargs)
         obj = self.get_object()
         project = self.get_project(obj)
         if project is not None:
-            project.update_iati_checks()
             log_project_changes(request.user, project, obj, request.data, 'changed')
+            if project_editor_change:
+                project.update_iati_checks()
         return response
 
     @staticmethod
@@ -268,3 +274,11 @@ def _projects_filter_for_non_privileged_users(user, queryset, project_relation,
         queryset = public_objects | private_objects
 
     return queryset.distinct()
+
+
+def is_project_editor_change(request):
+    try:
+        project_editor_change = request.data.get('project_editor_change', False)
+    except exceptions.UnsupportedMediaType:
+        project_editor_change = False
+    return project_editor_change


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
@@ -1132,13 +1132,33 @@ def setUp(self):
         self.c.login(username=self.username, password=self.password)
         self.project.update_iati_checks()
 
-    def test_update_project_attributes_runs_iati_checks(self):
+    def test_update_project_attributes_outside_project_editor_does_not_run_iati_checks(self):
         # Given
         success_checks = self.project.iati_checks.filter(status=1).count()
         error_checks = self.project.iati_checks.filter(status=3).count()
         url = '/rest/v1/project/{}/?format=json'.format(self.project.id)
         data = {"title": "DEMONSTRATION!", "date_start_planned": "2009-06-10"}
 
+        # When
+        response = self.c.patch(
+            url, data=json.dumps(data), follow=True, content_type='application/json')
+
+        # Then
+        self.assertEqual(response.status_code, 200)
+        self.project.refresh_from_db()
+        new_success_checks = self.project.iati_checks.filter(status=1).count()
+        new_error_checks = self.project.iati_checks.filter(status=3).count()
+        self.assertEqual(0, success_checks)
+        self.assertEqual(0, new_success_checks)
+        self.assertEqual(new_error_checks, error_checks)
+
+    def test_update_project_attributes_runs_iati_checks(self):
+        # Given
+        success_checks = self.project.iati_checks.filter(status=1).count()
+        error_checks = self.project.iati_checks.filter(status=3).count()
+        url = '/rest/v1/project/{}/?format=json'.format(self.project.id)
+        data = {"title": "DEMONSTRATION!", "date_start_planned": "2009-06-10", "project_editor_change": True}
+
         # When
         response = self.c.patch(
             url, data=json.dumps(data), follow=True, content_type='application/json')
@@ -1158,7 +1178,7 @@ def test_create_delete_update_direct_related_object_runs_iati_checks(self):
         success_checks = self.project.iati_checks.filter(status=1).count()
         error_checks = self.project.iati_checks.filter(status=3).count()
         url = '/rest/v1/results_framework_lite/?format=json'
-        data = {"type": "1", "indicators": [], "project": self.project.id}
+        data = {"type": "1", "indicators": [], "project": self.project.id, "project_editor_change": True}
 
         # When
         response = self.c.post(
@@ -1177,7 +1197,7 @@ def test_create_delete_update_direct_related_object_runs_iati_checks(self):
         # #### Update
         # Given
         url = '/rest/v1/results_framework_lite/{}/?format=json'.format(result_id)
-        data = {"title": "Demo Result"}
+        data = {"title": "Demo Result", "project_editor_change": True}
 
         # When
         response = self.c.patch(
@@ -1195,9 +1215,10 @@ def test_create_delete_update_direct_related_object_runs_iati_checks(self):
         # #### Delete
         # Given
         url = '/rest/v1/results_framework_lite/{}/?format=json'.format(result_id)
+        data = {"project_editor_change": True}
 
         # When
-        response = self.c.delete(url, follow=True, content_type='application/json')
+        response = self.c.delete(url, data=json.dumps(data), follow=True, content_type='application/json')
 
         # Then
         self.assertEqual(response.status_code, 204)
@@ -1217,7 +1238,7 @@ def test_create_delete_update_indirect_related_object_runs_iati_checks(self):
         success_checks = self.project.iati_checks.filter(status=1).count()
         error_checks = self.project.iati_checks.filter(status=3).count()
         url = '/rest/v1/indicator_framework/?format=json'
-        data = {"type": 1, "periods": [], "dimension_names": [], "result": result.id}
+        data = {"type": 1, "periods": [], "dimension_names": [], "result": result.id, "project_editor_change": True}
 
         # When
         response = self.c.post(
@@ -1237,7 +1258,7 @@ def test_create_delete_update_indirect_related_object_runs_iati_checks(self):
         # #### Update
         # Given
         url = '/rest/v1/indicator_framework/{}/?format=json'.format(indicator_id)
-        data = {"title": "Demo Indicator"}
+        data = {"title": "Demo Indicator", "project_editor_change": True}
 
         # When
         response = self.c.patch(
@@ -1255,9 +1276,10 @@ def test_create_delete_update_indirect_related_object_runs_iati_checks(self):
         # #### Delete
         # Given
         url = '/rest/v1/indicator_framework/{}/?format=json'.format(indicator_id)
+        data = {"project_editor_change": True}
 
         # When
-        response = self.c.delete(url, follow=True, content_type='application/json')
+        response = self.c.delete(url, data=json.dumps(data), follow=True, content_type='application/json')
 
         # Then
         self.assertEqual(response.status_code, 204)