membershipworks: Add event index and year reports

This commit is contained in:
Adam Goldsmith 2024-01-19 15:33:54 -05:00
parent cbe8d24fe4
commit bfa04be2d9
7 changed files with 199 additions and 28 deletions

View File

@ -1,4 +1,3 @@
from datetime import datetime
from typing import Any from typing import Any
from django.urls import reverse from django.urls import reverse
@ -16,11 +15,7 @@ class MembershipworksDashboardFragment(dashboard.DashboardFragment):
links = {} links = {}
if self.request.user.has_perm("membershipworks.view_event"): if self.request.user.has_perm("membershipworks.view_event"):
now = datetime.now() links["Event Report"] = reverse("membershipworks:event-index-report")
links["Event Report"] = reverse(
"membershipworks:event-month-report",
kwargs={"year": now.year, "month": now.month},
)
return {"links": links} return {"links": links}

View File

@ -427,26 +427,35 @@ class EventInstructor(models.Model):
return str(self.member) if self.member else self.name 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"]): class EventExtManager(models.Manager["EventExt"]):
def get_queryset(self) -> models.QuerySet["EventExt"]: def get_queryset(self) -> models.QuerySet["EventExt"]:
return ( return EventExtQuerySet(self.model, using=self._db).annotate(
super() Count("meeting_times"),
.get_queryset() duration=Subquery(
.annotate( EventMeetingTime.objects.filter(event=OuterRef("pk"))
Count("meeting_times"), .values("event__pk")
duration=Subquery( .annotate(d=Sum("duration"))
EventMeetingTime.objects.filter(event=OuterRef("pk")) .values("d")[:1],
.values("event__pk") output_field=models.DurationField(),
.annotate(d=Sum("duration")) ),
.values("d")[:1], person_hours=ExpressionWrapper(
output_field=models.DurationField(), ExpressionWrapper(F("duration"), models.IntegerField()) * F("count"),
), models.DurationField(),
person_hours=ExpressionWrapper( ),
ExpressionWrapper(F("duration"), models.IntegerField())
* F("count"),
models.DurationField(),
),
)
) )

View File

@ -0,0 +1,42 @@
{% extends "base.dj.html" %}
{% load membershipworks_tags %}
{% block title %}Event Report{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">MW Event Reports</li>
{% endblock %}
{% block content %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th for="column">Month</th>
<th for="column">Events</th>
<th for="column">Canceled Events</th>
<th for="column">Tickets</th>
<th for="column">Unique Instructors</th>
<th for="column">Meetings</th>
<th for="column">Total Hours</th>
<th for="column">Total Person Hours</th>
</tr>
</thead>
<tbody>
{% for year in object_list %}
<tr>
<td>
<a href="{% url 'membershipworks:event-year-report' year.year|date:"Y" %}">{{ year.year|date:"Y" }}</a>
</td>
<td>{{ year.event_count }}</td>
<td>{{ year.canceled_event_count }}</td>
<td>{{ year.count__sum }}</td>
<td>{{ year.instructor__count }}</td>
<td>{{ year.meeting_times__count }}</td>
<td>{{ year.duration__sum|duration_as_hours }}</td>
<td>{{ year.person_hours__sum|duration_as_hours }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -4,8 +4,10 @@
{% block title %}Event Report{% endblock %} {% block title %}Event Report{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item">MW Event Reports</li> <li class="breadcrumb-item"><a href="{% url 'membershipworks:event-index-report' %}">MW Event Reports</a></li>
<li class="breadcrumb-item">{{ month|date:"Y" }}</li> <li class="breadcrumb-item">
<a href="{% url 'membershipworks:event-year-report' month|date:"Y" %}">{{ month|date:"Y" }}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{{ month|date:"F" }}</li> <li class="breadcrumb-item active" aria-current="page">{{ month|date:"F" }}</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -0,0 +1,68 @@
{% extends "base.dj.html" %}
{% load membershipworks_tags %}
{% block title %}Event Report{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'membershipworks:event-index-report' %}">MW Event Reports</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ year|date:"Y" }}</li>
{% endblock %}
{% block content %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th for="column">Month</th>
<th for="column">Events</th>
<th for="column">Canceled Events</th>
<th for="column">Tickets</th>
<th for="column">Unique Instructors</th>
<th for="column">Meetings</th>
<th for="column">Total Hours</th>
<th for="column">Total Person Hours</th>
</tr>
</thead>
<tbody>
{% for month in object_list %}
<tr>
<td>
<a href="{% url 'membershipworks:event-month-report' month.month|date:"Y" month.month|date:"m" %}">{{ month.month|date:"F Y" }}</a>
</td>
<td>{{ month.event_count }}</td>
<td>{{ month.canceled_event_count }}</td>
<td>{{ month.count__sum }}</td>
<td>{{ month.instructor__count }}</td>
<td>{{ month.meeting_times__count }}</td>
<td>{{ month.duration__sum|duration_as_hours }}</td>
<td>{{ month.person_hours__sum|duration_as_hours }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if previous_year %}
<li class="page-item">
<a class="page-link"
href="{% url 'membershipworks:event-year-report' previous_year|date:"Y" %}">
<i class="bi bi-arrow-left"></i>
{{ previous_year|date:"Y" }}
</a>
</li>
{% endif %}
<li class="page-item active">
<a class="page-link" href="#">{{ year|date:"Y" }}</a>
</li>
{% if next_year %}
<li class="page-item">
<a class="page-link"
href="{% url 'membershipworks:event-year-report' next_year|date:"Y" %}">
{{ next_year|date:"Y" }}
<i class="bi bi-arrow-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endblock %}

View File

@ -1,6 +1,12 @@
from django.urls import path from django.urls import path
from .views import EventMonthReport, MemberAutocomplete, upcoming_events from .views import (
EventIndexReport,
EventMonthReport,
EventYearReport,
MemberAutocomplete,
upcoming_events,
)
app_name = "membershipworks" app_name = "membershipworks"
@ -15,6 +21,16 @@ urlpatterns = [
upcoming_events, upcoming_events,
name="upcoming-events", name="upcoming-events",
), ),
path(
"event-report/",
EventIndexReport.as_view(),
name="event-index-report",
),
path(
"event-report/<int:year>/",
EventYearReport.as_view(),
name="event-year-report",
),
path( path(
"event-report/<int:year>/<int:month>/", "event-report/<int:year>/<int:month>/",
EventMonthReport.as_view(month_format="%m"), EventMonthReport.as_view(month_format="%m"),

View File

@ -4,8 +4,13 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models.functions import TruncMonth, TruncYear
from django.shortcuts import render 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 from dal import autocomplete
@ -104,6 +109,40 @@ def upcoming_events(request):
return render(request, "membershipworks/upcoming_events.dj.html", context) 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): class EventMonthReport(PermissionRequiredMixin, MonthArchiveView):
permission_required = "membershipworks.view_eventext" permission_required = "membershipworks.view_eventext"
queryset = EventExt.objects.select_related("category", "instructor").all() queryset = EventExt.objects.select_related("category", "instructor").all()