We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Indicator update user should not change when update rejected or approved


--- 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/views/indicator_period_data.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 json
8 import os
9 
10 from akvo.rsr.models import IndicatorPeriodData, IndicatorPeriodDataComment, Project
11 from akvo.rest.models import TastyTokenAuthentication, JWTAuthentication
12 from akvo.rsr.models.result.utils import QUANTITATIVE, PERCENTAGE_MEASURE
13 
14 from ..serializers import (IndicatorPeriodDataSerializer, IndicatorPeriodDataFrameworkSerializer,
15                            IndicatorPeriodDataCommentSerializer)
16 from ..viewsets import PublicProjectViewSet
17 
18 from django.shortcuts import get_object_or_404
19 from django.http import HttpResponseBadRequest, HttpResponseForbidden
20 from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
21 from django.contrib.contenttypes.models import ContentType
22 from rest_framework import status
23 from rest_framework.authentication import SessionAuthentication
24 from rest_framework.decorators import api_view, authentication_classes
25 from rest_framework.response import Response
26 from rest_framework.utils.encoders import JSONEncoder
27 
28 
29 class IndicatorPeriodDataViewSet(PublicProjectViewSet):
30     """
31     """
32     queryset = IndicatorPeriodData.objects.select_related('user', 'approved_by').all()
33     serializer_class = IndicatorPeriodDataSerializer
34 
35     project_relation = 'period__indicator__result__project__'
36 
37     def get_queryset(self):
38         queryset = super(IndicatorPeriodDataViewSet, self).get_queryset()
39         return IndicatorPeriodData.get_user_viewable_updates(
40             queryset, self.request.user
41         )
42 
43     def perform_create(self, serializer):
44         serializer.save(user=self.request.user)
45 
46 
47 class IndicatorPeriodDataFrameworkViewSet(PublicProjectViewSet):
48     """
49     """
50     authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)
51 
52     queryset = IndicatorPeriodData.objects.select_related(
53         'period',
54         'user',
55         'approved_by',
56     ).prefetch_related(
57         'comments',
58         'disaggregations',
59     ).all()
60     serializer_class = IndicatorPeriodDataFrameworkSerializer
61     project_relation = 'period__indicator__result__project__'
62 
63     def get_queryset(self):
64         queryset = getattr(self, '_c_queryset', None)
65         if queryset is None:
66             queryset = super(IndicatorPeriodDataFrameworkViewSet, self).get_queryset()
67             queryset = IndicatorPeriodData.get_user_viewable_updates(
68                 queryset, self.request.user
69             )
70             self._c_queryset = queryset
71 
72         return queryset
73 
74     def perform_create(self, serializer):
75         data = {key: value for key, value in serializer.validated_data.items() if key not in ['period', 'files', 'photos']}
76         if len(serializer._disaggregations_data) > 0:
77             data['disaggregations'] = [
78                 {key: value for key, value in dsg.items() if key in ['id', 'dimension_value', 'value', 'numerator', 'denominator']}
79                 for dsg in serializer._disaggregations_data
80             ]
81         user = self.request.user
82         serializer.save(user=user)
83         instance = serializer.instance
84         log_data = {'audit_trail': True, 'data': data}
85         LogEntry.objects.log_action(
86             user_id=user.id,
87             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
88             object_id=instance.id,
89             object_repr=str(instance),
90             action_flag=ADDITION,
91             change_message=json.dumps(log_data, cls=JSONEncoder)
92         )
93 
94     def perform_update(self, serializer):
95         instance = serializer.instance
96         data = {
97             key: value
98             for key, value in serializer.validated_data.items()
99             if key not in ['period', 'files', 'photos'] and (key == 'comments' or getattr(instance, key) != value)
100         }
101         if len(serializer._disaggregations_data) > 0:
102             indicator = instance.period.indicator
103             is_percentage = indicator.type == QUANTITATIVE and indicator.measure == PERCENTAGE_MEASURE
104             dsg_attrs = ['id', 'dimension_value', 'numerator', 'denominator'] if is_percentage else ['id', 'dimension_value', 'value']
105             data['disaggregations'] = [
106                 {key: value for key, value in dsg.items() if key in dsg_attrs}
107                 for dsg in serializer._disaggregations_data
108             ]
109         user = self.request.user
110         serializer.save(user=user)
111         log_data = {'audit_trail': True, 'data': data}
112         LogEntry.objects.log_action(
113             user_id=user.id,
114             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
115             object_id=instance.id,
116             object_repr=str(instance),
117             action_flag=CHANGE,
118             change_message=json.dumps(log_data, cls=JSONEncoder)
119         )
120 
121     def perform_destroy(self, instance):
122         object_id = instance.id
123         object_repr = str(instance)
124         super().perform_destroy(instance)
125         LogEntry.objects.log_action(
126             user_id=self.request.user.id,
127             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
128             object_id=object_id,
129             object_repr=object_repr,
130             action_flag=DELETION,
131             change_message=json.dumps({'audit_trail': True})
132         )
133 
134 
135 class IndicatorPeriodDataCommentViewSet(PublicProjectViewSet):
136     """
137     """
138     authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)
139 
140     # TODO: Is there more optimization possible?
141     queryset = IndicatorPeriodDataComment.objects.select_related(
142         'user'
143     ).prefetch_related(
144         'user__employers', 'user__employers__organisation'
145     )
146     serializer_class = IndicatorPeriodDataCommentSerializer
147     project_relation = 'data__period__indicator__result__project__'
148 
149     def perform_create(self, serializer):
150         serializer.save(user=self.request.user)
151 
152 
153 @api_view(['POST', 'DELETE'])
154 @authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])
155 def period_update_files(request, update_pk, file_pk=None):
156     update = get_object_or_404(IndicatorPeriodData, pk=update_pk)
157     user = request.user
158     if not user.has_perm('rsr.change_indicatorperioddata', update):
159         return Response({'error': 'User has no permission to add/remove files'}, status=status.HTTP_403_FORBIDDEN)
160 
161     if request.method == 'POST' and not file_pk:
162         serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)
163         serializer.is_valid(raise_exception=True)
164         files = [f"Uploaded file \"{file.name}\"" for file in serializer.validated_data.get('files', [])]
165         serializer.save(user=user)
166         log_data = {'audit_trail': True, 'data': {'files': files}}
167         LogEntry.objects.log_action(
168             user_id=user.id,
169             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
170             object_id=update.id,
171             object_repr=str(update),
172             action_flag=CHANGE,
173             change_message=json.dumps(log_data)
174         )
175         return Response(serializer.data['file_set'])
176 
177     if request.method == 'DELETE' and file_pk:
178         file = update.indicatorperioddatafile_set.get(pk=file_pk)
179         filename = os.path.basename(file.file.name)
180         file.delete()
181         update.user = user
182         update.save(update_fields=['user'])
183         log_data = {'audit_trail': True, 'data': {'files': [f"Removed file \"{filename}\""]}}
184         LogEntry.objects.log_action(
185             user_id=user.id,
186             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
187             object_id=update.id,
188             object_repr=str(update),
189             action_flag=CHANGE,
190             change_message=json.dumps(log_data)
191         )
192         return Response(status=status.HTTP_204_NO_CONTENT)
193 
194     return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
195 
196 
197 @api_view(['POST', 'DELETE'])
198 @authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])
199 def period_update_photos(request, update_pk, photo_pk=None):
200     update = get_object_or_404(IndicatorPeriodData, pk=update_pk)
201     user = request.user
202     if user != update.user:
203         return Response({'error': 'User has no permission to add/remove photos'}, status=status.HTTP_403_FORBIDDEN)
204 
205     if request.method == 'POST' and not photo_pk:
206         serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)
207         serializer.is_valid(raise_exception=True)
208         serializer.save(user=user)
209         return Response(serializer.data['photo_set'])
210 
211     if request.method == 'DELETE' and photo_pk:
212         photo = update.indicatorperioddataphoto_set.get(pk=photo_pk)
213         photo.delete()
214         return Response(status=status.HTTP_204_NO_CONTENT)
215 
216     return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
217 
218 
219 @api_view(['POST', 'DELETE'])
220 def indicator_upload_file(request, pk=None):
221     """
222     Special API call for directly uploading a file.
223 
224     :param request; A Django request object.
225     :param pk; The primary key of an IndicatorPeriodData instance.
226     """
227     # Permissions
228     user = getattr(request, 'user', None)
229     if not user:
230         return Response({'error': 'User is not logged in'}, status=status.HTTP_403_FORBIDDEN)
231     # TODO: Check if user is allowed to upload a file
232     # if not user.has_perm('rsr.change_project', update.period.indicator.result.project):
233     #     return Response({'error': 'User has no permission to place an update'},
234     #                     status=status.HTTP_403_FORBIDDEN)
235 
236     update = IndicatorPeriodData.objects.get(pk=pk)
237     if request.method == 'DELETE':
238         try:
239             if request.data['type'] == 'photo':
240                 update.photo = ''
241                 update.save(update_fields=['photo'])
242                 return Response({}, status=status.HTTP_204_NO_CONTENT)
243             elif request.data['type'] == 'file':
244                 update.file = ''
245                 update.save(update_fields=['file'])
246                 return Response({}, status=status.HTTP_204_NO_CONTENT)
247         except Exception as e:
248             return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
249     else:  # POST
250         upload_file = request.data['file']
251         try:
252             file_type = request.POST.copy()['type']
253             if file_type == 'photo':
254                 update.photo = upload_file
255                 update.save(update_fields=['photo'])
256                 # Add photo member to be able to distinguish from file URL in new results version
257                 # while keeping the old API
258                 return Response({'file': update.photo.url, 'photo': update.photo.url})
259             elif file_type == 'file':
260                 update.file = upload_file
261                 update.save(update_fields=['file'])
262                 return Response({'file': update.file.url})
263         except Exception as e:
264             return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
265 
266 
267 @api_view(['POST'])
268 @authentication_classes([SessionAuthentication, TastyTokenAuthentication])
269 def set_updates_status(request, project_pk):
270     """Bulk update IndicatorPeriodData.status attributes of a project.
271     """
272     update_ids = request.data.get('updates', [])
273     status = request.data.get('status', None)
274     if len(update_ids) < 1 or status is None:
275         return HttpResponseBadRequest()
276     user = request.user
277     project = get_object_or_404(Project, pk=project_pk)
278     if not user.has_perm('rsr.change_project', project):
279         return HttpResponseForbidden()
280     IndicatorPeriodData.objects\
281         .filter(id__in=update_ids, period__indicator__result__project=project)\
282         .update(status=status)
283     log_data = {'audit_trail': True, 'data': {'status': status}}
284     for update_id in update_ids:
285         LogEntry.objects.log_action(
286             user_id=user.id,
287             content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,
288             object_id=update_id,
289             object_repr='IndicatorPeriodData',
290             action_flag=CHANGE,
291             change_message=json.dumps(log_data)
292         )
293     return Response({'success': True})
294
```
--- 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/views/indicator_period_data.py b/akvo/rest/views/indicator_period_data.py
--- a/akvo/rest/views/indicator_period_data.py
+++ b/akvo/rest/views/indicator_period_data.py
@@ -107,7 +107,9 @@
                 for dsg in serializer._disaggregations_data
             ]
         user = self.request.user
-        serializer.save(user=user)
+        status = data.get('status', None)
+        if status != 'R' and status != 'A':
+            serializer.save(user=user)
         log_data = {'audit_trail': True, 'data': data}
         LogEntry.objects.log_action(
             user_id=user.id,

{"golden_diff": "diff --git a/akvo/rest/views/indicator_period_data.py b/akvo/rest/views/indicator_period_data.py\n--- a/akvo/rest/views/indicator_period_data.py\n+++ b/akvo/rest/views/indicator_period_data.py\n@@ -107,7 +107,9 @@\n                 for dsg in serializer._disaggregations_data\n             ]\n         user = self.request.user\n-        serializer.save(user=user)\n+        status = data.get('status', None)\n+        if status != 'R' and status != 'A':\n+            serializer.save(user=user)\n         log_data = {'audit_trail': True, 'data': data}\n         LogEntry.objects.log_action(\n             user_id=user.id,\n", "issue": "Indicator update user should not change when update rejected or approved\n\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 json\nimport os\n\nfrom akvo.rsr.models import IndicatorPeriodData, IndicatorPeriodDataComment, Project\nfrom akvo.rest.models import TastyTokenAuthentication, JWTAuthentication\nfrom akvo.rsr.models.result.utils import QUANTITATIVE, PERCENTAGE_MEASURE\n\nfrom ..serializers import (IndicatorPeriodDataSerializer, IndicatorPeriodDataFrameworkSerializer,\n                           IndicatorPeriodDataCommentSerializer)\nfrom ..viewsets import PublicProjectViewSet\n\nfrom django.shortcuts import get_object_or_404\nfrom django.http import HttpResponseBadRequest, HttpResponseForbidden\nfrom django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION\nfrom django.contrib.contenttypes.models import ContentType\nfrom rest_framework import status\nfrom rest_framework.authentication import SessionAuthentication\nfrom rest_framework.decorators import api_view, authentication_classes\nfrom rest_framework.response import Response\nfrom rest_framework.utils.encoders import JSONEncoder\n\n\nclass IndicatorPeriodDataViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    queryset = IndicatorPeriodData.objects.select_related('user', 'approved_by').all()\n    serializer_class = IndicatorPeriodDataSerializer\n\n    project_relation = 'period__indicator__result__project__'\n\n    def get_queryset(self):\n        queryset = super(IndicatorPeriodDataViewSet, self).get_queryset()\n        return IndicatorPeriodData.get_user_viewable_updates(\n            queryset, self.request.user\n        )\n\n    def perform_create(self, serializer):\n        serializer.save(user=self.request.user)\n\n\nclass IndicatorPeriodDataFrameworkViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)\n\n    queryset = IndicatorPeriodData.objects.select_related(\n        'period',\n        'user',\n        'approved_by',\n    ).prefetch_related(\n        'comments',\n        'disaggregations',\n    ).all()\n    serializer_class = IndicatorPeriodDataFrameworkSerializer\n    project_relation = 'period__indicator__result__project__'\n\n    def get_queryset(self):\n        queryset = getattr(self, '_c_queryset', None)\n        if queryset is None:\n            queryset = super(IndicatorPeriodDataFrameworkViewSet, self).get_queryset()\n            queryset = IndicatorPeriodData.get_user_viewable_updates(\n                queryset, self.request.user\n            )\n            self._c_queryset = queryset\n\n        return queryset\n\n    def perform_create(self, serializer):\n        data = {key: value for key, value in serializer.validated_data.items() if key not in ['period', 'files', 'photos']}\n        if len(serializer._disaggregations_data) > 0:\n            data['disaggregations'] = [\n                {key: value for key, value in dsg.items() if key in ['id', 'dimension_value', 'value', 'numerator', 'denominator']}\n                for dsg in serializer._disaggregations_data\n            ]\n        user = self.request.user\n        serializer.save(user=user)\n        instance = serializer.instance\n        log_data = {'audit_trail': True, 'data': data}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=instance.id,\n            object_repr=str(instance),\n            action_flag=ADDITION,\n            change_message=json.dumps(log_data, cls=JSONEncoder)\n        )\n\n    def perform_update(self, serializer):\n        instance = serializer.instance\n        data = {\n            key: value\n            for key, value in serializer.validated_data.items()\n            if key not in ['period', 'files', 'photos'] and (key == 'comments' or getattr(instance, key) != value)\n        }\n        if len(serializer._disaggregations_data) > 0:\n            indicator = instance.period.indicator\n            is_percentage = indicator.type == QUANTITATIVE and indicator.measure == PERCENTAGE_MEASURE\n            dsg_attrs = ['id', 'dimension_value', 'numerator', 'denominator'] if is_percentage else ['id', 'dimension_value', 'value']\n            data['disaggregations'] = [\n                {key: value for key, value in dsg.items() if key in dsg_attrs}\n                for dsg in serializer._disaggregations_data\n            ]\n        user = self.request.user\n        serializer.save(user=user)\n        log_data = {'audit_trail': True, 'data': data}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=instance.id,\n            object_repr=str(instance),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data, cls=JSONEncoder)\n        )\n\n    def perform_destroy(self, instance):\n        object_id = instance.id\n        object_repr = str(instance)\n        super().perform_destroy(instance)\n        LogEntry.objects.log_action(\n            user_id=self.request.user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=object_id,\n            object_repr=object_repr,\n            action_flag=DELETION,\n            change_message=json.dumps({'audit_trail': True})\n        )\n\n\nclass IndicatorPeriodDataCommentViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)\n\n    # TODO: Is there more optimization possible?\n    queryset = IndicatorPeriodDataComment.objects.select_related(\n        'user'\n    ).prefetch_related(\n        'user__employers', 'user__employers__organisation'\n    )\n    serializer_class = IndicatorPeriodDataCommentSerializer\n    project_relation = 'data__period__indicator__result__project__'\n\n    def perform_create(self, serializer):\n        serializer.save(user=self.request.user)\n\n\n@api_view(['POST', 'DELETE'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])\ndef period_update_files(request, update_pk, file_pk=None):\n    update = get_object_or_404(IndicatorPeriodData, pk=update_pk)\n    user = request.user\n    if not user.has_perm('rsr.change_indicatorperioddata', update):\n        return Response({'error': 'User has no permission to add/remove files'}, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == 'POST' and not file_pk:\n        serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        files = [f\"Uploaded file \\\"{file.name}\\\"\" for file in serializer.validated_data.get('files', [])]\n        serializer.save(user=user)\n        log_data = {'audit_trail': True, 'data': {'files': files}}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update.id,\n            object_repr=str(update),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n        return Response(serializer.data['file_set'])\n\n    if request.method == 'DELETE' and file_pk:\n        file = update.indicatorperioddatafile_set.get(pk=file_pk)\n        filename = os.path.basename(file.file.name)\n        file.delete()\n        update.user = user\n        update.save(update_fields=['user'])\n        log_data = {'audit_trail': True, 'data': {'files': [f\"Removed file \\\"{filename}\\\"\"]}}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update.id,\n            object_repr=str(update),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)\n\n\n@api_view(['POST', 'DELETE'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])\ndef period_update_photos(request, update_pk, photo_pk=None):\n    update = get_object_or_404(IndicatorPeriodData, pk=update_pk)\n    user = request.user\n    if user != update.user:\n        return Response({'error': 'User has no permission to add/remove photos'}, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == 'POST' and not photo_pk:\n        serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        serializer.save(user=user)\n        return Response(serializer.data['photo_set'])\n\n    if request.method == 'DELETE' and photo_pk:\n        photo = update.indicatorperioddataphoto_set.get(pk=photo_pk)\n        photo.delete()\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)\n\n\n@api_view(['POST', 'DELETE'])\ndef indicator_upload_file(request, pk=None):\n    \"\"\"\n    Special API call for directly uploading a file.\n\n    :param request; A Django request object.\n    :param pk; The primary key of an IndicatorPeriodData instance.\n    \"\"\"\n    # Permissions\n    user = getattr(request, 'user', None)\n    if not user:\n        return Response({'error': 'User is not logged in'}, status=status.HTTP_403_FORBIDDEN)\n    # TODO: Check if user is allowed to upload a file\n    # if not user.has_perm('rsr.change_project', update.period.indicator.result.project):\n    #     return Response({'error': 'User has no permission to place an update'},\n    #                     status=status.HTTP_403_FORBIDDEN)\n\n    update = IndicatorPeriodData.objects.get(pk=pk)\n    if request.method == 'DELETE':\n        try:\n            if request.data['type'] == 'photo':\n                update.photo = ''\n                update.save(update_fields=['photo'])\n                return Response({}, status=status.HTTP_204_NO_CONTENT)\n            elif request.data['type'] == 'file':\n                update.file = ''\n                update.save(update_fields=['file'])\n                return Response({}, status=status.HTTP_204_NO_CONTENT)\n        except Exception as e:\n            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)\n    else:  # POST\n        upload_file = request.data['file']\n        try:\n            file_type = request.POST.copy()['type']\n            if file_type == 'photo':\n                update.photo = upload_file\n                update.save(update_fields=['photo'])\n                # Add photo member to be able to distinguish from file URL in new results version\n                # while keeping the old API\n                return Response({'file': update.photo.url, 'photo': update.photo.url})\n            elif file_type == 'file':\n                update.file = upload_file\n                update.save(update_fields=['file'])\n                return Response({'file': update.file.url})\n        except Exception as e:\n            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)\n\n\n@api_view(['POST'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication])\ndef set_updates_status(request, project_pk):\n    \"\"\"Bulk update IndicatorPeriodData.status attributes of a project.\n    \"\"\"\n    update_ids = request.data.get('updates', [])\n    status = request.data.get('status', None)\n    if len(update_ids) < 1 or status is None:\n        return HttpResponseBadRequest()\n    user = request.user\n    project = get_object_or_404(Project, pk=project_pk)\n    if not user.has_perm('rsr.change_project', project):\n        return HttpResponseForbidden()\n    IndicatorPeriodData.objects\\\n        .filter(id__in=update_ids, period__indicator__result__project=project)\\\n        .update(status=status)\n    log_data = {'audit_trail': True, 'data': {'status': status}}\n    for update_id in update_ids:\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update_id,\n            object_repr='IndicatorPeriodData',\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n    return Response({'success': True})\n", "path": "akvo/rest/views/indicator_period_data.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 json\nimport os\n\nfrom akvo.rsr.models import IndicatorPeriodData, IndicatorPeriodDataComment, Project\nfrom akvo.rest.models import TastyTokenAuthentication, JWTAuthentication\nfrom akvo.rsr.models.result.utils import QUANTITATIVE, PERCENTAGE_MEASURE\n\nfrom ..serializers import (IndicatorPeriodDataSerializer, IndicatorPeriodDataFrameworkSerializer,\n                           IndicatorPeriodDataCommentSerializer)\nfrom ..viewsets import PublicProjectViewSet\n\nfrom django.shortcuts import get_object_or_404\nfrom django.http import HttpResponseBadRequest, HttpResponseForbidden\nfrom django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION\nfrom django.contrib.contenttypes.models import ContentType\nfrom rest_framework import status\nfrom rest_framework.authentication import SessionAuthentication\nfrom rest_framework.decorators import api_view, authentication_classes\nfrom rest_framework.response import Response\nfrom rest_framework.utils.encoders import JSONEncoder\n\n\nclass IndicatorPeriodDataViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    queryset = IndicatorPeriodData.objects.select_related('user', 'approved_by').all()\n    serializer_class = IndicatorPeriodDataSerializer\n\n    project_relation = 'period__indicator__result__project__'\n\n    def get_queryset(self):\n        queryset = super(IndicatorPeriodDataViewSet, self).get_queryset()\n        return IndicatorPeriodData.get_user_viewable_updates(\n            queryset, self.request.user\n        )\n\n    def perform_create(self, serializer):\n        serializer.save(user=self.request.user)\n\n\nclass IndicatorPeriodDataFrameworkViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)\n\n    queryset = IndicatorPeriodData.objects.select_related(\n        'period',\n        'user',\n        'approved_by',\n    ).prefetch_related(\n        'comments',\n        'disaggregations',\n    ).all()\n    serializer_class = IndicatorPeriodDataFrameworkSerializer\n    project_relation = 'period__indicator__result__project__'\n\n    def get_queryset(self):\n        queryset = getattr(self, '_c_queryset', None)\n        if queryset is None:\n            queryset = super(IndicatorPeriodDataFrameworkViewSet, self).get_queryset()\n            queryset = IndicatorPeriodData.get_user_viewable_updates(\n                queryset, self.request.user\n            )\n            self._c_queryset = queryset\n\n        return queryset\n\n    def perform_create(self, serializer):\n        data = {key: value for key, value in serializer.validated_data.items() if key not in ['period', 'files', 'photos']}\n        if len(serializer._disaggregations_data) > 0:\n            data['disaggregations'] = [\n                {key: value for key, value in dsg.items() if key in ['id', 'dimension_value', 'value', 'numerator', 'denominator']}\n                for dsg in serializer._disaggregations_data\n            ]\n        user = self.request.user\n        serializer.save(user=user)\n        instance = serializer.instance\n        log_data = {'audit_trail': True, 'data': data}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=instance.id,\n            object_repr=str(instance),\n            action_flag=ADDITION,\n            change_message=json.dumps(log_data, cls=JSONEncoder)\n        )\n\n    def perform_update(self, serializer):\n        instance = serializer.instance\n        data = {\n            key: value\n            for key, value in serializer.validated_data.items()\n            if key not in ['period', 'files', 'photos'] and (key == 'comments' or getattr(instance, key) != value)\n        }\n        if len(serializer._disaggregations_data) > 0:\n            indicator = instance.period.indicator\n            is_percentage = indicator.type == QUANTITATIVE and indicator.measure == PERCENTAGE_MEASURE\n            dsg_attrs = ['id', 'dimension_value', 'numerator', 'denominator'] if is_percentage else ['id', 'dimension_value', 'value']\n            data['disaggregations'] = [\n                {key: value for key, value in dsg.items() if key in dsg_attrs}\n                for dsg in serializer._disaggregations_data\n            ]\n        user = self.request.user\n        status = data.get('status', None)\n        if status != 'R' and status != 'A':\n            serializer.save(user=user)\n        log_data = {'audit_trail': True, 'data': data}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=instance.id,\n            object_repr=str(instance),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data, cls=JSONEncoder)\n        )\n\n    def perform_destroy(self, instance):\n        object_id = instance.id\n        object_repr = str(instance)\n        super().perform_destroy(instance)\n        LogEntry.objects.log_action(\n            user_id=self.request.user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=object_id,\n            object_repr=object_repr,\n            action_flag=DELETION,\n            change_message=json.dumps({'audit_trail': True})\n        )\n\n\nclass IndicatorPeriodDataCommentViewSet(PublicProjectViewSet):\n    \"\"\"\n    \"\"\"\n    authentication_classes = (SessionAuthentication, TastyTokenAuthentication, JWTAuthentication)\n\n    # TODO: Is there more optimization possible?\n    queryset = IndicatorPeriodDataComment.objects.select_related(\n        'user'\n    ).prefetch_related(\n        'user__employers', 'user__employers__organisation'\n    )\n    serializer_class = IndicatorPeriodDataCommentSerializer\n    project_relation = 'data__period__indicator__result__project__'\n\n    def perform_create(self, serializer):\n        serializer.save(user=self.request.user)\n\n\n@api_view(['POST', 'DELETE'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])\ndef period_update_files(request, update_pk, file_pk=None):\n    update = get_object_or_404(IndicatorPeriodData, pk=update_pk)\n    user = request.user\n    if not user.has_perm('rsr.change_indicatorperioddata', update):\n        return Response({'error': 'User has no permission to add/remove files'}, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == 'POST' and not file_pk:\n        serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        files = [f\"Uploaded file \\\"{file.name}\\\"\" for file in serializer.validated_data.get('files', [])]\n        serializer.save(user=user)\n        log_data = {'audit_trail': True, 'data': {'files': files}}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update.id,\n            object_repr=str(update),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n        return Response(serializer.data['file_set'])\n\n    if request.method == 'DELETE' and file_pk:\n        file = update.indicatorperioddatafile_set.get(pk=file_pk)\n        filename = os.path.basename(file.file.name)\n        file.delete()\n        update.user = user\n        update.save(update_fields=['user'])\n        log_data = {'audit_trail': True, 'data': {'files': [f\"Removed file \\\"{filename}\\\"\"]}}\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update.id,\n            object_repr=str(update),\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)\n\n\n@api_view(['POST', 'DELETE'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication, JWTAuthentication])\ndef period_update_photos(request, update_pk, photo_pk=None):\n    update = get_object_or_404(IndicatorPeriodData, pk=update_pk)\n    user = request.user\n    if user != update.user:\n        return Response({'error': 'User has no permission to add/remove photos'}, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == 'POST' and not photo_pk:\n        serializer = IndicatorPeriodDataFrameworkSerializer(instance=update, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        serializer.save(user=user)\n        return Response(serializer.data['photo_set'])\n\n    if request.method == 'DELETE' and photo_pk:\n        photo = update.indicatorperioddataphoto_set.get(pk=photo_pk)\n        photo.delete()\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)\n\n\n@api_view(['POST', 'DELETE'])\ndef indicator_upload_file(request, pk=None):\n    \"\"\"\n    Special API call for directly uploading a file.\n\n    :param request; A Django request object.\n    :param pk; The primary key of an IndicatorPeriodData instance.\n    \"\"\"\n    # Permissions\n    user = getattr(request, 'user', None)\n    if not user:\n        return Response({'error': 'User is not logged in'}, status=status.HTTP_403_FORBIDDEN)\n    # TODO: Check if user is allowed to upload a file\n    # if not user.has_perm('rsr.change_project', update.period.indicator.result.project):\n    #     return Response({'error': 'User has no permission to place an update'},\n    #                     status=status.HTTP_403_FORBIDDEN)\n\n    update = IndicatorPeriodData.objects.get(pk=pk)\n    if request.method == 'DELETE':\n        try:\n            if request.data['type'] == 'photo':\n                update.photo = ''\n                update.save(update_fields=['photo'])\n                return Response({}, status=status.HTTP_204_NO_CONTENT)\n            elif request.data['type'] == 'file':\n                update.file = ''\n                update.save(update_fields=['file'])\n                return Response({}, status=status.HTTP_204_NO_CONTENT)\n        except Exception as e:\n            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)\n    else:  # POST\n        upload_file = request.data['file']\n        try:\n            file_type = request.POST.copy()['type']\n            if file_type == 'photo':\n                update.photo = upload_file\n                update.save(update_fields=['photo'])\n                # Add photo member to be able to distinguish from file URL in new results version\n                # while keeping the old API\n                return Response({'file': update.photo.url, 'photo': update.photo.url})\n            elif file_type == 'file':\n                update.file = upload_file\n                update.save(update_fields=['file'])\n                return Response({'file': update.file.url})\n        except Exception as e:\n            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)\n\n\n@api_view(['POST'])\n@authentication_classes([SessionAuthentication, TastyTokenAuthentication])\ndef set_updates_status(request, project_pk):\n    \"\"\"Bulk update IndicatorPeriodData.status attributes of a project.\n    \"\"\"\n    update_ids = request.data.get('updates', [])\n    status = request.data.get('status', None)\n    if len(update_ids) < 1 or status is None:\n        return HttpResponseBadRequest()\n    user = request.user\n    project = get_object_or_404(Project, pk=project_pk)\n    if not user.has_perm('rsr.change_project', project):\n        return HttpResponseForbidden()\n    IndicatorPeriodData.objects\\\n        .filter(id__in=update_ids, period__indicator__result__project=project)\\\n        .update(status=status)\n    log_data = {'audit_trail': True, 'data': {'status': status}}\n    for update_id in update_ids:\n        LogEntry.objects.log_action(\n            user_id=user.id,\n            content_type_id=ContentType.objects.get_for_model(IndicatorPeriodData).id,\n            object_id=update_id,\n            object_repr='IndicatorPeriodData',\n            action_flag=CHANGE,\n            change_message=json.dumps(log_data)\n        )\n    return Response({'success': True})\n", "path": "akvo/rest/views/indicator_period_data.py"}]}