From 4f37a6dd4b1dbf60f5d503a0de9523e0685cfbad Mon Sep 17 00:00:00 2001 From: Sravan Reddy Date: Sun, 22 Sep 2024 20:04:10 +0530 Subject: [PATCH] add delivery-type, year, quarter filter to admin report --- commcare_connect/reports/urls.py | 5 +- commcare_connect/reports/views.py | 124 +++++++++++++++--- .../templates/reports/htmx_table.html | 87 ++++++++++++ .../templates/reports/report_table.html | 44 +++++++ config/settings/base.py | 1 + requirements/base.in | 3 + 6 files changed, 244 insertions(+), 20 deletions(-) create mode 100644 commcare_connect/templates/reports/htmx_table.html create mode 100644 commcare_connect/templates/reports/report_table.html diff --git a/commcare_connect/reports/urls.py b/commcare_connect/reports/urls.py index 5f703cbb..66b84daf 100644 --- a/commcare_connect/reports/urls.py +++ b/commcare_connect/reports/urls.py @@ -1,9 +1,10 @@ from django.urls import path -from commcare_connect.reports import views +from commcare_connect.reports.views import DeliveryStatsReportView app_name = "reports" urlpatterns = [ - path("delivery_stats", views.delivery_stats_report, name="delivery_stats_report"), + # path("delivery_stats", views.delivery_stats_report, name="delivery_stats_report"), + path("delivery_stats", view=DeliveryStatsReportView.as_view(), name="delivery_stats_report"), ] diff --git a/commcare_connect/reports/views.py b/commcare_connect/reports/views.py index c16db07b..224156b3 100644 --- a/commcare_connect/reports/views.py +++ b/commcare_connect/reports/views.py @@ -1,11 +1,13 @@ -from datetime import date +from datetime import date, datetime -from django.contrib.auth.decorators import login_required, user_passes_test -from django.db.models import Max, Sum -from django.shortcuts import render -from django.views.decorators.http import require_GET +import django_filters +import django_tables2 as tables +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.db.models import Max, Q, Sum +from django.urls import reverse +from django_filters.views import FilterView -from commcare_connect.opportunity.models import CompletedWork, CompletedWorkStatus, Payment +from commcare_connect.opportunity.models import CompletedWork, CompletedWorkStatus, DeliveryType, Payment from .tables import AdminReportTable @@ -33,7 +35,12 @@ def _get_quarters_since_start(): return quarters -def _get_table_data_for_quarter(quarter): +def _get_table_data_for_quarter(quarter, delivery_type): + if delivery_type: + delivery_type_filter = Q(opportunity_access__opportunity__delivery_type__slug=delivery_type) + else: + delivery_type_filter = Q() + quarter_start = date(quarter[0], (quarter[1] - 1) * 3 + 1, 1) next_quarter = _increment(quarter) quarter_end = date(next_quarter[0], (next_quarter[1] - 1) * 3 + 1, 1) @@ -48,6 +55,7 @@ def _get_table_data_for_quarter(quarter): visit_data = ( CompletedWork.objects.annotate(work_date=Max("uservisit__visit_date")) .filter( + delivery_type_filter, opportunity_access__opportunity__is_test=False, status=CompletedWorkStatus.approved, work_date__gte=quarter_start, @@ -67,6 +75,7 @@ def _get_table_data_for_quarter(quarter): approved_payment_data = ( Payment.objects.filter( + delivery_type_filter, opportunity_access__opportunity__is_test=False, confirmed=True, date_paid__gte=quarter_start, @@ -88,6 +97,7 @@ def payment_strings(payment_data): total_payment_data = ( Payment.objects.filter( + delivery_type_filter, opportunity_access__opportunity__is_test=False, date_paid__gte=quarter_start, date_paid__lt=quarter_end, @@ -106,14 +116,92 @@ def payment_strings(payment_data): } -@login_required -@user_passes_test(lambda user: user.is_superuser) -@require_GET -def delivery_stats_report(request): - table_data = [] - quarters = _get_quarters_since_start() - for q in quarters: - data = _get_table_data_for_quarter(q) - table_data.append(data) - table = AdminReportTable(table_data) - return render(request, "reports/admin.html", context={"table": table}) +class SuperUserRequiredMixin(LoginRequiredMixin, UserPassesTestMixin): + def test_func(self): + return self.request.user.is_superuser + + +class DeliveryReportFilters(django_filters.FilterSet): + delivery_type = django_filters.ChoiceFilter(method="filter_by_ignore") + year = django_filters.ChoiceFilter(method="filter_by_ignore") + quarter = django_filters.ChoiceFilter( + choices=[(1, "Q1"), (2, "Q2"), (3, "Q3"), (4, "Q4")], label="Quarter", method="filter_by_ignore" + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + current_year = datetime.now().year + year_choices = [(year, str(year)) for year in range(2023, current_year + 1)] + self.filters["year"] = django_filters.ChoiceFilter( + choices=year_choices, label="Year", method="filter_by_ignore" + ) + + delivery_types = DeliveryType.objects.values_list("slug", "name") + self.filters["delivery_type"] = django_filters.ChoiceFilter(choices=delivery_types, label="Delivery Type") + + def filter_by_ignore(self, queryset, name, value): + return queryset + + class Meta: + model = None + fields = ["delivery_type", "year", "quarter"] + unknown_field_behavior = django_filters.UnknownFieldBehavior.IGNORE + + +class NonModelFilterView(FilterView): + def get_queryset(self): + # Doesn't matter which model it is here + return CompletedWork.objects.none() + + @property + def object_list(self): + # Override this + return [] + + def get(self, request, *args, **kwargs): + filterset_class = self.get_filterset_class() + self.filterset = self.get_filterset(filterset_class) + context = self.get_context_data(filter=self.filterset, object_list=self.object_list) + return self.render_to_response(context) + + +class DeliveryStatsReportView(tables.SingleTableMixin, SuperUserRequiredMixin, NonModelFilterView): + table_class = AdminReportTable + filterset_class = DeliveryReportFilters + + def get_template_names(self): + if self.request.htmx: + template_name = "reports/htmx_table.html" + else: + template_name = "reports/report_table.html" + + return template_name + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(**kwargs) + context["report_url"] = reverse("reports:delivery_stats_report") + return context + + @property + def object_list(self): + table_data = [] + if not self.filterset.form.is_valid(): + return [] + + filter_values = self.filterset.form.cleaned_data + delivery_type = filter_values["delivery_type"] + year = int(filter_values["year"]) + quarter = filter_values["quarter"] + + if not year: + quarters = _get_quarters_since_start() + elif year: + if quarter: + quarters = [(year, quarter)] + else: + quarters = [(year, q) for q in range(1, 5)] + + for q in quarters: + data = _get_table_data_for_quarter(q, delivery_type) + table_data.append(data) + return table_data diff --git a/commcare_connect/templates/reports/htmx_table.html b/commcare_connect/templates/reports/htmx_table.html new file mode 100644 index 00000000..d896eec5 --- /dev/null +++ b/commcare_connect/templates/reports/htmx_table.html @@ -0,0 +1,87 @@ +{% extends "django_tables2/bootstrap5.html" %} + +{% load django_tables2 %} +{% load i18n %} + +{% block table.thead %} + {% if table.show_header %} + + + {% for column in table.columns %} + + {{ column.header }} + {% if column.orderable %} + {% if column.order_by_alias == column.order_by_alias.next %} + + {% elif column.order_by_alias|slice:":1" == "-" %} + + {% else %} + + {% endif %} + {% endif%} + + {% endfor %} + + + {% endif %} +{% endblock table.thead %} + +{% block pagination %} + {% if table.page and table.paginator.num_pages > 1 %} + + {% endif %} +{% endblock pagination %} diff --git a/commcare_connect/templates/reports/report_table.html b/commcare_connect/templates/reports/report_table.html new file mode 100644 index 00000000..beb0e738 --- /dev/null +++ b/commcare_connect/templates/reports/report_table.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} + +{% load render_table from django_tables2 %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block content %} +

Delivery Stats Report

+ +
+ {% crispy filter.form %} +
+ + {% render_table table %} +{% endblock %} + +{% block javascript %} +{{ block.super }} + + + + + +{% endblock %} + +{% block css %} +{{ block.super }} + +{% endblock %} diff --git a/config/settings/base.py b/config/settings/base.py index 76dca293..7891840c 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -117,6 +117,7 @@ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", + "django_htmx.middleware.HtmxMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "commcare_connect.users.middleware.OrganizationMiddleware", diff --git a/requirements/base.in b/requirements/base.in index ebbd1547..8209681a 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -30,6 +30,9 @@ django-cors-headers # DRF-spectacular for api documentation drf-spectacular django-tables2 +django-filter +django-autocomplete-light +django-htmx # Temporary # -------------------------------------------------------------------------------