Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6e0d11d
feat(eap): Add DevelopmentRegistration EAP model
susilnem Nov 4, 2025
6d06b88
feat(eap): Add DevelopmentRegistrationEAP Endpoint
susilnem Nov 4, 2025
9c4cfdb
feat(eap): Add EAP type and status for EAP Registration
susilnem Nov 5, 2025
d5fb920
chore(eap): Remove disaster type and national society filters from admin
susilnem Nov 5, 2025
6da7d0a
chore(eap): Add eap enums in global enums
susilnem Nov 5, 2025
67f42e9
feat(eap): Add Simplified EAP model
susilnem Nov 5, 2025
6846aa0
feat(eap): Add Base Model and serializer
susilnem Nov 6, 2025
dd93de1
feat(eap): Add simplified model, operational, actions
susilnem Nov 6, 2025
8bd0f45
feat(eap): Add test cases for eap registration and simplified
susilnem Nov 7, 2025
73aa050
feat(eap): Add Simplified Admin, FilterSet, Status update endpoints
susilnem Nov 8, 2025
e3a248d
feat(eap): Add validations, multiple file upload
susilnem Nov 11, 2025
268342e
feat(eap): Add status transition validations and permissions
susilnem Nov 12, 2025
1be3ac7
feat(eap): Add status transition, timeline and validated budget file
susilnem Nov 13, 2025
9821ded
feat(eap): Upload review checklist and active-eap endpoint
susilnem Nov 14, 2025
8102055
feat(eap): Add snapshot feature on simplified eap
susilnem Nov 19, 2025
c422cfc
feat(eap): Add snapshot feature and validation checks on status update
susilnem Nov 20, 2025
07297ad
feat(eap): add simplified eap to global pdf export
sudip-khanal Nov 19, 2025
f61a1f5
feat(eap): Add validation on operation timeframe and time_value
susilnem Nov 25, 2025
2da4822
feat(eap): update schema on updating eap file instance
susilnem Nov 25, 2025
b87d3f0
feat(eap): add full eap model
sudip-khanal Nov 20, 2025
0baae52
feat(eap): Update changes on Full EAP
susilnem Nov 21, 2025
012f547
chore(eap): Update filters on eap and update migration file
susilnem Nov 24, 2025
db3f637
feat(full_eap): Add snapshot feature and update on active EAPs
susilnem Nov 24, 2025
c03e1d4
feat(full-eap): Add test cases for full-eap
susilnem Nov 25, 2025
46c9801
fix(eap): Update test cases for simplified eap generate pdf
susilnem Nov 26, 2025
db3f4dc
feat(eap): Add full eap export pdf
susilnem Nov 26, 2025
0e10703
feat(eap): Update full eap fields and add new fields
susilnem Nov 26, 2025
0206f5e
feat(eap): add test cases for full eap, snapshot, active-eap
susilnem Nov 27, 2025
a447e04
Merge pull request #2595 from IFRCGo/feat/add-full-eap-model
susilnem Dec 3, 2025
a685718
chore(assest): Update asset commit head
susilnem Dec 4, 2025
3f77a55
feat(full-eap): Add new fields on full eap
susilnem Dec 5, 2025
f0f0637
feat(full-eap): Add new status and update on status transition
susilnem Dec 10, 2025
efcbeda
feat(full-eap): Add new field forecast table file
susilnem Dec 10, 2025
b3d372a
chore(eap): Update on active eaps endpoint
susilnem Dec 11, 2025
d622b2a
feat(eap): Add multiple validation checks for files
susilnem Dec 12, 2025
85a119b
chore(assets): Update assets commit reference
susilnem Dec 12, 2025
4da83f4
fix(eap): typing issue on eap actiona and source information
susilnem Dec 12, 2025
86af728
Merge pull request #2605 from IFRCGo/feature/add-new-field-full-eap
susilnem Dec 12, 2025
e16bab7
fix(eap-export): Update Export url for EAP
susilnem Dec 4, 2025
64ea825
feat(eap): Add diff and version tracking for pdf export
susilnem Dec 5, 2025
abc20b9
feat(eap): Update on Export url for eaps
susilnem Dec 12, 2025
c2beacf
fix(eap): Replace update checklist file to EAPFile
susilnem Dec 15, 2025
1c75306
Merge pull request #2606 from IFRCGo/fix/export-url-eap
susilnem Dec 15, 2025
cf7bb7e
fix(eap): Update export url on eap
susilnem Dec 15, 2025
971f3a1
chore(fulleap): Remove fields from fulleap model (#2614)
susilnem Dec 19, 2025
30f4589
chore(eap-registration): Update fields on eap registration
susilnem Dec 19, 2025
8aa45fa
EAP: Add api to download template files (#2619)
sudip-khanal Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions api/migrations/0227_alter_export_export_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.19 on 2025-11-18 05:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0226_nsdinitiativescategory_and_more'),
]

operations = [
migrations.AlterField(
model_name='export',
name='export_type',
field=models.CharField(choices=[('dref-applications', 'DREF Application'), ('dref-operational-updates', 'DREF Operational Update'), ('dref-final-reports', 'DREF Final Report'), ('old-dref-final-reports', 'Old DREF Final Report'), ('per', 'Per'), ('simplified-eap', 'Simplified EAP')], max_length=255, verbose_name='Export Type'),
),
]
29 changes: 29 additions & 0 deletions api/migrations/0228_alter_export_export_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.26 on 2025-11-26 10:03

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("api", "0227_alter_export_export_type"),
]

operations = [
migrations.AlterField(
model_name="export",
name="export_type",
field=models.CharField(
choices=[
("dref-applications", "DREF Application"),
("dref-operational-updates", "DREF Operational Update"),
("dref-final-reports", "DREF Final Report"),
("old-dref-final-reports", "Old DREF Final Report"),
("per", "Per"),
("simplified-eap", "Simplified EAP"),
("full-eap", "Full EAP"),
],
max_length=255,
verbose_name="Export Type",
),
),
]
29 changes: 29 additions & 0 deletions api/migrations/0229_alter_export_export_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.26 on 2025-12-04 09:06

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("api", "0228_alter_export_export_type"),
]

operations = [
migrations.AlterField(
model_name="export",
name="export_type",
field=models.CharField(
choices=[
("dref-applications", "DREF Application"),
("dref-operational-updates", "DREF Operational Update"),
("dref-final-reports", "DREF Final Report"),
("old-dref-final-reports", "Old DREF Final Report"),
("per", "Per"),
("simplified", "Simplified EAP"),
("full", "Full EAP"),
],
max_length=255,
verbose_name="Export Type",
),
),
]
2 changes: 2 additions & 0 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,8 @@ class ExportType(models.TextChoices):
FINAL_REPORT = "dref-final-reports", _("DREF Final Report")
OLD_FINAL_REPORT = "old-dref-final-reports", _("Old DREF Final Report")
PER = "per", _("Per")
SIMPLIFIED_EAP = "simplified", _("Simplified EAP")
FULL_EAP = "full", _("Full EAP")

export_id = models.IntegerField(verbose_name=_("Export Id"))
export_type = models.CharField(verbose_name=_("Export Type"), max_length=255, choices=ExportType.choices)
Expand Down
58 changes: 58 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from api.utils import CountryValidator, RegionValidator
from deployments.models import EmergencyProject, Personnel, PersonnelDeployment
from dref.models import Dref, DrefFinalReport, DrefOperationalUpdate
from eap.models import EAPRegistration, FullEAP, SimplifiedEAP
from lang.models import String
from lang.serializers import ModelSerializer
from local_units.models import DelegationOffice
Expand Down Expand Up @@ -2543,6 +2544,11 @@ class ExportSerializer(serializers.ModelSerializer):
status_display = serializers.CharField(source="get_status_display", read_only=True)
# NOTE: is_pga is used to determine if the export contains PGA or not
is_pga = serializers.BooleanField(default=False, required=False, write_only=True)
# NOTE: diff is used to determine if the export is requested for diff view or not
# Currently only used for EAP exports
diff = serializers.BooleanField(default=False, required=False, write_only=True)
# NOTE: Version of a EAP export being requested, only applicable for full and simplified EAP exports
version = serializers.IntegerField(required=False, write_only=True)

class Meta:
model = Export
Expand All @@ -2558,6 +2564,7 @@ def create(self, validated_data):
export_id = validated_data.get("export_id")
export_type = validated_data.get("export_type")
country_id = validated_data.get("per_country")
version = validated_data.pop("version", None)
if export_type == Export.ExportType.DREF:
title = Dref.objects.filter(id=export_id).first().title
elif export_type == Export.ExportType.OPS_UPDATE:
Expand All @@ -2567,12 +2574,63 @@ def create(self, validated_data):
elif export_type == Export.ExportType.PER:
overview = Overview.objects.filter(id=export_id).first()
title = f"{overview.country.name}-preparedness-{overview.get_phase_display()}"
elif export_type == Export.ExportType.SIMPLIFIED_EAP:
if version:
simplified_eap = SimplifiedEAP.objects.filter(
eap_registration=export_id,
version=version,
).first()
if not simplified_eap:
raise serializers.ValidationError("No Simplified EAP found for the given EAP Registration ID and version")
else:
eap_registration = EAPRegistration.objects.filter(id=export_id).first()
if not eap_registration:
raise serializers.ValidationError("No EAP Registration found for the given ID")

simplified_eap = eap_registration.latest_simplified_eap
if not simplified_eap:
serializers.ValidationError("No Simplified EAP found for the given EAP Registration ID")

title = (
f"{simplified_eap.eap_registration.national_society.name}-{simplified_eap.eap_registration.disaster_type.name}"
)
elif export_type == Export.ExportType.FULL_EAP:
if version:
full_eap = FullEAP.objects.filter(
eap_registration=export_id,
version=version,
).first()
if not full_eap:
raise serializers.ValidationError("No Full EAP found for the given EAP Registration ID and version")
else:
eap_registration = EAPRegistration.objects.filter(id=export_id).first()
if not eap_registration:
raise serializers.ValidationError("No EAP Registration found for the given ID")

full_eap = eap_registration.latest_full_eap
if not full_eap:
serializers.ValidationError("No Full EAP found for the given EAP Registration ID")

title = f"{full_eap.eap_registration.national_society.name}-{full_eap.eap_registration.disaster_type.name}"
else:
title = "Export"
user = self.context["request"].user

if export_type == Export.ExportType.PER:
validated_data["url"] = f"{settings.GO_WEB_INTERNAL_URL}/countries/{country_id}/{export_type}/{export_id}/export/"

elif export_type in [
Export.ExportType.SIMPLIFIED_EAP,
Export.ExportType.FULL_EAP,
]:
validated_data["url"] = f"{settings.GO_WEB_INTERNAL_URL}/eap/{export_id}/export/"
# NOTE: EAP exports with diff view only for EAPs exports
if version:
validated_data["url"] += f"?version={version}"
diff = validated_data.pop("diff")
if diff:
validated_data["url"] += "&diff=true" if version else "?diff=true"

else:
validated_data["url"] = f"{settings.GO_WEB_INTERNAL_URL}/{export_type}/{export_id}/export/"

Expand Down
4 changes: 4 additions & 0 deletions api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ def generate_url(url, export_id, user, title, language):
page.wait_for_selector("#pdf-preview-ready", state="attached", timeout=timeout)
if export.export_type == Export.ExportType.PER:
file_name = f'PER {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
elif export.export_type == Export.ExportType.SIMPLIFIED_EAP:
file_name = f'SIMPLIFIED EAP {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
elif export.export_type == Export.ExportType.FULL_EAP:
file_name = f'FULL EAP {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
else:
file_name = f'DREF {title} ({datetime.now().strftime("%Y-%m-%d %H-%M-%S")}).pdf'
file = ContentFile(
Expand Down
2 changes: 1 addition & 1 deletion docs/go-artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This is a **GitHub repository** used to store and manage **build files**, **gene

Command
```bash
docker compose run --rm serve ./manage.py spectacular --file .assets/openapi-schema.yaml
docker compose run --rm serve ./manage.py spectacular --file ./assets/openapi-schema.yaml
```

### 🚨 Manually added files
Expand Down
169 changes: 168 additions & 1 deletion eap/admin.py
Original file line number Diff line number Diff line change
@@ -1 +1,168 @@
# Register your models here.
from django.contrib import admin

from eap.models import EAPFile, EAPRegistration, FullEAP, KeyActor, SimplifiedEAP


@admin.register(EAPFile)
class EAPFileAdmin(admin.ModelAdmin):
search_fields = ("caption",)


@admin.register(EAPRegistration)
class DevelopmentRegistrationEAPAdmin(admin.ModelAdmin):
list_select_related = True
search_fields = (
"national_society__name",
"country__name",
"disaster_type__name",
)
list_filter = ("eap_type",)
list_display = (
"national_society_name",
"country",
"eap_type",
"disaster_type",
)
autocomplete_fields = (
"national_society",
"disaster_type",
"partners",
"created_by",
"modified_by",
)

def national_society_name(self, obj):
return obj.national_society.society_name

national_society_name.short_description = "National Society (NS)"

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.select_related(
"national_society",
"country",
"disaster_type",
"created_by",
"modified_by",
)
.prefetch_related(
"partners",
)
)


@admin.register(SimplifiedEAP)
class SimplifiedEAPAdmin(admin.ModelAdmin):
list_select_related = True
search_fields = (
"eap_registration__country__name",
"eap_registration__disaster_type__name",
)
list_display = ("simplifed_eap_application", "version", "is_locked")
autocomplete_fields = (
"eap_registration",
"created_by",
"modified_by",
"admin2",
)
readonly_fields = (
"cover_image",
"hazard_impact_images",
"risk_selected_protocols_images",
"selected_early_actions_images",
"planned_operations",
"enable_approaches",
"parent",
"is_locked",
"version",
)

def simplifed_eap_application(self, obj):
return f"{obj.eap_registration.national_society.society_name} - {obj.eap_registration.disaster_type.name}"

simplifed_eap_application.short_description = "Simplified EAP Application"

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.select_related(
"created_by",
"modified_by",
"eap_registration__country",
"eap_registration__national_society",
"eap_registration__disaster_type",
)
.prefetch_related(
"admin2",
)
)


@admin.register(KeyActor)
class KeyActorAdmin(admin.ModelAdmin):
list_display = ("national_society",)


@admin.register(FullEAP)
class FullEAPAdmin(admin.ModelAdmin):
list_select_related = True
search_fields = (
"eap_registration__country__name",
"eap_registration__disaster_type__name",
)
list_display = ("eap_registration",)
autocomplete_fields = (
"eap_registration",
"created_by",
"modified_by",
"admin2",
)
readonly_fields = (
"cover_image",
"planned_operations",
"enable_approaches",
"planned_operations",
"hazard_selection_images",
"theory_of_change_table_file",
"exposed_element_and_vulnerability_factor_images",
"prioritized_impact_images",
"risk_analysis_relevant_files",
"forecast_selection_images",
"definition_and_justification_impact_level_images",
"identification_of_the_intervention_area_images",
"trigger_model_relevant_files",
"early_action_selection_process_images",
"evidence_base_relevant_files",
"early_action_implementation_images",
"trigger_activation_system_images",
"activation_process_relevant_files",
"meal_relevant_files",
"capacity_relevant_files",
"forecast_table_file",
)

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.select_related(
"created_by",
"modified_by",
"cover_image",
"eap_registration__country",
"eap_registration__national_society",
"eap_registration__disaster_type",
)
.prefetch_related(
"admin2",
"key_actors",
"risk_analysis_source_of_information",
"trigger_statement_source_of_information",
"trigger_model_source_of_information",
"evidence_base_source_of_information",
"activation_process_source_of_information",
)
)
13 changes: 13 additions & 0 deletions eap/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from . import models

enum_register = {
"eap_status": models.EAPStatus,
"eap_type": models.EAPType,
"sector": models.PlannedOperation.Sector,
"timeframe": models.TimeFrame,
"years_timeframe_value": models.YearsTimeFrameChoices,
"months_timeframe_value": models.MonthsTimeFrameChoices,
"days_timeframe_value": models.DaysTimeFrameChoices,
"hours_timeframe_value": models.HoursTimeFrameChoices,
"approach": models.EnableApproach.Approach,
}
Loading
Loading