Fixed a number of TimeSlot-related issues, in particular related to getting the correct date from a timeslot. Also fixed the displayed slot times in the new schedule editors to show local times, not UTC times.

- Legacy-Id: 18785
This commit is contained in:
Henrik Levkowetz 2020-12-19 22:55:11 +00:00
parent d9ad8b8616
commit 59b49c846b
17 changed files with 90 additions and 66 deletions

View file

@ -85,7 +85,7 @@ def build_all_agenda_slices(meeting):
date_slices = {}
for ts in meeting.timeslot_set.filter(type__in=['regular',]).order_by('time','name'):
ymd = ts.time.date()
ymd = ts.local_date()
if ymd not in date_slices and ts.location != None:
date_slices[ymd] = []
@ -629,7 +629,7 @@ def send_interim_meeting_cancellation_notice(meeting):
acronym=group.acronym,
type=group.type.slug.upper(),
date=meeting.date.strftime('%Y-%m-%d'))
start_time = session.official_timeslotassignment().timeslot.time.astimezone(meeting.tz())
start_time = session.official_timeslotassignment().timeslot.local_start_time()
end_time = start_time + session.requested_duration
is_multi_day = session.meeting.session_set.with_current_status().filter(current_status='sched').count() > 1
template = 'meeting/interim_meeting_cancellation_notice.txt'
@ -646,7 +646,7 @@ def send_interim_meeting_cancellation_notice(meeting):
def send_interim_session_cancellation_notice(session):
"""Sends an email that one session of a scheduled interim meeting has been cancelled."""
group = session.group
start_time = session.official_timeslotassignment().timeslot.time
start_time = session.official_timeslotassignment().timeslot.local_start_time()
end_time = start_time + session.requested_duration
(to_email, cc_list) = gather_address_lists('interim_cancelled',group=group)
from_email = settings.INTERIM_ANNOUNCE_FROM_EMAIL_PROGRAM if group.type_id=='program' else settings.INTERIM_ANNOUNCE_FROM_EMAIL_DEFAULT

View file

@ -2,38 +2,41 @@
# -*- coding: utf-8 -*-
import time
import datetime
import shutil
import os
import pytz
import re
import shutil
import time
from unittest import skipIf
import django
from django.db.models import F
from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.text import slugify
#from django.test.utils import override_settings
import debug # pyflakes:ignore
from ietf import settings
from ietf.doc.factories import DocumentFactory
from ietf.doc.models import State
from ietf.group import colors
from ietf.person.models import Person
from ietf.group.models import Group
from ietf.group.factories import GroupFactory
from ietf.group.models import Group
from ietf.meeting.factories import SessionFactory
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
Room, TimeSlot, Constraint, ConstraintName,
Meeting, SchedulingEvent, SessionStatusName)
Meeting, SchedulingEvent, SessionStatusName,
Person)
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.utils.pipe import pipe
from ietf.utils.test_runner import IetfLiveServerTestCase
from ietf.utils.test_utils import assert_ical_response_is_valid
from ietf import settings
from ietf.utils.timezone import datetime_today_start
skip_selenium = False
skip_message = ""
@ -102,7 +105,7 @@ class EditMeetingScheduleTests(MeetingTestCase):
schedule = Schedule.objects.filter(meeting=meeting, owner__user__username="plain").first()
room1 = Room.objects.get(name="Test Room")
slot1 = TimeSlot.objects.filter(meeting=meeting, location=room1).order_by('_time').first()
slot1 = TimeSlot.objects.filter(meeting=meeting, location=room1).order_by('time').first()
room2 = Room.objects.create(meeting=meeting, name="Test Room2", capacity=1)
room2.session_types.add('regular')
@ -265,14 +268,14 @@ class EditMeetingScheduleTests(MeetingTestCase):
# hide timeslots
self.driver.find_element_by_css_selector(".timeslot-group-toggles button").click()
self.assertTrue(self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.time.strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.local_start_time().strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [data-dismiss=\"modal\"]").click()
self.assertTrue(not self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
# swap days
self.driver.find_element_by_css_selector(".day [data-target=\"#swap-days-modal\"][data-dayid=\"{}\"]".format(slot4.time.date().isoformat())).click()
self.driver.find_element_by_css_selector(".day [data-target=\"#swap-days-modal\"][data-dayid=\"{}\"]".format(slot4.local_date().isoformat())).click()
self.assertTrue(self.driver.find_element_by_css_selector("#swap-days-modal").is_displayed())
self.driver.find_element_by_css_selector("#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.time.date().isoformat())).click()
self.driver.find_element_by_css_selector("#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.local_date().isoformat())).click()
self.driver.find_element_by_css_selector("#swap-days-modal button[type=\"submit\"]").click()
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{} #session{}'.format(slot4.pk, s1.pk)))
@ -734,13 +737,15 @@ class AgendaTests(MeetingTestCase):
# 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(
meeting = Meeting.objects.get(number=meeting_number)
tz = pytz.timezone(meeting.time_zone)
start_time = tz.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'
@ -756,8 +761,7 @@ class AgendaTests(MeetingTestCase):
else:
# Last component was a group, this is a regular session
session_type = 'regular'
meeting = Meeting.objects.get(number=meeting_number)
possible_assignments = SchedTimeSessAssignment.objects.filter(
schedule__in=[meeting.schedule, meeting.schedule.base],
timeslot__time=start_time,
@ -916,7 +920,7 @@ class InterimTests(MeetingTestCase):
# Create a group with a plenary interim session for testing type filters
somegroup = GroupFactory(acronym='sg', name='Some Group')
sg_interim = make_interim_meeting(somegroup, timezone.now().date() + datetime.timedelta(days=20))
sg_interim = make_interim_meeting(somegroup, datetime_today_start() + datetime.timedelta(days=20))
sg_sess = sg_interim.session_set.first()
sg_slot = sg_sess.timeslotassignments.first().timeslot
sg_sess.type_id = 'plenary'
@ -946,7 +950,7 @@ class InterimTests(MeetingTestCase):
Session.objects.filter(
meeting__type_id='interim',
timeslotassignments__schedule=F('meeting__schedule'),
timeslotassignments__timeslot__time__gte=datetime.datetime.today()
timeslotassignments__timeslot__time__gte=datetime_today_start()
)
).filter(current_status__in=('sched','canceled'))
meetings = []
@ -959,7 +963,7 @@ class InterimTests(MeetingTestCase):
def all_ietf_meetings(self):
meetings = Meeting.objects.filter(
type_id='ietf',
date__gte=datetime.datetime.today()-datetime.timedelta(days=7)
date__gte=datetime_today_start()-datetime.timedelta(days=7)
)
for m in meetings:
m.calendar_label = 'IETF %s' % m.number
@ -1084,7 +1088,7 @@ class InterimTests(MeetingTestCase):
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
schedule__in=expected_schedules,
session__in=expected_interim_sessions,
timeslot__time__gte=timezone.now().date(),
timeslot__time__gte=datetime_today_start(),
))
# The UID formats should match those in the upcoming.ics template
expected_uids = [

View file

@ -1421,8 +1421,8 @@ class EditTests(TestCase):
r = self.client.post(url, {
'action': 'swapdays',
'source_day': timeslots[0].time.date().isoformat(),
'target_day': timeslots[2].time.date().isoformat(),
'source_day': timeslots[0].local_date().isoformat(),
'target_day': timeslots[2].local_date().isoformat(),
})
self.assertResponseStatus(r, 302)
@ -1434,8 +1434,8 @@ class EditTests(TestCase):
# swap back
r = self.client.post(url, {
'action': 'swapdays',
'source_day': timeslots[2].time.date().isoformat(),
'target_day': timeslots[0].time.date().isoformat(),
'source_day': timeslots[2].local_date().isoformat(),
'target_day': timeslots[0].local_date().isoformat(),
})
self.assertResponseStatus(r, 302)
@ -1469,7 +1469,7 @@ class EditTests(TestCase):
break_slot = TimeSlot.objects.get(location=break_room, type='break')
room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.time.date().isoformat(), break_slot.location_id))
room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.local_date().isoformat(), break_slot.location_id))
self.assertTrue(room_row)
self.assertTrue(room_row.find("#timeslot{}".format(break_slot.pk)))
@ -1584,7 +1584,7 @@ class EditTests(TestCase):
r = self.client.post(url, {
'timeslot': assignment.timeslot_id,
'day': assignment.timeslot.time.date().isoformat(),
'day': assignment.timeslot.local_date().isoformat(),
'time': assignment.timeslot.time.time().isoformat(),
'duration': assignment.timeslot.duration,
'location': assignment.timeslot.location_id,
@ -3248,7 +3248,7 @@ class InterimTests(TestCase):
data.update(form_initial)
r = self.client.post(url, data)
if r.status_code != 302:
self.save_response(r)
self.debug_save_response(r)
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}))
self.assertEqual(len(outbox),length_before+1)
self.assertIn('CHANGED', outbox[-1]['Subject'])

View file

@ -654,8 +654,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
source_day = swap_days_form.cleaned_data['source_day']
target_day = swap_days_form.cleaned_data['target_day']
source_timeslots = [ts for ts in timeslots_qs if ts.time.date() == source_day]
target_timeslots = [ts for ts in timeslots_qs if ts.time.date() == target_day]
source_timeslots = [ts for ts in timeslots_qs if ts.local_date() == source_day]
target_timeslots = [ts for ts in timeslots_qs if ts.local_date() == target_day]
swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, target_timeslots, target_day - source_day)
return HttpResponseRedirect(request.get_full_path())
@ -668,10 +668,10 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
room_has_timeslots = set()
for t in timeslots_qs:
room_has_timeslots.add(t.location_id)
timeslots_by_room_and_day[(t.location_id, t.time.date())].append(t)
timeslots_by_room_and_day[(t.location_id, t.local_date())].append(t)
days = []
for day in sorted(set(t.time.date() for t in timeslots_qs)):
for day in sorted(set(t.local_date() for t in timeslots_qs)):
room_timeslots = []
for r in rooms:
if r.pk not in room_has_timeslots:
@ -694,8 +694,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
# possible timeslot start/ends
timeslot_groups = defaultdict(set)
for ts in timeslots_qs:
ts.start_end_group = "ts-group-{}-{}".format(ts.time.strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
timeslot_groups[ts.time.date()].add((ts.time, ts.end_time(), ts.start_end_group))
ts.start_end_group = "ts-group-{}-{}".format(ts.local_start_time().strftime("%Y%m%d-%H%M"), int(ts.duration.total_seconds() / 60))
timeslot_groups[ts.local_date()].add((ts.local_start_time(), ts.local_end_time(), ts.start_end_group))
# prepare sessions
prepare_sessions_for_display(sessions)
@ -792,7 +792,7 @@ class TimeSlotForm(forms.Form):
if timeslot:
self.initial = {
'day': timeslot.time.date(),
'day': timeslot.local_date(),
'time': timeslot.time.time(),
'duration': timeslot.duration,
'location': timeslot.location_id,
@ -1033,7 +1033,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
timeslots_by_day_and_room = defaultdict(list)
for t in timeslot_qs:
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
timeslots_by_day_and_room[(t.local_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)])
@ -1567,7 +1567,7 @@ def agenda_by_room(request, num=None, name=None, owner=None):
for day in assignments.dates('timeslot__time','day'):
ss_by_day[day]=[]
for ss in assignments.order_by('timeslot__location__functional_name','timeslot__location__name','timeslot__time'):
day = ss.timeslot.time.date()
day = ss.timeslot.local_date()
ss_by_day[day].append(ss)
return render(request,"meeting/agenda_by_room.html",{"meeting":meeting,"schedule":schedule,"ss_by_day":ss_by_day})
@ -3233,8 +3233,8 @@ def interim_request_session_cancel(request, sessionid):
messages.success(request, 'Interim meeting session cancelled')
return redirect(interim_request_details, number=session.meeting.number)
else:
session_time = session.official_timeslotassignment().timeslot.time
form = InterimCancelForm(initial={'group': group.acronym, 'date': session_time.date()})
date = session.official_timeslotassignment().timeslot.local_date()
form = InterimCancelForm(initial={'group': group.acronym, 'date': date})
return render(request, "meeting/interim_request_cancel.html", {
"form": form,
@ -3732,8 +3732,8 @@ def api_set_session_video_url(request):
else:
return err(400, "URL is the same")
else:
time = session.official_timeslotassignment().timeslot.time
title = 'Video recording for %s on %s at %s' % (acronym, time.date(), time.time())
timeslot = session.official_timeslotassignment().timeslot
title = 'Video recording for %s on %s at %s' % (acronym, timeslot.local_date(), timeslot.time.time())
create_recording(session, url, title=title, user=user)
else:
return err(405, "Method not allowed")

View file

@ -30,6 +30,9 @@ def get_next_slot(slot):
aren't any. You must check availability of the slot as we sometimes need to get the next
slot whether it's available or not. For use with combine option.
'''
# timezone-aware note: The following works because the slot times are
# saved as if they were local timezone-naive times, even if Django sees
# them as UTC times when USE_TZ == True.
same_day_slots = TimeSlot.objects.filter(meeting=slot.meeting,location=slot.location,time__day=slot.time.day).order_by('time')
try:
i = list(same_day_slots).index(slot)

View file

@ -553,7 +553,7 @@ def misc_session_edit(request, meeting_id, schedule_name, slot_id):
else:
# we need to pass the session to the form in order to disallow changing
# of group after materials have been uploaded
delta = slot.time.date() - meeting.date
delta = slot.local_date() - meeting.date
initial = {'location':slot.location,
'group':session.group,
'name':session.name,

View file

@ -147,7 +147,7 @@ def create_recording(session, url, title=None, user=None):
'''
sequence = get_next_sequence(session.group,session.meeting,'recording')
name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence)
time = session.official_timeslotassignment().timeslot.time.strftime('%Y-%m-%d %H:%M')
time = session.official_timeslotassignment().timeslot.local_start_time().strftime('%Y-%m-%d %H:%M')
if not title:
if url.endswith('mp3'):
title = 'Audio recording for {}'.format(time)

View file

@ -284,7 +284,7 @@
{% 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" }}
{% else %}
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}
{{ item.session.rescheduled_to.local_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.local_end_time|date:"G:i" }}
{% endif %}
{% endif %}
</span>

View file

@ -23,7 +23,7 @@
{% endif %}{% if item.timeslot.type_id == 'regular' %}{% if item.session.historic_group %}{% ifchanged %}
{{ item.timeslot.time_desc }} {{ item.timeslot.name }}
{% endifchanged %}{{ item.timeslot.location.name|ljust:14 }} {{ item.session.historic_group.historic_parent.acronym|upper|ljust:4 }} {{ item.session.historic_group.acronym|ljust:10 }} {{ item.session.historic_group.name }} {% if item.session.historic_group.state_id == "bof" %}BOF{% elif item.session.historic_group.type_id == "wg" %}WG{% endif %}{% if item.session.agenda_note %} - {{ item.session.agenda_note }}{% endif %}{% if item.session.current_status == 'canceled' %} *** CANCELLED ***{% elif item.session.current_status == 'resched' %} *** RESCHEDULED{% if item.session.rescheduled_to %} TO {{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}{% endif %} ***{% endif %}
{% endifchanged %}{{ item.timeslot.location.name|ljust:14 }} {{ item.session.historic_group.historic_parent.acronym|upper|ljust:4 }} {{ item.session.historic_group.acronym|ljust:10 }} {{ item.session.historic_group.name }} {% if item.session.historic_group.state_id == "bof" %}BOF{% elif item.session.historic_group.type_id == "wg" %}WG{% endif %}{% if item.session.agenda_note %} - {{ item.session.agenda_note }}{% endif %}{% if item.session.current_status == 'canceled' %} *** CANCELLED ***{% elif item.session.current_status == 'resched' %} *** RESCHEDULED{% if item.session.rescheduled_to %} TO {{ item.session.rescheduled_to.local_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.local_end_time|date:"G:i" }}{% endif %} ***{% endif %}
{% endif %}{% endif %}{% if item.timeslot.type.slug == "break" %}
{{ item.timeslot.time_desc }} {{ item.timeslot.name }}{% if schedule.meeting.break_area and item.timeslot.show_location %} - {{ schedule.meeting.break_area }}{% endif %}{% endif %}{% if item.timeslot.type.slug == "other" %}
{{ item.timeslot.time_desc }} {{ item.timeslot.name }} - {{ item.timeslot.location.name }}{% endif %}{% endfor %}

View file

@ -22,11 +22,11 @@
<tr>
<td>
{% if d.change == 'schedule' %}
Scheduled <b>{{ d.session.session_label }}</b> to <b>{{ d.to.time|date:"l G:i" }} at {{ d.to.location.name }}</b>
Scheduled <b>{{ d.session.session_label }}</b> to <b>{{ d.to.local_start_time|date:"l G:i" }} at {{ d.to.location.name }}</b>
{% elif d.change == 'move' %}
Moved <b>{{ d.session.session_label }}</b> from {{ d.from.time|date:"l G:i" }} at {{ d.from.location.name }} to <b>{{ d.to.time|date:"l G:i" }} at {{ d.to.location.name }}</b>
Moved <b>{{ d.session.session_label }}</b> from {{ d.from.local_start_time|date:"l G:i" }} at {{ d.from.location.name }} to <b>{{ d.to.local_start_time|date:"l G:i" }} at {{ d.to.location.name }}</b>
{% elif d.change == 'unschedule' %}
Unscheduled <b>{{ d.session.session_label }}</b> from {{ d.from.time|date:"l G:i" }} at {{ d.from.location.name }}
Unscheduled <b>{{ d.session.session_label }}</b> from {{ d.from.local_start_time|date:"l G:i" }} at {{ d.from.location.name }}
{% endif %}
</td>
</tr>

View file

@ -85,9 +85,9 @@
<div class="timeslots" data-roomcapacity="{{ room.capacity }}">
{% for t in timeslots %}
<div id="timeslot{{ t.pk }}" class="timeslot {{ t.start_end_group }}" data-start="{{ t.time.isoformat }}" data-end="{{ t.end_time.isoformat }}" data-duration="{{ t.duration.total_seconds }}" data-scheduledatlabel="{{ t.time|date:"l G:i" }}-{{ t.end_time|date:"G:i" }}" style="width: {{ t.layout_width }}rem;">
<div id="timeslot{{ t.pk }}" class="timeslot {{ t.start_end_group }}" data-start="{{ t.local_start_time.isoformat }}" data-end="{{ t.local_end_time.isoformat }}" data-duration="{{ t.duration.total_seconds }}" data-scheduledatlabel="{{ t.local_start_time|date:"l G:i" }}-{{ t.local_end_time|date:"G:i" }}" style="width: {{ t.layout_width }}rem;">
<div class="time-label">
{{ t.time|date:"G:i" }} - {{ t.end_time|date:"G:i" }}
{{ t.local_start_time|date:"G:i" }} - {{ t.local_end_time|date:"G:i" }}
</div>
<div class="drop-target">

View file

@ -62,7 +62,7 @@
{{ t.type.name }}
{% endif %}
{% endfor %}
<span class="time-label">{{ t.time|date:"G:i" }}-{{ t.end_time|date:"G:i" }}</span>
<span class="time-label">{{ t.local_start_time|date:"G:i" }}-{{ t.local_end_time|date:"G:i" }}</span>
</div>
{% endfor %}
</div>

View file

@ -20,7 +20,7 @@
{% for slot in unavailable %}
if (room_names.indexOf("{{slot.get_hidden_location}}") >= 0 )
{
items.push({room_index:room_names.indexOf("{{slot.get_hidden_location}}"),day:{{slot.day}}, delta_from_beginning:{{slot.delta_from_beginning}},time:"{{slot.time|date:"Hi"}}-{{slot.end_time|date:"Hi"}}", verbose_time:"{{slot.time|date:"D M d Hi"}}-{{slot.end_time|date:"Hi"}}",duration:{{slot.duration.total_seconds}}, type:"{{slot.type}}", name:"Unavailable", dayname:"{{ slot.time|date:"l"|upper }}, {{ slot.time|date:"F j, Y" }}" });
items.push({room_index:room_names.indexOf("{{slot.get_hidden_location}}"),day:{{slot.day}}, delta_from_beginning:{{slot.delta_from_beginning}},time:"{{slot.local_start_time|date:"Hi"}}-{{slot.local_end_time|date:"Hi"}}", verbose_time:"{{slot.local_start_time|date:"D M d Hi"}}-{{slot.local_end_time|date:"Hi"}}",duration:{{slot.duration.total_seconds}}, type:"{{slot.type}}", name:"Unavailable", dayname:"{{ slot.local_start_time|date:"l"|upper }}, {{ slot.local_start_time|date:"F j, Y" }}" });
}
{% endfor %}
{% for ss in assignments %}

View file

@ -55,9 +55,9 @@
<div id="proposedslidelist" class="panel-body">
{% for s in pending_suggestions %}
{% if can_manage_materials %}
<p><a href="{% url "ietf.meeting.views.approve_proposed_slides" slidesubmission_id=s.pk num=s.session.meeting.number %}">{{s.submitter}} - {{s.title}} ({{s.time}})</a></p>
<p><a href="{% url "ietf.meeting.views.approve_proposed_slides" slidesubmission_id=s.pk num=s.session.meeting.number %}">{{s.submitter}} - {{s.title}} ({{s.local_start_time}})</a></p>
{% else %}
<p>{{s.title}} ({{s.time}})</p>
<p>{{s.title}} ({{s.local_start_time}})</p>
{% endif %}
{% endfor %}
</div>

View file

@ -33,7 +33,7 @@
{% for day in time_slices %}
{% for slot in slot_slices|lookup:day %}
<th>
{{slot.time|date:'Hi'}}-{{slot.end_time|date:'Hi'}}
{{slot.local_start_time|date:'Hi'}}-{{slot.local_end_time|date:'Hi'}}
</th>
{% endfor %}
{% endfor %}

View file

@ -94,7 +94,7 @@ def convert(apps, tzfrom, tzto):
if values and inv_count:
with open(datafn, "w") as f:
json.dump(values, f)
note(f" Saved {inv_count} new unconverted datetime field values")
note(f" Saved {inv_count:,} new unconverted datetime field values")
else:
note(" No new unconverted datetime field values")
if error:

View file

@ -35,23 +35,26 @@
import datetime
import factory.random
import gzip
import importlib
import inspect
import io
import re
import json
import os
import pytz
import re
import socket
import sys
import time
import json
import pytz
import importlib
import socket
import gzip
import unittest
import factory.random
from fnmatch import fnmatch
from coverage.report import Reporter
from coverage.results import Numbers
from coverage.misc import NotPython
from unittest.util import strclass
import django
from django.conf import settings
@ -806,4 +809,18 @@ class IetfLiveServerTestCase(StaticLiveServerTestCase):
super(IetfLiveServerTestCase, cls).tearDownClass()
set_coverage_checking(True)
def __str__(self):
return u"%s (%s.%s)" % (self._testMethodName, strclass(self.__class__),self._testMethodName)
def debug_save_response(self, r):
"""
This is intended as a debug help, to be inserted whenever one wants
to save a page response in a test case; not for production.
"""
stack = inspect.stack() # stack[0] is the current frame
caller = stack[1]
fn = caller.function + '.html'
with open(fn, 'bw') as f:
f.write(r.content)
sys.stderr.write(f'Wrote response to {fn}\n')