From f37ff3e87b06eab0a79342d5774de59f9354aa89 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Tue, 4 Aug 2026 11:00:24 -0700 Subject: [PATCH] Add start/end time fields to CalendarEvent (#28) (#29) ## Summary Closes #28. - Add optional `start_time` / `end_time` (`TimeField`) on `CalendarEvent` - Expose times in Django admin list + form - Replace hardcoded calendar template times with `time_range_display` - Add migration `0013` and unit coverage for time formatting ## Test plan - [ ] `python manage.py migrate` - [ ] `python manage.py test schasite.tests.CalendarEventTests` - [ ] In admin, edit an event with start/end times and confirm calendar page shows them - [ ] Confirm events without times hide the Time rowReviewed-on: https://git.aimloperations.com/ai_ml_operations/scha/pulls/29 --- schasite/admin.py | 13 +++++++++++ .../0013_calendarevent_start_end_time.py | 23 +++++++++++++++++++ schasite/models.py | 15 ++++++++++++ schasite/templates/schasite/calendar.html | 6 +++++ schasite/templates/schasite/calendar2.html | 8 +++++-- schasite/tests.py | 17 ++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 schasite/migrations/0013_calendarevent_start_end_time.py diff --git a/schasite/admin.py b/schasite/admin.py index 24adf24..7e45ef9 100644 --- a/schasite/admin.py +++ b/schasite/admin.py @@ -165,10 +165,23 @@ class CalendarEventAdmin(admin.ModelAdmin): list_display = [ "event_name", "start_date", + "start_time", "end_date", + "end_time", "coordinator_email", "event_link_name", ] + fields = [ + "event_name", + "start_date", + "start_time", + "end_date", + "end_time", + "location_name", + "coordinator_email", + "event_link_name", + "event_url", + ] class AddressModelAdmin(admin.ModelAdmin): diff --git a/schasite/migrations/0013_calendarevent_start_end_time.py b/schasite/migrations/0013_calendarevent_start_end_time.py new file mode 100644 index 0000000..b26e92d --- /dev/null +++ b/schasite/migrations/0013_calendarevent_start_end_time.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.7 on 2026-08-04 17:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0012_alter_payments_date'), + ] + + operations = [ + migrations.AddField( + model_name='calendarevent', + name='end_time', + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='calendarevent', + name='start_time', + field=models.TimeField(blank=True, null=True), + ), + ] diff --git a/schasite/models.py b/schasite/models.py index 8f10bdd..a9faa3d 100644 --- a/schasite/models.py +++ b/schasite/models.py @@ -66,6 +66,8 @@ class CalendarEvent(TimeInfoBase): event_name = models.CharField(max_length=256) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) + start_time = models.TimeField(blank=True, null=True) + end_time = models.TimeField(blank=True, null=True) location_name = models.CharField(max_length=256, blank=True, null=True) coordinator_email = models.EmailField(max_length=256, blank=True, null=True) event_link_name = models.CharField(max_length=64, blank=True, null=True) @@ -90,6 +92,19 @@ class CalendarEvent(TimeInfoBase): def no_date(self): return not self.has_date() + def time_range_display(self): + """Format start/end times for templates, e.g. '11:00 AM - 3:00 PM'.""" + from django.utils.formats import time_format + + if not self.start_time and not self.end_time: + return "" + + start = time_format(self.start_time, "g:i A") if self.start_time else "" + end = time_format(self.end_time, "g:i A") if self.end_time else "" + if start and end: + return f"{start} - {end}" + return start or end + class CalendarEventAddressModel(TimeInfoBase): calendar_event = models.OneToOneField(CalendarEvent, on_delete=models.CASCADE) diff --git a/schasite/templates/schasite/calendar.html b/schasite/templates/schasite/calendar.html index 6517634..6613732 100644 --- a/schasite/templates/schasite/calendar.html +++ b/schasite/templates/schasite/calendar.html @@ -23,6 +23,9 @@ {% else %}

Date: {{ event.start_date }} - {{ event.end_date }}

{% endif %} + {% if event.time_range_display %} +

Time: {{ event.time_range_display }}

+ {% endif %}

Location: {{ event.location_name }} - {{ event.calendareventaddressmodel }}

{% if event.coordinator_email %}

Coordinator: {{ event.coordinator_email }}

@@ -44,6 +47,9 @@ {% else %}

Date: {{ event.start_date }} - {{ event.end_date }}

{% endif %} + {% if event.time_range_display %} +

Time: {{ event.time_range_display }}

+ {% endif %}

Location: {{ event.location_name }} - {{ event.calendareventaddressmodel }}

{% if event.coordinator_email %}

Coordinator: {{ event.coordinator_email }}

diff --git a/schasite/templates/schasite/calendar2.html b/schasite/templates/schasite/calendar2.html index 5f5b276..e86ff78 100644 --- a/schasite/templates/schasite/calendar2.html +++ b/schasite/templates/schasite/calendar2.html @@ -115,7 +115,9 @@

Date: {{ event.start_date }}

-

Time: 11:00 AM - 3:00 PM

+ {% if event.time_range_display %} +

Time: {{ event.time_range_display }}

+ {% endif %}

Location: {{ event.location_name }}

Address:

Coordinator: {{ event.coordinator_name }}

@@ -159,7 +161,9 @@

Date: {{ event.start_date }}

-

Time: 9:00 AM - 12:00 PM

+ {% if event.time_range_display %} +

Time: {{ event.time_range_display }}

+ {% endif %}

Location: {{ event.location_name }}

Address:

Coordinator: {{ event.coordinator_name }}

diff --git a/schasite/tests.py b/schasite/tests.py index 70fc9ff..989b39a 100644 --- a/schasite/tests.py +++ b/schasite/tests.py @@ -321,6 +321,23 @@ class CalendarEventTests(TestCase): self.assertFalse(undated.past_event()) self.assertFalse(undated.future_event()) + def test_time_range_display(self): + event = CalendarEvent.objects.create( + event_name="Timed Event", + start_time=datetime.time(11, 0), + end_time=datetime.time(15, 0), + ) + self.assertEqual(event.time_range_display(), "11:00 AM - 3:00 PM") + + start_only = CalendarEvent.objects.create( + event_name="Start Only", + start_time=datetime.time(9, 30), + ) + self.assertEqual(start_only.time_range_display(), "9:30 AM") + + no_time = CalendarEvent.objects.create(event_name="No Time") + self.assertEqual(no_time.time_range_display(), "") + def test_calendar_page_splits_past_and_future(self): today = datetime.date.today() CalendarEvent.objects.create(