Skip to content

Commit 2df4bd7

Browse files
committed
feat(eap): Remove required fields and add feedbacks
- changes on enums - add sourceinformation for meal and ns capacity
1 parent de42bff commit 2df4bd7

6 files changed

Lines changed: 143 additions & 34 deletions

File tree

assets

eap/factories.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class Meta:
8282
readiness_budget = fuzzy.FuzzyInteger(1000, 1000000)
8383
pre_positioning_budget = fuzzy.FuzzyInteger(1000, 1000000)
8484
early_action_budget = fuzzy.FuzzyInteger(1000, 1000000)
85-
people_targeted = fuzzy.FuzzyInteger(100, 100000)
85+
people_targeted = fuzzy.FuzzyInteger(2001, 100000)
8686
seap_lead_timeframe_unit = fuzzy.FuzzyInteger(TimeFrame.MONTHS)
8787
seap_lead_time = fuzzy.FuzzyInteger(1, 12)
8888
operational_timeframe = fuzzy.FuzzyInteger(1, 12)
@@ -222,7 +222,7 @@ class Meta:
222222
readiness_budget = fuzzy.FuzzyInteger(1000, 1000000)
223223
pre_positioning_budget = fuzzy.FuzzyInteger(1000, 1000000)
224224
early_action_budget = fuzzy.FuzzyInteger(1000, 1000000)
225-
people_targeted = fuzzy.FuzzyInteger(100, 100000)
225+
people_targeted = fuzzy.FuzzyInteger(10001, 10000000)
226226
national_society_contact_name = fuzzy.FuzzyText(length=10, prefix="NS-")
227227
national_society_contact_email = factory.LazyAttribute(lambda obj: f"{obj.national_society_contact_name.lower()}@example.com")
228228
ifrc_delegation_focal_point_name = fuzzy.FuzzyText(length=10, prefix="IFRC-")
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Generated by Django 4.2.28 on 2026-02-27 09:28
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("eap", "0005_merge_20260213_0909"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="fulleap",
14+
name="meal_source_of_information",
15+
field=models.ManyToManyField(
16+
blank=True,
17+
related_name="meal_source_of_information",
18+
to="eap.sourceinformation",
19+
verbose_name="meal source of information",
20+
),
21+
),
22+
migrations.AddField(
23+
model_name="fulleap",
24+
name="ns_capacity_source_of_information",
25+
field=models.ManyToManyField(
26+
blank=True,
27+
related_name="ns_capacity_source_of_information",
28+
to="eap.sourceinformation",
29+
verbose_name="ns_capacity source of information",
30+
),
31+
),
32+
migrations.AlterField(
33+
model_name="fulleap",
34+
name="is_worked_with_government",
35+
field=models.BooleanField(
36+
blank=True,
37+
default=False,
38+
null=True,
39+
verbose_name="Has Worked with government or other relevant actors.",
40+
),
41+
),
42+
migrations.AlterField(
43+
model_name="fulleap",
44+
name="people_targeted",
45+
field=models.IntegerField(
46+
blank=True, null=True, verbose_name="People Targeted."
47+
),
48+
),
49+
migrations.AlterField(
50+
model_name="plannedoperation",
51+
name="ap_code",
52+
field=models.IntegerField(verbose_name="AP Code"),
53+
),
54+
migrations.AlterField(
55+
model_name="plannedoperation",
56+
name="sector",
57+
field=models.IntegerField(
58+
choices=[
59+
(101, "Shelter, Settlement and Housing"),
60+
(102, "Livelihoods"),
61+
(103, "Protection, Gender and Inclusion"),
62+
(104, "Health and Care"),
63+
(105, "Risk Reduction, Climate Adaptation and Recovery"),
64+
(106, "Multipurpose Cash"),
65+
(107, "Water, Sanitation And Hygiene"),
66+
(108, "WASH"),
67+
(109, "Education"),
68+
(110, "Migration"),
69+
(111, "Environment Sustainability"),
70+
(112, "Community Engagement And Accountability"),
71+
],
72+
verbose_name="sector",
73+
),
74+
),
75+
migrations.AlterField(
76+
model_name="simplifiedeap",
77+
name="people_targeted",
78+
field=models.IntegerField(
79+
blank=True, null=True, verbose_name="People Targeted."
80+
),
81+
),
82+
]

eap/models.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,25 +379,23 @@ def __str__(self):
379379

380380
class PlannedOperation(models.Model):
381381
class Sector(models.IntegerChoices):
382-
SHELTER = 101, _("Shelter")
383-
SETTLEMENT_AND_HOUSING = 102, _("Settlement and Housing")
384-
LIVELIHOODS = 103, _("Livelihoods")
385-
PROTECTION_GENDER_AND_INCLUSION = 104, _("Protection, Gender and Inclusion")
386-
HEALTH_AND_CARE = 105, _("Health and Care")
387-
RISK_REDUCTION = 106, _("Risk Reduction")
388-
CLIMATE_ADAPTATION_AND_RECOVERY = 107, _("Climate Adaptation and Recovery")
389-
MULTIPURPOSE_CASH = 108, _("Multipurpose Cash")
390-
WATER_SANITATION_AND_HYGIENE = 109, _("Water, Sanitation And Hygiene")
391-
WASH = 110, _("WASH")
392-
EDUCATION = 111, _("Education")
393-
MIGRATION = 112, _("Migration")
394-
ENVIRONMENT_SUSTAINABILITY = 113, _("Environment Sustainability")
395-
COMMUNITY_ENGAGEMENT_AND_ACCOUNTABILITY = 114, _("Community Engagement And Accountability")
382+
SHELTER_SETTLEMENT_AND_HOUSING = 101, _("Shelter, Settlement and Housing")
383+
LIVELIHOODS = 102, _("Livelihoods")
384+
PROTECTION_GENDER_AND_INCLUSION = 103, _("Protection, Gender and Inclusion")
385+
HEALTH_AND_CARE = 104, _("Health and Care")
386+
RISK_REDUCTION_CLIMATE_ADAPTATION_AND_RECOVERY = 105, _("Risk Reduction, Climate Adaptation and Recovery")
387+
MULTIPURPOSE_CASH = 106, _("Multipurpose Cash")
388+
WATER_SANITATION_AND_HYGIENE = 107, _("Water, Sanitation And Hygiene")
389+
WASH = 108, _("WASH")
390+
EDUCATION = 109, _("Education")
391+
MIGRATION = 110, _("Migration")
392+
ENVIRONMENT_SUSTAINABILITY = 111, _("Environment Sustainability")
393+
COMMUNITY_ENGAGEMENT_AND_ACCOUNTABILITY = 112, _("Community Engagement And Accountability")
396394

397395
sector = models.IntegerField(choices=Sector.choices, verbose_name=_("sector"))
398396
people_targeted = models.IntegerField(verbose_name=_("People Targeted"))
399397
budget_per_sector = models.IntegerField(verbose_name=_("Budget per sector (CHF)"))
400-
ap_code = models.IntegerField(verbose_name=_("AP Code"), null=True, blank=True)
398+
ap_code = models.IntegerField(verbose_name=_("AP Code"))
401399
previous_id = models.PositiveIntegerField(verbose_name=_("Previous ID"), null=True, blank=True)
402400

403401
indicators = models.ManyToManyField(
@@ -812,6 +810,8 @@ class CommonEAPFields(models.Model):
812810

813811
people_targeted = models.IntegerField(
814812
verbose_name=_("People Targeted."),
813+
blank=True,
814+
null=True,
815815
)
816816

817817
# Contacts
@@ -1343,6 +1343,8 @@ class FullEAP(EAPBaseModel, CommonEAPFields):
13431343
is_worked_with_government = models.BooleanField(
13441344
verbose_name=_("Has Worked with government or other relevant actors."),
13451345
default=False,
1346+
null=True,
1347+
blank=True,
13461348
)
13471349

13481350
worked_with_government_description = models.TextField(
@@ -1447,6 +1449,7 @@ class FullEAP(EAPBaseModel, CommonEAPFields):
14471449
)
14481450

14491451
# NOTE: In days
1452+
# TODO(susilnem): add unit for lead time
14501453
lead_time = models.IntegerField(
14511454
verbose_name=_("Lead Time"),
14521455
null=True,
@@ -1675,6 +1678,13 @@ class FullEAP(EAPBaseModel, CommonEAPFields):
16751678
related_name="full_eap_meal_files",
16761679
)
16771680

1681+
meal_source_of_information = models.ManyToManyField(
1682+
SourceInformation,
1683+
verbose_name=_("meal source of information"),
1684+
related_name="meal_source_of_information",
1685+
blank=True,
1686+
)
1687+
16781688
# NATIONAL SOCIETY CAPACITY
16791689
operational_administrative_capacity = models.TextField(
16801690
verbose_name=_("National Society Operational, thematic and administrative capacity"),
@@ -1701,6 +1711,13 @@ class FullEAP(EAPBaseModel, CommonEAPFields):
17011711
related_name="ns_capacity_relevant_files",
17021712
)
17031713

1714+
ns_capacity_source_of_information = models.ManyToManyField(
1715+
SourceInformation,
1716+
verbose_name=_("ns_capacity source of information"),
1717+
related_name="ns_capacity_source_of_information",
1718+
blank=True,
1719+
)
1720+
17041721
# FINANCE AND LOGISTICS
17051722

17061723
budget_description = models.TextField(

eap/serializers.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,9 @@ def get_fields(self):
495495
fields["cover_image_file"] = EAPFileUpdateSerializer(source="cover_image", required=False, allow_null=True)
496496
fields["planned_operations"] = PlannedOperationSerializer(many=True, required=False)
497497
fields["enabling_approaches"] = EnablingApproachSerializer(many=True, required=False)
498-
fields["budget_file"] = serializers.PrimaryKeyRelatedField(queryset=EAPFile.objects.all(), required=False)
498+
fields["budget_file"] = serializers.PrimaryKeyRelatedField(
499+
queryset=EAPFile.objects.all(), required=False, allow_null=True
500+
)
499501
fields["budget_file_details"] = EAPFileSerializer(source="budget_file", read_only=True)
500502
fields["updated_checklist_file_details"] = EAPFileSerializer(source="updated_checklist_file", read_only=True)
501503
return fields
@@ -556,6 +558,7 @@ class SimplifiedEAPSerializer(
556558
seap_lead_timeframe_unit_display = serializers.CharField(source="get_seap_lead_timeframe_unit_display", read_only=True)
557559
operational_timeframe_unit_display = serializers.CharField(source="get_operational_timeframe_unit_display", read_only=True)
558560

561+
people_targeted = serializers.IntegerField(min_value=2000, required=False, allow_null=True)
559562
# IMAGES
560563

561564
# NOTE: When adding new image fields, include their names in IMAGE_FIELDS below
@@ -580,9 +583,11 @@ def _validate_timeframe(self, data: dict[str, typing.Any]) -> None:
580583
seap_unit = data.get("seap_lead_timeframe_unit")
581584
seap_value = data.get("seap_lead_time")
582585

583-
if (seap_unit is None) != (seap_value is None):
586+
if seap_value is not None and seap_unit is None:
584587
raise serializers.ValidationError(
585-
{"seap_lead_timeframe_unit": gettext("seap lead timeframe and unit must both be provided.")}
588+
{
589+
"seap_lead_timeframe_unit": gettext("seap lead timeframe and unit must both be provided."),
590+
}
586591
)
587592

588593
if seap_unit is not None and seap_value is not None:
@@ -604,10 +609,11 @@ def _validate_timeframe(self, data: dict[str, typing.Any]) -> None:
604609
op_unit = data.get("operational_timeframe_unit")
605610
op_value = data.get("operational_timeframe")
606611

607-
# Require both if one is provided
608-
if (op_unit is None) != (op_value is None):
612+
if op_value is not None and op_unit is None:
609613
raise serializers.ValidationError(
610-
{"operational_timeframe_unit": gettext("operational timeframe and unit must both be provided.")}
614+
{
615+
"operational_timeframe_unit": gettext("operational timeframe and unit must both be provided."),
616+
}
611617
)
612618

613619
if op_unit is not None and op_value is not None:
@@ -679,12 +685,16 @@ class FullEAPSerializer(
679685
early_actions = EAPActionSerializer(many=True, required=True)
680686
prioritized_impacts = ImpactSerializer(many=True, required=True)
681687

688+
people_targeted = serializers.IntegerField(min_value=10000, required=False, allow_null=True)
689+
682690
# SOURCE OF INFORMATIONS
683691
risk_analysis_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
684692
trigger_statement_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
685693
trigger_model_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
686694
evidence_base_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
687695
activation_process_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
696+
meal_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
697+
ns_capacity_source_of_information = EAPSourceInformationSerializer(many=True, required=False, allow_null=True)
688698

689699
# IMAGES
690700
hazard_selection_images = EAPFileUpdateSerializer(
@@ -737,8 +747,8 @@ class FullEAPSerializer(
737747
forecast_table_file_details = EAPFileSerializer(source="forecast_table_file", read_only=True)
738748
forecast_table_file = serializers.PrimaryKeyRelatedField(
739749
queryset=EAPFile.objects.all(),
740-
required=True,
741-
allow_null=False,
750+
required=False,
751+
allow_null=True,
742752
)
743753

744754
theory_of_change_table_file_details = EAPFileSerializer(source="theory_of_change_table_file", read_only=True)

eap/test_views.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ def test_create_simplified_eap(self):
605605
"next_step_towards_full_eap": "Plan to expand.",
606606
"planned_operations": [
607607
{
608-
"sector": PlannedOperation.Sector.SETTLEMENT_AND_HOUSING,
608+
"sector": PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
609609
"ap_code": 111,
610610
"people_targeted": 10000,
611611
"budget_per_sector": 100000,
@@ -829,7 +829,7 @@ def test_update_simplified_eap(self):
829829

830830
# PLANNED OPERATION with activities
831831
planned_operation = PlannedOperationFactory.create(
832-
sector=PlannedOperation.Sector.SHELTER,
832+
sector=PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
833833
ap_code=456,
834834
people_targeted=5000,
835835
budget_per_sector=50000,
@@ -942,7 +942,7 @@ def test_update_simplified_eap(self):
942942
"planned_operations": [
943943
{
944944
"id": planned_operation.id,
945-
"sector": PlannedOperation.Sector.SHELTER,
945+
"sector": PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
946946
"ap_code": 456,
947947
"people_targeted": 8000,
948948
"budget_per_sector": 80000,
@@ -1268,7 +1268,7 @@ def test_status_transition(self):
12681268
)
12691269

12701270
planned_operation = PlannedOperationFactory.create(
1271-
sector=PlannedOperation.Sector.SHELTER,
1271+
sector=PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
12721272
ap_code=456,
12731273
people_targeted=5000,
12741274
budget_per_sector=50000,
@@ -1806,7 +1806,7 @@ def test_status_transitions_trigger_email(
18061806
)
18071807

18081808
planned_operation = PlannedOperationFactory.create(
1809-
sector=PlannedOperation.Sector.SHELTER,
1809+
sector=PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
18101810
ap_code=456,
18111811
people_targeted=5000,
18121812
budget_per_sector=50000,
@@ -2431,7 +2431,7 @@ def test_create_full_eap(self):
24312431
"readiness_budget": 3000,
24322432
"pre_positioning_budget": 4000,
24332433
"early_action_budget": 3000,
2434-
"people_targeted": 5000,
2434+
"people_targeted": 10000,
24352435
"key_actors": [
24362436
{
24372437
"national_society": self.national_society.id,
@@ -2474,7 +2474,7 @@ def test_create_full_eap(self):
24742474
"eap_endorsement": "EAP endorsement text",
24752475
"planned_operations": [
24762476
{
2477-
"sector": PlannedOperation.Sector.SETTLEMENT_AND_HOUSING,
2477+
"sector": PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
24782478
"ap_code": 111,
24792479
"people_targeted": 10000,
24802480
"budget_per_sector": 100000,
@@ -2689,7 +2689,7 @@ def test_snapshot_full_eap(self):
26892689
)
26902690

26912691
planned_operation = PlannedOperationFactory.create(
2692-
sector=PlannedOperation.Sector.SHELTER,
2692+
sector=PlannedOperation.Sector.SHELTER_SETTLEMENT_AND_HOUSING,
26932693
ap_code=456,
26942694
people_targeted=5000,
26952695
budget_per_sector=50000,

0 commit comments

Comments
 (0)