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",
"rest_framework.authtoken", "rest_framework.authtoken",
"django_q", "django_q",
"django_bleach",
"tasks.apps.TasksConfig", "tasks.apps.TasksConfig",
"rentals.apps.RentalsConfig", "rentals.apps.RentalsConfig",
"membershipworks.apps.MembershipworksConfig", "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 django.urls import path
from .views import MemberAutocomplete from .views import MemberAutocomplete, upcoming_events
app_name = "membershipworks" app_name = "membershipworks"
@ -10,4 +10,9 @@ urlpatterns = [
MemberAutocomplete.as_view(), MemberAutocomplete.as_view(),
name="member-autocomplete", 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 dal import autocomplete
from .models import Member from .models import Member
from membershipworks import MembershipWorks
class MemberAutocomplete(autocomplete.Select2QuerySetView): class MemberAutocomplete(autocomplete.Select2QuerySetView):
@ -12,3 +21,84 @@ class MemberAutocomplete(autocomplete.Select2QuerySetView):
return Member.objects.none() return Member.objects.none()
else: else:
return super().get_queryset() 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"] groups = ["default", "debug", "lint", "server", "typing", "dev"]
strategy = ["cross_platform"] strategy = ["cross_platform"]
lock_version = "4.4" lock_version = "4.4"
content_hash = "sha256:8afb89517cfd55ec9138e679d7df47143d3dda10543096382656cb2028e97b50" content_hash = "sha256:4b66341c252a0c283b65ee725342a18f2b71c34811cc7853c75e78fde80815df"
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -443,6 +443,19 @@ files = [
{file = "django-autocomplete-light-3.9.7.tar.gz", hash = "sha256:a34f192ac438c4df056dbfd399550799ddc631c4661960134ded924648770373"}, {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]] [[package]]
name = "django-debug-toolbar" name = "django-debug-toolbar"
version = "4.2.0" version = "4.2.0"

View File

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