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 %}
+
+
+ {% 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..a3123b82
--- /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 %}
+
+ {{ 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 %}
+