Skip to content

Commit ab80fae

Browse files
Merge pull request #1606 from IFRCGo/feature/optimistic-lock-op-update
Add optimistic lock in dref-op-update
2 parents 731f99c + d927918 commit ab80fae

File tree

4 files changed

+57
-4
lines changed

4 files changed

+57
-4
lines changed

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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ def test_dref_operational_update_patch(self):
620620
'new_operational_end_date': '2022-10-10',
621621
'reporting_timeframe': '2022-10-16',
622622
'is_timeframe_extension_required': True,
623+
'modified_at': datetime.now() + timedelta(days=12),
623624
}
624625
url = f'/api/v2/dref-op-update/{response_id}/'
625626
self.authenticate(user=user1)
@@ -920,3 +921,42 @@ def test_dref_latest_update(self):
920921
self.assertEqual(response.status_code, 200)
921922
# Title should be latest since modified_at is greater than modified_at in database
922923
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)

main/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,6 @@
467467

468468
# MISC
469469
FRONTEND_URL = env('FRONTEND_URL')
470+
471+
472+
DREF_OP_UPDATE_UPDATE_ERROR_MESSAGE = "OBSOLETE_PAYLOAD"

0 commit comments

Comments
 (0)