From 9b9f1dd66403ee64ef6b09dccd4ebf49ecaa5829 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 12 Jul 2022 15:23:32 -0300 Subject: [PATCH 01/39] refactor: replace datetime.now with timezone.now --- ietf/api/management/commands/makeresources.py | 4 +- ietf/community/views.py | 5 ++- ietf/doc/expire.py | 3 +- ietf/doc/factories.py | 5 ++- ietf/doc/feeds.py | 3 +- ietf/doc/mails.py | 5 ++- .../commands/generate_draft_aliases.py | 3 +- .../commands/generate_draft_bibxml_files.py | 3 +- ietf/doc/models.py | 11 ++--- ietf/doc/templatetags/ietf_filters.py | 11 ++--- ietf/doc/tests.py | 19 ++++---- ietf/doc/tests_ballot.py | 43 ++++++++++--------- ietf/doc/tests_draft.py | 23 +++++----- ietf/doc/tests_material.py | 4 +- ietf/doc/tests_review.py | 31 ++++++------- ietf/doc/utils.py | 5 ++- ietf/doc/utils_charter.py | 7 +-- ietf/doc/views_charter.py | 9 ++-- ietf/doc/views_draft.py | 3 +- ietf/doc/views_review.py | 14 +++--- ietf/doc/views_search.py | 5 ++- .../commands/generate_group_aliases.py | 3 +- ietf/group/models.py | 5 ++- ietf/group/tests.py | 5 ++- ietf/group/tests_review.py | 5 ++- ietf/group/views.py | 11 ++--- ietf/idindex/index.py | 2 +- ietf/idindex/tests.py | 3 +- .../commands/send_apikey_usage_emails.py | 3 +- ietf/ietfauth/tests.py | 7 +-- ietf/ipr/models.py | 5 +-- ietf/ipr/tests.py | 9 ++-- ietf/ipr/views.py | 3 +- ietf/liaisons/tests.py | 4 +- ietf/meeting/models.py | 3 +- ietf/meeting/tests_views.py | 16 +++---- ietf/meeting/utils.py | 3 +- ietf/meeting/views.py | 14 +++--- .../management/commands/show_messages.py | 3 +- ietf/message/models.py | 6 +-- ietf/message/tests.py | 5 ++- ietf/message/utils.py | 5 ++- ietf/nomcom/tests.py | 3 +- .../purge_old_personal_api_key_events.py | 5 ++- ietf/person/management/commands/tests.py | 5 ++- ietf/person/models.py | 8 ++-- ietf/review/factories.py | 4 +- ietf/review/models.py | 5 ++- ietf/review/utils.py | 6 ++- ietf/secr/meetings/tests.py | 5 ++- ietf/secr/meetings/views.py | 3 +- ietf/secr/proceedings/reports.py | 3 +- ietf/stats/tests.py | 5 ++- ietf/submit/models.py | 7 +-- ietf/submit/tests.py | 17 ++++---- ietf/submit/utils.py | 5 ++- ietf/sync/iana.py | 3 +- ietf/sync/rfceditor.py | 3 +- ietf/sync/tests.py | 5 ++- ietf/sync/views.py | 5 ++- ietf/utils/decorators.py | 5 ++- ietf/utils/mail.py | 8 ++-- .../check_draft_event_revision_integrity.py | 3 +- .../commands/fix_ambiguous_timestamps.py | 3 +- .../commands/send_gdpr_consent_request.py | 3 +- ietf/utils/test_data.py | 11 ++--- ietf/utils/test_runner.py | 14 +++--- ietf/utils/tests_meetecho.py | 36 ++++++++-------- 68 files changed, 288 insertions(+), 228 deletions(-) diff --git a/ietf/api/management/commands/makeresources.py b/ietf/api/management/commands/makeresources.py index 07f8402e7dc..889b2cdfb51 100644 --- a/ietf/api/management/commands/makeresources.py +++ b/ietf/api/management/commands/makeresources.py @@ -3,7 +3,6 @@ import os -import datetime import collections import io @@ -14,6 +13,7 @@ from django.core.management.base import AppCommand from django.db import models from django.template import Template, Context +from django.utils import timezone from tastypie.resources import ModelResource @@ -89,7 +89,7 @@ def handle_app_config(self, app, **options): info = dict( app=app.name, app_label=app.label, - date=datetime.datetime.now() + date=timezone.now() ) new_models = {} for model, rclass_name in missing_resources: diff --git a/ietf/community/views.py b/ietf/community/views.py index 7717c6ee1f1..a9ae82c71fe 100644 --- a/ietf/community/views.py +++ b/ietf/community/views.py @@ -10,6 +10,7 @@ from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.shortcuts import get_object_or_404, render from django.contrib.auth.decorators import login_required +from django.utils import timezone from django.utils.html import strip_tags import debug # pyflakes:ignore @@ -218,7 +219,7 @@ def feed(request, username=None, acronym=None, group_type=None): significant = request.GET.get('significant', '') == '1' documents = docs_tracked_by_community_list(clist).values_list('pk', flat=True) - since = datetime.datetime.now() - datetime.timedelta(days=14) + since = timezone.now() - datetime.timedelta(days=14) events = DocEvent.objects.filter( doc__id__in=documents, @@ -243,7 +244,7 @@ def feed(request, username=None, acronym=None, group_type=None): 'title': title, 'subtitle': subtitle, 'id': feed_id.urn, - 'updated': datetime.datetime.now(), + 'updated': timezone.now(), }, content_type='text/xml') diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py index b780c73bd42..328a9740424 100644 --- a/ietf/doc/expire.py +++ b/ietf/doc/expire.py @@ -4,6 +4,7 @@ from django.conf import settings +from django.utils import timezone import datetime, os, shutil, glob, re from pathlib import Path @@ -62,7 +63,7 @@ def get_expired_drafts(): def in_draft_expire_freeze(when=None): if when == None: - when = datetime.datetime.now() + when = timezone.now() meeting = Meeting.objects.filter(type='ietf', date__gte=when-datetime.timedelta(days=7)).order_by('date').first() diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index fbb6d8156f2..24422b6c545 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -10,6 +10,7 @@ from typing import Optional # pyflakes:ignore from django.conf import settings +from django.utils import timezone from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor, StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent, @@ -38,7 +39,7 @@ class Meta: rev = '00' std_level_id = None # type: Optional[str] intended_std_level_id = None - time = datetime.datetime.now() + time = timezone.now() expires = factory.LazyAttribute(lambda o: o.time+datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)) pages = factory.fuzzy.FuzzyInteger(2,400) @@ -403,7 +404,7 @@ class IRSGBallotDocEventFactory(BallotDocEventFactory): class Meta: model = IRSGBallotDocEvent - duedate = datetime.datetime.now() + datetime.timedelta(days=14) + duedate = timezone.now() + datetime.timedelta(days=14) ballot_type = factory.SubFactory(BallotTypeFactory, slug='irsg-approve') class BallotPositionDocEventFactory(DocEventFactory): diff --git a/ietf/doc/feeds.py b/ietf/doc/feeds.py index 1169db105f5..cddc3329b62 100644 --- a/ietf/doc/feeds.py +++ b/ietf/doc/feeds.py @@ -10,6 +10,7 @@ from django.urls import reverse as urlreverse from django.template.defaultfilters import truncatewords, truncatewords_html, date as datefilter from django.template.defaultfilters import linebreaks # type: ignore +from django.utils import timezone from django.utils.html import strip_tags from ietf.doc.models import Document, State, LastCallDocEvent, DocEvent @@ -135,7 +136,7 @@ def items(self): if self.year: rfc_events = DocEvent.objects.filter(type='published_rfc',time__year=self.year).order_by('-time') else: - cutoff = datetime.datetime.now() - datetime.timedelta(days=8) + cutoff = timezone.now() - datetime.timedelta(days=8) rfc_events = DocEvent.objects.filter(type='published_rfc',time__gte=cutoff).order_by('-time') results = [(e.doc, e.time) for e in rfc_events] for doc,time in results: diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 0f344f0159c..ddc669ed885 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -10,6 +10,7 @@ from django.utils.html import strip_tags from django.conf import settings from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.encoding import force_text import debug # pyflakes:ignore @@ -418,7 +419,7 @@ def generate_issue_ballot_mail(request, doc, ballot): e = doc.latest_event(LastCallDocEvent, type="sent_last_call") last_call_expires = e.expires if e else None - last_call_has_expired = last_call_expires and last_call_expires < datetime.datetime.now() + last_call_has_expired = last_call_expires and last_call_expires < timezone.now() return render_to_string("doc/mail/issue_iesg_ballot_mail.txt", dict(doc=doc, @@ -437,7 +438,7 @@ def _send_irsg_ballot_email(request, doc, ballot, subject, template): (to, cc) = gather_address_lists('irsg_ballot_issued', doc=doc) sender = 'IESG Secretary ' - ballot_expired = ballot.duedate < datetime.datetime.now() + ballot_expired = ballot.duedate < timezone.now() active_ballot = doc.active_ballot() if active_ballot is None: needed_bps = '' diff --git a/ietf/doc/management/commands/generate_draft_aliases.py b/ietf/doc/management/commands/generate_draft_aliases.py index 796e3db639b..88f4aa98cb6 100755 --- a/ietf/doc/management/commands/generate_draft_aliases.py +++ b/ietf/doc/management/commands/generate_draft_aliases.py @@ -16,6 +16,7 @@ from django.conf import settings from django.core.management.base import BaseCommand +from django.utils import timezone import debug # pyflakes:ignore @@ -101,7 +102,7 @@ class Command(BaseCommand): 'that have seen activity in the last %s years.' % (DEFAULT_YEARS)) def handle(self, *args, **options): - show_since = datetime.datetime.now() - datetime.timedelta(DEFAULT_YEARS*365) + show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365) date = time.strftime("%Y-%m-%d_%H:%M:%S") signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date) diff --git a/ietf/doc/management/commands/generate_draft_bibxml_files.py b/ietf/doc/management/commands/generate_draft_bibxml_files.py index 8bdaa0a8626..21843080689 100644 --- a/ietf/doc/management/commands/generate_draft_bibxml_files.py +++ b/ietf/doc/management/commands/generate_draft_bibxml_files.py @@ -11,6 +11,7 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.template.loader import render_to_string +from django.utils import timezone import debug # pyflakes:ignore @@ -68,7 +69,7 @@ def handle(self, *args, **options): if process_all: doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft') else: - start = datetime.datetime.now() - datetime.timedelta(days=days) + start = timezone.now() - datetime.timedelta(days=days) doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft', time__gte=start) doc_events = doc_events.order_by('time') diff --git a/ietf/doc/models.py b/ietf/doc/models.py index eb66b658a7e..14b207b1be6 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -20,6 +20,7 @@ from django.urls import reverse as urlreverse from django.contrib.contenttypes.models import ContentType from django.conf import settings +from django.utils import timezone from django.utils.encoding import force_text from django.utils.html import mark_safe # type:ignore @@ -87,7 +88,7 @@ class Meta: class DocumentInfo(models.Model): """Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement""" - time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True + time = models.DateTimeField(default=timezone.now) # should probably have auto_now=True type = ForeignKey(DocTypeName, blank=True, null=True) # Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki, External ... title = models.CharField(max_length=255, validators=[validate_no_control_chars, ]) @@ -689,7 +690,7 @@ class DocumentActionHolder(models.Model): """Action holder for a document""" document = ForeignKey('Document') person = ForeignKey(Person) - time_added = models.DateTimeField(default=datetime.datetime.now) + time_added = models.DateTimeField(default=timezone.now) CLEAR_ACTION_HOLDERS_STATES = ['approved', 'ann', 'rfcqueue', 'pub', 'dead'] # draft-iesg state slugs GROUP_ROLES_OF_INTEREST = ['chair', 'techadv', 'editor', 'secr'] @@ -849,7 +850,7 @@ def past_telechat_date(self): def previous_telechat_date(self): "Return the most recent telechat date in the past, if any (even if there's another in the future)" - e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat", telechat_date__lt=datetime.datetime.now()) + e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat", telechat_date__lt=timezone.now()) return e.telechat_date if e else None def request_closed_time(self, review_req): @@ -1219,7 +1220,7 @@ class DocReminder(models.Model): class DocEvent(models.Model): """An occurrence for a document, used for tracking who, when and what.""" - time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened", db_index=True) + time = models.DateTimeField(default=timezone.now, help_text="When the event happened", db_index=True) type = models.CharField(max_length=50, choices=EVENT_TYPES) by = ForeignKey(Person) doc = ForeignKey(Document) @@ -1403,7 +1404,7 @@ class DeletedEvent(models.Model): content_type = ForeignKey(ContentType) json = models.TextField(help_text="Deleted object in JSON format, with attribute names chosen to be suitable for passing into the relevant create method.") by = ForeignKey(Person) - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) def __str__(self): return u"%s by %s %s" % (self.content_type, self.by, self.time) diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py index 5b19ccf67cf..1f5fc065b3f 100644 --- a/ietf/doc/templatetags/ietf_filters.py +++ b/ietf/doc/templatetags/ietf_filters.py @@ -18,6 +18,7 @@ from django.core.cache import cache from django.core.exceptions import ValidationError from django.urls import NoReverseMatch +from django.utils import timezone import debug # pyflakes:ignore @@ -318,7 +319,7 @@ def timesince_days(date): """Returns the number of days since 'date' (relative to now)""" if date.__class__ is not datetime.datetime: date = datetime.datetime(date.year, date.month, date.day) - delta = datetime.datetime.now() - date + delta = timezone.now() - date return delta.days @register.filter @@ -637,19 +638,19 @@ def action_holder_badge(action_holder): >>> action_holder_badge(DocumentActionHolderFactory()) '' - >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=15))) + >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=15))) '' - >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=16))) + >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=16))) ' 16' - >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=30))) + >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=30))) ' 30' >>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit """ age_limit = settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS - age = (datetime.datetime.now() - action_holder.time_added).days + age = (timezone.now() - action_holder.time_added).days if age > age_limit: return mark_safe( ' %d' diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 32ec80acc0f..b15379bd178 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -23,6 +23,7 @@ from django.forms import Form from django.utils.html import escape from django.test import override_settings +from django.utils import timezone from django.utils.text import slugify from tastypie.test import ResourceTestCaseMixin @@ -343,13 +344,13 @@ def test_recent_drafts(self): # Three drafts to show with various warnings drafts = WgDraftFactory.create_batch(3,states=[('draft','active'),('draft-iesg','ad-eval')]) for index, draft in enumerate(drafts): - StateDocEventFactory(doc=draft, state=('draft-iesg','ad-eval'), time=datetime.datetime.now()-datetime.timedelta(days=[1,15,29][index])) + StateDocEventFactory(doc=draft, state=('draft-iesg','ad-eval'), time=timezone.now()-datetime.timedelta(days=[1,15,29][index])) draft.action_holders.set([PersonFactory()]) # And one draft that should not show (with the default of 7 days to view) old = WgDraftFactory() - old.docevent_set.filter(newrevisiondocevent__isnull=False).update(time=datetime.datetime.now()-datetime.timedelta(days=8)) - StateDocEventFactory(doc=old, time=datetime.datetime.now()-datetime.timedelta(days=8)) + old.docevent_set.filter(newrevisiondocevent__isnull=False).update(time=timezone.now()-datetime.timedelta(days=8)) + StateDocEventFactory(doc=old, time=timezone.now()-datetime.timedelta(days=8)) url = urlreverse('ietf.doc.views_search.recent_drafts') r = self.client.get(url) @@ -722,7 +723,7 @@ def test_document_draft(self): replacement = WgDraftFactory( name="draft-ietf-replacement", - time=datetime.datetime.now(), + time=timezone.now(), title="Replacement Draft", stream_id=draft.stream_id, group_id=draft.group_id, abstract=draft.abstract,stream=draft.stream, rev=draft.rev, pages=draft.pages, intended_std_level_id=draft.intended_std_level_id, @@ -1538,7 +1539,7 @@ def test_document_material(self): name = "session-72-mars-1", meeting = Meeting.objects.get(number='72'), group = Group.objects.get(acronym='mars'), - modified = datetime.datetime.now(), + modified = timezone.now(), add_to_schedule=False, ) SchedulingEvent.objects.create( @@ -1568,7 +1569,7 @@ def test_document_ballot(self): type="changed_ballot_position", pos_id="yes", comment="Looks fine to me", - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), balloter=Person.objects.get(user__username="ad"), by=Person.objects.get(name="(System)")) @@ -1602,7 +1603,7 @@ def test_document_ballot(self): type="changed_ballot_position", pos_id="noobj", comment="Still looks okay to me", - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), balloter=Person.objects.get(user__username="ad"), by=Person.objects.get(name="(System)")) @@ -1624,7 +1625,7 @@ def test_document_ballot_popup_unique_anchors_per_doc(self): type="changed_ballot_position", pos_id="yes", comment="Looks fine to me", - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), balloter=Person.objects.get(user__username="ad"), by=Person.objects.get(name="(System)")) @@ -1997,7 +1998,7 @@ def tearDown(self): super().tearDown() def testManagementCommand(self): - a_month_ago = datetime.datetime.now() - datetime.timedelta(30) + a_month_ago = timezone.now() - datetime.timedelta(30) ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person shepherd = PersonFactory() author1 = PersonFactory() diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index 317e4e3a1d0..163a98a46f9 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -12,6 +12,7 @@ from django.test import RequestFactory from django.utils.text import slugify from django.urls import reverse as urlreverse +from django.utils import timezone from ietf.doc.models import (Document, State, DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent) @@ -105,7 +106,7 @@ def test_api_set_position(self): draft = WgDraftFactory(ad=ad) url = urlreverse('ietf.doc.views_ballot.api_set_position') create_ballot_if_not_open(None, draft, ad, 'approve') - ad.user.last_login = datetime.datetime.now() + ad.user.last_login = timezone.now() ad.user.save() apikey = PersonalApiKey.objects.create(endpoint=url, person=ad) @@ -238,9 +239,9 @@ def test_send_ballot_comment(self): doc=draft, rev=draft.rev, type="changed_ballot_position", by=ad, balloter=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"), discuss="This draft seems to be lacking a clearer title?", - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), comment="Test!", - comment_time=datetime.datetime.now()) + comment_time=timezone.now()) url = urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=draft.name, ballot_id=ballot.pk)) @@ -466,7 +467,7 @@ def test_issue_ballot(self): doc=draft, rev=draft.rev, desc='issued last call', - expires = datetime.datetime.now()+datetime.timedelta(days = 1 if case=='future' else -1) + expires = timezone.now()+datetime.timedelta(days = 1 if case=='future' else -1) ) url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "ad", url) @@ -791,7 +792,7 @@ def test_ballot_downref_approve(self): doc=draft, rev=draft.rev, desc='issued last call', - expires = datetime.datetime.now()-datetime.timedelta(days=14) ) + expires = timezone.now()-datetime.timedelta(days=14) ) WriteupDocEvent.objects.create( by=Person.objects.get(name='(System)'), doc=draft, @@ -1117,7 +1118,7 @@ def test_regenerate_last_call(self): class BallotContentTests(TestCase): def test_ballotpositiondocevent_any_email_sent(self): - now = datetime.datetime.now() # be sure event timestamps are at distinct times + now = timezone.now() # be sure event timestamps are at distinct times bpde_with_null_send_email = BallotPositionDocEventFactory( time=now - datetime.timedelta(minutes=30), send_email=None, @@ -1219,7 +1220,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[0], pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=True, ) BallotPositionDocEventFactory( @@ -1227,7 +1228,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[1], pos_id='noobj', comment='Commentary', - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), send_email=True, ) @@ -1237,7 +1238,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[2], pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=False, ) BallotPositionDocEventFactory( @@ -1245,7 +1246,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[3], pos_id='noobj', comment='Commentary', - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), send_email=False, ) @@ -1255,7 +1256,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[4], pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), + discuss_time=timezone.now() - datetime.timedelta(days=1), send_email=True, ) BallotPositionDocEventFactory( @@ -1263,7 +1264,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[4], pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=False, ) BallotPositionDocEventFactory( @@ -1271,7 +1272,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[5], pos_id='noobj', comment='Commentary', - comment_time=datetime.datetime.now() - datetime.timedelta(days=1), + comment_time=timezone.now() - datetime.timedelta(days=1), send_email=True, ) BallotPositionDocEventFactory( @@ -1279,7 +1280,7 @@ def test_document_ballot_content_email_sent(self): balloter=balloters[5], pos_id='noobj', comment='Commentary', - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), send_email=False, ) @@ -1296,7 +1297,7 @@ def test_document_ballot_content_email_sent(self): balloter__plain='plain name1', pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=False, ).balloter send_email_balloter = BallotPositionDocEventFactory( @@ -1304,7 +1305,7 @@ def test_document_ballot_content_email_sent(self): balloter__plain='plain name2', pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=True, ).balloter prev_send_email_balloter = BallotPositionDocEventFactory( @@ -1312,7 +1313,7 @@ def test_document_ballot_content_email_sent(self): balloter__plain='plain name3', pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), + discuss_time=timezone.now() - datetime.timedelta(days=1), send_email=True, ).balloter BallotPositionDocEventFactory( @@ -1320,7 +1321,7 @@ def test_document_ballot_content_email_sent(self): balloter=prev_send_email_balloter, pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=False, ) @@ -1351,7 +1352,7 @@ def test_document_ballot_content_without_send_email_values(self): balloter=balloters[0], pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=None, ) BallotPositionDocEventFactory( @@ -1359,7 +1360,7 @@ def test_document_ballot_content_without_send_email_values(self): balloter=balloters[1], pos_id='noobj', comment='Commentary', - comment_time=datetime.datetime.now(), + comment_time=timezone.now(), send_email=None, ) old_balloter = BallotPositionDocEventFactory( @@ -1367,7 +1368,7 @@ def test_document_ballot_content_without_send_email_values(self): balloter__plain='plain name', # ensure plain name is slugifiable pos_id='discuss', discuss='Discussion text', - discuss_time=datetime.datetime.now(), + discuss_time=timezone.now(), send_email=None, ).balloter diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 61fe2f6c7e3..4d4a9654d40 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -13,6 +13,7 @@ from django.urls import reverse as urlreverse from django.conf import settings +from django.utils import timezone from django.utils.html import escape import debug # pyflakes:ignore @@ -618,7 +619,7 @@ def test_resurrect(self): self.assertEqual(draft.docevent_set.count(), events_before + 1) self.assertEqual(draft.latest_event().type, "completed_resurrect") self.assertEqual(draft.get_state_slug(), "active") - self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) + self.assertTrue(draft.expires >= timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue('Resurrection Completed' in outbox[-1]['Subject']) self.assertTrue('iesg-secretary' in outbox[-1]['To']) @@ -659,7 +660,7 @@ def test_warn_expirable_drafts(self): # hack into expirable state draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - draft.expires = datetime.datetime.now() + datetime.timedelta(days=10) + draft.expires = timezone.now() + datetime.timedelta(days=10) draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")]) self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 1) @@ -698,7 +699,7 @@ def test_expire_drafts(self): # hack into expirable state draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - draft.expires = datetime.datetime.now() + draft.expires = timezone.now() draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")]) self.assertEqual(len(list(get_expired_drafts())), 1) @@ -741,7 +742,7 @@ def test_expire_drafts(self): draft.delete() - rgdraft = RgDraftFactory(expires=datetime.datetime.now()) + rgdraft = RgDraftFactory(expires=timezone.now()) self.assertEqual(len(list(get_expired_drafts())), 1) for slug in ('iesg-rev','irsgpoll'): rgdraft.set_state(State.objects.get(type_id='draft-stream-irtf',slug=slug)) @@ -791,7 +792,7 @@ def test_clean_up_draft_files(self): # expire draft draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) - draft.expires = datetime.datetime.now() - datetime.timedelta(days=1) + draft.expires = timezone.now() - datetime.timedelta(days=1) draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")]) e = DocEvent(doc=draft, rev=draft.rev, type= "expired_document", time=draft.expires, @@ -824,7 +825,7 @@ def test_expire_last_call(self): e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e.text = "Last call sent" - e.expires = datetime.datetime.now() + datetime.timedelta(days=14) + e.expires = timezone.now() + datetime.timedelta(days=14) e.save() self.assertEqual(len(list(get_expired_last_calls())), 0) @@ -832,7 +833,7 @@ def test_expire_last_call(self): # test expired e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e.text = "Last call sent" - e.expires = datetime.datetime.now() + e.expires = timezone.now() e.save() drafts = list(get_expired_last_calls()) @@ -866,7 +867,7 @@ def test_expire_last_call_with_downref(self): e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e.text = "Last call sent" e.desc = "Blah, blah, blah.\n\nThis document makes the following downward references (downrefs):\n ** Downref: Normative reference to an Experimental RFC: RFC 4764" - e.expires = datetime.datetime.now() + e.expires = timezone.now() e.save() drafts = list(get_expired_last_calls()) @@ -1710,7 +1711,7 @@ def test_set_initial_state(self): self.assertEqual(draft.docevent_set.count() - events_before, 2) reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s") self.assertEqual(len(reminder), 1) - due = datetime.datetime.now() + datetime.timedelta(weeks=10) + due = timezone.now() + datetime.timedelta(weeks=10) self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1)) self.assertEqual(len(outbox), 1) self.assertTrue("state changed" in outbox[0]["Subject"].lower()) @@ -1755,7 +1756,7 @@ def test_set_state(self): self.assertEqual(draft.docevent_set.count() - events_before, 2) reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s") self.assertEqual(len(reminder), 1) - due = datetime.datetime.now() + datetime.timedelta(weeks=10) + due = timezone.now() + datetime.timedelta(weeks=10) self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1)) self.assertEqual(len(outbox), 1) self.assertTrue("state changed" in outbox[0]["Subject"].lower()) @@ -1806,7 +1807,7 @@ def setUp(self): name="draft-test-base-b", title="Base B", group=mars_wg, - expires = datetime.datetime.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), + expires = timezone.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), ) p = PersonFactory(name="baseb_author") e = Email.objects.create(address="baseb_author@example.com", person=p, origin=p.user.username) diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py index 1e922197d52..05bbc2078b6 100644 --- a/ietf/doc/tests_material.py +++ b/ietf/doc/tests_material.py @@ -4,7 +4,6 @@ import os import shutil -import datetime import io from pathlib import Path @@ -14,6 +13,7 @@ from django.conf import settings from django.urls import reverse as urlreverse +from django.utils import timezone from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent from ietf.group.factories import RoleFactory @@ -155,7 +155,7 @@ def test_revise(self): name = "session-42-mars-1", meeting = Meeting.objects.get(number='42'), group = Group.objects.get(acronym='mars'), - modified = datetime.datetime.now(), + modified = timezone.now(), ) SchedulingEvent.objects.create( session=session, diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py index 7e902514d57..efe5b7b06f3 100644 --- a/ietf/doc/tests_review.py +++ b/ietf/doc/tests_review.py @@ -14,6 +14,7 @@ from django.apps import apps from django.urls import reverse as urlreverse from django.conf import settings +from django.utils import timezone from pyquery import PyQuery @@ -67,7 +68,7 @@ def test_request_review(self): RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') RoleFactory(group=review_team3,person__user__username='reviewsecretary3',person__user__email='reviewsecretary3@example.com',name_id='secr') - req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name }) @@ -145,7 +146,7 @@ def test_doc_page(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted') # move the review request to a doubly-replaced document to @@ -166,7 +167,7 @@ def test_review_request(self): doc = WgDraftFactory(group__acronym='mars',rev='01', authors=[author]) review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) @@ -195,7 +196,7 @@ def test_close_request(self): rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') RoleFactory(group=review_team,person__user__username='reviewsecretary2',person__user__email='reviewsecretary2@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request=review_req, state_id='accepted', reviewer=rev_role.person.email_set.first()) close_url = urlreverse('ietf.doc.views_review.close_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) @@ -260,7 +261,7 @@ def test_assign_reviewer(self): # previous review req = ReviewRequestFactory( - time=datetime.datetime.now() - datetime.timedelta(days=100), + time=timezone.now() - datetime.timedelta(days=100), requested_by=Person.objects.get(name="(System)"), doc=doc, type_id='early', @@ -372,7 +373,7 @@ def test_accept_reviewer_assignment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned', reviewer=rev_role.person.email_set.first()) url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) @@ -395,7 +396,7 @@ def test_reject_reviewer_assignment(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request = review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted') reject_url = urlreverse('ietf.doc.views_review.reject_reviewer_assignment', kwargs={ "name": doc.name, "assignment_id": assignment.pk }) @@ -495,7 +496,7 @@ def test_search_mail_archive(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted') # test URL construction @@ -587,7 +588,7 @@ def setup_complete_review_test(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request=review_req, state_id='accepted', reviewer=rev_role.person.email_set.first()) for r in ReviewResultName.objects.filter(slug__in=("issues", "ready")): review_req.team.reviewteamsettings.review_results.add(r) @@ -699,7 +700,7 @@ def test_complete_review_enter_content(self): assignment = reload_db_objects(assignment) self.assertEqual(assignment.state_id, "completed") # Completed time should be close to now, but will not be exactly, so check within 10s margin - completed_time_diff = datetime.datetime.now() - assignment.completed_on + completed_time_diff = timezone.now() - assignment.completed_on self.assertLess(completed_time_diff, datetime.timedelta(seconds=10)) with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f: @@ -739,7 +740,7 @@ def test_complete_review_enter_content_by_secretary(self): # - the event logging when the change when it was entered, i.e. very close to now. # - the completion of the review, set to the provided date/time events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-time') - event0_time_diff = datetime.datetime.now() - events[0].time + event0_time_diff = timezone.now() - events[0].time self.assertLess(event0_time_diff, datetime.timedelta(seconds=10)) self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14)) @@ -985,7 +986,7 @@ def test_revise_review_enter_content(self): self.assertEqual(assignment.state_id, "completed") # The revision event time should be the date the revision was submitted, i.e. not backdated event1 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent) - event_time_diff = datetime.datetime.now() - event1.time + event_time_diff = timezone.now() - event1.time self.assertLess(event_time_diff, datetime.timedelta(seconds=10)) self.assertTrue('revised' in event1.desc.lower()) @@ -1012,7 +1013,7 @@ def test_revise_review_enter_content(self): assignment = reload_db_objects(assignment) self.assertEqual(assignment.review.rev, "01") event2 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent) - event_time_diff = datetime.datetime.now() - event2.time + event_time_diff = timezone.now() - event2.time self.assertLess(event_time_diff, datetime.timedelta(seconds=10)) # Ensure that a new event was created for the new revision (#2590) self.assertNotEqual(event1.id, event2.id) @@ -1024,7 +1025,7 @@ def test_edit_comment(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.edit_comment', kwargs={ "name": doc.name, "request_id": review_req.pk }) @@ -1046,7 +1047,7 @@ def test_edit_deadline(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') - review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20)) + review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.edit_deadline', kwargs={ "name": doc.name, "request_id": review_req.pk }) diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index be5ba44926e..0d48476f060 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -18,6 +18,7 @@ from django.contrib import messages from django.forms import ValidationError from django.template.loader import render_to_string +from django.utils import timezone from django.utils.html import escape from django.urls import reverse as urlreverse @@ -825,7 +826,7 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, com cc.update(other_addrs.cc) RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete() if not RelatedDocument.objects.filter(target=d, relationship=relationship): - s = 'active' if d.document.expires > datetime.datetime.now() else 'expired' + s = 'active' if d.document.expires > timezone.now() else 'expired' d.document.set_state(State.objects.get(type='draft', slug=s)) for d in new_replaces: @@ -1136,7 +1137,7 @@ def add_markup(path, doc, lines): lines[i] = line return lines # - now = datetime.datetime.now() + now = timezone.now() draft_state = doc.get_state('draft') block = '' meta = {} diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py index ce95521063b..43982deabc0 100644 --- a/ietf/doc/utils_charter.py +++ b/ietf/doc/utils_charter.py @@ -11,6 +11,7 @@ from django.conf import settings from django.urls import reverse as urlreverse from django.template.loader import render_to_string +from django.utils import timezone from django.utils.encoding import smart_text, force_text import debug # pyflakes:ignore @@ -73,7 +74,7 @@ def change_group_state_after_charter_approval(group, by): save_group_in_history(group) group.state = new_state - group.time = datetime.datetime.now() + group.time = timezone.now() group.save() # create an event for the group state change, too @@ -132,7 +133,7 @@ def historic_milestones_for_charter(charter, rev): # revision (when approving a charter) just_before_next_rev = e[0].time - datetime.timedelta(seconds=5) else: - just_before_next_rev = datetime.datetime.now() + just_before_next_rev = timezone.now() res = [] if hasattr(charter, 'chartered_group'): @@ -197,7 +198,7 @@ def derive_new_work_text(review_text,group): return smart_text(m.as_string()) def default_review_text(group, charter, by): - now = datetime.datetime.now() + now = timezone.now() addrs = gather_address_lists('charter_external_review',group=group).as_strings(compact=False) e1 = WriteupDocEvent(doc=charter, rev=charter.rev, by=by) diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py index c2b88ac4730..3f85a19cebd 100644 --- a/ietf/doc/views_charter.py +++ b/ietf/doc/views_charter.py @@ -16,6 +16,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.utils import timezone from django.utils.encoding import force_text from django.utils.html import escape @@ -77,7 +78,7 @@ def change_state(request, name, option=None): chartering_type = get_chartering_type(charter) initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") - if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering": + if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < timezone.now()) or chartering_type == "rechartering": initial_review = None by = request.user.person @@ -183,7 +184,7 @@ def change_state(request, name, option=None): if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0: e = InitialReviewDocEvent(type="initial_review", by=by, doc=charter, rev=charter.rev) - e.expires = datetime.datetime.now() + datetime.timedelta(weeks=clean["initial_time"]) + e.expires = timezone.now() + datetime.timedelta(weeks=clean["initial_time"]) e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d") e.save() @@ -506,7 +507,7 @@ def review_announcement_text(request, name): existing_new_work.type = "changed_new_work_text" existing_new_work.desc = "%s review text was changed" % group.type.name existing_new_work.text = derive_new_work_text(existing.text,group) - existing_new_work.time = datetime.datetime.now() + existing_new_work.time = timezone.now() form = ReviewAnnouncementTextForm(initial=dict(announcement_text=escape(existing.text),new_work_text=escape(existing_new_work.text))) @@ -514,7 +515,7 @@ def review_announcement_text(request, name): form = ReviewAnnouncementTextForm(request.POST) if "save_text" in request.POST and form.is_valid(): - now = datetime.datetime.now() + now = timezone.now() events = [] t = form.cleaned_data['announcement_text'] diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 3d8567e8900..05f573712db 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -19,6 +19,7 @@ from django.template.loader import render_to_string from django.forms.utils import ErrorList from django.template.defaultfilters import pluralize +from django.utils import timezone import debug # pyflakes:ignore @@ -857,7 +858,7 @@ def resurrect(request, name): events.append(e) doc.set_state(State.objects.get(used=True, type="draft", slug="active")) - doc.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) + doc.expires = timezone.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) doc.save_with_history(events) restore_draft_file(request, doc) diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py index c7d8e58046c..19c822ec976 100644 --- a/ietf/doc/views_review.py +++ b/ietf/doc/views_review.py @@ -10,7 +10,9 @@ import requests import email.utils +from django.utils import timezone from django.utils.http import is_safe_url + from simple_history.utils import update_change_reason import debug # pyflakes:ignore @@ -117,7 +119,7 @@ def request_review(request, name): if not can_request_review_of_doc(request.user, doc): permission_denied(request, "You do not have permission to perform this action") - now = datetime.datetime.now() + now = timezone.now() lc_ends = None e = doc.latest_event(LastCallDocEvent, type="sent_last_call") @@ -364,7 +366,7 @@ def reject_reviewer_assignment(request, name, assignment_id): if form.is_valid(): # reject the assignment review_assignment.state = ReviewAssignmentStateName.objects.get(slug="rejected") - review_assignment.completed_on = datetime.datetime.now() + review_assignment.completed_on = timezone.now() review_assignment.save() descr = "Assignment of request for {} review by {} to {} was rejected".format( @@ -731,13 +733,13 @@ def complete_review(request, name, assignment_id=None, acronym=None): review_request=review_request, state_id='assigned', reviewer=form.cleaned_data['reviewer'].role_email('reviewer', group=team), - assigned_on=datetime.datetime.now(), + assigned_on=timezone.now(), review = review, ) review.rev = "00" if not review.rev else "{:02}".format(int(review.rev) + 1) review.title = "{} Review of {}-{}".format(assignment.review_request.type.name, assignment.review_request.doc.name, form.cleaned_data["reviewed_rev"]) - review.time = datetime.datetime.now() + review.time = timezone.now() if review_submission == "link": review.external_url = form.cleaned_data['review_url'] @@ -764,7 +766,7 @@ def complete_review(request, name, assignment_id=None, acronym=None): with io.open(filename, 'w', encoding='utf-8') as destination: destination.write(content) - completion_datetime = datetime.datetime.now() + completion_datetime = timezone.now() if "completion_date" in form.cleaned_data: completion_datetime = datetime.datetime.combine(form.cleaned_data["completion_date"], form.cleaned_data.get("completion_time") or datetime.time.min) @@ -799,7 +801,7 @@ def complete_review(request, name, assignment_id=None, acronym=None): close_event.by = request.user.person close_event.desc = desc close_event.state = assignment.state - close_event.time = datetime.datetime.now() + close_event.time = timezone.now() close_event.save() # If the completion date is different, record when the initial review was made too. diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index 7265964e155..39ff682e30a 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -44,6 +44,7 @@ from django.db.models import Q from django.http import Http404, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect, QueryDict from django.shortcuts import render +from django.utils import timezone from django.utils.cache import _generate_cache_key # type: ignore @@ -387,7 +388,7 @@ def ad_dashboard_sort_key(doc): ageseconds = 0 changetime= doc.latest_event(type='changed_document') if changetime: - ad = (datetime.datetime.now()-doc.latest_event(type='changed_document').time) + ad = (timezone.now()-doc.latest_event(type='changed_document').time) ageseconds = (ad.microseconds + (ad.seconds + ad.days * 24 * 3600) * 10**6) / 10**6 return "1%d%s%s%010d" % (state[0].order,seed,doc.type.slug,ageseconds) @@ -495,7 +496,7 @@ def recent_drafts(request, days=7): cache_key = f'recentdraftsview{days}' cached_val = slowcache.get(cache_key) if not cached_val: - since = datetime.datetime.now()-datetime.timedelta(days=days) + since = timezone.now()-datetime.timedelta(days=days) state = State.objects.get(type='draft', slug='active') events = NewRevisionDocEvent.objects.filter(time__gt=since) names = [ e.doc.name for e in events ] diff --git a/ietf/group/management/commands/generate_group_aliases.py b/ietf/group/management/commands/generate_group_aliases.py index 50802b3cf94..132fef753f1 100755 --- a/ietf/group/management/commands/generate_group_aliases.py +++ b/ietf/group/management/commands/generate_group_aliases.py @@ -15,6 +15,7 @@ from django.conf import settings from django.core.management.base import BaseCommand +from django.utils import timezone import debug # pyflakes:ignore @@ -39,7 +40,7 @@ class Command(BaseCommand): 'have seen activity in the last %s years.' % (DEFAULT_YEARS)) def handle(self, *args, **options): - show_since = datetime.datetime.now() - datetime.timedelta(DEFAULT_YEARS*365) + show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365) date = time.strftime("%Y-%m-%d_%H:%M:%S") signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date) diff --git a/ietf/group/models.py b/ietf/group/models.py index d598d862dc0..344cfe0f89f 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -13,6 +13,7 @@ from django.db import models from django.db.models.deletion import CASCADE, PROTECT from django.dispatch import receiver +from django.utils import timezone import debug # pyflakes:ignore @@ -27,7 +28,7 @@ class GroupInfo(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) name = models.CharField(max_length=80) state = ForeignKey(GroupStateName, null=True) type = ForeignKey(GroupTypeName, null=True) @@ -343,7 +344,7 @@ def __str__(self): class GroupEvent(models.Model): """An occurrence for a group, used for tracking who, when and what.""" group = ForeignKey(Group) - time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") + time = models.DateTimeField(default=timezone.now, help_text="When the event happened") type = models.CharField(max_length=50, choices=GROUP_EVENT_CHOICES) by = ForeignKey(Person) desc = models.TextField() diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 74995e60cbc..4281a0e23c2 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -13,6 +13,7 @@ from django.urls import reverse as urlreverse from django.db.models import Q from django.test import Client +from django.utils import timezone import debug # pyflakes:ignore @@ -143,8 +144,8 @@ def tearDown(self): super().tearDown() def testManagementCommand(self): - a_month_ago = datetime.datetime.now() - datetime.timedelta(30) - a_decade_ago = datetime.datetime.now() - datetime.timedelta(3650) + a_month_ago = timezone.now() - datetime.timedelta(30) + a_decade_ago = timezone.now() - datetime.timedelta(3650) role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active') area = role1.group ad = role1.person diff --git a/ietf/group/tests_review.py b/ietf/group/tests_review.py index ee4ae96fe56..924f6fadee6 100644 --- a/ietf/group/tests_review.py +++ b/ietf/group/tests_review.py @@ -8,6 +8,7 @@ from pyquery import PyQuery from django.urls import reverse as urlreverse +from django.utils import timezone from ietf.review.policies import get_reviewer_queue_policy from ietf.utils.test_utils import login_testing_unauthorized, TestCase, reload_db_objects @@ -131,7 +132,7 @@ def test_suggested_review_requests_on_lc_and_telechat(self): doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True)) LastCallDocEvent.objects.create( doc=doc, - expires=datetime.datetime.now() + datetime.timedelta(days=365), + expires=timezone.now() + datetime.timedelta(days=365), by=Person.objects.get(name="(System)"), rev=doc.rev ) @@ -429,7 +430,7 @@ def test_manage_review_requests_assign_suggested_reviews(self): doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True)) LastCallDocEvent.objects.create( doc=doc, - expires=datetime.datetime.now() + datetime.timedelta(days=365), + expires=timezone.now() + datetime.timedelta(days=365), by=Person.objects.get(name="(System)"), rev=doc.rev ) diff --git a/ietf/group/views.py b/ietf/group/views.py index 88267e78835..3ebc7fa4c6e 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -54,6 +54,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.template.loader import render_to_string from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.html import escape from django.views.decorators.cache import cache_page, cache_control @@ -567,7 +568,7 @@ def all_status(request): if e: wg_reports.append(e) - wg_reports.sort(key=lambda x: (x.group.parent.acronym,datetime.datetime.now()-x.time)) + wg_reports.sort(key=lambda x: (x.group.parent.acronym,timezone.now()-x.time)) rg_reports = [] for rg in rgs: @@ -767,7 +768,7 @@ def email_aliases(request, acronym=None, group_type=None): def meetings(request, acronym=None, group_type=None): group = get_group_or_404(acronym,group_type) if acronym else None - four_years_ago = datetime.datetime.now()-datetime.timedelta(days=4*365) + four_years_ago = timezone.now()-datetime.timedelta(days=4*365) sessions = add_event_info_to_session_qs( group.session_set.filter( @@ -931,7 +932,7 @@ def diff(attr, name): try: group = Group.objects.get(acronym=clean["acronym"]) save_group_in_history(group) - group.time = datetime.datetime.now() + group.time = timezone.now() group.save() except Group.DoesNotExist: group = Group.objects.create(name=clean["name"], @@ -1030,7 +1031,7 @@ def diff(attr, name): ) )) - group.time = datetime.datetime.now() + group.time = timezone.now() if changes and not new_group: for attr, new, desc in changes: @@ -1468,7 +1469,7 @@ def reviewer_overview(request, acronym, group_type=None): int(math.ceil(d.assignment_to_closure_days)) if d.assignment_to_closure_days is not None else None)) if d.state in ["completed", "completed_in_time", "completed_late"]: if d.assigned_time is not None: - delta = datetime.datetime.now() - d.assigned_time + delta = timezone.now() - d.assigned_time if d.assignment_to_closure_days is not None: days = int(delta.days - d.assignment_to_closure_days) if days_since > days: days_since = days diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py index 3d17b197ca7..b641f5c6792 100644 --- a/ietf/idindex/index.py +++ b/ietf/idindex/index.py @@ -296,6 +296,6 @@ def id_index_txt(with_abstracts=False): return render_to_string("idindex/id_index.txt", { 'groups': groups, - 'time': datetime.datetime.now(pytz.UTC).strftime("%Y-%m-%d %H:%M:%S %Z"), + 'time': datetime.datetime.now(pytz.utc).strftime("%Y-%m-%d %H:%M:%S %Z"), 'with_abstracts': with_abstracts, }) diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py index 413e3a4ed63..f207fa56212 100644 --- a/ietf/idindex/tests.py +++ b/ietf/idindex/tests.py @@ -7,6 +7,7 @@ from pathlib import Path from django.conf import settings +from django.utils import timezone import debug # pyflakes:ignore @@ -120,7 +121,7 @@ def get_fields(content): draft.set_state(State.objects.get(type="draft", slug="active")) draft.set_state(State.objects.get(type="draft-iesg", slug="lc")) - e = LastCallDocEvent.objects.create(doc=draft, rev=draft.rev, type="sent_last_call", expires=datetime.datetime.now() + datetime.timedelta(days=14), by=draft.ad) + e = LastCallDocEvent.objects.create(doc=draft, rev=draft.rev, type="sent_last_call", expires=timezone.now() + datetime.timedelta(days=14), by=draft.ad) t = get_fields(all_id2_txt()) self.assertEqual(t[11], e.expires.strftime("%Y-%m-%d")) diff --git a/ietf/ietfauth/management/commands/send_apikey_usage_emails.py b/ietf/ietfauth/management/commands/send_apikey_usage_emails.py index 4aa4e55248c..d3fce1bcc2b 100644 --- a/ietf/ietfauth/management/commands/send_apikey_usage_emails.py +++ b/ietf/ietfauth/management/commands/send_apikey_usage_emails.py @@ -8,6 +8,7 @@ from django.conf import settings from django.core.management.base import BaseCommand +from django.utils import timezone import debug # pyflakes:ignore @@ -37,7 +38,7 @@ def handle(self, *filenames, **options): keys = PersonalApiKey.objects.filter(valid=True) for key in keys: - earliest = datetime.datetime.now() - datetime.timedelta(days=days) + earliest = timezone.now() - datetime.timedelta(days=days) events = PersonApiKeyEvent.objects.filter(key=key, time__gt=earliest) count = events.count() events = events[:32] diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 14c25228300..06c7637a2a8 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -28,6 +28,7 @@ from django.contrib.auth.models import User from django.conf import settings from django.template.loader import render_to_string +from django.utils import timezone import debug # pyflakes:ignore @@ -740,11 +741,11 @@ def test_apikey_errors(self): self.assertContains(r, 'Invalid apikey', status_code=403) # too long since regular login - person.user.last_login = datetime.datetime.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS+1) + person.user.last_login = timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS+1) person.user.save() r = self.client.post(key.endpoint, {'apikey':key.hash(), 'dummy':'dummy',}) self.assertContains(r, 'Too long since last regular login', status_code=400) - person.user.last_login = datetime.datetime.now() + person.user.last_login = timezone.now() person.user.save() # endpoint mismatch @@ -773,7 +774,7 @@ def test_send_apikey_report(self): # apikey usage will be registered) count = 2 # avoid usage across dates - if datetime.datetime.now().time() > datetime.time(hour=23, minute=59, second=58): + if timezone.now().time() > datetime.time(hour=23, minute=59, second=58): time.sleep(2) for i in range(count): for key in person.apikeys.all(): diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py index 282f335684f..2eb588b8bc6 100644 --- a/ietf/ipr/models.py +++ b/ietf/ipr/models.py @@ -2,11 +2,10 @@ # -*- coding: utf-8 -*- -import datetime - from django.conf import settings from django.db import models from django.urls import reverse +from django.utils import timezone from ietf.doc.models import DocAlias, DocEvent from ietf.name.models import DocRelationshipName,IprDisclosureStateName,IprLicenseTypeName,IprEventTypeName @@ -220,7 +219,7 @@ def response_past_due(self): """Returns true if it's beyond the response_due date and no response has been received""" qs = IprEvent.objects.filter(disclosure=self.disclosure,in_reply_to=self.message) - if not qs and datetime.datetime.now().date() > self.response_due.date(): + if not qs and timezone.now().date() > self.response_due.date(): return True else: return False diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py index 5b389683266..8fb11c39b99 100644 --- a/ietf/ipr/tests.py +++ b/ietf/ipr/tests.py @@ -9,6 +9,7 @@ from urllib.parse import quote, urlparse from django.urls import reverse as urlreverse +from django.utils import timezone import debug # pyflakes:ignore @@ -636,7 +637,7 @@ def test_process_response_email(self): message_string.format( to=addrs.to, cc=addrs.cc, - date=datetime.datetime.now().ctime() + date=timezone.now().ctime() ) ) self.assertIsNone(result) @@ -646,7 +647,7 @@ def test_process_response_email(self): From: joe@test.com Date: {} Subject: test -""".format(reply_to, datetime.datetime.now().ctime()) +""".format(reply_to, timezone.now().ctime()) result = process_response_email(message_string) self.assertIsInstance(result, Message) @@ -660,7 +661,7 @@ def test_process_response_email_with_invalid_encoding(self): From: joe@test.com Date: {} Subject: test -""".format(reply_to, datetime.datetime.now().ctime()) +""".format(reply_to, timezone.now().ctime()) message_bytes = message_string.encode('utf8') + b'\nInvalid stuff: \xfe\xff\n' result = process_response_email(message_bytes) self.assertIsInstance(result, Message) @@ -676,7 +677,7 @@ def test_process_response_email_uninteresting_with_invalid_encoding(self): message_bytes = message_string.format( to=addrs.to, cc=addrs.cc, - date=datetime.datetime.now().ctime(), + date=timezone.now().ctime(), ).encode('utf8') + b'\nInvalid stuff: \xfe\xff\n' result = process_response_email(message_bytes) self.assertIsNone(result) diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index e290bf14d31..458186949e5 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -14,6 +14,7 @@ from django.shortcuts import render, get_object_or_404, redirect from django.template.loader import render_to_string from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.html import escape import debug # pyflakes:ignore @@ -587,7 +588,7 @@ def notify(request, id, type): type_id = form.cleaned_data['type'], by = request.user.person, disclosure = ipr, - response_due = datetime.datetime.now().date() + datetime.timedelta(days=30), + response_due = timezone.now().date() + datetime.timedelta(days=30), message = message, ) messages.success(request,'Notifications sent') diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index 4e4eaa136c3..d21484ec9bf 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -14,6 +14,8 @@ from django.contrib.auth.models import User from django.urls import reverse as urlreverse from django.db.models import Q +from django.utils import timezone + from io import StringIO from pyquery import PyQuery @@ -385,7 +387,7 @@ def test_approval_process(self): def test_edit_liaison(self): liaison = LiaisonStatementFactory(deadline=datetime.date.today()+datetime.timedelta(days=1)) - LiaisonStatementEventFactory(statement=liaison,type_id='submitted', time=datetime.datetime.now()-datetime.timedelta(days=1)) + LiaisonStatementEventFactory(statement=liaison,type_id='submitted', time=timezone.now()-datetime.timedelta(days=1)) LiaisonStatementEventFactory(statement=liaison,type_id='posted') from_group = liaison.from_groups.first() to_group = liaison.to_groups.first() diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 19aff9e2943..2274e2497c3 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -24,6 +24,7 @@ from django.db.models.functions import Coalesce from django.conf import settings from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.text import slugify from django.utils.safestring import mark_safe @@ -1271,7 +1272,7 @@ def notes_url(self): class SchedulingEvent(models.Model): session = ForeignKey(Session) - time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") + time = models.DateTimeField(default=timezone.now, help_text="When the event happened") status = ForeignKey(SessionStatusName) by = ForeignKey(Person) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index c984e1f15f8..9e4d4baac16 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -29,8 +29,8 @@ from django.db.models import F, Max from django.http import QueryDict, FileResponse from django.template import Context, Template +from django.utils import timezone from django.utils.text import slugify -from django.utils.timezone import now import debug # pyflakes:ignore @@ -1481,7 +1481,7 @@ def _decode_json_response(self, r): @staticmethod def _right_now_in(tzname): - right_now = now().astimezone(pytz.timezone(tzname)) + right_now = timezone.now().astimezone(pytz.timezone(tzname)) if not settings.USE_TZ: right_now = right_now.replace(tzinfo=None) return right_now @@ -4632,7 +4632,7 @@ 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 = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) duration = datetime.timedelta(hours=3) remote_instructions = 'Use webex' @@ -4703,7 +4703,7 @@ 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 = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) duration = datetime.timedelta(hours=3) city = 'San Francisco' @@ -4751,7 +4751,7 @@ def test_interim_request_multi_day(self): make_meeting_test_data() date = datetime.date.today() + datetime.timedelta(days=30) date2 = date + datetime.timedelta(days=1) - time = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) dt2 = datetime.datetime.combine(date2, time) duration = datetime.timedelta(hours=3) @@ -4817,7 +4817,7 @@ def test_interim_request_multi_day_non_consecutive(self): make_meeting_test_data() date = datetime.date.today() + datetime.timedelta(days=30) date2 = date + datetime.timedelta(days=2) - time = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) group = Group.objects.get(acronym='mars') city = 'San Francisco' country = 'US' @@ -4885,7 +4885,7 @@ def test_interim_request_series(self): if date.year != date2.year: date += datetime.timedelta(days=1) date2 += datetime.timedelta(days=1) - time = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) dt2 = datetime.datetime.combine(date2, time) duration = datetime.timedelta(hours=3) @@ -6510,7 +6510,7 @@ def do_request_interim(self, url, group, user, meeting_count): q = PyQuery(r.content) self.assertTrue(q('#id_group option[value="%d"]'%group.pk)) date = datetime.date.today() + datetime.timedelta(days=30+meeting_count) - time = datetime.datetime.now().time().replace(microsecond=0,second=0) + time = timezone.now().time().replace(microsecond=0,second=0) remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index 0d1a5627028..f6c1800f10a 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -12,6 +12,7 @@ from django.conf import settings from django.contrib import messages from django.template.loader import render_to_string +from django.utils import timezone from django.utils.encoding import smart_text import debug # pyflakes:ignore @@ -511,7 +512,7 @@ def swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, targe if max_overlap > datetime.timedelta(minutes=5): for a in lts_assignments: a.timeslot = most_overlapping_rts - a.modified = datetime.datetime.now() + a.modified = timezone.now() a.save() swapped = True diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index adc24eebffe..35aeded96bd 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -36,10 +36,10 @@ from django.forms.models import modelform_factory, inlineformset_factory from django.template import TemplateDoesNotExist from django.template.loader import render_to_string +from django.utils import timezone from django.utils.encoding import force_str from django.utils.functional import curry from django.utils.text import slugify -from django.utils.timezone import now from django.views.decorators.cache import cache_page from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt from django.views.generic import RedirectView @@ -128,7 +128,7 @@ def materials(request, num=None): cut_off_date = meeting.get_submission_cut_off_date() cor_cut_off_date = meeting.get_submission_correction_date() now = datetime.date.today() - old = datetime.datetime.now() - datetime.timedelta(days=1) + old = timezone.now() - datetime.timedelta(days=1) if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET: pass elif now > cor_cut_off_date: @@ -442,7 +442,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): lock_time = settings.MEETING_SESSION_LOCK_TIME def timeslot_locked(ts): - meeting_now = now().astimezone(pytz.timezone(meeting.time_zone)) + meeting_now = timezone.now().astimezone(pytz.timezone(meeting.time_zone)) if not settings.USE_TZ: meeting_now = meeting_now.replace(tzinfo=None) return schedule.is_official and (ts.time - meeting_now < lock_time) @@ -785,7 +785,7 @@ def _json_response(success, status=None, **extra_data): timeslot=old_timeslot, ) - existing_assignments.update(timeslot=timeslot, modified=datetime.datetime.now()) + existing_assignments.update(timeslot=timeslot, modified=timezone.now()) else: SchedTimeSessAssignment.objects.create( session=session, @@ -1560,7 +1560,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc="" "updated": updated, "filter_categories": filter_organizer.get_filter_categories(), "non_area_keywords": filter_organizer.get_non_area_keywords(), - "now": datetime.datetime.now().astimezone(pytz.UTC), + "now": datetime.datetime.now(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, @@ -2271,7 +2271,7 @@ def session_details(request, num, acronym): 'can_manage_materials' : can_manage, 'can_view_request': can_view_request, 'thisweek': datetime.date.today()-datetime.timedelta(days=7), - 'now': datetime.datetime.now(), + 'now': timezone.now(), 'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False, }) @@ -3432,7 +3432,7 @@ def upcoming(request): 'menu_actions': actions, 'menu_entries': menu_entries, 'selected_menu_entry': selected_menu_entry, - 'now': datetime.datetime.now(), + 'now': timezone.now(), 'use_codimd': True if datetime.date.today()>=settings.MEETING_USES_CODIMD_DATE else False, }) diff --git a/ietf/message/management/commands/show_messages.py b/ietf/message/management/commands/show_messages.py index b232da481ef..8741b2129a0 100644 --- a/ietf/message/management/commands/show_messages.py +++ b/ietf/message/management/commands/show_messages.py @@ -6,6 +6,7 @@ import datetime from django.core.management.base import BaseCommand +from django.utils import timezone import debug # pyflakes:ignore @@ -25,7 +26,7 @@ class Command(BaseCommand): """ def add_arguments(self, parser): - default_start = datetime.datetime.now() - datetime.timedelta(days=14) + default_start = timezone.now() - datetime.timedelta(days=14) parser.add_argument( '-t', '--start', '--from', type=str, default=default_start.strftime('%Y-%m-%d %H:%M'), help='Limit the list to messages saved after the given time (default %(default)s).', diff --git a/ietf/message/models.py b/ietf/message/models.py index 01162a4b63c..fe2c8d325f2 100644 --- a/ietf/message/models.py +++ b/ietf/message/models.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- -import datetime import email.utils from django.db import models +from django.utils import timezone import debug # pyflakes:ignore @@ -17,7 +17,7 @@ from ietf.utils.mail import get_email_addresses_from_text class Message(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) by = ForeignKey(Person) subject = models.CharField(max_length=255) @@ -62,7 +62,7 @@ def __str__(self): class SendQueue(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) by = ForeignKey(Person) message = ForeignKey(Message) diff --git a/ietf/message/tests.py b/ietf/message/tests.py index 4ab7906997a..c1f22b3d0c3 100644 --- a/ietf/message/tests.py +++ b/ietf/message/tests.py @@ -5,6 +5,7 @@ import datetime from django.urls import reverse as urlreverse +from django.utils import timezone import debug # pyflakes:ignore @@ -87,7 +88,7 @@ def test_send_plain_announcement(self): q = SendQueue.objects.create( by=Person.objects.get(name="(System)"), message=msg, - send_at=datetime.datetime.now() + datetime.timedelta(hours=12) + send_at=timezone.now() + datetime.timedelta(hours=12) ) mailbox_before = len(outbox) @@ -113,7 +114,7 @@ def test_send_mime_announcement(self): q = SendQueue.objects.create( by=Person.objects.get(name="(System)"), message=msg, - send_at=datetime.datetime.now() + datetime.timedelta(hours=12) + send_at=timezone.now() + datetime.timedelta(hours=12) ) mailbox_before = len(outbox) diff --git a/ietf/message/utils.py b/ietf/message/utils.py index 65c018a3973..2601eccab80 100644 --- a/ietf/message/utils.py +++ b/ietf/message/utils.py @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- -import re, datetime, email +import re, email +from django.utils import timezone from django.utils.encoding import force_str from ietf.utils.mail import send_mail_text, send_mail_mime @@ -52,7 +53,7 @@ def send_scheduled_message_from_send_queue(queue_item): send_mail_mime(None, message.to, message.frm, message.subject, msg, cc=message.cc, bcc=message.bcc) - queue_item.sent_at = datetime.datetime.now() + queue_item.sent_at = timezone.now() queue_item.save() queue_item.message.sent = queue_item.sent_at diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 0bbef21e1a6..67522d15bef 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -17,6 +17,7 @@ from django.core.files import File from django.contrib.auth.models import User from django.urls import reverse +from django.utils import timezone from django.utils.encoding import force_str import debug # pyflakes:ignore @@ -1389,7 +1390,7 @@ def setUp(self): f.nominees.add(self.nominee) f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id='comment') f.topics.add(self.topic) - now = datetime.datetime.now() + now = timezone.now() self.hour_ago = now - datetime.timedelta(hours=1) self.half_hour_ago = now - datetime.timedelta(minutes=30) self.second_from_now = now + datetime.timedelta(seconds=1) diff --git a/ietf/person/management/commands/purge_old_personal_api_key_events.py b/ietf/person/management/commands/purge_old_personal_api_key_events.py index e60f784b5d6..47674e2d5a0 100644 --- a/ietf/person/management/commands/purge_old_personal_api_key_events.py +++ b/ietf/person/management/commands/purge_old_personal_api_key_events.py @@ -1,9 +1,10 @@ # Copyright The IETF Trust 2021, All Rights Reserved # -*- coding: utf-8 -*- -from datetime import datetime, timedelta +from datetime import timedelta from django.core.management.base import BaseCommand, CommandError from django.db.models import Max, Min +from django.utils import timezone from ietf.person.models import PersonApiKeyEvent @@ -33,7 +34,7 @@ def _format_count(count, unit='day'): self.stdout.write('Finding events older than {}\n'.format(_format_count(keep_days))) self.stdout.flush() - now = datetime.now() + now = timezone.now() old_events = PersonApiKeyEvent.objects.filter( time__lt=now - timedelta(days=keep_days) ) diff --git a/ietf/person/management/commands/tests.py b/ietf/person/management/commands/tests.py index f30a80bf51f..291a6ace5f1 100644 --- a/ietf/person/management/commands/tests.py +++ b/ietf/person/management/commands/tests.py @@ -5,6 +5,7 @@ from io import StringIO from django.core.management import call_command, CommandError +from django.utils import timezone from ietf.person.factories import PersonApiKeyEventFactory from ietf.person.models import PersonApiKeyEvent, PersonEvent @@ -51,7 +52,7 @@ def test_purge_old_personal_api_key_events(self): # Remember how many PersonEvents were present so we can verify they're cleaned up properly. personevents_before = PersonEvent.objects.count() - now = datetime.datetime.now() + now = timezone.now() # The first of these events will be timestamped a fraction of a second more than keep_days # days ago by the time we call the management command, so will just barely chosen for purge. old_events = [ @@ -101,7 +102,7 @@ def test_purge_old_personal_api_key_events(self): def test_purge_old_personal_api_key_events_rejects_invalid_arguments(self): """The purge_old_personal_api_key_events command should reject invalid arguments""" - event = PersonApiKeyEventFactory(time=datetime.datetime.now() - datetime.timedelta(days=30)) + event = PersonApiKeyEventFactory(time=timezone.now() - datetime.timedelta(days=30)) with self.assertRaises(CommandError): self._call_command('purge_old_personal_api_key_events') diff --git a/ietf/person/models.py b/ietf/person/models.py index 143b8dd0884..7fff66d9a1f 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- -import datetime import email.utils import email.header import jsonfield @@ -18,6 +17,7 @@ from django.db import models from django.template.loader import render_to_string from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.encoding import smart_bytes from django.utils.text import slugify @@ -38,7 +38,7 @@ class Person(models.Model): history = HistoricalRecords() user = OneToOneField(User, blank=True, null=True, on_delete=models.SET_NULL) - time = models.DateTimeField(default=datetime.datetime.now) # When this Person record entered the system + time = models.DateTimeField(default=timezone.now) # When this Person record entered the system # The normal unicode form of the name. This must be # set to the same value as the ascii-form if equal. name = models.CharField("Full Name (Unicode)", max_length=255, db_index=True, help_text="Preferred long form of name.") @@ -377,7 +377,7 @@ def salt(): class PersonalApiKey(models.Model): person = ForeignKey(Person, related_name='apikeys') endpoint = models.CharField(max_length=128, null=False, blank=False, choices=PERSON_API_KEY_ENDPOINTS) - created = models.DateTimeField(default=datetime.datetime.now, null=False) + created = models.DateTimeField(default=timezone.now, null=False) valid = models.BooleanField(default=True) salt = models.BinaryField(default=salt, max_length=12, null=False, blank=False) count = models.IntegerField(default=0, null=False, blank=False) @@ -427,7 +427,7 @@ def __str__(self): class PersonEvent(models.Model): person = ForeignKey(Person) - time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") + time = models.DateTimeField(default=timezone.now, help_text="When the event happened") type = models.CharField(max_length=50, choices=PERSON_EVENT_CHOICES) desc = models.TextField() diff --git a/ietf/review/factories.py b/ietf/review/factories.py index cf66f1ed885..96eede80da7 100644 --- a/ietf/review/factories.py +++ b/ietf/review/factories.py @@ -2,6 +2,8 @@ import factory import datetime +from django.utils import timezone + from ietf.review.models import ReviewTeamSettings, ReviewRequest, ReviewAssignment, ReviewerSettings from ietf.name.models import ReviewTypeName, ReviewResultName @@ -49,7 +51,7 @@ class Meta: review_request = factory.SubFactory('ietf.review.factories.ReviewRequestFactory') state_id = 'assigned' reviewer = factory.SubFactory('ietf.person.factories.EmailFactory') - assigned_on = datetime.datetime.now() + assigned_on = timezone.now() class ReviewerSettingsFactory(factory.django.DjangoModelFactory): class Meta: diff --git a/ietf/review/models.py b/ietf/review/models.py index 96355417d15..0ec36781940 100644 --- a/ietf/review/models.py +++ b/ietf/review/models.py @@ -7,6 +7,7 @@ from simple_history.models import HistoricalRecords from django.db import models +from django.utils import timezone import debug # pyflakes:ignore @@ -95,7 +96,7 @@ def __str__(self): class ReviewWish(models.Model): """Reviewer wishes to review a document when it becomes available for review.""" - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None)) person = ForeignKey(Person) doc = ForeignKey(Document) @@ -125,7 +126,7 @@ class ReviewRequest(models.Model): # Fields filled in on the initial record creation - these # constitute the request part. - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) type = ForeignKey(ReviewTypeName) doc = ForeignKey(Document, related_name='reviewrequest_set') team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None)) diff --git a/ietf/review/utils.py b/ietf/review/utils.py index 3fec1b84f5b..f665ba06b9f 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -12,6 +12,8 @@ from django.template.loader import render_to_string from django.urls import reverse as urlreverse from django.contrib.sites.models import Site +from django.utils import timezone + from simple_history.utils import update_change_reason import debug # pyflakes:ignore @@ -119,7 +121,7 @@ def days_needed_to_fulfill_min_interval_for_reviewers(team): min_intervals = dict(ReviewerSettings.objects.filter(team=team).values_list("person_id", "min_interval")) - now = datetime.datetime.now() + now = timezone.now() res = {} for person_id, latest_assignment_time in latest_assignments.items(): @@ -495,7 +497,7 @@ def suggested_review_requests_for_team(team): requests = {} - now = datetime.datetime.now() + now = timezone.now() reviewable_docs_qs = Document.objects.filter(type="draft").exclude(stream="ise") diff --git a/ietf/secr/meetings/tests.py b/ietf/secr/meetings/tests.py index c6576a0ccef..2e90a23dc08 100644 --- a/ietf/secr/meetings/tests.py +++ b/ietf/secr/meetings/tests.py @@ -14,6 +14,7 @@ from django.conf import settings from django.urls import reverse +from django.utils import timezone from ietf.group.models import Group, GroupEvent from ietf.meeting.factories import MeetingFactory @@ -212,8 +213,8 @@ def test_notifications(self): self.assertEqual(q('#id_notification_list').html(),'ames, mars') # test that only changes since last notification show up - now = datetime.datetime.now() - then = datetime.datetime.now()+datetime.timedelta(hours=1) + now = timezone.now() + then = timezone.now()+datetime.timedelta(hours=1) person = Person.objects.get(name="(System)") GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification', by=person,desc='sent scheduled notification for %s' % meeting) diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index 6e87b3a1788..a42e06b6c32 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -11,6 +11,7 @@ from django.db.models.functions import Cast from django.forms.models import inlineformset_factory from django.shortcuts import render, get_object_or_404, redirect +from django.utils import timezone from django.utils.text import slugify import debug # pyflakes:ignore @@ -121,7 +122,7 @@ def send_notifications(meeting, groups, person): Send session scheduled email notifications for each group in groups. Person is the user who initiated this action, request.uesr.get_profile(). ''' - now = datetime.datetime.now() + now = timezone.now() for group in groups: sessions = group.session_set.filter(meeting=meeting) addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0]) diff --git a/ietf/secr/proceedings/reports.py b/ietf/secr/proceedings/reports.py index 115e4facdfb..49b63282281 100644 --- a/ietf/secr/proceedings/reports.py +++ b/ietf/secr/proceedings/reports.py @@ -1,6 +1,7 @@ import datetime from django.template.loader import render_to_string +from django.utils import timezone from ietf.meeting.models import Meeting from ietf.doc.models import DocEvent, Document @@ -9,7 +10,7 @@ def report_id_activity(start,end): # get previous meeting - meeting = Meeting.objects.filter(date__lt=datetime.datetime.now(),type='ietf').order_by('-date')[0] + meeting = Meeting.objects.filter(date__lt=timezone.now(),type='ietf').order_by('-date')[0] syear,smonth,sday = start.split('-') eyear,emonth,eday = end.split('-') sdate = datetime.datetime(int(syear),int(smonth),int(sday)) diff --git a/ietf/stats/tests.py b/ietf/stats/tests.py index 0204091b620..96a30214845 100644 --- a/ietf/stats/tests.py +++ b/ietf/stats/tests.py @@ -13,6 +13,7 @@ import debug # pyflakes:ignore from django.urls import reverse as urlreverse +from django.utils import timezone from ietf.utils.test_utils import login_testing_unauthorized, TestCase import ietf.stats.views @@ -63,7 +64,7 @@ def test_document_stats(self): Document.objects.filter(pk=draft.pk).update(words=4000) # move it back so it shows up in the yearly summaries NewRevisionDocEvent.objects.filter(doc=draft, rev=draft.rev).update( - time=datetime.datetime.now() - datetime.timedelta(days=500)) + time=timezone.now() - datetime.timedelta(days=500)) referencing_draft = Document.objects.create( name="draft-ietf-mars-referencing", @@ -88,7 +89,7 @@ def test_document_stats(self): doc=referencing_draft, desc="New revision available", rev=referencing_draft.rev, - time=datetime.datetime.now() - datetime.timedelta(days=1000) + time=timezone.now() - datetime.timedelta(days=1000) ) diff --git a/ietf/submit/models.py b/ietf/submit/models.py index 6043d4ab7cd..d8e492796d7 100644 --- a/ietf/submit/models.py +++ b/ietf/submit/models.py @@ -7,6 +7,7 @@ import jsonfield from django.db import models +from django.utils import timezone import debug # pyflakes:ignore @@ -115,7 +116,7 @@ def closed_wg_drafts_replaced(self): class SubmissionCheck(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) submission = ForeignKey(Submission, related_name='checks') checker = models.CharField(max_length=256, blank=True) passed = models.BooleanField(null=True, default=False) @@ -134,7 +135,7 @@ def has_errors(self): class SubmissionEvent(models.Model): submission = ForeignKey(Submission) - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) by = ForeignKey(Person, null=True, blank=True) desc = models.TextField() @@ -152,7 +153,7 @@ class Preapproval(models.Model): """Pre-approved draft submission name.""" name = models.CharField(max_length=255, db_index=True) by = ForeignKey(Person) - time = models.DateTimeField(default=datetime.datetime.now) + time = models.DateTimeField(default=timezone.now) def __str__(self): return self.name diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 827472c9b7e..677a7dc22df 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -19,6 +19,7 @@ from django.test import override_settings from django.test.client import RequestFactory from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.encoding import force_str, force_text import debug # pyflakes:ignore @@ -279,7 +280,7 @@ def submit_new_wg(self, formats): # prepare draft to suggest replace sug_replaced_draft = Document.objects.create( name="draft-ietf-ames-sug-replaced", - time=datetime.datetime.now(), + time=timezone.now(), type_id="draft", title="Draft to be suggested to be replaced", stream_id="ietf", @@ -290,7 +291,7 @@ def submit_new_wg(self, formats): words=100, intended_std_level_id="ps", ad=draft.ad, - expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), + expires=timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), notify="aliens@example.mars", note="", ) @@ -349,7 +350,7 @@ def submit_new_wg(self, formats): self.assertTrue(os.path.exists(os.path.join(self.repository_dir, "%s-%s.txt" % (name, rev)))) self.assertEqual(draft.type_id, "draft") self.assertEqual(draft.stream_id, "ietf") - self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) + self.assertTrue(draft.expires >= timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) self.assertEqual(draft.get_state("draft-stream-%s" % draft.stream_id).slug, "wg-doc") authors = draft.documentauthor_set.all() self.assertEqual(len(authors), 1) @@ -2312,7 +2313,7 @@ def test_waiting_for_draft(self): Please submit my draft at http://test.com/mydraft.txt Thank you -""".format(datetime.datetime.now().ctime()) +""".format(timezone.now().ctime()) message = email.message_from_string(force_str(message_string)) submission, submission_email_event = ( add_submission_email(request=None, @@ -2394,7 +2395,7 @@ def test_waiting_for_draft_with_attachment(self): QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg== --------------090908050800030909090207-- -""".format(frm, datetime.datetime.now().ctime()) +""".format(frm, timezone.now().ctime()) message = email.message_from_string(force_str(message_string)) submission, submission_email_event = ( @@ -2450,7 +2451,7 @@ def test_waiting_for_draft_with_attachment(self): QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg== --------------090908050800030909090207-- -""".format(datetime.datetime.now().ctime()) +""".format(timezone.now().ctime()) # Back to secretariat self.client.login(username="secretary", password="secretary+password") @@ -2589,7 +2590,7 @@ def check_manualpost_page(self, submission, submission_email_event, About my submission Thank you -""".format(datetime.datetime.now().ctime()) +""".format(timezone.now().ctime()) r = self.client.post(add_email_url, { "name": "{}-{}".format(submission.name, submission.rev), @@ -2656,7 +2657,7 @@ def do_submission_email(self, the_url, to, body): From: {} Date: {} Subject: test -""".format(reply_to, to, datetime.datetime.now().ctime()) +""".format(reply_to, to, timezone.now().ctime()) result = process_response_email(message_string) self.assertIsInstance(result, Message) diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index 429fea58e82..d8f3b6576b8 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -19,6 +19,7 @@ from django.utils.module_loading import import_string from django.template.loader import render_to_string from django.contrib.auth.models import AnonymousUser +from django.utils import timezone import debug # pyflakes:ignore @@ -333,7 +334,7 @@ def post_submission(request, submission, approved_doc_desc, approved_subm_desc): if stream_slug: draft.stream = StreamName.objects.get(slug=stream_slug) - draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) + draft.expires = timezone.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) log.log(f"{submission.name}: got draft details") events = [] @@ -616,7 +617,7 @@ def ensure_person_email_info_exists(name, email, docname): email.active = active email.person = person if email.time is None: - email.time = datetime.datetime.now() + email.time = timezone.now() email.origin = "author: %s" % docname email.save() diff --git a/ietf/sync/iana.py b/ietf/sync/iana.py index 9d0f66435b9..cf072d4c770 100644 --- a/ietf/sync/iana.py +++ b/ietf/sync/iana.py @@ -10,6 +10,7 @@ import requests from django.conf import settings +from django.utils import timezone from django.utils.encoding import smart_bytes, force_str from django.utils.http import urlquote @@ -241,7 +242,7 @@ def parse_review_email(text): doc_name = strip_version_extension(doc_name) # date - review_time = datetime.datetime.now() + review_time = timezone.now() if "Date" in msg: review_time = email_time_to_local_timezone(msg["Date"]) diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index c2264a0d3a1..6feb8630875 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -11,6 +11,7 @@ from xml.dom import pulldom, Node from django.conf import settings +from django.utils import timezone from django.utils.encoding import smart_bytes, force_str, force_text import debug # pyflakes:ignore @@ -443,7 +444,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non # at the moment because the data only has month/year, so # try to deduce it d = datetime.datetime.combine(rfc_published_date, datetime.time()) - synthesized = datetime.datetime.now() + synthesized = timezone.now() if abs(d - synthesized) > datetime.timedelta(days=60): synthesized = d else: diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py index 61bab795c8b..6b8186d7891 100644 --- a/ietf/sync/tests.py +++ b/ietf/sync/tests.py @@ -10,6 +10,7 @@ from django.conf import settings from django.urls import reverse as urlreverse +from django.utils import timezone import debug # pyflakes:ignore @@ -34,11 +35,11 @@ def test_protocol_page_sync(self): self.assertEqual(len(rfc_names), 1) self.assertEqual(rfc_names[0], "rfc1234") - iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1)) + iana.update_rfc_log_from_protocol_page(rfc_names, timezone.now() - datetime.timedelta(days=1)) self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1) # make sure it doesn't create duplicates - iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1)) + iana.update_rfc_log_from_protocol_page(rfc_names, timezone.now() - datetime.timedelta(days=1)) self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1) def test_changes_sync(self): diff --git a/ietf/sync/views.py b/ietf/sync/views.py index d6fc505c287..1d22c424cb7 100644 --- a/ietf/sync/views.py +++ b/ietf/sync/views.py @@ -12,6 +12,7 @@ from django.contrib.contenttypes.models import ContentType from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.shortcuts import render +from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from ietf.doc.models import DeletedEvent, StateDocEvent, DocEvent @@ -118,12 +119,12 @@ def rfceditor_undo(request): events = [] events.extend(StateDocEvent.objects.filter( state_type="draft-rfceditor", - time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1) + time__gte=timezone.now() - datetime.timedelta(weeks=1) ).order_by("-time", "-id")) events.extend(DocEvent.objects.filter( type="sync_from_rfc_editor", - time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1) + time__gte=timezone.now() - datetime.timedelta(weeks=1) ).order_by("-time", "-id")) events.sort(key=lambda e: (e.time, e.id), reverse=True) diff --git a/ietf/utils/decorators.py b/ietf/utils/decorators.py index 694e989f835..63472488041 100644 --- a/ietf/utils/decorators.py +++ b/ietf/utils/decorators.py @@ -10,6 +10,7 @@ from django.contrib.auth import login from django.http import HttpResponse from django.shortcuts import render +from django.utils import timezone from django.utils.encoding import force_bytes import debug # pyflakes:ignore @@ -64,7 +65,7 @@ def err(code, text): person = key.person last_login = person.user.last_login if not person.user.is_staff: - time_limit = (datetime.datetime.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) + time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) if last_login == None or last_login < time_limit: return err(400, "Too long since last regular login") # Log in @@ -74,7 +75,7 @@ def err(code, text): person.user.save() # Update stats key.count += 1 - key.latest = datetime.datetime.now() + key.latest = timezone.now() key.save() PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint)) # Execute decorated function diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index 45a145b30a3..63bd882fdf4 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -3,7 +3,6 @@ import copy -import datetime #import logging import re import smtplib @@ -27,6 +26,7 @@ from django.core.validators import validate_email from django.template.loader import render_to_string from django.template import Context,RequestContext +from django.utils import timezone from django.utils.encoding import force_text, force_str, force_bytes import debug # pyflakes:ignore @@ -321,7 +321,7 @@ def show_that_mail_was_sent(request,leadline,msg,bcc): if request and request.user: from ietf.ietfauth.utils import has_role if has_role(request.user,['Area Director','Secretariat','IANA','RFC Editor','ISE','IAD','IRTF Chair','WG Chair','RG Chair','WG Secretary','RG Secretary']): - info = "%s at %s %s\n" % (leadline,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),settings.TIME_ZONE) + info = "%s at %s %s\n" % (leadline,timezone.now().strftime("%Y-%m-%d %H:%M:%S"),settings.TIME_ZONE) info += "Subject: %s\n" % force_text(msg.get('Subject','[no subject]')) info += "To: %s\n" % msg.get('To','[no to]') if msg.get('Cc'): @@ -375,7 +375,7 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F try: send_smtp(msg, bcc) if save: - message.sent = datetime.datetime.now() + message.sent = timezone.now() message.save() if settings.SERVER_MODE != 'development': show_that_mail_was_sent(request,'Email was sent',msg,bcc) @@ -502,7 +502,7 @@ def send_mail_message(request, message, extra=None): # msg = send_mail_text(request, message.to, message.frm, message.subject, # message.body, cc=message.cc, bcc=message.bcc, extra=e, save=False) - message.sent = datetime.datetime.now() + message.sent = timezone.now() message.save() return msg diff --git a/ietf/utils/management/commands/check_draft_event_revision_integrity.py b/ietf/utils/management/commands/check_draft_event_revision_integrity.py index a4c38bd1c09..c8d2cbd21a8 100644 --- a/ietf/utils/management/commands/check_draft_event_revision_integrity.py +++ b/ietf/utils/management/commands/check_draft_event_revision_integrity.py @@ -13,6 +13,7 @@ django.setup() from django.core.management.base import BaseCommand #, CommandError +from django.utils import timezone import debug # pyflakes:ignore @@ -29,7 +30,7 @@ class Command(BaseCommand): """ def add_arguments(self, parser): - default_start = datetime.datetime.now() - datetime.timedelta(days=60) + default_start = timezone.now() - datetime.timedelta(days=60) parser.add_argument( '-d', '--from', type=str, default=default_start.strftime('%Y-%m-%d'), help='Limit the list to messages saved after the given date (default %(default)s).', diff --git a/ietf/utils/management/commands/fix_ambiguous_timestamps.py b/ietf/utils/management/commands/fix_ambiguous_timestamps.py index fc872f45867..84a82bb3098 100644 --- a/ietf/utils/management/commands/fix_ambiguous_timestamps.py +++ b/ietf/utils/management/commands/fix_ambiguous_timestamps.py @@ -10,6 +10,7 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.db import models +from django.utils import timezone import debug # pyflakes:ignore @@ -53,7 +54,7 @@ def fixup(self, model, field, start, stop): def handle(self, *app_labels, **options): self.verbosity = options['verbosity'] self.quiet = self.verbosity < 1 - stop = datetime.datetime.now() + stop = timezone.now() start = stop - datetime.timedelta(days=14) for name, appconf in apps.app_configs.items(): diff --git a/ietf/utils/management/commands/send_gdpr_consent_request.py b/ietf/utils/management/commands/send_gdpr_consent_request.py index 2bb08eed1b3..152fc83fe35 100644 --- a/ietf/utils/management/commands/send_gdpr_consent_request.py +++ b/ietf/utils/management/commands/send_gdpr_consent_request.py @@ -7,6 +7,7 @@ from django.conf import settings from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone import debug # pyflakes:ignore @@ -65,7 +66,7 @@ def handle(self, *args, **options): delay = 1.0/options['rate'] # --minimum_interval minimum_interval = options['minimum_interval'] - latest_previous = datetime.datetime.now() - datetime.timedelta(days=minimum_interval) + latest_previous = timezone.now() - datetime.timedelta(days=minimum_interval) # user self.stdout.write('Querying the database for matching person records ...') if 'user' in options and options['user']: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 7a7a77af49b..cd5a06e8395 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -6,6 +6,7 @@ from django.conf import settings from django.contrib.auth.models import User +from django.utils import timezone from django.utils.encoding import smart_text import debug # pyflakes:ignore @@ -271,14 +272,14 @@ def make_test_data(): # old draft old_draft = Document.objects.create( name="draft-foo-mars-test", - time=datetime.datetime.now() - datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), + time=timezone.now() - datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), type_id="draft", title="Optimizing Martian Network Topologies", stream_id="ietf", abstract="Techniques for achieving near-optimal Martian networks.", rev="00", pages=2, - expires=datetime.datetime.now(), + expires=timezone.now(), ) old_draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) old_alias = DocAlias.objects.create(name=old_draft.name) @@ -287,7 +288,7 @@ def make_test_data(): # draft draft = Document.objects.create( name="draft-ietf-mars-test", - time=datetime.datetime.now(), + time=timezone.now(), type_id="draft", title="Optimizing Martian Network Topologies", stream_id="ietf", @@ -298,7 +299,7 @@ def make_test_data(): intended_std_level_id="ps", shepherd=email, ad=ad, - expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), + expires=timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), notify="aliens@example.mars", note="", ) @@ -458,7 +459,7 @@ def make_review_data(doc): doc=doc, team=team1, type_id="early", - deadline=datetime.datetime.now() + datetime.timedelta(days=20), + deadline=timezone.now() + datetime.timedelta(days=20), state_id="accepted", requested_by=reviewer, reviewer=email, diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 7438d1ae4e4..11b544294f3 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -569,7 +569,7 @@ def code_coverage_test(self): checker.stop() # Save to the .coverage file checker.save() - # Apply the configured and requested omit and include data + # Apply the configured and requested omit and include data checker.config.from_args(ignore_errors=None, omit=settings.TEST_CODE_COVERAGE_EXCLUDE_FILES, include=include, file=None) for pattern in settings.TEST_CODE_COVERAGE_EXCLUDE_LINES: @@ -738,7 +738,7 @@ def setup_test_environment(self, **kwargs): print(" Datatracker %s test suite, %s:" % (ietf.__version__, time.strftime("%d %B %Y %H:%M:%S %Z"))) print(" Python %s." % sys.version.replace('\n', ' ')) print(" Django %s, settings '%s'" % (django.get_version(), settings.SETTINGS_MODULE)) - + settings.TEMPLATES[0]['BACKEND'] = 'ietf.utils.test_runner.ValidatingTemplates' if self.check_coverage: if self.coverage_file.endswith('.gz'): @@ -750,17 +750,17 @@ def setup_test_environment(self, **kwargs): self.coverage_data = { "time": datetime.datetime.now(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "template": { - "coverage": 0.0, + "coverage": 0.0, "covered": {}, "format": 1, # default format, coverage data in 'covered' are just fractions }, "url": { - "coverage": 0.0, + "coverage": 0.0, "covered": {}, "format": 4, }, "code": { - "coverage": 0.0, + "coverage": 0.0, "covered": {}, "format": 1, }, @@ -807,8 +807,8 @@ def setup_test_environment(self, **kwargs): for offset in range(10): try: # remember the value so ietf.utils.mail.send_smtp() will use the same - ietf.utils.mail.SMTP_ADDR['port'] = base + offset - self.smtpd_driver = SMTPTestServerDriver((ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port']),None) + ietf.utils.mail.SMTP_ADDR['port'] = base + offset + self.smtpd_driver = SMTPTestServerDriver((ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port']),None) self.smtpd_driver.start() print((" Running an SMTP test server on %(ip4)s:%(port)s to catch outgoing email." % ietf.utils.mail.SMTP_ADDR)) break diff --git a/ietf/utils/tests_meetecho.py b/ietf/utils/tests_meetecho.py index d40e013f826..f279d42a9ed 100644 --- a/ietf/utils/tests_meetecho.py +++ b/ietf/utils/tests_meetecho.py @@ -1,10 +1,10 @@ # Copyright The IETF Trust 2021, All Rights Reserved # -*- coding: utf-8 -*- import datetime +import pytz import requests import requests_mock -from pytz import timezone, utc from unittest.mock import patch from urllib.parse import urljoin @@ -92,7 +92,7 @@ def test_schedule_meeting(self): api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET) api_response = api.schedule_meeting( wg_token='my-token', - start_time=utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), + start_time=pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), duration=datetime.timedelta(minutes=130), description='interim-2021-wgname-01', extrainfo='message for staff', @@ -120,11 +120,11 @@ def test_schedule_meeting(self): ) # same time in different time zones for start_time in [ - utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), - timezone('america/halifax').localize(datetime.datetime(2021, 9, 14, 7, 0, 0)), - timezone('europe/kiev').localize(datetime.datetime(2021, 9, 14, 13, 0, 0)), - timezone('pacific/easter').localize(datetime.datetime(2021, 9, 14, 5, 0, 0)), - timezone('africa/porto-novo').localize(datetime.datetime(2021, 9, 14, 11, 0, 0)), + pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), + pytz.timezone('america/halifax').localize(datetime.datetime(2021, 9, 14, 7, 0, 0)), + pytz.timezone('europe/kiev').localize(datetime.datetime(2021, 9, 14, 13, 0, 0)), + pytz.timezone('pacific/easter').localize(datetime.datetime(2021, 9, 14, 5, 0, 0)), + pytz.timezone('africa/porto-novo').localize(datetime.datetime(2021, 9, 14, 11, 0, 0)), ]: self.assertEqual( api_response, @@ -191,7 +191,7 @@ def test_fetch_meetings(self): '3d55bce0-535e-4ba8-bb8e-734911cf3c32': { 'room': { 'id': 18, - 'start_time': utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), + 'start_time': pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)), 'duration': datetime.timedelta(minutes=130), 'description': 'interim-2021-wgname-01', }, @@ -201,7 +201,7 @@ def test_fetch_meetings(self): 'e68e96d4-d38f-475b-9073-ecab46ca96a5': { 'room': { 'id': 23, - 'start_time': utc.localize(datetime.datetime(2021, 9, 15, 14, 30, 0)), + 'start_time': pytz.utc.localize(datetime.datetime(2021, 9, 15, 14, 30, 0)), 'duration': datetime.timedelta(minutes=30), 'description': 'interim-2021-wgname-02', }, @@ -249,7 +249,7 @@ def test_request_helper_exception(self): def test_time_serialization(self): """Time de/serialization should be consistent""" - time = datetime.datetime.now(utc).replace(microsecond=0) # cut off to 0 microseconds + time = datetime.datetime.now(pytz.utc).replace(microsecond=0) # cut off to 0 microseconds api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET) self.assertEqual(api._deserialize_time(api._serialize_time(time)), time) @@ -263,7 +263,7 @@ def test_conference_from_api_dict(self): 'session-1-uuid': { 'room': { 'id': 1, - 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)), + 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -273,7 +273,7 @@ def test_conference_from_api_dict(self): 'session-2-uuid': { 'room': { 'id': 2, - 'start_time': utc.localize(datetime.datetime(2022,2,5,4,5,6)), + 'start_time': pytz.utc.localize(datetime.datetime(2022,2,5,4,5,6)), 'duration': datetime.timedelta(minutes=90), 'description': 'another-description', }, @@ -290,7 +290,7 @@ def test_conference_from_api_dict(self): id=1, public_id='session-1-uuid', description='some-description', - start_time=utc.localize(datetime.datetime(2022, 2, 4, 1, 2, 3)), + start_time=pytz.utc.localize(datetime.datetime(2022, 2, 4, 1, 2, 3)), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', @@ -300,7 +300,7 @@ def test_conference_from_api_dict(self): id=2, public_id='session-2-uuid', description='another-description', - start_time=utc.localize(datetime.datetime(2022, 2, 5, 4, 5, 6)), + start_time=pytz.utc.localize(datetime.datetime(2022, 2, 5, 4, 5, 6)), duration=datetime.timedelta(minutes=90), url='https://example.com/another/url', deletion_token='delete-me-too', @@ -316,7 +316,7 @@ def test_fetch(self, mock_fetch, _): 'session-1-uuid': { 'room': { 'id': 1, - 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)), + 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -335,7 +335,7 @@ def test_fetch(self, mock_fetch, _): id=1, public_id='session-1-uuid', description='some-description', - start_time=utc.localize(datetime.datetime(2022,2,4,1,2,3)), + start_time=pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', @@ -351,7 +351,7 @@ def test_create(self, mock_schedule, _): 'session-1-uuid': { 'room': { 'id': 1, - 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)), + 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -369,7 +369,7 @@ def test_create(self, mock_schedule, _): id=1, public_id='session-1-uuid', description='some-description', - start_time=utc.localize(datetime.datetime(2022,2,4,1,2,3)), + start_time=pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', From 5b80fe05a509f9429e317f3bb1e4233b2760a94c Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 12 Jul 2022 15:30:23 -0300 Subject: [PATCH 02/39] refactor: migrate model fields to use timezone.now as default --- .../0045_use_timezone_now_for_doc_models.py | 39 +++++++++++++++++++ .../0057_use_timezone_now_for_group_models.py | 29 ++++++++++++++ ...054_use_timezone_now_for_meeting_models.py | 19 +++++++++ ...012_use_timezone_now_for_message_models.py | 24 ++++++++++++ ...0025_use_timezone_now_for_person_models.py | 34 ++++++++++++++++ ...0029_use_timezone_now_for_review_models.py | 29 ++++++++++++++ ...0009_use_timezone_now_for_submit_models.py | 29 ++++++++++++++ 7 files changed, 203 insertions(+) create mode 100644 ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py create mode 100644 ietf/group/migrations/0057_use_timezone_now_for_group_models.py create mode 100644 ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py create mode 100644 ietf/message/migrations/0012_use_timezone_now_for_message_models.py create mode 100644 ietf/person/migrations/0025_use_timezone_now_for_person_models.py create mode 100644 ietf/review/migrations/0029_use_timezone_now_for_review_models.py create mode 100644 ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py diff --git a/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py b/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py new file mode 100644 index 00000000000..152f0d4c2b6 --- /dev/null +++ b/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('doc', '0044_procmaterials_states'), + ] + + operations = [ + migrations.AlterField( + model_name='deletedevent', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='docevent', + name='time', + field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, help_text='When the event happened'), + ), + migrations.AlterField( + model_name='dochistory', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='document', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='documentactionholder', + name='time_added', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/ietf/group/migrations/0057_use_timezone_now_for_group_models.py b/ietf/group/migrations/0057_use_timezone_now_for_group_models.py new file mode 100644 index 00000000000..85347b2e55e --- /dev/null +++ b/ietf/group/migrations/0057_use_timezone_now_for_group_models.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('group', '0056_dir_chair_groupman_role'), + ] + + operations = [ + migrations.AlterField( + model_name='group', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='groupevent', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'), + ), + migrations.AlterField( + model_name='grouphistory', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py b/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py new file mode 100644 index 00000000000..3e6eb71ec39 --- /dev/null +++ b/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0053_attended'), + ] + + operations = [ + migrations.AlterField( + model_name='schedulingevent', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'), + ), + ] diff --git a/ietf/message/migrations/0012_use_timezone_now_for_message_models.py b/ietf/message/migrations/0012_use_timezone_now_for_message_models.py new file mode 100644 index 00000000000..dbef893ea40 --- /dev/null +++ b/ietf/message/migrations/0012_use_timezone_now_for_message_models.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('message', '0011_auto_20201109_0439'), + ] + + operations = [ + migrations.AlterField( + model_name='message', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='sendqueue', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/ietf/person/migrations/0025_use_timezone_now_for_person_models.py b/ietf/person/migrations/0025_use_timezone_now_for_person_models.py new file mode 100644 index 00000000000..22dee042cef --- /dev/null +++ b/ietf/person/migrations/0025_use_timezone_now_for_person_models.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('person', '0024_pronouns'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalperson', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='person', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='personalapikey', + name='created', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='personevent', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'), + ), + ] diff --git a/ietf/review/migrations/0029_use_timezone_now_for_review_models.py b/ietf/review/migrations/0029_use_timezone_now_for_review_models.py new file mode 100644 index 00000000000..a8a0558d4ca --- /dev/null +++ b/ietf/review/migrations/0029_use_timezone_now_for_review_models.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('review', '0028_auto_20220513_1456'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalreviewrequest', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='reviewrequest', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='reviewwish', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py b/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py new file mode 100644 index 00000000000..90208b20f18 --- /dev/null +++ b/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.28 on 2022-07-12 11:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('submit', '0008_submissionextresource'), + ] + + operations = [ + migrations.AlterField( + model_name='preapproval', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='submissioncheck', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='submissionevent', + name='time', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] From e3c76ce58243f63e728ccb69d1651531d1cf2446 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 12 Jul 2022 16:27:26 -0300 Subject: [PATCH 03/39] 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. --- ietf/doc/factories.py | 3 ++- ietf/doc/tests_bofreq.py | 3 ++- ietf/group/factories.py | 4 +++- ietf/group/tests_info.py | 2 +- ietf/ipr/factories.py | 3 ++- ietf/liaisons/tests.py | 2 +- ietf/meeting/helpers.py | 5 +++-- .../0013_make_separate_break_sessobj.py | 4 ++-- .../0020_remove_future_break_sessions.py | 4 ++-- ietf/meeting/models.py | 2 +- ietf/meeting/tests_js.py | 18 +++++++++--------- ietf/meeting/tests_views.py | 18 +++++++++--------- ietf/meeting/views.py | 2 +- ietf/person/tests.py | 3 ++- ietf/review/factories.py | 2 +- ietf/secr/proceedings/tests_reports.py | 6 ++++-- ietf/secr/telechat/tests.py | 5 +++-- .../commands/fetch_meeting_attendance.py | 4 ++-- ietf/stats/views.py | 3 ++- 19 files changed, 52 insertions(+), 41 deletions(-) diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 24422b6c545..28f7e28bbc9 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -351,7 +351,8 @@ class TelechatDocEventFactory(DocEventFactory): class Meta: model = TelechatDocEvent - telechat_date = datetime.datetime.today()+datetime.timedelta(days=14) + # note: this is evaluated at import time and not updated - all events will have the same telechat_date + telechat_date = timezone.now()+datetime.timedelta(days=14) type = 'scheduled_for_telechat' class NewRevisionDocEventFactory(DocEventFactory): diff --git a/ietf/doc/tests_bofreq.py b/ietf/doc/tests_bofreq.py index aecfe5acb83..00dec493a12 100644 --- a/ietf/doc/tests_bofreq.py +++ b/ietf/doc/tests_bofreq.py @@ -13,6 +13,7 @@ from django.conf import settings from django.urls import reverse as urlreverse from django.template.loader import render_to_string +from django.utils import timezone from ietf.group.factories import RoleFactory from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory @@ -45,7 +46,7 @@ def test_show_bof_requests(self): states = State.objects.filter(type_id='bofreq') self.assertTrue(states.count()>0) for i in range(3*len(states)): - BofreqFactory(states=[('bofreq',states[i%len(states)].slug)],newrevisiondocevent__time=datetime.datetime.today()-datetime.timedelta(days=randint(0,20))) + BofreqFactory(states=[('bofreq',states[i%len(states)].slug)],newrevisiondocevent__time=timezone.now()-datetime.timedelta(days=randint(0,20))) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) diff --git a/ietf/group/factories.py b/ietf/group/factories.py index d8d927d801a..e7fbef59ab0 100644 --- a/ietf/group/factories.py +++ b/ietf/group/factories.py @@ -5,6 +5,8 @@ from typing import List # pyflakes:ignore +from django.utils import timezone + from ietf.group.models import Group, Role, GroupEvent, GroupMilestone, \ GroupHistory, RoleHistory from ietf.review.factories import ReviewTeamSettingsFactory @@ -66,7 +68,7 @@ class Meta: class DatedGroupMilestoneFactory(BaseGroupMilestoneFactory): group = factory.SubFactory(GroupFactory, uses_milestone_dates=True) - due = datetime.datetime.today()+datetime.timedelta(days=180) + due = timezone.now()+datetime.timedelta(days=180) class DatelessGroupMilestoneFactory(BaseGroupMilestoneFactory): group = factory.SubFactory(GroupFactory, uses_milestone_dates=False) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index 041cd65dda9..3e2834ff113 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -1885,7 +1885,7 @@ def test_view_all_status_updates(self): def test_view_status_update_for_meeting(self): chair = RoleFactory(name_id='chair',group__type_id='wg') GroupEventFactory(type='status_update',group=chair.group) - sess = SessionFactory.create(meeting__type_id='ietf',group=chair.group,meeting__date=datetime.datetime.today()-datetime.timedelta(days=1)) + sess = SessionFactory.create(meeting__type_id='ietf',group=chair.group,meeting__date=timezone.now()-datetime.timedelta(days=1)) url = urlreverse('ietf.group.views.group_about_status_meeting',kwargs={'acronym':chair.group.acronym,'num':sess.meeting.number}) response = self.client.get(url) self.assertEqual(response.status_code,200) diff --git a/ietf/ipr/factories.py b/ietf/ipr/factories.py index da126291261..e32090a36bf 100644 --- a/ietf/ipr/factories.py +++ b/ietf/ipr/factories.py @@ -5,6 +5,7 @@ import datetime import factory +from django.utils import timezone from ietf.ipr.models import ( IprDisclosureBase, HolderIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure, @@ -13,7 +14,7 @@ def _fake_patent_info(): return "Date: %s\nNotes: %s\nTitle: %s\nNumber: %s\nInventor: %s\n" % ( - (datetime.datetime.today()-datetime.timedelta(days=365)).strftime("%Y-%m-%d"), + (timezone.now()-datetime.timedelta(days=365)).strftime("%Y-%m-%d"), factory.Faker('paragraph'), factory.Faker('sentence', nb_words=8), 'US9999999', diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index d21484ec9bf..f8f230ea92a 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -52,7 +52,7 @@ def get_liaison_post_data(type='incoming'): to_contacts='to_contacts@example.com', purpose="info", title="title", - submitted_date=datetime.datetime.today().strftime("%Y-%m-%d"), + submitted_date=timezone.now().strftime("%Y-%m-%d"), body="body", send="1" ) diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py index 4fd4579c980..5f463b269ae 100644 --- a/ietf/meeting/helpers.py +++ b/ietf/meeting/helpers.py @@ -17,6 +17,7 @@ from django.urls import reverse from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string +from django.utils import timezone import debug # pyflakes:ignore @@ -42,7 +43,7 @@ def get_meeting(num=None,type_in=['ietf',],days=28): if type_in: meetings = meetings.filter(type__in=type_in) if num == None: - meetings = meetings.filter(date__gte=datetime.datetime.today()-datetime.timedelta(days=days)).order_by('date') + meetings = meetings.filter(date__gte=timezone.now()-datetime.timedelta(days=days)).order_by('date') else: meetings = meetings.filter(number=num) if meetings.exists(): @@ -51,7 +52,7 @@ def get_meeting(num=None,type_in=['ietf',],days=28): raise Http404("No such meeting found: %s" % num) def get_current_ietf_meeting(): - meetings = Meeting.objects.filter(type='ietf',date__gte=datetime.datetime.today()-datetime.timedelta(days=31)).order_by('date') + meetings = Meeting.objects.filter(type='ietf',date__gte=timezone.now()-datetime.timedelta(days=31)).order_by('date') return meetings.first() def get_current_ietf_meeting_num(): diff --git a/ietf/meeting/migrations/0013_make_separate_break_sessobj.py b/ietf/meeting/migrations/0013_make_separate_break_sessobj.py index ceea5179d2b..5ba0cb1044b 100644 --- a/ietf/meeting/migrations/0013_make_separate_break_sessobj.py +++ b/ietf/meeting/migrations/0013_make_separate_break_sessobj.py @@ -3,8 +3,8 @@ # Generated by Django 1.11.20 on 2019-03-23 06:11 -import datetime from django.db import migrations +from django.utils import timezone def copy_session(session): @@ -15,7 +15,7 @@ def copy_session(session): def forward(apps, schema_editor): Meeting = apps.get_model('meeting', 'Meeting') - today = datetime.datetime.today() + today = timezone.now() meetings = Meeting.objects.filter(date__gt=today, type='ietf') for meeting in meetings: sessions = meeting.session_set.filter(type__in=['break', 'reg']) diff --git a/ietf/meeting/migrations/0020_remove_future_break_sessions.py b/ietf/meeting/migrations/0020_remove_future_break_sessions.py index 5d2569b1cce..37b62bb7416 100644 --- a/ietf/meeting/migrations/0020_remove_future_break_sessions.py +++ b/ietf/meeting/migrations/0020_remove_future_break_sessions.py @@ -3,13 +3,13 @@ # Generated by Django 1.11.22 on 2019-07-22 14:56 -import datetime from django.db import migrations +from django.utils import timezone def forward(apps, schema_editor): Meeting = apps.get_model('meeting', 'Meeting') - today = datetime.datetime.today() + today = timezone.now() meetings = Meeting.objects.filter(date__gt=today, type='ietf') for meeting in meetings: meeting.agenda.assignments.all().delete() diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 2274e2497c3..70d2f120a8a 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -178,7 +178,7 @@ def get_reopen_time(self): @classmethod def get_current_meeting(cls, type="ietf"): - return cls.objects.filter(type=type, date__gte=datetime.datetime.today()-datetime.timedelta(days=7) ).order_by('date').first() + return cls.objects.filter(type=type, date__gte=timezone.now()-datetime.timedelta(days=7) ).order_by('date').first() def get_first_cut_off(self): return self.get_00_cutoff() diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 94e41f5ed13..f62e41388a0 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -11,8 +11,8 @@ import urllib.parse import django +from django.utils import timezone from django.utils.text import slugify -from django.utils.timezone import now from django.db.models import F import pytz @@ -307,7 +307,7 @@ def test_past_flags(self): room = RoomFactory(meeting=meeting) # get current time in meeting time zone - right_now = now().astimezone( + right_now = timezone.now().astimezone( pytz.timezone(meeting.time_zone) ) if not settings.USE_TZ: @@ -394,11 +394,11 @@ def test_past_flags(self): def test_past_swap_days_buttons(self): """Swap days buttons should be hidden for past items""" wait = WebDriverWait(self.driver, 2) - meeting = MeetingFactory(type_id='ietf', date=datetime.datetime.today() - datetime.timedelta(days=3), days=7) + meeting = MeetingFactory(type_id='ietf', date=timezone.now() - datetime.timedelta(days=3), days=7) room = RoomFactory(meeting=meeting) # get current time in meeting time zone - right_now = now().astimezone( + right_now = timezone.now().astimezone( pytz.timezone(meeting.time_zone) ) if not settings.USE_TZ: @@ -518,11 +518,11 @@ def test_past_swap_days_buttons(self): def test_past_swap_timeslot_col_buttons(self): """Swap timeslot column buttons should be hidden for past items""" wait = WebDriverWait(self.driver, 2) - meeting = MeetingFactory(type_id='ietf', date=datetime.datetime.today() - datetime.timedelta(days=3), days=7) + meeting = MeetingFactory(type_id='ietf', date=timezone.now() - datetime.timedelta(days=3), days=7) room = RoomFactory(meeting=meeting) # get current time in meeting time zone - right_now = now().astimezone( + right_now = timezone.now().astimezone( pytz.timezone(meeting.time_zone) ) if not settings.USE_TZ: @@ -2048,7 +2048,7 @@ def displayed_interims(self, groups=None): Session.objects.filter( meeting__type_id='interim', timeslotassignments__schedule=F('meeting__schedule'), - timeslotassignments__timeslot__time__gte=datetime.datetime.today() + timeslotassignments__timeslot__time__gte=timezone.now() ) ).filter(current_status__in=('sched','canceled')) meetings = [] @@ -2061,7 +2061,7 @@ def displayed_interims(self, groups=None): def all_ietf_meetings(self): meetings = Meeting.objects.filter( type_id='ietf', - date__gte=datetime.datetime.today()-datetime.timedelta(days=7) + date__gte=timezone.now()-datetime.timedelta(days=7) ) for m in meetings: m.calendar_label = 'IETF %s' % m.number @@ -2609,7 +2609,7 @@ def setUp(self): self.meeting: Meeting = MeetingFactory( type_id='ietf', number=120, - date=datetime.datetime.today() + datetime.timedelta(days=10), + date=timezone.now() + datetime.timedelta(days=10), populate_schedule=False, ) self.edit_timeslot_url = self.absreverse( diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 9e4d4baac16..d9321652ddc 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -556,7 +556,7 @@ def test_materials(self): def test_interim_materials(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') - date = datetime.datetime.today() - datetime.timedelta(days=10) + date = timezone.now() - datetime.timedelta(days=10) meeting = make_interim_meeting(group=group, date=date, status='sched') session = meeting.session_set.first() @@ -1490,7 +1490,7 @@ def test_assign_session(self): """Allow assignment to future timeslots only for official schedule""" meeting = MeetingFactory( type_id='ietf', - date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), + date=(timezone.now() - datetime.timedelta(days=1)).date(), days=3, ) right_now = self._right_now_in(meeting.time_zone) @@ -1550,7 +1550,7 @@ def test_reassign_session(self): """Do not allow assignment of past sessions for official schedule""" meeting = MeetingFactory( type_id='ietf', - date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), + date=(timezone.now() - datetime.timedelta(days=1)).date(), days=3, ) right_now = self._right_now_in(meeting.time_zone) @@ -1685,7 +1685,7 @@ def test_unassign_session(self): """Allow unassignment only of future timeslots for official schedule""" meeting = MeetingFactory( type_id='ietf', - date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), + date=(timezone.now() - datetime.timedelta(days=1)).date(), days=3, ) right_now = self._right_now_in(meeting.time_zone) @@ -1809,7 +1809,7 @@ def create_bare_meeting(number=120) -> Meeting: return MeetingFactory( type_id='ietf', number=number, - date=datetime.datetime.today() + datetime.timedelta(days=10), + date=timezone.now() + datetime.timedelta(days=10), populate_schedule=False, ) @@ -5567,7 +5567,7 @@ def test_send_interim_session_cancellation_notice(self): def test_send_interim_minutes_reminder(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') - date = datetime.datetime.today() - datetime.timedelta(days=10) + date = timezone.now() - datetime.timedelta(days=10) meeting = make_interim_meeting(group=group, date=date, status='sched') length_before = len(outbox) send_interim_minutes_reminder(meeting=meeting) @@ -6606,7 +6606,7 @@ def test_appears_on_upcoming(self): session = SessionFactory( group__type_id = gf.type_id, meeting__type_id='interim', - meeting__date = datetime.datetime.today()+datetime.timedelta(days=30), + meeting__date = timezone.now()+datetime.timedelta(days=30), status_id='sched', ) sessions.append(session) @@ -6622,7 +6622,7 @@ def test_appears_on_pending(self): sessions=[] for gf in GroupFeatures.objects.filter(has_meetings=True): group = GroupFactory(type_id=gf.type_id) - meeting_date = datetime.datetime.today() + datetime.timedelta(days=30) + meeting_date = timezone.now() + datetime.timedelta(days=30) session = SessionFactory( group=group, meeting__type_id='interim', @@ -6643,7 +6643,7 @@ def test_appears_on_announce(self): sessions=[] for gf in GroupFeatures.objects.filter(has_meetings=True): group = GroupFactory(type_id=gf.type_id) - meeting_date = datetime.datetime.today() + datetime.timedelta(days=30) + meeting_date = timezone.now() + datetime.timedelta(days=30) session = SessionFactory( group=group, meeting__type_id='interim', diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 35aeded96bd..f8f41d6a119 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -3359,7 +3359,7 @@ def interim_request_edit(request, number): @cache_page(60*60) def past(request): '''List of past meetings''' - today = datetime.datetime.today() + today = timezone.now() meetings = data_for_meetings_overview(Meeting.objects.filter(date__lte=today).order_by('-date')) diff --git a/ietf/person/tests.py b/ietf/person/tests.py index 59228a79534..16bcdf6da09 100644 --- a/ietf/person/tests.py +++ b/ietf/person/tests.py @@ -13,6 +13,7 @@ from django.http import HttpRequest from django.test import override_settings from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.encoding import iri_to_uri import debug # pyflakes:ignore @@ -211,7 +212,7 @@ def test_determine_merge_order(self): self.assertEqual(results,(p1,p3)) # both have User - today = datetime.datetime.today() + today = timezone.now() p2.user.last_login = today p2.user.save() p4.user.last_login = today - datetime.timedelta(days=30) diff --git a/ietf/review/factories.py b/ietf/review/factories.py index 96eede80da7..d6780fad80f 100644 --- a/ietf/review/factories.py +++ b/ietf/review/factories.py @@ -41,7 +41,7 @@ class Meta: type_id = 'lc' doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft') team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='review') - deadline = datetime.datetime.today()+datetime.timedelta(days=14) + deadline = timezone.now()+datetime.timedelta(days=14) requested_by = factory.SubFactory('ietf.person.factories.PersonFactory') class ReviewAssignmentFactory(factory.django.DjangoModelFactory): diff --git a/ietf/secr/proceedings/tests_reports.py b/ietf/secr/proceedings/tests_reports.py index 6039cfced18..034dbe5fa58 100644 --- a/ietf/secr/proceedings/tests_reports.py +++ b/ietf/secr/proceedings/tests_reports.py @@ -1,6 +1,8 @@ import datetime import debug # pyflakes:ignore +from django.utils import timezone + from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory from ietf.secr.proceedings.reports import report_id_activity, report_progress_report from ietf.utils.test_utils import TestCase @@ -10,7 +12,7 @@ class ReportsTestCase(TestCase): def test_report_id_activity(self): - today = datetime.datetime.today() + today = timezone.now() yesterday = today - datetime.timedelta(days=1) last_quarter = today - datetime.timedelta(days=3*30) next_week = today+datetime.timedelta(days=7) @@ -24,7 +26,7 @@ def test_report_id_activity(self): self.assertTrue('IETF Activity since last IETF Meeting' in result) def test_report_progress_report(self): - today = datetime.datetime.today() + today = timezone.now() last_quarter = today - datetime.timedelta(days=3*30) next_week = today+datetime.timedelta(days=7) diff --git a/ietf/secr/telechat/tests.py b/ietf/secr/telechat/tests.py index f664745d768..7a65cb40d83 100644 --- a/ietf/secr/telechat/tests.py +++ b/ietf/secr/telechat/tests.py @@ -8,6 +8,7 @@ import debug # pyflakes:ignore from django.urls import reverse +from django.utils import timezone from ietf.doc.factories import (WgDraftFactory, IndividualRfcFactory, CharterFactory, IndividualDraftFactory, ConflictReviewFactory) @@ -22,7 +23,7 @@ SECR_USER='secretary' def augment_data(): - TelechatDate.objects.create(date=datetime.datetime.today()) + TelechatDate.objects.create(date=timezone.now()) class SecrTelechatTestCase(TestCase): def test_main(self): @@ -138,7 +139,7 @@ def test_doc_detail_charter(self): self.assertEqual(q("#telechat-positions-table").find("th:contains('No Record')").length,1) def test_bash(self): - today = datetime.datetime.today() + today = timezone.now() TelechatDate.objects.create(date=today) url = reverse('ietf.secr.telechat.views.bash',kwargs={'date':today.strftime('%Y-%m-%d')}) self.client.login(username="secretary", password="secretary+password") diff --git a/ietf/stats/management/commands/fetch_meeting_attendance.py b/ietf/stats/management/commands/fetch_meeting_attendance.py index 5078c7cee1d..7f936531d43 100644 --- a/ietf/stats/management/commands/fetch_meeting_attendance.py +++ b/ietf/stats/management/commands/fetch_meeting_attendance.py @@ -1,10 +1,10 @@ # Copyright The IETF Trust 2017-2019, All Rights Reserved # Copyright 2016 IETF Trust -import datetime import syslog from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone import debug # pyflakes:ignore @@ -32,7 +32,7 @@ def handle(self, *args, **options): elif options['all']: meetings = Meeting.objects.filter(type="ietf").order_by("date") elif options['latest']: - meetings = Meeting.objects.filter(type="ietf", date__lte=datetime.datetime.today()).order_by("-date")[:options['latest']] + meetings = Meeting.objects.filter(type="ietf", date__lte=timezone.now()).order_by("-date")[:options['latest']] else: raise CommandError("Please use one of --meeting, --all or --latest") diff --git a/ietf/stats/views.py b/ietf/stats/views.py index 90d953ce5fc..47bb7078703 100644 --- a/ietf/stats/views.py +++ b/ietf/stats/views.py @@ -18,6 +18,7 @@ from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.safestring import mark_safe from django.utils.text import slugify @@ -197,7 +198,7 @@ def build_document_stats_url(stats_type_override=Ellipsis, get_overrides=None): if "y" in time_choice: try: y = int(time_choice.rstrip("y")) - from_time = datetime.datetime.today() - dateutil.relativedelta.relativedelta(years=y) + from_time = timezone.now() - dateutil.relativedelta.relativedelta(years=y) except ValueError: pass From e54b3b5ca409cfb8a6cbb1915afbf9183fcf2ba9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 12 Jul 2022 16:32:50 -0300 Subject: [PATCH 04/39] 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. --- ietf/idindex/index.py | 3 ++- ietf/meeting/views.py | 2 +- ietf/submit/forms.py | 3 ++- ietf/utils/test_runner.py | 4 ++-- ietf/utils/tests_meetecho.py | 3 ++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py index b641f5c6792..782e8912e5e 100644 --- a/ietf/idindex/index.py +++ b/ietf/idindex/index.py @@ -11,6 +11,7 @@ from django.conf import settings from django.template.loader import render_to_string +from django.utils import timezone import debug # pyflakes:ignore @@ -296,6 +297,6 @@ def id_index_txt(with_abstracts=False): return render_to_string("idindex/id_index.txt", { 'groups': groups, - 'time': datetime.datetime.now(pytz.utc).strftime("%Y-%m-%d %H:%M:%S %Z"), + 'time': timezone.now().astimezone(pytz.utc).strftime("%Y-%m-%d %H:%M:%S %Z"), 'with_abstracts': with_abstracts, }) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index f8f41d6a119..1a97730947a 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1560,7 +1560,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc="" "updated": updated, "filter_categories": filter_organizer.get_filter_categories(), "non_area_keywords": filter_organizer.get_non_area_keywords(), - "now": datetime.datetime.now(pytz.utc), + "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, diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index f9789459496..f4d4de1affa 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -20,6 +20,7 @@ from django.conf import settings from django.utils.html import mark_safe # type:ignore from django.urls import reverse as urlreverse +from django.utils import timezone from django.utils.encoding import force_str import debug # pyflakes:ignore @@ -75,7 +76,7 @@ def __init__(self, request, *args, **kwargs): self.base_formats = None # None will raise an exception in clean() if this isn't changed in a subclass def set_cutoff_warnings(self): - now = datetime.datetime.now(pytz.utc) + now = timezone.now().astimezone(pytz.utc) meeting = Meeting.get_current_meeting() if not meeting: return diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 11b544294f3..cbc9082a0fa 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -43,7 +43,6 @@ import pytz import importlib import socket -import datetime import gzip import unittest import pathlib @@ -75,6 +74,7 @@ from django.urls import URLResolver # type: ignore from django.template.backends.django import DjangoTemplates from django.template.backends.django import Template # type: ignore[attr-defined] +from django.utils import timezone # from django.utils.safestring import mark_safe import debug # pyflakes:ignore @@ -748,7 +748,7 @@ def setup_test_environment(self, **kwargs): with io.open(self.coverage_file, encoding='utf-8') as file: self.coverage_master = json.load(file) self.coverage_data = { - "time": datetime.datetime.now(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "time": timezone.now().astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "template": { "coverage": 0.0, "covered": {}, diff --git a/ietf/utils/tests_meetecho.py b/ietf/utils/tests_meetecho.py index f279d42a9ed..db3d36f4051 100644 --- a/ietf/utils/tests_meetecho.py +++ b/ietf/utils/tests_meetecho.py @@ -10,6 +10,7 @@ from django.conf import settings from django.test import override_settings +from django.utils import timezone from ietf.utils.tests import TestCase from .meetecho import Conference, ConferenceManager, MeetechoAPI, MeetechoAPIError @@ -249,7 +250,7 @@ def test_request_helper_exception(self): def test_time_serialization(self): """Time de/serialization should be consistent""" - time = datetime.datetime.now(pytz.utc).replace(microsecond=0) # cut off to 0 microseconds + time = timezone.now().astimezone(pytz.utc).replace(microsecond=0) # cut off to 0 microseconds api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET) self.assertEqual(api._deserialize_time(api._serialize_time(time)), time) From 50fba74d24d3aaad5204c78a68c0a64f22429942 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 12 Jul 2022 17:45:06 -0300 Subject: [PATCH 05/39] refactor: revert datetime.today() change to old migrations --- ietf/meeting/migrations/0013_make_separate_break_sessobj.py | 4 ++-- ietf/meeting/migrations/0020_remove_future_break_sessions.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/migrations/0013_make_separate_break_sessobj.py b/ietf/meeting/migrations/0013_make_separate_break_sessobj.py index 5ba0cb1044b..ceea5179d2b 100644 --- a/ietf/meeting/migrations/0013_make_separate_break_sessobj.py +++ b/ietf/meeting/migrations/0013_make_separate_break_sessobj.py @@ -3,8 +3,8 @@ # Generated by Django 1.11.20 on 2019-03-23 06:11 +import datetime from django.db import migrations -from django.utils import timezone def copy_session(session): @@ -15,7 +15,7 @@ def copy_session(session): def forward(apps, schema_editor): Meeting = apps.get_model('meeting', 'Meeting') - today = timezone.now() + today = datetime.datetime.today() meetings = Meeting.objects.filter(date__gt=today, type='ietf') for meeting in meetings: sessions = meeting.session_set.filter(type__in=['break', 'reg']) diff --git a/ietf/meeting/migrations/0020_remove_future_break_sessions.py b/ietf/meeting/migrations/0020_remove_future_break_sessions.py index 37b62bb7416..5d2569b1cce 100644 --- a/ietf/meeting/migrations/0020_remove_future_break_sessions.py +++ b/ietf/meeting/migrations/0020_remove_future_break_sessions.py @@ -3,13 +3,13 @@ # Generated by Django 1.11.22 on 2019-07-22 14:56 +import datetime from django.db import migrations -from django.utils import timezone def forward(apps, schema_editor): Meeting = apps.get_model('meeting', 'Meeting') - today = timezone.now() + today = datetime.datetime.today() meetings = Meeting.objects.filter(date__gt=today, type='ietf') for meeting in meetings: meeting.agenda.assignments.all().delete() From d523839c932b5cd7ad8492d6490b42e57654bbac Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 18 Jul 2022 12:26:23 -0300 Subject: [PATCH 06/39] refactor: change a missed datetime.now to timezone.now --- ietf/bin/send-scheduled-mail | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ietf/bin/send-scheduled-mail b/ietf/bin/send-scheduled-mail index c6a2dbf5049..d3474db094c 100755 --- a/ietf/bin/send-scheduled-mail +++ b/ietf/bin/send-scheduled-mail @@ -3,7 +3,7 @@ # This script requires that the proper virtual python environment has been # invoked before start -import datetime, os, sys +import os, sys import syslog # boilerplate @@ -16,6 +16,7 @@ syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER) import django django.setup() +from django.utils import timezone from ietf.utils.mail import log_smtp_exception, send_error_email from smtplib import SMTPException @@ -32,7 +33,7 @@ from ietf.message.models import SendQueue mode = sys.argv[1] -now = datetime.datetime.now() +now = timezone.now() needs_sending = SendQueue.objects.filter(sent_at=None).select_related("message") if mode == "specific": From 252cb37a87f82f1c9541b577b1a4e65dab0a4e5a Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 18 Jul 2022 13:25:37 -0300 Subject: [PATCH 07/39] chore: renumber timezone_now migration --- ...roup_models.py => 0059_use_timezone_now_for_group_models.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ietf/group/migrations/{0057_use_timezone_now_for_group_models.py => 0059_use_timezone_now_for_group_models.py} (93%) diff --git a/ietf/group/migrations/0057_use_timezone_now_for_group_models.py b/ietf/group/migrations/0059_use_timezone_now_for_group_models.py similarity index 93% rename from ietf/group/migrations/0057_use_timezone_now_for_group_models.py rename to ietf/group/migrations/0059_use_timezone_now_for_group_models.py index 85347b2e55e..24c083855bf 100644 --- a/ietf/group/migrations/0057_use_timezone_now_for_group_models.py +++ b/ietf/group/migrations/0059_use_timezone_now_for_group_models.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('group', '0056_dir_chair_groupman_role'), + ('group', '0058_alter_has_default_chat'), ] operations = [ From 5e3a80ff63404b34782d604b177233a0cad37160 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 19 Jul 2022 16:49:48 -0300 Subject: [PATCH 08/39] chore: add migration to change timestamps to UTC --- .../0002_convert_timestamps_to_utc.py | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 ietf/utils/migrations/0002_convert_timestamps_to_utc.py diff --git a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py new file mode 100644 index 00000000000..dd6aced6e93 --- /dev/null +++ b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py @@ -0,0 +1,245 @@ +# Generated by Django 2.2.28 on 2022-06-21 11:44 + +from django.db import migrations, connection + + +# to generate the expected list: +# +# from django.db import connection +# from pprint import pp +# cursor = connection.cursor() +# cursor.execute(""" +# SELECT table_name, column_name +# FROM information_schema.columns +# WHERE table_schema='ietf_utf8' AND column_type LIKE 'datetime%' +# ORDER BY table_name, column_name; +# """) +# pp(cursor.fetchall()) +# +expected_datetime_columns = ( + ('auth_user', 'date_joined'), + ('auth_user', 'last_login'), + ('community_documentchangedates', 'new_version_date'), + ('community_documentchangedates', 'normal_change_date'), + ('community_documentchangedates', 'significant_change_date'), + ('django_admin_log', 'action_time'), + ('django_migrations', 'applied'), + ('django_session', 'expire_date'), + ('doc_ballotpositiondocevent', 'comment_time'), + ('doc_ballotpositiondocevent', 'discuss_time'), + ('doc_deletedevent', 'time'), + ('doc_docevent', 'time'), + ('doc_dochistory', 'expires'), + ('doc_dochistory', 'time'), + ('doc_docreminder', 'due'), + ('doc_document', 'expires'), + ('doc_document', 'time'), + ('doc_documentactionholder', 'time_added'), + ('doc_initialreviewdocevent', 'expires'), + ('doc_irsgballotdocevent', 'duedate'), + ('doc_lastcalldocevent', 'expires'), + ('group_group', 'time'), + ('group_groupevent', 'time'), + ('group_grouphistory', 'time'), + ('group_groupmilestone', 'time'), + ('group_groupmilestonehistory', 'time'), + ('ipr_iprdisclosurebase', 'time'), + ('ipr_iprevent', 'response_due'), + ('ipr_iprevent', 'time'), + ('liaisons_liaisonstatementevent', 'time'), + ('mailinglists_subscribed', 'time'), + ('mailinglists_whitelisted', 'time'), + ('meeting_floorplan', 'modified'), + ('meeting_room', 'modified'), + ('meeting_schedtimesessassignment', 'modified'), + ('meeting_schedulingevent', 'time'), + ('meeting_session', 'modified'), + ('meeting_session', 'scheduled'), + ('meeting_slidesubmission', 'time'), + ('meeting_timeslot', 'modified'), + ('meeting_timeslot', 'time'), + ('message_message', 'sent'), + ('message_message', 'time'), + ('message_sendqueue', 'send_at'), + ('message_sendqueue', 'sent_at'), + ('message_sendqueue', 'time'), + ('nomcom_feedback', 'time'), + ('nomcom_feedbacklastseen', 'time'), + ('nomcom_nomination', 'time'), + ('nomcom_nomineeposition', 'time'), + ('nomcom_topicfeedbacklastseen', 'time'), + ('oidc_provider_code', 'expires_at'), + ('oidc_provider_token', 'expires_at'), + ('oidc_provider_userconsent', 'date_given'), + ('oidc_provider_userconsent', 'expires_at'), + ('person_email', 'time'), + ('person_historicalemail', 'history_date'), + ('person_historicalemail', 'time'), + ('person_historicalperson', 'history_date'), + ('person_historicalperson', 'time'), + ('person_person', 'time'), + ('person_personalapikey', 'created'), + ('person_personalapikey', 'latest'), + ('person_personevent', 'time'), + ('request_profiler_profilingrecord', 'end_ts'), + ('request_profiler_profilingrecord', 'start_ts'), + ('review_historicalreviewassignment', 'assigned_on'), + ('review_historicalreviewassignment', 'completed_on'), + ('review_historicalreviewassignment', 'history_date'), + ('review_historicalreviewersettings', 'history_date'), + ('review_historicalreviewrequest', 'history_date'), + ('review_historicalreviewrequest', 'time'), + ('review_historicalunavailableperiod', 'history_date'), + ('review_reviewassignment', 'assigned_on'), + ('review_reviewassignment', 'completed_on'), + ('review_reviewrequest', 'time'), + ('review_reviewwish', 'time'), + ('south_migrationhistory', 'applied'), + ('submit_preapproval', 'time'), + ('submit_submissioncheck', 'time'), + ('submit_submissionevent', 'time'), + ('tastypie_apikey', 'created'), + ('utils_dumpinfo', 'date'), + ('utils_versioninfo', 'time'), +) + + +def forward(apps, schema_editor): + with connection.cursor() as cursor: + # Check that we have timezones. + # If these assertions fail, the DB does not know all the necessary time zones. + # To load timezones, + # $ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql + # (on a dev system, first connect to the db image with `docker compose exec db bash`) + cursor.execute("SELECT CONVERT_TZ('2022-06-22T17:43:00', 'PST8PDT', 'UTC');") + assert not any(None in row for row in cursor.fetchall()), 'database does not recognize PST8PDT' + cursor.execute( + "SELECT CONVERT_TZ('2022-06-22T17:43:00', time_zone, 'UTC') FROM meeting_meeting WHERE time_zone != '';" + ) + assert not any(None in row for row in cursor.fetchall()), 'database does not recognize a meeting time zone' + + # check that we have all and only the expected datetime columns to work with + # if this fails, figure out what changed and decide how to proceed safely + cursor.execute(""" + SELECT table_name, column_name + FROM information_schema.columns + WHERE table_schema='ietf_utf8' AND column_type LIKE 'datetime%' + ORDER BY table_name, column_name; + """) + assert cursor.fetchall() == expected_datetime_columns, 'unexpected or missing datetime columns in db' + + +class Migration(migrations.Migration): + dependencies = [ + ('utils', '0001_initial'), + ] + + # To generate the queries: + # + # pst8pdt_columns = [e for e in expected_datetime_columns if e != ('meeting_timeslot', 'time')] + # queries = [] + # for table, column in pst8pdt_columns: + # queries.append(f"UPDATE {table} SET {column} = CONVERT_TZ({column}, 'PST8PDT', 'UTC');") + # + # queries.append(""" + # UPDATE meeting_timeslot + # JOIN meeting_meeting + # ON meeting_meeting.id = meeting_id + # SET time = CONVERT_TZ(time, IF(time_zone != '', time_zone, 'PST8PDT'), 'UTC'); + # """) + # + # print("\n".join(queries)) + # + operations = [ + migrations.RunPython(forward), + migrations.RunSQL(""" +UPDATE auth_user SET date_joined = CONVERT_TZ(date_joined, 'PST8PDT', 'UTC'); +UPDATE auth_user SET last_login = CONVERT_TZ(last_login, 'PST8PDT', 'UTC'); +UPDATE community_documentchangedates SET new_version_date = CONVERT_TZ(new_version_date, 'PST8PDT', 'UTC'); +UPDATE community_documentchangedates SET normal_change_date = CONVERT_TZ(normal_change_date, 'PST8PDT', 'UTC'); +UPDATE community_documentchangedates SET significant_change_date = CONVERT_TZ(significant_change_date, 'PST8PDT', 'UTC'); +UPDATE django_admin_log SET action_time = CONVERT_TZ(action_time, 'PST8PDT', 'UTC'); +UPDATE django_migrations SET applied = CONVERT_TZ(applied, 'PST8PDT', 'UTC'); +UPDATE django_session SET expire_date = CONVERT_TZ(expire_date, 'PST8PDT', 'UTC'); +UPDATE doc_ballotpositiondocevent SET comment_time = CONVERT_TZ(comment_time, 'PST8PDT', 'UTC'); +UPDATE doc_ballotpositiondocevent SET discuss_time = CONVERT_TZ(discuss_time, 'PST8PDT', 'UTC'); +UPDATE doc_deletedevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE doc_docevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE doc_dochistory SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC'); +UPDATE doc_dochistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE doc_docreminder SET due = CONVERT_TZ(due, 'PST8PDT', 'UTC'); +UPDATE doc_document SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC'); +UPDATE doc_document SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE doc_documentactionholder SET time_added = CONVERT_TZ(time_added, 'PST8PDT', 'UTC'); +UPDATE doc_initialreviewdocevent SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC'); +UPDATE doc_irsgballotdocevent SET duedate = CONVERT_TZ(duedate, 'PST8PDT', 'UTC'); +UPDATE doc_lastcalldocevent SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC'); +UPDATE group_group SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE group_groupevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE group_grouphistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE group_groupmilestone SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE group_groupmilestonehistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE ipr_iprdisclosurebase SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE ipr_iprevent SET response_due = CONVERT_TZ(response_due, 'PST8PDT', 'UTC'); +UPDATE ipr_iprevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE liaisons_liaisonstatementevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE mailinglists_subscribed SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE mailinglists_whitelisted SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE meeting_floorplan SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC'); +UPDATE meeting_room SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC'); +UPDATE meeting_schedtimesessassignment SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC'); +UPDATE meeting_schedulingevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE meeting_session SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC'); +UPDATE meeting_session SET scheduled = CONVERT_TZ(scheduled, 'PST8PDT', 'UTC'); +UPDATE meeting_slidesubmission SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE meeting_timeslot SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC'); +UPDATE message_message SET sent = CONVERT_TZ(sent, 'PST8PDT', 'UTC'); +UPDATE message_message SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE message_sendqueue SET send_at = CONVERT_TZ(send_at, 'PST8PDT', 'UTC'); +UPDATE message_sendqueue SET sent_at = CONVERT_TZ(sent_at, 'PST8PDT', 'UTC'); +UPDATE message_sendqueue SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE nomcom_feedback SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE nomcom_feedbacklastseen SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE nomcom_nomination SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE nomcom_nomineeposition SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE nomcom_topicfeedbacklastseen SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE oidc_provider_code SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC'); +UPDATE oidc_provider_token SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC'); +UPDATE oidc_provider_userconsent SET date_given = CONVERT_TZ(date_given, 'PST8PDT', 'UTC'); +UPDATE oidc_provider_userconsent SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC'); +UPDATE person_email SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE person_historicalemail SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE person_historicalemail SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE person_historicalperson SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE person_historicalperson SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE person_person SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE person_personalapikey SET created = CONVERT_TZ(created, 'PST8PDT', 'UTC'); +UPDATE person_personalapikey SET latest = CONVERT_TZ(latest, 'PST8PDT', 'UTC'); +UPDATE person_personevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE request_profiler_profilingrecord SET end_ts = CONVERT_TZ(end_ts, 'PST8PDT', 'UTC'); +UPDATE request_profiler_profilingrecord SET start_ts = CONVERT_TZ(start_ts, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewassignment SET assigned_on = CONVERT_TZ(assigned_on, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewassignment SET completed_on = CONVERT_TZ(completed_on, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewassignment SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewersettings SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewrequest SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE review_historicalreviewrequest SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE review_historicalunavailableperiod SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC'); +UPDATE review_reviewassignment SET assigned_on = CONVERT_TZ(assigned_on, 'PST8PDT', 'UTC'); +UPDATE review_reviewassignment SET completed_on = CONVERT_TZ(completed_on, 'PST8PDT', 'UTC'); +UPDATE review_reviewrequest SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE review_reviewwish SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE south_migrationhistory SET applied = CONVERT_TZ(applied, 'PST8PDT', 'UTC'); +UPDATE submit_preapproval SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE submit_submissioncheck SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE submit_submissionevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); +UPDATE tastypie_apikey SET created = CONVERT_TZ(created, 'PST8PDT', 'UTC'); +UPDATE utils_dumpinfo SET date = CONVERT_TZ(date, 'PST8PDT', 'UTC'); +UPDATE utils_versioninfo SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC'); + +UPDATE meeting_timeslot + JOIN meeting_meeting + ON meeting_meeting.id = meeting_id + SET time = CONVERT_TZ(time, IF(time_zone != '', time_zone, 'PST8PDT'), 'UTC'); +"""), + ] From 641e1096501255fb7e504bae16d3bb1ede5b543c Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 26 Jul 2022 15:08:49 -0400 Subject: [PATCH 09/39] refactor: move tz instantiation/caching from TimeSlot to Meeting --- ietf/meeting/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 1bec9d5be75..126e73098ff 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -358,6 +358,11 @@ def build_timeslices(self): # 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): if self.time_zone: try: @@ -609,12 +614,7 @@ def get_html_location(self): return self._cached_html_location def tz(self): - if not hasattr(self, '_cached_tz'): - if self.meeting.time_zone: - self._cached_tz = pytz.timezone(self.meeting.time_zone) - else: - self._cached_tz = None - return self._cached_tz + return self.meeting.tz() def tzname(self): if self.tz(): From 706a434f170c300e757ae679663748b36c7fcafe Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 26 Jul 2022 15:14:53 -0400 Subject: [PATCH 10/39] fix: assume utc if meeting.time_zone is blank --- ietf/meeting/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 126e73098ff..c37ed73ccaf 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -360,7 +360,7 @@ def build_timeslices(self): def tz(self): if not hasattr(self, '_cached_tz'): - self._cached_tz = pytz.timezone(self.time_zone) + self._cached_tz = pytz.timezone(self.time_zone) if self.time_zone else pytz.utc return self._cached_tz def vtimezone(self): From c9068c74e2159db56e5da94ed3163ee157e3a67d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 27 Jul 2022 14:20:59 -0400 Subject: [PATCH 11/39] chore: make datetime.combine() calls tz aware in the meeting app --- ietf/meeting/factories.py | 4 +- ietf/meeting/helpers.py | 21 ++- .../commands/create_dummy_meeting.py | 24 +-- .../commands/session_purpose_demo.py | 4 +- ietf/meeting/test_data.py | 29 +++- ietf/meeting/tests_js.py | 24 ++- ietf/meeting/tests_schedule_generator.py | 8 +- ietf/meeting/tests_views.py | 139 ++++++++++++------ ietf/meeting/utils.py | 20 ++- ietf/meeting/views.py | 10 +- ietf/utils/timezone.py | 34 ++++- 11 files changed, 227 insertions(+), 90 deletions(-) diff --git a/ietf/meeting/factories.py b/ietf/meeting/factories.py index e59b559f413..c2a7f8e8a44 100644 --- a/ietf/meeting/factories.py +++ b/ietf/meeting/factories.py @@ -187,7 +187,9 @@ def location(obj, create, extracted, **kwargs): # pylint: disable=no-self-argume @factory.lazy_attribute def time(self): - return datetime.datetime.combine(self.meeting.date,datetime.time(11,0)) + return self.tz().localize( + datetime.datetime.combine(self.meeting.date, datetime.time(11, 0)) + ) @factory.lazy_attribute def duration(self): diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py index 5f463b269ae..99be2dd8959 100644 --- a/ietf/meeting/helpers.py +++ b/ietf/meeting/helpers.py @@ -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 diff --git a/ietf/meeting/management/commands/create_dummy_meeting.py b/ietf/meeting/management/commands/create_dummy_meeting.py index 79b1e5db617..2a4b1dad74d 100644 --- a/ietf/meeting/management/commands/create_dummy_meeting.py +++ b/ietf/meeting/management/commands/create_dummy_meeting.py @@ -48,7 +48,7 @@ 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 @@ def add_arguments(self, parser): 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 @@ def handle(self, *args, **options): 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 @@ def handle(self, *args, **options): 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 @@ def handle(self, *args, **options): type_id='IETF', date=self._meeting_datetime(0).date(), days=7, - time_zone=meeting_tz, + time_zone=meeting_tzname, ) # Set enabled constraints diff --git a/ietf/meeting/management/commands/session_purpose_demo.py b/ietf/meeting/management/commands/session_purpose_demo.py index 624e89794f4..94fc000a8af 100644 --- a/ietf/meeting/management/commands/session_purpose_demo.py +++ b/ietf/meeting/management/commands/session_purpose_demo.py @@ -60,7 +60,9 @@ def install_demo(self): type_id=type_id, meeting=meeting, location=room, - time=datetime.datetime.combine(date, datetime.time(10, 0, 0)) + datetime.timedelta(hours=n), + time=meeting.tz().localize( + datetime.datetime.combine(date, datetime.time(10, 0, 0)) + datetime.timedelta(hours=n) + ), duration=datetime.timedelta(hours=1), ) diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py index e5fdd71c571..09f3ee734e9 100644 --- a/ietf/meeting/test_data.py +++ b/ietf/meeting/test_data.py @@ -23,8 +23,10 @@ def make_interim_meeting(group,date,status='sched'): system_person = Person.objects.get(name="(System)") - time = datetime.datetime.combine(date, datetime.time(9)) meeting = create_interim_meeting(group=group,date=date) + 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, diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index f62e41388a0..7825c8ee63b 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -1929,7 +1929,9 @@ def _assert_not_wrapped(displayed, expected_time_string): ) 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) @@ -2688,13 +2690,17 @@ def do_delete_time_interval_test(self, cancel=False): 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 +2717,9 @@ def do_delete_time_interval_test(self, cancel=False): '[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 + ).astimezone(pytz.utc).strftime( '%H:%M' )) ) @@ -2733,14 +2741,18 @@ def do_delete_day_test(self, cancel=False): 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 ] diff --git a/ietf/meeting/tests_schedule_generator.py b/ietf/meeting/tests_schedule_generator.py index d414805d391..2ec13f19f7b 100644 --- a/ietf/meeting/tests_schedule_generator.py +++ b/ietf/meeting/tests_schedule_generator.py @@ -36,9 +36,11 @@ def setUp(self): 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), ) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 2b6b20d1c97..ca0a45e22cd 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -52,6 +52,7 @@ 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 @@ -430,7 +431,9 @@ def test_agenda_iab_session(self): 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 +804,12 @@ def test_group_ical(self): 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) # @@ -1105,7 +1113,9 @@ def test_room_grouping(self): 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 +1199,11 @@ def _setup_for_swap_timeslots(self): ] # 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 +1298,7 @@ def test_swap_timeslots_denies_past(self): 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 +1480,18 @@ def test_swap_days_denies_past(self): 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 +1525,12 @@ def test_swap_days_denies_past(self): # 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 +1553,8 @@ def _decode_json_response(self, r): 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 +1566,7 @@ def test_assign_session(self): 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 +1626,7 @@ def test_reassign_session(self): 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 +1761,7 @@ def test_unassign_session(self): 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, @@ -2068,7 +2093,7 @@ def _col_index(elt): 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 +2174,21 @@ def test_bulk_delete_buttons_exist(self): 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).date(), + 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).date(), + datetime.time(hour=14), + ) ), ) @@ -2258,9 +2287,8 @@ def test_edit_timeslot(self): name_before = 'Name Classic (tm)' type_before = 'regular' - time_before = datetime.datetime.combine( - meeting.date, - datetime.time(hour=10), + time_before = meeting.tz().localize( + datetime.datetime.combine(meeting.date, datetime.time(hour=10)) ) duration_before = datetime.timedelta(minutes=60) show_location_before = True @@ -3288,7 +3316,9 @@ def test_edit_meeting_schedule(self): 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 +3540,12 @@ def test_edit_meeting_timeslots_and_misc_sessions(self): 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 +3587,12 @@ def test_edit_meeting_timeslots_and_misc_sessions(self): }) 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) @@ -4679,9 +4719,9 @@ def test_interim_request_options(self): 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 +4790,14 @@ def test_interim_request_single_virtual_settings_approval_not_required(self): 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 +4838,17 @@ def test_interim_request_single_in_person(self): 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 +4965,7 @@ def test_interim_request_multi_day_cancel(self): 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 +4975,15 @@ def test_interim_request_series(self): 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' @@ -5630,7 +5673,11 @@ def test_group_ical(self): 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) # diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index f6c1800f10a..25366e7bd4e 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -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'): diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index e4fc9dc17bc..28eac5bdd85 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1151,7 +1151,7 @@ def redirect_with_scroll(): 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 +1200,7 @@ def redirect_with_scroll(): 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'] @@ -2386,7 +2386,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) @@ -4102,7 +4104,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( diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index 723512efeb6..4d3f4be5f54 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -3,6 +3,7 @@ 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() From ffc782a913e3225a6f67d4f5980ccf36782b571b Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 27 Jul 2022 14:28:54 -0400 Subject: [PATCH 12/39] ci: correctly use meeting.tz in TimeSlotFactory --- ietf/meeting/factories.py | 2 +- ietf/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/meeting/factories.py b/ietf/meeting/factories.py index c2a7f8e8a44..cf3c87e7c84 100644 --- a/ietf/meeting/factories.py +++ b/ietf/meeting/factories.py @@ -187,7 +187,7 @@ def location(obj, create, extracted, **kwargs): # pylint: disable=no-self-argume @factory.lazy_attribute def time(self): - return self.tz().localize( + return self.meeting.tz().localize( datetime.datetime.combine(self.meeting.date, datetime.time(11, 0)) ) diff --git a/ietf/settings.py b/ietf/settings.py index 740862dda0d..3e946ccd4c3 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -113,7 +113,7 @@ # 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/' From 75854cbc23d2572ea10817fa175f35060124a481 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 27 Jul 2022 14:46:37 -0400 Subject: [PATCH 13/39] chore: compute TimeSlot utc / local times assuming tz-aware times --- ietf/meeting/models.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index c37ed73ccaf..8e0f13c1b86 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -617,33 +617,19 @@ def tz(self): return self.meeting.tz() def tzname(self): - if self.tz(): - return self.tz().tzname(self.time) - else: - return "" + return self.tz().tzname(self.time) def utc_start_time(self): - if self.tz(): - local_start_time = self.tz().localize(self.time) - return local_start_time.astimezone(pytz.utc) - else: - return None + return self.time.astimezone(pytz.utc) 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.utc_start_time() + self.duration def local_start_time(self): - if self.tz(): - return self.tz().localize(self.time) - else: - return None + 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 + self.duration).astimezone(self.tz()) @property def js_identifier(self): From 28f7cc6d8dcf87b5be878c44038a4dd24108e0df Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 28 Jul 2022 14:19:10 -0300 Subject: [PATCH 14/39] chore: use tzaware math for agenda editor timeslot layout --- ietf/meeting/views.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 28eac5bdd85..52e33abf0e4 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1287,8 +1287,9 @@ def redirect_with_scroll(): 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 +1311,14 @@ def redirect_with_scroll(): 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) From 37c6ea26502d95e7fcf7520e0baadd1cffee6932 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 19:14:25 -0300 Subject: [PATCH 15/39] 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. --- .../0054_fill_in_empty_meeting_time_zone.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py diff --git a/ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py b/ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py new file mode 100644 index 00000000000..cdf3f0cebea --- /dev/null +++ b/ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.28 on 2022-08-08 11:37 + +import datetime + +from django.db import migrations + + +# date of last meeting with an empty time_zone before this migration +LAST_EMPTY_TZ = datetime.date(2022, 7, 1) + + +def forward(apps, schema_editor): + Meeting = apps.get_model('meeting', 'Meeting') + + # Check that we will be able to identify the migrated meetings later + old_meetings_in_pst8pdt = Meeting.objects.filter(type_id='interim', time_zone='PST8PDT', date__lte=LAST_EMPTY_TZ) + assert old_meetings_in_pst8pdt.count() == 0, 'not expecting interim meetings in PST8PDT time_zone' + + meetings_with_empty_tz = Meeting.objects.filter(time_zone='') + # check our expected conditions + for mtg in meetings_with_empty_tz: + assert mtg.type_id == 'interim', 'was not expecting non-interim meetings to be affected' + assert mtg.date <= LAST_EMPTY_TZ, 'affected meeting outside expected date range' + mtg.time_zone = 'PST8PDT' + + # commit the changes + Meeting.objects.bulk_update(meetings_with_empty_tz, ['time_zone']) + + +def reverse(apps, schema_editor): + Meeting = apps.get_model('meeting', 'Meeting') + meetings_to_restore = Meeting.objects.filter(time_zone='PST8PDT', date__lte=LAST_EMPTY_TZ) + for mtg in meetings_to_restore: + mtg.time_zone = '' + # commit the changes + Meeting.objects.bulk_update(meetings_to_restore, ['time_zone']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0053_attended'), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] From 13db2053e676303491d7888a583fb88876cfe38d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 19:17:12 -0300 Subject: [PATCH 16/39] chore: disallow blank Meeting.time_zone value --- .../0055_meeting_time_zone_not_blank.py | 18 ++++++++++++++++++ ietf/meeting/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py diff --git a/ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py b/ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py new file mode 100644 index 00000000000..c6e8393908a --- /dev/null +++ b/ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2022-08-08 11:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0054_fill_in_empty_meeting_time_zone'), + ] + + operations = [ + migrations.AlterField( + model_name='meeting', + name='time_zone', + field=models.CharField(choices=[('', '---------'), ('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('UTC', 'UTC')], default='UTC', max_length=255), + ), + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 19aff9e2943..85f0066f03b 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -86,7 +86,7 @@ class Meeting(models.Model): # We can't derive time-zone from country, as there are some that have # more than one timezone, and the pytz module doesn't provide timezone # lookup information for all relevant city/country combinations. - time_zone = models.CharField(blank=True, max_length=255, choices=timezones) + time_zone = models.CharField(max_length=255, choices=timezones, default='UTC') idsubmit_cutoff_day_offset_00 = models.IntegerField(blank=True, default=settings.IDSUBMIT_DEFAULT_CUTOFF_DAY_OFFSET_00, help_text = "The number of days before the meeting start date when the submission of -00 drafts will be closed.") From c94fb8a9c3f375bd9dd203e6850f9bef54c74724 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 19:17:37 -0300 Subject: [PATCH 17/39] refactor: no need to handle blank time_zone case in TZ migration --- ietf/utils/migrations/0002_convert_timestamps_to_utc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py index dd6aced6e93..5df3051eaeb 100644 --- a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py +++ b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py @@ -132,6 +132,7 @@ def forward(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ ('utils', '0001_initial'), + ('meeting', '0055_meeting_time_zone_not_blank'), ] # To generate the queries: @@ -240,6 +241,6 @@ class Migration(migrations.Migration): UPDATE meeting_timeslot JOIN meeting_meeting ON meeting_meeting.id = meeting_id - SET time = CONVERT_TZ(time, IF(time_zone != '', time_zone, 'PST8PDT'), 'UTC'); + SET time = CONVERT_TZ(time, time_zone, 'UTC'); """), ] From a59ca8d4d3b7e4ec98c405bb13d2ea194ecd6fd9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 19:51:02 -0300 Subject: [PATCH 18/39] refactor: remove now-unnecessary checks that meeting has time_zone --- ietf/meeting/models.py | 46 +++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 85f0066f03b..415f823e1b4 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -358,19 +358,17 @@ def build_timeslices(self): # timeslot = ts) def vtimezone(self): - if self.time_zone: - try: - tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics") - if os.path.exists(tzfn): - with io.open(tzfn) as tzf: - icstext = tzf.read() - vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip() - if vtimezone: - vtimezone += "\n" - return vtimezone - except IOError: - pass - return '' + try: + tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics") + if os.path.exists(tzfn): + with io.open(tzfn) as tzf: + icstext = tzf.read() + vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip() + if vtimezone: + vtimezone += "\n" + return vtimezone + except IOError: + pass def set_official_schedule(self, schedule): if self.schedule != schedule: @@ -609,24 +607,15 @@ def get_html_location(self): def tz(self): if not hasattr(self, '_cached_tz'): - if self.meeting.time_zone: - self._cached_tz = pytz.timezone(self.meeting.time_zone) - else: - self._cached_tz = None + self._cached_tz = pytz.timezone(self.meeting.time_zone) return self._cached_tz def tzname(self): - if self.tz(): - return self.tz().tzname(self.time) - else: - return "" + return self.tz().tzname(self.time) def utc_start_time(self): - if self.tz(): - local_start_time = self.tz().localize(self.time) - return local_start_time.astimezone(pytz.utc) - else: - return None + local_start_time = self.tz().localize(self.time) + return local_start_time.astimezone(pytz.utc) def utc_end_time(self): utc_start = self.utc_start_time() @@ -634,10 +623,7 @@ def utc_end_time(self): return None if utc_start is None else utc_start + self.duration def local_start_time(self): - if self.tz(): - return self.tz().localize(self.time) - else: - return None + return self.tz().localize(self.time) def local_end_time(self): local_start = self.local_start_time() From 327aea9c6f297a90eb2d7a59cca20963ba8dee5a Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 20:20:52 -0300 Subject: [PATCH 19/39] chore: fix timezone handling in agenda.ics and Meeting.updated() --- ietf/meeting/models.py | 8 +++----- ietf/meeting/tests_views.py | 4 ++-- ietf/templates/meeting/agenda.ics | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 8e0f13c1b86..b6e2e3e1b30 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -384,16 +384,14 @@ def set_official_schedule(self, schedule): 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): diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index ca0a45e22cd..adf3f75c034 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -5687,8 +5687,8 @@ def test_group_ical(self): 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.time.astimezone(meeting.tz()).strftime('%Y%m%dT%H%M%S')) + self.assertContains(r, t2.time.astimezone(meeting.tz()).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, }) diff --git a/ietf/templates/meeting/agenda.ics b/ietf/templates/meeting/agenda.ics index 4f207afae36..18ec8def82f 100644 --- a/ietf/templates/meeting/agenda.ics +++ b/ietf/templates/meeting/agenda.ics @@ -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 %} From 978260285ea85529a4bf7749af7d9bc2475b2280 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 20:52:06 -0300 Subject: [PATCH 20/39] chore: fix tz handling in interim_request_details, exercise in tests --- ietf/meeting/test_data.py | 14 ++++----- ietf/meeting/tests_views.py | 4 +-- .../meeting/interim_request_details.html | 30 ++++++++++--------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py index 09f3ee734e9..9fd49f1c830 100644 --- a/ietf/meeting/test_data.py +++ b/ietf/meeting/test_data.py @@ -21,9 +21,9 @@ 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)") - 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)) ) @@ -228,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') @@ -240,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 diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index adf3f75c034..58df711d875 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -5125,14 +5125,14 @@ def test_can_manage_group(self): 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)) diff --git a/ietf/templates/meeting/interim_request_details.html b/ietf/templates/meeting/interim_request_details.html index 0cfec501be1..60d031955bd 100644 --- a/ietf/templates/meeting/interim_request_details.html +++ b/ietf/templates/meeting/interim_request_details.html @@ -1,7 +1,7 @@ {% 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 %} @@ -58,19 +58,21 @@

Interim Meeting Request Details

Date
-
- {{ assignment.timeslot.time|date:"Y-m-d" }} -
-
- Start time -
-
- {{ assignment.timeslot.time|date:"H:i" }} - {% if meeting.time_zone != 'UTC' %} - ({{ assignment.timeslot.utc_start_time|date:"H:i" }} UTC) - {% endif %} -
-
+ {% localtime off %} +
+ {{ assignment.timeslot.time|timezone:meeting.tz|date:"Y-m-d" }} +
+
+ Start time +
+
+ {{ assignment.timeslot.local_start_time|date:"H:i" }} + {% if meeting.time_zone != 'UTC' %} + ({{ assignment.timeslot.utc_start_time|date:"H:i" }} UTC) + {% endif %} +
+
+ {% endlocaltime %} Duration
From 4428e8d99a8456e2ce659f9b1f9576bad2163481 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 21:06:36 -0300 Subject: [PATCH 21/39] chore: fix timezone handling for test_interim_send_announcement --- ietf/meeting/tests_views.py | 4 +--- ietf/templates/meeting/interim_announcement.txt | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 58df711d875..61285dacc65 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -4369,12 +4369,10 @@ def test_interim_skip_announcement_with_extra_and_canceled_sessions_and_base_sch 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) diff --git a/ietf/templates/meeting/interim_announcement.txt b/ietf/templates/meeting/interim_announcement.txt index 07439409967..53c676ff6bb 100644 --- a/ietf/templates/meeting/interim_announcement.txt +++ b/ietf/templates/meeting/interim_announcement.txt @@ -1,13 +1,13 @@ -{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW. +{% load ietf_filters 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 %}. +{% localtime off %}{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.local_start_time | date:"H:i" }} to {{ assignments.first.timeslot.local_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 %}. {% 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.local_start_time | date:"Y-m-d" }} {{ assignment.timeslot.local_start_time | date:"H:i" }} to {{ assignment.timeslot.local_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 %} {% endfor %}{% endif %} -{% if meeting.city %}Meeting Location: +{% endlocaltime %}{% if meeting.city %}Meeting Location: {{ meeting.city }}, {{ meeting.country }} {% endif %}Agenda: From 50c76eff53b5135c55771ac49413d63fa18e2d17 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 8 Aug 2022 21:19:20 -0300 Subject: [PATCH 22/39] chore: fix timezone handling in agenda_json() --- ietf/meeting/views.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 52e33abf0e4..e1993cb4ca9 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -2313,16 +2313,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 From d6d1fbe3d4c903a9fefd3b36a01f678f30c424ac Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 12:42:41 -0300 Subject: [PATCH 23/39] chore: fix timezone handling in old agenda --- ietf/meeting/models.py | 4 ++-- ietf/meeting/tests_views.py | 17 ++++++++------ ietf/meeting/views.py | 4 ++-- ietf/templates/meeting/agenda.html | 23 ++++++++++--------- .../templates/meeting/timeslot_start_end.html | 10 ++++---- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index b6e2e3e1b30..c3547291b6c 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -618,10 +618,10 @@ def tzname(self): return self.tz().tzname(self.time) def utc_start_time(self): - return self.time.astimezone(pytz.utc) + return self.time # USE_TZ is True, so time is UTC def utc_end_time(self): - return self.utc_start_time() + self.duration + return self.time + self.duration # USE_TZ is True, so time is UTC def local_start_time(self): return self.time.astimezone(self.tz()) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 61285dacc65..473f795977f 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -229,7 +229,10 @@ def test_meeting_agenda(self): 'Time zone indicator should be in nav sidebar') # plain - time_interval = r"%s-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0")) + time_interval = r"{}-{}".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) @@ -819,16 +822,16 @@ def test_group_ical(self): 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 @@ -5685,8 +5688,8 @@ def test_group_ical(self): 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.astimezone(meeting.tz()).strftime('%Y%m%dT%H%M%S')) - self.assertContains(r, t2.time.astimezone(meeting.tz()).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, }) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index e1993cb4ca9..996eece6a87 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1569,7 +1569,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc="" "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, + "display_timezone": 'UTC' if utc else 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, @@ -1925,7 +1925,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, } diff --git a/ietf/templates/meeting/agenda.html b/ietf/templates/meeting/agenda.html index 1d037010ab2..56bb0ca9a6a 100644 --- a/ietf/templates/meeting/agenda.html +++ b/ietf/templates/meeting/agenda.html @@ -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 %} @@ -20,7 +20,7 @@ Switch to New Agenda Display {% endblock %} -{% block content %} +{% block content %}{% timezone display_timezone %} {% origin %} {% if "-utc" in request.path %} {% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="agenda-utc" title_extra="(UTC)" %} @@ -36,10 +36,10 @@ Jump to current session {% endif %}
- {% 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 %}
- Showing {{ timezone|split:"_"|join:" "|split:"/"|join:" / " }} time + Showing {{ display_timezone|split:"_"|join:" "|split:"/"|join:" / " }} time
@@ -63,7 +63,7 @@

{{ schedule.meeting.agenda_info_note|removetags:"h1"|safe }}

{% 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 %}
@@ -249,7 +249,8 @@

{% endif %} {% if item.slot_type.slug == 'plenary' %} - {% include "meeting/timeslot_start_end.html" %} + + {% include "meeting/timeslot_start_end.html" %} {% if item.timeslot.show_location and item.timeslot.location %} {% location_anchor item.timeslot %} @@ -327,11 +328,11 @@

{% if item.session.rescheduled_to %} TO
-
{% 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 %} @@ -359,7 +360,7 @@

{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting personalize=personalize only %} {% endif %} {% endcache %} -{% endblock %} +{% endtimezone %}{% endblock %} {% block js %} diff --git a/ietf/templates/meeting/timeslot_start_end.html b/ietf/templates/meeting/timeslot_start_end.html index 30cb89f5135..243c514c1a7 100644 --- a/ietf/templates/meeting/timeslot_start_end.html +++ b/ietf/templates/meeting/timeslot_start_end.html @@ -1,11 +1,7 @@ -{% load tz %}
+
-{# {% if "-utc" in request.path %}#} -{# {{ item.timeslot.time|utc|date:"H:i" }}
-{{ item.timeslot.end_time|utc|date:"H:i" }}#} -{# {% else %}#} - {{ item.timeslot.time|date:"H:i" }}
-{{ item.timeslot.end_time|date:"H:i" }} -{# {% endif %}#} + {{ item.timeslot.time|date:"H:i" }}
-{{ item.timeslot.end_time|date:"H:i" }}
\ No newline at end of file From 4f3800496d6c7001908872a23cb38412beaa5d67 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 16:04:04 -0300 Subject: [PATCH 26/39] chore: add timezone info to timestamps in fixtures --- ietf/meeting/fixtures/proceedings_templates.json | 2 +- ietf/name/fixtures/names.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ietf/meeting/fixtures/proceedings_templates.json b/ietf/meeting/fixtures/proceedings_templates.json index 1594debff23..97d38f566cb 100644 --- a/ietf/meeting/fixtures/proceedings_templates.json +++ b/ietf/meeting/fixtures/proceedings_templates.json @@ -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", diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index e8509628db5..bb2d60852d6 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -15984,7 +15984,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" }, @@ -15995,7 +15995,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" }, @@ -16006,7 +16006,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" }, @@ -16017,7 +16017,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" }, From 5e74eb65d6c695297f48bc601618fdccd446ec07 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 17:27:44 -0300 Subject: [PATCH 27/39] chore: remove naive datetime warnings found in meetings.tests_views --- ietf/meeting/tests_views.py | 8 ++++---- ietf/meeting/views.py | 21 +++++++++++---------- ietf/secr/proceedings/proc_utils.py | 20 +++++++++++++------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 799bf497403..290b885b492 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -156,7 +156,7 @@ def test_meeting_agenda(self): # 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') @@ -971,7 +971,7 @@ def test_current_materials(self): 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) @@ -7469,7 +7469,7 @@ def test_proceedings(self): 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')) @@ -7578,7 +7578,7 @@ def _procmat_test_meeting(self): """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() diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 96968f7b6a8..5b38b1ff935 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -96,6 +96,7 @@ 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 = datetime_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) @@ -2474,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, }) @@ -3573,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)) @@ -3637,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), }) @@ -3651,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')) @@ -3698,7 +3699,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')) @@ -3747,7 +3748,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( @@ -4044,7 +4045,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) diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py index 505bcdcc777..f1fcae1321a 100644 --- a/ietf/secr/proceedings/proc_utils.py +++ b/ietf/secr/proceedings/proc_utils.py @@ -9,6 +9,7 @@ ''' 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 From d0c913d5845475810644c25de83ecc421acbcf79 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 18:33:12 -0300 Subject: [PATCH 28/39] chore: fix a few more test failures in meetings.tests_views All tests in meetings.tests_views now passing --- ietf/group/models.py | 10 +++++++--- ietf/meeting/models.py | 20 +++++++++++--------- ietf/meeting/views.py | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/ietf/group/models.py b/ietf/group/models.py index fd665e6b36a..4746cb47f3e 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -181,11 +181,15 @@ def liaison_approvers(self): 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): diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 638d2d371b2..b7edcb08a2c 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -7,26 +7,23 @@ import datetime import io import os -import pytz import random import re import string - from collections import namedtuple from pathlib import Path from urllib.parse import urljoin -import debug # pyflakes:ignore - +import pytz +from django.conf import settings from django.core.validators import MinValueValidator, RegexValidator from django.db import models from django.db.models import Max, Subquery, OuterRef, TextField, Value, Q from django.db.models.functions import Coalesce -from django.conf import settings from django.urls import reverse as urlreverse from django.utils import timezone -from django.utils.text import slugify from django.utils.safestring import mark_safe +from django.utils.text import slugify from ietf.dbtemplate.models import DBTemplate from ietf.doc.models import Document @@ -39,16 +36,16 @@ ) from ietf.person.models import Person from ietf.utils.decorators import memoize +from ietf.utils.fields import MissingOkImageField +from ietf.utils.log import unreachable +from ietf.utils.models import ForeignKey from ietf.utils.storage import NoLocationMigrationFileSystemStorage from ietf.utils.text import xslugify from ietf.utils.timezone import date2datetime -from ietf.utils.models import ForeignKey from ietf.utils.validators import ( MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type, validate_file_extension, ) -from ietf.utils.fields import MissingOkImageField -from ietf.utils.log import unreachable countries = list(pytz.country_names.items()) countries.sort(key=lambda x: x[1]) @@ -139,6 +136,11 @@ def get_meeting_date (self,offset): 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() diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 5b38b1ff935..68e98baf39d 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -143,7 +143,7 @@ def materials(request, num=None): 'cor_cut_off_date': cor_cut_off_date }) - past_cutoff_date = datetime_today() > meeting.get_submission_correction_date() + past_cutoff_date = date_today() > meeting.get_submission_correction_date() schedule = get_schedule(meeting, None) From 6441a588e3b63341b0b621910e80951a50862437 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 18:41:29 -0300 Subject: [PATCH 29/39] chore: remove unused import --- ietf/group/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/group/models.py b/ietf/group/models.py index 4746cb47f3e..8183f35a04f 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- -import datetime import email.utils import jsonfield import os From f8d31459d6d16649d2129facb510129483264d4d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 20:02:05 -0300 Subject: [PATCH 30/39] chore: fix timezone handling in test_schedule_generator.py --- ietf/meeting/tests_schedule_generator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ietf/meeting/tests_schedule_generator.py b/ietf/meeting/tests_schedule_generator.py index 2ec13f19f7b..a88280208f5 100644 --- a/ietf/meeting/tests_schedule_generator.py +++ b/ietf/meeting/tests_schedule_generator.py @@ -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 @@ -308,8 +309,11 @@ def _create_base_schedule(self): 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' From 2de3a762fd2f063b81509b1bc8a582f015394aff Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 21:17:48 -0300 Subject: [PATCH 31/39] chore: fix timezone handling affecting meeting.tests_js --- ietf/meeting/models.py | 4 ++-- ietf/meeting/tests_js.py | 26 ++++++++++++++------------ ietf/templates/meeting/upcoming.html | 12 ++++++------ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index b7edcb08a2c..041aad60a3e 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -620,10 +620,10 @@ def tzname(self): return self.tz().tzname(self.time) def utc_start_time(self): - return self.time # USE_TZ is True, so time is UTC + return self.time.astimezone(pytz.utc) # USE_TZ is True, so time is UTC def utc_end_time(self): - return self.time + self.duration # USE_TZ is True, so time is UTC + return self.time.astimezone(pytz.utc) + self.duration # USE_TZ is True, so time is UTC def local_start_time(self): return self.time.astimezone(self.tz()) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 7825c8ee63b..6fdc31c50dc 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -36,6 +36,7 @@ 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 @@ def session_from_agenda_row_id(self, row_id): # for others (break, reg, other): # row-------- 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 @@ def _assert_not_wrapped(displayed, expected_time_string): # 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 @@ def _assert_not_wrapped(displayed, expected_time_string): ) 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) @@ -1935,7 +1938,6 @@ def _assert_not_wrapped(displayed, expected_time_string): 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, @@ -2193,7 +2195,7 @@ def assert_upcoming_view_filter_matches_ics_filter(self, filter_string): 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 = [ @@ -2719,7 +2721,7 @@ def do_delete_time_interval_test(self, cancel=False): delete_time.strftime('%H:%M'), self.meeting.tz().localize( datetime.datetime.combine(delete_day, delete_time) + duration - ).astimezone(pytz.utc).strftime( + ).strftime( '%H:%M' )) ) diff --git a/ietf/templates/meeting/upcoming.html b/ietf/templates/meeting/upcoming.html index 43f998c9a60..0fb40c971ad 100644 --- a/ietf/templates/meeting/upcoming.html +++ b/ietf/templates/meeting/upcoming.html @@ -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 %} @@ -67,9 +67,9 @@

Upcoming Meetings

{% elif entry|classname == 'Session' %} {% with session=entry group=entry.group meeting=entry.meeting %} - {{ 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" }} {{ group.acronym }} @@ -132,8 +132,8 @@

No upcoming meetings

{ 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 %} From 382e81f0259996ed9039704fa4ccf51cc614a1f9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 22:02:50 -0300 Subject: [PATCH 32/39] chore: fix timeslot test bug when local date != UTC date --- ietf/meeting/tests_views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 290b885b492..392407a0f4f 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -1885,7 +1885,7 @@ def create_bare_meeting(number=120) -> Meeting: return MeetingFactory( type_id='ietf', number=number, - date=timezone.now() + datetime.timedelta(days=10), + date=date_today() + datetime.timedelta(days=10), populate_schedule=False, ) @@ -1917,7 +1917,8 @@ def create_meeting(self, number=120): 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""" @@ -2736,8 +2737,8 @@ def test_create_bulk_timeslots(self): """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( From cae0b9de9fefc0ef7d8688da9aa0deaddd12c90b Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 9 Aug 2022 22:18:35 -0300 Subject: [PATCH 33/39] test: fix a few failing tests, all meetings tests now pass (for me, anyway) --- ietf/meeting/tests_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 392407a0f4f..4d53ab85656 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -2087,7 +2087,7 @@ def _col_index(elt): 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) @@ -2180,7 +2180,7 @@ def test_bulk_delete_buttons_exist(self): location=meeting.room_set.first(), time=meeting.tz().localize( datetime.datetime.combine( - meeting.get_meeting_date(day).date(), + meeting.get_meeting_date(day), datetime.time(hour=11), ) ), @@ -2190,7 +2190,7 @@ def test_bulk_delete_buttons_exist(self): location=meeting.room_set.first(), time=meeting.tz().localize( datetime.datetime.combine( - meeting.get_meeting_date(day).date(), + meeting.get_meeting_date(day), datetime.time(hour=14), ) ), @@ -2499,7 +2499,7 @@ def test_create_single_timeslot_outside_meeting_days(self): """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', From b784d77e8314a00ea7afd3b7b85804610bde8616 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 25 Aug 2022 15:26:50 -0300 Subject: [PATCH 34/39] chore: renumber migrations --- ...ing_time_zone.py => 0057_fill_in_empty_meeting_time_zone.py} | 2 +- ...me_zone_not_blank.py => 0058_meeting_time_zone_not_blank.py} | 2 +- ietf/utils/migrations/0002_convert_timestamps_to_utc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename ietf/meeting/migrations/{0054_fill_in_empty_meeting_time_zone.py => 0057_fill_in_empty_meeting_time_zone.py} (95%) rename ietf/meeting/migrations/{0055_meeting_time_zone_not_blank.py => 0058_meeting_time_zone_not_blank.py} (99%) diff --git a/ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py b/ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py similarity index 95% rename from ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py rename to ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py index cdf3f0cebea..f009b08f3b3 100644 --- a/ietf/meeting/migrations/0054_fill_in_empty_meeting_time_zone.py +++ b/ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py @@ -39,7 +39,7 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('meeting', '0053_attended'), + ('meeting', '0056_use_timezone_now_for_meeting_models'), ] operations = [ diff --git a/ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py similarity index 99% rename from ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py rename to ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py index c6e8393908a..cde70b68f50 100644 --- a/ietf/meeting/migrations/0055_meeting_time_zone_not_blank.py +++ b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('meeting', '0054_fill_in_empty_meeting_time_zone'), + ('meeting', '0057_fill_in_empty_meeting_time_zone'), ] operations = [ diff --git a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py index 5df3051eaeb..19ef6248688 100644 --- a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py +++ b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py @@ -132,7 +132,7 @@ def forward(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ ('utils', '0001_initial'), - ('meeting', '0055_meeting_time_zone_not_blank'), + ('meeting', '0058_meeting_time_zone_not_blank'), ] # To generate the queries: From 59ef52c371a286889e19790c31417a5aaa8d37ff Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 25 Aug 2022 16:04:56 -0300 Subject: [PATCH 35/39] 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. --- .../0002_convert_timestamps_to_utc.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py index 19ef6248688..6cc02253807 100644 --- a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py +++ b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.28 on 2022-06-21 11:44 +from django.conf import settings from django.db import migrations, connection - # to generate the expected list: # # from django.db import connection @@ -11,7 +11,9 @@ # cursor.execute(""" # SELECT table_name, column_name # FROM information_schema.columns -# WHERE table_schema='ietf_utf8' AND column_type LIKE 'datetime%' +# WHERE table_schema='ietf_utf8' +# AND column_type LIKE 'datetime%' +# AND NOT table_name LIKE 'django_celery_beat_%' # ORDER BY table_name, column_name; # """) # pp(cursor.fetchall()) @@ -105,6 +107,12 @@ def forward(apps, schema_editor): + # Check that we can safely ignore celery beat columns - it defaults to UTC if CELERY_TIMEZONE is not set. + celery_timezone = getattr(settings, 'CELERY_TIMEZONE', None) + assert celery_timezone in ('UTC', None), 'update migration, celery is not using UTC' + # If the CELERY_ENABLE_UTC flag is set, abort because someone is using a strange configuration. + assert not hasattr(settings, 'CELERY_ENABLE_UTC'), 'update migration, settings.CELERY_ENABLE_UTC was not expected' + with connection.cursor() as cursor: # Check that we have timezones. # If these assertions fail, the DB does not know all the necessary time zones. @@ -118,12 +126,14 @@ def forward(apps, schema_editor): ) assert not any(None in row for row in cursor.fetchall()), 'database does not recognize a meeting time zone' - # check that we have all and only the expected datetime columns to work with - # if this fails, figure out what changed and decide how to proceed safely + # Check that we have all and only the expected datetime columns to work with. + # If this fails, figure out what changed and decide how to proceed safely. cursor.execute(""" SELECT table_name, column_name FROM information_schema.columns - WHERE table_schema='ietf_utf8' AND column_type LIKE 'datetime%' + WHERE table_schema='ietf_utf8' + AND column_type LIKE 'datetime%' + AND NOT table_name LIKE 'django_celery_beat_%' ORDER BY table_name, column_name; """) assert cursor.fetchall() == expected_datetime_columns, 'unexpected or missing datetime columns in db' @@ -146,7 +156,7 @@ class Migration(migrations.Migration): # UPDATE meeting_timeslot # JOIN meeting_meeting # ON meeting_meeting.id = meeting_id - # SET time = CONVERT_TZ(time, IF(time_zone != '', time_zone, 'PST8PDT'), 'UTC'); + # SET time = CONVERT_TZ(time, time_zone, 'UTC'); # """) # # print("\n".join(queries)) From 81061431219db2e5ae71be31d59a1e3a5f89a917 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 25 Aug 2022 16:10:52 -0300 Subject: [PATCH 36/39] chore: fix pytz-related change in migration --- ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py index cde70b68f50..0adec77a288 100644 --- a/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py +++ b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.28 on 2022-08-08 11:52 +# Generated by Django 2.2.28 on 2022-08-25 12:09 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='meeting', name='time_zone', - field=models.CharField(choices=[('', '---------'), ('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('UTC', 'UTC')], default='UTC', max_length=255), + field=models.CharField(choices=[('', '---------'), ('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('UTC', 'UTC')], default='UTC', max_length=255), ), ] From be2bcb2ad96d31101543eac7c9aded285920f649 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Fri, 26 Aug 2022 10:46:54 -0300 Subject: [PATCH 37/39] chore: remove duplicate migrations --- ...054_use_timezone_now_for_meeting_models.py | 19 ------------ ...0009_use_timezone_now_for_submit_models.py | 29 ------------------- 2 files changed, 48 deletions(-) delete mode 100644 ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py delete mode 100644 ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py diff --git a/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py b/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py deleted file mode 100644 index 3e6eb71ec39..00000000000 --- a/ietf/meeting/migrations/0054_use_timezone_now_for_meeting_models.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.28 on 2022-07-12 11:24 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('meeting', '0053_attended'), - ] - - operations = [ - migrations.AlterField( - model_name='schedulingevent', - name='time', - field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'), - ), - ] diff --git a/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py b/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py deleted file mode 100644 index 90208b20f18..00000000000 --- a/ietf/submit/migrations/0009_use_timezone_now_for_submit_models.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.28 on 2022-07-12 11:24 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('submit', '0008_submissionextresource'), - ] - - operations = [ - migrations.AlterField( - model_name='preapproval', - name='time', - field=models.DateTimeField(default=django.utils.timezone.now), - ), - migrations.AlterField( - model_name='submissioncheck', - name='time', - field=models.DateTimeField(default=django.utils.timezone.now), - ), - migrations.AlterField( - model_name='submissionevent', - name='time', - field=models.DateTimeField(default=django.utils.timezone.now), - ), - ] From 9d99637e237833b51f4c893abbaf6deef6f2c7da Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Fri, 26 Aug 2022 10:47:45 -0300 Subject: [PATCH 38/39] chore: remove CELERY_BEAT_TZ_AWARE setting now that USE_TZ is True --- ietf/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 5464dad0c4f..0beb06a843e 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -1187,8 +1187,6 @@ def skip_unreadable_post(record): 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 From 3c15d855580ab8725a82f2f174ff33c85fe67d36 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Fri, 26 Aug 2022 16:48:22 -0300 Subject: [PATCH 39/39] test: avoid failure in test with bogus timezone --- ietf/meeting/tests_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/meeting/tests_models.py b/ietf/meeting/tests_models.py index 4a2f42c1633..8dec586ec2d 100644 --- a/ietf/meeting/tests_models.py +++ b/ietf/meeting/tests_models.py @@ -56,16 +56,16 @@ def test_get_attendance(self): 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)