membershipworks: Add upcoming events generator

This commit is contained in:
Adam Goldsmith 2023-12-22 01:08:20 -05:00
parent 7afcc1f9e0
commit 97bcc1df6d
6 changed files with 233 additions and 2 deletions

View File

@ -37,6 +37,7 @@ INSTALLED_APPS = [
"rest_framework",
"rest_framework.authtoken",
"django_q",
"django_bleach",
"tasks.apps.TasksConfig",
"rentals.apps.RentalsConfig",
"membershipworks.apps.MembershipworksConfig",

View File

@ -0,0 +1,121 @@
{% extends "base.dj.html" %}
{% load bleach_tags %}
{% block title %}Upcoming Events{% endblock %}
{% block content %}
<div id="preview">
<p>
<img class="aligncenter size-medium wp-image-2319"
src="https://claremontmakerspace.org/wp-content/uploads/2019/03/CMS-Logo-b-y-g-300x168.png"
alt=""
width="300"
height="168" />
</p>
<p>Greetings Upper Valley Makers:</p>
<p>We have an exciting list of upcoming classes at the Claremont MakerSpace that we think might interest you.</p>
<div>
<strong>For most classes and events, CMS MEMBERSHIP IS NOT REQUIRED.</strong> That said, members receive a discount
on registration and there are some classes/events that are for members only (this will be clearly noted in the event
description).
</div>
<div>
<strong>Class policies</strong> (liability waiver, withdrawal, cancellation, etc.) can be found <a href="https://claremontmakerspace.org/class-policies/"
data-wpel-link="internal">here</a>.
</div>
<div>
<strong>Instructors:</strong> Interested in teaching a class at CMS? Please fill out our <a href="https://claremontmakerspace.org/cms-class-proposal-form/"
data-wpel-link="internal">Class Proposal Form</a>.
</div>
<div>
<strong>Tours:</strong> Want to see what the Claremont MakerSpace is all about? Tours are by appointment only.
</div>
<a href="https://tickets.claremontmakerspace.org/open.php"
target="_blank"
rel="noreferrer noopener external"
data-wpel-link="external">Contact Us</a> to schedule your tour where you can learn about all the awesome tools that
the CMS offers access to, as well as how membership, classes, and studio spaces work.
<hr />
{% for section in event_sections %}
{% if section.events %}
<h1>{{ section.title }}</h1>
<h4>
<i>{{ section.blurb }}</i>
</h4>
{% for event in section.events %}
{% with url="https://claremontmakerspace.org/events/#!event/register/"|add:event.url %}
{% spaceless %}
<h2 style="text-align: center;">
<a href="{{ url }}">
{# djlint:off H006,H013 #}
{% if "lgo" in event %}<img class="alignleft" width="400" src="{{ event.lgo.l }}">{% endif %}
{# djlint:on #}
<span>{{ event.ttl|bleach }}</span>
</a>
</h2>
{% endspaceless %}
{% spaceless %}
<div>
{# wordpress is very annoying with spacing here #}
{# djlint:off #}
<i>
{# TODO: different dates probably implies multiple instances. Should read RRULE or similar from the event notes #}
{{ event.sdp_dt|date }} {{ event.sdp_dt|time }} &mdash; {% if event.sdp_dt.date != event.edp_dt.date %}{{ event.edp_dt|date }}{% endif %} {{ event.edp_dt|time }}
</i>
{# djlint:on #}
</div>
{% endspaceless %}
{% if not section.truncate %}
<div>{{ event.dtl|bleach:"a,abbr,acronym,b,blockquote,code,em,i,li,ol,strong,ul,p,span,br,div" }}</div>
<div>
<a href="{{ url }}">Register for this class now!</a>
</div>
{% endif %}
<hr />
{% endwith %}
{% endfor %}
{% endif %}
{% endfor %}
<div style="clear: both;">
<div>Happy Makin!</div>
<div>
We are grateful for all of the public support that our 501(c)(3), non-profit organization receives. If youd
like to make a donation,please visit the <a href="https://claremontmakerspace.org/support/"><strong>Support Us
page</strong></a> of our website.
</div>
</div>
</div>
<div class="position-fixed end-0 bottom-0">
<button id="copy-button"
type="button"
class="btn btn-primary m-3"
data-bs-toggle="popover"
data-bs-content="Copied!">
<i class="bi bi-clipboard-fill"></i> Copy to clipboard
</button>
</div>
{% endblock %}
{% block script %}
<script>
// TODO: This should use the newer Clipboard API, but Firefox doesn't support it yet
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem
function copyToClipboard(event) {
const cb = e => {
e.clipboardData.setData("text/html", document.getElementById("preview").innerHTML);
e.clipboardData.setData("text/plain", document.getElementById("preview").innerHTML);
e.preventDefault();
};
document.addEventListener("copy", cb);
document.execCommand("copy");
document.removeEventListener("copy", cb);
bootstrap.Popover.getInstance(event.target).show();
setTimeout(() => bootstrap.Popover.getInstance(event.target).hide(), 1000);
}
const button = document.getElementById("copy-button");
const popover = new bootstrap.Popover(button, {
trigger: "manual"
})
button.addEventListener("click", copyToClipboard);
</script>
{% endblock %}

View File

@ -1,6 +1,6 @@
from django.urls import path
from .views import MemberAutocomplete
from .views import MemberAutocomplete, upcoming_events
app_name = "membershipworks"
@ -10,4 +10,9 @@ urlpatterns = [
MemberAutocomplete.as_view(),
name="member-autocomplete",
),
path(
"upcoming-events/",
upcoming_events,
name="upcoming-events",
),
]

View File

@ -1,6 +1,15 @@
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.shortcuts import render
from dal import autocomplete
from .models import Member
from membershipworks import MembershipWorks
class MemberAutocomplete(autocomplete.Select2QuerySetView):
@ -12,3 +21,84 @@ class MemberAutocomplete(autocomplete.Select2QuerySetView):
return Member.objects.none()
else:
return super().get_queryset()
@login_required
# TODO: permission required?
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"])
print(event_details["ttl"])
# 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)

View File

@ -5,7 +5,7 @@
groups = ["default", "debug", "lint", "server", "typing", "dev"]
strategy = ["cross_platform"]
lock_version = "4.4"
content_hash = "sha256:8afb89517cfd55ec9138e679d7df47143d3dda10543096382656cb2028e97b50"
content_hash = "sha256:4b66341c252a0c283b65ee725342a18f2b71c34811cc7853c75e78fde80815df"
[[package]]
name = "aiohttp"
@ -443,6 +443,19 @@ files = [
{file = "django-autocomplete-light-3.9.7.tar.gz", hash = "sha256:a34f192ac438c4df056dbfd399550799ddc631c4661960134ded924648770373"},
]
[[package]]
name = "django-bleach"
version = "1.0.0"
summary = "Easily use bleach with Django models and templates"
dependencies = [
"Django>=1.11",
"bleach>=1.5.0",
]
files = [
{file = "django-bleach-1.0.0.tar.gz", hash = "sha256:2586b90d641d4d7e70ee353570ad33d3625ed4b97036a3ea5b03ea1bb5bbeccd"},
{file = "django_bleach-1.0.0-py2.py3-none-any.whl", hash = "sha256:60074a4f4bc8d5200fdb2e03dce16fb4913427698b64570bc3e1a7ea1b8c3cf7"},
]
[[package]]
name = "django-debug-toolbar"
version = "4.2.0"

View File

@ -28,6 +28,7 @@ dependencies = [
"django-object-actions~=4.2",
"udm-rest-client~=1.2",
"openapi-client-udm~=1.0",
"django-bleach~=1.0",
]
requires-python = ">=3.11"