Skip to content

Commit 59b49c8

Browse files
committed
Fixed a number of TimeSlot-related issues, in particular related to getting the correct date from a timeslot. Also fixed the displayed slot times in the new schedule editors to show local times, not UTC times.
- Legacy-Id: 18785
1 parent d9ad8b8 commit 59b49c8

17 files changed

Lines changed: 90 additions & 66 deletions

ietf/meeting/helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def build_all_agenda_slices(meeting):
8585
date_slices = {}
8686

8787
for ts in meeting.timeslot_set.filter(type__in=['regular',]).order_by('time','name'):
88-
ymd = ts.time.date()
88+
ymd = ts.local_date()
8989

9090
if ymd not in date_slices and ts.location != None:
9191
date_slices[ymd] = []
@@ -629,7 +629,7 @@ def send_interim_meeting_cancellation_notice(meeting):
629629
acronym=group.acronym,
630630
type=group.type.slug.upper(),
631631
date=meeting.date.strftime('%Y-%m-%d'))
632-
start_time = session.official_timeslotassignment().timeslot.time.astimezone(meeting.tz())
632+
start_time = session.official_timeslotassignment().timeslot.local_start_time()
633633
end_time = start_time + session.requested_duration
634634
is_multi_day = session.meeting.session_set.with_current_status().filter(current_status='sched').count() > 1
635635
template = 'meeting/interim_meeting_cancellation_notice.txt'
@@ -646,7 +646,7 @@ def send_interim_meeting_cancellation_notice(meeting):
646646
def send_interim_session_cancellation_notice(session):
647647
"""Sends an email that one session of a scheduled interim meeting has been cancelled."""
648648
group = session.group
649-
start_time = session.official_timeslotassignment().timeslot.time
649+
start_time = session.official_timeslotassignment().timeslot.local_start_time()
650650
end_time = start_time + session.requested_duration
651651
(to_email, cc_list) = gather_address_lists('interim_cancelled',group=group)
652652
from_email = settings.INTERIM_ANNOUNCE_FROM_EMAIL_PROGRAM if group.type_id=='program' else settings.INTERIM_ANNOUNCE_FROM_EMAIL_DEFAULT

ietf/meeting/tests_js.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,41 @@
22
# -*- coding: utf-8 -*-
33

44

5-
import time
65
import datetime
7-
import shutil
86
import os
7+
import pytz
98
import re
9+
import shutil
10+
import time
11+
1012
from unittest import skipIf
1113

1214
import django
1315
from django.db.models import F
1416
from django.urls import reverse as urlreverse
15-
from django.utils import timezone
1617
from django.utils.text import slugify
1718
#from django.test.utils import override_settings
1819

1920
import debug # pyflakes:ignore
2021

22+
from ietf import settings
2123
from ietf.doc.factories import DocumentFactory
2224
from ietf.doc.models import State
2325
from ietf.group import colors
24-
from ietf.person.models import Person
25-
from ietf.group.models import Group
2626
from ietf.group.factories import GroupFactory
27+
from ietf.group.models import Group
2728
from ietf.meeting.factories import SessionFactory
28-
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
2929
from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
3030
Room, TimeSlot, Constraint, ConstraintName,
31-
Meeting, SchedulingEvent, SessionStatusName)
31+
Meeting, SchedulingEvent, SessionStatusName,
32+
Person)
33+
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
3234
from ietf.meeting.utils import add_event_info_to_session_qs
3335
from ietf.utils.pipe import pipe
3436
from ietf.utils.test_runner import IetfLiveServerTestCase
3537
from ietf.utils.test_utils import assert_ical_response_is_valid
36-
from ietf import settings
38+
from ietf.utils.timezone import datetime_today_start
39+
3740

3841
skip_selenium = False
3942
skip_message = ""
@@ -102,7 +105,7 @@ def test_edit_meeting_schedule(self):
102105
schedule = Schedule.objects.filter(meeting=meeting, owner__user__username="plain").first()
103106

104107
room1 = Room.objects.get(name="Test Room")
105-
slot1 = TimeSlot.objects.filter(meeting=meeting, location=room1).order_by('_time').first()
108+
slot1 = TimeSlot.objects.filter(meeting=meeting, location=room1).order_by('time').first()
106109

107110
room2 = Room.objects.create(meeting=meeting, name="Test Room2", capacity=1)
108111
room2.session_types.add('regular')
@@ -265,14 +268,14 @@ def test_edit_meeting_schedule(self):
265268
# hide timeslots
266269
self.driver.find_element_by_css_selector(".timeslot-group-toggles button").click()
267270
self.assertTrue(self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
268-
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.time.strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
271+
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.local_start_time().strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
269272
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [data-dismiss=\"modal\"]").click()
270273
self.assertTrue(not self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
271274

272275
# swap days
273-
self.driver.find_element_by_css_selector(".day [data-target=\"#swap-days-modal\"][data-dayid=\"{}\"]".format(slot4.time.date().isoformat())).click()
276+
self.driver.find_element_by_css_selector(".day [data-target=\"#swap-days-modal\"][data-dayid=\"{}\"]".format(slot4.local_date().isoformat())).click()
274277
self.assertTrue(self.driver.find_element_by_css_selector("#swap-days-modal").is_displayed())
275-
self.driver.find_element_by_css_selector("#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.time.date().isoformat())).click()
278+
self.driver.find_element_by_css_selector("#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.local_date().isoformat())).click()
276279
self.driver.find_element_by_css_selector("#swap-days-modal button[type=\"submit\"]").click()
277280

278281
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{} #session{}'.format(slot4.pk, s1.pk)))
@@ -734,13 +737,15 @@ def session_from_agenda_row_id(self, row_id):
734737
# for others (break, reg, other):
735738
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug>
736739
meeting_number = components[1]
737-
start_time = datetime.datetime(
740+
meeting = Meeting.objects.get(number=meeting_number)
741+
tz = pytz.timezone(meeting.time_zone)
742+
start_time = tz.localize(datetime.datetime(
738743
year=int(components[2]),
739744
month=int(components[3]),
740745
day=int(components[4]),
741746
hour=int(components[6][0:2]),
742747
minute=int(components[6][2:4]),
743-
)
748+
))
744749
# If labeled as plenary, it's plenary...
745750
if components[7] == '1plenary':
746751
session_type = 'plenary'
@@ -756,8 +761,7 @@ def session_from_agenda_row_id(self, row_id):
756761
else:
757762
# Last component was a group, this is a regular session
758763
session_type = 'regular'
759-
760-
meeting = Meeting.objects.get(number=meeting_number)
764+
761765
possible_assignments = SchedTimeSessAssignment.objects.filter(
762766
schedule__in=[meeting.schedule, meeting.schedule.base],
763767
timeslot__time=start_time,
@@ -916,7 +920,7 @@ def setUp(self):
916920

917921
# Create a group with a plenary interim session for testing type filters
918922
somegroup = GroupFactory(acronym='sg', name='Some Group')
919-
sg_interim = make_interim_meeting(somegroup, timezone.now().date() + datetime.timedelta(days=20))
923+
sg_interim = make_interim_meeting(somegroup, datetime_today_start() + datetime.timedelta(days=20))
920924
sg_sess = sg_interim.session_set.first()
921925
sg_slot = sg_sess.timeslotassignments.first().timeslot
922926
sg_sess.type_id = 'plenary'
@@ -946,7 +950,7 @@ def displayed_interims(self, groups=None):
946950
Session.objects.filter(
947951
meeting__type_id='interim',
948952
timeslotassignments__schedule=F('meeting__schedule'),
949-
timeslotassignments__timeslot__time__gte=datetime.datetime.today()
953+
timeslotassignments__timeslot__time__gte=datetime_today_start()
950954
)
951955
).filter(current_status__in=('sched','canceled'))
952956
meetings = []
@@ -959,7 +963,7 @@ def displayed_interims(self, groups=None):
959963
def all_ietf_meetings(self):
960964
meetings = Meeting.objects.filter(
961965
type_id='ietf',
962-
date__gte=datetime.datetime.today()-datetime.timedelta(days=7)
966+
date__gte=datetime_today_start()-datetime.timedelta(days=7)
963967
)
964968
for m in meetings:
965969
m.calendar_label = 'IETF %s' % m.number
@@ -1084,7 +1088,7 @@ def assert_upcoming_view_filter_matches_ics_filter(self, filter_string):
10841088
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
10851089
schedule__in=expected_schedules,
10861090
session__in=expected_interim_sessions,
1087-
timeslot__time__gte=timezone.now().date(),
1091+
timeslot__time__gte=datetime_today_start(),
10881092
))
10891093
# The UID formats should match those in the upcoming.ics template
10901094
expected_uids = [

ietf/meeting/tests_views.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,8 +1421,8 @@ def test_edit_meeting_schedule(self):
14211421

14221422
r = self.client.post(url, {
14231423
'action': 'swapdays',
1424-
'source_day': timeslots[0].time.date().isoformat(),
1425-
'target_day': timeslots[2].time.date().isoformat(),
1424+
'source_day': timeslots[0].local_date().isoformat(),
1425+
'target_day': timeslots[2].local_date().isoformat(),
14261426
})
14271427
self.assertResponseStatus(r, 302)
14281428

@@ -1434,8 +1434,8 @@ def test_edit_meeting_schedule(self):
14341434
# swap back
14351435
r = self.client.post(url, {
14361436
'action': 'swapdays',
1437-
'source_day': timeslots[2].time.date().isoformat(),
1438-
'target_day': timeslots[0].time.date().isoformat(),
1437+
'source_day': timeslots[2].local_date().isoformat(),
1438+
'target_day': timeslots[0].local_date().isoformat(),
14391439
})
14401440
self.assertResponseStatus(r, 302)
14411441

@@ -1469,7 +1469,7 @@ def test_edit_meeting_timeslots_and_misc_sessions(self):
14691469

14701470
break_slot = TimeSlot.objects.get(location=break_room, type='break')
14711471

1472-
room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.time.date().isoformat(), break_slot.location_id))
1472+
room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.local_date().isoformat(), break_slot.location_id))
14731473
self.assertTrue(room_row)
14741474
self.assertTrue(room_row.find("#timeslot{}".format(break_slot.pk)))
14751475

@@ -1584,7 +1584,7 @@ def test_edit_meeting_timeslots_and_misc_sessions(self):
15841584

15851585
r = self.client.post(url, {
15861586
'timeslot': assignment.timeslot_id,
1587-
'day': assignment.timeslot.time.date().isoformat(),
1587+
'day': assignment.timeslot.local_date().isoformat(),
15881588
'time': assignment.timeslot.time.time().isoformat(),
15891589
'duration': assignment.timeslot.duration,
15901590
'location': assignment.timeslot.location_id,
@@ -3248,7 +3248,7 @@ def test_interim_request_edit(self):
32483248
data.update(form_initial)
32493249
r = self.client.post(url, data)
32503250
if r.status_code != 302:
3251-
self.save_response(r)
3251+
self.debug_save_response(r)
32523252
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}))
32533253
self.assertEqual(len(outbox),length_before+1)
32543254
self.assertIn('CHANGED', outbox[-1]['Subject'])

ietf/meeting/views.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -654,8 +654,8 @@ def prepare_sessions_for_display(sessions):
654654
source_day = swap_days_form.cleaned_data['source_day']
655655
target_day = swap_days_form.cleaned_data['target_day']
656656

657-
source_timeslots = [ts for ts in timeslots_qs if ts.time.date() == source_day]
658-
target_timeslots = [ts for ts in timeslots_qs if ts.time.date() == target_day]
657+
source_timeslots = [ts for ts in timeslots_qs if ts.local_date() == source_day]
658+
target_timeslots = [ts for ts in timeslots_qs if ts.local_date() == target_day]
659659
swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, target_timeslots, target_day - source_day)
660660

661661
return HttpResponseRedirect(request.get_full_path())
@@ -668,10 +668,10 @@ def prepare_sessions_for_display(sessions):
668668
room_has_timeslots = set()
669669
for t in timeslots_qs:
670670
room_has_timeslots.add(t.location_id)
671-
timeslots_by_room_and_day[(t.location_id, t.time.date())].append(t)
671+
timeslots_by_room_and_day[(t.location_id, t.local_date())].append(t)
672672

673673
days = []
674-
for day in sorted(set(t.time.date() for t in timeslots_qs)):
674+
for day in sorted(set(t.local_date() for t in timeslots_qs)):
675675
room_timeslots = []
676676
for r in rooms:
677677
if r.pk not in room_has_timeslots:
@@ -694,8 +694,8 @@ def prepare_sessions_for_display(sessions):
694694
# possible timeslot start/ends
695695
timeslot_groups = defaultdict(set)
696696
for ts in timeslots_qs:
697-
ts.start_end_group = "ts-group-{}-{}".format(ts.time.strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
698-
timeslot_groups[ts.time.date()].add((ts.time, ts.end_time(), ts.start_end_group))
697+
ts.start_end_group = "ts-group-{}-{}".format(ts.local_start_time().strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
698+
timeslot_groups[ts.local_date()].add((ts.local_start_time(), ts.local_end_time(), ts.start_end_group))
699699

700700
# prepare sessions
701701
prepare_sessions_for_display(sessions)
@@ -792,7 +792,7 @@ def __init__(self, meeting, schedule, *args, timeslot=None, **kwargs):
792792

793793
if timeslot:
794794
self.initial = {
795-
'day': timeslot.time.date(),
795+
'day': timeslot.local_date(),
796796
'time': timeslot.time.time(),
797797
'duration': timeslot.duration,
798798
'location': timeslot.location_id,
@@ -1033,7 +1033,7 @@ def redirect_with_scroll():
10331033

10341034
timeslots_by_day_and_room = defaultdict(list)
10351035
for t in timeslot_qs:
1036-
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
1036+
timeslots_by_day_and_room[(t.local_date(), t.location_id)].append(t)
10371037

10381038
min_time = min([t.time.time() for t in timeslot_qs] + [datetime.time(8)])
10391039
max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)])
@@ -1567,7 +1567,7 @@ def agenda_by_room(request, num=None, name=None, owner=None):
15671567
for day in assignments.dates('timeslot__time','day'):
15681568
ss_by_day[day]=[]
15691569
for ss in assignments.order_by('timeslot__location__functional_name','timeslot__location__name','timeslot__time'):
1570-
day = ss.timeslot.time.date()
1570+
day = ss.timeslot.local_date()
15711571
ss_by_day[day].append(ss)
15721572
return render(request,"meeting/agenda_by_room.html",{"meeting":meeting,"schedule":schedule,"ss_by_day":ss_by_day})
15731573

@@ -3233,8 +3233,8 @@ def interim_request_session_cancel(request, sessionid):
32333233
messages.success(request, 'Interim meeting session cancelled')
32343234
return redirect(interim_request_details, number=session.meeting.number)
32353235
else:
3236-
session_time = session.official_timeslotassignment().timeslot.time
3237-
form = InterimCancelForm(initial={'group': group.acronym, 'date': session_time.date()})
3236+
date = session.official_timeslotassignment().timeslot.local_date()
3237+
form = InterimCancelForm(initial={'group': group.acronym, 'date': date})
32383238

32393239
return render(request, "meeting/interim_request_cancel.html", {
32403240
"form": form,
@@ -3732,8 +3732,8 @@ def err(code, text):
37323732
else:
37333733
return err(400, "URL is the same")
37343734
else:
3735-
time = session.official_timeslotassignment().timeslot.time
3736-
title = 'Video recording for %s on %s at %s' % (acronym, time.date(), time.time())
3735+
timeslot = session.official_timeslotassignment().timeslot
3736+
title = 'Video recording for %s on %s at %s' % (acronym, timeslot.local_date(), timeslot.time.time())
37373737
create_recording(session, url, title=title, user=user)
37383738
else:
37393739
return err(405, "Method not allowed")

ietf/secr/meetings/forms.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ def get_next_slot(slot):
3030
aren't any. You must check availability of the slot as we sometimes need to get the next
3131
slot whether it's available or not. For use with combine option.
3232
'''
33+
# timezone-aware note: The following works because the slot times are
34+
# saved as if they were local timezone-naive times, even if Django sees
35+
# them as UTC times when USE_TZ == True.
3336
same_day_slots = TimeSlot.objects.filter(meeting=slot.meeting,location=slot.location,time__day=slot.time.day).order_by('time')
3437
try:
3538
i = list(same_day_slots).index(slot)

ietf/secr/meetings/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ def misc_session_edit(request, meeting_id, schedule_name, slot_id):
553553
else:
554554
# we need to pass the session to the form in order to disallow changing
555555
# of group after materials have been uploaded
556-
delta = slot.time.date() - meeting.date
556+
delta = slot.local_date() - meeting.date
557557
initial = {'location':slot.location,
558558
'group':session.group,
559559
'name':session.name,

ietf/secr/proceedings/proc_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def create_recording(session, url, title=None, user=None):
147147
'''
148148
sequence = get_next_sequence(session.group,session.meeting,'recording')
149149
name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence)
150-
time = session.official_timeslotassignment().timeslot.time.strftime('%Y-%m-%d %H:%M')
150+
time = session.official_timeslotassignment().timeslot.local_start_time().strftime('%Y-%m-%d %H:%M')
151151
if not title:
152152
if url.endswith('mp3'):
153153
title = 'Audio recording for {}'.format(time)

ietf/templates/meeting/agenda.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ <h2>
284284
{% if "-utc" in request.path %}
285285
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
286286
{% else %}
287-
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}
287+
{{ item.session.rescheduled_to.local_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.local_end_time|date:"G:i" }}
288288
{% endif %}
289289
{% endif %}
290290
</span>

0 commit comments

Comments
 (0)