We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
PE API flag
Flag all requests made from PE with `project_editor_change: true`

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `akvo/rest/viewsets.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Akvo RSR is covered by the GNU Affero General Public License.
4 # See more details in the license.txt file located at the root folder of the Akvo RSR module.
5 # For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
6 
7 import logging
8 
9 from django.db.models.fields import FieldDoesNotExist
10 from django.db.models.fields.related import ForeignObject
11 from django.core.exceptions import FieldError
12 
13 from akvo.rsr.models import PublishingStatus, Project
14 from akvo.rest.models import TastyTokenAuthentication
15 from akvo.utils import log_project_changes
16 
17 from rest_framework import authentication, filters, permissions, viewsets
18 
19 from .filters import RSRGenericFilterBackend
20 from .pagination import TastypieOffsetPagination
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class SafeMethodsPermissions(permissions.DjangoObjectPermissions):
26     """
27     Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to
28     authenticate.
29     """
30 
31     def has_permission(self, request, view):
32         if request.method in permissions.SAFE_METHODS:
33             return True
34         return super(SafeMethodsPermissions, self).has_permission(request, view)
35 
36 
37 class BaseRSRViewSet(viewsets.ModelViewSet):
38     """
39     Base class used for the view sets for RSR models. Provides unified auth and perms settings.
40     """
41     authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )
42     permission_classes = (SafeMethodsPermissions, )
43     filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)
44     ordering_fields = '__all__'
45 
46     def paginate_queryset(self, queryset):
47         """ Custom offset-based pagination for the Tastypie API emulation
48         """
49         if self.request and '/api/v1/' in self.request.path:
50             self.pagination_class = TastypieOffsetPagination
51         return super(BaseRSRViewSet, self).paginate_queryset(queryset)
52 
53     def get_queryset(self):
54 
55         def django_filter_filters(request):
56             """
57             Support emulating the DjangoFilterBackend-based filtering that some views used to have
58             """
59             # query string keys reserved by the RSRGenericFilterBackend
60             qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]
61             # query string keys used by core DRF, OrderingFilter and Akvo custom views
62             exclude_params = ['limit', 'format', 'page', 'offset', 'ordering', 'partner_type',
63                               'sync_owner', 'reporting_org', ]
64             filters = {}
65             for key in request.query_params:
66                 if key not in qs_params + exclude_params and not key.startswith('image_thumb_'):
67                     filters.update({key: request.query_params.get(key)})
68             return filters
69 
70         def get_lookups_from_filters(legacy_filters):
71             """
72             Cast the values in DjangoFilterBackend-styled query string filters to correct types to
73             be able to use them in regular queryset-filter() calls
74             """
75             # types of lookups supported by the views using DjangoFilterBackend
76             LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',
77                                     'lte', ]
78             query_set_lookups = []
79             for key, value in legacy_filters.items():
80                 parts = key.split('__')
81                 if parts[-1] in LEGACY_FIELD_LOOKUPS:
82                     parts = parts[:-1]
83                     model = queryset.model
84                     for part in parts:
85                         try:
86                             field_object = model._meta.get_field(part)
87                             related_model = field_object.model
88                             direct = not field_object.auto_created or field_object.concrete
89 
90                             if direct:
91                                 if issubclass(field_object.__class__, ForeignObject):
92                                     model = field_object.related_model
93                                 else:
94                                     value = field_object.to_python(value)
95                                     break
96                             else:
97                                 model = related_model
98                         except FieldDoesNotExist:
99                             pass
100                 query_set_lookups += [{key: value}]
101             return query_set_lookups
102 
103         queryset = super(BaseRSRViewSet, self).get_queryset()
104 
105         # support for old DjangoFilterBackend-based filtering if not pk is given
106         if not self.kwargs.get('pk'):
107             # find all "old styled" filters
108             legacy_filters = django_filter_filters(self.request)
109             # create lookup dicts from the filters found
110             lookups = get_lookups_from_filters(legacy_filters)
111             for lookup in lookups:
112                 try:
113                     queryset = queryset.filter(**lookup)
114                 except (FieldError, ValueError):
115                     # In order to mimick 'old' behaviour of the API, we should ignore non-valid
116                     # parameters or values. Returning a warning would be more preferable.
117                     pass
118 
119         return queryset
120 
121 
122 class ReadOnlyPublicProjectViewSet(viewsets.ReadOnlyModelViewSet):
123     """
124     Read only viewset for public projects or objects related to public projects will be shown.
125     """
126     authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )
127     permission_classes = (SafeMethodsPermissions, )
128     ordering_fields = '__all__'
129     project_relation = 'project__'
130 
131     def get_queryset(self):
132 
133         request = self.request
134         user = request.user
135 
136         queryset = super(ReadOnlyPublicProjectViewSet, self).get_queryset()
137 
138         # filter projects if user is "non-privileged"
139         if user.is_anonymous() or not (user.is_superuser or user.is_admin):
140             queryset = _projects_filter_for_non_privileged_users(
141                 user, queryset, self.project_relation, action=self.action
142             )
143 
144         return queryset.distinct()
145 
146 
147 class PublicProjectViewSet(BaseRSRViewSet):
148     """
149     Only public projects or objects related to public projects will be shown.
150     """
151     # project_relation is the default string for constructing a field lookup to the is_public field
152     # on the related Project. Override this in when the viewset is for a model that doesn't have a
153     # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:
154     #   project_relation = 'result__project__'
155     # The lookup is used to filter out objects associated with private projects, see below.
156     project_relation = 'project__'
157 
158     def get_queryset(self):
159 
160         request = self.request
161         user = request.user
162 
163         queryset = super(PublicProjectViewSet, self).get_queryset()
164 
165         # filter projects if user is "non-privileged"
166         if user.is_anonymous() or not (user.is_superuser or user.is_admin):
167             queryset = self.projects_filter_for_non_privileged_users(
168                 user, queryset, self.project_relation, action=self.action
169             )
170 
171         return queryset.distinct()
172 
173     def create(self, request, *args, **kwargs):
174         response = super(PublicProjectViewSet, self).create(request, *args, **kwargs)
175         obj = self.queryset.model.objects.get(pk=response.data['id'])
176         project = self.get_project(obj)
177         if project is not None:
178             project.update_iati_checks()
179             log_project_changes(request.user, project, obj, {}, 'added')
180         return response
181 
182     def destroy(self, request, *args, **kwargs):
183         obj = self.get_object()
184         project = self.get_project(obj)
185         response = super(PublicProjectViewSet, self).destroy(request, *args, **kwargs)
186         if project is not None:
187             project.update_iati_checks()
188             log_project_changes(request.user, project, obj, {}, 'deleted')
189         return response
190 
191     def update(self, request, *args, **kwargs):
192         response = super(PublicProjectViewSet, self).update(request, *args, **kwargs)
193         obj = self.get_object()
194         project = self.get_project(obj)
195         if project is not None:
196             project.update_iati_checks()
197             log_project_changes(request.user, project, obj, request.data, 'changed')
198         return response
199 
200     @staticmethod
201     def get_project(obj):
202         obj_model = obj._meta.model
203         model_project_relation = getattr(obj_model, 'project_relation', None)
204         if model_project_relation:
205             query = {model_project_relation: [obj.id]}
206             project = Project.objects.get(**query)
207         elif obj_model == Project:
208             project = obj
209         elif hasattr(obj, 'project'):
210             project = obj.project
211         else:
212             logger.error('%s does not define a relation to a project', obj_model)
213             project = None
214         return project
215 
216     @staticmethod
217     def projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):
218         return _projects_filter_for_non_privileged_users(user, queryset, project_relation, action)
219 
220 
221 def _projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):
222 
223     if not user.is_anonymous() and (user.is_admin or user.is_superuser):
224         return queryset.distinct()
225 
226     # Construct the public projects filter field lookup.
227     project_filter = project_relation + 'is_public'
228 
229     # Filter the object list into two querysets;
230     # One where the related Projects are public and one where they are private
231     public_objects = queryset.filter(**{project_filter: True}).distinct()
232     private_objects = queryset.filter(**{project_filter: False}).distinct()
233 
234     # In case of an anonymous user, only return the public objects
235     if user.is_anonymous():
236         unpublished_exclude = project_relation + 'publishingstatus__status'
237         queryset = public_objects.exclude(
238             **{unpublished_exclude: PublishingStatus.STATUS_UNPUBLISHED}
239         ).distinct()
240 
241     # Otherwise, check to which objects the user has (change) permission
242     elif private_objects.exists():
243         include_user_owned = hasattr(queryset.model, 'user')
244         if action == 'list':
245             # The view permission is new, and previously only the change
246             # permission existed. To avoid adding new view permissions for
247             # all the objects, we also check if a user has change
248             # permissions, which implicitly implies view permissions.
249             change_permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
250             change_filter = user.get_permission_filter(
251                 change_permission, project_relation, include_user_owned
252             )
253             change_objects = private_objects.filter(change_filter).distinct()
254 
255             # Check if user has view permission on the queryset
256             view_permission = change_permission.replace('.change_', '.view_')
257             view_filter = user.get_permission_filter(
258                 view_permission, project_relation, include_user_owned
259             )
260             view_objects = private_objects.filter(view_filter).distinct()
261 
262             private_objects = (change_objects | view_objects).distinct()
263         else:
264             permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
265             filter_ = user.get_permission_filter(permission, project_relation, include_user_owned)
266             private_objects = private_objects.filter(filter_).distinct()
267 
268         queryset = public_objects | private_objects
269 
270     return queryset.distinct()
271
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         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 @@
         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

{"golden_diff": "diff --git a/akvo/rest/viewsets.py b/akvo/rest/viewsets.py\n--- a/akvo/rest/viewsets.py\n+++ b/akvo/rest/viewsets.py\n@@ -14,7 +14,7 @@\n from akvo.rest.models import TastyTokenAuthentication\n from akvo.utils import log_project_changes\n \n-from rest_framework import authentication, filters, permissions, viewsets\n+from rest_framework import authentication, exceptions, filters, permissions, viewsets\n \n from .filters import RSRGenericFilterBackend\n from .pagination import TastypieOffsetPagination\n@@ -171,30 +171,36 @@\n         return queryset.distinct()\n \n     def create(self, request, *args, **kwargs):\n+        project_editor_change = is_project_editor_change(request)\n         response = super(PublicProjectViewSet, self).create(request, *args, **kwargs)\n         obj = self.queryset.model.objects.get(pk=response.data['id'])\n         project = self.get_project(obj)\n         if project is not None:\n-            project.update_iati_checks()\n             log_project_changes(request.user, project, obj, {}, 'added')\n+            if project_editor_change:\n+                project.update_iati_checks()\n         return response\n \n     def destroy(self, request, *args, **kwargs):\n+        project_editor_change = is_project_editor_change(request)\n         obj = self.get_object()\n         project = self.get_project(obj)\n         response = super(PublicProjectViewSet, self).destroy(request, *args, **kwargs)\n         if project is not None:\n-            project.update_iati_checks()\n             log_project_changes(request.user, project, obj, {}, 'deleted')\n+            if project_editor_change:\n+                project.update_iati_checks()\n         return response\n \n     def update(self, request, *args, **kwargs):\n+        project_editor_change = is_project_editor_change(request)\n         response = super(PublicProjectViewSet, self).update(request, *args, **kwargs)\n         obj = self.get_object()\n         project = self.get_project(obj)\n         if project is not None:\n-            project.update_iati_checks()\n             log_project_changes(request.user, project, obj, request.data, 'changed')\n+            if project_editor_change:\n+                project.update_iati_checks()\n         return response\n \n     @staticmethod\n@@ -268,3 +274,11 @@\n         queryset = public_objects | private_objects\n \n     return queryset.distinct()\n+\n+\n+def is_project_editor_change(request):\n+    try:\n+        project_editor_change = request.data.get('project_editor_change', False)\n+    except exceptions.UnsupportedMediaType:\n+        project_editor_change = False\n+    return project_editor_change\n", "issue": "PE API flag\nFlag all requests made from PE with `project_editor_change: true`\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nimport logging\n\nfrom django.db.models.fields import FieldDoesNotExist\nfrom django.db.models.fields.related import ForeignObject\nfrom django.core.exceptions import FieldError\n\nfrom akvo.rsr.models import PublishingStatus, Project\nfrom akvo.rest.models import TastyTokenAuthentication\nfrom akvo.utils import log_project_changes\n\nfrom rest_framework import authentication, filters, permissions, viewsets\n\nfrom .filters import RSRGenericFilterBackend\nfrom .pagination import TastypieOffsetPagination\n\nlogger = logging.getLogger(__name__)\n\n\nclass SafeMethodsPermissions(permissions.DjangoObjectPermissions):\n    \"\"\"\n    Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to\n    authenticate.\n    \"\"\"\n\n    def has_permission(self, request, view):\n        if request.method in permissions.SAFE_METHODS:\n            return True\n        return super(SafeMethodsPermissions, self).has_permission(request, view)\n\n\nclass BaseRSRViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    Base class used for the view sets for RSR models. Provides unified auth and perms settings.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)\n    ordering_fields = '__all__'\n\n    def paginate_queryset(self, queryset):\n        \"\"\" Custom offset-based pagination for the Tastypie API emulation\n        \"\"\"\n        if self.request and '/api/v1/' in self.request.path:\n            self.pagination_class = TastypieOffsetPagination\n        return super(BaseRSRViewSet, self).paginate_queryset(queryset)\n\n    def get_queryset(self):\n\n        def django_filter_filters(request):\n            \"\"\"\n            Support emulating the DjangoFilterBackend-based filtering that some views used to have\n            \"\"\"\n            # query string keys reserved by the RSRGenericFilterBackend\n            qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]\n            # query string keys used by core DRF, OrderingFilter and Akvo custom views\n            exclude_params = ['limit', 'format', 'page', 'offset', 'ordering', 'partner_type',\n                              'sync_owner', 'reporting_org', ]\n            filters = {}\n            for key in request.query_params:\n                if key not in qs_params + exclude_params and not key.startswith('image_thumb_'):\n                    filters.update({key: request.query_params.get(key)})\n            return filters\n\n        def get_lookups_from_filters(legacy_filters):\n            \"\"\"\n            Cast the values in DjangoFilterBackend-styled query string filters to correct types to\n            be able to use them in regular queryset-filter() calls\n            \"\"\"\n            # types of lookups supported by the views using DjangoFilterBackend\n            LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',\n                                    'lte', ]\n            query_set_lookups = []\n            for key, value in legacy_filters.items():\n                parts = key.split('__')\n                if parts[-1] in LEGACY_FIELD_LOOKUPS:\n                    parts = parts[:-1]\n                    model = queryset.model\n                    for part in parts:\n                        try:\n                            field_object = model._meta.get_field(part)\n                            related_model = field_object.model\n                            direct = not field_object.auto_created or field_object.concrete\n\n                            if direct:\n                                if issubclass(field_object.__class__, ForeignObject):\n                                    model = field_object.related_model\n                                else:\n                                    value = field_object.to_python(value)\n                                    break\n                            else:\n                                model = related_model\n                        except FieldDoesNotExist:\n                            pass\n                query_set_lookups += [{key: value}]\n            return query_set_lookups\n\n        queryset = super(BaseRSRViewSet, self).get_queryset()\n\n        # support for old DjangoFilterBackend-based filtering if not pk is given\n        if not self.kwargs.get('pk'):\n            # find all \"old styled\" filters\n            legacy_filters = django_filter_filters(self.request)\n            # create lookup dicts from the filters found\n            lookups = get_lookups_from_filters(legacy_filters)\n            for lookup in lookups:\n                try:\n                    queryset = queryset.filter(**lookup)\n                except (FieldError, ValueError):\n                    # In order to mimick 'old' behaviour of the API, we should ignore non-valid\n                    # parameters or values. Returning a warning would be more preferable.\n                    pass\n\n        return queryset\n\n\nclass ReadOnlyPublicProjectViewSet(viewsets.ReadOnlyModelViewSet):\n    \"\"\"\n    Read only viewset for public projects or objects related to public projects will be shown.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    ordering_fields = '__all__'\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(ReadOnlyPublicProjectViewSet, self).get_queryset()\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = _projects_filter_for_non_privileged_users(\n                user, queryset, self.project_relation, action=self.action\n            )\n\n        return queryset.distinct()\n\n\nclass PublicProjectViewSet(BaseRSRViewSet):\n    \"\"\"\n    Only public projects or objects related to public projects will be shown.\n    \"\"\"\n    # project_relation is the default string for constructing a field lookup to the is_public field\n    # on the related Project. Override this in when the viewset is for a model that doesn't have a\n    # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:\n    #   project_relation = 'result__project__'\n    # The lookup is used to filter out objects associated with private projects, see below.\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(PublicProjectViewSet, self).get_queryset()\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = self.projects_filter_for_non_privileged_users(\n                user, queryset, self.project_relation, action=self.action\n            )\n\n        return queryset.distinct()\n\n    def create(self, request, *args, **kwargs):\n        response = super(PublicProjectViewSet, self).create(request, *args, **kwargs)\n        obj = self.queryset.model.objects.get(pk=response.data['id'])\n        project = self.get_project(obj)\n        if project is not None:\n            project.update_iati_checks()\n            log_project_changes(request.user, project, obj, {}, 'added')\n        return response\n\n    def destroy(self, request, *args, **kwargs):\n        obj = self.get_object()\n        project = self.get_project(obj)\n        response = super(PublicProjectViewSet, self).destroy(request, *args, **kwargs)\n        if project is not None:\n            project.update_iati_checks()\n            log_project_changes(request.user, project, obj, {}, 'deleted')\n        return response\n\n    def update(self, request, *args, **kwargs):\n        response = super(PublicProjectViewSet, self).update(request, *args, **kwargs)\n        obj = self.get_object()\n        project = self.get_project(obj)\n        if project is not None:\n            project.update_iati_checks()\n            log_project_changes(request.user, project, obj, request.data, 'changed')\n        return response\n\n    @staticmethod\n    def get_project(obj):\n        obj_model = obj._meta.model\n        model_project_relation = getattr(obj_model, 'project_relation', None)\n        if model_project_relation:\n            query = {model_project_relation: [obj.id]}\n            project = Project.objects.get(**query)\n        elif obj_model == Project:\n            project = obj\n        elif hasattr(obj, 'project'):\n            project = obj.project\n        else:\n            logger.error('%s does not define a relation to a project', obj_model)\n            project = None\n        return project\n\n    @staticmethod\n    def projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):\n        return _projects_filter_for_non_privileged_users(user, queryset, project_relation, action)\n\n\ndef _projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):\n\n    if not user.is_anonymous() and (user.is_admin or user.is_superuser):\n        return queryset.distinct()\n\n    # Construct the public projects filter field lookup.\n    project_filter = project_relation + 'is_public'\n\n    # Filter the object list into two querysets;\n    # One where the related Projects are public and one where they are private\n    public_objects = queryset.filter(**{project_filter: True}).distinct()\n    private_objects = queryset.filter(**{project_filter: False}).distinct()\n\n    # In case of an anonymous user, only return the public objects\n    if user.is_anonymous():\n        unpublished_exclude = project_relation + 'publishingstatus__status'\n        queryset = public_objects.exclude(\n            **{unpublished_exclude: PublishingStatus.STATUS_UNPUBLISHED}\n        ).distinct()\n\n    # Otherwise, check to which objects the user has (change) permission\n    elif private_objects.exists():\n        include_user_owned = hasattr(queryset.model, 'user')\n        if action == 'list':\n            # The view permission is new, and previously only the change\n            # permission existed. To avoid adding new view permissions for\n            # all the objects, we also check if a user has change\n            # permissions, which implicitly implies view permissions.\n            change_permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n            change_filter = user.get_permission_filter(\n                change_permission, project_relation, include_user_owned\n            )\n            change_objects = private_objects.filter(change_filter).distinct()\n\n            # Check if user has view permission on the queryset\n            view_permission = change_permission.replace('.change_', '.view_')\n            view_filter = user.get_permission_filter(\n                view_permission, project_relation, include_user_owned\n            )\n            view_objects = private_objects.filter(view_filter).distinct()\n\n            private_objects = (change_objects | view_objects).distinct()\n        else:\n            permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n            filter_ = user.get_permission_filter(permission, project_relation, include_user_owned)\n            private_objects = private_objects.filter(filter_).distinct()\n\n        queryset = public_objects | private_objects\n\n    return queryset.distinct()\n", "path": "akvo/rest/viewsets.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nimport logging\n\nfrom django.db.models.fields import FieldDoesNotExist\nfrom django.db.models.fields.related import ForeignObject\nfrom django.core.exceptions import FieldError\n\nfrom akvo.rsr.models import PublishingStatus, Project\nfrom akvo.rest.models import TastyTokenAuthentication\nfrom akvo.utils import log_project_changes\n\nfrom rest_framework import authentication, exceptions, filters, permissions, viewsets\n\nfrom .filters import RSRGenericFilterBackend\nfrom .pagination import TastypieOffsetPagination\n\nlogger = logging.getLogger(__name__)\n\n\nclass SafeMethodsPermissions(permissions.DjangoObjectPermissions):\n    \"\"\"\n    Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to\n    authenticate.\n    \"\"\"\n\n    def has_permission(self, request, view):\n        if request.method in permissions.SAFE_METHODS:\n            return True\n        return super(SafeMethodsPermissions, self).has_permission(request, view)\n\n\nclass BaseRSRViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    Base class used for the view sets for RSR models. Provides unified auth and perms settings.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)\n    ordering_fields = '__all__'\n\n    def paginate_queryset(self, queryset):\n        \"\"\" Custom offset-based pagination for the Tastypie API emulation\n        \"\"\"\n        if self.request and '/api/v1/' in self.request.path:\n            self.pagination_class = TastypieOffsetPagination\n        return super(BaseRSRViewSet, self).paginate_queryset(queryset)\n\n    def get_queryset(self):\n\n        def django_filter_filters(request):\n            \"\"\"\n            Support emulating the DjangoFilterBackend-based filtering that some views used to have\n            \"\"\"\n            # query string keys reserved by the RSRGenericFilterBackend\n            qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]\n            # query string keys used by core DRF, OrderingFilter and Akvo custom views\n            exclude_params = ['limit', 'format', 'page', 'offset', 'ordering', 'partner_type',\n                              'sync_owner', 'reporting_org', ]\n            filters = {}\n            for key in request.query_params:\n                if key not in qs_params + exclude_params and not key.startswith('image_thumb_'):\n                    filters.update({key: request.query_params.get(key)})\n            return filters\n\n        def get_lookups_from_filters(legacy_filters):\n            \"\"\"\n            Cast the values in DjangoFilterBackend-styled query string filters to correct types to\n            be able to use them in regular queryset-filter() calls\n            \"\"\"\n            # types of lookups supported by the views using DjangoFilterBackend\n            LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',\n                                    'lte', ]\n            query_set_lookups = []\n            for key, value in legacy_filters.items():\n                parts = key.split('__')\n                if parts[-1] in LEGACY_FIELD_LOOKUPS:\n                    parts = parts[:-1]\n                    model = queryset.model\n                    for part in parts:\n                        try:\n                            field_object = model._meta.get_field(part)\n                            related_model = field_object.model\n                            direct = not field_object.auto_created or field_object.concrete\n\n                            if direct:\n                                if issubclass(field_object.__class__, ForeignObject):\n                                    model = field_object.related_model\n                                else:\n                                    value = field_object.to_python(value)\n                                    break\n                            else:\n                                model = related_model\n                        except FieldDoesNotExist:\n                            pass\n                query_set_lookups += [{key: value}]\n            return query_set_lookups\n\n        queryset = super(BaseRSRViewSet, self).get_queryset()\n\n        # support for old DjangoFilterBackend-based filtering if not pk is given\n        if not self.kwargs.get('pk'):\n            # find all \"old styled\" filters\n            legacy_filters = django_filter_filters(self.request)\n            # create lookup dicts from the filters found\n            lookups = get_lookups_from_filters(legacy_filters)\n            for lookup in lookups:\n                try:\n                    queryset = queryset.filter(**lookup)\n                except (FieldError, ValueError):\n                    # In order to mimick 'old' behaviour of the API, we should ignore non-valid\n                    # parameters or values. Returning a warning would be more preferable.\n                    pass\n\n        return queryset\n\n\nclass ReadOnlyPublicProjectViewSet(viewsets.ReadOnlyModelViewSet):\n    \"\"\"\n    Read only viewset for public projects or objects related to public projects will be shown.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    ordering_fields = '__all__'\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(ReadOnlyPublicProjectViewSet, self).get_queryset()\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = _projects_filter_for_non_privileged_users(\n                user, queryset, self.project_relation, action=self.action\n            )\n\n        return queryset.distinct()\n\n\nclass PublicProjectViewSet(BaseRSRViewSet):\n    \"\"\"\n    Only public projects or objects related to public projects will be shown.\n    \"\"\"\n    # project_relation is the default string for constructing a field lookup to the is_public field\n    # on the related Project. Override this in when the viewset is for a model that doesn't have a\n    # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:\n    #   project_relation = 'result__project__'\n    # The lookup is used to filter out objects associated with private projects, see below.\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(PublicProjectViewSet, self).get_queryset()\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = self.projects_filter_for_non_privileged_users(\n                user, queryset, self.project_relation, action=self.action\n            )\n\n        return queryset.distinct()\n\n    def create(self, request, *args, **kwargs):\n        project_editor_change = is_project_editor_change(request)\n        response = super(PublicProjectViewSet, self).create(request, *args, **kwargs)\n        obj = self.queryset.model.objects.get(pk=response.data['id'])\n        project = self.get_project(obj)\n        if project is not None:\n            log_project_changes(request.user, project, obj, {}, 'added')\n            if project_editor_change:\n                project.update_iati_checks()\n        return response\n\n    def destroy(self, request, *args, **kwargs):\n        project_editor_change = is_project_editor_change(request)\n        obj = self.get_object()\n        project = self.get_project(obj)\n        response = super(PublicProjectViewSet, self).destroy(request, *args, **kwargs)\n        if project is not None:\n            log_project_changes(request.user, project, obj, {}, 'deleted')\n            if project_editor_change:\n                project.update_iati_checks()\n        return response\n\n    def update(self, request, *args, **kwargs):\n        project_editor_change = is_project_editor_change(request)\n        response = super(PublicProjectViewSet, self).update(request, *args, **kwargs)\n        obj = self.get_object()\n        project = self.get_project(obj)\n        if project is not None:\n            log_project_changes(request.user, project, obj, request.data, 'changed')\n            if project_editor_change:\n                project.update_iati_checks()\n        return response\n\n    @staticmethod\n    def get_project(obj):\n        obj_model = obj._meta.model\n        model_project_relation = getattr(obj_model, 'project_relation', None)\n        if model_project_relation:\n            query = {model_project_relation: [obj.id]}\n            project = Project.objects.get(**query)\n        elif obj_model == Project:\n            project = obj\n        elif hasattr(obj, 'project'):\n            project = obj.project\n        else:\n            logger.error('%s does not define a relation to a project', obj_model)\n            project = None\n        return project\n\n    @staticmethod\n    def projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):\n        return _projects_filter_for_non_privileged_users(user, queryset, project_relation, action)\n\n\ndef _projects_filter_for_non_privileged_users(user, queryset, project_relation, action='create'):\n\n    if not user.is_anonymous() and (user.is_admin or user.is_superuser):\n        return queryset.distinct()\n\n    # Construct the public projects filter field lookup.\n    project_filter = project_relation + 'is_public'\n\n    # Filter the object list into two querysets;\n    # One where the related Projects are public and one where they are private\n    public_objects = queryset.filter(**{project_filter: True}).distinct()\n    private_objects = queryset.filter(**{project_filter: False}).distinct()\n\n    # In case of an anonymous user, only return the public objects\n    if user.is_anonymous():\n        unpublished_exclude = project_relation + 'publishingstatus__status'\n        queryset = public_objects.exclude(\n            **{unpublished_exclude: PublishingStatus.STATUS_UNPUBLISHED}\n        ).distinct()\n\n    # Otherwise, check to which objects the user has (change) permission\n    elif private_objects.exists():\n        include_user_owned = hasattr(queryset.model, 'user')\n        if action == 'list':\n            # The view permission is new, and previously only the change\n            # permission existed. To avoid adding new view permissions for\n            # all the objects, we also check if a user has change\n            # permissions, which implicitly implies view permissions.\n            change_permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n            change_filter = user.get_permission_filter(\n                change_permission, project_relation, include_user_owned\n            )\n            change_objects = private_objects.filter(change_filter).distinct()\n\n            # Check if user has view permission on the queryset\n            view_permission = change_permission.replace('.change_', '.view_')\n            view_filter = user.get_permission_filter(\n                view_permission, project_relation, include_user_owned\n            )\n            view_objects = private_objects.filter(view_filter).distinct()\n\n            private_objects = (change_objects | view_objects).distinct()\n        else:\n            permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n            filter_ = user.get_permission_filter(permission, project_relation, include_user_owned)\n            private_objects = private_objects.filter(filter_).distinct()\n\n        queryset = public_objects | private_objects\n\n    return queryset.distinct()\n\n\ndef is_project_editor_change(request):\n    try:\n        project_editor_change = request.data.get('project_editor_change', False)\n    except exceptions.UnsupportedMediaType:\n        project_editor_change = False\n    return project_editor_change\n", "path": "akvo/rest/viewsets.py"}]}