We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fallback to value in "Report Admin > Pending Approval" list view
# Current situation

Some updates look like they have no value and others do.

![Image](https://user-images.githubusercontent.com/91939214/211502642-df01abac-d774-4fad-a167-a0940796a42f.png)

The reason is that some have descriptions and others don't.

**Examples:**

With description
![Image](https://user-images.githubusercontent.com/91939214/211502702-3a0a1f62-4de4-4dc0-9212-f4d061bad2f2.png)

Without description
![Image](https://user-images.githubusercontent.com/91939214/211502718-d95d8e2a-60f0-4ff2-b348-909fc9411559.png)

# Improvement

Fallback to the actual value when no description has been provided.

--- 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/serializers/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 import json
7 
8 from rest_framework import serializers
9 from django.db.models import Sum
10 from django.contrib.admin.models import LogEntry, CHANGE, DELETION
11 from django.contrib.contenttypes.models import ContentType
12 
13 from akvo.rest.serializers.disaggregation import DisaggregationSerializer, DisaggregationReadOnlySerializer
14 from akvo.rest.serializers.rsr_serializer import BaseRSRSerializer
15 from akvo.rest.serializers.user import UserDetailsSerializer, UserRawSerializer
16 from akvo.rsr.models import (
17     IndicatorPeriod, IndicatorPeriodData, IndicatorPeriodDataComment, IndicatorPeriodDataFile, IndicatorPeriodDataPhoto,
18     IndicatorDimensionValue, Disaggregation
19 )
20 from akvo.utils import ensure_decimal
21 
22 
23 class IndicatorPeriodDataCommentSerializer(BaseRSRSerializer):
24 
25     user_details = UserDetailsSerializer(read_only=True, source='user')
26 
27     class Meta:
28         model = IndicatorPeriodDataComment
29         fields = '__all__'
30         read_only_fields = ['user']
31 
32 
33 class IndicatorPeriodDataCommentNestedSerializer(BaseRSRSerializer):
34     id = serializers.IntegerField(required=False)
35 
36     class Meta:
37         model = IndicatorPeriodDataComment
38         fields = '__all__'
39         read_only_fields = ('id', 'data', 'user')
40 
41 
42 class IndicatorPeriodDataFileSerializer(BaseRSRSerializer):
43     class Meta:
44         model = IndicatorPeriodDataFile
45         fields = '__all__'
46 
47 
48 class IndicatorPeriodDataPhotoSerializer(BaseRSRSerializer):
49     class Meta:
50         model = IndicatorPeriodDataPhoto
51         fields = '__all__'
52 
53 
54 class IndicatorPeriodDataSerializer(BaseRSRSerializer):
55 
56     user_details = UserDetailsSerializer(read_only=True, source='user')
57     approver_details = UserDetailsSerializer(read_only=True, source='approved_by')
58     status_display = serializers.ReadOnlyField()
59     photo_url = serializers.ReadOnlyField()
60     file_url = serializers.ReadOnlyField()
61 
62     class Meta:
63         model = IndicatorPeriodData
64         fields = '__all__'
65         read_only_fields = ['user']
66 
67 
68 class IndicatorPeriodDataLiteSerializer(BaseRSRSerializer):
69 
70     user_details = UserRawSerializer(required=False, source='user')
71     status_display = serializers.ReadOnlyField()
72     photo_url = serializers.ReadOnlyField()
73     file_url = serializers.ReadOnlyField()
74     disaggregations = DisaggregationReadOnlySerializer(many=True, required=False)
75     value = serializers.SerializerMethodField()
76     file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')
77     photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')
78     comments = IndicatorPeriodDataCommentSerializer(read_only=True, many=True, required=False)
79 
80     def get_value(self, obj):
81         return ensure_decimal(obj.value)
82 
83     class Meta:
84         model = IndicatorPeriodData
85         fields = (
86             'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',
87             'disaggregations', 'narrative', 'photo_url', 'file_url', 'created_at', 'last_modified_at',
88             'file_set', 'photo_set', 'review_note', 'comments',
89         )
90 
91 
92 class IndicatorPeriodDataFrameworkSerializer(BaseRSRSerializer):
93 
94     period = serializers.PrimaryKeyRelatedField(queryset=IndicatorPeriod.objects.all())
95     comments = IndicatorPeriodDataCommentNestedSerializer(many=True, required=False)
96     disaggregations = DisaggregationSerializer(many=True, required=False)
97     user_details = UserDetailsSerializer(read_only=True, source='user')
98     approver_details = UserDetailsSerializer(read_only=True, source='approved_by')
99     status_display = serializers.ReadOnlyField()
100     photo_url = serializers.ReadOnlyField()
101     file_url = serializers.ReadOnlyField()
102     period_can_add_update = serializers.ReadOnlyField(source='period.can_save_update')
103     files = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)
104     photos = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)
105     file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')
106     photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')
107     audit_trail = serializers.SerializerMethodField()
108 
109     class Meta:
110         model = IndicatorPeriodData
111         fields = '__all__'
112         read_only_fields = ['user']
113 
114     def get_audit_trail(self, obj):
115         entries = LogEntry.objects.filter(
116             content_type=ContentType.objects.get_for_model(IndicatorPeriodData),
117             object_id=obj.id,
118             change_message__contains='"audit_trail": true'
119         )
120         return [
121             {
122                 'user': {'id': entry.user.id, 'email': entry.user.email, 'first_name': entry.user.first_name, 'last_name': entry.user.last_name},
123                 'action_time': entry.action_time,
124                 'action_flag': 'CHANGE' if entry.action_flag == CHANGE else 'DELETION' if entry.action_flag == DELETION else 'ADDITION',
125                 'data': json.loads(entry.change_message)['data'],
126             }
127             for entry in entries
128         ]
129 
130     def create(self, validated_data):
131         self._validate_disaggregations(
132             self._disaggregations_data,
133             value=ensure_decimal(validated_data.get('value', 0)),
134             numerator=ensure_decimal(validated_data.get('numerator', None)),
135             denominator=ensure_decimal(validated_data.get('denominator', None))
136         )
137         """Over-ridden to handle nested writes."""
138         files = validated_data.pop('files', [])
139         photos = validated_data.pop('photos', [])
140         comments = validated_data.pop('comments', [])
141         update = super(IndicatorPeriodDataFrameworkSerializer, self).create(validated_data)
142         for disaggregation in self._disaggregations_data:
143             disaggregation['update'] = update.id
144             if 'type_id' in disaggregation and 'dimension_value' not in disaggregation:
145                 disaggregation['dimension_value'] = disaggregation['type_id']
146             serializer = DisaggregationSerializer(data=disaggregation)
147             serializer.is_valid(raise_exception=True)
148             serializer.create(serializer.validated_data)
149         for file in files:
150             IndicatorPeriodDataFile.objects.create(update=update, file=file)
151         for photo in photos:
152             IndicatorPeriodDataPhoto.objects.create(update=update, photo=photo)
153         for comment in comments:
154             IndicatorPeriodDataComment.objects.create(data=update, user=update.user, comment=comment['comment'])
155 
156         return update
157 
158     def update(self, instance, validated_data):
159         self._validate_disaggregations(
160             self._disaggregations_data,
161             value=ensure_decimal(validated_data.get('value', instance.value)),
162             numerator=ensure_decimal(validated_data.get('numerator', instance.numerator)),
163             denominator=ensure_decimal(validated_data.get('denominator', instance.denominator)),
164             update=instance
165         )
166         """Over-ridden to handle nested updates."""
167         files = validated_data.pop('files', [])
168         photos = validated_data.pop('photos', [])
169         comments = validated_data.pop('comments', [])
170         super(IndicatorPeriodDataFrameworkSerializer, self).update(instance, validated_data)
171         for disaggregation in self._disaggregations_data:
172             disaggregation['update'] = instance.id
173             serializer = DisaggregationSerializer(data=disaggregation)
174             serializer.is_valid(raise_exception=True)
175             disaggregation_instance, _ = instance.disaggregations.get_or_create(
176                 update=instance,
177                 dimension_value=serializer.validated_data['dimension_value'],
178             )
179             serializer.update(disaggregation_instance, serializer.validated_data)
180         for file in files:
181             IndicatorPeriodDataFile.objects.create(update=instance, file=file)
182         for photo in photos:
183             IndicatorPeriodDataPhoto.objects.create(update=instance, photo=photo)
184         for comment in comments:
185             comment_id = int(comment.get('id', 0))
186             comment_txt = str(comment.get('comment', ''))
187             if not comment_id:
188                 IndicatorPeriodDataComment.objects.create(data=instance, user=instance.user, comment=comment['comment'])
189             else:
190                 comment_obj = IndicatorPeriodDataComment.objects.get(id=comment_id)
191                 if not comment_txt:
192                     comment_obj.delete()
193                 else:
194                     comment_obj.comment = comment_txt
195                     comment_obj.save()
196 
197         return instance._meta.model.objects.select_related(
198             'period',
199             'user',
200             'approved_by',
201         ).prefetch_related(
202             'comments',
203             'disaggregations',
204         ).get(id=instance.id)
205 
206     def _validate_disaggregations(self, disaggregations, value, numerator=None, denominator=None, update=None):
207         adjustments = {}
208         for disaggregation in disaggregations:
209             type_id = disaggregation.get('type_id', disaggregation.get('dimension_value', None))
210             if type_id is None:
211                 continue
212             if denominator is not None:
213                 disaggregation_denominator = ensure_decimal(disaggregation.get('denominator', 0))
214                 if disaggregation_denominator > denominator:
215                     raise serializers.ValidationError("disaggregations denominator should not exceed update denominator")
216             category = IndicatorDimensionValue.objects.get(pk=type_id).name
217             if category.id not in adjustments:
218                 adjustments[category.id] = {'values': 0, 'numerators': 0, 'type_ids': []}
219             adjustments[category.id]['values'] += ensure_decimal(disaggregation.get('value', 0))
220             adjustments[category.id]['numerators'] += ensure_decimal(disaggregation.get('numerator', 0))
221             adjustments[category.id]['type_ids'].append(type_id)
222         for key, adjustment in adjustments.items():
223             unmodifieds = Disaggregation.objects.filter(update=update, dimension_value__name=key)\
224                 .exclude(dimension_value__in=adjustment['type_ids'])\
225                 .aggregate(values=Sum('value'))
226             total = adjustment['values'] + ensure_decimal(unmodifieds['values'])
227             if numerator is not None and adjustment['numerators'] > numerator:
228                 raise serializers.ValidationError("The disaggregation numerator should not exceed update numerator")
229             if total > value:
230                 raise serializers.ValidationError("The accumulated disaggregations value should not exceed update value")
231 
232     def is_valid(self, raise_exception=False):
233         # HACK to allow nested posting...
234         self._disaggregations_data = self.initial_data.pop('disaggregations', [])
235         super(IndicatorPeriodDataFrameworkSerializer, self).is_valid(raise_exception=raise_exception)
236
```
--- 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/serializers/indicator_period_data.py b/akvo/rest/serializers/indicator_period_data.py
--- a/akvo/rest/serializers/indicator_period_data.py
+++ b/akvo/rest/serializers/indicator_period_data.py
@@ -84,7 +84,7 @@
         model = IndicatorPeriodData
         fields = (
             'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',
-            'disaggregations', 'narrative', 'photo_url', 'file_url', 'created_at', 'last_modified_at',
+            'disaggregations', 'narrative', 'score_indices', 'photo_url', 'file_url', 'created_at', 'last_modified_at',
             'file_set', 'photo_set', 'review_note', 'comments',
         )

{"golden_diff": "diff --git a/akvo/rest/serializers/indicator_period_data.py b/akvo/rest/serializers/indicator_period_data.py\n--- a/akvo/rest/serializers/indicator_period_data.py\n+++ b/akvo/rest/serializers/indicator_period_data.py\n@@ -84,7 +84,7 @@\n         model = IndicatorPeriodData\n         fields = (\n             'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',\n-            'disaggregations', 'narrative', 'photo_url', 'file_url', 'created_at', 'last_modified_at',\n+            'disaggregations', 'narrative', 'score_indices', 'photo_url', 'file_url', 'created_at', 'last_modified_at',\n             'file_set', 'photo_set', 'review_note', 'comments',\n         )\n", "issue": "Fallback to value in \"Report Admin > Pending Approval\" list view\n# Current situation\n\nSome updates look like they have no value and others do.\n\n![Image](https://user-images.githubusercontent.com/91939214/211502642-df01abac-d774-4fad-a167-a0940796a42f.png)\n\nThe reason is that some have descriptions and others don't.\n\n**Examples:**\n\nWith description\n![Image](https://user-images.githubusercontent.com/91939214/211502702-3a0a1f62-4de4-4dc0-9212-f4d061bad2f2.png)\n\nWithout description\n![Image](https://user-images.githubusercontent.com/91939214/211502718-d95d8e2a-60f0-4ff2-b348-909fc9411559.png)\n\n# Improvement\n\nFallback to the actual value when no description has been provided.\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 >.\nimport json\n\nfrom rest_framework import serializers\nfrom django.db.models import Sum\nfrom django.contrib.admin.models import LogEntry, CHANGE, DELETION\nfrom django.contrib.contenttypes.models import ContentType\n\nfrom akvo.rest.serializers.disaggregation import DisaggregationSerializer, DisaggregationReadOnlySerializer\nfrom akvo.rest.serializers.rsr_serializer import BaseRSRSerializer\nfrom akvo.rest.serializers.user import UserDetailsSerializer, UserRawSerializer\nfrom akvo.rsr.models import (\n    IndicatorPeriod, IndicatorPeriodData, IndicatorPeriodDataComment, IndicatorPeriodDataFile, IndicatorPeriodDataPhoto,\n    IndicatorDimensionValue, Disaggregation\n)\nfrom akvo.utils import ensure_decimal\n\n\nclass IndicatorPeriodDataCommentSerializer(BaseRSRSerializer):\n\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n\n    class Meta:\n        model = IndicatorPeriodDataComment\n        fields = '__all__'\n        read_only_fields = ['user']\n\n\nclass IndicatorPeriodDataCommentNestedSerializer(BaseRSRSerializer):\n    id = serializers.IntegerField(required=False)\n\n    class Meta:\n        model = IndicatorPeriodDataComment\n        fields = '__all__'\n        read_only_fields = ('id', 'data', 'user')\n\n\nclass IndicatorPeriodDataFileSerializer(BaseRSRSerializer):\n    class Meta:\n        model = IndicatorPeriodDataFile\n        fields = '__all__'\n\n\nclass IndicatorPeriodDataPhotoSerializer(BaseRSRSerializer):\n    class Meta:\n        model = IndicatorPeriodDataPhoto\n        fields = '__all__'\n\n\nclass IndicatorPeriodDataSerializer(BaseRSRSerializer):\n\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n    approver_details = UserDetailsSerializer(read_only=True, source='approved_by')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\n\n\nclass IndicatorPeriodDataLiteSerializer(BaseRSRSerializer):\n\n    user_details = UserRawSerializer(required=False, source='user')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n    disaggregations = DisaggregationReadOnlySerializer(many=True, required=False)\n    value = serializers.SerializerMethodField()\n    file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')\n    photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')\n    comments = IndicatorPeriodDataCommentSerializer(read_only=True, many=True, required=False)\n\n    def get_value(self, obj):\n        return ensure_decimal(obj.value)\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = (\n            'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',\n            'disaggregations', 'narrative', 'photo_url', 'file_url', 'created_at', 'last_modified_at',\n            'file_set', 'photo_set', 'review_note', 'comments',\n        )\n\n\nclass IndicatorPeriodDataFrameworkSerializer(BaseRSRSerializer):\n\n    period = serializers.PrimaryKeyRelatedField(queryset=IndicatorPeriod.objects.all())\n    comments = IndicatorPeriodDataCommentNestedSerializer(many=True, required=False)\n    disaggregations = DisaggregationSerializer(many=True, required=False)\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n    approver_details = UserDetailsSerializer(read_only=True, source='approved_by')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n    period_can_add_update = serializers.ReadOnlyField(source='period.can_save_update')\n    files = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)\n    photos = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)\n    file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')\n    photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')\n    audit_trail = serializers.SerializerMethodField()\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\n\n    def get_audit_trail(self, obj):\n        entries = LogEntry.objects.filter(\n            content_type=ContentType.objects.get_for_model(IndicatorPeriodData),\n            object_id=obj.id,\n            change_message__contains='\"audit_trail\": true'\n        )\n        return [\n            {\n                'user': {'id': entry.user.id, 'email': entry.user.email, 'first_name': entry.user.first_name, 'last_name': entry.user.last_name},\n                'action_time': entry.action_time,\n                'action_flag': 'CHANGE' if entry.action_flag == CHANGE else 'DELETION' if entry.action_flag == DELETION else 'ADDITION',\n                'data': json.loads(entry.change_message)['data'],\n            }\n            for entry in entries\n        ]\n\n    def create(self, validated_data):\n        self._validate_disaggregations(\n            self._disaggregations_data,\n            value=ensure_decimal(validated_data.get('value', 0)),\n            numerator=ensure_decimal(validated_data.get('numerator', None)),\n            denominator=ensure_decimal(validated_data.get('denominator', None))\n        )\n        \"\"\"Over-ridden to handle nested writes.\"\"\"\n        files = validated_data.pop('files', [])\n        photos = validated_data.pop('photos', [])\n        comments = validated_data.pop('comments', [])\n        update = super(IndicatorPeriodDataFrameworkSerializer, self).create(validated_data)\n        for disaggregation in self._disaggregations_data:\n            disaggregation['update'] = update.id\n            if 'type_id' in disaggregation and 'dimension_value' not in disaggregation:\n                disaggregation['dimension_value'] = disaggregation['type_id']\n            serializer = DisaggregationSerializer(data=disaggregation)\n            serializer.is_valid(raise_exception=True)\n            serializer.create(serializer.validated_data)\n        for file in files:\n            IndicatorPeriodDataFile.objects.create(update=update, file=file)\n        for photo in photos:\n            IndicatorPeriodDataPhoto.objects.create(update=update, photo=photo)\n        for comment in comments:\n            IndicatorPeriodDataComment.objects.create(data=update, user=update.user, comment=comment['comment'])\n\n        return update\n\n    def update(self, instance, validated_data):\n        self._validate_disaggregations(\n            self._disaggregations_data,\n            value=ensure_decimal(validated_data.get('value', instance.value)),\n            numerator=ensure_decimal(validated_data.get('numerator', instance.numerator)),\n            denominator=ensure_decimal(validated_data.get('denominator', instance.denominator)),\n            update=instance\n        )\n        \"\"\"Over-ridden to handle nested updates.\"\"\"\n        files = validated_data.pop('files', [])\n        photos = validated_data.pop('photos', [])\n        comments = validated_data.pop('comments', [])\n        super(IndicatorPeriodDataFrameworkSerializer, self).update(instance, validated_data)\n        for disaggregation in self._disaggregations_data:\n            disaggregation['update'] = instance.id\n            serializer = DisaggregationSerializer(data=disaggregation)\n            serializer.is_valid(raise_exception=True)\n            disaggregation_instance, _ = instance.disaggregations.get_or_create(\n                update=instance,\n                dimension_value=serializer.validated_data['dimension_value'],\n            )\n            serializer.update(disaggregation_instance, serializer.validated_data)\n        for file in files:\n            IndicatorPeriodDataFile.objects.create(update=instance, file=file)\n        for photo in photos:\n            IndicatorPeriodDataPhoto.objects.create(update=instance, photo=photo)\n        for comment in comments:\n            comment_id = int(comment.get('id', 0))\n            comment_txt = str(comment.get('comment', ''))\n            if not comment_id:\n                IndicatorPeriodDataComment.objects.create(data=instance, user=instance.user, comment=comment['comment'])\n            else:\n                comment_obj = IndicatorPeriodDataComment.objects.get(id=comment_id)\n                if not comment_txt:\n                    comment_obj.delete()\n                else:\n                    comment_obj.comment = comment_txt\n                    comment_obj.save()\n\n        return instance._meta.model.objects.select_related(\n            'period',\n            'user',\n            'approved_by',\n        ).prefetch_related(\n            'comments',\n            'disaggregations',\n        ).get(id=instance.id)\n\n    def _validate_disaggregations(self, disaggregations, value, numerator=None, denominator=None, update=None):\n        adjustments = {}\n        for disaggregation in disaggregations:\n            type_id = disaggregation.get('type_id', disaggregation.get('dimension_value', None))\n            if type_id is None:\n                continue\n            if denominator is not None:\n                disaggregation_denominator = ensure_decimal(disaggregation.get('denominator', 0))\n                if disaggregation_denominator > denominator:\n                    raise serializers.ValidationError(\"disaggregations denominator should not exceed update denominator\")\n            category = IndicatorDimensionValue.objects.get(pk=type_id).name\n            if category.id not in adjustments:\n                adjustments[category.id] = {'values': 0, 'numerators': 0, 'type_ids': []}\n            adjustments[category.id]['values'] += ensure_decimal(disaggregation.get('value', 0))\n            adjustments[category.id]['numerators'] += ensure_decimal(disaggregation.get('numerator', 0))\n            adjustments[category.id]['type_ids'].append(type_id)\n        for key, adjustment in adjustments.items():\n            unmodifieds = Disaggregation.objects.filter(update=update, dimension_value__name=key)\\\n                .exclude(dimension_value__in=adjustment['type_ids'])\\\n                .aggregate(values=Sum('value'))\n            total = adjustment['values'] + ensure_decimal(unmodifieds['values'])\n            if numerator is not None and adjustment['numerators'] > numerator:\n                raise serializers.ValidationError(\"The disaggregation numerator should not exceed update numerator\")\n            if total > value:\n                raise serializers.ValidationError(\"The accumulated disaggregations value should not exceed update value\")\n\n    def is_valid(self, raise_exception=False):\n        # HACK to allow nested posting...\n        self._disaggregations_data = self.initial_data.pop('disaggregations', [])\n        super(IndicatorPeriodDataFrameworkSerializer, self).is_valid(raise_exception=raise_exception)\n", "path": "akvo/rest/serializers/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 >.\nimport json\n\nfrom rest_framework import serializers\nfrom django.db.models import Sum\nfrom django.contrib.admin.models import LogEntry, CHANGE, DELETION\nfrom django.contrib.contenttypes.models import ContentType\n\nfrom akvo.rest.serializers.disaggregation import DisaggregationSerializer, DisaggregationReadOnlySerializer\nfrom akvo.rest.serializers.rsr_serializer import BaseRSRSerializer\nfrom akvo.rest.serializers.user import UserDetailsSerializer, UserRawSerializer\nfrom akvo.rsr.models import (\n    IndicatorPeriod, IndicatorPeriodData, IndicatorPeriodDataComment, IndicatorPeriodDataFile, IndicatorPeriodDataPhoto,\n    IndicatorDimensionValue, Disaggregation\n)\nfrom akvo.utils import ensure_decimal\n\n\nclass IndicatorPeriodDataCommentSerializer(BaseRSRSerializer):\n\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n\n    class Meta:\n        model = IndicatorPeriodDataComment\n        fields = '__all__'\n        read_only_fields = ['user']\n\n\nclass IndicatorPeriodDataCommentNestedSerializer(BaseRSRSerializer):\n    id = serializers.IntegerField(required=False)\n\n    class Meta:\n        model = IndicatorPeriodDataComment\n        fields = '__all__'\n        read_only_fields = ('id', 'data', 'user')\n\n\nclass IndicatorPeriodDataFileSerializer(BaseRSRSerializer):\n    class Meta:\n        model = IndicatorPeriodDataFile\n        fields = '__all__'\n\n\nclass IndicatorPeriodDataPhotoSerializer(BaseRSRSerializer):\n    class Meta:\n        model = IndicatorPeriodDataPhoto\n        fields = '__all__'\n\n\nclass IndicatorPeriodDataSerializer(BaseRSRSerializer):\n\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n    approver_details = UserDetailsSerializer(read_only=True, source='approved_by')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\n\n\nclass IndicatorPeriodDataLiteSerializer(BaseRSRSerializer):\n\n    user_details = UserRawSerializer(required=False, source='user')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n    disaggregations = DisaggregationReadOnlySerializer(many=True, required=False)\n    value = serializers.SerializerMethodField()\n    file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')\n    photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')\n    comments = IndicatorPeriodDataCommentSerializer(read_only=True, many=True, required=False)\n\n    def get_value(self, obj):\n        return ensure_decimal(obj.value)\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = (\n            'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',\n            'disaggregations', 'narrative', 'score_indices', 'photo_url', 'file_url', 'created_at', 'last_modified_at',\n            'file_set', 'photo_set', 'review_note', 'comments',\n        )\n\n\nclass IndicatorPeriodDataFrameworkSerializer(BaseRSRSerializer):\n\n    period = serializers.PrimaryKeyRelatedField(queryset=IndicatorPeriod.objects.all())\n    comments = IndicatorPeriodDataCommentNestedSerializer(many=True, required=False)\n    disaggregations = DisaggregationSerializer(many=True, required=False)\n    user_details = UserDetailsSerializer(read_only=True, source='user')\n    approver_details = UserDetailsSerializer(read_only=True, source='approved_by')\n    status_display = serializers.ReadOnlyField()\n    photo_url = serializers.ReadOnlyField()\n    file_url = serializers.ReadOnlyField()\n    period_can_add_update = serializers.ReadOnlyField(source='period.can_save_update')\n    files = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)\n    photos = serializers.ListField(child=serializers.FileField(), required=False, write_only=True)\n    file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')\n    photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')\n    audit_trail = serializers.SerializerMethodField()\n\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\n\n    def get_audit_trail(self, obj):\n        entries = LogEntry.objects.filter(\n            content_type=ContentType.objects.get_for_model(IndicatorPeriodData),\n            object_id=obj.id,\n            change_message__contains='\"audit_trail\": true'\n        )\n        return [\n            {\n                'user': {'id': entry.user.id, 'email': entry.user.email, 'first_name': entry.user.first_name, 'last_name': entry.user.last_name},\n                'action_time': entry.action_time,\n                'action_flag': 'CHANGE' if entry.action_flag == CHANGE else 'DELETION' if entry.action_flag == DELETION else 'ADDITION',\n                'data': json.loads(entry.change_message)['data'],\n            }\n            for entry in entries\n        ]\n\n    def create(self, validated_data):\n        self._validate_disaggregations(\n            self._disaggregations_data,\n            value=ensure_decimal(validated_data.get('value', 0)),\n            numerator=ensure_decimal(validated_data.get('numerator', None)),\n            denominator=ensure_decimal(validated_data.get('denominator', None))\n        )\n        \"\"\"Over-ridden to handle nested writes.\"\"\"\n        files = validated_data.pop('files', [])\n        photos = validated_data.pop('photos', [])\n        comments = validated_data.pop('comments', [])\n        update = super(IndicatorPeriodDataFrameworkSerializer, self).create(validated_data)\n        for disaggregation in self._disaggregations_data:\n            disaggregation['update'] = update.id\n            if 'type_id' in disaggregation and 'dimension_value' not in disaggregation:\n                disaggregation['dimension_value'] = disaggregation['type_id']\n            serializer = DisaggregationSerializer(data=disaggregation)\n            serializer.is_valid(raise_exception=True)\n            serializer.create(serializer.validated_data)\n        for file in files:\n            IndicatorPeriodDataFile.objects.create(update=update, file=file)\n        for photo in photos:\n            IndicatorPeriodDataPhoto.objects.create(update=update, photo=photo)\n        for comment in comments:\n            IndicatorPeriodDataComment.objects.create(data=update, user=update.user, comment=comment['comment'])\n\n        return update\n\n    def update(self, instance, validated_data):\n        self._validate_disaggregations(\n            self._disaggregations_data,\n            value=ensure_decimal(validated_data.get('value', instance.value)),\n            numerator=ensure_decimal(validated_data.get('numerator', instance.numerator)),\n            denominator=ensure_decimal(validated_data.get('denominator', instance.denominator)),\n            update=instance\n        )\n        \"\"\"Over-ridden to handle nested updates.\"\"\"\n        files = validated_data.pop('files', [])\n        photos = validated_data.pop('photos', [])\n        comments = validated_data.pop('comments', [])\n        super(IndicatorPeriodDataFrameworkSerializer, self).update(instance, validated_data)\n        for disaggregation in self._disaggregations_data:\n            disaggregation['update'] = instance.id\n            serializer = DisaggregationSerializer(data=disaggregation)\n            serializer.is_valid(raise_exception=True)\n            disaggregation_instance, _ = instance.disaggregations.get_or_create(\n                update=instance,\n                dimension_value=serializer.validated_data['dimension_value'],\n            )\n            serializer.update(disaggregation_instance, serializer.validated_data)\n        for file in files:\n            IndicatorPeriodDataFile.objects.create(update=instance, file=file)\n        for photo in photos:\n            IndicatorPeriodDataPhoto.objects.create(update=instance, photo=photo)\n        for comment in comments:\n            comment_id = int(comment.get('id', 0))\n            comment_txt = str(comment.get('comment', ''))\n            if not comment_id:\n                IndicatorPeriodDataComment.objects.create(data=instance, user=instance.user, comment=comment['comment'])\n            else:\n                comment_obj = IndicatorPeriodDataComment.objects.get(id=comment_id)\n                if not comment_txt:\n                    comment_obj.delete()\n                else:\n                    comment_obj.comment = comment_txt\n                    comment_obj.save()\n\n        return instance._meta.model.objects.select_related(\n            'period',\n            'user',\n            'approved_by',\n        ).prefetch_related(\n            'comments',\n            'disaggregations',\n        ).get(id=instance.id)\n\n    def _validate_disaggregations(self, disaggregations, value, numerator=None, denominator=None, update=None):\n        adjustments = {}\n        for disaggregation in disaggregations:\n            type_id = disaggregation.get('type_id', disaggregation.get('dimension_value', None))\n            if type_id is None:\n                continue\n            if denominator is not None:\n                disaggregation_denominator = ensure_decimal(disaggregation.get('denominator', 0))\n                if disaggregation_denominator > denominator:\n                    raise serializers.ValidationError(\"disaggregations denominator should not exceed update denominator\")\n            category = IndicatorDimensionValue.objects.get(pk=type_id).name\n            if category.id not in adjustments:\n                adjustments[category.id] = {'values': 0, 'numerators': 0, 'type_ids': []}\n            adjustments[category.id]['values'] += ensure_decimal(disaggregation.get('value', 0))\n            adjustments[category.id]['numerators'] += ensure_decimal(disaggregation.get('numerator', 0))\n            adjustments[category.id]['type_ids'].append(type_id)\n        for key, adjustment in adjustments.items():\n            unmodifieds = Disaggregation.objects.filter(update=update, dimension_value__name=key)\\\n                .exclude(dimension_value__in=adjustment['type_ids'])\\\n                .aggregate(values=Sum('value'))\n            total = adjustment['values'] + ensure_decimal(unmodifieds['values'])\n            if numerator is not None and adjustment['numerators'] > numerator:\n                raise serializers.ValidationError(\"The disaggregation numerator should not exceed update numerator\")\n            if total > value:\n                raise serializers.ValidationError(\"The accumulated disaggregations value should not exceed update value\")\n\n    def is_valid(self, raise_exception=False):\n        # HACK to allow nested posting...\n        self._disaggregations_data = self.initial_data.pop('disaggregations', [])\n        super(IndicatorPeriodDataFrameworkSerializer, self).is_valid(raise_exception=raise_exception)\n", "path": "akvo/rest/serializers/indicator_period_data.py"}]}