Skip to content

Commit b26bd90

Browse files
Merge pull request #1608 from IFRCGo/develop
Release 1.1.463
2 parents 32a928d + 4d6d79d commit b26bd90

File tree

13 files changed

+110
-43
lines changed

13 files changed

+110
-43
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## Unreleased
88

9+
## 1.1.463
10+
11+
### Added
12+
- DREF Final report, v0.1
13+
- Ops Update: optimistic lock
14+
- Dropping non-used enum Choices
15+
- Fix filter in DREF
16+
- Admin page: search possibility of Admin2 countries
17+
918
## 1.1.462
1019

1120
### Added
@@ -2105,7 +2114,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
21052114

21062115
## 0.1.20
21072116

2108-
[Unreleased]: https://github.com/IFRCGo/go-api/compare/1.1.462...HEAD
2117+
[Unreleased]: https://github.com/IFRCGo/go-api/compare/1.1.463...HEAD
2118+
[1.1.463]: https://github.com/IFRCGo/go-api/compare/1.1.462...1.1.463
21092119
[1.1.462]: https://github.com/IFRCGo/go-api/compare/1.1.461...1.1.462
21102120
[1.1.461]: https://github.com/IFRCGo/go-api/compare/1.1.460...1.1.461
21112121
[1.1.460]: https://github.com/IFRCGo/go-api/compare/1.1.459...1.1.460

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ The shapefile should have the following mandatory fields:
225225
* admin1_id (this is the ID of the GO district this admin2 belongs to)
226226

227227
See [this ticket](https://github.com/IFRCGo/go-api/issues/1492#issuecomment-1284120696) for a full workflow of preparing the admin2 shapefiles.
228-
The above command will generate a list of missing admin2s in the database based on the code (we use pcodes) to a file called `missing-admin2.txt`
228+
The above command will generate a list of missing admin2-s in the database based on the code (we use pcodes) to a file called `missing-admin2.txt`
229229

230230
### Options available for the command
231231
* `--update-geom` -- updates the geometry for all admin2 matched in the shapefile.
@@ -237,13 +237,13 @@ The above command will generate a list of missing admin2s in the database based
237237
Run `python manage.py update-region-bbox` to update the bbox for each region in the database.
238238

239239
## Import FDRS codes
240-
Run `python manage.py import-fdrs iso-fdrs.csv` to update the countries table with FDRS codes. The csv should have `iso,fdrs` structure
240+
Run `python manage.py import-fdrs iso-fdrs.csv` to update the countries table with FDRS codes. The csv should have `iso, fdrs` structure
241241

242242
## Update sovereign state and disputed status
243-
Run ` python manage.py update-sovereign-and-disputed new_fields.csv` to update the countries table with sovereign states and disputed status. The CSV should have the `id,iso,name,sovereign_state,disputed` columns. The matching is based on iso and name. If iso is null, we fallback to name.
243+
Run ` python manage.py update-sovereign-and-disputed new_fields.csv` to update the countries table with sovereign states and disputed status. The CSV should have the `id,iso,name,sovereign_state,disputed` columns. The matching is based on iso and name. If iso is null, we fall back to name.
244244

245245
## Update Mapbox Tilesets
246-
To update GO countries and districts Mapbox tilesets, run the management command `python manage.py update-mapbox-tilesets`. This will export all country and district geometries to a GeoJSON file, and then upload them to Mapbox. The tilesets will take a while to process. The updated status can be viewed on the Mapbox Studio under tilesets. To run this management command, MAPBOX_ACCESS_TOKEN should be set in the environment.
246+
To update GO countries and districts Mapbox tilesets, run the management command `python manage.py update-mapbox-tilesets`. This will export all country and district geometries to a GeoJSON file, and then upload them to Mapbox. The tilesets will take a while to process. The updated status can be viewed on the Mapbox Studio under tilesets. To run this management command, MAPBOX_ACCESS_TOKEN should be set in the environment. The referred files are in ./mapbox/..., so you should **not** run this command from an arbitrary point of the vm's filesystem (e.g. from the location of shapefiles), but from Django root.
247247

248248
### Options available for the command
249249
* `--production` — update production tilesets. If this flag is not set, by default the script will only update staging tiles

api/admin.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -245,24 +245,6 @@ def field_reports(self, instance):
245245
return mark_safe('<span class="errors">No related field reports</span>')
246246
field_reports.short_description = 'Field Reports'
247247

248-
# For multiple document fields inline. TO be FIXED: only the last one is saved. Change also tabular.html (DELETEME)
249-
# def save_formset(self, request, form, formset, change):
250-
# if hasattr(formset.model, 'document'): # SituationReports (or other similars)
251-
# instances = formset.save(commit=False)
252-
# for inst in formset.deleted_objects:
253-
# inst.delete()
254-
# for inst in formset.changed_objects:
255-
# inst.save()
256-
# for inst in formset.new_objects:
257-
# for i,one_document in enumerate(request.FILES.getlist('documents_multiple')):
258-
# if i<30: # not letting tons of documents to be attached
259-
# inst.name = inst.name if i == 0 else inst.name + '-' + str(i)
260-
# inst.document = one_document
261-
# inst.save()
262-
# formset.save_m2m()
263-
# else:
264-
# formset.save()
265-
266248

267249
class GdacsAdmin(CompareVersionAdmin, RegionRestrictedAdmin, TranslationAdmin):
268250
country_in = 'countries__pk__in'
@@ -550,10 +532,12 @@ class RegionAdmin(geoadmin.OSMGeoAdmin, CompareVersionAdmin, RegionRestrictedAdm
550532
search_fields = ('name',)
551533
modifiable = True
552534

535+
553536
class Admin2Admin(geoadmin.OSMGeoAdmin, CompareVersionAdmin, RegionRestrictedAdmin):
554-
search_fields = ('name',)
537+
search_fields = ('name', 'admin1__country__name')
555538
modifiable = True
556539

540+
557541
class UserProfileAdmin(CompareVersionAdmin):
558542
search_fields = ('user__username', 'user__email', 'country__name',)
559543
list_filter = (

deployments/models.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
from django.utils.hashable import make_hashable
1010
from django.utils.encoding import force_str
1111
from django.contrib.postgres.fields import ArrayField
12-
from django.contrib.gis.db import models as gid_models
1312
from django.db.models import Q
1413
from django.db.models import JSONField
1514

16-
from main.enums import TextChoices
1715
from api.models import (
1816
District,
1917
Country,

dref/filter_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class DrefFilter(filters.FilterSet):
1414
widget=filters.widgets.CSVWidget,
1515
)
1616
country = filters.ModelMultipleChoiceFilter(
17-
field_name='drefcountrydistrict__country',
17+
field_name='country',
1818
queryset=Country.objects.all()
1919
)
2020

dref/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,11 @@ def clone(self, user):
689689

690690
class DrefOperationalUpdate(models.Model):
691691
created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True)
692-
modified_at = models.DateTimeField(verbose_name=_('modified at'), auto_now=True)
692+
modified_at = models.DateTimeField(
693+
verbose_name=_('modified at'),
694+
auto_now=True,
695+
blank=True
696+
)
693697
created_by = models.ForeignKey(
694698
settings.AUTH_USER_MODEL,
695699
verbose_name=_('created by'),

dref/serializers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from django.utils.translation import gettext
55
from django.db import models, transaction
6+
from django.conf import settings
67

78
from rest_framework import serializers
89

@@ -184,7 +185,6 @@ class DrefSerializer(
184185
ALLOWED_ASSESSMENT_REPORT_EXTENSIONS = ["pdf", "docx", "pptx"]
185186
MAX_OPERATION_TIMEFRAME = 30
186187
ASSESSMENT_REPORT_MAX_OPERATION_TIMEFRAME = 2
187-
DREF_UPDATE_ERROR_MESSAGE = "OBSOLETE_PAYLOAD"
188188
national_society_actions = NationalSocietyActionSerializer(many=True, required=False)
189189
needs_identified = IdentifiedNeedSerializer(many=True, required=False)
190190
planned_interventions = PlannedInterventionSerializer(many=True, required=False)
@@ -349,7 +349,7 @@ def update(self, instance, validated_data):
349349
is_assessment_report = validated_data.get('is_assessment_report')
350350
modified_at = validated_data.pop('modified_at', None)
351351
if modified_at is None:
352-
raise serializers.ValidationError({ 'modified_at': 'Modified At is required!' })
352+
raise serializers.ValidationError({'modified_at': 'Modified At is required!'})
353353
if is_assessment_report:
354354
# Previous Operations
355355
validated_data['lessons_learned'] = None
@@ -383,7 +383,7 @@ def update(self, instance, validated_data):
383383
else:
384384
to = None
385385
if modified_at and instance.modified_at and modified_at < instance.modified_at:
386-
raise serializers.ValidationError({ 'modified_at': self.DREF_UPDATE_ERROR_MESSAGE })
386+
raise serializers.ValidationError({'modified_at': settings.DREF_OP_UPDATE_UPDATE_ERROR_MESSAGE})
387387
dref = super().update(instance, validated_data)
388388
if to:
389389
transaction.on_commit(
@@ -414,6 +414,7 @@ class DrefOperationalUpdateSerializer(
414414
district_details = MiniDistrictSerializer(source='district', read_only=True, many=True)
415415
assessment_report_file = DrefFileSerializer(source='assessment_report', required=False, allow_null=True)
416416
risk_security = RiskSecuritySerializer(many=True, required=False)
417+
modified_at = serializers.DateTimeField(required=False)
417418

418419
class Meta:
419420
model = DrefOperationalUpdate
@@ -656,6 +657,9 @@ def update(self, instance, validated_data):
656657
dref_target_population_of_operation = validated_data.get('dref', instance.dref).total_targeted_population
657658
dref_amount_requested = validated_data.get('dref', instance.dref).amount_requested
658659
dref_district = validated_data.get('dref', instance.dref).district.all()
660+
modified_at = validated_data.pop('modified_at', None)
661+
if modified_at is None:
662+
raise serializers.ValidationError({'modified_at': 'Modified At is required!'})
659663

660664
if (not changing_timeframe_operation) and total_operation_timeframe and dref_operation_timeframe and\
661665
total_operation_timeframe != dref_operation_timeframe:
@@ -682,6 +686,8 @@ def update(self, instance, validated_data):
682686
if changing_geographic_location and district and dref_district and set(district) == set(dref_district):
683687
raise serializers.ValidationError('Found same district for dref and operational update with changing set to true')
684688

689+
if modified_at and instance.modified_at and modified_at < instance.modified_at:
690+
raise serializers.ValidationError({'modified_at': settings.DREF_OP_UPDATE_UPDATE_ERROR_MESSAGE})
685691
return super().update(instance, validated_data)
686692

687693

dref/test_views.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,35 @@ def test_filter_dref_status(self):
426426
self.assertEqual(response.status_code, 200)
427427
self.assertEqual(len(response.data['results']), 2)
428428

429+
def test_dref_country_filter(self):
430+
country1 = Country.objects.create(name='country1')
431+
country2 = Country.objects.create(name='country2')
432+
DrefFactory.create(
433+
title='test',
434+
status=Dref.Status.COMPLETED,
435+
created_by=self.user,
436+
country=country1
437+
)
438+
DrefFactory.create(
439+
status=Dref.Status.COMPLETED, created_by=self.user
440+
)
441+
DrefFactory.create(
442+
status=Dref.Status.COMPLETED,
443+
created_by=self.user,
444+
country=country2
445+
)
446+
DrefFactory.create(
447+
status=Dref.Status.IN_PROGRESS,
448+
created_by=self.user,
449+
country=country1
450+
)
451+
DrefFactory.create(status=Dref.Status.IN_PROGRESS, created_by=self.user)
452+
url = f'/api/v2/dref/?country={country1.id}'
453+
self.client.force_authenticate(self.user)
454+
response = self.client.get(url)
455+
self.assertEqual(response.status_code, 200)
456+
self.assertEqual(len(response.data['results']), 2)
457+
429458
def test_dref_options(self):
430459
"""
431460
Test for various dref attributes
@@ -591,6 +620,7 @@ def test_dref_operational_update_patch(self):
591620
'new_operational_end_date': '2022-10-10',
592621
'reporting_timeframe': '2022-10-16',
593622
'is_timeframe_extension_required': True,
623+
'modified_at': datetime.now() + timedelta(days=12),
594624
}
595625
url = f'/api/v2/dref-op-update/{response_id}/'
596626
self.authenticate(user=user1)
@@ -891,3 +921,42 @@ def test_dref_latest_update(self):
891921
self.assertEqual(response.status_code, 200)
892922
# Title should be latest since modified_at is greater than modified_at in database
893923
self.assertEqual(response.data['title'], "New title")
924+
925+
def test_dref_op_update_locking(self):
926+
user1, _ = UserFactory.create_batch(2)
927+
dref = DrefFactory.create(
928+
title='Test Title', created_by=user1,
929+
is_published=True,
930+
)
931+
dref.users.add(user1)
932+
DrefOperationalUpdateFactory.create(
933+
dref=dref,
934+
is_published=True,
935+
operational_update_number=1,
936+
modified_at=datetime.now()
937+
)
938+
url = '/api/v2/dref-op-update/'
939+
data = {
940+
'dref': dref.id,
941+
}
942+
self.authenticate(user=user1)
943+
response = self.client.post(url, data=data)
944+
self.assert_201(response)
945+
response_id = response.data['id']
946+
947+
# without `modified_at`
948+
data = {
949+
'title': 'New Operation title',
950+
'new_operational_end_date': '2022-10-10',
951+
'reporting_timeframe': '2022-10-16',
952+
'is_timeframe_extension_required': True,
953+
}
954+
url = f'/api/v2/dref-op-update/{response_id}/'
955+
self.authenticate(user=user1)
956+
response = self.client.patch(url, data)
957+
self.assert_400(response)
958+
959+
# with `modified_at` less than instance `modified_at`
960+
data['modified_at'] = datetime.now() - timedelta(days=2)
961+
response = self.client.patch(url, data=data)
962+
self.assertEqual(response.status_code, 400)

dref/views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
DrefFilter,
3333
DrefOperationalUpdateFilter
3434
)
35-
from dref.permissions import DrefOperationalUpdateCreatePermission
3635

3736

3837
class DrefViewSet(RevisionMixin, viewsets.ModelViewSet):
@@ -71,7 +70,7 @@ def get_published(self, request, pk=None, version=None):
7170

7271
class DrefOperationalUpdateViewSet(RevisionMixin, viewsets.ModelViewSet):
7372
serializer_class = DrefOperationalUpdateSerializer
74-
permission_classes = [permissions.IsAuthenticated, DrefOperationalUpdateCreatePermission]
73+
permission_classes = [permissions.IsAuthenticated]
7574
filterset_class = DrefOperationalUpdateFilter
7675

7776
def get_queryset(self):

eap/models.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
from django.db import models
22
from django.conf import settings
33
from django.utils.translation import gettext_lazy as _
4-
5-
from main.enums import TextChoices
6-
from deployments.models import Sectors
7-
from main.enums import IntegerChoices
84
from api.models import (
95
Country,
106
District,
@@ -13,7 +9,7 @@
139

1410

1511
class EarlyActionIndicator(models.Model):
16-
class IndicatorChoices(TextChoices): # TODO these indicator are yet to be provided by client.
12+
class IndicatorChoices(models.TextChoices): # TODO these indicator are yet to be provided by client.
1713
INDICATOR_1 = 'indicator_1', _('Indicator 1')
1814
INDICATOR_2 = 'indicator_2', _('Indicator 2')
1915

@@ -32,7 +28,7 @@ def __str__(self):
3228

3329

3430
class EarlyAction(models.Model):
35-
class Sector(IntegerChoices):
31+
class Sector(models.IntegerChoices):
3632
SHELTER_HOUSING_AND_SETTLEMENTS = 0, _('Shelter, Housing And Settlements')
3733
LIVELIHOODS = 1, _('Livelihoods')
3834
MULTI_PURPOSE_CASH = 2, _('Multi-purpose Cash')
@@ -67,7 +63,7 @@ def __str__(self):
6763

6864

6965
class EAP(models.Model):
70-
class Status(TextChoices): # TODO some more status choices are to be expected by client.
66+
class Status(models.TextChoices): # TODO some more status choices are to be expected by client.
7167
APPROVED = 'approved', _('Approved')
7268
IN_PROCESS = 'in_process', _('In Process')
7369

0 commit comments

Comments
 (0)