We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug: Locations are reset on some projects
### What were you doing?

A client has reported that on some random projects after the location has been set, the locations are reset within a few hours. The occurrence has mostly been observed within 24h of making the modification.

### What should've happened?

Locations (and other fields) should magically be reset.

### My environment

_No response_

### Additional context

This particular client has a nightly script that pulls information from an external system, of which location is a part. However, the script only modifies locations of newly created projects.

Possibilities:

 - something is deeply wrong with Django and after a project is created it doesn't say it's created (highly unlikely)
 - the script has nothing to do with it and it's
   * human error
   * a bug in the frontend e.g changes are cached and when a user gets access to the network again the changes are pushed (React?)
   * a side effect in the backend or another script

--- 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/location.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 
8 from django.db import models
9 from django.utils.translation import ugettext_lazy as _
10 
11 from ..fields import LatitudeField, LongitudeField, ValidXMLCharField
12 from akvo.codelists.models import (Country, GeographicExactness, GeographicLocationClass,
13                                    GeographicLocationReach, GeographicVocabulary, LocationType)
14 from akvo.codelists.store.default_codelists import (
15     COUNTRY, GEOGRAPHIC_EXACTNESS, GEOGRAPHIC_LOCATION_CLASS, GEOGRAPHIC_LOCATION_REACH,
16     GEOGRAPHIC_VOCABULARY, LOCATION_TYPE
17 )
18 from akvo.utils import codelist_choices, codelist_value
19 
20 
21 class BaseLocation(models.Model):
22     latitude = LatitudeField(
23         _('latitude'), null=True, blank=True, db_index=True, default=None,
24         help_text=_('Use a period to denote decimals.')
25     )
26     longitude = LongitudeField(
27         _('longitude'), null=True, blank=True, db_index=True, default=None,
28         help_text=_('Use a period to denote decimals.')
29     )
30     city = ValidXMLCharField(_('city'), blank=True, max_length=255)
31     state = ValidXMLCharField(_('state'), blank=True, max_length=255)
32     address_1 = ValidXMLCharField(_('address 1'), max_length=255, blank=True)
33     address_2 = ValidXMLCharField(_('address 2'), max_length=255, blank=True)
34     postcode = ValidXMLCharField(_('postal code'), max_length=10, blank=True)
35     country = models.ForeignKey('Country', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('country'))
36 
37     def __str__(self):
38         return '{0}, {1}, {2}{3}'.format(
39             '{0}: {1}'.format(
40                 _('Latitude'),
41                 str(self.latitude) if self.latitude else _('No latitude specified')),
42             '{0}: {1}'.format(
43                 _('Longitude'),
44                 str(self.longitude) if self.longitude else _('No longitude specified')),
45             '{0}: {1}'.format(
46                 _('Country'),
47                 str(self.country.name) if self.country else _('No country specified')),
48             ' ({0})'.format(self.name) if getattr(self, 'name', None) else ''
49         )
50 
51     def delete(self, *args, **kwargs):
52         super(BaseLocation, self).delete(*args, **kwargs)
53 
54         # If location_target has more locations, set the first as primary location
55         location_target = self.location_target
56         other_locations = location_target.locations.all()
57 
58         if other_locations.count() > 0:
59             location_target.primary_location = other_locations.first()
60         else:
61             location_target.primary_location = None
62 
63         location_target.save()
64 
65     def save(self, *args, **kwargs):
66         super(BaseLocation, self).save(*args, **kwargs)
67 
68         # Set location as primary location if it is the first location
69         location_target = self.location_target
70         if location_target.primary_location is None or location_target.primary_location.pk > self.pk:
71             location_target.primary_location = self
72             location_target.save()
73 
74     def is_valid(self):
75         if (self.latitude is None or self.longitude is None) or \
76            (self.latitude == 0 and self.longitude == 0) or \
77            (self.latitude > 90 or self.latitude < -90) or \
78            (self.longitude > 180 or self.latitude < -180):
79             return False
80         return True
81 
82     class Meta:
83         app_label = 'rsr'
84         abstract = True
85         ordering = ['id', ]
86 
87 
88 class OrganisationLocation(BaseLocation):
89     location_target = models.ForeignKey('Organisation', on_delete=models.CASCADE, related_name='locations')
90     iati_country = ValidXMLCharField(
91         _('country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False),
92         help_text=_('The country in which the organisation is located.')
93     )
94 
95     def iati_country_value(self):
96         return codelist_value(Country, self, 'iati_country')
97 
98     def iati_country_value_unicode(self):
99         return str(self.iati_country_value())
100 
101 
102 class ProjectLocation(BaseLocation):
103 
104     project_relation = 'locations__in'
105 
106     location_target = models.ForeignKey('Project', on_delete=models.CASCADE, related_name='locations')
107 
108     # Additional IATI fields
109     reference = ValidXMLCharField(
110         _('reference'), blank=True, max_length=50,
111         help_text=_('An internal reference that describes the location in the reporting '
112                     'organisation\'s own system. For reference see: '
113                     '<a href="http://iatistandard.org/202/activity-standard/iati-activities/'
114                     'iati-activity/location/#attributes" target="_blank">'
115                     'http://iatistandard.org/202/activity-standard/iati-activities/iati-activity/'
116                     'location/#attributes</a>.')
117     )
118     location_code = ValidXMLCharField(
119         _('code'), blank=True, max_length=25,
120         help_text=_('Enter a code to identify the region. Codes are based on DAC region codes. '
121                     'Where an activity is considered global, the code 998 can be used. For '
122                     'reference: <a href="http://www.oecd.org/dac/stats/dacandcrscodelists.htm" '
123                     'target="_blank">http://www.oecd.org/dac/stats/dacandcrscodelists.htm</a>.')
124     )
125     vocabulary = ValidXMLCharField(_('vocabulary'), blank=True, max_length=2,
126                                    choices=codelist_choices(GEOGRAPHIC_VOCABULARY))
127     name = ValidXMLCharField(
128         _('name'), blank=True, max_length=100,
129         help_text=_('The human-readable name for the location.')
130     )
131     description = ValidXMLCharField(
132         _('location description'), blank=True, max_length=2000,
133         help_text=_('This provides free text space for providing an additional description, if '
134                     'needed, of the actual target of the activity. A description that qualifies '
135                     'the location, not the activity.')
136     )
137     activity_description = ValidXMLCharField(
138         _('activity description'), blank=True, max_length=2000,
139         help_text=_('A description that qualifies the activity taking place at the location. '
140                     'This should not duplicate information provided in the main activity '
141                     'description, and should typically be used to distinguish between activities '
142                     'at multiple locations within a single iati-activity record.')
143     )
144     exactness = ValidXMLCharField(
145         _('location precision'), blank=True, max_length=1,
146         choices=codelist_choices(GEOGRAPHIC_EXACTNESS),
147         help_text=_('Defines whether the location represents the most distinct point reasonably '
148                     'possible for this type of activity or is an approximation due to lack of '
149                     'more detailed information.')
150     )
151     location_reach = ValidXMLCharField(
152         _('reach'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_REACH),
153         help_text=_('Does this location describe where the activity takes place or where the '
154                     'intended beneficiaries reside?')
155     )
156     location_class = ValidXMLCharField(
157         _('class'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_CLASS),
158         help_text=_('Does the location refer to a physical structure such as a building, a '
159                     'populated place (e.g. city or village), an administrative division, or '
160                     'another topological feature (e.g. river, nature reserve)? For reference: '
161                     '<a href="http://iatistandard.org/202/codelists/GeographicLocationClass/" '
162                     'target="_blank">http://iatistandard.org/202/codelists/'
163                     'GeographicLocationClass/</a>.')
164     )
165     feature_designation = ValidXMLCharField(
166         _('feature designation'), blank=True, max_length=5,
167         choices=codelist_choices(LOCATION_TYPE),
168         help_text=_('A more refined coded classification of the type of feature referred to by '
169                     'this location. For reference: <a href="http://iatistandard.org/202/codelists/'
170                     'LocationType/" target="_blank">http://iatistandard.org/202/codelists/'
171                     'LocationType/</a>.')
172     )
173 
174     def iati_country(self):
175         return codelist_value(Country, self, 'country')
176 
177     def iati_country_unicode(self):
178         return str(self.iati_country())
179 
180     def iati_vocabulary(self):
181         return codelist_value(GeographicVocabulary, self, 'vocabulary')
182 
183     def iati_vocabulary_unicode(self):
184         return str(self.iati_vocabulary())
185 
186     def iati_exactness(self):
187         return codelist_value(GeographicExactness, self, 'exactness')
188 
189     def iati_exactness_unicode(self):
190         return str(self.iati_exactness())
191 
192     def iati_reach(self):
193         return codelist_value(GeographicLocationReach, self, 'location_reach')
194 
195     def iati_reach_unicode(self):
196         return str(self.iati_reach())
197 
198     def iati_class(self):
199         return codelist_value(GeographicLocationClass, self, 'location_class')
200 
201     def iati_class_unicode(self):
202         return str(self.iati_class())
203 
204     def iati_designation(self):
205         return codelist_value(LocationType, self, 'feature_designation')
206 
207     def iati_designation_unicode(self):
208         return str(self.iati_designation())
209 
210 
211 # Over-riding fields doesn't work in Django < 1.10, and hence this hack.
212 ProjectLocation._meta.get_field('country').help_text = _(
213     'The country or countries that benefit(s) from the activity.'
214 )
215 
216 
217 class AdministrativeLocation(models.Model):
218 
219     project_relation = 'locations__administratives__in'
220 
221     location = models.ForeignKey(
222         'ProjectLocation', on_delete=models.CASCADE, verbose_name=_('location'), related_name='administratives'
223     )
224     code = ValidXMLCharField(
225         _('administrative code'), blank=True, max_length=25,
226         help_text=_('Coded identification of national and sub-national divisions according to '
227                     'recognised administrative boundary repositories. Multiple levels may be '
228                     'reported.')
229     )
230     vocabulary = ValidXMLCharField(
231         _('administrative vocabulary'), blank=True, max_length=2,
232         choices=codelist_choices(GEOGRAPHIC_VOCABULARY),
233         help_text=_('For reference: <a href="http://iatistandard.org/202/codelists/'
234                     'GeographicVocabulary/" target="_blank">http://iatistandard.org/202/codelists/'
235                     'GeographicVocabulary/</a>.')
236     )
237 
238     level = models.PositiveSmallIntegerField(_('administrative level'), blank=True, null=True)
239 
240     def __str__(self):
241         return str(self.code) if self.code else '%s' % _('No code specified')
242 
243     def iati_vocabulary(self):
244         return codelist_value(GeographicVocabulary, self, 'vocabulary')
245 
246     def iati_vocabulary_unicode(self):
247         return str(self.iati_vocabulary())
248 
249     class Meta:
250         app_label = 'rsr'
251         verbose_name = _('location administrative')
252         verbose_name_plural = _('location administratives')
253         ordering = ('pk',)
254 
255 
256 class ProjectUpdateLocation(BaseLocation):
257     location_target = models.ForeignKey('ProjectUpdate', on_delete=models.CASCADE, related_name='locations')
258
```
--- 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/location.py b/akvo/rsr/models/location.py
--- a/akvo/rsr/models/location.py
+++ b/akvo/rsr/models/location.py
@@ -3,9 +3,11 @@
 # Akvo RSR is covered by the GNU Affero General Public License.
 # See more details in the license.txt file located at the root folder of the Akvo RSR module.
 # For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
-
+import logging
 
 from django.db import models
+from django.db.models.signals import pre_delete
+from django.dispatch import receiver
 from django.utils.translation import ugettext_lazy as _
 
 from ..fields import LatitudeField, LongitudeField, ValidXMLCharField
@@ -213,9 +215,20 @@
     'The country or countries that benefit(s) from the activity.'
 )
 
+logger = logging.getLogger(__name__)
+
+
+@receiver(pre_delete, sender=ProjectLocation)
+def on_projectlocation_delete(sender, instance: ProjectLocation, using, **kwargs):
+    logger.warning(
+        "About to delete ProjectLocation(%s) %s of project(%s) %s",
+        instance.id, instance,
+        instance.location_target.id, instance.location_target,
+        stack_info=True
+    )
 
-class AdministrativeLocation(models.Model):
 
+class AdministrativeLocation(models.Model):
     project_relation = 'locations__administratives__in'
 
     location = models.ForeignKey(

{"golden_diff": "diff --git a/akvo/rsr/models/location.py b/akvo/rsr/models/location.py\n--- a/akvo/rsr/models/location.py\n+++ b/akvo/rsr/models/location.py\n@@ -3,9 +3,11 @@\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-\n+import logging\n \n from django.db import models\n+from django.db.models.signals import pre_delete\n+from django.dispatch import receiver\n from django.utils.translation import ugettext_lazy as _\n \n from ..fields import LatitudeField, LongitudeField, ValidXMLCharField\n@@ -213,9 +215,20 @@\n     'The country or countries that benefit(s) from the activity.'\n )\n \n+logger = logging.getLogger(__name__)\n+\n+\n+@receiver(pre_delete, sender=ProjectLocation)\n+def on_projectlocation_delete(sender, instance: ProjectLocation, using, **kwargs):\n+    logger.warning(\n+        \"About to delete ProjectLocation(%s) %s of project(%s) %s\",\n+        instance.id, instance,\n+        instance.location_target.id, instance.location_target,\n+        stack_info=True\n+    )\n \n-class AdministrativeLocation(models.Model):\n \n+class AdministrativeLocation(models.Model):\n     project_relation = 'locations__administratives__in'\n \n     location = models.ForeignKey(\n", "issue": "Bug: Locations are reset on some projects\n### What were you doing?\n\nA client has reported that on some random projects after the location has been set, the locations are reset within a few hours. The occurrence has mostly been observed within 24h of making the modification.\n\n### What should've happened?\n\nLocations (and other fields) should magically be reset.\n\n### My environment\n\n_No response_\n\n### Additional context\n\nThis particular client has a nightly script that pulls information from an external system, of which location is a part. However, the script only modifies locations of newly created projects.\r\n\r\nPossibilities:\r\n\r\n - something is deeply wrong with Django and after a project is created it doesn't say it's created (highly unlikely)\r\n - the script has nothing to do with it and it's\r\n   * human error\r\n   * a bug in the frontend e.g changes are cached and when a user gets access to the network again the changes are pushed (React?)\r\n   * a side effect in the backend or another script\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\n\nfrom django.db import models\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom ..fields import LatitudeField, LongitudeField, ValidXMLCharField\nfrom akvo.codelists.models import (Country, GeographicExactness, GeographicLocationClass,\n                                   GeographicLocationReach, GeographicVocabulary, LocationType)\nfrom akvo.codelists.store.default_codelists import (\n    COUNTRY, GEOGRAPHIC_EXACTNESS, GEOGRAPHIC_LOCATION_CLASS, GEOGRAPHIC_LOCATION_REACH,\n    GEOGRAPHIC_VOCABULARY, LOCATION_TYPE\n)\nfrom akvo.utils import codelist_choices, codelist_value\n\n\nclass BaseLocation(models.Model):\n    latitude = LatitudeField(\n        _('latitude'), null=True, blank=True, db_index=True, default=None,\n        help_text=_('Use a period to denote decimals.')\n    )\n    longitude = LongitudeField(\n        _('longitude'), null=True, blank=True, db_index=True, default=None,\n        help_text=_('Use a period to denote decimals.')\n    )\n    city = ValidXMLCharField(_('city'), blank=True, max_length=255)\n    state = ValidXMLCharField(_('state'), blank=True, max_length=255)\n    address_1 = ValidXMLCharField(_('address 1'), max_length=255, blank=True)\n    address_2 = ValidXMLCharField(_('address 2'), max_length=255, blank=True)\n    postcode = ValidXMLCharField(_('postal code'), max_length=10, blank=True)\n    country = models.ForeignKey('Country', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('country'))\n\n    def __str__(self):\n        return '{0}, {1}, {2}{3}'.format(\n            '{0}: {1}'.format(\n                _('Latitude'),\n                str(self.latitude) if self.latitude else _('No latitude specified')),\n            '{0}: {1}'.format(\n                _('Longitude'),\n                str(self.longitude) if self.longitude else _('No longitude specified')),\n            '{0}: {1}'.format(\n                _('Country'),\n                str(self.country.name) if self.country else _('No country specified')),\n            ' ({0})'.format(self.name) if getattr(self, 'name', None) else ''\n        )\n\n    def delete(self, *args, **kwargs):\n        super(BaseLocation, self).delete(*args, **kwargs)\n\n        # If location_target has more locations, set the first as primary location\n        location_target = self.location_target\n        other_locations = location_target.locations.all()\n\n        if other_locations.count() > 0:\n            location_target.primary_location = other_locations.first()\n        else:\n            location_target.primary_location = None\n\n        location_target.save()\n\n    def save(self, *args, **kwargs):\n        super(BaseLocation, self).save(*args, **kwargs)\n\n        # Set location as primary location if it is the first location\n        location_target = self.location_target\n        if location_target.primary_location is None or location_target.primary_location.pk > self.pk:\n            location_target.primary_location = self\n            location_target.save()\n\n    def is_valid(self):\n        if (self.latitude is None or self.longitude is None) or \\\n           (self.latitude == 0 and self.longitude == 0) or \\\n           (self.latitude > 90 or self.latitude < -90) or \\\n           (self.longitude > 180 or self.latitude < -180):\n            return False\n        return True\n\n    class Meta:\n        app_label = 'rsr'\n        abstract = True\n        ordering = ['id', ]\n\n\nclass OrganisationLocation(BaseLocation):\n    location_target = models.ForeignKey('Organisation', on_delete=models.CASCADE, related_name='locations')\n    iati_country = ValidXMLCharField(\n        _('country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False),\n        help_text=_('The country in which the organisation is located.')\n    )\n\n    def iati_country_value(self):\n        return codelist_value(Country, self, 'iati_country')\n\n    def iati_country_value_unicode(self):\n        return str(self.iati_country_value())\n\n\nclass ProjectLocation(BaseLocation):\n\n    project_relation = 'locations__in'\n\n    location_target = models.ForeignKey('Project', on_delete=models.CASCADE, related_name='locations')\n\n    # Additional IATI fields\n    reference = ValidXMLCharField(\n        _('reference'), blank=True, max_length=50,\n        help_text=_('An internal reference that describes the location in the reporting '\n                    'organisation\\'s own system. For reference see: '\n                    '<a href=\"http://iatistandard.org/202/activity-standard/iati-activities/'\n                    'iati-activity/location/#attributes\" target=\"_blank\">'\n                    'http://iatistandard.org/202/activity-standard/iati-activities/iati-activity/'\n                    'location/#attributes</a>.')\n    )\n    location_code = ValidXMLCharField(\n        _('code'), blank=True, max_length=25,\n        help_text=_('Enter a code to identify the region. Codes are based on DAC region codes. '\n                    'Where an activity is considered global, the code 998 can be used. For '\n                    'reference: <a href=\"http://www.oecd.org/dac/stats/dacandcrscodelists.htm\" '\n                    'target=\"_blank\">http://www.oecd.org/dac/stats/dacandcrscodelists.htm</a>.')\n    )\n    vocabulary = ValidXMLCharField(_('vocabulary'), blank=True, max_length=2,\n                                   choices=codelist_choices(GEOGRAPHIC_VOCABULARY))\n    name = ValidXMLCharField(\n        _('name'), blank=True, max_length=100,\n        help_text=_('The human-readable name for the location.')\n    )\n    description = ValidXMLCharField(\n        _('location description'), blank=True, max_length=2000,\n        help_text=_('This provides free text space for providing an additional description, if '\n                    'needed, of the actual target of the activity. A description that qualifies '\n                    'the location, not the activity.')\n    )\n    activity_description = ValidXMLCharField(\n        _('activity description'), blank=True, max_length=2000,\n        help_text=_('A description that qualifies the activity taking place at the location. '\n                    'This should not duplicate information provided in the main activity '\n                    'description, and should typically be used to distinguish between activities '\n                    'at multiple locations within a single iati-activity record.')\n    )\n    exactness = ValidXMLCharField(\n        _('location precision'), blank=True, max_length=1,\n        choices=codelist_choices(GEOGRAPHIC_EXACTNESS),\n        help_text=_('Defines whether the location represents the most distinct point reasonably '\n                    'possible for this type of activity or is an approximation due to lack of '\n                    'more detailed information.')\n    )\n    location_reach = ValidXMLCharField(\n        _('reach'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_REACH),\n        help_text=_('Does this location describe where the activity takes place or where the '\n                    'intended beneficiaries reside?')\n    )\n    location_class = ValidXMLCharField(\n        _('class'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_CLASS),\n        help_text=_('Does the location refer to a physical structure such as a building, a '\n                    'populated place (e.g. city or village), an administrative division, or '\n                    'another topological feature (e.g. river, nature reserve)? For reference: '\n                    '<a href=\"http://iatistandard.org/202/codelists/GeographicLocationClass/\" '\n                    'target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'GeographicLocationClass/</a>.')\n    )\n    feature_designation = ValidXMLCharField(\n        _('feature designation'), blank=True, max_length=5,\n        choices=codelist_choices(LOCATION_TYPE),\n        help_text=_('A more refined coded classification of the type of feature referred to by '\n                    'this location. For reference: <a href=\"http://iatistandard.org/202/codelists/'\n                    'LocationType/\" target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'LocationType/</a>.')\n    )\n\n    def iati_country(self):\n        return codelist_value(Country, self, 'country')\n\n    def iati_country_unicode(self):\n        return str(self.iati_country())\n\n    def iati_vocabulary(self):\n        return codelist_value(GeographicVocabulary, self, 'vocabulary')\n\n    def iati_vocabulary_unicode(self):\n        return str(self.iati_vocabulary())\n\n    def iati_exactness(self):\n        return codelist_value(GeographicExactness, self, 'exactness')\n\n    def iati_exactness_unicode(self):\n        return str(self.iati_exactness())\n\n    def iati_reach(self):\n        return codelist_value(GeographicLocationReach, self, 'location_reach')\n\n    def iati_reach_unicode(self):\n        return str(self.iati_reach())\n\n    def iati_class(self):\n        return codelist_value(GeographicLocationClass, self, 'location_class')\n\n    def iati_class_unicode(self):\n        return str(self.iati_class())\n\n    def iati_designation(self):\n        return codelist_value(LocationType, self, 'feature_designation')\n\n    def iati_designation_unicode(self):\n        return str(self.iati_designation())\n\n\n# Over-riding fields doesn't work in Django < 1.10, and hence this hack.\nProjectLocation._meta.get_field('country').help_text = _(\n    'The country or countries that benefit(s) from the activity.'\n)\n\n\nclass AdministrativeLocation(models.Model):\n\n    project_relation = 'locations__administratives__in'\n\n    location = models.ForeignKey(\n        'ProjectLocation', on_delete=models.CASCADE, verbose_name=_('location'), related_name='administratives'\n    )\n    code = ValidXMLCharField(\n        _('administrative code'), blank=True, max_length=25,\n        help_text=_('Coded identification of national and sub-national divisions according to '\n                    'recognised administrative boundary repositories. Multiple levels may be '\n                    'reported.')\n    )\n    vocabulary = ValidXMLCharField(\n        _('administrative vocabulary'), blank=True, max_length=2,\n        choices=codelist_choices(GEOGRAPHIC_VOCABULARY),\n        help_text=_('For reference: <a href=\"http://iatistandard.org/202/codelists/'\n                    'GeographicVocabulary/\" target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'GeographicVocabulary/</a>.')\n    )\n\n    level = models.PositiveSmallIntegerField(_('administrative level'), blank=True, null=True)\n\n    def __str__(self):\n        return str(self.code) if self.code else '%s' % _('No code specified')\n\n    def iati_vocabulary(self):\n        return codelist_value(GeographicVocabulary, self, 'vocabulary')\n\n    def iati_vocabulary_unicode(self):\n        return str(self.iati_vocabulary())\n\n    class Meta:\n        app_label = 'rsr'\n        verbose_name = _('location administrative')\n        verbose_name_plural = _('location administratives')\n        ordering = ('pk',)\n\n\nclass ProjectUpdateLocation(BaseLocation):\n    location_target = models.ForeignKey('ProjectUpdate', on_delete=models.CASCADE, related_name='locations')\n", "path": "akvo/rsr/models/location.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 logging\n\nfrom django.db import models\nfrom django.db.models.signals import pre_delete\nfrom django.dispatch import receiver\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom ..fields import LatitudeField, LongitudeField, ValidXMLCharField\nfrom akvo.codelists.models import (Country, GeographicExactness, GeographicLocationClass,\n                                   GeographicLocationReach, GeographicVocabulary, LocationType)\nfrom akvo.codelists.store.default_codelists import (\n    COUNTRY, GEOGRAPHIC_EXACTNESS, GEOGRAPHIC_LOCATION_CLASS, GEOGRAPHIC_LOCATION_REACH,\n    GEOGRAPHIC_VOCABULARY, LOCATION_TYPE\n)\nfrom akvo.utils import codelist_choices, codelist_value\n\n\nclass BaseLocation(models.Model):\n    latitude = LatitudeField(\n        _('latitude'), null=True, blank=True, db_index=True, default=None,\n        help_text=_('Use a period to denote decimals.')\n    )\n    longitude = LongitudeField(\n        _('longitude'), null=True, blank=True, db_index=True, default=None,\n        help_text=_('Use a period to denote decimals.')\n    )\n    city = ValidXMLCharField(_('city'), blank=True, max_length=255)\n    state = ValidXMLCharField(_('state'), blank=True, max_length=255)\n    address_1 = ValidXMLCharField(_('address 1'), max_length=255, blank=True)\n    address_2 = ValidXMLCharField(_('address 2'), max_length=255, blank=True)\n    postcode = ValidXMLCharField(_('postal code'), max_length=10, blank=True)\n    country = models.ForeignKey('Country', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('country'))\n\n    def __str__(self):\n        return '{0}, {1}, {2}{3}'.format(\n            '{0}: {1}'.format(\n                _('Latitude'),\n                str(self.latitude) if self.latitude else _('No latitude specified')),\n            '{0}: {1}'.format(\n                _('Longitude'),\n                str(self.longitude) if self.longitude else _('No longitude specified')),\n            '{0}: {1}'.format(\n                _('Country'),\n                str(self.country.name) if self.country else _('No country specified')),\n            ' ({0})'.format(self.name) if getattr(self, 'name', None) else ''\n        )\n\n    def delete(self, *args, **kwargs):\n        super(BaseLocation, self).delete(*args, **kwargs)\n\n        # If location_target has more locations, set the first as primary location\n        location_target = self.location_target\n        other_locations = location_target.locations.all()\n\n        if other_locations.count() > 0:\n            location_target.primary_location = other_locations.first()\n        else:\n            location_target.primary_location = None\n\n        location_target.save()\n\n    def save(self, *args, **kwargs):\n        super(BaseLocation, self).save(*args, **kwargs)\n\n        # Set location as primary location if it is the first location\n        location_target = self.location_target\n        if location_target.primary_location is None or location_target.primary_location.pk > self.pk:\n            location_target.primary_location = self\n            location_target.save()\n\n    def is_valid(self):\n        if (self.latitude is None or self.longitude is None) or \\\n           (self.latitude == 0 and self.longitude == 0) or \\\n           (self.latitude > 90 or self.latitude < -90) or \\\n           (self.longitude > 180 or self.latitude < -180):\n            return False\n        return True\n\n    class Meta:\n        app_label = 'rsr'\n        abstract = True\n        ordering = ['id', ]\n\n\nclass OrganisationLocation(BaseLocation):\n    location_target = models.ForeignKey('Organisation', on_delete=models.CASCADE, related_name='locations')\n    iati_country = ValidXMLCharField(\n        _('country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False),\n        help_text=_('The country in which the organisation is located.')\n    )\n\n    def iati_country_value(self):\n        return codelist_value(Country, self, 'iati_country')\n\n    def iati_country_value_unicode(self):\n        return str(self.iati_country_value())\n\n\nclass ProjectLocation(BaseLocation):\n\n    project_relation = 'locations__in'\n\n    location_target = models.ForeignKey('Project', on_delete=models.CASCADE, related_name='locations')\n\n    # Additional IATI fields\n    reference = ValidXMLCharField(\n        _('reference'), blank=True, max_length=50,\n        help_text=_('An internal reference that describes the location in the reporting '\n                    'organisation\\'s own system. For reference see: '\n                    '<a href=\"http://iatistandard.org/202/activity-standard/iati-activities/'\n                    'iati-activity/location/#attributes\" target=\"_blank\">'\n                    'http://iatistandard.org/202/activity-standard/iati-activities/iati-activity/'\n                    'location/#attributes</a>.')\n    )\n    location_code = ValidXMLCharField(\n        _('code'), blank=True, max_length=25,\n        help_text=_('Enter a code to identify the region. Codes are based on DAC region codes. '\n                    'Where an activity is considered global, the code 998 can be used. For '\n                    'reference: <a href=\"http://www.oecd.org/dac/stats/dacandcrscodelists.htm\" '\n                    'target=\"_blank\">http://www.oecd.org/dac/stats/dacandcrscodelists.htm</a>.')\n    )\n    vocabulary = ValidXMLCharField(_('vocabulary'), blank=True, max_length=2,\n                                   choices=codelist_choices(GEOGRAPHIC_VOCABULARY))\n    name = ValidXMLCharField(\n        _('name'), blank=True, max_length=100,\n        help_text=_('The human-readable name for the location.')\n    )\n    description = ValidXMLCharField(\n        _('location description'), blank=True, max_length=2000,\n        help_text=_('This provides free text space for providing an additional description, if '\n                    'needed, of the actual target of the activity. A description that qualifies '\n                    'the location, not the activity.')\n    )\n    activity_description = ValidXMLCharField(\n        _('activity description'), blank=True, max_length=2000,\n        help_text=_('A description that qualifies the activity taking place at the location. '\n                    'This should not duplicate information provided in the main activity '\n                    'description, and should typically be used to distinguish between activities '\n                    'at multiple locations within a single iati-activity record.')\n    )\n    exactness = ValidXMLCharField(\n        _('location precision'), blank=True, max_length=1,\n        choices=codelist_choices(GEOGRAPHIC_EXACTNESS),\n        help_text=_('Defines whether the location represents the most distinct point reasonably '\n                    'possible for this type of activity or is an approximation due to lack of '\n                    'more detailed information.')\n    )\n    location_reach = ValidXMLCharField(\n        _('reach'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_REACH),\n        help_text=_('Does this location describe where the activity takes place or where the '\n                    'intended beneficiaries reside?')\n    )\n    location_class = ValidXMLCharField(\n        _('class'), blank=True, max_length=1, choices=codelist_choices(GEOGRAPHIC_LOCATION_CLASS),\n        help_text=_('Does the location refer to a physical structure such as a building, a '\n                    'populated place (e.g. city or village), an administrative division, or '\n                    'another topological feature (e.g. river, nature reserve)? For reference: '\n                    '<a href=\"http://iatistandard.org/202/codelists/GeographicLocationClass/\" '\n                    'target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'GeographicLocationClass/</a>.')\n    )\n    feature_designation = ValidXMLCharField(\n        _('feature designation'), blank=True, max_length=5,\n        choices=codelist_choices(LOCATION_TYPE),\n        help_text=_('A more refined coded classification of the type of feature referred to by '\n                    'this location. For reference: <a href=\"http://iatistandard.org/202/codelists/'\n                    'LocationType/\" target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'LocationType/</a>.')\n    )\n\n    def iati_country(self):\n        return codelist_value(Country, self, 'country')\n\n    def iati_country_unicode(self):\n        return str(self.iati_country())\n\n    def iati_vocabulary(self):\n        return codelist_value(GeographicVocabulary, self, 'vocabulary')\n\n    def iati_vocabulary_unicode(self):\n        return str(self.iati_vocabulary())\n\n    def iati_exactness(self):\n        return codelist_value(GeographicExactness, self, 'exactness')\n\n    def iati_exactness_unicode(self):\n        return str(self.iati_exactness())\n\n    def iati_reach(self):\n        return codelist_value(GeographicLocationReach, self, 'location_reach')\n\n    def iati_reach_unicode(self):\n        return str(self.iati_reach())\n\n    def iati_class(self):\n        return codelist_value(GeographicLocationClass, self, 'location_class')\n\n    def iati_class_unicode(self):\n        return str(self.iati_class())\n\n    def iati_designation(self):\n        return codelist_value(LocationType, self, 'feature_designation')\n\n    def iati_designation_unicode(self):\n        return str(self.iati_designation())\n\n\n# Over-riding fields doesn't work in Django < 1.10, and hence this hack.\nProjectLocation._meta.get_field('country').help_text = _(\n    'The country or countries that benefit(s) from the activity.'\n)\n\nlogger = logging.getLogger(__name__)\n\n\n@receiver(pre_delete, sender=ProjectLocation)\ndef on_projectlocation_delete(sender, instance: ProjectLocation, using, **kwargs):\n    logger.warning(\n        \"About to delete ProjectLocation(%s) %s of project(%s) %s\",\n        instance.id, instance,\n        instance.location_target.id, instance.location_target,\n        stack_info=True\n    )\n\n\nclass AdministrativeLocation(models.Model):\n    project_relation = 'locations__administratives__in'\n\n    location = models.ForeignKey(\n        'ProjectLocation', on_delete=models.CASCADE, verbose_name=_('location'), related_name='administratives'\n    )\n    code = ValidXMLCharField(\n        _('administrative code'), blank=True, max_length=25,\n        help_text=_('Coded identification of national and sub-national divisions according to '\n                    'recognised administrative boundary repositories. Multiple levels may be '\n                    'reported.')\n    )\n    vocabulary = ValidXMLCharField(\n        _('administrative vocabulary'), blank=True, max_length=2,\n        choices=codelist_choices(GEOGRAPHIC_VOCABULARY),\n        help_text=_('For reference: <a href=\"http://iatistandard.org/202/codelists/'\n                    'GeographicVocabulary/\" target=\"_blank\">http://iatistandard.org/202/codelists/'\n                    'GeographicVocabulary/</a>.')\n    )\n\n    level = models.PositiveSmallIntegerField(_('administrative level'), blank=True, null=True)\n\n    def __str__(self):\n        return str(self.code) if self.code else '%s' % _('No code specified')\n\n    def iati_vocabulary(self):\n        return codelist_value(GeographicVocabulary, self, 'vocabulary')\n\n    def iati_vocabulary_unicode(self):\n        return str(self.iati_vocabulary())\n\n    class Meta:\n        app_label = 'rsr'\n        verbose_name = _('location administrative')\n        verbose_name_plural = _('location administratives')\n        ordering = ('pk',)\n\n\nclass ProjectUpdateLocation(BaseLocation):\n    location_target = models.ForeignKey('ProjectUpdate', on_delete=models.CASCADE, related_name='locations')\n", "path": "akvo/rsr/models/location.py"}]}