from datetime import datetime, timedelta
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.template.defaultfilters import floatformat
from django.views.generic.dates import (
ArchiveIndexView,
MonthArchiveView,
YearArchiveView,
)
import django_tables2 as tables
from dal import autocomplete
from django_tables2 import A, SingleTableMixin
from django_tables2.export.views import ExportMixin
from membershipworks.membershipworks_api import MembershipWorks
from .models import EventExt, Member
class MemberAutocomplete(autocomplete.Select2QuerySetView):
model = Member
search_fields = ["account_name"]
def get_queryset(self):
if not self.request.user.has_perm("membershipworks.view_member"):
return Member.objects.none()
else:
return super().get_queryset()
@permission_required("membershipworks.view_eventext")
def upcoming_events(request):
now = datetime.now()
membershipworks = MembershipWorks()
membershipworks.login(
settings.MEMBERSHIPWORKS_USERNAME, settings.MEMBERSHIPWORKS_PASSWORD
)
events = membershipworks.get_events_list(now)
if "error" in events:
messages.add_message(
request,
messages.ERROR,
f"MembershipWorks Error: {events['error']}",
)
# TODO: this should probably be an HTTP 500 response
return render(request, "base.dj.html")
ongoing_events = []
full_events = []
upcoming_events = []
for event in events["evt"]:
try:
# ignore hidden events
if event["cal"] == 0:
continue
event_details = membershipworks.get_event_by_eid(event["eid"])
# Convert timestamps to datetime objects
event_details["sdp_dt"] = datetime.fromtimestamp(event_details["sdp"])
event_details["edp_dt"] = datetime.fromtimestamp(event_details["edp"])
# registration has already ended
if (
"erd" in event_details
and datetime.fromtimestamp(event_details["erd"]) < now
):
ongoing_events.append(event_details)
# class is full
elif event_details["cnt"] >= event_details["cap"]:
full_events.append(event_details)
else:
upcoming_events.append(event_details)
except KeyError as e:
messages.add_message(
request,
messages.ERROR,
f"Event '{event.get('ttl')}' missing required property: '{e.args[0]}'",
)
# TODO: this should probably be an HTTP 500 response
return render(request, "base.dj.html")
context = {
"event_sections": [
{
"title": "Upcoming Events",
"blurb": "Events that are currently open for registration.",
"events": upcoming_events,
"truncate": False,
},
{
"title": "Just Missed",
"blurb": "These classes are currently full at time of writing. If you are interested, please check the event's page; spots occasionally open up. Keep an eye on this newsletter to see when these classes are offered again.",
"events": full_events,
"truncate": True,
},
{
"title": "Ongoing Events",
"blurb": "These events are ongoing. Registration is currently closed, but these events may be offered again in the future.",
"events": ongoing_events,
"truncate": True,
},
]
}
return render(request, "membershipworks/upcoming_events.dj.html", context)
class DurationColumn(tables.Column):
def render(self, value: timedelta):
if value is None:
return None
return floatformat(value.total_seconds() / 60 / 60, -2)
def value(self, value: timedelta):
if value is None:
return None
return value.total_seconds() / 60 / 60
class EventTable(tables.Table):
title = tables.TemplateColumn(
template_code=(
'{{ value }} '
' '
),
)
occurred = tables.BooleanColumn(visible=False)
start = tables.DateColumn("N d, Y")
duration = DurationColumn()
person_hours = DurationColumn()
meetings = tables.Column()
class Meta:
model = EventExt
fields = (
"title",
"occurred",
"start",
"instructor",
"category",
"count",
"cap",
"meetings",
"duration",
"person_hours",
)
row_attrs = {
"class": lambda record: (
"" if record.occurred else "text-decoration-line-through table-danger"
)
}
class EventSummaryTable(tables.Table):
event_count = tables.Column("Events")
canceled_event_count = tables.Column("Canceled Events")
count__sum = tables.Column("Tickets")
instructor__count = tables.Column("Unique Instructors")
meetings__sum = tables.Column("Meetings")
duration__sum = DurationColumn("Class Hours")
person_hours__sum = DurationColumn("Person Hours")
class EventIndexReport(
ExportMixin, SingleTableMixin, 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
table_class = EventSummaryTable
export_formats = ("csv", "xlsx", "ods")
export_name = "mw_events_index"
def get_table_data(self):
return (
super()
.get_table_data()
.values(year=TruncYear("start"))
.summarize()
.order_by("year")
)
def get_table_kwargs(self):
year_column = tables.DateColumn(
"Y",
linkify=(
"membershipworks:event-year-report",
[A("year__year")],
),
)
return {
"sequence": ("year", "..."),
"extra_columns": (("year", year_column),),
}
class EventYearReport(
ExportMixin, SingleTableMixin, 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
table_class = EventSummaryTable
export_formats = ("csv", "xlsx", "ods")
def get_table_data(self):
return (
super()
.get_table_data()
.values(month=TruncMonth("start"))
.summarize()
.order_by("month")
)
def get_export_filename(self, export_format):
return f"mw_events_{self.get_year()}.{export_format}"
def get_table_kwargs(self):
month_column = tables.DateColumn(
"F Y",
linkify=(
"membershipworks:event-month-report",
[A("month__year"), A("month__month")],
),
)
return {
"sequence": ("month", "..."),
"extra_columns": (("month", month_column),),
}
class EventMonthReport(
ExportMixin, SingleTableMixin, PermissionRequiredMixin, MonthArchiveView
):
permission_required = "membershipworks.view_eventext"
queryset = EventExt.objects.all()
date_field = "start"
template_name = "membershipworks/event_month_report.dj.html"
table_class = EventTable
export_formats = ("csv", "xlsx", "ods")
def get_table_data(self):
return super().get_table_data().select_related("category", "instructor")
def get_export_filename(self, export_format):
return f"mw_events_{self.get_year()}-{self.get_month():02}.{export_format}"