From 0048bf7cc213ebadec8bc75ad39eb00341d3facb Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Tue, 6 Oct 2026 13:06:01 -0500 Subject: [PATCH] Show in-progress multi-day events on the calendar. Closes #32. Keep an event upcoming until its end date (and end time, when set) has passed, and render the Past tab from past events. --- schasite/models.py | 27 +++++++--- schasite/templates/schasite/calendar2.html | 6 +-- schasite/tests.py | 63 +++++++++++++++++++--- 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/schasite/models.py b/schasite/models.py index a9faa3d..89f5b3b 100644 --- a/schasite/models.py +++ b/schasite/models.py @@ -77,17 +77,32 @@ class CalendarEvent(TimeInfoBase): def has_date(self): return not self.start_date is None + def _has_ended(self, now=None): + """True once the event is over. In-progress multi-day events stay open + through end_date, and through end_time on that date when one is set. + """ + if not self.has_date(): + return None + if now is None: + now = datetime.datetime.now() + + end_date = self.end_date or self.start_date + if self.end_time: + return now >= datetime.datetime.combine(end_date, self.end_time) + return now.date() > end_date + def past_event(self): - if self.has_date(): - return self.start_date <= datetime.datetime.now().date() - else: + ended = self._has_ended() + if ended is None: return False + return ended def future_event(self): - if self.has_date(): - return self.start_date > datetime.datetime.now().date() - else: + """Upcoming or currently in progress.""" + ended = self._has_ended() + if ended is None: return False + return not ended def no_date(self): return not self.has_date() diff --git a/schasite/templates/schasite/calendar2.html b/schasite/templates/schasite/calendar2.html index e86ff78..be73f45 100644 --- a/schasite/templates/schasite/calendar2.html +++ b/schasite/templates/schasite/calendar2.html @@ -114,7 +114,7 @@
-

Date: {{ event.start_date }}

+

Date: {% if event.end_date and event.end_date != event.start_date %}{{ event.start_date }} – {{ event.end_date }}{% else %}{{ event.start_date }}{% endif %}

{% if event.time_range_display %}

Time: {{ event.time_range_display }}

{% endif %} @@ -144,7 +144,7 @@
- {% for event in future_events %} + {% for event in past_events %}
@@ -160,7 +160,7 @@
-

Date: {{ event.start_date }}

+

Date: {% if event.end_date and event.end_date != event.start_date %}{{ event.start_date }} – {{ event.end_date }}{% else %}{{ event.start_date }}{% endif %}

{% if event.time_range_display %}

Time: {{ event.time_range_display }}

{% endif %} diff --git a/schasite/tests.py b/schasite/tests.py index 9028f6d..afdc331 100644 --- a/schasite/tests.py +++ b/schasite/tests.py @@ -326,6 +326,47 @@ class CalendarEventTests(TestCase): self.assertFalse(undated.past_event()) self.assertFalse(undated.future_event()) + def test_in_progress_multiday_event_counts_as_upcoming(self): + today = datetime.date.today() + ongoing = CalendarEvent.objects.create( + event_name="Block Party", + start_date=today - datetime.timedelta(days=1), + end_date=today + datetime.timedelta(days=1), + ) + ended = CalendarEvent.objects.create( + event_name="Finished Sale", + start_date=today - datetime.timedelta(days=4), + end_date=today - datetime.timedelta(days=1), + ) + + self.assertTrue(ongoing.future_event()) + self.assertFalse(ongoing.past_event()) + self.assertTrue(ended.past_event()) + self.assertFalse(ended.future_event()) + + def test_end_time_hides_event_only_after_it_finishes(self): + now = datetime.datetime.now() + still_going = now + datetime.timedelta(hours=2) + already_over = now - datetime.timedelta(hours=1) + + current = CalendarEvent.objects.create( + event_name="Still Going", + start_date=(now - datetime.timedelta(days=1)).date(), + end_date=still_going.date(), + end_time=still_going.time().replace(microsecond=0), + ) + finished = CalendarEvent.objects.create( + event_name="Already Over", + start_date=(already_over - datetime.timedelta(days=1)).date(), + end_date=already_over.date(), + end_time=already_over.time().replace(microsecond=0), + ) + + self.assertTrue(current.future_event()) + self.assertFalse(current.past_event()) + self.assertTrue(finished.past_event()) + self.assertFalse(finished.future_event()) + def test_time_range_display(self): event = CalendarEvent.objects.create( event_name="Timed Event", @@ -346,23 +387,33 @@ class CalendarEventTests(TestCase): def test_calendar_page_splits_past_and_future(self): today = datetime.date.today() CalendarEvent.objects.create( - event_name="Past Event", + event_name="Archived Picnic", start_date=today - datetime.timedelta(days=3), ) CalendarEvent.objects.create( event_name="Future Event", start_date=today + datetime.timedelta(days=3), ) + CalendarEvent.objects.create( + event_name="Ongoing Event", + start_date=today - datetime.timedelta(days=1), + end_date=today + datetime.timedelta(days=1), + ) response = self.client.get(reverse("calendar2")) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["past_events"]), 1) - self.assertEqual(len(response.context["future_events"]), 1) - self.assertEqual(response.context["past_events"][0].event_name, "Past Event") - self.assertEqual( - response.context["future_events"][0].event_name, "Future Event" - ) + self.assertEqual(len(response.context["future_events"]), 2) + self.assertEqual(response.context["past_events"][0].event_name, "Archived Picnic") + future_names = {event.event_name for event in response.context["future_events"]} + self.assertEqual(future_names, {"Future Event", "Ongoing Event"}) + content = response.content.decode() + upcoming_html, past_html = content.split('id="past" role="tabpanel"', 1) + self.assertIn("Ongoing Event", upcoming_html) + self.assertNotIn("Ongoing Event", past_html) + self.assertIn("Archived Picnic", past_html) + self.assertNotIn("Archived Picnic", upcoming_html) @override_settings(