From bfa04be2d9b4c123f176635ca9d59f47b3f26169 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 19 Jan 2024 15:33:54 -0500 Subject: [PATCH] membershipworks: Add event index and year reports --- membershipworks/dashboard.py | 7 +- membershipworks/models.py | 45 +++++++----- .../event_index_report.dj.html | 42 ++++++++++++ .../event_month_report.dj.html | 6 +- .../membershipworks/event_year_report.dj.html | 68 +++++++++++++++++++ membershipworks/urls.py | 18 ++++- membershipworks/views.py | 41 ++++++++++- 7 files changed, 199 insertions(+), 28 deletions(-) create mode 100644 membershipworks/templates/membershipworks/event_index_report.dj.html create mode 100644 membershipworks/templates/membershipworks/event_year_report.dj.html diff --git a/membershipworks/dashboard.py b/membershipworks/dashboard.py index 2520088..519434f 100644 --- a/membershipworks/dashboard.py +++ b/membershipworks/dashboard.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Any from django.urls import reverse @@ -16,11 +15,7 @@ class MembershipworksDashboardFragment(dashboard.DashboardFragment): links = {} if self.request.user.has_perm("membershipworks.view_event"): - now = datetime.now() - links["Event Report"] = reverse( - "membershipworks:event-month-report", - kwargs={"year": now.year, "month": now.month}, - ) + links["Event Report"] = reverse("membershipworks:event-index-report") return {"links": links} diff --git a/membershipworks/models.py b/membershipworks/models.py index 92c3a96..e84ac9b 100644 --- a/membershipworks/models.py +++ b/membershipworks/models.py @@ -427,26 +427,35 @@ class EventInstructor(models.Model): return str(self.member) if self.member else self.name +class EventExtQuerySet(models.QuerySet["EventExt"]): + def summarize(self, aggregate: bool = False): + method = self.aggregate if aggregate else self.annotate + return method( + count__sum=Sum("count", filter=F("occurred")), + instructor__count=Count("instructor", distinct=True, filter=F("occurred")), + meeting_times__count=Count("meeting_times", filter=F("occurred")), + duration__sum=Sum("duration", filter=F("occurred")), + person_hours__sum=Sum("person_hours", filter=F("occurred")), + event_count=Count("eid", filter=F("occurred")), + canceled_event_count=Count("eid", filter=~F("occurred")), + ) + + class EventExtManager(models.Manager["EventExt"]): def get_queryset(self) -> models.QuerySet["EventExt"]: - return ( - super() - .get_queryset() - .annotate( - Count("meeting_times"), - duration=Subquery( - EventMeetingTime.objects.filter(event=OuterRef("pk")) - .values("event__pk") - .annotate(d=Sum("duration")) - .values("d")[:1], - output_field=models.DurationField(), - ), - person_hours=ExpressionWrapper( - ExpressionWrapper(F("duration"), models.IntegerField()) - * F("count"), - models.DurationField(), - ), - ) + return EventExtQuerySet(self.model, using=self._db).annotate( + Count("meeting_times"), + duration=Subquery( + EventMeetingTime.objects.filter(event=OuterRef("pk")) + .values("event__pk") + .annotate(d=Sum("duration")) + .values("d")[:1], + output_field=models.DurationField(), + ), + person_hours=ExpressionWrapper( + ExpressionWrapper(F("duration"), models.IntegerField()) * F("count"), + models.DurationField(), + ), ) diff --git a/membershipworks/templates/membershipworks/event_index_report.dj.html b/membershipworks/templates/membershipworks/event_index_report.dj.html new file mode 100644 index 0000000..83276f1 --- /dev/null +++ b/membershipworks/templates/membershipworks/event_index_report.dj.html @@ -0,0 +1,42 @@ +{% extends "base.dj.html" %} + +{% load membershipworks_tags %} + +{% block title %}Event Report{% endblock %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} +
+ + + + + + + + + + + + + + + {% for year in object_list %} + + + + + + + + + + + {% endfor %} + +
MonthEventsCanceled EventsTicketsUnique InstructorsMeetingsTotal HoursTotal Person Hours
+ {{ year.year|date:"Y" }} + {{ year.event_count }}{{ year.canceled_event_count }}{{ year.count__sum }}{{ year.instructor__count }}{{ year.meeting_times__count }}{{ year.duration__sum|duration_as_hours }}{{ year.person_hours__sum|duration_as_hours }}
+
+{% endblock %} diff --git a/membershipworks/templates/membershipworks/event_month_report.dj.html b/membershipworks/templates/membershipworks/event_month_report.dj.html index ae4b61a..92a0951 100644 --- a/membershipworks/templates/membershipworks/event_month_report.dj.html +++ b/membershipworks/templates/membershipworks/event_month_report.dj.html @@ -4,8 +4,10 @@ {% block title %}Event Report{% endblock %} {% block breadcrumbs %} - - + + {% endblock %} {% block content %} diff --git a/membershipworks/templates/membershipworks/event_year_report.dj.html b/membershipworks/templates/membershipworks/event_year_report.dj.html new file mode 100644 index 0000000..d42f0b6 --- /dev/null +++ b/membershipworks/templates/membershipworks/event_year_report.dj.html @@ -0,0 +1,68 @@ +{% extends "base.dj.html" %} + +{% load membershipworks_tags %} + +{% block title %}Event Report{% endblock %} +{% block breadcrumbs %} + + +{% endblock %} +{% block content %} +
+ + + + + + + + + + + + + + + {% for month in object_list %} + + + + + + + + + + + {% endfor %} + +
MonthEventsCanceled EventsTicketsUnique InstructorsMeetingsTotal HoursTotal Person Hours
+ {{ month.month|date:"F Y" }} + {{ month.event_count }}{{ month.canceled_event_count }}{{ month.count__sum }}{{ month.instructor__count }}{{ month.meeting_times__count }}{{ month.duration__sum|duration_as_hours }}{{ month.person_hours__sum|duration_as_hours }}
+
+ +{% endblock %} diff --git a/membershipworks/urls.py b/membershipworks/urls.py index 48084c5..9bc68de 100644 --- a/membershipworks/urls.py +++ b/membershipworks/urls.py @@ -1,6 +1,12 @@ from django.urls import path -from .views import EventMonthReport, MemberAutocomplete, upcoming_events +from .views import ( + EventIndexReport, + EventMonthReport, + EventYearReport, + MemberAutocomplete, + upcoming_events, +) app_name = "membershipworks" @@ -15,6 +21,16 @@ urlpatterns = [ upcoming_events, name="upcoming-events", ), + path( + "event-report/", + EventIndexReport.as_view(), + name="event-index-report", + ), + path( + "event-report//", + EventYearReport.as_view(), + name="event-year-report", + ), path( "event-report///", EventMonthReport.as_view(month_format="%m"), diff --git a/membershipworks/views.py b/membershipworks/views.py index af137b7..fbaae1c 100644 --- a/membershipworks/views.py +++ b/membershipworks/views.py @@ -4,8 +4,13 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin +from django.db.models.functions import TruncMonth, TruncYear from django.shortcuts import render -from django.views.generic.dates import MonthArchiveView +from django.views.generic.dates import ( + ArchiveIndexView, + MonthArchiveView, + YearArchiveView, +) from dal import autocomplete @@ -104,6 +109,40 @@ def upcoming_events(request): return render(request, "membershipworks/upcoming_events.dj.html", context) +class EventIndexReport(PermissionRequiredMixin, ArchiveIndexView): + permission_required = "membershipworks.view_eventext" + queryset = EventExt.objects.all() + date_field = "start" + template_name = "membershipworks/event_index_report.dj.html" + make_object_list = True + + def get_dated_queryset(self, **lookup): + return ( + super() + .get_dated_queryset(**lookup) + .values(year=TruncYear("start")) + .summarize() + .order_by("year") + ) + + +class EventYearReport(PermissionRequiredMixin, YearArchiveView): + permission_required = "membershipworks.view_eventext" + queryset = EventExt.objects.all() + date_field = "start" + template_name = "membershipworks/event_year_report.dj.html" + make_object_list = True + + def get_dated_queryset(self, **lookup): + return ( + super() + .get_dated_queryset(**lookup) + .values(month=TruncMonth("start")) + .summarize() + .order_by("month") + ) + + class EventMonthReport(PermissionRequiredMixin, MonthArchiveView): permission_required = "membershipworks.view_eventext" queryset = EventExt.objects.select_related("category", "instructor").all()