diff --git a/assets b/assets index a393d4ffb..780b7a6c0 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a393d4ffbdf8f81b10ed899a688a37757b74df9b +Subproject commit 780b7a6c0efc7b6e353015308c701be2d7c2c419 diff --git a/dref/serializers.py b/dref/serializers.py index 2e2bb49f9..9a75ec5bd 100644 --- a/dref/serializers.py +++ b/dref/serializers.py @@ -2252,6 +2252,48 @@ class Meta(BaseDref3Serializer.Meta): model = DrefFinalReport +# Flatten the DREF's scalar contact fields into an api.EventContact-shaped list +# for the emergency page. Integrity contact is intentionally excluded. +DREF_EMERGENCY_CONTACT_GROUPS = ( + ("national_society_contact", "National Society Contact"), + ("ifrc_appeal_manager", "IFRC Appeal Manager"), + ("ifrc_project_manager", "IFRC Project Manager"), + ("ifrc_emergency", "IFRC Emergency"), + ("media_contact", "Media Contact"), +) + + +def get_dref_emergency_contacts(obj): + contacts = [] + for prefix, ctype in DREF_EMERGENCY_CONTACT_GROUPS: + name = getattr(obj, f"{prefix}_name", None) + title = getattr(obj, f"{prefix}_title", None) + email = getattr(obj, f"{prefix}_email", None) + phone = getattr(obj, f"{prefix}_phone_number", None) + if not any([name, title, email, phone]): + continue + contacts.append( + { + "id": f"{obj.id}-{prefix}", + "ctype": ctype, + "name": name or "", + "title": title or "", + "email": email or "", + "phone": phone or "", + } + ) + return contacts + + +class EmergencyDrefContactSerializer(serializers.Serializer): + id = serializers.CharField() + ctype = serializers.CharField() + name = serializers.CharField() + title = serializers.CharField() + email = serializers.CharField() + phone = serializers.CharField() + + # NOTE: This serializer is only used for the emergency page in GO, # which has a very specific and limited use case. # It is not intended to be a general-purpose serializer for DREF objects, @@ -2265,6 +2307,7 @@ class EmergencyDrefFinalReportSerializer(serializers.ModelSerializer): cover_image_file = DrefFileSerializer(source="cover_image", read_only=True) proposed_action = ProposedActionSerializer(many=True, required=False) disaster_type_details = DisasterTypeSerializer(source="disaster_type", read_only=True) + dref_contacts = serializers.SerializerMethodField() class Meta: model = DrefFinalReport @@ -2293,6 +2336,7 @@ class Meta: "number_of_people_targeted", "number_of_people_affected", "total_targeted_population", + "people_targeted_with_early_actions", "estimated_number_of_affected_male", "estimated_number_of_affected_female", "estimated_number_of_affected_girls_under_18", @@ -2308,8 +2352,13 @@ class Meta: "date_of_approval", "created_at", "modified_at", + "dref_contacts", ) + @extend_schema_field(EmergencyDrefContactSerializer(many=True)) + def get_dref_contacts(self, obj): + return get_dref_emergency_contacts(obj) + class TimelineEmergencyDrefOperationalUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -2318,6 +2367,8 @@ class Meta: "id", "summary_of_change", "date_of_approval", + # fallback timeline date when date_of_approval is unset + "modified_at", "operational_update_number", "total_targeted_population", "total_dref_allocation", @@ -2331,6 +2382,7 @@ class EmergencyDrefOperationalUpdateSerializer(serializers.ModelSerializer): planned_interventions = PlannedInterventionSerializer(many=True, read_only=True) cover_image_file = DrefFileSerializer(source="cover_image", required=False, allow_null=True) disaster_type_details = DisasterTypeSerializer(source="disaster_type", read_only=True) + dref_contacts = serializers.SerializerMethodField() class Meta: model = DrefOperationalUpdate @@ -2362,12 +2414,18 @@ class Meta: "number_of_people_targeted", "number_of_people_affected", "total_targeted_population", + "people_targeted_with_early_actions", "estimated_number_of_affected_male", "estimated_number_of_affected_female", "estimated_number_of_affected_girls_under_18", "estimated_number_of_affected_boys_under_18", + "dref_contacts", ) + @extend_schema_field(EmergencyDrefContactSerializer(many=True)) + def get_dref_contacts(self, obj): + return get_dref_emergency_contacts(obj) + class EmergencyDrefSerializer(serializers.ModelSerializer): status_display = serializers.CharField(source="get_status_display", read_only=True) @@ -2387,6 +2445,8 @@ class EmergencyDrefSerializer(serializers.ModelSerializer): # Timeline of operational updates timeline_operational_updates = serializers.SerializerMethodField() + dref_contacts = serializers.SerializerMethodField() + class Meta: model = Dref fields = ( @@ -2422,6 +2482,7 @@ class Meta: "amount_requested", "total_cost", "total_targeted_population", + "people_targeted_with_early_actions", "estimated_number_of_affected_male", "estimated_number_of_affected_female", "estimated_number_of_affected_girls_under_18", @@ -2435,17 +2496,26 @@ class Meta: # For Response Type "event_date", "timeline_operational_updates", + # Contacts + "dref_contacts", ) @extend_schema_field(TimelineEmergencyDrefOperationalUpdateSerializer(many=True)) def get_timeline_operational_updates(self, obj): - ops_updates = DrefOperationalUpdate.objects.filter(dref=obj).order_by("operational_update_number") + ops_updates = DrefOperationalUpdate.objects.filter( + dref=obj, + status=Dref.Status.APPROVED, + ).order_by("operational_update_number") serializer = TimelineEmergencyDrefOperationalUpdateSerializer( ops_updates, many=True, ) return serializer.data + @extend_schema_field(EmergencyDrefContactSerializer(many=True)) + def get_dref_contacts(self, obj): + return get_dref_emergency_contacts(obj) + @extend_schema_field(EmergencyDrefOperationalUpdateSerializer()) def get_operational_update_details(self, obj): if not obj.operational_update_id: diff --git a/dref/utils.py b/dref/utils.py index d7e04ccbb..3baa13521 100644 --- a/dref/utils.py +++ b/dref/utils.py @@ -1,6 +1,7 @@ from django.conf import settings from django.contrib.postgres.aggregates import ArrayAgg from django.db import models +from django.utils import timezone from api.models import Event from dref.models import Dref, DrefFinalReport, DrefOperationalUpdate @@ -57,7 +58,7 @@ def get_dref_users(): def create_event_from_dref(dref: Dref) -> Event: - event = Event.objects.create( + create_kwargs = dict( name=dref.title, dtype=dref.disaster_type, summary=dref.event_description or dref.event_scope or "", @@ -67,6 +68,14 @@ def create_event_from_dref(dref: Dref) -> Event: source=Event.EventSource.DREF, ) + # Dref.DisasterCategory and api.AlertLevel share the same 0/1/2 indices, so + # the value maps directly with no remap. + if dref.disaster_category is not None: + create_kwargs["ifrc_severity_level"] = dref.disaster_category + create_kwargs["ifrc_severity_level_update_date"] = dref.date_of_approval or timezone.now() + + event = Event.objects.create(**create_kwargs) + country = getattr(dref, "country", None) if country: event.countries.add(dref.country) diff --git a/dref/views.py b/dref/views.py index 0c0a47881..2633f9798 100644 --- a/dref/views.py +++ b/dref/views.py @@ -232,7 +232,8 @@ def get_approved(self, request, pk=None, version=None): raise serializers.ValidationError(gettext("Must be finalized before it can be approved.")) operational_update.status = Dref.Status.APPROVED - operational_update.save(update_fields=["status"]) + operational_update.date_of_approval = timezone.now().date() + operational_update.save(update_fields=["status", "date_of_approval"]) serializer = DrefOperationalUpdateSerializer(operational_update, context={"request": request}) return response.Response(serializer.data) @@ -367,7 +368,22 @@ class CompletedDrefOperationsViewSet(viewsets.ReadOnlyModelViewSet): DenyGuestUserPermission, ] filterset_class = CompletedDrefOperationsFilterSet - queryset = DrefFinalReport.objects.filter(status=Dref.Status.APPROVED).order_by("-created_at").distinct() + queryset = ( + DrefFinalReport.objects.filter(status=Dref.Status.APPROVED) + .select_related("country", "dref", "dref__country") + .prefetch_related( + # MiniDrefSerializer.operational_update_details reads this prefetched + # attr; without it DRF silently drops the field. + Prefetch( + "dref__drefoperationalupdate_set", + queryset=DrefOperationalUpdate.objects.select_related("country").order_by("-created_at"), + to_attr="prefetched_operational_updates", + ), + "dref__dreffinalreport__country", + ) + .order_by("-created_at") + .distinct() + ) def get_queryset(self): user = self.request.user