We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
support saving nested comments at indicator_period_data_framework endpoint


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

{"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@@ -26,6 +26,15 @@\n         read_only_fields = ['user']\n \n \n+class 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+\n class IndicatorPeriodDataFileSerializer(BaseRSRSerializer):\n     class Meta:\n         model = IndicatorPeriodDataFile\n@@ -79,7 +88,7 @@\n class IndicatorPeriodDataFrameworkSerializer(BaseRSRSerializer):\n \n     period = serializers.PrimaryKeyRelatedField(queryset=IndicatorPeriod.objects.all())\n-    comments = IndicatorPeriodDataCommentSerializer(read_only=True, many=True, required=False)\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@@ -107,6 +116,7 @@\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@@ -119,6 +129,8 @@\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@@ -133,6 +145,7 @@\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@@ -147,6 +160,18 @@\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", "issue": "support saving nested comments at indicator_period_data_framework endpoint\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 >.\nfrom rest_framework import serializers\nfrom django.db.models import Sum\n\nfrom akvo.rest.serializers.disaggregation import DisaggregationSerializer, DisaggregationReadOnlySerializer\nfrom akvo.rest.serializers.rsr_serializer import BaseRSRSerializer\nfrom akvo.rest.serializers.user import UserDetailsSerializer\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 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 = UserDetailsSerializer(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', 'period_actual_value', '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 = IndicatorPeriodDataCommentSerializer(read_only=True, 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\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\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        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\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        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\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 >.\nfrom rest_framework import serializers\nfrom django.db.models import Sum\n\nfrom akvo.rest.serializers.disaggregation import DisaggregationSerializer, DisaggregationReadOnlySerializer\nfrom akvo.rest.serializers.rsr_serializer import BaseRSRSerializer\nfrom akvo.rest.serializers.user import UserDetailsSerializer\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 = UserDetailsSerializer(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', 'period_actual_value', '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\n    class Meta:\n        model = IndicatorPeriodData\n        fields = '__all__'\n        read_only_fields = ['user']\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"}]}