refactor: refactor timestamp handling so tests in meeting app pass (#4371)
* refactor: replace datetime.now with timezone.now * refactor: migrate model fields to use timezone.now as default * refactor: replace datetime.today with timezone.now datetime.datetime.today() is equivalent to datetime.datetime.now(); both return a naive datetime with the current local time. * refactor: rephrase datetime.now(tz) as timezone.now().astimezone(tz) This is effectively the same, but is less likely to encourage accidental use of naive datetimes. * refactor: revert datetime.today() change to old migrations * refactor: change a missed datetime.now to timezone.now * chore: renumber timezone_now migration * chore: add migration to change timestamps to UTC * refactor: move tz instantiation/caching from TimeSlot to Meeting * fix: assume utc if meeting.time_zone is blank * chore: make datetime.combine() calls tz aware in the meeting app * ci: correctly use meeting.tz in TimeSlotFactory * chore: compute TimeSlot utc / local times assuming tz-aware times * chore: use tzaware math for agenda editor timeslot layout * chore: fill in Meeting.time_zone where it is blank Nearly all interim meetings on or before 2016-07-01 have blank time_zone values. This migration fills these in with PST8PDT. * chore: disallow blank Meeting.time_zone value * refactor: no need to handle blank time_zone case in TZ migration * refactor: remove now-unnecessary checks that meeting has time_zone * chore: fix timezone handling in agenda.ics and Meeting.updated() * chore: fix tz handling in interim_request_details, exercise in tests * chore: fix timezone handling for test_interim_send_announcement * chore: fix timezone handling in agenda_json() * chore: fix timezone handling in old agenda * chore: fix timezone handling for EditTimeslotsTests * refactor: refactor a few fixes for more consistent timezone handling * chore: add timezone info to timestamps in fixtures * chore: remove naive datetime warnings found in meetings.tests_views * chore: fix a few more test failures in meetings.tests_views All tests in meetings.tests_views now passing * chore: remove unused import * chore: fix timezone handling in test_schedule_generator.py * chore: fix timezone handling affecting meeting.tests_js * chore: fix timeslot test bug when local date != UTC date * test: fix a few failing tests, all meetings tests now pass (for me, anyway) * chore: renumber migrations * chore: update timestamp conversion migration The django-celery-beat package introduces tables with timestamp columns. These columns are stored in CELERY_TIMEZONE. Because we run with this set to UTC, the migration ignores these columns. * chore: fix pytz-related change in migration * chore: remove duplicate migrations * chore: remove CELERY_BEAT_TZ_AWARE setting now that USE_TZ is True * test: avoid failure in test with bogus timezone
This commit is contained in:
parent
42203d7a9c
commit
8b52d27b02
|
@ -2,7 +2,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
import email.utils
|
||||
import jsonfield
|
||||
import os
|
||||
|
@ -181,11 +180,15 @@ class Group(GroupInfo):
|
|||
return self.role_set.none()
|
||||
|
||||
def status_for_meeting(self,meeting):
|
||||
end_date = meeting.end_date()+datetime.timedelta(days=1)
|
||||
previous_meeting = meeting.previous_meeting()
|
||||
status_events = self.groupevent_set.filter(type='status_update',time__lte=end_date).order_by('-time')
|
||||
status_events = self.groupevent_set.filter(
|
||||
type='status_update',
|
||||
time__lt=meeting.end_datetime(),
|
||||
).order_by('-time')
|
||||
if previous_meeting:
|
||||
status_events = status_events.filter(time__gte=previous_meeting.end_date()+datetime.timedelta(days=1))
|
||||
status_events = status_events.filter(
|
||||
time__gte=previous_meeting.end_datetime()
|
||||
)
|
||||
return status_events.first()
|
||||
|
||||
def get_description(self):
|
||||
|
|
|
@ -187,7 +187,9 @@ class TimeSlotFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
@factory.lazy_attribute
|
||||
def time(self):
|
||||
return datetime.datetime.combine(self.meeting.date,datetime.time(11,0))
|
||||
return self.meeting.tz().localize(
|
||||
datetime.datetime.combine(self.meeting.date, datetime.time(11, 0))
|
||||
)
|
||||
|
||||
@factory.lazy_attribute
|
||||
def duration(self):
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"comments": "",
|
||||
"list_subscribe": "",
|
||||
"state": "active",
|
||||
"time": "2012-02-26T00:21:36",
|
||||
"time": "2012-02-26T00:21:36Z",
|
||||
"unused_tags": [],
|
||||
"list_archive": "",
|
||||
"type": "ietf",
|
||||
|
|
|
@ -118,7 +118,10 @@ def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefe
|
|||
# assignments = list(assignments_queryset) # make sure we're set in stone
|
||||
assignments = assignments_queryset
|
||||
|
||||
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
|
||||
# meeting_time is meeting-local midnight at the start of the meeting date
|
||||
meeting_time = meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time())
|
||||
)
|
||||
|
||||
# replace groups with historic counterparts
|
||||
groups = [ ]
|
||||
|
@ -1149,11 +1152,15 @@ def sessions_post_cancel(request, sessions):
|
|||
|
||||
|
||||
def update_interim_session_assignment(form):
|
||||
"""Helper function to create / update timeslot assigned to interim session"""
|
||||
time = datetime.datetime.combine(
|
||||
form.cleaned_data['date'],
|
||||
form.cleaned_data['time'])
|
||||
"""Helper function to create / update timeslot assigned to interim session
|
||||
|
||||
form is an InterimSessionModelForm
|
||||
"""
|
||||
session = form.instance
|
||||
meeting = session.meeting
|
||||
time = meeting.tz().localize(
|
||||
datetime.datetime.combine(form.cleaned_data['date'], form.cleaned_data['time'])
|
||||
)
|
||||
if session.official_timeslotassignment():
|
||||
slot = session.official_timeslotassignment().timeslot
|
||||
slot.time = time
|
||||
|
@ -1161,14 +1168,14 @@ def update_interim_session_assignment(form):
|
|||
slot.save()
|
||||
else:
|
||||
slot = TimeSlot.objects.create(
|
||||
meeting=session.meeting,
|
||||
meeting=meeting,
|
||||
type_id='regular',
|
||||
duration=session.requested_duration,
|
||||
time=time)
|
||||
SchedTimeSessAssignment.objects.create(
|
||||
timeslot=slot,
|
||||
session=session,
|
||||
schedule=session.meeting.schedule)
|
||||
schedule=meeting.schedule)
|
||||
|
||||
def populate_important_dates(meeting):
|
||||
assert ImportantDate.objects.filter(meeting=meeting).exists() is False
|
||||
|
|
|
@ -48,7 +48,7 @@ import socket
|
|||
import datetime
|
||||
import pytz
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
||||
|
@ -75,10 +75,12 @@ class Command(BaseCommand):
|
|||
|
||||
def _meeting_datetime(self, day, *time_args):
|
||||
"""Generate a datetime on a meeting day"""
|
||||
return datetime.datetime.combine(
|
||||
self.start_date,
|
||||
datetime.time(*time_args)
|
||||
) + datetime.timedelta(days=day)
|
||||
return self.meeting_tz.localize(
|
||||
datetime.datetime.combine(
|
||||
self.start_date,
|
||||
datetime.time(*time_args)
|
||||
) + datetime.timedelta(days=day)
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]:
|
||||
|
@ -87,10 +89,7 @@ class Command(BaseCommand):
|
|||
opt_delete = options.get('delete', False)
|
||||
opt_use_old_conflicts = options.get('old_conflicts', False)
|
||||
self.start_date = options['start_date']
|
||||
meeting_tz = options['tz']
|
||||
if not opt_delete and (meeting_tz not in pytz.common_timezones):
|
||||
self.stderr.write("Warning: {} is not a recognized time zone.".format(meeting_tz))
|
||||
|
||||
meeting_tzname = options['tz']
|
||||
if opt_delete:
|
||||
if Meeting.objects.filter(number='999').exists():
|
||||
Meeting.objects.filter(number='999').delete()
|
||||
|
@ -98,6 +97,11 @@ class Command(BaseCommand):
|
|||
else:
|
||||
self.stderr.write("Dummy meeting IETF 999 does not exist; nothing to do.\n")
|
||||
else:
|
||||
try:
|
||||
self.meeting_tz = pytz.timezone(meeting_tzname)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
raise CommandError("{} is not a recognized time zone.".format(meeting_tzname))
|
||||
|
||||
if Meeting.objects.filter(number='999').exists():
|
||||
self.stderr.write("Dummy meeting IETF 999 already exists; nothing to do.\n")
|
||||
else:
|
||||
|
@ -111,7 +115,7 @@ class Command(BaseCommand):
|
|||
type_id='IETF',
|
||||
date=self._meeting_datetime(0).date(),
|
||||
days=7,
|
||||
time_zone=meeting_tz,
|
||||
time_zone=meeting_tzname,
|
||||
)
|
||||
|
||||
# Set enabled constraints
|
||||
|
|
|
@ -138,6 +138,11 @@ class Meeting(models.Model):
|
|||
def end_date(self):
|
||||
return self.get_meeting_date(self.days-1)
|
||||
|
||||
def end_datetime(self):
|
||||
"""Datetime of the first instant _after_ the meeting's last day"""
|
||||
return self.tz().localize(
|
||||
datetime.datetime.combine(self.get_meeting_date(self.days), datetime.time())
|
||||
)
|
||||
def get_00_cutoff(self):
|
||||
start_date = datetime.datetime(year=self.date.year, month=self.date.month, day=self.date.day, tzinfo=pytz.utc)
|
||||
importantdate = self.importantdate_set.filter(name_id='idcutoff').first()
|
||||
|
@ -322,7 +327,7 @@ class Meeting(models.Model):
|
|||
for ts in self.timeslot_set.all():
|
||||
if ts.location_id is None:
|
||||
continue
|
||||
ymd = ts.time.date()
|
||||
ymd = ts.local_start_time().date()
|
||||
if ymd not in time_slices:
|
||||
time_slices[ymd] = []
|
||||
slots[ymd] = []
|
||||
|
@ -330,15 +335,15 @@ class Meeting(models.Model):
|
|||
|
||||
if ymd in time_slices:
|
||||
# only keep unique entries
|
||||
if [ts.time, ts.time + ts.duration, ts.duration.seconds] not in time_slices[ymd]:
|
||||
time_slices[ymd].append([ts.time, ts.time + ts.duration, ts.duration.seconds])
|
||||
if [ts.local_start_time(), ts.local_end_time(), ts.duration.seconds] not in time_slices[ymd]:
|
||||
time_slices[ymd].append([ts.local_start_time(), ts.local_end_time(), ts.duration.seconds])
|
||||
slots[ymd].append(ts)
|
||||
|
||||
days.sort()
|
||||
for ymd in time_slices:
|
||||
# Make sure these sort the same way
|
||||
time_slices[ymd].sort()
|
||||
slots[ymd].sort(key=lambda x: (x.time, x.duration))
|
||||
slots[ymd].sort(key=lambda x: (x.local_start_time(), x.duration))
|
||||
return days,time_slices,slots
|
||||
|
||||
# this functions makes a list of timeslices and rooms, and
|
||||
|
@ -354,6 +359,11 @@ class Meeting(models.Model):
|
|||
# SchedTimeSessAssignment.objects.create(schedule = sched,
|
||||
# timeslot = ts)
|
||||
|
||||
def tz(self):
|
||||
if not hasattr(self, '_cached_tz'):
|
||||
self._cached_tz = pytz.timezone(self.time_zone)
|
||||
return self._cached_tz
|
||||
|
||||
def vtimezone(self):
|
||||
try:
|
||||
tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
|
||||
|
@ -374,16 +384,14 @@ class Meeting(models.Model):
|
|||
self.save()
|
||||
|
||||
def updated(self):
|
||||
min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
|
||||
# should be Meeting.modified, but we don't have that
|
||||
min_time = pytz.utc.localize(datetime.datetime(1970, 1, 1, 0, 0, 0))
|
||||
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
assignments_updated = min_time
|
||||
if self.schedule:
|
||||
assignments_updated = SchedTimeSessAssignment.objects.filter(schedule__in=[self.schedule, self.schedule.base if self.schedule else None]).aggregate(Max('modified'))["modified__max"] or min_time
|
||||
ts = max(timeslots_updated, sessions_updated, assignments_updated)
|
||||
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
|
||||
ts = tz.localize(ts)
|
||||
return ts
|
||||
return max(timeslots_updated, sessions_updated, assignments_updated)
|
||||
|
||||
@memoize
|
||||
def previous_meeting(self):
|
||||
|
@ -604,29 +612,22 @@ class TimeSlot(models.Model):
|
|||
return self._cached_html_location
|
||||
|
||||
def tz(self):
|
||||
if not hasattr(self, '_cached_tz'):
|
||||
self._cached_tz = pytz.timezone(self.meeting.time_zone)
|
||||
return self._cached_tz
|
||||
return self.meeting.tz()
|
||||
|
||||
def tzname(self):
|
||||
return self.tz().tzname(self.time)
|
||||
|
||||
def utc_start_time(self):
|
||||
local_start_time = self.tz().localize(self.time)
|
||||
return local_start_time.astimezone(pytz.utc)
|
||||
return self.time.astimezone(pytz.utc) # USE_TZ is True, so time is aware
|
||||
|
||||
def utc_end_time(self):
|
||||
utc_start = self.utc_start_time()
|
||||
# Add duration after converting start time, otherwise errors creep in around DST change
|
||||
return None if utc_start is None else utc_start + self.duration
|
||||
return self.time.astimezone(pytz.utc) + self.duration # USE_TZ is True, so time is aware
|
||||
|
||||
def local_start_time(self):
|
||||
return self.tz().localize(self.time)
|
||||
return self.time.astimezone(self.tz())
|
||||
|
||||
def local_end_time(self):
|
||||
local_start = self.local_start_time()
|
||||
# Add duration after converting start time, otherwise errors creep in around DST change
|
||||
return None if local_start is None else local_start + self.duration
|
||||
return (self.time.astimezone(pytz.utc) + self.duration).astimezone(self.tz())
|
||||
|
||||
@property
|
||||
def js_identifier(self):
|
||||
|
|
|
@ -21,10 +21,12 @@ from ietf.person.factories import PersonFactory
|
|||
from ietf.person.models import Person
|
||||
from ietf.utils.test_data import make_test_data
|
||||
|
||||
def make_interim_meeting(group,date,status='sched'):
|
||||
def make_interim_meeting(group,date,status='sched',tz='UTC'):
|
||||
system_person = Person.objects.get(name="(System)")
|
||||
time = datetime.datetime.combine(date, datetime.time(9))
|
||||
meeting = create_interim_meeting(group=group,date=date)
|
||||
meeting = create_interim_meeting(group=group,date=date,timezone=tz)
|
||||
time = meeting.tz().localize(
|
||||
datetime.datetime.combine(date, datetime.time(9))
|
||||
)
|
||||
session = SessionFactory(meeting=meeting, group=group,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
|
@ -102,24 +104,37 @@ def make_meeting_test_data(meeting=None, create_interims=False):
|
|||
|
||||
# slots
|
||||
session_date = meeting.date + datetime.timedelta(days=1)
|
||||
tz = meeting.tz()
|
||||
slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(9, 30)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(9, 30))
|
||||
))
|
||||
slot2 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(10, 50)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(10, 50))
|
||||
))
|
||||
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", location=breakfast_room,
|
||||
duration=datetime.timedelta(minutes=90),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(7,0))
|
||||
))
|
||||
reg_slot = TimeSlot.objects.create(meeting=meeting, type_id="reg", location=reg_room,
|
||||
duration=datetime.timedelta(minutes=480),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(9,0)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(9,0))
|
||||
))
|
||||
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
|
||||
duration=datetime.timedelta(minutes=90),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(7,0))
|
||||
))
|
||||
plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(11,0)))
|
||||
time=tz.localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(11,0))
|
||||
))
|
||||
# mars WG
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars_session = SessionFactory(meeting=meeting, group=mars,
|
||||
|
@ -213,7 +228,7 @@ def make_meeting_test_data(meeting=None, create_interims=False):
|
|||
|
||||
return meeting
|
||||
|
||||
def make_interim_test_data():
|
||||
def make_interim_test_data(meeting_tz='UTC'):
|
||||
date = datetime.date.today() + datetime.timedelta(days=365)
|
||||
date2 = datetime.date.today() + datetime.timedelta(days=1000)
|
||||
PersonFactory(user__username='plain')
|
||||
|
@ -225,10 +240,10 @@ def make_interim_test_data():
|
|||
RoleFactory(group=mars,person__user__username='marschairman',name_id='chair')
|
||||
RoleFactory(group=ames,person__user__username='ameschairman',name_id='chair')
|
||||
|
||||
make_interim_meeting(group=mars,date=date,status='sched')
|
||||
make_interim_meeting(group=mars,date=date2,status='apprw')
|
||||
make_interim_meeting(group=ames,date=date,status='canceled')
|
||||
make_interim_meeting(group=ames,date=date2,status='apprw')
|
||||
make_interim_meeting(group=mars,date=date,status='sched',tz=meeting_tz)
|
||||
make_interim_meeting(group=mars,date=date2,status='apprw',tz=meeting_tz)
|
||||
make_interim_meeting(group=ames,date=date,status='canceled',tz=meeting_tz)
|
||||
make_interim_meeting(group=ames,date=date2,status='apprw',tz=meeting_tz)
|
||||
|
||||
return
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs
|
|||
from ietf.utils.test_utils import assert_ical_response_is_valid
|
||||
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
|
||||
presence_of_element_child_by_css_selector )
|
||||
from ietf.utils.timezone import datetime_today
|
||||
|
||||
if selenium_enabled():
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
|
@ -1434,13 +1435,16 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
# for others (break, reg, other):
|
||||
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug>
|
||||
meeting_number = components[1]
|
||||
start_time = datetime.datetime(
|
||||
year=int(components[2]),
|
||||
month=int(components[3]),
|
||||
day=int(components[4]),
|
||||
hour=int(components[6][0:2]),
|
||||
minute=int(components[6][2:4]),
|
||||
start_time = pytz.utc.localize(
|
||||
datetime.datetime(
|
||||
year=int(components[2]),
|
||||
month=int(components[3]),
|
||||
day=int(components[4]),
|
||||
hour=int(components[6][0:2]),
|
||||
minute=int(components[6][2:4]),
|
||||
)
|
||||
)
|
||||
|
||||
# If labeled as plenary, it's plenary...
|
||||
if components[7] == '1plenary':
|
||||
session_type = 'plenary'
|
||||
|
@ -1904,10 +1908,9 @@ class WeekviewTests(IetfSeleniumTestCase):
|
|||
|
||||
# Session during a single day in meeting local time but multi-day UTC
|
||||
# Compute a time that overlaps midnight, UTC, but won't when shifted to a local time zone
|
||||
start_time_utc = pytz.timezone('UTC').localize(
|
||||
start_time_utc = pytz.utc.localize(
|
||||
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
|
||||
)
|
||||
start_time_local = start_time_utc.astimezone(pytz.timezone(self.meeting.time_zone))
|
||||
|
||||
daytime_session = SessionFactory(
|
||||
meeting=self.meeting,
|
||||
|
@ -1916,7 +1919,7 @@ class WeekviewTests(IetfSeleniumTestCase):
|
|||
)
|
||||
daytime_timeslot = TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
time=start_time_local.replace(tzinfo=None), # drop timezone for Django
|
||||
time=start_time_utc,
|
||||
duration=duration,
|
||||
)
|
||||
daytime_session.timeslotassignments.create(timeslot=daytime_timeslot, schedule=self.meeting.schedule)
|
||||
|
@ -1929,11 +1932,12 @@ class WeekviewTests(IetfSeleniumTestCase):
|
|||
)
|
||||
overnight_timeslot = TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
time=datetime.datetime.combine(self.meeting.date, datetime.time(23,0)),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
|
||||
),
|
||||
duration=duration,
|
||||
)
|
||||
overnight_session.timeslotassignments.create(timeslot=overnight_timeslot, schedule=self.meeting.schedule)
|
||||
|
||||
# Check assumptions about events overlapping midnight
|
||||
self.assertEqual(daytime_timeslot.local_start_time().day,
|
||||
daytime_timeslot.local_end_time().day,
|
||||
|
@ -2191,7 +2195,7 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=expected_schedules,
|
||||
session__in=expected_interim_sessions,
|
||||
timeslot__time__gte=datetime.date.today(),
|
||||
timeslot__time__gte=datetime_today(),
|
||||
))
|
||||
# The UID formats should match those in the upcoming.ics template
|
||||
expected_uids = [
|
||||
|
@ -2688,13 +2692,17 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
|
|||
delete: [TimeSlot] = TimeSlotFactory.create_batch(
|
||||
2,
|
||||
meeting=self.meeting,
|
||||
time=datetime.datetime.combine(delete_day, delete_time),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(delete_day, delete_time)
|
||||
),
|
||||
duration=duration)
|
||||
|
||||
keep: [TimeSlot] = [
|
||||
TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
time=datetime.datetime.combine(day, time),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(day, time)
|
||||
),
|
||||
duration=duration
|
||||
)
|
||||
for (day, time) in (
|
||||
|
@ -2711,7 +2719,9 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
|
|||
'[data-col-id="{}T{}-{}"]'.format(
|
||||
delete_day.isoformat(),
|
||||
delete_time.strftime('%H:%M'),
|
||||
(datetime.datetime.combine(delete_day, delete_time) + duration).strftime(
|
||||
self.meeting.tz().localize(
|
||||
datetime.datetime.combine(delete_day, delete_time) + duration
|
||||
).strftime(
|
||||
'%H:%M'
|
||||
))
|
||||
)
|
||||
|
@ -2733,14 +2743,18 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
|
|||
delete: [TimeSlot] = [
|
||||
TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
time=datetime.datetime.combine(delete_day, time),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(delete_day, time)
|
||||
),
|
||||
) for time in times
|
||||
]
|
||||
|
||||
keep: [TimeSlot] = [
|
||||
TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
time=datetime.datetime.combine(day, time),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(day, time)
|
||||
),
|
||||
) for day in other_days for time in times
|
||||
]
|
||||
|
||||
|
|
|
@ -56,16 +56,16 @@ class MeetingTests(TestCase):
|
|||
|
||||
def test_vtimezone(self):
|
||||
# normal time zone that should have a zoneinfo file
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
|
||||
vtz = meeting.vtimezone()
|
||||
self.assertIsNotNone(vtz)
|
||||
self.assertGreater(len(vtz), 0)
|
||||
# time zone that does not have a zoneinfo file should return None
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone')
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone', populate_schedule=False)
|
||||
vtz = meeting.vtimezone()
|
||||
self.assertIsNone(vtz)
|
||||
# ioerror trying to read zoneinfo should return None
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
|
||||
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
|
||||
with patch('ietf.meeting.models.io.open', side_effect=IOError):
|
||||
vtz = meeting.vtimezone()
|
||||
self.assertIsNone(vtz)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright The IETF Trust 2020, All Rights Reserved
|
||||
import calendar
|
||||
import datetime
|
||||
import pytz
|
||||
from io import StringIO
|
||||
|
||||
from django.core.management.base import CommandError
|
||||
|
@ -36,9 +37,11 @@ class ScheduleGeneratorTest(TestCase):
|
|||
t = TimeSlotFactory(
|
||||
meeting=self.meeting,
|
||||
location=room,
|
||||
time=datetime.datetime.combine(
|
||||
self.meeting.date + datetime.timedelta(days=day),
|
||||
datetime.time(hour, 0),
|
||||
time=self.meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
self.meeting.date + datetime.timedelta(days=day),
|
||||
datetime.time(hour, 0),
|
||||
)
|
||||
),
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
)
|
||||
|
@ -306,8 +309,11 @@ class ScheduleGeneratorTest(TestCase):
|
|||
add_to_schedule=False
|
||||
)
|
||||
# use a timeslot not on Sunday
|
||||
meeting_date = pytz.utc.localize(
|
||||
datetime.datetime.combine(self.meeting.get_meeting_date(1), datetime.time())
|
||||
)
|
||||
ts = self.meeting.timeslot_set.filter(
|
||||
time__gt=self.meeting.date + datetime.timedelta(days=1),
|
||||
time__gt=meeting_date,
|
||||
location__capacity__lt=base_reg_session.attendees,
|
||||
).order_by(
|
||||
'time'
|
||||
|
|
|
@ -52,6 +52,7 @@ from ietf.utils.decorators import skip_coverage
|
|||
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
|
||||
from ietf.utils.text import xslugify
|
||||
from ietf.utils.timezone import date_today, time_now
|
||||
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory
|
||||
|
@ -155,7 +156,7 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
#
|
||||
self.write_materials_files(meeting, session)
|
||||
#
|
||||
future_year = datetime.date.today().year+1
|
||||
future_year = date_today().year+1
|
||||
future_num = (future_year-1984)*3 # valid for the mid-year meeting
|
||||
future_meeting = Meeting.objects.create(date=datetime.date(future_year, 7, 22), number=future_num, type_id='ietf',
|
||||
city="Panama City", country="PA", time_zone='America/Panama')
|
||||
|
@ -228,7 +229,10 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
'Time zone indicator should be in nav sidebar')
|
||||
|
||||
# plain
|
||||
time_interval = r"%s<span.*/span>-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0"))
|
||||
time_interval = r"{}<span.*/span>-{}".format(
|
||||
slot.time.astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
|
||||
slot.end_time().astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
|
||||
)
|
||||
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -430,7 +434,9 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
session_date = meeting.date + datetime.timedelta(days=1)
|
||||
slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(13, 30)))
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(session_date, datetime.time(13, 30))
|
||||
))
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot3, session=venus_session, schedule=meeting.schedule)
|
||||
url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number))
|
||||
r = self.client.get(url)
|
||||
|
@ -801,7 +807,12 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
a1 = s1.official_timeslotassignment()
|
||||
t1 = a1.timeslot
|
||||
# Create an extra session
|
||||
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
|
||||
t2 = TimeSlotFactory.create(
|
||||
meeting=meeting,
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(11, 30))
|
||||
)
|
||||
)
|
||||
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
|
||||
#
|
||||
|
@ -811,16 +822,16 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
r,
|
||||
expected_event_summaries=['mars - Martian Special Interest Group'],
|
||||
expected_event_count=2)
|
||||
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
#
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
|
||||
r = self.client.get(url)
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=['mars - Martian Special Interest Group'],
|
||||
expected_event_count=1)
|
||||
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertNotContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
|
||||
def test_meeting_agenda_has_static_ical_links(self):
|
||||
"""Links to the agenda_ical view must appear on the agenda page
|
||||
|
@ -960,7 +971,7 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
url = urlreverse('ietf.meeting.views.current_materials')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
MeetingFactory(type_id='ietf', date=date_today())
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
@ -1105,7 +1116,9 @@ class EditMeetingScheduleTests(TestCase):
|
|||
TimeSlotFactory(
|
||||
meeting=meeting,
|
||||
location=room,
|
||||
time=datetime.datetime.combine(meeting.date, time),
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, time)
|
||||
),
|
||||
duration=datetime.timedelta(minutes=duration),
|
||||
)
|
||||
|
||||
|
@ -1189,7 +1202,11 @@ class EditMeetingScheduleTests(TestCase):
|
|||
]
|
||||
|
||||
# Set up different sets of timeslots
|
||||
t0 = datetime.datetime.combine(meeting.date, datetime.time(11, 0))
|
||||
# Work with t0 in UTC for arithmetic. This does not change the results but is cleaner if someone looks
|
||||
# at intermediate results which may be misleading until passed through tz.normalize().
|
||||
t0 = meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(11, 0))
|
||||
).astimezone(pytz.utc)
|
||||
dur = datetime.timedelta(hours=2)
|
||||
for room in room_groups[0]:
|
||||
TimeSlotFactory(meeting=meeting, location=room, duration=dur, time=t0)
|
||||
|
@ -1284,7 +1301,7 @@ class EditMeetingScheduleTests(TestCase):
|
|||
self.client.login(username=username, password=username + '+password')
|
||||
|
||||
# Swap group 0's first and last sessions, first in the past
|
||||
right_now = self._right_now_in(meeting.time_zone)
|
||||
right_now = self._right_now_in(meeting.tz())
|
||||
for room in room_groups[0]:
|
||||
ts = room.timeslot_set.last()
|
||||
ts.time = right_now - datetime.timedelta(minutes=5)
|
||||
|
@ -1466,12 +1483,18 @@ class EditMeetingScheduleTests(TestCase):
|
|||
self.client.login(username=username, password=username + '+password')
|
||||
|
||||
# Swap group 0's first and last sessions, first in the past
|
||||
right_now = self._right_now_in(meeting.time_zone)
|
||||
yesterday = (right_now - datetime.timedelta(days=1)).date()
|
||||
day_before = (right_now - datetime.timedelta(days=2)).date()
|
||||
right_now = self._right_now_in(meeting.tz())
|
||||
yesterday = right_now.date() - datetime.timedelta(days=1)
|
||||
day_before = right_now.date() - datetime.timedelta(days=2)
|
||||
for room in room_groups[0]:
|
||||
ts = room.timeslot_set.last()
|
||||
ts.time = datetime.datetime.combine(yesterday, ts.time.time())
|
||||
# Calculation keeps local clock time, shifted to a different day.
|
||||
ts.time = meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
yesterday,
|
||||
ts.time.astimezone(meeting.tz()).time()
|
||||
),
|
||||
)
|
||||
ts.save()
|
||||
# timeslot_set is ordered by -time, so check that we know which is past/future
|
||||
self.assertTrue(room_groups[0][0].timeslot_set.last().time < right_now)
|
||||
|
@ -1505,7 +1528,12 @@ class EditMeetingScheduleTests(TestCase):
|
|||
# now with both in the past
|
||||
for room in room_groups[0]:
|
||||
ts = room.timeslot_set.first()
|
||||
ts.time = datetime.datetime.combine(day_before, ts.time.time())
|
||||
ts.time = meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
day_before,
|
||||
ts.time.astimezone(meeting.tz()).time(),
|
||||
)
|
||||
)
|
||||
ts.save()
|
||||
past_slots = room_groups[0][0].timeslot_set.filter(time__lt=right_now)
|
||||
self.assertEqual(len(past_slots), 2, 'Need two timeslots in the past!')
|
||||
|
@ -1528,8 +1556,8 @@ class EditMeetingScheduleTests(TestCase):
|
|||
self.fail('Response was not valid JSON: {}'.format(err))
|
||||
|
||||
@staticmethod
|
||||
def _right_now_in(tzname):
|
||||
right_now = timezone.now().astimezone(pytz.timezone(tzname))
|
||||
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
|
||||
|
@ -1541,7 +1569,7 @@ class EditMeetingScheduleTests(TestCase):
|
|||
date=(timezone.now() - datetime.timedelta(days=1)).date(),
|
||||
days=3,
|
||||
)
|
||||
right_now = self._right_now_in(meeting.time_zone)
|
||||
right_now = self._right_now_in(meeting.tz())
|
||||
|
||||
schedules = dict(
|
||||
official=meeting.schedule,
|
||||
|
@ -1601,7 +1629,7 @@ class EditMeetingScheduleTests(TestCase):
|
|||
date=(timezone.now() - datetime.timedelta(days=1)).date(),
|
||||
days=3,
|
||||
)
|
||||
right_now = self._right_now_in(meeting.time_zone)
|
||||
right_now = self._right_now_in(meeting.tz())
|
||||
|
||||
schedules = dict(
|
||||
official=meeting.schedule,
|
||||
|
@ -1736,7 +1764,7 @@ class EditMeetingScheduleTests(TestCase):
|
|||
date=(timezone.now() - datetime.timedelta(days=1)).date(),
|
||||
days=3,
|
||||
)
|
||||
right_now = self._right_now_in(meeting.time_zone)
|
||||
right_now = self._right_now_in(meeting.tz())
|
||||
|
||||
schedules = dict(
|
||||
official=meeting.schedule,
|
||||
|
@ -1857,7 +1885,7 @@ class EditTimeslotsTests(TestCase):
|
|||
return MeetingFactory(
|
||||
type_id='ietf',
|
||||
number=number,
|
||||
date=timezone.now() + datetime.timedelta(days=10),
|
||||
date=date_today() + datetime.timedelta(days=10),
|
||||
populate_schedule=False,
|
||||
)
|
||||
|
||||
|
@ -1889,7 +1917,8 @@ class EditTimeslotsTests(TestCase):
|
|||
meeting = self.create_bare_meeting(number=number)
|
||||
RoomFactory.create_batch(8, meeting=meeting)
|
||||
self.create_initial_schedule(meeting)
|
||||
return meeting
|
||||
# retrieve meeting from DB so it goes through Django's processing
|
||||
return Meeting.objects.get(pk=meeting.pk)
|
||||
|
||||
def test_view_permissions(self):
|
||||
"""Only the secretary should be able to edit timeslots"""
|
||||
|
@ -2058,7 +2087,7 @@ class EditTimeslotsTests(TestCase):
|
|||
meeting = self.create_meeting()
|
||||
# add some timeslots
|
||||
times = [datetime.time(hour=h) for h in (11, 14)]
|
||||
days = [meeting.get_meeting_date(ii).date() for ii in range(meeting.days)]
|
||||
days = [meeting.get_meeting_date(ii) for ii in range(meeting.days)]
|
||||
|
||||
timeslots = []
|
||||
duration = datetime.timedelta(minutes=90)
|
||||
|
@ -2068,7 +2097,7 @@ class EditTimeslotsTests(TestCase):
|
|||
TimeSlotFactory(
|
||||
meeting=meeting,
|
||||
location=room,
|
||||
time=datetime.datetime.combine(day, t),
|
||||
time=meeting.tz().localize(datetime.datetime.combine(day, t)),
|
||||
duration=duration,
|
||||
)
|
||||
for t in times
|
||||
|
@ -2149,17 +2178,21 @@ class EditTimeslotsTests(TestCase):
|
|||
TimeSlotFactory(
|
||||
meeting=meeting,
|
||||
location=meeting.room_set.first(),
|
||||
time=datetime.datetime.combine(
|
||||
meeting.get_meeting_date(day).date(),
|
||||
datetime.time(hour=11)
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
meeting.get_meeting_date(day),
|
||||
datetime.time(hour=11),
|
||||
)
|
||||
),
|
||||
)
|
||||
TimeSlotFactory(
|
||||
meeting=meeting,
|
||||
location=meeting.room_set.first(),
|
||||
time=datetime.datetime.combine(
|
||||
meeting.get_meeting_date(day).date(),
|
||||
datetime.time(hour=14)
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
meeting.get_meeting_date(day),
|
||||
datetime.time(hour=14),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -2258,10 +2291,8 @@ class EditTimeslotsTests(TestCase):
|
|||
|
||||
name_before = 'Name Classic (tm)'
|
||||
type_before = 'regular'
|
||||
time_before = datetime.datetime.combine(
|
||||
meeting.date,
|
||||
datetime.time(hour=10),
|
||||
)
|
||||
time_utc = pytz.utc.localize(datetime.datetime.combine(meeting.date, datetime.time(hour=10)))
|
||||
time_before = time_utc.astimezone(meeting.tz())
|
||||
duration_before = datetime.timedelta(minutes=60)
|
||||
show_location_before = True
|
||||
location_before = meeting.room_set.first()
|
||||
|
@ -2278,7 +2309,7 @@ class EditTimeslotsTests(TestCase):
|
|||
self.login()
|
||||
name_after = 'New Name (tm)'
|
||||
type_after = 'plenary'
|
||||
time_after = time_before + datetime.timedelta(days=1, hours=2)
|
||||
time_after = (time_utc + datetime.timedelta(days=1, hours=2)).astimezone(meeting.tz())
|
||||
duration_after = duration_before * 2
|
||||
show_location_after = False
|
||||
location_after = meeting.room_set.last()
|
||||
|
@ -2458,8 +2489,8 @@ class EditTimeslotsTests(TestCase):
|
|||
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
|
||||
self.assertEqual(ts.name, post_data['name'])
|
||||
self.assertEqual(ts.type_id, post_data['type'])
|
||||
self.assertEqual(str(ts.time.date().toordinal()), post_data['days'])
|
||||
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(str(ts.local_start_time().date().toordinal()), post_data['days'])
|
||||
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
|
||||
self.assertEqual(ts.show_location, post_data['show_location'])
|
||||
self.assertEqual(str(ts.location.pk), post_data['locations'])
|
||||
|
@ -2468,7 +2499,7 @@ class EditTimeslotsTests(TestCase):
|
|||
"""Creating a single timeslot outside the official meeting days should work"""
|
||||
meeting = self.create_meeting()
|
||||
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
|
||||
other_date = meeting.get_meeting_date(-7).date()
|
||||
other_date = meeting.get_meeting_date(-7)
|
||||
post_data = dict(
|
||||
name='some name',
|
||||
type='regular',
|
||||
|
@ -2491,8 +2522,8 @@ class EditTimeslotsTests(TestCase):
|
|||
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
|
||||
self.assertEqual(ts.name, post_data['name'])
|
||||
self.assertEqual(ts.type_id, post_data['type'])
|
||||
self.assertEqual(ts.time.date(), other_date)
|
||||
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(ts.local_start_time().date(), other_date)
|
||||
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
|
||||
self.assertEqual(ts.show_location, post_data['show_location'])
|
||||
self.assertEqual(str(ts.location.pk), post_data['locations'])
|
||||
|
@ -2706,8 +2737,8 @@ class EditTimeslotsTests(TestCase):
|
|||
"""Creating multiple timeslots should work"""
|
||||
meeting = self.create_meeting()
|
||||
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
|
||||
days = [meeting.get_meeting_date(n).date() for n in range(meeting.days)]
|
||||
other_date = meeting.get_meeting_date(-1).date() # date before start of meeting
|
||||
days = [meeting.get_meeting_date(n) for n in range(meeting.days)]
|
||||
other_date = meeting.get_meeting_date(-1) # date before start of meeting
|
||||
self.assertNotIn(other_date, days)
|
||||
locations = meeting.room_set.all()
|
||||
post_data = dict(
|
||||
|
@ -2737,10 +2768,10 @@ class EditTimeslotsTests(TestCase):
|
|||
for ts in meeting.timeslot_set.exclude(pk__in=timeslots_before):
|
||||
self.assertEqual(ts.name, post_data['name'])
|
||||
self.assertEqual(ts.type_id, post_data['type'])
|
||||
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
|
||||
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
|
||||
self.assertEqual(ts.show_location, post_data['show_location'])
|
||||
self.assertIn(ts.time.date(), days)
|
||||
self.assertIn(ts.local_start_time().date(), days)
|
||||
self.assertIn(ts.location, locations)
|
||||
self.assertIn((ts.time.date(), ts.location), day_locs,
|
||||
'Duplicated day / location found')
|
||||
|
@ -3288,7 +3319,9 @@ class EditTests(TestCase):
|
|||
room = Room.objects.get(meeting=meeting, session_types='regular')
|
||||
base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=50),
|
||||
time=datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30)))
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30))
|
||||
))
|
||||
|
||||
timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time'))
|
||||
|
||||
|
@ -3510,7 +3543,12 @@ class EditTests(TestCase):
|
|||
self.assertIn("#scroll=1234", r['Location'])
|
||||
|
||||
test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing")
|
||||
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(8, 30)))
|
||||
self.assertEqual(
|
||||
test_timeslot.time,
|
||||
meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(8, 30))
|
||||
),
|
||||
)
|
||||
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1, minutes=30))
|
||||
self.assertEqual(test_timeslot.location_id, break_room.pk)
|
||||
self.assertEqual(test_timeslot.show_location, True)
|
||||
|
@ -3552,7 +3590,12 @@ class EditTests(TestCase):
|
|||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
test_timeslot.refresh_from_db()
|
||||
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(9, 30)))
|
||||
self.assertEqual(
|
||||
test_timeslot.time,
|
||||
meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(9, 30))
|
||||
),
|
||||
)
|
||||
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1))
|
||||
self.assertEqual(test_timeslot.location_id, breakfast_room.pk)
|
||||
self.assertEqual(test_timeslot.show_location, False)
|
||||
|
@ -4329,12 +4372,10 @@ class InterimTests(TestCase):
|
|||
self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True, base_session=True)
|
||||
|
||||
def do_interim_send_announcement_test(self, base_session=False, extra_session=False, canceled_session=False):
|
||||
make_interim_test_data()
|
||||
make_interim_test_data(meeting_tz='America/Los_Angeles')
|
||||
session = Session.objects.with_current_status().filter(
|
||||
meeting__type='interim', group__acronym='mars', current_status='apprw').first()
|
||||
meeting = session.meeting
|
||||
meeting.time_zone = 'America/Los_Angeles'
|
||||
meeting.save()
|
||||
|
||||
if base_session:
|
||||
base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False)
|
||||
|
@ -4679,9 +4720,9 @@ class InterimTests(TestCase):
|
|||
def do_interim_request_single_virtual(self, emails_expected):
|
||||
make_meeting_test_data()
|
||||
group = Group.objects.get(acronym='mars')
|
||||
date = datetime.date.today() + datetime.timedelta(days=30)
|
||||
time = timezone.now().time().replace(microsecond=0,second=0)
|
||||
dt = datetime.datetime.combine(date, time)
|
||||
date = date_today() + datetime.timedelta(days=30)
|
||||
time = time_now().replace(microsecond=0,second=0)
|
||||
dt = pytz.utc.localize(datetime.datetime.combine(date, time))
|
||||
duration = datetime.timedelta(hours=3)
|
||||
remote_instructions = 'Use webex'
|
||||
agenda = 'Intro. Slides. Discuss.'
|
||||
|
@ -4750,13 +4791,14 @@ class InterimTests(TestCase):
|
|||
def test_interim_request_single_in_person(self):
|
||||
make_meeting_test_data()
|
||||
group = Group.objects.get(acronym='mars')
|
||||
date = datetime.date.today() + datetime.timedelta(days=30)
|
||||
time = timezone.now().time().replace(microsecond=0,second=0)
|
||||
dt = datetime.datetime.combine(date, time)
|
||||
date = date_today() + datetime.timedelta(days=30)
|
||||
time = time_now().replace(microsecond=0,second=0)
|
||||
time_zone = 'America/Los_Angeles'
|
||||
tz = pytz.timezone(time_zone)
|
||||
dt = tz.localize(datetime.datetime.combine(date, time))
|
||||
duration = datetime.timedelta(hours=3)
|
||||
city = 'San Francisco'
|
||||
country = 'US'
|
||||
time_zone = 'America/Los_Angeles'
|
||||
remote_instructions = 'Use webex'
|
||||
agenda = 'Intro. Slides. Discuss.'
|
||||
agenda_note = 'On second level'
|
||||
|
@ -4797,16 +4839,17 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_interim_request_multi_day(self):
|
||||
make_meeting_test_data()
|
||||
date = datetime.date.today() + datetime.timedelta(days=30)
|
||||
date = date_today() + datetime.timedelta(days=30)
|
||||
date2 = date + datetime.timedelta(days=1)
|
||||
time = timezone.now().time().replace(microsecond=0,second=0)
|
||||
dt = datetime.datetime.combine(date, time)
|
||||
dt2 = datetime.datetime.combine(date2, time)
|
||||
time = time_now().replace(microsecond=0,second=0)
|
||||
time_zone = 'America/Los_Angeles'
|
||||
tz = pytz.timezone(time_zone)
|
||||
dt = tz.localize(datetime.datetime.combine(date, time))
|
||||
dt2 = tz.localize(datetime.datetime.combine(date2, time))
|
||||
duration = datetime.timedelta(hours=3)
|
||||
group = Group.objects.get(acronym='mars')
|
||||
city = 'San Francisco'
|
||||
country = 'US'
|
||||
time_zone = 'America/Los_Angeles'
|
||||
remote_instructions = 'Use webex'
|
||||
agenda = 'Intro. Slides. Discuss.'
|
||||
agenda_note = 'On second level'
|
||||
|
@ -4923,7 +4966,7 @@ class InterimTests(TestCase):
|
|||
def test_interim_request_series(self):
|
||||
make_meeting_test_data()
|
||||
meeting_count_before = Meeting.objects.filter(type='interim').count()
|
||||
date = datetime.date.today() + datetime.timedelta(days=30)
|
||||
date = date_today() + datetime.timedelta(days=30)
|
||||
if (date.month, date.day) == (12, 31):
|
||||
# Avoid date and date2 in separate years
|
||||
# (otherwise the test will fail if run on December 1st)
|
||||
|
@ -4933,14 +4976,15 @@ class InterimTests(TestCase):
|
|||
if date.year != date2.year:
|
||||
date += datetime.timedelta(days=1)
|
||||
date2 += datetime.timedelta(days=1)
|
||||
time = timezone.now().time().replace(microsecond=0,second=0)
|
||||
dt = datetime.datetime.combine(date, time)
|
||||
dt2 = datetime.datetime.combine(date2, time)
|
||||
time = time_now().replace(microsecond=0,second=0)
|
||||
time_zone = 'America/Los_Angeles'
|
||||
tz = pytz.timezone(time_zone)
|
||||
dt = tz.localize(datetime.datetime.combine(date, time))
|
||||
dt2 = tz.localize(datetime.datetime.combine(date2, time))
|
||||
duration = datetime.timedelta(hours=3)
|
||||
group = Group.objects.get(acronym='mars')
|
||||
city = ''
|
||||
country = ''
|
||||
time_zone = 'America/Los_Angeles'
|
||||
remote_instructions = 'Use webex'
|
||||
agenda = 'Intro. Slides. Discuss.'
|
||||
agenda_note = 'On second level'
|
||||
|
@ -5082,14 +5126,14 @@ class InterimTests(TestCase):
|
|||
self.assertFalse(can_manage_group(user=user,group=group))
|
||||
|
||||
def test_interim_request_details(self):
|
||||
make_interim_test_data()
|
||||
make_interim_test_data(meeting_tz='America/Chicago')
|
||||
meeting = Session.objects.with_current_status().filter(
|
||||
meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
start_time = meeting.session_set.first().official_timeslotassignment().timeslot.time.strftime('%H:%M')
|
||||
start_time = meeting.session_set.first().official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M')
|
||||
utc_start_time = meeting.session_set.first().official_timeslotassignment().timeslot.utc_start_time().strftime('%H:%M')
|
||||
self.assertIn(start_time, unicontent(r))
|
||||
self.assertIn(utc_start_time, unicontent(r))
|
||||
|
@ -5630,7 +5674,11 @@ class InterimTests(TestCase):
|
|||
a1 = s1.official_timeslotassignment()
|
||||
t1 = a1.timeslot
|
||||
# Create an extra session
|
||||
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
|
||||
t2 = TimeSlotFactory.create(
|
||||
meeting=meeting,
|
||||
time=meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(11, 30))
|
||||
))
|
||||
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
|
||||
#
|
||||
|
@ -5640,8 +5688,8 @@ class InterimTests(TestCase):
|
|||
self.assertContains(r, 'BEGIN:VEVENT')
|
||||
self.assertEqual(r.content.count(b'UID'), 2)
|
||||
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
|
||||
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, 'END:VEVENT')
|
||||
#
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
|
||||
|
@ -7422,7 +7470,7 @@ class ProceedingsTests(BaseMeetingTestCase):
|
|||
|
||||
def test_proceedings_no_agenda(self):
|
||||
# Meeting number must be larger than the last special-cased proceedings (currently 96)
|
||||
meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=datetime.date.today(), number='100')
|
||||
meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=date_today(), number='100')
|
||||
url = urlreverse('ietf.meeting.views.proceedings')
|
||||
r = self.client.get(url)
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.materials'))
|
||||
|
@ -7531,7 +7579,7 @@ class ProceedingsTests(BaseMeetingTestCase):
|
|||
"""Generate a meeting for proceedings material test"""
|
||||
# meeting number 123 avoids various legacy cases that affect these tests
|
||||
# (as of Aug 2021, anything above 96 is probably ok)
|
||||
return MeetingFactory(type_id='ietf', number='123', date=datetime.date.today())
|
||||
return MeetingFactory(type_id='ietf', number='123', date=date_today())
|
||||
|
||||
def _secretary_only_permission_test(self, url, include_post=True):
|
||||
self.client.logout()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import itertools
|
||||
import pytz
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
|
@ -34,13 +35,17 @@ def session_time_for_sorting(session, use_meeting_date):
|
|||
if official_timeslot:
|
||||
return official_timeslot.time
|
||||
elif use_meeting_date and session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date, datetime.time.min)
|
||||
return session.meeting.tz().localize(
|
||||
datetime.datetime.combine(session.meeting.date, datetime.time.min)
|
||||
)
|
||||
else:
|
||||
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
|
||||
if first_event:
|
||||
return first_event.time
|
||||
else:
|
||||
return datetime.datetime.min
|
||||
# n.b. cannot interpret this in timezones west of UTC. That is not expected to be necessary,
|
||||
# but could probably safely add a day to the minimum datetime to make that possible.
|
||||
return pytz.utc.localize(datetime.datetime.min)
|
||||
|
||||
def session_requested_by(session):
|
||||
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
|
||||
|
@ -159,7 +164,12 @@ def create_proceedings_templates(meeting):
|
|||
|
||||
def finalize(meeting):
|
||||
end_date = meeting.end_date()
|
||||
end_time = datetime.datetime.combine(end_date, datetime.datetime.min.time())+datetime.timedelta(days=1)
|
||||
end_time = meeting.tz().localize(
|
||||
datetime.datetime.combine(
|
||||
end_date,
|
||||
datetime.time.min,
|
||||
)
|
||||
).astimezone(pytz.utc) + datetime.timedelta(days=1)
|
||||
for session in meeting.session_set.all():
|
||||
for sp in session.sessionpresentation_set.filter(document__type='draft',rev=None):
|
||||
rev_before_end = [e for e in sp.document.docevent_set.filter(newrevisiondocevent__isnull=False).order_by('-time') if e.time <= end_time ]
|
||||
|
@ -323,7 +333,9 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
|
|||
# synthesize AD constraints - we can treat them as a special kind of 'bethere'
|
||||
responsible_ad_for_group = {}
|
||||
session_groups = set(s.group for s in sessions if s.group and s.group.parent and s.group.parent.type_id == 'area')
|
||||
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
|
||||
meeting_time = meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
|
||||
)
|
||||
|
||||
# dig up historic AD names
|
||||
for group_id, history_time, pk in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'pk').order_by('rolehistory__group__time'):
|
||||
|
|
|
@ -96,6 +96,7 @@ from ietf.utils.pipe import pipe
|
|||
from ietf.utils.pdf import pdf_pages
|
||||
from ietf.utils.response import permission_denied
|
||||
from ietf.utils.text import xslugify
|
||||
from ietf.utils.timezone import datetime_today, date_today
|
||||
|
||||
from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
|
||||
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
|
||||
|
@ -127,7 +128,7 @@ def materials(request, num=None):
|
|||
begin_date = meeting.get_submission_start_date()
|
||||
cut_off_date = meeting.get_submission_cut_off_date()
|
||||
cor_cut_off_date = meeting.get_submission_correction_date()
|
||||
now = datetime.date.today()
|
||||
now = date_today()
|
||||
old = timezone.now() - datetime.timedelta(days=1)
|
||||
if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET:
|
||||
pass
|
||||
|
@ -142,7 +143,7 @@ def materials(request, num=None):
|
|||
'cor_cut_off_date': cor_cut_off_date
|
||||
})
|
||||
|
||||
past_cutoff_date = datetime.date.today() > meeting.get_submission_correction_date()
|
||||
past_cutoff_date = date_today() > meeting.get_submission_correction_date()
|
||||
|
||||
schedule = get_schedule(meeting, None)
|
||||
|
||||
|
@ -192,7 +193,7 @@ def materials(request, num=None):
|
|||
})
|
||||
|
||||
def current_materials(request):
|
||||
today = datetime.date.today()
|
||||
today = date_today()
|
||||
meetings = Meeting.objects.exclude(number__startswith='interim-').filter(date__lte=today).order_by('-date')
|
||||
if meetings:
|
||||
return redirect(materials, meetings[0].number)
|
||||
|
@ -281,60 +282,63 @@ def materials_editable_groups(request, num=None):
|
|||
def edit_timeslots(request, num=None):
|
||||
|
||||
meeting = get_meeting(num)
|
||||
timezone.activate(meeting.tz())
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
# handle AJAX requests
|
||||
action = request.POST.get('action')
|
||||
if action == 'delete':
|
||||
# delete a timeslot
|
||||
# Parameters:
|
||||
# slot_id: comma-separated list of TimeSlot PKs to delete
|
||||
slot_id = request.POST.get('slot_id')
|
||||
if slot_id is None:
|
||||
return HttpResponseBadRequest('missing slot_id')
|
||||
slot_ids = [id.strip() for id in slot_id.split(',')]
|
||||
try:
|
||||
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest('invalid slot_id specification')
|
||||
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
|
||||
if len(missing_ids) != 0:
|
||||
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
|
||||
meeting.number,
|
||||
', '.join(sorted(missing_ids))
|
||||
))
|
||||
timeslots.delete()
|
||||
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
|
||||
else:
|
||||
return HttpResponseBadRequest('unknown action')
|
||||
|
||||
if request.method == 'POST':
|
||||
# handle AJAX requests
|
||||
action = request.POST.get('action')
|
||||
if action == 'delete':
|
||||
# delete a timeslot
|
||||
# Parameters:
|
||||
# slot_id: comma-separated list of TimeSlot PKs to delete
|
||||
slot_id = request.POST.get('slot_id')
|
||||
if slot_id is None:
|
||||
return HttpResponseBadRequest('missing slot_id')
|
||||
slot_ids = [id.strip() for id in slot_id.split(',')]
|
||||
try:
|
||||
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest('invalid slot_id specification')
|
||||
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
|
||||
if len(missing_ids) != 0:
|
||||
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
|
||||
meeting.number,
|
||||
', '.join(sorted(missing_ids))
|
||||
))
|
||||
timeslots.delete()
|
||||
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
|
||||
else:
|
||||
return HttpResponseBadRequest('unknown action')
|
||||
# Labels here differ from those in the build_timeslices() method. The labels here are
|
||||
# relative to the table: time_slices are the row headings (ie, days), date_slices are
|
||||
# the column headings (i.e., time intervals), and slots are the per-day list of timeslots
|
||||
# (with only one timeslot per unique time/duration)
|
||||
time_slices, date_slices, slots = meeting.build_timeslices()
|
||||
|
||||
# Labels here differ from those in the build_timeslices() method. The labels here are
|
||||
# relative to the table: time_slices are the row headings (ie, days), date_slices are
|
||||
# the column headings (i.e., time intervals), and slots are the per-day list of timeslots
|
||||
# (with only one timeslot per unique time/duration)
|
||||
time_slices, date_slices, slots = meeting.build_timeslices()
|
||||
ts_list = deque()
|
||||
rooms = meeting.room_set.order_by("capacity","name","id")
|
||||
for room in rooms:
|
||||
for day in time_slices:
|
||||
for slice in date_slices[day]:
|
||||
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
|
||||
|
||||
ts_list = deque()
|
||||
rooms = meeting.room_set.order_by("capacity","name","id")
|
||||
for room in rooms:
|
||||
for day in time_slices:
|
||||
for slice in date_slices[day]:
|
||||
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
|
||||
# Grab these in one query each to identify sessions that are in use and should be handled with care
|
||||
ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
|
||||
ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
|
||||
|
||||
# Grab these in one query each to identify sessions that are in use and should be handled with care
|
||||
ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
|
||||
ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
|
||||
|
||||
return render(request, "meeting/timeslot_edit.html",
|
||||
{"rooms":rooms,
|
||||
"time_slices":time_slices,
|
||||
"slot_slices": slots,
|
||||
"date_slices":date_slices,
|
||||
"meeting":meeting,
|
||||
"ts_list":ts_list,
|
||||
"ts_with_official_assignments": ts_with_official_assignments,
|
||||
"ts_with_any_assignments": ts_with_any_assignments,
|
||||
})
|
||||
return render(request, "meeting/timeslot_edit.html",
|
||||
{"rooms":rooms,
|
||||
"time_slices":time_slices,
|
||||
"slot_slices": slots,
|
||||
"date_slices":date_slices,
|
||||
"meeting":meeting,
|
||||
"ts_list":ts_list,
|
||||
"ts_with_official_assignments": ts_with_official_assignments,
|
||||
"ts_with_any_assignments": ts_with_any_assignments,
|
||||
})
|
||||
finally:
|
||||
timezone.deactivate()
|
||||
|
||||
class NewScheduleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
@ -1151,7 +1155,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
|||
meeting=meeting,
|
||||
type=c['type'],
|
||||
name=c['name'],
|
||||
time=datetime.datetime.combine(c['day'], c['time']),
|
||||
time=meeting.tz().localize(datetime.datetime.combine(c['day'], c['time'])),
|
||||
duration=c['duration'],
|
||||
location=c['location'],
|
||||
show_location=c['show_location'],
|
||||
|
@ -1200,7 +1204,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
|||
|
||||
timeslot.type = c['type']
|
||||
timeslot.name = c['name']
|
||||
timeslot.time = datetime.datetime.combine(c['day'], c['time'])
|
||||
timeslot.time = meeting.tz().localize(datetime.datetime.combine(c['day'], c['time']))
|
||||
timeslot.duration = c['duration']
|
||||
timeslot.location = c['location']
|
||||
timeslot.show_location = c['show_location']
|
||||
|
@ -1287,8 +1291,9 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
|||
for t in timeslot_qs:
|
||||
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
|
||||
|
||||
min_time = min([t.time.time() for t in timeslot_qs] + [datetime.time(8)])
|
||||
max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)])
|
||||
# Calculate full time range for display in meeting-local time, always showing at least 8am to 10pm
|
||||
min_time = min([t.local_start_time().time() for t in timeslot_qs] + [datetime.time(8)])
|
||||
max_time = max([t.local_end_time().time() for t in timeslot_qs] + [datetime.time(22)])
|
||||
min_max_delta = datetime.datetime.combine(meeting.date, max_time) - datetime.datetime.combine(meeting.date, min_time)
|
||||
|
||||
day_grid = []
|
||||
|
@ -1310,7 +1315,14 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
|||
if s:
|
||||
t.assigned_sessions.append(s)
|
||||
|
||||
t.left_offset = 100.0 * (t.time - datetime.datetime.combine(t.time.date(), min_time)) / min_max_delta
|
||||
local_start_dt = t.local_start_time()
|
||||
local_min_dt = local_start_dt.replace(
|
||||
hour=min_time.hour,
|
||||
minute=min_time.minute,
|
||||
second=min_time.second,
|
||||
microsecond=min_time.microsecond,
|
||||
)
|
||||
t.left_offset = 100.0 * (local_start_dt - local_min_dt) / min_max_delta
|
||||
t.layout_width = min(100.0 * t.duration / min_max_delta, 100 - t.left_offset)
|
||||
ts.append(t)
|
||||
|
||||
|
@ -1553,19 +1565,29 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
|
||||
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
|
||||
|
||||
rendered_page = render(request, "meeting/"+base+ext, {
|
||||
"personalize": False,
|
||||
"schedule": schedule,
|
||||
"filtered_assignments": filtered_assignments,
|
||||
"updated": updated,
|
||||
"filter_categories": filter_organizer.get_filter_categories(),
|
||||
"non_area_keywords": filter_organizer.get_non_area_keywords(),
|
||||
"now": timezone.now().astimezone(pytz.utc),
|
||||
"timezone": meeting.time_zone,
|
||||
"is_current_meeting": is_current_meeting,
|
||||
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
|
||||
"cache_time": 150 if is_current_meeting else 3600,
|
||||
}, content_type=mimetype[ext])
|
||||
display_timezone = 'UTC' if utc else meeting.time_zone
|
||||
timezone.activate(display_timezone)
|
||||
try:
|
||||
rendered_page = render(
|
||||
request,
|
||||
"meeting/" + base + ext,
|
||||
{
|
||||
"personalize": False,
|
||||
"schedule": schedule,
|
||||
"filtered_assignments": filtered_assignments,
|
||||
"updated": updated,
|
||||
"filter_categories": filter_organizer.get_filter_categories(),
|
||||
"non_area_keywords": filter_organizer.get_non_area_keywords(),
|
||||
"now": timezone.now().astimezone(pytz.utc),
|
||||
"display_timezone": display_timezone,
|
||||
"is_current_meeting": is_current_meeting,
|
||||
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
|
||||
"cache_time": 150 if is_current_meeting else 3600,
|
||||
},
|
||||
content_type=mimetype[ext],
|
||||
)
|
||||
finally:
|
||||
timezone.deactivate()
|
||||
|
||||
return rendered_page
|
||||
|
||||
|
@ -1917,7 +1939,7 @@ def agenda_personalize(request, num):
|
|||
'filtered_assignments': filtered_assignments,
|
||||
'filter_categories': filter_organizer.get_filter_categories(),
|
||||
'non_area_labels': filter_organizer.get_non_area_keywords(),
|
||||
'timezone': meeting.time_zone,
|
||||
'display_timezone': meeting.time_zone,
|
||||
'is_current_meeting': is_current_meeting,
|
||||
'cache_time': 150 if is_current_meeting else 3600,
|
||||
}
|
||||
|
@ -2305,16 +2327,14 @@ def agenda_json(request, num=None):
|
|||
meetinfo.sort(key=lambda x: x['modified'],reverse=True)
|
||||
last_modified = meetinfo and meetinfo[0]['modified']
|
||||
|
||||
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
|
||||
|
||||
for obj in meetinfo:
|
||||
obj['modified'] = tz.localize(obj['modified']).astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
obj['modified'] = obj['modified'].astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
data = {"%s"%num: meetinfo}
|
||||
|
||||
response = HttpResponse(json.dumps(data, indent=2, sort_keys=True), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET)
|
||||
if last_modified:
|
||||
last_modified = tz.localize(last_modified).astimezone(pytz.utc)
|
||||
last_modified = last_modified.astimezone(pytz.utc)
|
||||
response['Last-Modified'] = format_date_time(timegm(last_modified.timetuple()))
|
||||
return response
|
||||
|
||||
|
@ -2386,7 +2406,9 @@ def session_details(request, num, acronym):
|
|||
|
||||
# Find the time of the meeting, so that we can look back historically
|
||||
# for what the group was called at the time.
|
||||
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
|
||||
meeting_time = meeting.tz().localize(
|
||||
datetime.datetime.combine(meeting.date, datetime.time())
|
||||
)
|
||||
|
||||
groups = list(set([ s.group for s in sessions ]))
|
||||
group_replacements = find_history_replacements_active_at(groups, meeting_time)
|
||||
|
@ -2453,7 +2475,7 @@ def session_details(request, num, acronym):
|
|||
'is_materials_manager' : session.group.has_role(request.user, session.group.features.matman_roles),
|
||||
'can_manage_materials' : can_manage,
|
||||
'can_view_request': can_view_request,
|
||||
'thisweek': datetime.date.today()-datetime.timedelta(days=7),
|
||||
'thisweek': datetime_today()-datetime.timedelta(days=7),
|
||||
'now': timezone.now(),
|
||||
'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
|
||||
})
|
||||
|
@ -3552,7 +3574,7 @@ def past(request):
|
|||
|
||||
def upcoming(request):
|
||||
'''List of upcoming meetings'''
|
||||
today = datetime.date.today()
|
||||
today = datetime_today()
|
||||
|
||||
# Get ietf meetings starting 7 days ago, and interim meetings starting today
|
||||
ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
|
||||
|
@ -3616,7 +3638,7 @@ def upcoming(request):
|
|||
'menu_entries': menu_entries,
|
||||
'selected_menu_entry': selected_menu_entry,
|
||||
'now': timezone.now(),
|
||||
'use_codimd': True if datetime.date.today()>=settings.MEETING_USES_CODIMD_DATE else False,
|
||||
'use_codimd': (date_today() >= settings.MEETING_USES_CODIMD_DATE),
|
||||
})
|
||||
|
||||
|
||||
|
@ -3630,7 +3652,7 @@ def upcoming_ical(request):
|
|||
except ValueError as e:
|
||||
return HttpResponseBadRequest(str(e))
|
||||
|
||||
today = datetime.date.today()
|
||||
today = datetime_today()
|
||||
|
||||
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
|
||||
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).prefetch_related('schedule').order_by('date'))
|
||||
|
@ -3680,7 +3702,7 @@ def upcoming_ical(request):
|
|||
|
||||
def upcoming_json(request):
|
||||
'''Return Upcoming meetings in json format'''
|
||||
today = datetime.date.today()
|
||||
today = datetime_today()
|
||||
|
||||
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
|
||||
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).order_by('date'))
|
||||
|
@ -3729,7 +3751,7 @@ def proceedings(request, num=None):
|
|||
begin_date = meeting.get_submission_start_date()
|
||||
cut_off_date = meeting.get_submission_cut_off_date()
|
||||
cor_cut_off_date = meeting.get_submission_correction_date()
|
||||
now = datetime.date.today()
|
||||
now = date_today()
|
||||
|
||||
schedule = get_schedule(meeting, None)
|
||||
sessions = add_event_info_to_session_qs(
|
||||
|
@ -4026,7 +4048,7 @@ def important_dates(request, num=None, output_format=None):
|
|||
base_num = int(meeting.number)
|
||||
|
||||
user = request.user
|
||||
today = datetime.date.today()
|
||||
today = datetime_today()
|
||||
meetings = []
|
||||
if meeting.show_important_dates or meeting.date < today:
|
||||
meetings.append(meeting)
|
||||
|
@ -4078,23 +4100,27 @@ def edit_timeslot(request, num, slot_id):
|
|||
meeting = get_object_or_404(Meeting, number=num)
|
||||
if timeslot.meeting != meeting:
|
||||
raise Http404()
|
||||
if request.method == 'POST':
|
||||
form = TimeSlotEditForm(instance=timeslot, data=request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
|
||||
else:
|
||||
form = TimeSlotEditForm(instance=timeslot)
|
||||
timezone.activate(meeting.tz()) # specifies current_timezone used for rendering and form handling
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
form = TimeSlotEditForm(instance=timeslot, data=request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
|
||||
else:
|
||||
form = TimeSlotEditForm(instance=timeslot)
|
||||
|
||||
sessions = timeslot.sessions.filter(
|
||||
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
|
||||
sessions = timeslot.sessions.filter(
|
||||
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
|
||||
|
||||
return render(
|
||||
request,
|
||||
'meeting/edit_timeslot.html',
|
||||
{'timeslot': timeslot, 'form': form, 'sessions': sessions},
|
||||
status=400 if form.errors else 200,
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
'meeting/edit_timeslot.html',
|
||||
{'timeslot': timeslot, 'form': form, 'sessions': sessions},
|
||||
status=400 if form.errors else 200,
|
||||
)
|
||||
finally:
|
||||
timezone.deactivate()
|
||||
|
||||
|
||||
@role_required('Secretariat')
|
||||
|
@ -4105,7 +4131,7 @@ def create_timeslot(request, num):
|
|||
if form.is_valid():
|
||||
bulk_create_timeslots(
|
||||
meeting,
|
||||
[datetime.datetime.combine(day, form.cleaned_data['time'])
|
||||
[meeting.tz().localize(datetime.datetime.combine(day, form.cleaned_data['time']))
|
||||
for day in form.cleaned_data.get('days', [])],
|
||||
form.cleaned_data['locations'],
|
||||
dict(
|
||||
|
|
|
@ -15998,7 +15998,7 @@
|
|||
"fields": {
|
||||
"command": "xym",
|
||||
"switch": "--version",
|
||||
"time": "2022-07-13T00:09:29.108",
|
||||
"time": "2022-07-13T00:09:29.108Z",
|
||||
"used": true,
|
||||
"version": "xym 0.5"
|
||||
},
|
||||
|
@ -16009,7 +16009,7 @@
|
|||
"fields": {
|
||||
"command": "pyang",
|
||||
"switch": "--version",
|
||||
"time": "2022-07-13T00:09:29.475",
|
||||
"time": "2022-07-13T00:09:29.475Z",
|
||||
"used": true,
|
||||
"version": "pyang 2.5.3"
|
||||
},
|
||||
|
@ -16020,7 +16020,7 @@
|
|||
"fields": {
|
||||
"command": "yanglint",
|
||||
"switch": "--version",
|
||||
"time": "2022-07-13T00:09:29.497",
|
||||
"time": "2022-07-13T00:09:29.497Z",
|
||||
"used": true,
|
||||
"version": "yanglint SO 1.9.2"
|
||||
},
|
||||
|
@ -16031,7 +16031,7 @@
|
|||
"fields": {
|
||||
"command": "xml2rfc",
|
||||
"switch": "--version",
|
||||
"time": "2022-07-13T00:09:30.513",
|
||||
"time": "2022-07-13T00:09:30.513Z",
|
||||
"used": true,
|
||||
"version": "xml2rfc 3.13.0"
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ This module contains all the functions for generating static proceedings pages
|
|||
'''
|
||||
import datetime
|
||||
import os
|
||||
import pytz
|
||||
import re
|
||||
import subprocess
|
||||
from urllib.parse import urlencode
|
||||
|
@ -201,17 +202,22 @@ def send_audio_import_warning(unmatched_files):
|
|||
# End Recording Functions
|
||||
# -------------------------------------------------
|
||||
|
||||
def get_progress_stats(sdate,edate):
|
||||
def get_progress_stats(sdate, edate):
|
||||
'''
|
||||
This function takes a date range and produces a dictionary of statistics / objects for
|
||||
use in a progress report. Generally the end date will be the date of the last meeting
|
||||
and the start date will be the date of the meeting before that.
|
||||
|
||||
Data between midnight UTC on the specified dates are included in the stats.
|
||||
'''
|
||||
sdatetime = pytz.utc.localize(datetime.datetime.combine(sdate, datetime.time()))
|
||||
edatetime = pytz.utc.localize(datetime.datetime.combine(edate, datetime.time()))
|
||||
|
||||
data = {}
|
||||
data['sdate'] = sdate
|
||||
data['edate'] = edate
|
||||
|
||||
events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lt=edate)
|
||||
events = DocEvent.objects.filter(doc__type='draft', time__gte=sdatetime, time__lt=edatetime)
|
||||
|
||||
data['actions_count'] = events.filter(type='iesg_approved').count()
|
||||
data['last_calls_count'] = events.filter(type='sent_last_call').count()
|
||||
|
@ -226,7 +232,7 @@ def get_progress_stats(sdate,edate):
|
|||
data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ]))
|
||||
|
||||
# Calculate Final Four Weeks stats (ffw)
|
||||
ffwdate = edate - datetime.timedelta(days=28)
|
||||
ffwdate = edatetime - datetime.timedelta(days=28)
|
||||
ffw_new_count = events.filter(time__gte=ffwdate,newrevisiondocevent__rev='00').count()
|
||||
try:
|
||||
ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%')
|
||||
|
@ -257,14 +263,14 @@ def get_progress_stats(sdate,edate):
|
|||
data['new_groups'] = Group.objects.filter(
|
||||
type='wg',
|
||||
groupevent__changestategroupevent__state='active',
|
||||
groupevent__time__gte=sdate,
|
||||
groupevent__time__lt=edate)
|
||||
groupevent__time__gte=sdatetime,
|
||||
groupevent__time__lt=edatetime)
|
||||
|
||||
data['concluded_groups'] = Group.objects.filter(
|
||||
type='wg',
|
||||
groupevent__changestategroupevent__state='conclude',
|
||||
groupevent__time__gte=sdate,
|
||||
groupevent__time__lt=edate)
|
||||
groupevent__time__gte=sdatetime,
|
||||
groupevent__time__lt=edatetime)
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ SITE_ID = 1
|
|||
# to load the internationalization machinery.
|
||||
USE_I18N = False
|
||||
|
||||
USE_TZ = False
|
||||
USE_TZ = True
|
||||
|
||||
if SERVER_MODE == 'production':
|
||||
MEDIA_ROOT = '/a/www/www6s/lib/dt/media/'
|
||||
|
@ -1187,8 +1187,6 @@ CELERY_TIMEZONE = 'UTC'
|
|||
CELERY_BROKER_URL = 'amqp://mq/'
|
||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||
CELERY_BEAT_SYNC_EVERY = 1 # update DB after every event
|
||||
assert not USE_TZ, 'Drop DJANGO_CELERY_BEAT_TZ_AWARE setting once USE_TZ is True!'
|
||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
||||
|
||||
# Meetecho API setup: Uncomment this and provide real credentials to enable
|
||||
# Meetecho conference creation for interim session requests
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% load static %}
|
||||
{% load ietf_filters %}
|
||||
{% load textfilters %}
|
||||
{% load htmlfilters agenda_custom_tags %}
|
||||
{% load htmlfilters agenda_custom_tags tz %}
|
||||
{% block title %}
|
||||
IETF {{ schedule.meeting.number }} Meeting Agenda
|
||||
{% if "-utc" in request.path %}(UTC){% endif %}
|
||||
|
@ -36,10 +36,10 @@
|
|||
<a class="btn btn-sm btn-primary my-3 now-link" href="#">Jump to current session</a>
|
||||
{% endif %}
|
||||
<div class="d-flex">
|
||||
{% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=timezone minimal=True only %}
|
||||
{% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=display_timezone minimal=True only %}
|
||||
</div>
|
||||
<div class="small text-muted">
|
||||
Showing <span class="current-tz">{{ timezone|split:"_"|join:" "|split:"/"|join:" / " }}</span> time
|
||||
Showing <span class="current-tz">{{ display_timezone|split:"_"|join:" "|split:"/"|join:" / " }}</span> time
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,7 +63,7 @@
|
|||
{{ schedule.meeting.agenda_info_note|removetags:"h1"|safe }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=timezone only %}
|
||||
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=display_timezone only %}
|
||||
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Filter this agenda view..." always_show=personalize %}
|
||||
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting personalize=personalize only %}
|
||||
<div class="input-group mb-3">
|
||||
|
@ -249,7 +249,8 @@
|
|||
</td>
|
||||
{% endif %}
|
||||
{% if item.slot_type.slug == 'plenary' %}
|
||||
<td class="text-end">{% include "meeting/timeslot_start_end.html" %}</td>
|
||||
<td class="text-end">
|
||||
{% include "meeting/timeslot_start_end.html" %}</td>
|
||||
<td colspan="3">
|
||||
{% if item.timeslot.show_location and item.timeslot.location %}
|
||||
{% location_anchor item.timeslot %}
|
||||
|
@ -327,11 +328,11 @@
|
|||
{% if item.session.rescheduled_to %}
|
||||
TO
|
||||
<div class="timetooltip reschedtimetooltip">
|
||||
<div data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}"
|
||||
data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}"
|
||||
<div data-start-time="{{ item.session.rescheduled_to.time|utc|date:"U" }}"
|
||||
data-end-time="{{ item.session.rescheduled_to.end_time|utc|date:"U" }}"
|
||||
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %} data-weekday="1"{% endif %}>
|
||||
{% if "-utc" in request.path %}
|
||||
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
|
||||
{{ item.session.rescheduled_to.time|utc|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|utc|date:"G:i" }}
|
||||
{% else %}
|
||||
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}
|
||||
{% endif %}
|
||||
|
@ -487,7 +488,7 @@
|
|||
|
||||
$(document).ready(function() {
|
||||
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
|
||||
meeting_timezone = '{{ timezone }}';
|
||||
meeting_timezone = '{{ display_timezone }}';
|
||||
|
||||
// First, initialize_moments(). This must be done before calling any of the update methods.
|
||||
// It does not need timezone info, so safe to call before initializing ietf_timezone.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load humanize %}{% autoescape off %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
|
||||
{% load humanize tz %}{% autoescape off %}{% timezone schedule.meeting.tz %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
PRODID:-//IETF//datatracker.ietf.org ical agenda//EN
|
||||
|
@ -8,8 +8,8 @@ SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{% if n
|
|||
{% if item.timeslot.show_location %}LOCATION:{{item.timeslot.get_location}}
|
||||
{% endif %}STATUS:{{item.session.ical_status}}
|
||||
CLASS:PUBLIC
|
||||
DTSTART{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
|
||||
DTEND{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
|
||||
DTSTART;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
|
||||
DTEND;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
|
||||
DTSTAMP:{{ item.timeslot.modified|date:"Ymd" }}T{{ item.timeslot.modified|date:"His" }}Z{% if item.session.agenda %}
|
||||
URL:{{item.session.agenda.get_versionless_href}}{% endif %}
|
||||
DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
|
||||
|
@ -25,4 +25,4 @@ DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
|
|||
\n{# link agenda for ietf meetings #}
|
||||
See in schedule: {% absurl 'ietf.meeting.views.agenda' num=schedule.meeting.number %}#row-{{ item.slug }}\n{% endif %}
|
||||
END:VEVENT
|
||||
{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endautoescape %}
|
||||
{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endtimezone %}{% endautoescape %}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
|
||||
{% load ietf_filters tz %}{% timezone meeting.tz %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
|
||||
|
||||
{% endif %}The {{ group.name }} ({{ group.acronym }}) {% if group.type.slug == 'wg' and group.state.slug == 'bof' %}BOF{% else %}{{group.type.name}}{% endif %} will hold
|
||||
{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.utc_start_time | date:"H:i" }} to {{ assignments.first.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}.
|
||||
{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.time | utc | date:"H:i" }} to {{ assignments.first.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}.
|
||||
{% else %}a multi-day {% if not meeting.city %}virtual {% endif %}interim meeting.
|
||||
|
||||
{% for assignment in assignments %}Session {{ forloop.counter }}:
|
||||
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.utc_start_time | date:"H:i" }} to {{ assignment.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}
|
||||
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.time | utc | date:"H:i" }} to {{ assignment.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}
|
||||
{% endfor %}{% endif %}
|
||||
{% if meeting.city %}Meeting Location:
|
||||
{{ meeting.city }}, {{ meeting.country }}
|
||||
|
@ -17,3 +17,4 @@ Information about remote participation:
|
|||
{{ meeting.session_set.first.remote_instructions }}
|
||||
|
||||
{{ meeting.session_set.first.agenda_note }}
|
||||
{% endtimezone %}
|
|
@ -1,12 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters %}
|
||||
{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters tz %}
|
||||
{% block title %}Interim Request Details{% endblock %}
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'ietf/css/select2.css' %}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% block content %}{% timezone meeting.tz %}
|
||||
{% origin %}
|
||||
<h1>Interim Meeting Request Details</h1>
|
||||
<dl class="row my-3">
|
||||
|
@ -67,7 +67,7 @@
|
|||
<dd class="col-sm-10">
|
||||
{{ assignment.timeslot.time|date:"H:i" }}
|
||||
{% if meeting.time_zone != 'UTC' %}
|
||||
({{ assignment.timeslot.utc_start_time|date:"H:i" }} UTC)
|
||||
({{ assignment.timeslot.time|utc|date:"H:i" }} UTC)
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
|
@ -151,7 +151,7 @@
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if can_approve and status_slug == 'apprw' %}</form>{% endif %}
|
||||
{% endblock %}
|
||||
{% endtimezone %}{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{% static 'ietf/js/select2.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/meeting-interim-request.js' %}"></script>
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
<div class="time"
|
||||
data-start-time="{{ item.start_timestamp }}"
|
||||
data-end-time="{{ item.end_timestamp }}">
|
||||
{% if "-utc" in request.path %}
|
||||
{{ item.timeslot.utc_start_time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.utc_end_time|date:"H:i" }}
|
||||
{% else %}
|
||||
{{ item.timeslot.time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.end_time|date:"H:i" }}
|
||||
{% endif %}
|
||||
{{ item.timeslot.time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.end_time|date:"H:i" }}
|
||||
</div>
|
||||
</div>
|
|
@ -2,7 +2,7 @@
|
|||
{# Copyright The IETF Trust 2015, 2020, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load cache %}
|
||||
{% load ietf_filters static classname %}
|
||||
{% load ietf_filters static classname tz %}
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
||||
<link rel="stylesheet" href="{% static "ietf/js/fullcalendar.css" %}">
|
||||
|
@ -67,9 +67,9 @@
|
|||
{% elif entry|classname == 'Session' %}
|
||||
{% with session=entry group=entry.group meeting=entry.meeting %}
|
||||
<td class="session-time"
|
||||
data-start-utc="{{ session.official_timeslotassignment.timeslot.utc_start_time | date:'Y-m-d H:i' }}Z"
|
||||
data-end-utc="{{ session.official_timeslotassignment.timeslot.utc_end_time | date:'Y-m-d H:i' }}Z">
|
||||
{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i" }}-{{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i" }}
|
||||
data-start-utc="{{ session.official_timeslotassignment.timeslot.time | utc | date:'Y-m-d H:i' }}Z"
|
||||
data-end-utc="{{ session.official_timeslotassignment.timeslot.end_time | utc | date:'Y-m-d H:i' }}Z">
|
||||
{{ session.official_timeslotassignment.timeslot.time | utc | date:"Y-m-d H:i" }}-{{ session.official_timeslotassignment.timeslot.end_time | utc | date:"H:i" }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'ietf.group.views.group_home' acronym=group.acronym %}">{{ group.acronym }}</a>
|
||||
|
@ -132,8 +132,8 @@
|
|||
{
|
||||
group: '{% if session.group %}{{session.group.acronym}}{% endif %}{% if session.name %} - {{session.name}}{% endif %}',
|
||||
filter_keywords: ["{{ session.filter_keywords|join:'","' }}"],
|
||||
start_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}}'),
|
||||
end_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_end_time | date:"Y-m-d H:i"}}'),
|
||||
start_moment: moment.utc('{{session.official_timeslotassignment.timeslot.time | utc | date:"Y-m-d H:i"}}'),
|
||||
end_moment: moment.utc('{{session.official_timeslotassignment.timeslot.end_time | utc | date:"Y-m-d H:i"}}'),
|
||||
url: '{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}'
|
||||
}
|
||||
{% endwith %}
|
||||
|
|
|
@ -3,6 +3,7 @@ import email.utils
|
|||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
def local_timezone_to_utc(d):
|
||||
"""Takes a naive datetime in the local timezone and returns a
|
||||
|
@ -36,4 +37,35 @@ def email_time_to_local_timezone(date_string):
|
|||
|
||||
def date2datetime(date, tz=pytz.utc):
|
||||
return datetime.datetime(*(date.timetuple()[:6]), tzinfo=tz)
|
||||
|
||||
|
||||
|
||||
def datetime_today(tzinfo=None):
|
||||
"""Get a timezone-aware datetime representing midnight today
|
||||
|
||||
For use with datetime fields representing a date.
|
||||
"""
|
||||
if tzinfo is None:
|
||||
tzinfo = pytz.utc
|
||||
return timezone.now().astimezone(tzinfo).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
|
||||
def date_today(tzinfo=None):
|
||||
"""Get the date corresponding to the current moment
|
||||
|
||||
Note that Dates are not themselves timezone aware.
|
||||
"""
|
||||
if tzinfo is None:
|
||||
tzinfo = pytz.utc
|
||||
return timezone.now().astimezone(tzinfo).date()
|
||||
|
||||
|
||||
def time_now(tzinfo=None):
|
||||
"""Get the "wall clock" time corresponding to the current moment
|
||||
|
||||
The value returned by this data is a Time with no tzinfo attached. (Time
|
||||
objects have only limited timezone support, even if tzinfo is filled in,
|
||||
and may not behave correctly when daylight savings time shifts are relevant.)
|
||||
"""
|
||||
if tzinfo is None:
|
||||
tzinfo = pytz.utc
|
||||
return timezone.now().astimezone(tzinfo).time()
|
||||
|
|
Loading…
Reference in a new issue