diff --git a/membershipworks/dashboard.py b/membershipworks/dashboard.py index 519434f..3daae1a 100644 --- a/membershipworks/dashboard.py +++ b/membershipworks/dashboard.py @@ -16,6 +16,7 @@ class MembershipworksDashboardFragment(dashboard.DashboardFragment): if self.request.user.has_perm("membershipworks.view_event"): links["Event Report"] = reverse("membershipworks:event-index-report") + links["Event Attendees"] = reverse("membershipworks:event-attendees") return {"links": links} diff --git a/membershipworks/migrations/0013_eventattendee.py b/membershipworks/migrations/0013_eventattendee.py new file mode 100644 index 0000000..e95e18e --- /dev/null +++ b/membershipworks/migrations/0013_eventattendee.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.1 on 2024-02-02 22:07 + +from django.db import migrations + +import django_db_views.migration_functions +import django_db_views.operations + + +class Migration(migrations.Migration): + dependencies = [ + ("membershipworks", "0012_eventattendeestats_eventtickettype"), + ] + + operations = [ + django_db_views.operations.ViewRunPython( + code=django_db_views.migration_functions.ForwardViewMigration( + "SELECT eventext.event_ptr_id as event_id, tkt.uid, tkt.name, tkt.email, tkt.sum\n FROM\n membershipworks_eventext as eventext,\n JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (\n uid VARCHAR(24) PATH '$.uid',\n name VARCHAR(256) PATH '$.nam',\n email VARCHAR(256) PATH '$.eml',\n sum DOUBLE PATH '$.sum'\n )) as tkt", + "membershipworks_eventattendee", + engine="django.db.backends.mysql", + ), + reverse_code=django_db_views.migration_functions.BackwardViewMigration( + "", "membershipworks_eventattendee", engine="django.db.backends.mysql" + ), + atomic=False, + ), + ] diff --git a/membershipworks/models.py b/membershipworks/models.py index 6296e25..0c969ab 100644 --- a/membershipworks/models.py +++ b/membershipworks/models.py @@ -688,3 +688,28 @@ class EventAttendeeStats(DBView): class Meta: managed = False + + +class EventAttendee(DBView): + event = models.ForeignKey( + EventExt, on_delete=models.CASCADE, related_name="attendees" + ) + uid = models.ForeignKey(Member, on_delete=models.DO_NOTHING) + name = models.CharField(max_length=256) + email = models.CharField(max_length=256) + sum = models.FloatField() + + view_definition = """ + SELECT eventext.event_ptr_id as event_id, tkt.uid, tkt.name, tkt.email, tkt.sum + FROM + membershipworks_eventext as eventext, + JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS ( + uid VARCHAR(24) PATH '$.uid', + name VARCHAR(256) PATH '$.nam', + email VARCHAR(256) PATH '$.eml', + sum DOUBLE PATH '$.sum' + )) as tkt + """ + + class Meta: + managed = False diff --git a/membershipworks/templates/membershipworks/eventattendee_list.dj.html b/membershipworks/templates/membershipworks/eventattendee_list.dj.html new file mode 100644 index 0000000..6a2d192 --- /dev/null +++ b/membershipworks/templates/membershipworks/eventattendee_list.dj.html @@ -0,0 +1,27 @@ +{% extends "base.dj.html" %} + +{% load render_table from django_tables2 %} + +{% block title %}Event Attendees{% endblock %} +{% block admin_link %} + {% url 'admin:membershipworks_eventext_changelist' %} +{% endblock %} +{% block content %} +
+
+
+
+ + +
+
+ +
{% include "cmsmanage/components/download_table.dj.html" %}
+
+
+ {% render_table table %} +{% endblock %} diff --git a/membershipworks/urls.py b/membershipworks/urls.py index 4925c82..0c3f326 100644 --- a/membershipworks/urls.py +++ b/membershipworks/urls.py @@ -1,6 +1,7 @@ from django.urls import path from .views import ( + EventAttendeeListView, EventIndexReport, EventInvoiceView, EventMonthReport, @@ -42,4 +43,9 @@ urlpatterns = [ EventInvoiceView.as_view(), name="event-invoice", ), + path( + "event-attendees", + EventAttendeeListView.as_view(), + name="event-attendees", + ), ] diff --git a/membershipworks/views.py b/membershipworks/views.py index 6540dad..deabddb 100644 --- a/membershipworks/views.py +++ b/membershipworks/views.py @@ -4,24 +4,27 @@ 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 import Subquery from django.db.models.functions import TruncMonth, TruncYear from django.shortcuts import render from django.template.defaultfilters import floatformat -from django.views.generic import DetailView +from django.views.generic import DetailView, ListView from django.views.generic.dates import ( ArchiveIndexView, MonthArchiveView, YearArchiveView, ) +import django_filters import django_tables2 as tables from dal import autocomplete +from django_filters.views import BaseFilterView from django_tables2 import A, SingleTableMixin from django_tables2.export.views import ExportMixin from membershipworks.membershipworks_api import MembershipWorks -from .models import EventExt, Member +from .models import EventAttendee, EventExt, Member class MemberAutocomplete(autocomplete.Select2QuerySetView): @@ -329,3 +332,36 @@ class EventInvoiceView(SingleTableMixin, PermissionRequiredMixin, DetailView): def get_table_kwargs(self): return {"event": self.object} + + +class EventAttendeeTable(tables.Table): + class Meta: + model = EventAttendee + fields = ("name", "email") + + +class EventAttendeeFilters(django_filters.FilterSet): + new_since = django_filters.DateFilter( + field_name="event__start", method="filter_new_since" + ) + + def filter_new_since(self, queryset, name, value): + return queryset.filter(**{f"{name}__gte": value}).exclude( + email__in=Subquery( + queryset.filter(**{f"{name}__lt": value}).values("email") + ) + ) + + +class EventAttendeeListView( + BaseFilterView, ExportMixin, SingleTableMixin, PermissionRequiredMixin, ListView +): + permission_required = "membershipworks.view_eventext" + queryset = EventAttendee.objects.all() + table_class = EventAttendeeTable + template_name = "membershipworks/eventattendee_list.dj.html" + export_formats = ("csv", "xlsx", "ods") + filterset_class = EventAttendeeFilters + + def get_table_data(self): + return super().get_table_data().values("name", "email").distinct()