diff --git a/api/drf_views.py b/api/drf_views.py index 541566c84..4381c56e6 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -32,7 +32,7 @@ from api.filter_set import ( Admin2Filter, AppealDocumentFilter, - AppealHistoryFilter, + AppealFilter, CountryFilter, CountryFilterRMD, CountryKeyDocumentFilter, @@ -66,7 +66,6 @@ Admin2, Appeal, AppealDocument, - AppealHistory, AppealType, Country, CountryKeyDocument, @@ -97,8 +96,8 @@ Admin2Serializer, AppealDocumentSerializer, AppealDocumentTableauSerializer, - AppealHistorySerializer, - AppealHistoryTableauSerializer, + AppealSerializer, + AppealTableauSerializer, CountryDisasterTypeCountSerializer, CountryDisasterTypeMonthlySerializer, CountryGeoSerializer, @@ -262,24 +261,29 @@ def get_databank(self, request, pk): ) def get_country_figure(self, request, pk): country = self.get_object() - end_date = timezone.now() - start_date = end_date + timedelta(days=-2 * 365) - start_date = request.GET.get("start_date", start_date) - end_date = request.GET.get("end_date", end_date) - appeal_conditions = Q(atype=AppealType.APPEAL) - - all_appealhistory = AppealHistory.objects.select_related("appeal").filter(appeal__code__isnull=False) - all_appealhistory = all_appealhistory.filter(country=country) + start_date_to = timezone.now() + start_date_from = start_date_to + timedelta(days=-2 * 365) + start_date = request.GET.get("start_date_from", start_date_from) + end_date = request.GET.get("start_date_to", start_date_to) + appeal_conditions = Q(atype=AppealType.APPEAL) | Q(atype=AppealType.INTL) + + all_appeal = Appeal.objects.select_related("country").filter( + code__isnull=False, + ) + all_appeal = all_appeal.filter(country=country) if start_date and end_date: - all_appealhistory = all_appealhistory.filter(start_date__lte=end_date, end_date__gte=start_date) - appeals_aggregated = all_appealhistory.annotate( + all_appeal = all_appeal.filter( + start_date__lte=end_date, + start_date__gte=start_date, + ) + appeals_aggregated = all_appeal.annotate( appeal_with_dref=Count( Case( When(Q(atype=AppealType.DREF), then=1), output_field=IntegerField(), ) ), - appeal_without_dref=Count(Case(When(Q(atype=AppealType.APPEAL), then=1), output_field=IntegerField()), distinct=True), + appeal_without_dref=Count(Case(When(appeal_conditions, then=1), output_field=IntegerField())), total_population=( Case( When(appeal_conditions | Q(atype=AppealType.DREF), then=F("num_beneficiaries")), @@ -287,24 +291,38 @@ def get_country_figure(self, request, pk): ) ), amount_requested_all=( + Case( + When(appeal_conditions, then=F("amount_requested")), + output_field=IntegerField(), + ) + ), + amordref=( Case( When(appeal_conditions | Q(atype=AppealType.DREF), then=F("amount_requested")), output_field=IntegerField(), ) ), - amount_funded_all=( + amof=( + Case( + When(appeal_conditions, then=F("amount_funded")), + output_field=IntegerField(), + ) + ), + amofdref=( Case( When(appeal_conditions | Q(atype=AppealType.DREF), then=F("amount_funded")), output_field=IntegerField(), ) ), - emergencies_count=Count(F("appeal__event_id"), distinct=True), + emergencies_count=Count(F("event_id"), distinct=True), ).aggregate( active_drefs=Sum("appeal_with_dref"), active_appeals=Sum("appeal_without_dref"), target_population=Sum("total_population"), - total_amount_requested=Sum("amount_requested_all"), - total_amount_funded=Sum("amount_funded_all"), + amount_requested=Sum("amount_requested_all"), + amount_requested_dref_included=Sum("amordref"), + amount_funded=Sum("amof"), + amount_funded_dref_included=Sum("amofdref"), emergencies=Sum("emergencies_count"), ) return Response(CountryKeyFigureSerializer(appeals_aggregated).data) @@ -318,10 +336,10 @@ def get_country_figure(self, request, pk): @action(detail=True, url_path="disaster-count", pagination_class=None) def get_country_disaster_count(self, request, pk): country = self.get_object() - end_date = timezone.now() - start_date = end_date + timedelta(days=-2 * 365) - start_date = request.GET.get("start_date", start_date) - end_date = request.GET.get("end_date", end_date) + start_date_to = timezone.now() + start_date_from = start_date_to + timedelta(days=-2 * 365) + start_date = request.GET.get("start_date_from", start_date_from) + end_date = request.GET.get("start_date_to", start_date_to) queryset = ( Event.objects.filter( @@ -349,16 +367,16 @@ def get_country_disaster_count(self, request, pk): @action(detail=True, url_path="disaster-monthly-count", pagination_class=None) def get_country_disaster_monthly_count(self, request, pk): country = self.get_object() - end_date = timezone.now() - start_date = end_date + timedelta(days=-2 * 365) - start_date = request.GET.get("start_date", start_date) - end_date = request.GET.get("end_date", end_date) + start_date_to = timezone.now() + start_date_from = start_date_to + timedelta(days=-2 * 365) + start_date = request.GET.get("start_date_from", start_date_from) + end_date = request.GET.get("start_date_to", start_date_to) queryset = ( Event.objects.filter( countries__in=[country.id], dtype__isnull=False, ) - .annotate(date=TruncMonth("created_at")) + .annotate(date=TruncMonth("disaster_start_date")) .values("date", "countries", "dtype") .annotate( appeal_targeted_population=Coalesce( @@ -404,10 +422,10 @@ def get_country_disaster_monthly_count(self, request, pk): @action(detail=True, url_path="historical-disaster", pagination_class=None) def get_country_historical_disaster(self, request, pk): country = self.get_object() - end_date = timezone.now() - start_date = end_date + timedelta(days=-2 * 365) - start_date = request.GET.get("start_date", start_date) - end_date = request.GET.get("end_date", end_date) + start_date_to = timezone.now() + start_date_from = start_date_to + timedelta(days=-2 * 365) + start_date = request.GET.get("start_date_from", start_date_from) + end_date = request.GET.get("start_date_to", start_date_to) dtype = request.GET.get("dtype", None) queryset = ( @@ -415,7 +433,7 @@ def get_country_historical_disaster(self, request, pk): countries__in=[country.id], dtype__isnull=False, ) - .annotate(date=TruncMonth("created_at")) + .annotate(date=TruncMonth("disaster_start_date")) .values("date", "dtype", "countries") .annotate( appeal_targeted_population=Coalesce( @@ -744,27 +762,19 @@ def get_serializer_class(self): class AppealViewset(mixins.ListModelMixin, viewsets.GenericViewSet): """Used to get Appeals from AppealHistory. Has no 'read' option, just 'list'.""" - # queryset = Appeal.objects.select_related('dtype', 'country', 'region').all() - # queryset = AppealHistory.objects.select_related('appeal__event', 'dtype', 'country', 'region').all() - queryset = AppealHistory.objects.select_related("appeal__event", "dtype", "country", "region").filter( - appeal__code__isnull=False - ) - # serializer_class = AppealSerializer - serializer_class = AppealHistorySerializer + queryset = Appeal.objects.select_related("event", "dtype", "country", "region").filter(code__isnull=False) + serializer_class = AppealSerializer ordering_fields = "__all__" - # filterset_class = AppealFilter - filterset_class = AppealHistoryFilter + filterset_class = AppealFilter search_fields = ( - "appeal__name", + "name", "code", ) # for /docs def get_serializer_class(self): if is_tableau(self.request) is True: - return AppealHistoryTableauSerializer - # return AppealTableauSerializer - return AppealHistorySerializer - # return AppealSerializer + return AppealTableauSerializer + return AppealSerializer def remove_unconfirmed_event(self, obj): if obj["needs_confirmation"]: @@ -776,9 +786,7 @@ def remove_unconfirmed_events(self, objs): # Overwrite to exclude the events which require confirmation def list(self, request, *args, **kwargs): - now = timezone.now() - date = request.GET.get("date", now) - queryset = self.filter_queryset(self.get_queryset()).filter(valid_from__lt=date, valid_to__gt=date) + queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: diff --git a/api/filter_set.py b/api/filter_set.py index 81f5d70f9..397c8c2b1 100644 --- a/api/filter_set.py +++ b/api/filter_set.py @@ -204,14 +204,38 @@ class AppealFilter(filters.FilterSet): code = filters.CharFilter(field_name="code", lookup_expr="exact") status = filters.NumberFilter(field_name="status", lookup_expr="exact") id = filters.NumberFilter(field_name="id", lookup_expr="exact") + appeal_id = filters.NumberFilter( + field_name="appeal_id", lookup_expr="exact", help_text="Use this (or code) for appeal identification." + ) + district = filters.ModelMultipleChoiceFilter( + field_name="country__district", queryset=District.objects.all(), label="district", method="get_country_district" + ) + admin2 = filters.ModelMultipleChoiceFilter( + field_name="country__district__admin2", + queryset=Admin2.objects.all(), + label="admin2", + method="get_country_admin2", + ) class Meta: model = Appeal fields = { "start_date": ("exact", "gt", "gte", "lt", "lte"), "end_date": ("exact", "gt", "gte", "lt", "lte"), + "real_data_update": ("exact", "gt", "gte", "lt", "lte"), + "country__iso3": ("exact",), } + def get_country_district(self, qs, name, value): + if value: + return qs.filter(country__district=value).distinct() + return qs + + def get_country_admin2(self, qs, name, value): + if value: + return qs.filter(country__district__admin2=value).distinct() + return qs + class AppealHistoryFilter(filters.FilterSet): atype = filters.NumberFilter(field_name="atype", lookup_expr="exact") diff --git a/api/serializers.py b/api/serializers.py index 371f5370a..e950931ec 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -2118,6 +2118,7 @@ class AggregateHeaderFiguresSerializer(serializers.Serializer): amount_requested = serializers.IntegerField() amount_requested_dref_included = serializers.IntegerField() amount_funded = serializers.IntegerField() + amount_funded_dref_included = serializers.IntegerField() # SearchPage Serializer @@ -2292,8 +2293,8 @@ class AggregateByDtypeSerializer(serializers.Serializer): class CountryKeyFigureInputSerializer(serializers.Serializer): - start_date = serializers.DateField(required=False) - end_date = serializers.DateField(required=False) + start_date_from = serializers.DateField(required=False) + start_date_to = serializers.DateField(required=False) dtype = serializers.IntegerField(required=False) @@ -2304,9 +2305,11 @@ class CountryKeyClimateInputSerializer(serializers.Serializer): class CountryKeyFigureSerializer(serializers.Serializer): active_drefs = serializers.IntegerField() active_appeals = serializers.IntegerField() + amount_requested = serializers.IntegerField() target_population = serializers.IntegerField() - total_amount_requested = serializers.IntegerField() - total_amount_funded = serializers.IntegerField() + amount_requested_dref_included = serializers.IntegerField() + amount_funded = serializers.IntegerField() + amount_funded_dref_included = serializers.IntegerField() emergencies = serializers.IntegerField() diff --git a/api/test_views.py b/api/test_views.py index 9d0a2227d..a4bf987ee 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -5,7 +5,6 @@ import api.models as models from api.factories.event import ( AppealFactory, - AppealHistoryFactory, AppealType, EventFactory, EventFeaturedDocumentFactory, @@ -543,58 +542,53 @@ def test_appeal_key_figure(self): ) event2 = EventFactory.create(name="test0", dtype=dtype1, num_affected=10000, countries=[country1]) event3 = EventFactory.create(name="test2", dtype=dtype2, num_affected=99999, countries=[country2]) - appeal1 = AppealFactory.create( - event=event1, dtype=dtype1, num_beneficiaries=9000, amount_requested=10000, amount_funded=1899999, code=12 - ) - appeal2 = AppealFactory.create( - event=event2, dtype=dtype2, num_beneficiaries=90023, amount_requested=100440, amount_funded=12299999, code=123 - ) - appeal3 = AppealFactory.create( - event=event3, dtype=dtype2, num_beneficiaries=91000, amount_requested=10000888, amount_funded=678888, code=1234 - ) - AppealHistoryFactory.create( - appeal=appeal1, + AppealFactory.create( + event=event1, dtype=dtype1, num_beneficiaries=9000, amount_requested=10000, amount_funded=1899999, - country=country1, - atype=AppealType.APPEAL, + code=12, start_date="2024-1-1", end_date="2024-1-1", + atype=AppealType.APPEAL, + country=country1, ) - AppealHistoryFactory.create( - appeal=appeal2, + AppealFactory.create( + event=event2, dtype=dtype2, - num_beneficiaries=1, - amount_requested=1, - amount_funded=1, - country=country1, - atype=AppealType.DREF, + num_beneficiaries=90023, + amount_requested=100440, + amount_funded=12299999, + code=123, start_date="2024-2-2", end_date="2024-2-2", + atype=AppealType.DREF, + country=country1, ) - AppealHistoryFactory.create( - appeal=appeal3, + AppealFactory.create( + event=event3, dtype=dtype2, - num_beneficiaries=1, - amount_requested=1, - amount_funded=1, - country=country1, - atype=AppealType.APPEAL, + num_beneficiaries=91000, + amount_requested=10000888, + amount_funded=678888, + code=1234, start_date="2024-3-3", end_date="2024-3-3", + atype=AppealType.APPEAL, + country=country1, ) - AppealHistoryFactory.create( - appeal=appeal3, + AppealFactory.create( + event=event3, dtype=dtype2, - num_beneficiaries=1, - amount_requested=1, - amount_funded=1, - country=country1, - atype=AppealType.APPEAL, + num_beneficiaries=91000, + amount_requested=10000888, + amount_funded=678888, + code=12345, start_date="2024-4-4", end_date="2024-4-4", + atype=AppealType.APPEAL, + country=country1, ) url = f"/api/v2/country/{country1.id}/figure/" self.client.force_authenticate(self.user) diff --git a/api/views.py b/api/views.py index 6ea078360..47cf6ebc4 100644 --- a/api/views.py +++ b/api/views.py @@ -50,15 +50,7 @@ from .esconnection import ES_CLIENT from .indexes import ES_PAGE_NAME from .logger import logger -from .models import ( - Appeal, - AppealHistory, - AppealType, - CronJob, - Event, - FieldReport, - Snippet, -) +from .models import Appeal, AppealType, CronJob, Event, FieldReport, Snippet from .utils import is_user_ifrc @@ -609,25 +601,24 @@ def get(self, request): now = timezone.now() date = request.GET.get("date", now) appeal_conditions = ( - (Q(atype=AppealType.APPEAL) | Q(atype=AppealType.INTL)) & Q(end_date__gt=date) & Q(start_date__lt=date) + (Q(atype=AppealType.APPEAL) | Q(atype=AppealType.INTL)) & Q(end_date__gte=date) & Q(start_date__lte=date) ) - all_appealhistory = AppealHistory.objects.select_related("appeal").filter( - valid_from__lt=date, valid_to__gt=date, appeal__code__isnull=False + all_appeal = Appeal.objects.filter( + code__isnull=False, ) if iso3: - all_appealhistory = all_appealhistory.filter(country__iso3__iexact=iso3) + all_appeal = all_appeal.filter(country__iso3__iexact=iso3) if country: - all_appealhistory = all_appealhistory.filter(country__id=country) + all_appeal = all_appeal.filter(country__id=country) if region: - all_appealhistory = all_appealhistory.filter(country__region__id=region) - - appeals_aggregated = all_appealhistory.annotate( + all_appeal = all_appeal.filter(country__region__id=region) + appeals_aggregated = all_appeal.annotate( # Active Appeals with DREF type actd=Count( Case( - When(Q(atype=AppealType.DREF) & Q(end_date__gt=date) & Q(start_date__lt=date), then=1), + When(Q(atype=AppealType.DREF) & Q(end_date__gte=date) & Q(start_date__lte=date), then=1), output_field=IntegerField(), ) ), @@ -638,17 +629,20 @@ def get(self, request): # Active Appeals' target population tarp=Sum( Case( - When(Q(end_date__gt=date) & Q(start_date__lt=date), then=F("num_beneficiaries")), + When(Q(end_date__gte=date) & Q(start_date__lte=date), then=F("num_beneficiaries")), output_field=IntegerField(), ) ), # Active Appeals' requested amount, which are not DREF amor=Case(When(appeal_conditions, then=F("amount_requested")), output_field=IntegerField()), amordref=Case( - When(Q(end_date__gt=date) & Q(start_date__lt=date), then=F("amount_requested")), output_field=IntegerField() + When(Q(end_date__gte=date) & Q(start_date__lte=date), then=F("amount_requested")), output_field=IntegerField() ), # Active Appeals' funded amount, which are not DREF amof=Case(When(appeal_conditions, then=F("amount_funded")), output_field=IntegerField()), + amofdref=Case( + When(Q(end_date__gte=date) & Q(start_date__lte=date), then=F("amount_funded")), output_field=IntegerField() + ), ).aggregate( active_drefs=Sum("actd"), active_appeals=Sum("acta"), @@ -657,6 +651,7 @@ def get(self, request): amount_requested=Sum("amor"), amount_requested_dref_included=Sum("amordref"), amount_funded=Sum("amof"), + amount_funded_dref_included=Sum("amofdref"), ) return Response(AggregateHeaderFiguresSerializer(appeals_aggregated).data)