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 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}
|
||||||
|
|
||||||
|
@ -427,12 +427,23 @@ 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()
|
|
||||||
.get_queryset()
|
|
||||||
.annotate(
|
|
||||||
Count("meeting_times"),
|
Count("meeting_times"),
|
||||||
duration=Subquery(
|
duration=Subquery(
|
||||||
EventMeetingTime.objects.filter(event=OuterRef("pk"))
|
EventMeetingTime.objects.filter(event=OuterRef("pk"))
|
||||||
@ -442,12 +453,10 @@ class EventExtManager(models.Manager["EventExt"]):
|
|||||||
output_field=models.DurationField(),
|
output_field=models.DurationField(),
|
||||||
),
|
),
|
||||||
person_hours=ExpressionWrapper(
|
person_hours=ExpressionWrapper(
|
||||||
ExpressionWrapper(F("duration"), models.IntegerField())
|
ExpressionWrapper(F("duration"), models.IntegerField()) * F("count"),
|
||||||
* F("count"),
|
|
||||||
models.DurationField(),
|
models.DurationField(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EventExt(Event):
|
class EventExt(Event):
|
||||||
|
@ -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 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 %}
|
||||||
|
@ -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 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"),
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user