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 %}
+
MW Event Reports
+{% endblock %}
+{% block content %}
+
+
+
+
+ Month |
+ Events |
+ Canceled Events |
+ Tickets |
+ Unique Instructors |
+ Meetings |
+ Total Hours |
+ Total Person Hours |
+
+
+
+ {% for year in object_list %}
+
+
+ {{ 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 }} |
+
+ {% endfor %}
+
+
+
+{% 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 %}
- MW Event Reports
- {{ month|date:"Y" }}
+ MW Event Reports
+
+ {{ month|date:"Y" }}
+
{{ month|date:"F" }}
{% 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 %}
+ MW Event Reports
+ {{ year|date:"Y" }}
+{% endblock %}
+{% block content %}
+
+
+
+
+ Month |
+ Events |
+ Canceled Events |
+ Tickets |
+ Unique Instructors |
+ Meetings |
+ Total Hours |
+ Total Person Hours |
+
+
+
+ {% for month in object_list %}
+
+
+ {{ 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 }} |
+
+ {% endfor %}
+
+
+
+
+{% 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()