Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b713e25
feat(event): make event source enum
sudip-khanal Apr 22, 2026
6e58c5f
feat(api): add new api endpoint for emergency
sudip-khanal Apr 24, 2026
942d9ab
feat(api): add related appeal id on emergency api endpoint
sudip-khanal Apr 28, 2026
e376cc4
feat(api): add script to update event source
sudip-khanal Apr 29, 2026
bbc5510
fix(test): fix emergency api test case
sudip-khanal May 12, 2026
fd4bf27
Merge pull request #2734 from IFRCGo/fix/emergency-api-test-case
susilnem May 12, 2026
02e3750
feat(api): update on emergency endpoint
susilnem May 13, 2026
62322d4
feat(dref): replace field report with event and update dref approve l…
susilnem May 13, 2026
0153186
feat(dref): add script to migrate events from appeal based on appeal …
susilnem May 13, 2026
1a3001e
feat(event): add latest field report on mini event endpoint
susilnem May 14, 2026
4829ff5
feat(emergency): add staging logic for emergency page
susilnem May 15, 2026
02f7b18
feat(emergency): Add new stage for appeal dref type
susilnem May 21, 2026
7288ddc
feat(emergency): add previous data on field report and dref ops update
susilnem May 22, 2026
95f5c3c
Merge pull request #2736 from IFRCGo/feature/attach-dref-emergency-page
susilnem May 25, 2026
ee0aea9
feat(emergency): Squash migration
susilnem May 25, 2026
3632101
feat(dref): trigger event translation for approved dref
susilnem May 28, 2026
ba2935f
feat(dref): add test cases for dref approved event translation
susilnem Jun 1, 2026
55ba9b7
Merge pull request #2754 from IFRCGo/feature/dref-event-trigger-trans…
susilnem Jun 2, 2026
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
46 changes: 7 additions & 39 deletions api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import api.models as models
from api.admin_classes import RegionRestrictedAdmin
from api.event_sources import SOURCES
from api.management.commands.index_and_notify import Command as Notify
from lang.admin import TranslationAdmin, TranslationInlineModelAdmin
from notifications.models import RecordType, SubscriptionType
Expand Down Expand Up @@ -158,38 +157,6 @@ def queryset(self, request, queryset):
return queryset.filter(is_featured=False)


class EventSourceFilter(admin.SimpleListFilter):
title = _("source")
parameter_name = "event_source"

def lookups(self, request, model_admin):
return (
("input", _("Manual input")),
("gdacs", _("GDACs scraper")),
("who", _("WHO scraper")),
("report_ingest", _("Field report ingest")),
("report_admin", _("Field report admin")),
("appeal_admin", _("Appeals admin")),
("unknown", _("Unknown automated")),
)

def queryset(self, request, queryset):
if self.value() == "input":
return queryset.filter(auto_generated=False)
elif self.value() == "gdacs":
return queryset.filter(auto_generated_source=SOURCES["gdacs"])
elif self.value() == "who":
return queryset.filter(auto_generated_source__startswith="www.who.int")
elif self.value() == "report_ingest":
return queryset.filter(auto_generated_source=SOURCES["report_ingest"])
elif self.value() == "report_admin":
return queryset.filter(auto_generated_source=SOURCES["report_admin"])
elif self.value() == "appeal_admin":
return queryset.filter(auto_generated_source=SOURCES["appeal_admin"])
elif self.value() == "unknown":
return queryset.filter(auto_generated=True).filter(auto_generated_source__isnull=True)


class DisasterTypeAdmin(CompareVersionAdmin, TranslationAdmin, admin.ModelAdmin):
search_fields = ("name",)

Expand Down Expand Up @@ -246,9 +213,9 @@ def level_updated_at(self, obj):
"cc_status",
"glide",
"auto_generated",
"auto_generated_source",
"source",
)
list_filter = [IsFeaturedFilter, EventSourceFilter]
list_filter = [IsFeaturedFilter, "source"]
actions = ["create_field_reports"]
search_fields = (
"name",
Expand Down Expand Up @@ -369,7 +336,7 @@ def changeform_view(self, request, object_id=None, form_url="", extra_context=No
self.readonly_fields = (
"appeals",
"field_reports",
"auto_generated_source",
"source",
"parent_event",
"created_at",
"updated_at",
Expand All @@ -378,9 +345,10 @@ def changeform_view(self, request, object_id=None, form_url="", extra_context=No
self.readonly_fields = (
"appeals",
"field_reports",
"auto_generated_source",
"source",
"created_at",
"updated_at",
"who_guid",
)

# Set severity level from GET parameter
Expand Down Expand Up @@ -651,7 +619,7 @@ def create_events(self, request, queryset):
dtype=getattr(report, "dtype"),
disaster_start_date=getattr(report, "created_at"),
auto_generated=True,
auto_generated_source=SOURCES["report_admin"],
source=models.Event.EventSource.FIELD_REPORT_ADMIN,
)
if getattr(report, "countries").exists():
for country in report.countries.all():
Expand Down Expand Up @@ -760,7 +728,7 @@ def create_events(self, request, queryset):
dtype=getattr(appeal, "dtype"),
disaster_start_date=getattr(appeal, "start_date"),
auto_generated=True,
auto_generated_source=SOURCES["appeal_admin"],
source=models.Event.EventSource.APPEAL_ADMIN,
)
if appeal.country is not None:
event.countries.add(appeal.country)
Expand Down
228 changes: 224 additions & 4 deletions api/drf_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
Avg,
Case,
Count,
Exists,
ExpressionWrapper,
F,
OuterRef,
Prefetch,
Q,
Subquery,
Sum,
Value,
When,
)
from django.db.models.fields import IntegerField
Expand Down Expand Up @@ -59,6 +61,7 @@
from databank.serializers import CountryOverviewSerializer
from deployments.models import ERU, Personnel
from deployments.serializers import ListDeployedERUByEventSerializer
from dref.models import Dref, DrefFinalReport, DrefOperationalUpdate
from main.enums import GlobalEnumSerializer, get_enum_values
from main.filters import NullsLastOrderingFilter
from main.permissions import DenyGuestUserMutationPermission, DenyGuestUserPermission
Expand All @@ -73,6 +76,7 @@
Appeal,
AppealDocument,
AppealHistory,
AppealStatus,
AppealType,
Country,
CountryKeyDocument,
Expand All @@ -85,10 +89,13 @@
Event,
EventContact,
EventFeaturedDocument,
EventLink,
EventSeverityLevelHistory,
EventStage,
Export,
ExternalPartner,
FieldReport,
KeyFigure,
MainContact,
Profile,
Region,
Expand Down Expand Up @@ -122,6 +129,7 @@
CountrySupportingPartnerSerializer,
CountryTableauSerializer,
DeploymentsByEventSerializer,
DetailEmergencySerializer,
DetailEventSerializer,
DisasterTypeSerializer,
DistrictSerializer,
Expand Down Expand Up @@ -747,11 +755,12 @@ def get_queryset(self, *args, **kwargs):
qset = super().get_queryset()
if self.action == "mini_events":
# return Event.objects.filter(parent_event__isnull=True).select_related('dtype')
return qset.filter(parent_event__isnull=True).select_related("dtype")
return qset.filter(parent_event__isnull=True).select_related("dtype").prefetch_related("countries_for_preview")

if self.action == "response_activity_events":
return (
qset.filter(parent_event__isnull=True)
.filter(Q(auto_generated=False) | Q(auto_generated_source="New field report"))
.filter(Q(auto_generated=False) | Q(source=Event.EventSource.NEW_FIELD_REPORT))
.select_related("dtype")
)
return (
Expand Down Expand Up @@ -867,7 +876,11 @@ def retrieve(self, request, pk=None, *args, **kwargs):
)
@action(methods=["get"], detail=False, url_path="mini")
def mini_events(self, request):
queryset = self.filter_queryset(self.get_queryset())
queryset = self.filter_queryset(self.get_queryset()).annotate(
latest_field_report_id=Subquery(
FieldReport.objects.filter(event=OuterRef("pk")).order_by("-updated_at").values("id")[:1]
)
)
serializer = ListMiniEventSerializer(queryset, many=True)
page = self.paginate_queryset(queryset)
if page is not None:
Expand Down Expand Up @@ -1349,7 +1362,7 @@ class SupportedActivityViewset(viewsets.ReadOnlyModelViewSet):
# summary=report.description or "",
# disaster_start_date=report.start_date,
# auto_generated=True,
# auto_generated_source=SOURCES["new_report"],
# source=Event.EventSource.NEW_FIELD_REPORT,
# visibility=report.visibility,
# **{TRANSLATOR_ORIGINAL_LANGUAGE_FIELD_NAME: django_get_language()},
# )
Expand Down Expand Up @@ -1555,3 +1568,210 @@ class CountrySupportingPartnerViewSet(viewsets.ModelViewSet):

def get_queryset(self):
return CountrySupportingPartner.objects.select_related("country")


class EmergencyViewset(
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
ReadOnlyVisibilityViewsetMixin,
):
queryset = Event.objects.all()
lookup_field = "id"
serializer_class = DetailEmergencySerializer
filterset_class = EventFilter

def get_queryset(self):
today = timezone.now().date()

appeal_priority_qs = (
Appeal.objects.filter(
event=OuterRef("pk"),
status__in=[AppealStatus.ACTIVE, AppealStatus.CLOSED],
)
.annotate(
priority=Case(
When(status=AppealStatus.ACTIVE, then=Value(1)),
When(status=AppealStatus.CLOSED, then=Value(2)),
output_field=IntegerField(),
)
)
.order_by("priority", "-start_date")
)

active_dref_appeal_qs = appeal_priority_qs.filter(
atype=AppealType.DREF,
)

active_emergency_appeal_qs = appeal_priority_qs.filter(
atype=AppealType.APPEAL,
)

field_report_qs = FieldReport.objects.filter(event=OuterRef("pk"))

approved_dref_qs = Dref.objects.filter(
event=OuterRef("pk"),
status=Dref.Status.APPROVED,
)

approved_ops_update_qs = DrefOperationalUpdate.objects.filter(
dref__event=OuterRef("pk"),
status=Dref.Status.APPROVED,
).order_by("-created_at")

approved_final_report_qs = DrefFinalReport.objects.filter(
dref__event=OuterRef("pk"),
status=Dref.Status.APPROVED,
).order_by("-created_at")

return (
super()
.get_queryset()
.annotate(
# Aggregated Values
response_activity_count=Count(
"emergency_projects",
distinct=True,
),
active_deployments_count=Count(
"personneldeployment__personnel",
filter=Q(
personneldeployment__personnel__type=Personnel.TypeChoices.RR,
personneldeployment__personnel__start_date__date__lte=today,
personneldeployment__personnel__end_date__date__gte=today,
personneldeployment__personnel__is_active=True,
),
distinct=True,
),
surge_alerts_count=Count(
"surgealert",
distinct=True,
),
# Stage
stage=Case(
When(
Exists(active_emergency_appeal_qs),
then=Value(EventStage.EMERGENCY_APPEAL),
),
When(
Exists(approved_final_report_qs),
then=Value(EventStage.DREF_FINAL_REPORT),
),
When(
Exists(approved_ops_update_qs),
then=Value(EventStage.DREF_OPERATIONAL_UPDATE),
),
When(
Exists(approved_dref_qs),
then=Value(EventStage.DREF_APPLICATION),
),
# If there is an active appeal of DREF type, but no approved DREF yet,
# we consider the emergency to be in the Dref Appeal only stage.
# Reaches here only if no approved DREF/ops-update/final-report exists
# So an active appeal type DREF appeal with no approved DREF = DREF_APPEAL_ONLY stage.
When(
Exists(active_dref_appeal_qs),
then=Value(EventStage.DREF_APPEAL_ONLY),
),
When(
Exists(FieldReport.objects.filter(event=OuterRef("pk"))),
then=Value(EventStage.FIELD_REPORT),
),
default=Value(None),
output_field=IntegerField(null=True),
),
)
.annotate(
# Passing values for the current stage's instance,
# to avoid extra queries in serializer.
stage_appeal_id=Case(
When(
stage=EventStage.EMERGENCY_APPEAL,
then=Subquery(active_emergency_appeal_qs.values("id")[:1]),
),
When(
stage=EventStage.DREF_APPEAL_ONLY,
then=Subquery(active_dref_appeal_qs.values("id")[:1]),
),
default=Value(None),
output_field=IntegerField(null=True),
),
stage_dref_id=Case(
When(
stage__in=[
EventStage.DREF_APPLICATION,
EventStage.DREF_OPERATIONAL_UPDATE,
EventStage.DREF_FINAL_REPORT,
],
then=Subquery(approved_dref_qs.values("id")[:1]),
),
default=Value(None),
output_field=IntegerField(null=True),
),
stage_final_report_id=Case(
When(
stage=EventStage.DREF_FINAL_REPORT,
then=Subquery(
DrefFinalReport.objects.filter(
dref__id=OuterRef("stage_dref_id"), status=Dref.Status.APPROVED
).values("id")[:1]
),
),
default=Value(None),
output_field=IntegerField(null=True),
),
stage_ops_update_id=Case(
When(
stage__in=[
EventStage.DREF_OPERATIONAL_UPDATE,
EventStage.DREF_FINAL_REPORT,
],
then=Subquery(
DrefOperationalUpdate.objects.filter(
dref__id=OuterRef("stage_dref_id"), status=Dref.Status.APPROVED
).values("id")[:1]
),
),
default=Value(None),
output_field=IntegerField(null=True),
),
stage_field_report_id=Case(
When(
stage=EventStage.FIELD_REPORT,
then=Subquery(field_report_qs.order_by("-updated_at", "-fr_num").values("id")[:1]),
),
default=Value(None),
output_field=IntegerField(null=True),
),
first_field_report_created_at=Subquery(field_report_qs.order_by("created_at", "fr_num").values("created_at")[:1]),
latest_field_report_created_at=Subquery(
field_report_qs.order_by("-created_at", "-fr_num").values("created_at")[:1]
),
)
.select_related("dtype")
.prefetch_related(
Prefetch(
"countries",
queryset=Country.objects.select_related("region"),
),
Prefetch(
"districts",
queryset=District.objects.select_related("country"),
),
Prefetch(
"key_figures",
queryset=KeyFigure.objects.all(),
),
Prefetch(
"contacts",
queryset=EventContact.objects.all(),
),
Prefetch(
"links",
queryset=EventLink.objects.all(),
),
Prefetch(
"featured_documents",
queryset=EventFeaturedDocument.objects.order_by("-id"),
),
)
)
Loading
Loading