membershipworks: Add event index and year reports
This commit is contained in:
parent
cbe8d24fe4
commit
bfa04be2d9
@ -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}
|
||||
|
||||
|
@ -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(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -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 %}
|
@ -4,8 +4,10 @@
|
||||
|
||||
{% block title %}Event Report{% endblock %}
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item">MW Event Reports</li>
|
||||
<li class="breadcrumb-item">{{ month|date:"Y" }}</li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'membershipworks:event-index-report' %}">MW Event Reports</a></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>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
@ -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 %}
|
@ -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/<int:year>/",
|
||||
EventYearReport.as_view(),
|
||||
name="event-year-report",
|
||||
),
|
||||
path(
|
||||
"event-report/<int:year>/<int:month>/",
|
||||
EventMonthReport.as_view(month_format="%m"),
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user