We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
enumerators should be able to see "pending/unapproved" indicator updates from other enumerators.
When multiple enumerators are collaborating on a single indicator, they need to be able to see the updates from other enumerators. 

--- 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/rsr/models/result/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 from decimal import Decimal, InvalidOperation
8 
9 from django.conf import settings
10 from django.contrib.postgres.fields import ArrayField
11 from django.core.exceptions import ValidationError
12 from django.db import models
13 from django.db.models.signals import post_save
14 from django.dispatch import receiver
15 from django.utils.translation import ugettext_lazy as _
16 from sorl.thumbnail.fields import ImageField
17 
18 from .utils import (calculate_percentage, file_path, image_path,
19                     MultipleUpdateError, PERCENTAGE_MEASURE, QUALITATIVE,
20                     QUANTITATIVE)
21 from akvo.rsr.fields import ValidXMLCharField, ValidXMLTextField
22 from akvo.rsr.mixins import TimestampsMixin, IndicatorUpdateMixin
23 from akvo.utils import rsr_image_path
24 
25 
26 class IndicatorPeriodData(TimestampsMixin, IndicatorUpdateMixin, models.Model):
27     """
28     Model for adding data to an indicator period.
29     """
30 
31     project_relation = 'results__indicators__periods__data__in'
32 
33     STATUS_DRAFT = str(_('draft'))
34     STATUS_PENDING = str(_('pending approval'))
35     STATUS_REVISION = str(_('return for revision'))
36     STATUS_APPROVED = str(_('approved'))
37 
38     STATUS_DRAFT_CODE = 'D'
39     STATUS_PENDING_CODE = 'P'
40     STATUS_REVISION_CODE = 'R'
41     STATUS_APPROVED_CODE = 'A'
42 
43     STATUS_CODES_LIST = [STATUS_DRAFT_CODE, STATUS_PENDING_CODE,
44                          STATUS_REVISION_CODE, STATUS_APPROVED_CODE]
45     STATUSES_LABELS_LIST = [STATUS_DRAFT, STATUS_PENDING, STATUS_REVISION,
46                             STATUS_APPROVED]
47     STATUSES = list(zip(STATUS_CODES_LIST, STATUSES_LABELS_LIST))
48 
49     UPDATE_METHODS = (
50         ('W', _('web')),
51         ('M', _('mobile')),
52     )
53 
54     period = models.ForeignKey('IndicatorPeriod', verbose_name=_('indicator period'),
55                                related_name='data', on_delete=models.PROTECT)
56     # TODO: rename to created_by when old results framework page is no longer in use
57     user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), db_index=True,
58                              related_name='created_period_updates')
59     approved_by = models.ForeignKey(
60         settings.AUTH_USER_MODEL, verbose_name=_('approved by'), db_index=True,
61         related_name='approved_period_updates', blank=True, null=True,
62     )
63     narrative = ValidXMLTextField(_('qualitative indicator narrative'), blank=True)
64     score_index = models.SmallIntegerField(_('score index'), null=True, blank=True)
65     score_indices = ArrayField(models.SmallIntegerField(), default=[])
66     period_actual_value = ValidXMLCharField(_('period actual value'), max_length=50, default='')
67     status = ValidXMLCharField(_('status'), max_length=1, choices=STATUSES, db_index=True,
68                                default=STATUS_DRAFT_CODE)
69     text = ValidXMLTextField(_('text'), blank=True)
70     review_note = ValidXMLTextField(_('text'), blank=True)
71     photo = ImageField(_('photo'), blank=True, upload_to=image_path, max_length=255)
72     file = models.FileField(_('file'), blank=True, upload_to=file_path, max_length=255)
73     update_method = ValidXMLCharField(_('update method'), blank=True, max_length=1,
74                                       choices=UPDATE_METHODS, db_index=True, default='W')
75 
76     class Meta:
77         app_label = 'rsr'
78         verbose_name = _('indicator period data')
79         verbose_name_plural = _('indicator period data')
80         ordering = ('-id', )
81 
82     def save(self, recalculate=True, *args, **kwargs):
83         # Allow only a single update for percentage measure indicators
84         if not self.period.can_save_update(self.id):
85             raise MultipleUpdateError('Cannot create multiple updates with percentages')
86 
87         if (
88                 self.period.indicator.measure == PERCENTAGE_MEASURE
89                 and self.numerator is not None
90                 and self.denominator not in {0, '0', None}
91         ):
92             self.value = calculate_percentage(self.numerator, self.denominator)
93 
94         super(IndicatorPeriodData, self).save(*args, **kwargs)
95 
96         # In case the status is approved, recalculate the period
97         if recalculate and self.status == self.STATUS_APPROVED_CODE:
98             # FIXME: Should we call this even when status is not approved?
99             self.period.recalculate_period()
100             self.period.update_actual_comment()
101         # Update score even when the update is not approved, yet. It handles the
102         # case where an approved update is returned for revision, etc.
103         self.period.update_score()
104 
105     def delete(self, *args, **kwargs):
106         old_status = self.status
107 
108         super(IndicatorPeriodData, self).delete(*args, **kwargs)
109 
110         # In case the status was approved, recalculate the period
111         if old_status == self.STATUS_APPROVED_CODE:
112             self.period.recalculate_period()
113             self.period.update_actual_comment()
114             self.period.update_score()
115 
116     def clean(self):
117         """
118         Perform several checks before we can actually save the update data.
119         """
120         validation_errors = {}
121 
122         project = self.period.indicator.result.project
123 
124         # Don't allow a data update to an unpublished project
125         if not project.is_published():
126             validation_errors['period'] = str(_('Indicator period must be part of a published '
127                                                 'project to add data to it'))
128             raise ValidationError(validation_errors)
129 
130         # Don't allow a data update to a non-Impact project
131         if not project.is_impact_project:
132             validation_errors['period'] = str(_('Indicator period must be part of an RSR '
133                                                 'Impact project to add data to it'))
134             raise ValidationError(validation_errors)
135 
136         # Don't allow a data update to a locked period
137         if self.period.locked:
138             validation_errors['period'] = str(_('Indicator period must be unlocked to add '
139                                                 'data to it'))
140             raise ValidationError(validation_errors)
141 
142         # Don't allow a data update to an aggregated parent period with 'percentage' as measurement
143         if self.period.indicator.children_aggregate_percentage:
144             validation_errors['period'] = str(
145                 _('Indicator period has an average aggregate of the child projects. Disable '
146                   'aggregations to add data to it'))
147             raise ValidationError(validation_errors)
148 
149         if self.pk:
150             orig = IndicatorPeriodData.objects.get(pk=self.pk)
151 
152             # Don't allow for the indicator period to change
153             if orig.period != self.period:
154                 validation_errors['period'] = str(_('Not allowed to change indicator period '
155                                                     'in a data update'))
156 
157         if self.period.indicator.type == QUANTITATIVE:
158             if self.narrative is not None:
159                 validation_errors['period'] = str(
160                     _('Narrative field should be empty in quantitative indicators'))
161             if self.value is not None:
162                 try:
163                     self.value = Decimal(self.value)
164                 except Exception:
165                     validation_errors['period'] = str(
166                         _('Only numeric values are allowed in quantitative indicators'))
167 
168         if self.period.indicator.type == QUALITATIVE:
169             if self.value is not None:
170                 validation_errors['period'] = str(
171                     _('Value field should be empty in qualitative indicators'))
172 
173         if validation_errors:
174             raise ValidationError(validation_errors)
175 
176     @property
177     def status_display(self):
178         """
179         Returns the display of the status.
180         """
181         try:
182             return dict(self.STATUSES)[self.status].capitalize()
183         except KeyError:
184             return ''
185 
186     @property
187     def photo_url(self):
188         """
189         Returns the full URL of the photo.
190         """
191         return self.photo.url if self.photo else ''
192 
193     @property
194     def file_url(self):
195         """
196         Returns the full URL of the file.
197         """
198         return self.file.url if self.file else ''
199 
200     def update_new_value(self):
201         """Returns a string with the new value."""
202         try:
203             add_up = Decimal(self.value) + Decimal(self.period_actual_value)
204             relative = '+' + str(self.value) if self.value >= 0 else str(self.value)
205             return "{} ({})".format(str(add_up), relative)
206         except (InvalidOperation, TypeError):
207             return self.value
208 
209     @classmethod
210     def get_user_viewable_updates(cls, queryset, user):
211         approved_updates = queryset.filter(status=cls.STATUS_APPROVED_CODE)
212 
213         if user.is_anonymous():
214             f_queryset = approved_updates
215 
216         elif user.is_admin or user.is_superuser:
217             f_queryset = queryset
218 
219         else:
220             own_updates = queryset.filter(user=user)
221             non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)
222             filter_ = user.get_permission_filter(
223                 'rsr.view_indicatorperioddata',
224                 'period__indicator__result__project__'
225             )
226 
227             from akvo.rsr.models import Project
228             projects = Project.objects\
229                               .filter(results__indicators__periods__data__in=queryset)\
230                               .distinct()
231 
232             project = projects.first()
233             if projects.count() == 1 and user.has_perm('rsr.view_project', project) and project.in_nuffic_hierarchy():
234                 others_updates = non_draft_updates
235             else:
236                 others_updates = non_draft_updates.filter(filter_)
237 
238             f_queryset = (
239                 approved_updates
240                 | own_updates
241                 | others_updates
242             )
243 
244         return f_queryset.distinct()
245 
246 
247 def update_image_path(instance, file_name):
248     path = 'db/indicator_period_data/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.update.pk
249     return rsr_image_path(instance, file_name, path)
250 
251 
252 class IndicatorPeriodDataPhoto(models.Model):
253     update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)
254     photo = ImageField(_('photo'), upload_to=update_image_path, max_length=255)
255 
256     class Meta:
257         app_label = 'rsr'
258 
259 
260 def update_file_path(instance, file_name):
261     path = 'db/indicator_period_data/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.update.pk
262     return rsr_image_path(instance, file_name, path)
263 
264 
265 class IndicatorPeriodDataFile(models.Model):
266     update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)
267     file = models.FileField(_('file'), upload_to=update_file_path, max_length=255)
268 
269     class Meta:
270         app_label = 'rsr'
271 
272 
273 @receiver(post_save, sender=IndicatorPeriodData)
274 def set_qualitative_narrative(sender, **kwargs):
275     """Update the narrative field of a qualitative indicator on updates."""
276 
277     # Disable signal handler when loading fixtures
278     if kwargs.get('raw', False):
279         return
280 
281     update = kwargs['instance']
282     if update.status != IndicatorPeriodData.STATUS_APPROVED_CODE:
283         return
284 
285     if update.period.indicator.type != QUALITATIVE:
286         return
287 
288     # Current update is the latest update?
289     if update.period.approved_updates.last().id != update.id:
290         return
291 
292     update.period.narrative = update.narrative
293     update.period.save()
294 
295 
296 @receiver(post_save, sender=IndicatorPeriodData)
297 def _send_return_for_revision_email(sender, **kwargs):
298     """Send email to assigned enumerator when indicator is returned for revision."""
299 
300     # Disable signal handler when loading fixtures
301     if kwargs.get('raw', False):
302         return
303 
304     update = kwargs['instance']
305     if update.status != IndicatorPeriodData.STATUS_REVISION_CODE:
306         return
307 
308     from akvo.rest.views.project_enumerators import send_return_for_revision_email
309     send_return_for_revision_email(update)
310
```
--- 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/rsr/models/result/indicator_period_data.py b/akvo/rsr/models/result/indicator_period_data.py
--- a/akvo/rsr/models/result/indicator_period_data.py
+++ b/akvo/rsr/models/result/indicator_period_data.py
@@ -217,29 +217,29 @@
             f_queryset = queryset
 
         else:
-            own_updates = queryset.filter(user=user)
-            non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)
-            filter_ = user.get_permission_filter(
-                'rsr.view_indicatorperioddata',
-                'period__indicator__result__project__'
-            )
-
             from akvo.rsr.models import Project
             projects = Project.objects\
                               .filter(results__indicators__periods__data__in=queryset)\
                               .distinct()
+            project = projects.first() if projects.count() == 1 else None
+
+            # Allow Nuffic users to see all updates, irrespective of what state they are in
+            if project is not None and project.in_nuffic_hierarchy() and user.has_perm('rsr.view_project', project):
+                f_queryset = queryset
 
-            project = projects.first()
-            if projects.count() == 1 and user.has_perm('rsr.view_project', project) and project.in_nuffic_hierarchy():
-                others_updates = non_draft_updates
             else:
+                own_updates = queryset.filter(user=user)
+                non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)
+                filter_ = user.get_permission_filter(
+                    'rsr.view_indicatorperioddata',
+                    'period__indicator__result__project__'
+                )
                 others_updates = non_draft_updates.filter(filter_)
-
-            f_queryset = (
-                approved_updates
-                | own_updates
-                | others_updates
-            )
+                f_queryset = (
+                    approved_updates
+                    | own_updates
+                    | others_updates
+                )
 
         return f_queryset.distinct()

{"golden_diff": "diff --git a/akvo/rsr/models/result/indicator_period_data.py b/akvo/rsr/models/result/indicator_period_data.py\n--- a/akvo/rsr/models/result/indicator_period_data.py\n+++ b/akvo/rsr/models/result/indicator_period_data.py\n@@ -217,29 +217,29 @@\n             f_queryset = queryset\n \n         else:\n-            own_updates = queryset.filter(user=user)\n-            non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)\n-            filter_ = user.get_permission_filter(\n-                'rsr.view_indicatorperioddata',\n-                'period__indicator__result__project__'\n-            )\n-\n             from akvo.rsr.models import Project\n             projects = Project.objects\\\n                               .filter(results__indicators__periods__data__in=queryset)\\\n                               .distinct()\n+            project = projects.first() if projects.count() == 1 else None\n+\n+            # Allow Nuffic users to see all updates, irrespective of what state they are in\n+            if project is not None and project.in_nuffic_hierarchy() and user.has_perm('rsr.view_project', project):\n+                f_queryset = queryset\n \n-            project = projects.first()\n-            if projects.count() == 1 and user.has_perm('rsr.view_project', project) and project.in_nuffic_hierarchy():\n-                others_updates = non_draft_updates\n             else:\n+                own_updates = queryset.filter(user=user)\n+                non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)\n+                filter_ = user.get_permission_filter(\n+                    'rsr.view_indicatorperioddata',\n+                    'period__indicator__result__project__'\n+                )\n                 others_updates = non_draft_updates.filter(filter_)\n-\n-            f_queryset = (\n-                approved_updates\n-                | own_updates\n-                | others_updates\n-            )\n+                f_queryset = (\n+                    approved_updates\n+                    | own_updates\n+                    | others_updates\n+                )\n \n         return f_queryset.distinct()\n", "issue": "enumerators should be able to see \"pending/unapproved\" indicator updates from other enumerators.\nWhen multiple enumerators are collaborating on a single indicator, they need to be able to see the updates from other enumerators. \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\nfrom decimal import Decimal, InvalidOperation\n\nfrom django.conf import settings\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\nfrom django.utils.translation import ugettext_lazy as _\nfrom sorl.thumbnail.fields import ImageField\n\nfrom .utils import (calculate_percentage, file_path, image_path,\n                    MultipleUpdateError, PERCENTAGE_MEASURE, QUALITATIVE,\n                    QUANTITATIVE)\nfrom akvo.rsr.fields import ValidXMLCharField, ValidXMLTextField\nfrom akvo.rsr.mixins import TimestampsMixin, IndicatorUpdateMixin\nfrom akvo.utils import rsr_image_path\n\n\nclass IndicatorPeriodData(TimestampsMixin, IndicatorUpdateMixin, models.Model):\n    \"\"\"\n    Model for adding data to an indicator period.\n    \"\"\"\n\n    project_relation = 'results__indicators__periods__data__in'\n\n    STATUS_DRAFT = str(_('draft'))\n    STATUS_PENDING = str(_('pending approval'))\n    STATUS_REVISION = str(_('return for revision'))\n    STATUS_APPROVED = str(_('approved'))\n\n    STATUS_DRAFT_CODE = 'D'\n    STATUS_PENDING_CODE = 'P'\n    STATUS_REVISION_CODE = 'R'\n    STATUS_APPROVED_CODE = 'A'\n\n    STATUS_CODES_LIST = [STATUS_DRAFT_CODE, STATUS_PENDING_CODE,\n                         STATUS_REVISION_CODE, STATUS_APPROVED_CODE]\n    STATUSES_LABELS_LIST = [STATUS_DRAFT, STATUS_PENDING, STATUS_REVISION,\n                            STATUS_APPROVED]\n    STATUSES = list(zip(STATUS_CODES_LIST, STATUSES_LABELS_LIST))\n\n    UPDATE_METHODS = (\n        ('W', _('web')),\n        ('M', _('mobile')),\n    )\n\n    period = models.ForeignKey('IndicatorPeriod', verbose_name=_('indicator period'),\n                               related_name='data', on_delete=models.PROTECT)\n    # TODO: rename to created_by when old results framework page is no longer in use\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), db_index=True,\n                             related_name='created_period_updates')\n    approved_by = models.ForeignKey(\n        settings.AUTH_USER_MODEL, verbose_name=_('approved by'), db_index=True,\n        related_name='approved_period_updates', blank=True, null=True,\n    )\n    narrative = ValidXMLTextField(_('qualitative indicator narrative'), blank=True)\n    score_index = models.SmallIntegerField(_('score index'), null=True, blank=True)\n    score_indices = ArrayField(models.SmallIntegerField(), default=[])\n    period_actual_value = ValidXMLCharField(_('period actual value'), max_length=50, default='')\n    status = ValidXMLCharField(_('status'), max_length=1, choices=STATUSES, db_index=True,\n                               default=STATUS_DRAFT_CODE)\n    text = ValidXMLTextField(_('text'), blank=True)\n    review_note = ValidXMLTextField(_('text'), blank=True)\n    photo = ImageField(_('photo'), blank=True, upload_to=image_path, max_length=255)\n    file = models.FileField(_('file'), blank=True, upload_to=file_path, max_length=255)\n    update_method = ValidXMLCharField(_('update method'), blank=True, max_length=1,\n                                      choices=UPDATE_METHODS, db_index=True, default='W')\n\n    class Meta:\n        app_label = 'rsr'\n        verbose_name = _('indicator period data')\n        verbose_name_plural = _('indicator period data')\n        ordering = ('-id', )\n\n    def save(self, recalculate=True, *args, **kwargs):\n        # Allow only a single update for percentage measure indicators\n        if not self.period.can_save_update(self.id):\n            raise MultipleUpdateError('Cannot create multiple updates with percentages')\n\n        if (\n                self.period.indicator.measure == PERCENTAGE_MEASURE\n                and self.numerator is not None\n                and self.denominator not in {0, '0', None}\n        ):\n            self.value = calculate_percentage(self.numerator, self.denominator)\n\n        super(IndicatorPeriodData, self).save(*args, **kwargs)\n\n        # In case the status is approved, recalculate the period\n        if recalculate and self.status == self.STATUS_APPROVED_CODE:\n            # FIXME: Should we call this even when status is not approved?\n            self.period.recalculate_period()\n            self.period.update_actual_comment()\n        # Update score even when the update is not approved, yet. It handles the\n        # case where an approved update is returned for revision, etc.\n        self.period.update_score()\n\n    def delete(self, *args, **kwargs):\n        old_status = self.status\n\n        super(IndicatorPeriodData, self).delete(*args, **kwargs)\n\n        # In case the status was approved, recalculate the period\n        if old_status == self.STATUS_APPROVED_CODE:\n            self.period.recalculate_period()\n            self.period.update_actual_comment()\n            self.period.update_score()\n\n    def clean(self):\n        \"\"\"\n        Perform several checks before we can actually save the update data.\n        \"\"\"\n        validation_errors = {}\n\n        project = self.period.indicator.result.project\n\n        # Don't allow a data update to an unpublished project\n        if not project.is_published():\n            validation_errors['period'] = str(_('Indicator period must be part of a published '\n                                                'project to add data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to a non-Impact project\n        if not project.is_impact_project:\n            validation_errors['period'] = str(_('Indicator period must be part of an RSR '\n                                                'Impact project to add data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to a locked period\n        if self.period.locked:\n            validation_errors['period'] = str(_('Indicator period must be unlocked to add '\n                                                'data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to an aggregated parent period with 'percentage' as measurement\n        if self.period.indicator.children_aggregate_percentage:\n            validation_errors['period'] = str(\n                _('Indicator period has an average aggregate of the child projects. Disable '\n                  'aggregations to add data to it'))\n            raise ValidationError(validation_errors)\n\n        if self.pk:\n            orig = IndicatorPeriodData.objects.get(pk=self.pk)\n\n            # Don't allow for the indicator period to change\n            if orig.period != self.period:\n                validation_errors['period'] = str(_('Not allowed to change indicator period '\n                                                    'in a data update'))\n\n        if self.period.indicator.type == QUANTITATIVE:\n            if self.narrative is not None:\n                validation_errors['period'] = str(\n                    _('Narrative field should be empty in quantitative indicators'))\n            if self.value is not None:\n                try:\n                    self.value = Decimal(self.value)\n                except Exception:\n                    validation_errors['period'] = str(\n                        _('Only numeric values are allowed in quantitative indicators'))\n\n        if self.period.indicator.type == QUALITATIVE:\n            if self.value is not None:\n                validation_errors['period'] = str(\n                    _('Value field should be empty in qualitative indicators'))\n\n        if validation_errors:\n            raise ValidationError(validation_errors)\n\n    @property\n    def status_display(self):\n        \"\"\"\n        Returns the display of the status.\n        \"\"\"\n        try:\n            return dict(self.STATUSES)[self.status].capitalize()\n        except KeyError:\n            return ''\n\n    @property\n    def photo_url(self):\n        \"\"\"\n        Returns the full URL of the photo.\n        \"\"\"\n        return self.photo.url if self.photo else ''\n\n    @property\n    def file_url(self):\n        \"\"\"\n        Returns the full URL of the file.\n        \"\"\"\n        return self.file.url if self.file else ''\n\n    def update_new_value(self):\n        \"\"\"Returns a string with the new value.\"\"\"\n        try:\n            add_up = Decimal(self.value) + Decimal(self.period_actual_value)\n            relative = '+' + str(self.value) if self.value >= 0 else str(self.value)\n            return \"{} ({})\".format(str(add_up), relative)\n        except (InvalidOperation, TypeError):\n            return self.value\n\n    @classmethod\n    def get_user_viewable_updates(cls, queryset, user):\n        approved_updates = queryset.filter(status=cls.STATUS_APPROVED_CODE)\n\n        if user.is_anonymous():\n            f_queryset = approved_updates\n\n        elif user.is_admin or user.is_superuser:\n            f_queryset = queryset\n\n        else:\n            own_updates = queryset.filter(user=user)\n            non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)\n            filter_ = user.get_permission_filter(\n                'rsr.view_indicatorperioddata',\n                'period__indicator__result__project__'\n            )\n\n            from akvo.rsr.models import Project\n            projects = Project.objects\\\n                              .filter(results__indicators__periods__data__in=queryset)\\\n                              .distinct()\n\n            project = projects.first()\n            if projects.count() == 1 and user.has_perm('rsr.view_project', project) and project.in_nuffic_hierarchy():\n                others_updates = non_draft_updates\n            else:\n                others_updates = non_draft_updates.filter(filter_)\n\n            f_queryset = (\n                approved_updates\n                | own_updates\n                | others_updates\n            )\n\n        return f_queryset.distinct()\n\n\ndef update_image_path(instance, file_name):\n    path = 'db/indicator_period_data/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.update.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass IndicatorPeriodDataPhoto(models.Model):\n    update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)\n    photo = ImageField(_('photo'), upload_to=update_image_path, max_length=255)\n\n    class Meta:\n        app_label = 'rsr'\n\n\ndef update_file_path(instance, file_name):\n    path = 'db/indicator_period_data/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.update.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass IndicatorPeriodDataFile(models.Model):\n    update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)\n    file = models.FileField(_('file'), upload_to=update_file_path, max_length=255)\n\n    class Meta:\n        app_label = 'rsr'\n\n\n@receiver(post_save, sender=IndicatorPeriodData)\ndef set_qualitative_narrative(sender, **kwargs):\n    \"\"\"Update the narrative field of a qualitative indicator on updates.\"\"\"\n\n    # Disable signal handler when loading fixtures\n    if kwargs.get('raw', False):\n        return\n\n    update = kwargs['instance']\n    if update.status != IndicatorPeriodData.STATUS_APPROVED_CODE:\n        return\n\n    if update.period.indicator.type != QUALITATIVE:\n        return\n\n    # Current update is the latest update?\n    if update.period.approved_updates.last().id != update.id:\n        return\n\n    update.period.narrative = update.narrative\n    update.period.save()\n\n\n@receiver(post_save, sender=IndicatorPeriodData)\ndef _send_return_for_revision_email(sender, **kwargs):\n    \"\"\"Send email to assigned enumerator when indicator is returned for revision.\"\"\"\n\n    # Disable signal handler when loading fixtures\n    if kwargs.get('raw', False):\n        return\n\n    update = kwargs['instance']\n    if update.status != IndicatorPeriodData.STATUS_REVISION_CODE:\n        return\n\n    from akvo.rest.views.project_enumerators import send_return_for_revision_email\n    send_return_for_revision_email(update)\n", "path": "akvo/rsr/models/result/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\nfrom decimal import Decimal, InvalidOperation\n\nfrom django.conf import settings\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\nfrom django.utils.translation import ugettext_lazy as _\nfrom sorl.thumbnail.fields import ImageField\n\nfrom .utils import (calculate_percentage, file_path, image_path,\n                    MultipleUpdateError, PERCENTAGE_MEASURE, QUALITATIVE,\n                    QUANTITATIVE)\nfrom akvo.rsr.fields import ValidXMLCharField, ValidXMLTextField\nfrom akvo.rsr.mixins import TimestampsMixin, IndicatorUpdateMixin\nfrom akvo.utils import rsr_image_path\n\n\nclass IndicatorPeriodData(TimestampsMixin, IndicatorUpdateMixin, models.Model):\n    \"\"\"\n    Model for adding data to an indicator period.\n    \"\"\"\n\n    project_relation = 'results__indicators__periods__data__in'\n\n    STATUS_DRAFT = str(_('draft'))\n    STATUS_PENDING = str(_('pending approval'))\n    STATUS_REVISION = str(_('return for revision'))\n    STATUS_APPROVED = str(_('approved'))\n\n    STATUS_DRAFT_CODE = 'D'\n    STATUS_PENDING_CODE = 'P'\n    STATUS_REVISION_CODE = 'R'\n    STATUS_APPROVED_CODE = 'A'\n\n    STATUS_CODES_LIST = [STATUS_DRAFT_CODE, STATUS_PENDING_CODE,\n                         STATUS_REVISION_CODE, STATUS_APPROVED_CODE]\n    STATUSES_LABELS_LIST = [STATUS_DRAFT, STATUS_PENDING, STATUS_REVISION,\n                            STATUS_APPROVED]\n    STATUSES = list(zip(STATUS_CODES_LIST, STATUSES_LABELS_LIST))\n\n    UPDATE_METHODS = (\n        ('W', _('web')),\n        ('M', _('mobile')),\n    )\n\n    period = models.ForeignKey('IndicatorPeriod', verbose_name=_('indicator period'),\n                               related_name='data', on_delete=models.PROTECT)\n    # TODO: rename to created_by when old results framework page is no longer in use\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), db_index=True,\n                             related_name='created_period_updates')\n    approved_by = models.ForeignKey(\n        settings.AUTH_USER_MODEL, verbose_name=_('approved by'), db_index=True,\n        related_name='approved_period_updates', blank=True, null=True,\n    )\n    narrative = ValidXMLTextField(_('qualitative indicator narrative'), blank=True)\n    score_index = models.SmallIntegerField(_('score index'), null=True, blank=True)\n    score_indices = ArrayField(models.SmallIntegerField(), default=[])\n    period_actual_value = ValidXMLCharField(_('period actual value'), max_length=50, default='')\n    status = ValidXMLCharField(_('status'), max_length=1, choices=STATUSES, db_index=True,\n                               default=STATUS_DRAFT_CODE)\n    text = ValidXMLTextField(_('text'), blank=True)\n    review_note = ValidXMLTextField(_('text'), blank=True)\n    photo = ImageField(_('photo'), blank=True, upload_to=image_path, max_length=255)\n    file = models.FileField(_('file'), blank=True, upload_to=file_path, max_length=255)\n    update_method = ValidXMLCharField(_('update method'), blank=True, max_length=1,\n                                      choices=UPDATE_METHODS, db_index=True, default='W')\n\n    class Meta:\n        app_label = 'rsr'\n        verbose_name = _('indicator period data')\n        verbose_name_plural = _('indicator period data')\n        ordering = ('-id', )\n\n    def save(self, recalculate=True, *args, **kwargs):\n        # Allow only a single update for percentage measure indicators\n        if not self.period.can_save_update(self.id):\n            raise MultipleUpdateError('Cannot create multiple updates with percentages')\n\n        if (\n                self.period.indicator.measure == PERCENTAGE_MEASURE\n                and self.numerator is not None\n                and self.denominator not in {0, '0', None}\n        ):\n            self.value = calculate_percentage(self.numerator, self.denominator)\n\n        super(IndicatorPeriodData, self).save(*args, **kwargs)\n\n        # In case the status is approved, recalculate the period\n        if recalculate and self.status == self.STATUS_APPROVED_CODE:\n            # FIXME: Should we call this even when status is not approved?\n            self.period.recalculate_period()\n            self.period.update_actual_comment()\n        # Update score even when the update is not approved, yet. It handles the\n        # case where an approved update is returned for revision, etc.\n        self.period.update_score()\n\n    def delete(self, *args, **kwargs):\n        old_status = self.status\n\n        super(IndicatorPeriodData, self).delete(*args, **kwargs)\n\n        # In case the status was approved, recalculate the period\n        if old_status == self.STATUS_APPROVED_CODE:\n            self.period.recalculate_period()\n            self.period.update_actual_comment()\n            self.period.update_score()\n\n    def clean(self):\n        \"\"\"\n        Perform several checks before we can actually save the update data.\n        \"\"\"\n        validation_errors = {}\n\n        project = self.period.indicator.result.project\n\n        # Don't allow a data update to an unpublished project\n        if not project.is_published():\n            validation_errors['period'] = str(_('Indicator period must be part of a published '\n                                                'project to add data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to a non-Impact project\n        if not project.is_impact_project:\n            validation_errors['period'] = str(_('Indicator period must be part of an RSR '\n                                                'Impact project to add data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to a locked period\n        if self.period.locked:\n            validation_errors['period'] = str(_('Indicator period must be unlocked to add '\n                                                'data to it'))\n            raise ValidationError(validation_errors)\n\n        # Don't allow a data update to an aggregated parent period with 'percentage' as measurement\n        if self.period.indicator.children_aggregate_percentage:\n            validation_errors['period'] = str(\n                _('Indicator period has an average aggregate of the child projects. Disable '\n                  'aggregations to add data to it'))\n            raise ValidationError(validation_errors)\n\n        if self.pk:\n            orig = IndicatorPeriodData.objects.get(pk=self.pk)\n\n            # Don't allow for the indicator period to change\n            if orig.period != self.period:\n                validation_errors['period'] = str(_('Not allowed to change indicator period '\n                                                    'in a data update'))\n\n        if self.period.indicator.type == QUANTITATIVE:\n            if self.narrative is not None:\n                validation_errors['period'] = str(\n                    _('Narrative field should be empty in quantitative indicators'))\n            if self.value is not None:\n                try:\n                    self.value = Decimal(self.value)\n                except Exception:\n                    validation_errors['period'] = str(\n                        _('Only numeric values are allowed in quantitative indicators'))\n\n        if self.period.indicator.type == QUALITATIVE:\n            if self.value is not None:\n                validation_errors['period'] = str(\n                    _('Value field should be empty in qualitative indicators'))\n\n        if validation_errors:\n            raise ValidationError(validation_errors)\n\n    @property\n    def status_display(self):\n        \"\"\"\n        Returns the display of the status.\n        \"\"\"\n        try:\n            return dict(self.STATUSES)[self.status].capitalize()\n        except KeyError:\n            return ''\n\n    @property\n    def photo_url(self):\n        \"\"\"\n        Returns the full URL of the photo.\n        \"\"\"\n        return self.photo.url if self.photo else ''\n\n    @property\n    def file_url(self):\n        \"\"\"\n        Returns the full URL of the file.\n        \"\"\"\n        return self.file.url if self.file else ''\n\n    def update_new_value(self):\n        \"\"\"Returns a string with the new value.\"\"\"\n        try:\n            add_up = Decimal(self.value) + Decimal(self.period_actual_value)\n            relative = '+' + str(self.value) if self.value >= 0 else str(self.value)\n            return \"{} ({})\".format(str(add_up), relative)\n        except (InvalidOperation, TypeError):\n            return self.value\n\n    @classmethod\n    def get_user_viewable_updates(cls, queryset, user):\n        approved_updates = queryset.filter(status=cls.STATUS_APPROVED_CODE)\n\n        if user.is_anonymous():\n            f_queryset = approved_updates\n\n        elif user.is_admin or user.is_superuser:\n            f_queryset = queryset\n\n        else:\n            from akvo.rsr.models import Project\n            projects = Project.objects\\\n                              .filter(results__indicators__periods__data__in=queryset)\\\n                              .distinct()\n            project = projects.first() if projects.count() == 1 else None\n\n            # Allow Nuffic users to see all updates, irrespective of what state they are in\n            if project is not None and project.in_nuffic_hierarchy() and user.has_perm('rsr.view_project', project):\n                f_queryset = queryset\n\n            else:\n                own_updates = queryset.filter(user=user)\n                non_draft_updates = queryset.exclude(status=cls.STATUS_DRAFT_CODE)\n                filter_ = user.get_permission_filter(\n                    'rsr.view_indicatorperioddata',\n                    'period__indicator__result__project__'\n                )\n                others_updates = non_draft_updates.filter(filter_)\n                f_queryset = (\n                    approved_updates\n                    | own_updates\n                    | others_updates\n                )\n\n        return f_queryset.distinct()\n\n\ndef update_image_path(instance, file_name):\n    path = 'db/indicator_period_data/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.update.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass IndicatorPeriodDataPhoto(models.Model):\n    update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)\n    photo = ImageField(_('photo'), upload_to=update_image_path, max_length=255)\n\n    class Meta:\n        app_label = 'rsr'\n\n\ndef update_file_path(instance, file_name):\n    path = 'db/indicator_period_data/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.update.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass IndicatorPeriodDataFile(models.Model):\n    update = models.ForeignKey('IndicatorPeriodData', on_delete=models.CASCADE)\n    file = models.FileField(_('file'), upload_to=update_file_path, max_length=255)\n\n    class Meta:\n        app_label = 'rsr'\n\n\n@receiver(post_save, sender=IndicatorPeriodData)\ndef set_qualitative_narrative(sender, **kwargs):\n    \"\"\"Update the narrative field of a qualitative indicator on updates.\"\"\"\n\n    # Disable signal handler when loading fixtures\n    if kwargs.get('raw', False):\n        return\n\n    update = kwargs['instance']\n    if update.status != IndicatorPeriodData.STATUS_APPROVED_CODE:\n        return\n\n    if update.period.indicator.type != QUALITATIVE:\n        return\n\n    # Current update is the latest update?\n    if update.period.approved_updates.last().id != update.id:\n        return\n\n    update.period.narrative = update.narrative\n    update.period.save()\n\n\n@receiver(post_save, sender=IndicatorPeriodData)\ndef _send_return_for_revision_email(sender, **kwargs):\n    \"\"\"Send email to assigned enumerator when indicator is returned for revision.\"\"\"\n\n    # Disable signal handler when loading fixtures\n    if kwargs.get('raw', False):\n        return\n\n    update = kwargs['instance']\n    if update.status != IndicatorPeriodData.STATUS_REVISION_CODE:\n        return\n\n    from akvo.rest.views.project_enumerators import send_return_for_revision_email\n    send_return_for_revision_email(update)\n", "path": "akvo/rsr/models/result/indicator_period_data.py"}]}