diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 492854704..2215113a5 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -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'): diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 1d38346c7..cd2e5e144 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -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):