diff --git a/cmsmanage/settings/base.py b/cmsmanage/settings/base.py index 785b8a3..4ab9574 100644 --- a/cmsmanage/settings/base.py +++ b/cmsmanage/settings/base.py @@ -37,6 +37,7 @@ INSTALLED_APPS = [ "rest_framework", "rest_framework.authtoken", "django_q", + "django_bleach", "tasks.apps.TasksConfig", "rentals.apps.RentalsConfig", "membershipworks.apps.MembershipworksConfig", diff --git a/membershipworks/templates/membershipworks/upcoming_events.dj.html b/membershipworks/templates/membershipworks/upcoming_events.dj.html new file mode 100644 index 0000000..8aea82c --- /dev/null +++ b/membershipworks/templates/membershipworks/upcoming_events.dj.html @@ -0,0 +1,121 @@ +{% extends "base.dj.html" %} + +{% load bleach_tags %} + +{% block title %}Upcoming Events{% endblock %} +{% block content %} +
+

+ +

+

Greetings Upper Valley Makers:

+

We have an exciting list of upcoming classes at the Claremont MakerSpace that we think might interest you.

+
+ For most classes and events, CMS MEMBERSHIP IS NOT REQUIRED. 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). +
+
+ Class policies (liability waiver, withdrawal, cancellation, etc.) can be found here. +
+
+ Instructors: Interested in teaching a class at CMS? Please fill out our Class Proposal Form. +
+
+ Tours: Want to see what the Claremont MakerSpace is all about? Tours are by appointment only. +
+ Contact Us 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. +
+ {% for section in event_sections %} + {% if section.events %} +

{{ section.title }}

+

+ {{ section.blurb }} +

+ {% for event in section.events %} + {% with url="https://claremontmakerspace.org/events/#!event/register/"|add:event.url %} + {% spaceless %} +

+ + {# djlint:off H006,H013 #} + {% if "lgo" in event %}{% endif %} + {# djlint:on #} + {{ event.ttl|bleach }} + +

+ {% endspaceless %} + {% spaceless %} +
+ {# wordpress is very annoying with spacing here #} + {# djlint:off #} + + {# TODO: different dates probably implies multiple instances. Should read RRULE or similar from the event notes #} + {{ event.sdp_dt|date }} {{ event.sdp_dt|time }} — {% if event.sdp_dt.date != event.edp_dt.date %}{{ event.edp_dt|date }}{% endif %} {{ event.edp_dt|time }} + + {# djlint:on #} +
+ {% endspaceless %} + {% if not section.truncate %} +
{{ event.dtl|bleach:"a,abbr,acronym,b,blockquote,code,em,i,li,ol,strong,ul,p,span,br,div" }}
+
+ Register for this class now! +
+ {% endif %} +
+ {% endwith %} + {% endfor %} + {% endif %} + {% endfor %} +
+
Happy Makin’!
+
+ We are grateful for all of the public support that our 501(c)(3), non-profit organization receives. If you’d + like to make a donation,please visit the Support Us + page of our website. +
+
+
+
+ +
+{% endblock %} +{% block script %} + +{% endblock %} diff --git a/membershipworks/urls.py b/membershipworks/urls.py index 726e9d8..f36170d 100644 --- a/membershipworks/urls.py +++ b/membershipworks/urls.py @@ -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", + ), ] diff --git a/membershipworks/views.py b/membershipworks/views.py index 80ebca4..80767a9 100644 --- a/membershipworks/views.py +++ b/membershipworks/views.py @@ -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) diff --git a/pdm.lock b/pdm.lock index 297d608..b397515 100644 --- a/pdm.lock +++ b/pdm.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index e310a38..bcc3e0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"