fix: use meeting-local times for agenda editor (#4458)

* fix: use meeting-local times for agenda editor

* test: test agenda editor timezone handling
This commit is contained in:
Jennifer Richards 2022-09-15 13:18:49 -03:00 committed by GitHub
parent aa4ba65bc5
commit 3cbcfde475
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 26 deletions

View file

@ -21,6 +21,7 @@ from urllib.parse import urlparse, urlsplit, quote
from PIL import Image
from pathlib import Path
from tempfile import NamedTemporaryFile
from zoneinfo import ZoneInfo
from django.urls import reverse as urlreverse
from django.conf import settings
@ -1558,8 +1559,6 @@ class EditMeetingScheduleTests(TestCase):
@staticmethod
def _right_now_in(tzinfo):
right_now = timezone.now().astimezone(tzinfo)
if not settings.USE_TZ:
right_now = right_now.replace(tzinfo=None)
return right_now
def test_assign_session(self):
@ -1860,6 +1859,58 @@ class EditMeetingScheduleTests(TestCase):
self.assertContains(r, 'No timeslots exist')
self.assertContains(r, urlreverse('ietf.meeting.views.edit_timeslots', kwargs={'num': meeting.number}))
def test_editor_time_zone(self):
"""Agenda editor should show meeting time zone"""
time_zone = 'Etc/GMT+8'
meeting_tz = ZoneInfo(time_zone)
meeting = MeetingFactory(
type_id='ietf',
date=date_today(meeting_tz) + datetime.timedelta(days=7),
populate_schedule=False,
time_zone=time_zone,
)
meeting.schedule = ScheduleFactory(meeting=meeting)
meeting.save()
timeslot = TimeSlotFactory(meeting=meeting)
ts_start = timeslot.time.astimezone(meeting_tz)
ts_end = timeslot.end_time().astimezone(meeting_tz)
url = urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number})
self.assertTrue(self.client.login(username='secretary', password='secretary+password'))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
pq = PyQuery(r.content)
day_header = pq('.day-flow .day-label')
self.assertIn(ts_start.strftime('%A'), day_header.text())
day_swap = day_header.find('.swap-days')
self.assertEqual(day_swap.attr('data-dayid'), ts_start.date().isoformat())
self.assertEqual(day_swap.attr('data-start'), ts_start.date().isoformat())
time_label = pq('.day-flow .time-header .time-label')
self.assertEqual(len(time_label), 1)
# strftime() does not seem to support hours without leading 0, so do this manually
time_label_string = f'{ts_start.hour:d}:{ts_start.minute:02d} - {ts_end.hour:d}:{ts_end.minute:02d}'
self.assertIn(time_label_string, time_label.text())
self.assertEqual(time_label.attr('data-start'), ts_start.astimezone(datetime.timezone.utc).isoformat())
self.assertEqual(time_label.attr('data-end'), ts_end.astimezone(datetime.timezone.utc).isoformat())
ts_swap = time_label.find('.swap-timeslot-col')
origin_label = ts_swap.attr('data-origin-label')
# testing the exact date in origin_label is hard because Django's date filter uses
# different month formats than Python's strftime, so just check a couple parts.
self.assertIn(ts_start.strftime('%A'), origin_label)
self.assertIn(f'{ts_start.hour:d}:{ts_start.minute:02d}-{ts_end.hour:d}:{ts_end.minute:02d}', origin_label)
timeslot_elt = pq(f'#timeslot{timeslot.pk}')
self.assertEqual(len(timeslot_elt), 1)
self.assertEqual(timeslot_elt.attr('data-start'), ts_start.astimezone(datetime.timezone.utc).isoformat())
self.assertEqual(timeslot_elt.attr('data-end'), ts_end.astimezone(datetime.timezone.utc).isoformat())
timeslot_label = pq(f'#timeslot{timeslot.pk} .time-label')
self.assertEqual(len(timeslot_label), 1)
self.assertIn(time_label_string, timeslot_label.text())
class EditTimeslotsTests(TestCase):
def login(self, username='secretary'):

View file

@ -446,9 +446,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
lock_time = settings.MEETING_SESSION_LOCK_TIME
def timeslot_locked(ts):
meeting_now = timezone.now().astimezone(pytz.timezone(meeting.time_zone))
if not settings.USE_TZ:
meeting_now = meeting_now.replace(tzinfo=None)
meeting_now = timezone.now().astimezone(meeting.tz())
return schedule.is_official and (ts.time - meeting_now < lock_time)
if not can_see:
@ -645,7 +643,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
rd = room_data[t.location_id]
rd['timeslot_count'] += 1
rd['start_and_duration'].append((t.time, t.duration))
ttd = t.time.date()
ttd = t.local_start_time().date() # date in meeting timezone
all_days.add(ttd)
if ttd not in rd['timeslots_by_day']:
rd['timeslots_by_day'][ttd] = []
@ -830,8 +828,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
source_day = swap_days_form.cleaned_data['source_day']
target_day = swap_days_form.cleaned_data['target_day']
source_timeslots = [ts for ts in timeslots_qs if ts.time.date() == source_day]
target_timeslots = [ts for ts in timeslots_qs if ts.time.date() == target_day]
source_timeslots = [ts for ts in timeslots_qs if ts.local_start_time().date() == source_day]
target_timeslots = [ts for ts in timeslots_qs if ts.local_start_time().date() == target_day]
if any(timeslot_locked(ts) for ts in source_timeslots + target_timeslots):
return HttpResponseBadRequest("Can't swap these days.")
@ -888,8 +886,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
# possible timeslot start/ends
timeslot_groups = defaultdict(set)
for ts in timeslots_qs:
ts.start_end_group = "ts-group-{}-{}".format(ts.time.strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
timeslot_groups[ts.time.date()].add((ts.time, ts.end_time(), ts.start_end_group))
ts.start_end_group = "ts-group-{}-{}".format(ts.local_start_time().strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
timeslot_groups[ts.local_start_time().date()].add((ts.local_start_time(), ts.local_end_time(), ts.start_end_group))
# prepare sessions
prepare_sessions_for_display(sessions)
@ -970,22 +968,23 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
if ts_type.slug in session_data.get('enabled_timeslot_types', [])
]
return render(request, "meeting/edit_meeting_schedule.html", {
'meeting': meeting,
'schedule': schedule,
'can_edit': can_edit,
'can_edit_properties': can_edit or secretariat,
'secretariat': secretariat,
'days': days,
'timeslot_groups': sorted((d, list(sorted(t_groups))) for d, t_groups in timeslot_groups.items()),
'unassigned_sessions': unassigned_sessions,
'session_parents': session_parents,
'session_purposes': session_purposes,
'timeslot_types': timeslot_types,
'hide_menu': True,
'lock_time': lock_time,
'enabled_timeslot_types': enabled_timeslot_types,
})
with timezone.override(meeting.tz()):
return render(request, "meeting/edit_meeting_schedule.html", {
'meeting': meeting,
'schedule': schedule,
'can_edit': can_edit,
'can_edit_properties': can_edit or secretariat,
'secretariat': secretariat,
'days': days,
'timeslot_groups': sorted((d, list(sorted(t_groups))) for d, t_groups in timeslot_groups.items()),
'unassigned_sessions': unassigned_sessions,
'session_parents': session_parents,
'session_purposes': session_purposes,
'timeslot_types': timeslot_types,
'hide_menu': True,
'lock_time': lock_time,
'enabled_timeslot_types': enabled_timeslot_types,
})
class RoomNameModelChoiceField(forms.ModelChoiceField):