Skip to content

Commit 708991f

Browse files
Merge pull request #1554 from IFRCGo/feature/dref-superuser-access
Allow superuser to view all dref
2 parents 09c3067 + 33e6fc7 commit 708991f

File tree

7 files changed

+174
-27
lines changed

7 files changed

+174
-27
lines changed

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,
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: 6 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'

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(

dref/test_views.py

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from unittest import mock
3+
from datetime import datetime, timedelta
34

45

56
from django.conf import settings
@@ -355,7 +356,8 @@ def test_update_dref_image(self):
355356
"file": file2.id,
356357
"caption": "Test Caption"
357358
}
358-
]
359+
],
360+
"modified_at": datetime.now()
359361
}
360362
self.client.force_authenticate(self.user)
361363
response = self.client.patch(url, data=data, format="json")
@@ -372,7 +374,8 @@ def test_update_dref_image(self):
372374
"file": file4.id,
373375
"caption": "Test Caption"
374376
}
375-
]
377+
],
378+
"modified_at": datetime.now()
376379
}
377380
self.client.force_authenticate(self.ifrc_user)
378381
response = self.client.patch(url, data, format='multipart')
@@ -385,7 +388,8 @@ def test_update_dref_image(self):
385388
"file": file4.id,
386389
"caption": "Test Caption"
387390
}
388-
]
391+
],
392+
"modified_at": datetime.now()
389393
}
390394
self.client.force_authenticate(self.ifrc_user)
391395
response = self.client.patch(url, data)
@@ -398,7 +402,8 @@ def test_update_dref_image(self):
398402
"file": file5.id,
399403
"caption": "Test Caption"
400404
}
401-
]
405+
],
406+
"modified_at": datetime.now()
402407
}
403408
self.client.force_authenticate(self.ifrc_user)
404409
response = self.client.patch(url, data)
@@ -436,31 +441,42 @@ def test_dref_options(self):
436441
self.assertIn('needs_identified', response.data)
437442
self.assertIn('national_society_actions', response.data)
438443

439-
def test_dref_is_published(self):
444+
@mock.patch('django.utils.timezone.now')
445+
def test_dref_is_published(self, mock_now):
440446
"""
441447
Test for dref if is_published = True
442448
"""
449+
initial_now = datetime(2011, 11, 11)
450+
mock_now.return_value = initial_now
451+
443452
dref = DrefFactory.create(
444-
title='test', created_by=self.user,
445-
is_published=True
453+
title='test',
454+
created_by=self.user,
455+
is_published=True,
446456
)
447457
url = f'/api/v2/dref/{dref.id}/'
448458
data = {
449-
"title" : "New Update Title"
459+
"title": "New Update Title",
460+
"modified_at": initial_now,
450461
}
451462
self.client.force_authenticate(self.user)
452463
response = self.client.patch(url, data)
453464
self.assert_400(response)
454465

455466
# create new dref with is_published = False
456467
not_published_dref = DrefFactory.create(
457-
title='test', created_by=self.user,
468+
title='test',
469+
created_by=self.user,
458470
)
459471
url = f'/api/v2/dref/{not_published_dref.id}/'
460472
self.client.force_authenticate(self.user)
461473
response = self.client.patch(url, data)
462474
self.assert_200(response)
463475

476+
data['modified_at'] = initial_now - timedelta(seconds=10)
477+
response = self.client.patch(url, data)
478+
self.assert_400(response)
479+
464480
# test dref published endpoint
465481
url = f'/api/v2/dref/{not_published_dref.id}/publish/'
466482
data = {}
@@ -795,3 +811,83 @@ def test_dref_for_assessment_report(self):
795811
response = self.client.post(url, data)
796812
self.assertEqual(response.status_code, 201)
797813
self.assertEqual(Dref.objects.count(), old_count + 1)
814+
815+
def test_dref_for_super_user(self):
816+
user1 = UserFactory.create(
817+
username='[email protected]',
818+
first_name='Test',
819+
last_name='User1',
820+
password='admin123',
821+
822+
is_superuser=True,
823+
)
824+
user2 = UserFactory.create(
825+
username='[email protected]',
826+
first_name='Test',
827+
last_name='User2',
828+
password='admin123',
829+
830+
)
831+
user3 = UserFactory.create(
832+
username='[email protected]',
833+
first_name='Test',
834+
last_name='User3',
835+
password='admin123',
836+
837+
)
838+
dref1 = DrefFactory.create(
839+
title='Test Title',
840+
created_by=user2,
841+
)
842+
DrefFactory.create(
843+
title='Test Title New',
844+
)
845+
846+
# authenticate with user1(superuser)
847+
# user1 should be able to view all dref
848+
url = '/api/v2/dref/'
849+
self.client.force_authenticate(user1)
850+
response = self.client.get(url)
851+
self.assertEqual(response.status_code, 200)
852+
self.assertEqual(len(response.data['results']), 2)
853+
854+
# authenticate with User2
855+
self.client.force_authenticate(user2)
856+
response = self.client.get(url)
857+
self.assertEqual(response.status_code, 200)
858+
self.assertEqual(len(response.data['results']), 1)
859+
self.assertEqual(response.data['results'][0]['id'], dref1.id)
860+
861+
# authenticate with User3
862+
self.client.force_authenticate(user3)
863+
response = self.client.get(url)
864+
self.assertEqual(response.status_code, 200)
865+
self.assertEqual(len(response.data['results']), 0)
866+
867+
def test_dref_latest_update(self):
868+
dref = DrefFactory.create(
869+
title='Test Title',
870+
created_by=self.user,
871+
modified_at=datetime(2022, 4, 18, 2, 29, 39, 793615)
872+
)
873+
url = f'/api/v2/dref/{dref.id}/'
874+
data = {
875+
'title': "New title",
876+
}
877+
878+
# without `modified_at`
879+
self.client.force_authenticate(self.user)
880+
response = self.client.patch(url, data=data)
881+
self.assertEqual(response.status_code, 400)
882+
883+
# with `modified_at` less than instance `modified_at`
884+
data['modified_at'] = datetime(2022, 2, 18, 2, 29, 39, 793615)
885+
self.client.force_authenticate(self.user)
886+
response = self.client.patch(url, data=data)
887+
self.assertEqual(response.status_code, 400)
888+
889+
data['modified_at'] = datetime.now()
890+
response = self.client.patch(url, data=data)
891+
self.assertEqual(response.status_code, 200)
892+
# Title should be latest since modified_at is greater than modified_at in database
893+
self.assertEqual(response.data['title'], "New title")

dref/views.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ class DrefViewSet(viewsets.ModelViewSet):
4040
filterset_class = DrefFilter
4141

4242
def get_queryset(self):
43-
return Dref.objects\
44-
.filter(
45-
models.Q(created_by=self.request.user) |
46-
models.Q(users=self.request.user)
47-
)\
48-
.prefetch_related(
49-
'planned_interventions',
50-
'needs_identified',
51-
'national_society_actions',
52-
'users'
53-
).order_by('-created_at').distinct()
43+
user = self.request.user
44+
queryset = Dref.objects.prefetch_related(
45+
'planned_interventions',
46+
'needs_identified',
47+
'national_society_actions',
48+
'users'
49+
).order_by('-created_at').distinct()
50+
if user.is_superuser:
51+
return queryset
52+
else:
53+
return queryset.filter(models.Q(created_by=user) | models.Q(users=user))
5454

5555
@action(
5656
detail=True,
@@ -74,10 +74,8 @@ class DrefOperationalUpdateViewSet(viewsets.ModelViewSet):
7474
filterset_class = DrefOperationalUpdateFilter
7575

7676
def get_queryset(self):
77-
return DrefOperationalUpdate.objects.filter(
78-
models.Q(created_by=self.request.user) |
79-
models.Q(users=self.request.user)
80-
).select_related(
77+
user = self.request.user
78+
queryset = DrefOperationalUpdate.objects.select_related(
8179
'national_society',
8280
'national_society',
8381
'disaster_type',
@@ -94,6 +92,10 @@ def get_queryset(self):
9492
'images',
9593
'photos',
9694
).order_by('-created_at').distinct()
95+
if user.is_superuser:
96+
return queryset
97+
else:
98+
return queryset.filter(models.Q(created_by=user) | models.Q(users=user))
9799

98100
@action(
99101
detail=True,
@@ -121,7 +123,6 @@ def get_queryset(self):
121123
'dref__needs_identified',
122124
).order_by('-created_at').distinct()
123125

124-
125126
@action(
126127
detail=True,
127128
url_path='publish',

0 commit comments

Comments
 (0)