Skip to content

Commit 0206f5e

Browse files
committed
feat(eap): add test cases for full eap, snapshot, active-eap
- Add new fields for the seap timeframe and operations
1 parent 0e10703 commit 0206f5e

10 files changed

Lines changed: 811 additions & 263 deletions

File tree

api/tasks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ def generate_url(url, export_id, user, title, language):
120120
file_name = f'PER {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
121121
elif export.export_type == Export.ExportType.SIMPLIFIED_EAP:
122122
file_name = f'SIMPLIFIED EAP {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
123+
elif export.export_type == Export.ExportType.FULL_EAP:
124+
file_name = f'FULL EAP {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
123125
else:
124126
file_name = f'DREF {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
125127
file = ContentFile(

eap/enums.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
"eap_status": models.EAPStatus,
55
"eap_type": models.EAPType,
66
"sector": models.PlannedOperation.Sector,
7-
"timeframe": models.OperationActivity.TimeFrame,
8-
"years_timeframe_value": models.OperationActivity.YearsTimeFrameChoices,
9-
"months_timeframe_value": models.OperationActivity.MonthsTimeFrameChoices,
10-
"days_timeframe_value": models.OperationActivity.DaysTimeFrameChoices,
11-
"hours_timeframe_value": models.OperationActivity.HoursTimeFrameChoices,
7+
"timeframe": models.TimeFrame,
8+
"years_timeframe_value": models.YearsTimeFrameChoices,
9+
"months_timeframe_value": models.MonthsTimeFrameChoices,
10+
"days_timeframe_value": models.DaysTimeFrameChoices,
11+
"hours_timeframe_value": models.HoursTimeFrameChoices,
1212
"approach": models.EnableApproach.Approach,
1313
}

eap/factories.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
EAPType,
99
EnableApproach,
1010
FullEAP,
11+
KeyActor,
1112
OperationActivity,
1213
PlannedOperation,
1314
SimplifiedEAP,
15+
TimeFrame,
1416
)
1517

1618

@@ -65,6 +67,9 @@ class Meta:
6567
pre_positioning_budget = fuzzy.FuzzyInteger(1000, 1000000)
6668
early_action_budget = fuzzy.FuzzyInteger(1000, 1000000)
6769
people_targeted = fuzzy.FuzzyInteger(100, 100000)
70+
seap_lead_timeframe_unit = fuzzy.FuzzyInteger(TimeFrame.MONTHS)
71+
seap_lead_time = fuzzy.FuzzyInteger(1, 12)
72+
operational_timeframe = fuzzy.FuzzyInteger(1, 12)
6873

6974
@factory.post_generation
7075
def enable_approaches(self, create, extracted, **kwargs):
@@ -90,7 +95,7 @@ class Meta:
9095
model = OperationActivity
9196

9297
activity = fuzzy.FuzzyText(length=50, prefix="Activity-")
93-
timeframe = fuzzy.FuzzyChoice(OperationActivity.TimeFrame)
98+
timeframe = fuzzy.FuzzyChoice(TimeFrame)
9499

95100

96101
class EnableApproachFactory(factory.django.DjangoModelFactory):
@@ -167,6 +172,13 @@ def early_action_activities(self, create, extracted, **kwargs):
167172
self.early_action_activities.add(activity)
168173

169174

175+
class KeyActorFactory(factory.django.DjangoModelFactory):
176+
class Meta:
177+
model = KeyActor
178+
179+
description = fuzzy.FuzzyText(length=5, prefix="KeyActor-")
180+
181+
170182
class FullEAPFactory(factory.django.DjangoModelFactory):
171183
class Meta:
172184
model = FullEAP

eap/migrations/0009_sourceinformation_alter_simplifiedeap_admin2_and_more.py renamed to eap/migrations/0009_sourceinformation_and_more.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.26 on 2025-11-26 15:19
1+
# Generated by Django 4.2.19 on 2025-11-27 05:18
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -40,6 +40,25 @@ class Migration(migrations.Migration):
4040
"verbose_name_plural": "Source of Information",
4141
},
4242
),
43+
migrations.AddField(
44+
model_name="simplifiedeap",
45+
name="operational_timeframe_unit",
46+
field=models.IntegerField(
47+
choices=[(10, "Years"), (20, "Months"), (30, "Days"), (40, "Hours")],
48+
default=20,
49+
verbose_name="Operational Timeframe Unit",
50+
),
51+
),
52+
migrations.AddField(
53+
model_name="simplifiedeap",
54+
name="seap_lead_timeframe_unit",
55+
field=models.IntegerField(
56+
choices=[(10, "Years"), (20, "Months"), (30, "Days"), (40, "Hours")],
57+
default=20,
58+
verbose_name="sEAP Lead Timeframe Unit",
59+
),
60+
preserve_default=False,
61+
),
4362
migrations.AlterField(
4463
model_name="simplifiedeap",
4564
name="admin2",
@@ -69,11 +88,21 @@ class Migration(migrations.Migration):
6988
verbose_name="cover image",
7089
),
7190
),
91+
migrations.AlterField(
92+
model_name="simplifiedeap",
93+
name="operational_timeframe",
94+
field=models.IntegerField(verbose_name="Operational Time"),
95+
),
7296
migrations.AlterField(
7397
model_name="simplifiedeap",
7498
name="people_targeted",
7599
field=models.IntegerField(verbose_name="People Targeted."),
76100
),
101+
migrations.AlterField(
102+
model_name="simplifiedeap",
103+
name="seap_lead_time",
104+
field=models.IntegerField(verbose_name="sEAP Lead Time"),
105+
),
77106
migrations.AlterField(
78107
model_name="simplifiedeap",
79108
name="seap_timeframe",

eap/models.py

Lines changed: 115 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -232,80 +232,84 @@ class Meta:
232232
ordering = ["-id"]
233233

234234

235+
class TimeFrame(models.IntegerChoices):
236+
YEARS = 10, _("Years")
237+
MONTHS = 20, _("Months")
238+
DAYS = 30, _("Days")
239+
HOURS = 40, _("Hours")
240+
241+
242+
class YearsTimeFrameChoices(models.IntegerChoices):
243+
ONE_YEAR = 1, _("1")
244+
TWO_YEARS = 2, _("2")
245+
THREE_YEARS = 3, _("3")
246+
FOUR_YEARS = 4, _("4")
247+
FIVE_YEARS = 5, _("5")
248+
249+
250+
class MonthsTimeFrameChoices(models.IntegerChoices):
251+
ONE_MONTH = 1, _("1")
252+
TWO_MONTHS = 2, _("2")
253+
THREE_MONTHS = 3, _("3")
254+
FOUR_MONTHS = 4, _("4")
255+
FIVE_MONTHS = 5, _("5")
256+
SIX_MONTHS = 6, _("6")
257+
SEVEN_MONTHS = 7, _("7")
258+
EIGHT_MONTHS = 8, _("8")
259+
NINE_MONTHS = 9, _("9")
260+
TEN_MONTHS = 10, _("10")
261+
ELEVEN_MONTHS = 11, _("11")
262+
TWELVE_MONTHS = 12, _("12")
263+
264+
265+
class DaysTimeFrameChoices(models.IntegerChoices):
266+
ONE_DAY = 1, _("1")
267+
TWO_DAYS = 2, _("2")
268+
THREE_DAYS = 3, _("3")
269+
FOUR_DAYS = 4, _("4")
270+
FIVE_DAYS = 5, _("5")
271+
SIX_DAYS = 6, _("6")
272+
SEVEN_DAYS = 7, _("7")
273+
EIGHT_DAYS = 8, _("8")
274+
NINE_DAYS = 9, _("9")
275+
TEN_DAYS = 10, _("10")
276+
ELEVEN_DAYS = 11, _("11")
277+
TWELVE_DAYS = 12, _("12")
278+
THIRTEEN_DAYS = 13, _("13")
279+
FOURTEEN_DAYS = 14, _("14")
280+
FIFTEEN_DAYS = 15, _("15")
281+
SIXTEEN_DAYS = 16, _("16")
282+
SEVENTEEN_DAYS = 17, _("17")
283+
EIGHTEEN_DAYS = 18, _("18")
284+
NINETEEN_DAYS = 19, _("19")
285+
TWENTY_DAYS = 20, _("20")
286+
TWENTY_ONE_DAYS = 21, _("21")
287+
TWENTY_TWO_DAYS = 22, _("22")
288+
TWENTY_THREE_DAYS = 23, _("23")
289+
TWENTY_FOUR_DAYS = 24, _("24")
290+
TWENTY_FIVE_DAYS = 25, _("25")
291+
TWENTY_SIX_DAYS = 26, _("26")
292+
TWENTY_SEVEN_DAYS = 27, _("27")
293+
TWENTY_EIGHT_DAYS = 28, _("28")
294+
TWENTY_NINE_DAYS = 29, _("29")
295+
THIRTY_DAYS = 30, _("30")
296+
THIRTY_ONE_DAYS = 31, _("31")
297+
298+
299+
class HoursTimeFrameChoices(models.IntegerChoices):
300+
ZERO_TO_FIVE_HOURS = 5, _("0-5")
301+
FIVE_TO_TEN_HOURS = 10, _("5-10")
302+
TEN_TO_FIFTEEN_HOURS = 15, _("10-15")
303+
FIFTEEN_TO_TWENTY_HOURS = 20, _("15-20")
304+
TWENTY_TO_TWENTY_FIVE_HOURS = 25, _("20-25")
305+
TWENTY_FIVE_TO_THIRTY_HOURS = 30, _("25-30")
306+
307+
235308
class OperationActivity(models.Model):
236309
# NOTE: `timeframe` and `time_value` together represent the time span for an activity.
237310
# Make sure to keep them in sync.
238-
class TimeFrame(models.IntegerChoices):
239-
YEARS = 10, _("Years")
240-
MONTHS = 20, _("Months")
241-
DAYS = 30, _("Days")
242-
HOURS = 40, _("Hours")
243-
244-
class YearsTimeFrameChoices(models.IntegerChoices):
245-
ONE_YEAR = 1, _("1")
246-
TWO_YEARS = 2, _("2")
247-
THREE_YEARS = 3, _("3")
248-
FOUR_YEARS = 4, _("4")
249-
FIVE_YEARS = 5, _("5")
250-
251-
class MonthsTimeFrameChoices(models.IntegerChoices):
252-
ONE_MONTH = 1, _("1")
253-
TWO_MONTHS = 2, _("2")
254-
THREE_MONTHS = 3, _("3")
255-
FOUR_MONTHS = 4, _("4")
256-
FIVE_MONTHS = 5, _("5")
257-
SIX_MONTHS = 6, _("6")
258-
SEVEN_MONTHS = 7, _("7")
259-
EIGHT_MONTHS = 8, _("8")
260-
NINE_MONTHS = 9, _("9")
261-
TEN_MONTHS = 10, _("10")
262-
ELEVEN_MONTHS = 11, _("11")
263-
TWELVE_MONTHS = 12, _("12")
264-
265-
class DaysTimeFrameChoices(models.IntegerChoices):
266-
ONE_DAY = 1, _("1")
267-
TWO_DAYS = 2, _("2")
268-
THREE_DAYS = 3, _("3")
269-
FOUR_DAYS = 4, _("4")
270-
FIVE_DAYS = 5, _("5")
271-
SIX_DAYS = 6, _("6")
272-
SEVEN_DAYS = 7, _("7")
273-
EIGHT_DAYS = 8, _("8")
274-
NINE_DAYS = 9, _("9")
275-
TEN_DAYS = 10, _("10")
276-
ELEVEN_DAYS = 11, _("11")
277-
TWELVE_DAYS = 12, _("12")
278-
THIRTEEN_DAYS = 13, _("13")
279-
FOURTEEN_DAYS = 14, _("14")
280-
FIFTEEN_DAYS = 15, _("15")
281-
SIXTEEN_DAYS = 16, _("16")
282-
SEVENTEEN_DAYS = 17, _("17")
283-
EIGHTEEN_DAYS = 18, _("18")
284-
NINETEEN_DAYS = 19, _("19")
285-
TWENTY_DAYS = 20, _("20")
286-
TWENTY_ONE_DAYS = 21, _("21")
287-
TWENTY_TWO_DAYS = 22, _("22")
288-
TWENTY_THREE_DAYS = 23, _("23")
289-
TWENTY_FOUR_DAYS = 24, _("24")
290-
TWENTY_FIVE_DAYS = 25, _("25")
291-
TWENTY_SIX_DAYS = 26, _("26")
292-
TWENTY_SEVEN_DAYS = 27, _("27")
293-
TWENTY_EIGHT_DAYS = 28, _("28")
294-
TWENTY_NINE_DAYS = 29, _("29")
295-
THIRTY_DAYS = 30, _("30")
296-
THIRTY_ONE_DAYS = 31, _("31")
297-
298-
class HoursTimeFrameChoices(models.IntegerChoices):
299-
ZERO_TO_FIVE_HOURS = 5, _("0-5")
300-
FIVE_TO_TEN_HOURS = 10, _("5-10")
301-
TEN_TO_FIFTEEN_HOURS = 15, _("10-15")
302-
FIFTEEN_TO_TWENTY_HOURS = 20, _("15-20")
303-
TWENTY_TO_TWENTY_FIVE_HOURS = 25, _("20-25")
304-
TWENTY_FIVE_TO_THIRTY_HOURS = 30, _("25-30")
305-
306311
activity = models.CharField(max_length=255, verbose_name=_("Activity"))
307312
timeframe = models.IntegerField(choices=TimeFrame.choices, verbose_name=_("Timeframe"))
308-
# TODO(susilnem): Use enums for time_value?
309313
time_value = ArrayField(
310314
base_field=models.IntegerField(),
311315
verbose_name=_("Activity time span"),
@@ -950,16 +954,26 @@ class SimplifiedEAP(EAPBaseModel, CommonEAPFields):
950954
blank=True,
951955
)
952956

957+
# NOTE: seap_lead_timeframe_unit and seap_lead_time are atomic
958+
seap_lead_timeframe_unit = models.IntegerField(
959+
choices=TimeFrame.choices,
960+
verbose_name=_("sEAP Lead Timeframe Unit"),
961+
)
953962
seap_lead_time = models.IntegerField(
954-
verbose_name=_("sEAP Lead Time (Hours)"),
955-
null=True,
956-
blank=True,
963+
verbose_name=_("sEAP Lead Time"),
964+
)
965+
966+
# NOTE: operational_timeframe_unit and operational_time are atomic
967+
# operational_timeframe is set default to Months
968+
operational_timeframe_unit = models.IntegerField(
969+
choices=TimeFrame.choices,
970+
default=TimeFrame.MONTHS,
971+
verbose_name=_("Operational Timeframe Unit"),
957972
)
958973
operational_timeframe = models.IntegerField(
959-
verbose_name=_("Operational Timeframe (Months)"),
960-
null=True,
961-
blank=True,
974+
verbose_name=_("Operational Time"),
962975
)
976+
963977
trigger_threshold_justification = models.TextField(
964978
verbose_name=_("Trigger Threshold Justification"),
965979
help_text=_("Explain how the trigger were set and provide information"),
@@ -1054,9 +1068,13 @@ def generate_snapshot(self):
10541068
"modified_by_id": self.modified_by_id,
10551069
"updated_checklist_file": None,
10561070
},
1057-
exclude_clone_m2m_fields=[
1071+
exclude_clone_m2m_fields={
10581072
"admin2",
1059-
],
1073+
"cover_image",
1074+
"hazard_impact_images",
1075+
"risk_selected_protocols_images",
1076+
"selected_early_actions_images",
1077+
},
10601078
)
10611079

10621080
# Setting Parent as locked
@@ -1438,9 +1456,27 @@ def generate_snapshot(self):
14381456
"modified_by_id": self.modified_by_id,
14391457
"updated_checklist_file": None,
14401458
},
1441-
exclude_clone_m2m_fields=[
1459+
exclude_clone_m2m_fields={
14421460
"admin2",
1443-
],
1461+
"cover_image",
1462+
# Files
1463+
"hazard_selection_images",
1464+
"theory_of_change_table_file",
1465+
"exposed_element_and_vulnerability_factor_images",
1466+
"prioritized_impact_images",
1467+
"risk_analysis_relevant_files",
1468+
"forecast_selection_images",
1469+
"definition_and_justification_impact_level_images",
1470+
"identification_of_the_intervention_area_images",
1471+
"trigger_model_relevant_files",
1472+
"early_action_selection_process_images",
1473+
"evidence_base_relevant_files",
1474+
"early_action_implementation_images",
1475+
"trigger_activation_system_images",
1476+
"activation_process_relevant_files",
1477+
"meal_relevant_files",
1478+
"capacity_relevant_files",
1479+
},
14441480
)
14451481

14461482
# Setting Parent as locked

eap/permissions.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,24 @@ def has_permission(self, request, view) -> bool:
3131

3232
user = request.user
3333
national_society_id = request.data.get("national_society")
34-
return has_country_permission(user=user, national_society_id=national_society_id)
34+
return user.is_superuser or has_country_permission(user=user, national_society_id=national_society_id)
3535

3636

3737
class EAPBasePermission(BasePermission):
3838
message = "You don't have permission to create/update EAP"
3939

40-
def has_permission(self, request, view) -> bool:
40+
def has_object_permission(self, request, view, obj) -> bool:
4141
if request.method not in ["PUT", "PATCH", "POST"]:
4242
return True
4343

4444
user = request.user
45-
eap_registration = EAPRegistration.objects.filter(id=request.data.get("eap_registration")).first()
46-
47-
if not eap_registration:
48-
return False
45+
eap_reg_id = request.data.get("eap_registration", None) or obj.eap_registration_id
46+
eap_registration = EAPRegistration.objects.filter(id=eap_reg_id).first()
4947

48+
assert eap_registration is not None, "EAP Registration does not exist"
5049
national_society_id = eap_registration.national_society_id
5150

52-
return has_country_permission(
53-
user=user,
54-
national_society_id=national_society_id,
55-
)
51+
return user.is_superuser or has_country_permission(user=user, national_society_id=national_society_id)
5652

5753

5854
class EAPValidatedBudgetPermission(BasePermission):

0 commit comments

Comments
 (0)