Skip to content

Commit 3c3247d

Browse files
Merge pull request #1571 from IFRCGo/develop
Optimistic Lock for DREF-s
2 parents 5ce01cb + 6dc6468 commit 3c3247d

File tree

16 files changed

+234
-139
lines changed

16 files changed

+234
-139
lines changed

CHANGELOG.md

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

77
## Unreleased
88

9+
## 1.1.458
10+
11+
### Added
12+
- Optimistic Lock for DREF-s
13+
- Affected Figures on FR/Emergencies
14+
- DREF historical data on admin page (even via frontend input)
15+
916
## 1.1.457
1017

1118
### Added
@@ -2072,7 +2079,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
20722079

20732080
## 0.1.20
20742081

2075-
[Unreleased]: https://github.com/IFRCGo/go-api/compare/1.1.457...HEAD
2082+
[Unreleased]: https://github.com/IFRCGo/go-api/compare/1.1.458...HEAD
2083+
[1.1.458]: https://github.com/IFRCGo/go-api/compare/1.1.457...1.1.458
20762084
[1.1.457]: https://github.com/IFRCGo/go-api/compare/1.1.456...1.1.457
20772085
[1.1.456]: https://github.com/IFRCGo/go-api/compare/1.1.455...1.1.456
20782086
[1.1.455]: https://github.com/IFRCGo/go-api/compare/1.1.454...1.1.455

api/drf_views.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,19 @@ def retrieve(self, request, pk=None, *args, **kwargs):
446446
raise BadRequest('Emergency ID or Slug parameters are missing')
447447

448448
serializer = self.get_serializer(instance)
449+
450+
# Hide the "affected" values that are kept only for history – see (¤) in other code parts
451+
if 'field_reports' in serializer.data:
452+
for j, fr in enumerate(serializer.data['field_reports']):
453+
if 'recent_affected' in fr: # should always be True
454+
for i, field in enumerate([
455+
'num_affected', 'gov_num_affected', 'other_num_affected',
456+
'num_potentially_affected', 'gov_num_potentially_affected', 'other_num_potentially_affected']):
457+
if fr['recent_affected'] - 1 != i and field in serializer.data['field_reports'][j]:
458+
del serializer.data['field_reports'][j][field]
459+
del serializer.data['field_reports'][j]['recent_affected']
460+
461+
449462
return Response(serializer.data)
450463

451464
@action(methods=['get'], detail=False, url_path='mini')
@@ -722,7 +735,7 @@ def serialize(self, data, instance=None):
722735
else:
723736
data['visibility'] = VisibilityChoices.MEMBERSHIP
724737

725-
# Set RecentAffected according to the sent _affected key
738+
# Set RecentAffected according to the sent _affected key – see (¤) in other code parts
726739
if 'status' in data and data['status'] == FieldReport.Status.EW: # Early Warning
727740
if 'num_potentially_affected' in data:
728741
data['recent_affected'] = FieldReport.RecentAffected.RCRC_POTENTIALLY

api/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ class RecentAffected(models.IntegerChoices):
11651165
RCRC_POTENTIALLY = 4, _('Red Cross / Red Crescent, potentially')
11661166
GOVERNMENT_POTENTIALLY = 5, _('Government, potentially')
11671167
OTHER_POTENTIALLY = 6, _('Other, potentially')
1168-
# Take care, these values are connected to (¤) in serializers.py, search it!
1168+
# Take care of these values – see (¤) in other code parts
11691169

11701170
user = models.ForeignKey(
11711171
settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name='user',

api/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ class Meta:
451451
'other_affected_pop_centres', 'epi_cases', 'epi_suspected_cases', 'epi_probable_cases', 'epi_confirmed_cases',
452452
'epi_figures_source', 'epi_figures_source_display', 'epi_cases_since_last_fr', 'epi_deaths_since_last_fr',
453453
'epi_notes_since_last_fr', 'visibility', 'visibility_display', 'request_assistance', 'ns_request_assistance',
454-
'notes_health', 'notes_ns', 'notes_socioeco'
454+
'notes_health', 'notes_ns', 'notes_socioeco', 'recent_affected'
455455
)
456456

457457

@@ -1067,7 +1067,7 @@ def __init__(self, *args, **kwargs):
10671067
'num_affected', 'gov_num_affected', 'other_num_affected',
10681068
'num_potentially_affected', 'gov_num_potentially_affected', 'other_num_potentially_affected']):
10691069
# We allow only 1 of these _affected values ^, pointed by RecentAffected. The other 5 gets 0 on client side.
1070-
# Attention! This indexing is related to RecentAffected values – in models.py: (¤)
1070+
# About "recent_affected - 1" as index see (¤) in other code parts:
10711071
if self.instance.recent_affected - 1 != i:
10721072
self.fields.pop(field)
10731073

deployments/snapshots/snap_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@
873873
'is_deprecated': False,
874874
'iso': 'HA',
875875
'iso3': 'xcS',
876-
'name': 'country-jYLXlNQGqkURvDMLeoyyigbmHGRAjMglENMcYIGWhfEQiMIaXR',
876+
'name': 'country-AMjYLXlNQGqkURvDMLeoyyigbmHGRAjMglENMcYIGWhfEQiMIa',
877877
'record_type': 4,
878878
'record_type_display': 'Country Office',
879879
'region': 3,

dref/admin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.contrib import admin
2+
from reversion_compare.admin import CompareVersionAdmin
23

34
from lang.admin import TranslationAdmin
45
from .models import (
@@ -15,7 +16,7 @@ class DrefFileAdmin(admin.ModelAdmin):
1516

1617

1718
@admin.register(Dref)
18-
class DrefAdmin(TranslationAdmin, admin.ModelAdmin):
19+
class DrefAdmin(CompareVersionAdmin, TranslationAdmin, admin.ModelAdmin):
1920
search_fields = ('title',)
2021
list_display = ('title', 'national_society', 'disaster_type',
2122
'ns_request_date', 'submission_to_geneva', 'status',)
@@ -39,7 +40,7 @@ def get_queryset(self, request):
3940

4041

4142
@admin.register(DrefOperationalUpdate)
42-
class DrefOperationalUpdateAdmin(admin.ModelAdmin):
43+
class DrefOperationalUpdateAdmin(CompareVersionAdmin, admin.ModelAdmin):
4344
list_display = ('title', 'national_society', 'disaster_type')
4445
autocomplete_fields = (
4546
'national_society',
@@ -62,7 +63,7 @@ def get_queryset(self, request):
6263
)
6364

6465
@admin.register(DrefFinalReport)
65-
class DrefFinalReportAdmin(admin.ModelAdmin):
66+
class DrefFinalReportAdmin(CompareVersionAdmin, admin.ModelAdmin):
6667
list_display = ('title', 'national_society', 'disaster_type')
6768
autocomplete_fields = (
6869
'national_society',
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 3.2.16 on 2022-10-18 07:32
2+
3+
import datetime
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('dref', '0043_delete_dreffileupload'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='dref',
16+
name='modified_at',
17+
field=models.DateTimeField(blank=True, default=datetime.datetime.now, verbose_name='modified at'),
18+
),
19+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.16 on 2022-10-24 06:41
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('dref', '0044_alter_dref_modified_at'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='dref',
15+
name='modified_at',
16+
field=models.DateTimeField(auto_now=True, verbose_name='modified at'),
17+
),
18+
]

dref/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import reversion
22
import os
33
import copy
4+
from datetime import datetime
45

56
from pdf2image import convert_from_bytes
67

@@ -227,7 +228,11 @@ class Status(models.IntegerChoices):
227228
COMPLETED = 1, _('Completed')
228229

229230
created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True)
230-
modified_at = models.DateTimeField(verbose_name=_('modified at'), auto_now=True)
231+
modified_at = models.DateTimeField(
232+
verbose_name=_('modified at'),
233+
auto_now=True,
234+
blank=True
235+
)
231236
created_by = models.ForeignKey(
232237
settings.AUTH_USER_MODEL, verbose_name=_('created by'), on_delete=models.SET_NULL,
233238
null=True, related_name='created_by_dref'
@@ -653,6 +658,9 @@ def save(self, *args, **kwargs):
653658
self.__budget_file_id = self.budget_file_id
654659
super().save(*args, **kwargs)
655660

661+
def __str__(self):
662+
return f'{self.title}{self.created_at.date()}, {self.get_status_display()}'
663+
656664

657665
class DrefFile(models.Model):
658666
file = models.FileField(

dref/serializers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class DrefSerializer(
184184
ALLOWED_ASSESSMENT_REPORT_EXTENSIONS = ["pdf", "docx", "pptx"]
185185
MAX_OPERATION_TIMEFRAME = 30
186186
ASSESSMENT_REPORT_MAX_OPERATION_TIMEFRAME = 2
187+
DREF_UPDATE_ERROR_MESSAGE = "OBSOLETE_PAYLOAD"
187188
national_society_actions = NationalSocietyActionSerializer(many=True, required=False)
188189
needs_identified = IdentifiedNeedSerializer(many=True, required=False)
189190
planned_interventions = PlannedInterventionSerializer(many=True, required=False)
@@ -206,6 +207,7 @@ class DrefSerializer(
206207
assessment_report_details = DrefFileSerializer(source='assessment_report', read_only=True)
207208
supporting_document_details = DrefFileSerializer(read_only=True, source='supporting_document')
208209
risk_security = RiskSecuritySerializer(many=True, required=False)
210+
modified_at = serializers.DateTimeField(required=False)
209211

210212
class Meta:
211213
model = Dref
@@ -349,6 +351,9 @@ def create(self, validated_data):
349351
def update(self, instance, validated_data):
350352
validated_data['modified_by'] = self.context['request'].user
351353
is_assessment_report = validated_data.get('is_assessment_report')
354+
modified_at = validated_data.pop('modified_at', None)
355+
if modified_at is None:
356+
raise serializers.ValidationError({ 'modified_at': 'Modified At is required!' })
352357
if is_assessment_report:
353358
# Previous Operations
354359
validated_data['lessons_learned'] = None
@@ -374,12 +379,15 @@ def update(self, instance, validated_data):
374379
dref_assessment_report = super().update(instance, validated_data)
375380
dref_assessment_report.needs_identified.clear()
376381
return dref_assessment_report
382+
377383
# we don't send notification again to the already notified users:
378384
if 'users' in validated_data:
379385
to = {u.email for u in validated_data['users']
380386
if u.email not in {t.email for t in instance.users.iterator()}}
381387
else:
382388
to = None
389+
if modified_at and instance.modified_at and modified_at < instance.modified_at:
390+
raise serializers.ValidationError({ 'modified_at': self.DREF_UPDATE_ERROR_MESSAGE })
383391
dref = super().update(instance, validated_data)
384392
if to:
385393
transaction.on_commit(

0 commit comments

Comments
 (0)