diff --git a/membershipworks/dashboard.py b/membershipworks/dashboard.py
new file mode 100644
index 0000000..22d8850
--- /dev/null
+++ b/membershipworks/dashboard.py
@@ -0,0 +1,29 @@
+from typing import Any
+from datetime import datetime
+
+from django.urls import reverse
+
+import dashboard
+
+
+@dashboard.register
+class MembershipworksDashboardFragment(dashboard.DashboardFragment):
+ name = "MembershipWorks"
+ template = "dashboard/links_card.dj.html"
+
+ @property
+ def context(self) -> Any:
+ links = {}
+
+ if self.request.user.has_perm("membershipworks.view_event"):
+ now = datetime.now()
+ links["Event Report"] = reverse(
+ "membershipworks:event-report",
+ kwargs={"year": now.year, "month": now.month},
+ )
+
+ return {"links": links}
+
+ @property
+ def visible(self) -> bool:
+ return self.request.user.has_perm("doorcontrol.view_hidevent")
diff --git a/membershipworks/models.py b/membershipworks/models.py
index d0aaeda..db02c97 100644
--- a/membershipworks/models.py
+++ b/membershipworks/models.py
@@ -436,6 +436,14 @@ class EventExt(Event):
max_digits=13, decimal_places=4, default=0
)
+ # TODO: ideally this would be a generated column or annotation,
+ # but I couldn't get the types to work out
+ @property
+ def person_hours(self):
+ if self.duration is None:
+ return None
+ return self.count * self.duration
+
class Meta:
verbose_name = "event"
diff --git a/membershipworks/templates/membershipworks/event_report.dj.html b/membershipworks/templates/membershipworks/event_report.dj.html
new file mode 100644
index 0000000..6324a2c
--- /dev/null
+++ b/membershipworks/templates/membershipworks/event_report.dj.html
@@ -0,0 +1,66 @@
+{% extends "base.dj.html" %}
+
+{% load membershipworks_tags %}
+
+{% block title %}Event Report{% endblock %}
+{% block content %}
+
+
+
+
+ Title |
+ Date |
+ Instructor |
+ Category |
+ Ticket Count |
+ Ticket Cap |
+ Meetings |
+ Total Duration |
+ Person Hours |
+
+
+
+ {% for event in object_list %}
+
+
+ {{ event.title }}
+ |
+ {{ event.start|date }} |
+ {{ event.instructor }} |
+ {{ event.category }} |
+ {{ event.count }} |
+ {{ event.cap }} |
+ {{ event.meeting_times.count }} |
+ {{ event.duration|duration_as_hours }} |
+ {{ event.person_hours|duration_as_hours }} |
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
diff --git a/membershipworks/templatetags/__init__.py b/membershipworks/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/membershipworks/templatetags/membershipworks_tags.py b/membershipworks/templatetags/membershipworks_tags.py
new file mode 100644
index 0000000..454e000
--- /dev/null
+++ b/membershipworks/templatetags/membershipworks_tags.py
@@ -0,0 +1,12 @@
+from datetime import timedelta
+
+from django import template
+
+register = template.Library()
+
+
+@register.filter
+def duration_as_hours(td: timedelta) -> str | None:
+ if td is None:
+ return None
+ return td.total_seconds() / 60 / 60
diff --git a/membershipworks/urls.py b/membershipworks/urls.py
index f36170d..71f7400 100644
--- a/membershipworks/urls.py
+++ b/membershipworks/urls.py
@@ -1,6 +1,6 @@
from django.urls import path
-from .views import MemberAutocomplete, upcoming_events
+from .views import MemberAutocomplete, upcoming_events, EventReport
app_name = "membershipworks"
@@ -15,4 +15,9 @@ urlpatterns = [
upcoming_events,
name="upcoming-events",
),
+ path(
+ "event-report///",
+ EventReport.as_view(month_format="%m"),
+ name="event-report",
+ ),
]
diff --git a/membershipworks/views.py b/membershipworks/views.py
index 645e411..4a36fe0 100644
--- a/membershipworks/views.py
+++ b/membershipworks/views.py
@@ -3,12 +3,13 @@ from datetime import datetime
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
-from django.http import HttpResponse
+from django.contrib.auth.mixins import PermissionRequiredMixin
from django.shortcuts import render
+from django.views.generic.dates import MonthArchiveView
from dal import autocomplete
-from .models import Member
+from .models import Member, EventExt
from membershipworks import MembershipWorks
@@ -101,3 +102,10 @@ def upcoming_events(request):
]
}
return render(request, "membershipworks/upcoming_events.dj.html", context)
+
+
+class EventReport(PermissionRequiredMixin, MonthArchiveView):
+ permission_required = "membershipworks.view_eventext"
+ queryset = EventExt.objects.select_related("category", "instructor").all()
+ date_field = "start"
+ template_name = "membershipworks/event_report.dj.html"