Merge branch 'feat/tzaware' into jennifer/main-to-tzaware-conflicts

This commit is contained in:
Jennifer Richards 2022-09-14 12:36:33 -03:00 committed by GitHub
commit 1366b6de38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
136 changed files with 1903 additions and 834 deletions

View file

@ -139,6 +139,8 @@ jobs:
run: | run: |
echo "Running checks..." echo "Running checks..."
./ietf/manage.py check ./ietf/manage.py check
./ietf/manage.py migrate || true
echo "USE_TZ = True" >> ./ietf/settings_local.py
./ietf/manage.py migrate ./ietf/manage.py migrate
echo "Validating migrations..." echo "Validating migrations..."
if ! ( ietf/manage.py makemigrations --dry-run --check --verbosity 3 ) ; then if ! ( ietf/manage.py makemigrations --dry-run --check --verbosity 3 ) ; then

View file

@ -12,5 +12,24 @@ yarn legacy:build
echo "Creating data directories..." echo "Creating data directories..."
chmod +x ./docker/scripts/app-create-dirs.sh chmod +x ./docker/scripts/app-create-dirs.sh
./docker/scripts/app-create-dirs.sh ./docker/scripts/app-create-dirs.sh
./ietf/manage.py check ./ietf/manage.py check
./ietf/manage.py migrate if ./ietf/manage.py showmigrations | grep "\[ \] 0003_pause_to_change_use_tz"; then
if grep "USE_TZ" ./ietf/settings_local.py; then
cat ./ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = False/' > /tmp/settings_local.py && mv /tmp/settings_local.py ./ietf/settings_local.py
else
echo "USE_TZ = False" >> ./ietf/settings_local.py
fi
# This is expected to exit non-zero at the pause
/usr/local/bin/python ./ietf/manage.py migrate || true
cat ./ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = True/' > /tmp/settings_local.py && mv /tmp/settings_local.py ./ietf/settings_local.py
/usr/local/bin/python ./ietf/manage.py migrate
else
if grep "USE_TZ" ./ietf/settings_local.py; then
cat ./ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = True/' > /tmp/settings_local.py && mv /tmp/settings_local.py ./ietf/settings_local.py
else
echo "USE_TZ = True" >> ./ietf/settings_local.py
/usr/local/bin/python ./ietf/manage.py migrate
fi
fi

View file

@ -100,7 +100,28 @@ echo "Starting memcached..."
echo "Running initial checks..." echo "Running initial checks..."
/usr/local/bin/python $WORKSPACEDIR/ietf/manage.py check --settings=settings_local /usr/local/bin/python $WORKSPACEDIR/ietf/manage.py check --settings=settings_local
# /usr/local/bin/python $WORKSPACEDIR/ietf/manage.py migrate --settings=settings_local
# Migrate, adjusting to what the current state of the underlying database might be:
if ietf/manage.py showmigrations | grep "\[ \] 0003_pause_to_change_use_tz"; then
if grep "USE_TZ" $WORKSPACEDIR/ietf/settings_local.py; then
cat $WORKSPACEDIR/ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = False/' > /tmp/settings_local.py && mv /tmp/settings_local.py $WORKSPACEDIR/ietf/settings_local.py
else
echo "USE_TZ = False" >> $WORKSPACEDIR/ietf/settings_local.py
fi
# This is expected to exit non-zero at the pause
/usr/local/bin/python $WORKSPACEDIR/ietf/manage.py migrate --settings=settings_local || true
cat $WORKSPACEDIR/ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = True/' > /tmp/settings_local.py && mv /tmp/settings_local.py $WORKSPACEDIR/ietf/settings_local.py
/usr/local/bin/python $WORKSPACEDIR/ietf/manage.py migrate --settings=settings_local
else
if grep "USE_TZ" $WORKSPACEDIR/ietf/settings_local.py; then
cat $WORKSPACEDIR/ietf/settings_local.py | sed 's/USE_TZ.*$/USE_TZ = True/' > /tmp/settings_local.py && mv /tmp/settings_local.py $WORKSPACEDIR/ietf/settings_local.py
else
echo "USE_TZ = True" >> $WORKSPACEDIR/ietf/settings_local.py
/usr/local/bin/python $WORKSPACEDIR/ietf/manage.py migrate --settings=settings_local
fi
fi
echo "-----------------------------------------------------------------" echo "-----------------------------------------------------------------"
echo "Done!" echo "Done!"

View file

@ -3,7 +3,6 @@
import os import os
import datetime
import collections import collections
import io import io
@ -14,6 +13,7 @@ import debug # pyflakes:ignore
from django.core.management.base import AppCommand from django.core.management.base import AppCommand
from django.db import models from django.db import models
from django.template import Template, Context from django.template import Template, Context
from django.utils import timezone
from tastypie.resources import ModelResource from tastypie.resources import ModelResource
@ -89,7 +89,7 @@ class Command(AppCommand):
info = dict( info = dict(
app=app.name, app=app.name,
app_label=app.label, app_label=app.label,
date=datetime.datetime.now() date=timezone.now()
) )
new_models = {} new_models = {}
for model, rclass_name in missing_resources: for model, rclass_name in missing_resources:

View file

@ -30,6 +30,7 @@ from ietf.person.models import User
from ietf.person.models import PersonalApiKey from ietf.person.models import PersonalApiKey
from ietf.stats.models import MeetingRegistration from ietf.stats.models import MeetingRegistration
from ietf.utils.mail import outbox, get_payload_text from ietf.utils.mail import outbox, get_payload_text
from ietf.utils.models import DumpInfo
from ietf.utils.test_utils import TestCase, login_testing_unauthorized from ietf.utils.test_utils import TestCase, login_testing_unauthorized
OMITTED_APPS = ( OMITTED_APPS = (
@ -419,10 +420,17 @@ class CustomApiTests(TestCase):
self.assertEqual(set(missing_fields), set(drop_fields)) self.assertEqual(set(missing_fields), set(drop_fields))
def test_api_version(self): def test_api_version(self):
DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=timezone.utc), host='testapi.example.com',tz='UTC')
url = urlreverse('ietf.api.views.version') url = urlreverse('ietf.api.views.version')
r = self.client.get(url) r = self.client.get(url)
data = r.json() data = r.json()
self.assertEqual(data['version'], ietf.__version__+ietf.__patch__) self.assertEqual(data['version'], ietf.__version__+ietf.__patch__)
self.assertEqual(data['dumptime'], "2022-08-31 07:10:01 +0000")
DumpInfo.objects.update(tz='PST8PDT')
r = self.client.get(url)
data = r.json()
self.assertEqual(data['dumptime'], "2022-08-31 07:10:01 -0700")
def test_api_appauth(self): def test_api_appauth(self):
url = urlreverse('ietf.api.views.app_auth') url = urlreverse('ietf.api.views.app_auth')

View file

@ -203,8 +203,13 @@ def api_new_meeting_registration(request):
def version(request): def version(request):
dumpdate = None
dumpinfo = DumpInfo.objects.order_by('-date').first() dumpinfo = DumpInfo.objects.order_by('-date').first()
dumptime = pytz.timezone(dumpinfo.tz).localize(dumpinfo.date).strftime('%Y-%m-%d %H:%M:%S %z') if dumpinfo else None if dumpinfo:
dumpdate = dumpinfo.date
if dumpinfo.tz != "UTC":
dumpdate = pytz.timezone(dumpinfo.tz).localize(dumpinfo.date.replace(tzinfo=None))
dumptime = dumpdate.strftime('%Y-%m-%d %H:%M:%S %z') if dumpinfo else None
return HttpResponse( return HttpResponse(
json.dumps({ json.dumps({
'version': ietf.__version__+ietf.__patch__, 'version': ietf.__version__+ietf.__patch__,

View file

@ -18,6 +18,7 @@ django.setup()
from django.conf import settings from django.conf import settings
from optparse import OptionParser from optparse import OptionParser
from zoneinfo import ZoneInfo
parser = OptionParser() parser = OptionParser()
parser.add_option("-f", "--from", dest="start", parser.add_option("-f", "--from", dest="start",
@ -38,13 +39,16 @@ CLOCK_SKEW_COMPENSATION = 5 # seconds
MAX_INTERVAL_ACCEPTED_BY_IANA = datetime.timedelta(hours=23) MAX_INTERVAL_ACCEPTED_BY_IANA = datetime.timedelta(hours=23)
local_tzinfo = ZoneInfo(settings.TIME_ZONE)
start = datetime.datetime.now() - datetime.timedelta(hours=23) + datetime.timedelta(seconds=CLOCK_SKEW_COMPENSATION) start = datetime.datetime.now() - datetime.timedelta(hours=23) + datetime.timedelta(seconds=CLOCK_SKEW_COMPENSATION)
if options.start: if options.start:
start = datetime.datetime.strptime(options.start, "%Y-%m-%d %H:%M:%S") start = datetime.datetime.strptime(options.start, "%Y-%m-%d %H:%M:%S")
start = start.replace(tzinfo=local_tzinfo).astimezone(datetime.timezone.utc)
end = start + datetime.timedelta(hours=23) end = start + datetime.timedelta(hours=23)
if options.end: if options.end:
end = datetime.datetime.strptime(options.end, "%Y-%m-%d %H:%M:%S") end = datetime.datetime.strptime(options.end, "%Y-%m-%d %H:%M:%S").replace(tzinfo=local_tzinfo)
end = end.astimezone(datetime.timezone.utc)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER) syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER)
@ -52,7 +56,13 @@ syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER)
from ietf.sync.iana import fetch_changes_json, parse_changes_json, update_history_with_changes from ietf.sync.iana import fetch_changes_json, parse_changes_json, update_history_with_changes
syslog.syslog("Updating history log with new changes from IANA from %s, period %s - %s" % (settings.IANA_SYNC_CHANGES_URL, start, end)) syslog.syslog(
"Updating history log with new changes from IANA from %s, period %s - %s" % (
settings.IANA_SYNC_CHANGES_URL,
start.astimezone(local_tzinfo),
end.astimezone(local_tzinfo),
)
)
t = start t = start
while t < end: while t < end:

View file

@ -3,7 +3,7 @@
# This script requires that the proper virtual python environment has been # This script requires that the proper virtual python environment has been
# invoked before start # invoked before start
import datetime, os, sys import os, sys
import syslog import syslog
# boilerplate # boilerplate
@ -16,6 +16,7 @@ syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER)
import django import django
django.setup() django.setup()
from django.utils import timezone
from ietf.utils.mail import log_smtp_exception, send_error_email from ietf.utils.mail import log_smtp_exception, send_error_email
from smtplib import SMTPException from smtplib import SMTPException
@ -32,7 +33,7 @@ from ietf.message.models import SendQueue
mode = sys.argv[1] mode = sys.argv[1]
now = datetime.datetime.now() now = timezone.now()
needs_sending = SendQueue.objects.filter(sent_at=None).select_related("message") needs_sending = SendQueue.objects.filter(sent_at=None).select_related("message")
if mode == "specific": if mode == "specific":

View file

@ -10,6 +10,7 @@ import uuid
from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.utils.html import strip_tags from django.utils.html import strip_tags
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -218,7 +219,7 @@ def feed(request, username=None, acronym=None, group_type=None):
significant = request.GET.get('significant', '') == '1' significant = request.GET.get('significant', '') == '1'
documents = docs_tracked_by_community_list(clist).values_list('pk', flat=True) 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( events = DocEvent.objects.filter(
doc__id__in=documents, doc__id__in=documents,
@ -243,7 +244,7 @@ def feed(request, username=None, acronym=None, group_type=None):
'title': title, 'title': title,
'subtitle': subtitle, 'subtitle': subtitle,
'id': feed_id.urn, 'id': feed_id.urn,
'updated': datetime.datetime.now(), 'updated': timezone.now(),
}, content_type='text/xml') }, content_type='text/xml')

View file

@ -4,6 +4,7 @@
from django.conf import settings from django.conf import settings
from django.utils import timezone
import datetime, os, shutil, glob, re import datetime, os, shutil, glob, re
from pathlib import Path from pathlib import Path
@ -17,6 +18,7 @@ from ietf.person.models import Person
from ietf.meeting.models import Meeting from ietf.meeting.models import Meeting
from ietf.doc.utils import add_state_change_event, update_action_holders from ietf.doc.utils import add_state_change_event, update_action_holders
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
nonexpirable_states: Optional[List[State]] = None nonexpirable_states: Optional[List[State]] = None
@ -52,17 +54,17 @@ def expirable_drafts(queryset=None):
def get_soon_to_expire_drafts(days_of_warning): def get_soon_to_expire_drafts(days_of_warning):
start_date = datetime.date.today() - datetime.timedelta(1) start_date = datetime_today(DEADLINE_TZINFO) - datetime.timedelta(1)
end_date = start_date + datetime.timedelta(days_of_warning) end_date = start_date + datetime.timedelta(days_of_warning)
return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date) return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date)
def get_expired_drafts(): def get_expired_drafts():
return expirable_drafts().filter(expires__lt=datetime.date.today() + datetime.timedelta(1)) return expirable_drafts().filter(expires__lt=datetime_today(DEADLINE_TZINFO) + datetime.timedelta(1))
def in_draft_expire_freeze(when=None): def in_draft_expire_freeze(when=None):
if 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() meeting = Meeting.objects.filter(type='ietf', date__gte=when-datetime.timedelta(days=7)).order_by('date').first()

View file

@ -10,6 +10,7 @@ import datetime
from typing import Optional # pyflakes:ignore from typing import Optional # pyflakes:ignore
from django.conf import settings from django.conf import settings
from django.utils import timezone
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor, from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent, StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
@ -38,7 +39,7 @@ class BaseDocumentFactory(factory.django.DjangoModelFactory):
rev = '00' rev = '00'
std_level_id = None # type: Optional[str] std_level_id = None # type: Optional[str]
intended_std_level_id = None 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)) expires = factory.LazyAttribute(lambda o: o.time+datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE))
pages = factory.fuzzy.FuzzyInteger(2,400) pages = factory.fuzzy.FuzzyInteger(2,400)
@ -357,7 +358,8 @@ class TelechatDocEventFactory(DocEventFactory):
class Meta: class Meta:
model = TelechatDocEvent 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' type = 'scheduled_for_telechat'
class NewRevisionDocEventFactory(DocEventFactory): class NewRevisionDocEventFactory(DocEventFactory):
@ -410,7 +412,7 @@ class IRSGBallotDocEventFactory(BallotDocEventFactory):
class Meta: class Meta:
model = IRSGBallotDocEvent 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') ballot_type = factory.SubFactory(BallotTypeFactory, slug='irsg-approve')
class BallotPositionDocEventFactory(DocEventFactory): class BallotPositionDocEventFactory(DocEventFactory):

View file

@ -10,11 +10,13 @@ from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.template.defaultfilters import truncatewords, truncatewords_html, date as datefilter from django.template.defaultfilters import truncatewords, truncatewords_html, date as datefilter
from django.template.defaultfilters import linebreaks # type: ignore from django.template.defaultfilters import linebreaks # type: ignore
from django.utils import timezone
from django.utils.html import strip_tags from django.utils.html import strip_tags
from ietf.doc.models import Document, State, LastCallDocEvent, DocEvent from ietf.doc.models import Document, State, LastCallDocEvent, DocEvent
from ietf.doc.utils import augment_events_with_revision from ietf.doc.utils import augment_events_with_revision
from ietf.doc.templatetags.ietf_filters import format_textarea from ietf.doc.templatetags.ietf_filters import format_textarea
from ietf.utils.timezone import RPC_TZINFO
def strip_control_characters(s): def strip_control_characters(s):
@ -133,9 +135,16 @@ class RfcFeed(Feed):
def items(self): def items(self):
if self.year: if self.year:
rfc_events = DocEvent.objects.filter(type='published_rfc',time__year=self.year).order_by('-time') # Find published RFCs based on their official publication year
start_of_year = datetime.datetime(int(self.year), 1, 1, tzinfo=RPC_TZINFO)
start_of_next_year = datetime.datetime(int(self.year) + 1, 1, 1, tzinfo=RPC_TZINFO)
rfc_events = DocEvent.objects.filter(
type='published_rfc',
time__gte=start_of_year,
time__lt=start_of_next_year,
).order_by('-time')
else: 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') rfc_events = DocEvent.objects.filter(type='published_rfc',time__gte=cutoff).order_by('-time')
results = [(e.doc, e.time) for e in rfc_events] results = [(e.doc, e.time) for e in rfc_events]
for doc,time in results: for doc,time in results:

View file

@ -10,6 +10,7 @@ from django.template.loader import render_to_string
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
import debug # pyflakes:ignore 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") e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
last_call_expires = e.expires if e else None 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", return render_to_string("doc/mail/issue_iesg_ballot_mail.txt",
dict(doc=doc, 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) (to, cc) = gather_address_lists('irsg_ballot_issued', doc=doc)
sender = 'IESG Secretary <iesg-secretary@ietf.org>' sender = 'IESG Secretary <iesg-secretary@ietf.org>'
ballot_expired = ballot.duedate < datetime.datetime.now() ballot_expired = ballot.duedate < timezone.now()
active_ballot = doc.active_ballot() active_ballot = doc.active_ballot()
if active_ballot is None: if active_ballot is None:
needed_bps = '' needed_bps = ''

View file

@ -16,6 +16,7 @@ from tempfile import mkstemp
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -101,7 +102,7 @@ class Command(BaseCommand):
'that have seen activity in the last %s years.' % (DEFAULT_YEARS)) 'that have seen activity in the last %s years.' % (DEFAULT_YEARS))
def handle(self, *args, **options): 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") date = time.strftime("%Y-%m-%d_%H:%M:%S")
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date) signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)

View file

@ -11,6 +11,7 @@ import sys
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -68,7 +69,7 @@ class Command(BaseCommand):
if process_all: if process_all:
doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft') doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft')
else: 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 = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft', time__gte=start)
doc_events = doc_events.order_by('time') doc_events = doc_events.order_by('time')

View file

@ -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),
),
]

View file

@ -0,0 +1,61 @@
# Generated by Django 2.2.28 on 2022-08-31 20:26
import datetime
import json
from zoneinfo import ZoneInfo
from django.db import migrations
TZ_BEFORE = ZoneInfo('PST8PDT')
def forward(apps, schema_editor):
DeletedEvent = apps.get_model('doc', 'DeletedEvent')
for deleted_event in DeletedEvent.objects.all():
fields = json.loads(deleted_event.json)
replacements = {}
for k, v in fields.items():
if isinstance(v, str):
try:
dt = datetime.datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
except:
pass
else:
replacements[k] = dt.replace(tzinfo=TZ_BEFORE).astimezone(datetime.timezone.utc).isoformat()
if len(replacements) > 0:
fields.update(replacements)
deleted_event.json = json.dumps(fields)
deleted_event.save()
def reverse(apps, schema_editor):
DeletedEvent = apps.get_model('doc', 'DeletedEvent')
for deleted_event in DeletedEvent.objects.all():
fields = json.loads(deleted_event.json)
replacements = {}
for k, v in fields.items():
if isinstance(v, str) and 'T' in v:
try:
dt = datetime.datetime.fromisoformat(v)
except:
pass
else:
replacements[k] = dt.astimezone(TZ_BEFORE).replace(tzinfo=None).strftime('%Y-%m-%d %H:%M:%S')
if len(replacements) > 0:
fields.update(replacements)
deleted_event.json = json.dumps(fields)
deleted_event.save()
class Migration(migrations.Migration):
dependencies = [
('doc', '0045_use_timezone_now_for_doc_models'),
('utils', '0003_pause_to_change_use_tz'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -18,6 +18,7 @@ from django.core.validators import URLValidator, RegexValidator
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import mark_safe # type:ignore from django.utils.html import mark_safe # type:ignore
@ -35,6 +36,7 @@ from ietf.utils.decorators import memoize
from ietf.utils.validators import validate_no_control_chars from ietf.utils.validators import validate_no_control_chars
from ietf.utils.mail import formataddr from ietf.utils.mail import formataddr
from ietf.utils.models import ForeignKey from ietf.utils.models import ForeignKey
from ietf.utils.timezone import RPC_TZINFO
if TYPE_CHECKING: if TYPE_CHECKING:
# importing other than for type checking causes errors due to cyclic imports # importing other than for type checking causes errors due to cyclic imports
from ietf.meeting.models import ProceedingsMaterial, Session from ietf.meeting.models import ProceedingsMaterial, Session
@ -85,7 +87,7 @@ IESG_SUBSTATE_TAGS = ('ad-f-up', 'need-rev', 'extpty')
class DocumentInfo(models.Model): class DocumentInfo(models.Model):
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement""" """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 ... 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, ]) title = models.CharField(max_length=255, validators=[validate_no_control_chars, ])
@ -682,7 +684,7 @@ class DocumentActionHolder(models.Model):
"""Action holder for a document""" """Action holder for a document"""
document = ForeignKey('Document') document = ForeignKey('Document')
person = ForeignKey(Person) 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 CLEAR_ACTION_HOLDERS_STATES = ['approved', 'ann', 'rfcqueue', 'pub', 'dead'] # draft-iesg state slugs
GROUP_ROLES_OF_INTEREST = ['chair', 'techadv', 'editor', 'secr'] GROUP_ROLES_OF_INTEREST = ['chair', 'techadv', 'editor', 'secr']
@ -838,7 +840,7 @@ class Document(DocumentInfo):
def previous_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)" "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 return e.telechat_date if e else None
def request_closed_time(self, review_req): def request_closed_time(self, review_req):
@ -924,13 +926,18 @@ class Document(DocumentInfo):
return s return s
def pub_date(self): def pub_date(self):
"""This is the rfc publication date (datetime) for RFCs, """Get the publication date for this document
and the new-revision datetime for other documents."""
This is the rfc publication date for RFCs, and the new-revision date for other documents.
"""
if self.get_state_slug() == "rfc": if self.get_state_slug() == "rfc":
# As of Sept 2022, in ietf.sync.rfceditor.update_docs_from_rfc_index() `published_rfc` events are
# created with a timestamp whose date *in the PST8PDT timezone* is the official publication date
# assigned by the RFC editor.
event = self.latest_event(type='published_rfc') event = self.latest_event(type='published_rfc')
else: else:
event = self.latest_event(type='new_revision') event = self.latest_event(type='new_revision')
return event.time return event.time.astimezone(RPC_TZINFO).date() if event else None
def is_dochistory(self): def is_dochistory(self):
return False return False
@ -1208,7 +1215,7 @@ EVENT_TYPES = [
class DocEvent(models.Model): class DocEvent(models.Model):
"""An occurrence for a document, used for tracking who, when and what.""" """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) type = models.CharField(max_length=50, choices=EVENT_TYPES)
by = ForeignKey(Person) by = ForeignKey(Person)
doc = ForeignKey(Document) doc = ForeignKey(Document)
@ -1388,7 +1395,7 @@ class DeletedEvent(models.Model):
content_type = ForeignKey(ContentType) 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.") 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) by = ForeignKey(Person)
time = models.DateTimeField(default=datetime.datetime.now) time = models.DateTimeField(default=timezone.now)
def __str__(self): def __str__(self):
return u"%s by %s %s" % (self.content_type, self.by, self.time) return u"%s by %s %s" % (self.content_type, self.by, self.time)

View file

@ -18,6 +18,7 @@ from django.urls import reverse as urlreverse
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.urls import NoReverseMatch from django.urls import NoReverseMatch
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -318,7 +319,7 @@ def timesince_days(date):
"""Returns the number of days since 'date' (relative to now)""" """Returns the number of days since 'date' (relative to now)"""
if date.__class__ is not datetime.datetime: if date.__class__ is not datetime.datetime:
date = datetime.datetime(date.year, date.month, date.day) date = datetime.datetime(date.year, date.month, date.day)
delta = datetime.datetime.now() - date delta = timezone.now() - date
return delta.days return delta.days
@register.filter @register.filter
@ -637,19 +638,19 @@ def action_holder_badge(action_holder):
>>> action_holder_badge(DocumentActionHolderFactory()) >>> 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)))
'<span class="badge bg-danger" title="In state for 16 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 16</span>' '<span class="badge bg-danger" title="In state for 16 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 16</span>'
>>> 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)))
'<span class="badge bg-danger" title="In state for 30 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 30</span>' '<span class="badge bg-danger" title="In state for 30 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 30</span>'
>>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit >>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit
""" """
age_limit = settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS 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: if age > age_limit:
return mark_safe( return mark_safe(
'<span class="badge bg-danger" title="In state for %d day%s; goal is &lt;%d days."><i class="bi bi-clock-fill"></i> %d</span>' '<span class="badge bg-danger" title="In state for %d day%s; goal is &lt;%d days."><i class="bi bi-clock-fill"></i> %d</span>'

View file

@ -18,6 +18,7 @@ from pyquery import PyQuery
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from collections import defaultdict from collections import defaultdict
from zoneinfo import ZoneInfo
from django.core.management import call_command from django.core.management import call_command
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
@ -25,6 +26,7 @@ from django.conf import settings
from django.forms import Form from django.forms import Form
from django.utils.html import escape from django.utils.html import escape
from django.test import override_settings from django.test import override_settings
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from tastypie.test import ResourceTestCaseMixin from tastypie.test import ResourceTestCaseMixin
@ -56,6 +58,8 @@ from ietf.utils.mail import outbox, empty_outbox
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.text import normalize_text from ietf.utils.text import normalize_text
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO, RPC_TZINFO
class SearchTests(TestCase): class SearchTests(TestCase):
def test_search(self): def test_search(self):
@ -385,13 +389,13 @@ class SearchTests(TestCase):
# Three drafts to show with various warnings # Three drafts to show with various warnings
drafts = WgDraftFactory.create_batch(3,states=[('draft','active'),('draft-iesg','ad-eval')]) drafts = WgDraftFactory.create_batch(3,states=[('draft','active'),('draft-iesg','ad-eval')])
for index, draft in enumerate(drafts): 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()]) draft.action_holders.set([PersonFactory()])
# And one draft that should not show (with the default of 7 days to view) # And one draft that should not show (with the default of 7 days to view)
old = WgDraftFactory() old = WgDraftFactory()
old.docevent_set.filter(newrevisiondocevent__isnull=False).update(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=datetime.datetime.now()-datetime.timedelta(days=8)) StateDocEventFactory(doc=old, time=timezone.now()-datetime.timedelta(days=8))
url = urlreverse('ietf.doc.views_search.recent_drafts') url = urlreverse('ietf.doc.views_search.recent_drafts')
r = self.client.get(url) r = self.client.get(url)
@ -764,7 +768,7 @@ Man Expires September 22, 2015 [Page 3]
replacement = WgDraftFactory( replacement = WgDraftFactory(
name="draft-ietf-replacement", name="draft-ietf-replacement",
time=datetime.datetime.now(), time=timezone.now(),
title="Replacement Draft", title="Replacement Draft",
stream_id=draft.stream_id, group_id=draft.group_id, abstract=draft.abstract,stream=draft.stream, rev=draft.rev, 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, pages=draft.pages, intended_std_level_id=draft.intended_std_level_id,
@ -1427,6 +1431,8 @@ Man Expires September 22, 2015 [Page 3]
def test_draft_group_link(self): def test_draft_group_link(self):
"""Link to group 'about' page should have correct format""" """Link to group 'about' page should have correct format"""
event_datetime = datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO)
for group_type_id in ['wg', 'rg', 'ag']: for group_type_id in ['wg', 'rg', 'ag']:
group = GroupFactory(type_id=group_type_id) group = GroupFactory(type_id=group_type_id)
draft = WgDraftFactory(name='draft-document-%s' % group_type_id, group=group) draft = WgDraftFactory(name='draft-document-%s' % group_type_id, group=group)
@ -1435,7 +1441,7 @@ Man Expires September 22, 2015 [Page 3]
self.assert_correct_wg_group_link(r, group) self.assert_correct_wg_group_link(r, group)
rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group) rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group)
DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime)
# get the rfc name to avoid a redirect # get the rfc name to avoid a redirect
rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name))) r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
@ -1450,7 +1456,7 @@ Man Expires September 22, 2015 [Page 3]
self.assert_correct_non_wg_group_link(r, group) self.assert_correct_non_wg_group_link(r, group)
rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group) rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group)
DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime)
# get the rfc name to avoid a redirect # get the rfc name to avoid a redirect
rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name))) r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
@ -1580,7 +1586,7 @@ class DocTestCase(TestCase):
name = "session-72-mars-1", name = "session-72-mars-1",
meeting = Meeting.objects.get(number='72'), meeting = Meeting.objects.get(number='72'),
group = Group.objects.get(acronym='mars'), group = Group.objects.get(acronym='mars'),
modified = datetime.datetime.now(), modified = timezone.now(),
add_to_schedule=False, add_to_schedule=False,
) )
SchedulingEvent.objects.create( SchedulingEvent.objects.create(
@ -1610,7 +1616,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position", type="changed_ballot_position",
pos_id="yes", pos_id="yes",
comment="Looks fine to me", comment="Looks fine to me",
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"), balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)")) by=Person.objects.get(name="(System)"))
@ -1644,7 +1650,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position", type="changed_ballot_position",
pos_id="noobj", pos_id="noobj",
comment="Still looks okay to me", comment="Still looks okay to me",
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"), balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)")) by=Person.objects.get(name="(System)"))
@ -1666,7 +1672,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position", type="changed_ballot_position",
pos_id="yes", pos_id="yes",
comment="Looks fine to me", comment="Looks fine to me",
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"), balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)")) by=Person.objects.get(name="(System)"))
@ -1836,7 +1842,7 @@ class DocTestCase(TestCase):
desc="Last call\x0b", # include a control character to be sure it does not break anything desc="Last call\x0b", # include a control character to be sure it does not break anything
type="sent_last_call", type="sent_last_call",
by=Person.objects.get(user__username="secretary"), by=Person.objects.get(user__username="secretary"),
expires=datetime.date.today() + datetime.timedelta(days=7)) expires=datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=7))
r = self.client.get("/feed/last-call/") r = self.client.get("/feed/last-call/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -1884,10 +1890,14 @@ class DocTestCase(TestCase):
#other_aliases = ['rfc6020',], #other_aliases = ['rfc6020',],
states = [('draft','rfc'),('draft-iesg','pub')], states = [('draft','rfc'),('draft-iesg','pub')],
std_level_id = 'ps', std_level_id = 'ps',
time = datetime.datetime(2010,10,10), time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
) )
num = rfc.rfc_number() num = rfc.rfc_number()
DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') DocEventFactory.create(
doc=rfc,
type='published_rfc',
time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO),
)
# #
url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name)) url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name))
r = self.client.get(url) r = self.client.get(url)
@ -1905,10 +1915,14 @@ class DocTestCase(TestCase):
stream_id = 'ise', stream_id = 'ise',
states = [('draft','rfc'),('draft-iesg','pub')], states = [('draft','rfc'),('draft-iesg','pub')],
std_level_id = 'inf', std_level_id = 'inf',
time = datetime.datetime(1990,0o4,0o1), time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo(settings.TIME_ZONE)),
) )
num = april1.rfc_number() num = april1.rfc_number()
DocEventFactory.create(doc=april1, type='published_rfc', time = '1990-04-01') DocEventFactory.create(
doc=april1,
type='published_rfc',
time=datetime.datetime(1990, 4, 1, tzinfo=RPC_TZINFO),
)
# #
url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name)) url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name))
r = self.client.get(url) r = self.client.get(url)
@ -2043,7 +2057,8 @@ class GenerateDraftAliasesTests(TestCase):
super().tearDown() super().tearDown()
def testManagementCommand(self): def testManagementCommand(self):
a_month_ago = datetime.datetime.now() - datetime.timedelta(30) a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(RPC_TZINFO)
a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0)
ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
shepherd = PersonFactory() shepherd = PersonFactory()
author1 = PersonFactory() author1 = PersonFactory()
@ -2058,9 +2073,9 @@ class GenerateDraftAliasesTests(TestCase):
doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad) doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad)
doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad) doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad)
doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago) doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago)
DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago.strftime("%Y-%m-%d")) DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago)
doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10)) doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10, tzinfo=ZoneInfo(settings.TIME_ZONE)))
DocEventFactory.create(doc=doc4, type='published_rfc', time = '2010-10-10') DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO))
doc5 = IndividualDraftFactory(authors=[author6]) doc5 = IndividualDraftFactory(authors=[author6])
args = [ ] args = [ ]

View file

@ -12,6 +12,7 @@ import debug # pyflakes:ignore
from django.test import RequestFactory from django.test import RequestFactory
from django.utils.text import slugify from django.utils.text import slugify
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from ietf.doc.models import (Document, State, DocEvent, from ietf.doc.models import (Document, State, DocEvent,
BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent) BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent)
@ -105,7 +106,7 @@ class EditPositionTests(TestCase):
draft = WgDraftFactory(ad=ad) draft = WgDraftFactory(ad=ad)
url = urlreverse('ietf.doc.views_ballot.api_set_position') url = urlreverse('ietf.doc.views_ballot.api_set_position')
create_ballot_if_not_open(None, draft, ad, 'approve') 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() ad.user.save()
apikey = PersonalApiKey.objects.create(endpoint=url, person=ad) apikey = PersonalApiKey.objects.create(endpoint=url, person=ad)
@ -238,9 +239,9 @@ class EditPositionTests(TestCase):
doc=draft, rev=draft.rev, type="changed_ballot_position", doc=draft, rev=draft.rev, type="changed_ballot_position",
by=ad, balloter=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"), by=ad, balloter=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"),
discuss="This draft seems to be lacking a clearer title?", discuss="This draft seems to be lacking a clearer title?",
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
comment="Test!", 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, url = urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=draft.name,
ballot_id=ballot.pk)) ballot_id=ballot.pk))
@ -466,7 +467,7 @@ class BallotWriteupsTests(TestCase):
doc=draft, doc=draft,
rev=draft.rev, rev=draft.rev,
desc='issued last call', 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)) url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "ad", url) login_testing_unauthorized(self, "ad", url)
@ -791,7 +792,7 @@ class ApproveBallotTests(TestCase):
doc=draft, doc=draft,
rev=draft.rev, rev=draft.rev,
desc='issued last call', desc='issued last call',
expires = datetime.datetime.now()-datetime.timedelta(days=14) ) expires = timezone.now()-datetime.timedelta(days=14) )
WriteupDocEvent.objects.create( WriteupDocEvent.objects.create(
by=Person.objects.get(name='(System)'), by=Person.objects.get(name='(System)'),
doc=draft, doc=draft,
@ -1117,7 +1118,7 @@ class RegenerateLastCallTestCase(TestCase):
class BallotContentTests(TestCase): class BallotContentTests(TestCase):
def test_ballotpositiondocevent_any_email_sent(self): 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( bpde_with_null_send_email = BallotPositionDocEventFactory(
time=now - datetime.timedelta(minutes=30), time=now - datetime.timedelta(minutes=30),
send_email=None, send_email=None,
@ -1219,7 +1220,7 @@ class BallotContentTests(TestCase):
balloter=balloters[0], balloter=balloters[0],
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=True, send_email=True,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1227,7 +1228,7 @@ class BallotContentTests(TestCase):
balloter=balloters[1], balloter=balloters[1],
pos_id='noobj', pos_id='noobj',
comment='Commentary', comment='Commentary',
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
send_email=True, send_email=True,
) )
@ -1237,7 +1238,7 @@ class BallotContentTests(TestCase):
balloter=balloters[2], balloter=balloters[2],
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=False, send_email=False,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1245,7 +1246,7 @@ class BallotContentTests(TestCase):
balloter=balloters[3], balloter=balloters[3],
pos_id='noobj', pos_id='noobj',
comment='Commentary', comment='Commentary',
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
send_email=False, send_email=False,
) )
@ -1255,7 +1256,7 @@ class BallotContentTests(TestCase):
balloter=balloters[4], balloter=balloters[4],
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), discuss_time=timezone.now() - datetime.timedelta(days=1),
send_email=True, send_email=True,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1263,7 +1264,7 @@ class BallotContentTests(TestCase):
balloter=balloters[4], balloter=balloters[4],
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=False, send_email=False,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1271,7 +1272,7 @@ class BallotContentTests(TestCase):
balloter=balloters[5], balloter=balloters[5],
pos_id='noobj', pos_id='noobj',
comment='Commentary', comment='Commentary',
comment_time=datetime.datetime.now() - datetime.timedelta(days=1), comment_time=timezone.now() - datetime.timedelta(days=1),
send_email=True, send_email=True,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1279,7 +1280,7 @@ class BallotContentTests(TestCase):
balloter=balloters[5], balloter=balloters[5],
pos_id='noobj', pos_id='noobj',
comment='Commentary', comment='Commentary',
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
send_email=False, send_email=False,
) )
@ -1296,7 +1297,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name1', balloter__plain='plain name1',
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=False, send_email=False,
).balloter ).balloter
send_email_balloter = BallotPositionDocEventFactory( send_email_balloter = BallotPositionDocEventFactory(
@ -1304,7 +1305,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name2', balloter__plain='plain name2',
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=True, send_email=True,
).balloter ).balloter
prev_send_email_balloter = BallotPositionDocEventFactory( prev_send_email_balloter = BallotPositionDocEventFactory(
@ -1312,7 +1313,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name3', balloter__plain='plain name3',
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now() - datetime.timedelta(days=1), discuss_time=timezone.now() - datetime.timedelta(days=1),
send_email=True, send_email=True,
).balloter ).balloter
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1320,7 +1321,7 @@ class BallotContentTests(TestCase):
balloter=prev_send_email_balloter, balloter=prev_send_email_balloter,
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=False, send_email=False,
) )
@ -1351,7 +1352,7 @@ class BallotContentTests(TestCase):
balloter=balloters[0], balloter=balloters[0],
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=None, send_email=None,
) )
BallotPositionDocEventFactory( BallotPositionDocEventFactory(
@ -1359,7 +1360,7 @@ class BallotContentTests(TestCase):
balloter=balloters[1], balloter=balloters[1],
pos_id='noobj', pos_id='noobj',
comment='Commentary', comment='Commentary',
comment_time=datetime.datetime.now(), comment_time=timezone.now(),
send_email=None, send_email=None,
) )
old_balloter = BallotPositionDocEventFactory( old_balloter = BallotPositionDocEventFactory(
@ -1367,7 +1368,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name', # ensure plain name is slugifiable balloter__plain='plain name', # ensure plain name is slugifiable
pos_id='discuss', pos_id='discuss',
discuss='Discussion text', discuss='Discussion text',
discuss_time=datetime.datetime.now(), discuss_time=timezone.now(),
send_email=None, send_email=None,
).balloter ).balloter

View file

@ -14,6 +14,7 @@ from html import unescape
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
@ -48,7 +49,7 @@ This test section has some text.
states = State.objects.filter(type_id='bofreq') states = State.objects.filter(type_id='bofreq')
self.assertTrue(states.count()>0) self.assertTrue(states.count()>0)
for i in range(3*len(states)): 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
q = PyQuery(r.content) q = PyQuery(r.content)

View file

@ -26,6 +26,8 @@ from ietf.person.models import Person
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.timezone import datetime_today, date_today, DEADLINE_TZINFO
class ViewCharterTests(TestCase): class ViewCharterTests(TestCase):
def test_view_revisions(self): def test_view_revisions(self):
@ -402,7 +404,7 @@ class EditCharterTests(TestCase):
# Make it so that the charter has been through internal review, and passed its external review # Make it so that the charter has been through internal review, and passed its external review
# ballot on a previous telechat # ballot on a previous telechat
last_week = datetime.date.today()-datetime.timedelta(days=7) last_week = datetime_today(DEADLINE_TZINFO) - datetime.timedelta(days=7)
BallotDocEvent.objects.create(type='created_ballot',by=login,doc=charter, rev=charter.rev, BallotDocEvent.objects.create(type='created_ballot',by=login,doc=charter, rev=charter.rev,
ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'),
time=last_week) time=last_week)
@ -746,7 +748,7 @@ class EditCharterTests(TestCase):
charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev")) charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev"))
due_date = datetime.date.today() + datetime.timedelta(days=180) due_date = date_today(DEADLINE_TZINFO) + datetime.timedelta(days=180)
m1 = GroupMilestone.objects.create(group=group, m1 = GroupMilestone.objects.create(group=group,
state_id="active", state_id="active",
desc="Has been copied", desc="Has been copied",
@ -826,7 +828,7 @@ class EditCharterTests(TestCase):
m = GroupMilestone.objects.create(group=charter.group, m = GroupMilestone.objects.create(group=charter.group,
state_id="active", state_id="active",
desc="Test milestone", desc="Test milestone",
due=datetime.date.today(), due=date_today(DEADLINE_TZINFO),
resolved="") resolved="")
url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev)) url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev))

View file

@ -13,6 +13,7 @@ from pyquery import PyQuery
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -33,6 +34,7 @@ from ietf.iesg.models import TelechatDate
from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.timezone import date_today, datetime_from_date
class ChangeStateTests(TestCase): class ChangeStateTests(TestCase):
@ -401,11 +403,11 @@ class EditInfoTests(TestCase):
# change to a telechat that should cause returning item to be auto-detected # change to a telechat that should cause returning item to be auto-detected
# First, make it appear that the previous telechat has already passed # First, make it appear that the previous telechat has already passed
telechat_event.telechat_date = datetime.date.today()-datetime.timedelta(days=7) telechat_event.telechat_date = date_today() - datetime.timedelta(days=7)
telechat_event.save() telechat_event.save()
ad = Person.objects.get(user__username="ad") ad = Person.objects.get(user__username="ad")
ballot = create_ballot_if_not_open(None, draft, ad, 'approve') ballot = create_ballot_if_not_open(None, draft, ad, 'approve')
ballot.time = telechat_event.telechat_date ballot.time = datetime_from_date(telechat_event.telechat_date)
ballot.save() ballot.save()
r = self.client.post(url, data) r = self.client.post(url, data)
@ -428,7 +430,7 @@ class EditInfoTests(TestCase):
self.assertTrue("Telechat update" in outbox[-1]['Subject']) self.assertTrue("Telechat update" in outbox[-1]['Subject'])
# Put it on an agenda that's very soon from now # Put it on an agenda that's very soon from now
next_week = datetime.date.today()+datetime.timedelta(days=7) next_week = date_today() + datetime.timedelta(days=7)
td = TelechatDate.objects.active()[0] td = TelechatDate.objects.active()[0]
td.date = next_week td.date = next_week
td.save() td.save()
@ -618,7 +620,7 @@ class ResurrectTests(DraftFileMixin, TestCase):
self.assertEqual(draft.docevent_set.count(), events_before + 1) self.assertEqual(draft.docevent_set.count(), events_before + 1)
self.assertEqual(draft.latest_event().type, "completed_resurrect") self.assertEqual(draft.latest_event().type, "completed_resurrect")
self.assertEqual(draft.get_state_slug(), "active") 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.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue('Resurrection Completed' in outbox[-1]['Subject']) self.assertTrue('Resurrection Completed' in outbox[-1]['Subject'])
self.assertTrue('iesg-secretary' in outbox[-1]['To']) self.assertTrue('iesg-secretary' in outbox[-1]['To'])
@ -659,7 +661,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# hack into expirable state # hack into expirable state
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) 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")]) 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) self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 1)
@ -698,7 +700,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# hack into expirable state # hack into expirable state
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) 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")]) 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) self.assertEqual(len(list(get_expired_drafts())), 1)
@ -741,7 +743,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
draft.delete() draft.delete()
rgdraft = RgDraftFactory(expires=datetime.datetime.now()) rgdraft = RgDraftFactory(expires=timezone.now())
self.assertEqual(len(list(get_expired_drafts())), 1) self.assertEqual(len(list(get_expired_drafts())), 1)
for slug in ('iesg-rev','irsgpoll'): for slug in ('iesg-rev','irsgpoll'):
rgdraft.set_state(State.objects.get(type_id='draft-stream-irtf',slug=slug)) rgdraft.set_state(State.objects.get(type_id='draft-stream-irtf',slug=slug))
@ -791,7 +793,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# expire draft # expire draft
draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) 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")]) 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, e = DocEvent(doc=draft, rev=draft.rev, type= "expired_document", time=draft.expires,
@ -824,7 +826,7 @@ class ExpireLastCallTests(TestCase):
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent" e.text = "Last call sent"
e.expires = datetime.datetime.now() + datetime.timedelta(days=14) e.expires = timezone.now() + datetime.timedelta(days=14)
e.save() e.save()
self.assertEqual(len(list(get_expired_last_calls())), 0) self.assertEqual(len(list(get_expired_last_calls())), 0)
@ -832,7 +834,7 @@ class ExpireLastCallTests(TestCase):
# test expired # test expired
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent" e.text = "Last call sent"
e.expires = datetime.datetime.now() e.expires = timezone.now()
e.save() e.save()
drafts = list(get_expired_last_calls()) drafts = list(get_expired_last_calls())
@ -866,7 +868,7 @@ class ExpireLastCallTests(TestCase):
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary) e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent" 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.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() e.save()
drafts = list(get_expired_last_calls()) drafts = list(get_expired_last_calls())
@ -1730,7 +1732,7 @@ class ChangeStreamStateTests(TestCase):
self.assertEqual(draft.docevent_set.count() - events_before, 2) self.assertEqual(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s") reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEqual(len(reminder), 1) 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.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEqual(len(outbox), 1) self.assertEqual(len(outbox), 1)
self.assertTrue("state changed" in outbox[0]["Subject"].lower()) self.assertTrue("state changed" in outbox[0]["Subject"].lower())
@ -1775,7 +1777,7 @@ class ChangeStreamStateTests(TestCase):
self.assertEqual(draft.docevent_set.count() - events_before, 2) self.assertEqual(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s") reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEqual(len(reminder), 1) 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.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEqual(len(outbox), 1) self.assertEqual(len(outbox), 1)
self.assertTrue("state changed" in outbox[0]["Subject"].lower()) self.assertTrue("state changed" in outbox[0]["Subject"].lower())
@ -1826,7 +1828,7 @@ class ChangeReplacesTests(TestCase):
name="draft-test-base-b", name="draft-test-base-b",
title="Base B", title="Base B",
group=mars_wg, 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") p = PersonFactory(name="baseb_author")
e = Email.objects.create(address="baseb_author@example.com", person=p, origin=p.user.username) e = Email.objects.create(address="baseb_author@example.com", person=p, origin=p.user.username)

View file

@ -19,6 +19,7 @@ from ietf.doc.utils import create_ballot_if_not_open, close_ballot
from ietf.person.utils import get_active_irsg, get_active_ads from ietf.person.utils import get_active_irsg, get_active_ads
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
class IssueIRSGBallotTests(TestCase): class IssueIRSGBallotTests(TestCase):
@ -254,7 +255,7 @@ class IssueIRSGBallotTests(TestCase):
irsgmember = get_active_irsg()[0] irsgmember = get_active_irsg()[0]
secr = RoleFactory(group__acronym='secretariat',name_id='secr') secr = RoleFactory(group__acronym='secretariat',name_id='secr')
wg_ballot = create_ballot_if_not_open(None, wg_draft, ad.person, 'approve') wg_ballot = create_ballot_if_not_open(None, wg_draft, ad.person, 'approve')
due = datetime.date.today()+datetime.timedelta(days=14) due = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=14)
rg_ballot = create_ballot_if_not_open(None, rg_draft, secr.person, 'irsg-approve', due) rg_ballot = create_ballot_if_not_open(None, rg_draft, secr.person, 'irsg-approve', due)
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=wg_ballot.pk)) url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=wg_ballot.pk))

View file

@ -4,7 +4,6 @@
import os import os
import shutil import shutil
import datetime
import io import io
from pathlib import Path from pathlib import Path
@ -14,6 +13,7 @@ import debug # pyflakes:ignore
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
@ -155,7 +155,7 @@ class GroupMaterialTests(TestCase):
name = "session-42-mars-1", name = "session-42-mars-1",
meeting = Meeting.objects.get(number='42'), meeting = Meeting.objects.get(number='42'),
group = Group.objects.get(acronym='mars'), group = Group.objects.get(acronym='mars'),
modified = datetime.datetime.now(), modified = timezone.now(),
) )
SchedulingEvent.objects.create( SchedulingEvent.objects.create(
session=session, session=session,

View file

@ -10,10 +10,10 @@ import email.mime.multipart, email.mime.text, email.utils
from mock import patch from mock import patch
from requests import Response from requests import Response
from django.apps import apps from django.apps import apps
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.conf import settings from django.conf import settings
from django.utils import timezone
from pyquery import PyQuery from pyquery import PyQuery
@ -38,6 +38,7 @@ from ietf.utils.mail import outbox, empty_outbox, parseaddr, on_behalf_of, get_p
from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.text import strip_prefix, xslugify from ietf.utils.text import strip_prefix, xslugify
from ietf.utils.timezone import DEADLINE_TZINFO
from django.utils.html import escape from django.utils.html import escape
class ReviewTests(TestCase): class ReviewTests(TestCase):
@ -67,7 +68,7 @@ class ReviewTests(TestCase):
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') 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') 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') 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 }) url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
@ -145,7 +146,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01') 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")) 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') 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') 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 # move the review request to a doubly-replaced document to
@ -166,7 +167,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01', authors=[author]) 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")) 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') 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') 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 }) url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@ -195,7 +196,7 @@ class ReviewTests(TestCase):
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') 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='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') 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()) 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 }) close_url = urlreverse('ietf.doc.views_review.close_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@ -260,7 +261,7 @@ class ReviewTests(TestCase):
# previous review # previous review
req = ReviewRequestFactory( req = ReviewRequestFactory(
time=datetime.datetime.now() - datetime.timedelta(days=100), time=timezone.now() - datetime.timedelta(days=100),
requested_by=Person.objects.get(name="(System)"), requested_by=Person.objects.get(name="(System)"),
doc=doc, doc=doc,
type_id='early', type_id='early',
@ -372,7 +373,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01') 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")) 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') 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()) 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 }) url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@ -395,7 +396,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) 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') 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='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') 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 }) reject_url = urlreverse('ietf.doc.views_review.reject_reviewer_assignment', kwargs={ "name": doc.name, "assignment_id": assignment.pk })
@ -495,7 +496,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) 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') 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='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') assignment = ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted')
# test URL construction # test URL construction
@ -587,7 +588,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) 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') 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='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()) 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")): for r in ReviewResultName.objects.filter(slug__in=("issues", "ready")):
review_req.team.reviewteamsettings.review_results.add(r) review_req.team.reviewteamsettings.review_results.add(r)
@ -699,7 +700,7 @@ class ReviewTests(TestCase):
assignment = reload_db_objects(assignment) assignment = reload_db_objects(assignment)
self.assertEqual(assignment.state_id, "completed") 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 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)) self.assertLess(completed_time_diff, datetime.timedelta(seconds=10))
with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f: with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f:
@ -733,15 +734,15 @@ class ReviewTests(TestCase):
# The secretary is allowed to set a custom completion date (#2590) # The secretary is allowed to set a custom completion date (#2590)
assignment = reload_db_objects(assignment) assignment = reload_db_objects(assignment)
self.assertEqual(assignment.state_id, "completed") self.assertEqual(assignment.state_id, "completed")
self.assertEqual(assignment.completed_on, datetime.datetime(2012, 12, 24, 12, 13, 14)) self.assertEqual(assignment.completed_on, datetime.datetime(2012, 12, 24, 12, 13, 14, tzinfo=DEADLINE_TZINFO))
# There should be two events: # There should be two events:
# - the event logging when the change when it was entered, i.e. very close to now. # - 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 # - the completion of the review, set to the provided date/time
events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-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.assertLess(event0_time_diff, datetime.timedelta(seconds=10))
self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14)) self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14, tzinfo=DEADLINE_TZINFO))
with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f: with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f:
self.assertEqual(f.read(), "This is a review\nwith two lines") self.assertEqual(f.read(), "This is a review\nwith two lines")
@ -985,7 +986,7 @@ class ReviewTests(TestCase):
self.assertEqual(assignment.state_id, "completed") self.assertEqual(assignment.state_id, "completed")
# The revision event time should be the date the revision was submitted, i.e. not backdated # The revision event time should be the date the revision was submitted, i.e. not backdated
event1 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent) 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.assertLess(event_time_diff, datetime.timedelta(seconds=10))
self.assertTrue('revised' in event1.desc.lower()) self.assertTrue('revised' in event1.desc.lower())
@ -1012,7 +1013,7 @@ class ReviewTests(TestCase):
assignment = reload_db_objects(assignment) assignment = reload_db_objects(assignment)
self.assertEqual(assignment.review.rev, "01") self.assertEqual(assignment.review.rev, "01")
event2 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent) 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)) self.assertLess(event_time_diff, datetime.timedelta(seconds=10))
# Ensure that a new event was created for the new revision (#2590) # Ensure that a new event was created for the new revision (#2590)
self.assertNotEqual(event1.id, event2.id) self.assertNotEqual(event1.id, event2.id)
@ -1024,7 +1025,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) 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') 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='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') 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 }) url = urlreverse('ietf.doc.views_review.edit_comment', kwargs={ "name": doc.name, "request_id": review_req.pk })
@ -1046,7 +1047,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) 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') 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='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') 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 }) url = urlreverse('ietf.doc.views_review.edit_deadline', kwargs={ "name": doc.name, "request_id": review_req.pk })

View file

@ -143,7 +143,7 @@ class ActionHoldersTests(TestCase):
doc = self.doc_in_iesg_state('pub-req') doc = self.doc_in_iesg_state('pub-req')
doc.action_holders.set([self.ad]) doc.action_holders.set([self.ad])
dah = doc.documentactionholder_set.get(person=self.ad) dah = doc.documentactionholder_set.get(person=self.ad)
dah.time_added = datetime.datetime(2020, 1, 1) # arbitrary date in the past dah.time_added = datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc) # arbitrary date in the past
dah.save() dah.save()
self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today()) self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today())

View file

@ -18,6 +18,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.forms import ValidationError from django.forms import ValidationError
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
@ -38,6 +39,7 @@ from ietf.review.models import ReviewWish
from ietf.utils import draft, log from ietf.utils import draft, log
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.timezone import date_today, datetime_from_date, datetime_today, DEADLINE_TZINFO
from ietf.utils.xmldraft import XMLDraft from ietf.utils.xmldraft import XMLDraft
@ -636,11 +638,22 @@ def has_same_ballot(doc, date1, date2=None):
""" Test if the most recent ballot created before the end of date1 """ Test if the most recent ballot created before the end of date1
is the same as the most recent ballot created before the is the same as the most recent ballot created before the
end of date 2. """ end of date 2. """
datetime1 = datetime_from_date(date1, DEADLINE_TZINFO)
if date2 is None: if date2 is None:
date2 = datetime.date.today() datetime2 = datetime_today(DEADLINE_TZINFO)
ballot1 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date1+datetime.timedelta(days=1)) else:
ballot2 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date2+datetime.timedelta(days=1)) datetime2 = datetime_from_date(date2, DEADLINE_TZINFO)
return ballot1==ballot2 ballot1 = doc.latest_event(
BallotDocEvent,
type='created_ballot',
time__lt=datetime1 + datetime.timedelta(days=1),
)
ballot2 = doc.latest_event(
BallotDocEvent,
type='created_ballot',
time__lt=datetime2 + datetime.timedelta(days=1),
)
return ballot1 == ballot2
def make_notify_changed_event(request, doc, by, new_notify, time=None): def make_notify_changed_event(request, doc, by, new_notify, time=None):
@ -686,7 +699,7 @@ def update_telechat(request, doc, by, new_telechat_date, new_returning_item=None
and on_agenda and on_agenda
and prev_agenda and prev_agenda
and new_telechat_date != prev_telechat and new_telechat_date != prev_telechat
and prev_telechat < datetime.date.today() and prev_telechat < date_today(DEADLINE_TZINFO)
and has_same_ballot(doc,prev.telechat_date) and has_same_ballot(doc,prev.telechat_date)
): ):
returning = True returning = True
@ -807,7 +820,7 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, com
cc.update(other_addrs.cc) cc.update(other_addrs.cc)
RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete() RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete()
if not RelatedDocument.objects.filter(target=d, relationship=relationship): 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)) d.document.set_state(State.objects.get(type='draft', slug=s))
for d in new_replaces: for d in new_replaces:
@ -955,6 +968,7 @@ def make_rev_history(doc):
history[url]['pages'] = d.history_set.filter(rev=e.newrevisiondocevent.rev).first().pages history[url]['pages'] = d.history_set.filter(rev=e.newrevisiondocevent.rev).first().pages
if doc.type_id == "draft": if doc.type_id == "draft":
# e.time.date() agrees with RPC publication date when shown in the RPC_TZINFO time zone
e = doc.latest_event(type='published_rfc') e = doc.latest_event(type='published_rfc')
else: else:
e = doc.latest_event(type='iesg_approved') e = doc.latest_event(type='iesg_approved')
@ -1118,7 +1132,7 @@ def build_doc_meta_block(doc, path):
lines[i] = line lines[i] = line
return lines return lines
# #
now = datetime.datetime.now() now = timezone.now()
draft_state = doc.get_state('draft') draft_state = doc.get_state('draft')
block = '' block = ''
meta = {} meta = {}

View file

@ -11,6 +11,7 @@ import shutil
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import smart_text, force_text from django.utils.encoding import smart_text, force_text
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -73,7 +74,7 @@ def change_group_state_after_charter_approval(group, by):
save_group_in_history(group) save_group_in_history(group)
group.state = new_state group.state = new_state
group.time = datetime.datetime.now() group.time = timezone.now()
group.save() group.save()
# create an event for the group state change, too # 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) # revision (when approving a charter)
just_before_next_rev = e[0].time - datetime.timedelta(seconds=5) just_before_next_rev = e[0].time - datetime.timedelta(seconds=5)
else: else:
just_before_next_rev = datetime.datetime.now() just_before_next_rev = timezone.now()
res = [] res = []
if hasattr(charter, 'chartered_group'): if hasattr(charter, 'chartered_group'):
@ -197,7 +198,7 @@ def derive_new_work_text(review_text,group):
return smart_text(m.as_string()) return smart_text(m.as_string())
def default_review_text(group, charter, by): 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) addrs = gather_address_lists('charter_external_review',group=group).as_strings(compact=False)
e1 = WriteupDocEvent(doc=charter, rev=charter.rev, by=by) e1 = WriteupDocEvent(doc=charter, rev=charter.rev, by=by)

View file

@ -5,6 +5,10 @@ import re
import datetime import datetime
import debug # pyflakes:ignore import debug # pyflakes:ignore
from zoneinfo import ZoneInfo
from django.conf import settings
from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, TelechatDocEvent, BallotDocEvent from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, TelechatDocEvent, BallotDocEvent
from ietf.doc.expire import expirable_drafts from ietf.doc.expire import expirable_drafts
from ietf.doc.utils import augment_docs_and_user_with_user_info from ietf.doc.utils import augment_docs_and_user_with_user_info
@ -204,7 +208,7 @@ def prepare_document_table(request, docs, query=None, max_results=200):
if sort_key == "title": if sort_key == "title":
res.append(d.title) res.append(d.title)
elif sort_key == "date": elif sort_key == "date":
res.append(str(d.latest_revision_date)) res.append(str(d.latest_revision_date.astimezone(ZoneInfo(settings.TIME_ZONE))))
elif sort_key == "status": elif sort_key == "status":
if rfc_num != None: if rfc_num != None:
res.append(num(rfc_num)) res.append(num(rfc_num))

View file

@ -40,6 +40,8 @@ from ietf.person.models import Person
from ietf.utils.mail import send_mail_text, send_mail_preformatted from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.utils.decorators import require_api_key from ietf.utils.decorators import require_api_key
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO
BALLOT_CHOICES = (("yes", "Yes"), BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"), ("noobj", "No Objection"),
@ -1055,9 +1057,11 @@ def make_last_call(request, name):
e.desc = "The following Last Call announcement was sent out (ends %s):<br><br>" % expiration_date e.desc = "The following Last Call announcement was sent out (ends %s):<br><br>" % expiration_date
e.desc += announcement e.desc += announcement
if form.cleaned_data['last_call_sent_date'] != e.time.date(): e_production_time = e.time.astimezone(DEADLINE_TZINFO)
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) if form.cleaned_data['last_call_sent_date'] != e_production_time.date():
e.expires = expiration_date lcsd = form.cleaned_data['last_call_sent_date']
e.time = e_production_time.replace(year=lcsd.year, month=lcsd.month, day=lcsd.day) # preserves tzinfo
e.expires = datetime_from_date(expiration_date, DEADLINE_TZINFO)
e.save() e.save()
events.append(e) events.append(e)
@ -1108,7 +1112,7 @@ def issue_irsg_ballot(request, name):
raise Http404 raise Http404
by = request.user.person by = request.user.person
fillerdate = datetime.date.today() + datetime.timedelta(weeks=2) fillerdate = date_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=2)
if request.method == 'POST': if request.method == 'POST':
button = request.POST.get("irsg_button") button = request.POST.get("irsg_button")
@ -1117,7 +1121,7 @@ def issue_irsg_ballot(request, name):
e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person) e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person)
if (duedate == None or duedate==""): if (duedate == None or duedate==""):
duedate = str(fillerdate) duedate = str(fillerdate)
e.duedate = datetime.datetime.strptime(duedate, '%Y-%m-%d') e.duedate = datetime_from_date(datetime.datetime.strptime(duedate, '%Y-%m-%d'), DEADLINE_TZINFO)
e.type = "created_ballot" e.type = "created_ballot"
e.desc = "Created IRSG Ballot" e.desc = "Created IRSG Ballot"
ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve") ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve")

View file

@ -16,6 +16,7 @@ from django.utils.safestring import mark_safe
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import escape from django.utils.html import escape
@ -77,7 +78,7 @@ def change_state(request, name, option=None):
chartering_type = get_chartering_type(charter) chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") 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 initial_review = None
by = request.user.person 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: 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 = 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.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
e.save() e.save()
@ -506,7 +507,7 @@ def review_announcement_text(request, name):
existing_new_work.type = "changed_new_work_text" existing_new_work.type = "changed_new_work_text"
existing_new_work.desc = "%s review text was changed" % group.type.name 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.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))) 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) form = ReviewAnnouncementTextForm(request.POST)
if "save_text" in request.POST and form.is_valid(): if "save_text" in request.POST and form.is_valid():
now = datetime.datetime.now() now = timezone.now()
events = [] events = []
t = form.cleaned_data['announcement_text'] t = form.cleaned_data['announcement_text']

View file

@ -967,7 +967,7 @@ def document_bibtex(request, name, rev=None):
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision") latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
replaced_by = [d.name for d in doc.related_that("replaces")] replaced_by = [d.name for d in doc.related_that("replaces")]
published = doc.latest_event(type="published_rfc") published = doc.latest_event(type="published_rfc") is not None
rfc = latest_revision.doc if latest_revision and latest_revision.doc.get_state_slug() == "rfc" else None rfc = latest_revision.doc if latest_revision and latest_revision.doc.get_state_slug() == "rfc" else None
if rev != None and rev != doc.rev: if rev != None and rev != doc.rev:

View file

@ -19,6 +19,7 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.template.defaultfilters import pluralize from django.template.defaultfilters import pluralize
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -52,6 +53,8 @@ from ietf.utils.mail import send_mail, send_mail_message, on_behalf_of
from ietf.utils.textupload import get_cleaned_text_file_content from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.utils import log from ietf.utils import log
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
class ChangeStateForm(forms.Form): class ChangeStateForm(forms.Form):
state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True) state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True)
@ -857,7 +860,7 @@ def resurrect(request, name):
events.append(e) events.append(e)
doc.set_state(State.objects.get(used=True, type="draft", slug="active")) 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) doc.save_with_history(events)
restore_draft_file(request, doc) restore_draft_file(request, doc)
@ -1476,7 +1479,7 @@ def adopt_draft(request, name):
due_date = None due_date = None
if form.cleaned_data["weeks"] != None: if form.cleaned_data["weeks"] != None:
due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) due_date = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=form.cleaned_data["weeks"])
update_reminder(doc, "stream-s", e, due_date) update_reminder(doc, "stream-s", e, due_date)
@ -1667,7 +1670,7 @@ def change_stream_state(request, name, state_type):
due_date = None due_date = None
if form.cleaned_data["weeks"] != None: if form.cleaned_data["weeks"] != None:
due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) due_date = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=form.cleaned_data["weeks"])
update_reminder(doc, "stream-s", e, due_date) update_reminder(doc, "stream-s", e, due_date)

View file

@ -10,7 +10,9 @@ import datetime
import requests import requests
import email.utils import email.utils
from django.utils import timezone
from django.utils.http import is_safe_url from django.utils.http import is_safe_url
from simple_history.utils import update_change_reason from simple_history.utils import update_change_reason
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -52,6 +54,8 @@ from ietf.utils.mail import send_mail_message
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.fields import MultiEmailField from ietf.utils.fields import MultiEmailField
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import DEADLINE_TZINFO
def clean_doc_revision(doc, rev): def clean_doc_revision(doc, rev):
if rev: if rev:
@ -117,7 +121,7 @@ def request_review(request, name):
if not can_request_review_of_doc(request.user, doc): if not can_request_review_of_doc(request.user, doc):
permission_denied(request, "You do not have permission to perform this action") permission_denied(request, "You do not have permission to perform this action")
now = datetime.datetime.now() now = timezone.now()
lc_ends = None lc_ends = None
e = doc.latest_event(LastCallDocEvent, type="sent_last_call") e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
@ -364,7 +368,7 @@ def reject_reviewer_assignment(request, name, assignment_id):
if form.is_valid(): if form.is_valid():
# reject the assignment # reject the assignment
review_assignment.state = ReviewAssignmentStateName.objects.get(slug="rejected") review_assignment.state = ReviewAssignmentStateName.objects.get(slug="rejected")
review_assignment.completed_on = datetime.datetime.now() review_assignment.completed_on = timezone.now()
review_assignment.save() review_assignment.save()
descr = "Assignment of request for {} review by {} to {} was rejected".format( descr = "Assignment of request for {} review by {} to {} was rejected".format(
@ -731,13 +735,13 @@ def complete_review(request, name, assignment_id=None, acronym=None):
review_request=review_request, review_request=review_request,
state_id='assigned', state_id='assigned',
reviewer=form.cleaned_data['reviewer'].role_email('reviewer', group=team), reviewer=form.cleaned_data['reviewer'].role_email('reviewer', group=team),
assigned_on=datetime.datetime.now(), assigned_on=timezone.now(),
review = review, review = review,
) )
review.rev = "00" if not review.rev else "{:02}".format(int(review.rev) + 1) 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.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": if review_submission == "link":
review.external_url = form.cleaned_data['review_url'] review.external_url = form.cleaned_data['review_url']
@ -764,9 +768,13 @@ def complete_review(request, name, assignment_id=None, acronym=None):
with io.open(filename, 'w', encoding='utf-8') as destination: with io.open(filename, 'w', encoding='utf-8') as destination:
destination.write(content) destination.write(content)
completion_datetime = datetime.datetime.now() completion_datetime = timezone.now()
if "completion_date" in form.cleaned_data: 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) completion_datetime = datetime.datetime.combine(
form.cleaned_data["completion_date"],
form.cleaned_data.get("completion_time") or datetime.time.min,
tzinfo=DEADLINE_TZINFO,
)
# complete assignment # complete assignment
assignment.state = form.cleaned_data["state"] assignment.state = form.cleaned_data["state"]
@ -799,7 +807,7 @@ def complete_review(request, name, assignment_id=None, acronym=None):
close_event.by = request.user.person close_event.by = request.user.person
close_event.desc = desc close_event.desc = desc
close_event.state = assignment.state close_event.state = assignment.state
close_event.time = datetime.datetime.now() close_event.time = timezone.now()
close_event.save() close_event.save()
# If the completion date is different, record when the initial review was made too. # If the completion date is different, record when the initial review was made too.

View file

@ -453,7 +453,7 @@ def ad_dashboard_sort_key(doc):
ageseconds = 0 ageseconds = 0
changetime= doc.latest_event(type='changed_document') changetime= doc.latest_event(type='changed_document')
if changetime: 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 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) return "1%d%s%s%010d" % (state[0].order,seed,doc.type.slug,ageseconds)
@ -761,7 +761,7 @@ def recent_drafts(request, days=7):
cache_key = f'recentdraftsview{days}' cache_key = f'recentdraftsview{days}'
cached_val = slowcache.get(cache_key) cached_val = slowcache.get(cache_key)
if not cached_val: 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') state = State.objects.get(type='draft', slug='active')
events = NewRevisionDocEvent.objects.filter(time__gt=since) events = NewRevisionDocEvent.objects.filter(time__gt=since)
names = [ e.doc.name for e in events ] names = [ e.doc.name for e in events ]

View file

@ -5,6 +5,8 @@ import factory
from typing import List # pyflakes:ignore from typing import List # pyflakes:ignore
from django.utils import timezone
from ietf.group.models import Group, Role, GroupEvent, GroupMilestone, \ from ietf.group.models import Group, Role, GroupEvent, GroupMilestone, \
GroupHistory, RoleHistory GroupHistory, RoleHistory
from ietf.review.factories import ReviewTeamSettingsFactory from ietf.review.factories import ReviewTeamSettingsFactory
@ -66,7 +68,7 @@ class BaseGroupMilestoneFactory(factory.django.DjangoModelFactory):
class DatedGroupMilestoneFactory(BaseGroupMilestoneFactory): class DatedGroupMilestoneFactory(BaseGroupMilestoneFactory):
group = factory.SubFactory(GroupFactory, uses_milestone_dates=True) 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): class DatelessGroupMilestoneFactory(BaseGroupMilestoneFactory):
group = factory.SubFactory(GroupFactory, uses_milestone_dates=False) group = factory.SubFactory(GroupFactory, uses_milestone_dates=False)

View file

@ -15,6 +15,7 @@ from tempfile import mkstemp
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -39,7 +40,7 @@ class Command(BaseCommand):
'have seen activity in the last %s years.' % (DEFAULT_YEARS)) 'have seen activity in the last %s years.' % (DEFAULT_YEARS))
def handle(self, *args, **options): 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") date = time.strftime("%Y-%m-%d_%H:%M:%S")
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date) signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)

View file

@ -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', '0058_alter_has_default_chat'),
]
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),
),
]

View file

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
import email.utils import email.utils
import jsonfield import jsonfield
import os import os
@ -13,6 +12,7 @@ from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.db.models.deletion import CASCADE, PROTECT from django.db.models.deletion import CASCADE, PROTECT
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -27,7 +27,7 @@ from ietf.utils.validators import JSONForeignKeyListValidator
class GroupInfo(models.Model): class GroupInfo(models.Model):
time = models.DateTimeField(default=datetime.datetime.now) time = models.DateTimeField(default=timezone.now)
name = models.CharField(max_length=80) name = models.CharField(max_length=80)
state = ForeignKey(GroupStateName, null=True) state = ForeignKey(GroupStateName, null=True)
type = ForeignKey(GroupTypeName, null=True) type = ForeignKey(GroupTypeName, null=True)
@ -180,11 +180,15 @@ class Group(GroupInfo):
return self.role_set.none() return self.role_set.none()
def status_for_meeting(self,meeting): def status_for_meeting(self,meeting):
end_date = meeting.end_date()+datetime.timedelta(days=1)
previous_meeting = meeting.previous_meeting() 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: 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() return status_events.first()
def get_description(self): def get_description(self):
@ -353,7 +357,7 @@ GROUP_EVENT_CHOICES = [
class GroupEvent(models.Model): class GroupEvent(models.Model):
"""An occurrence for a group, used for tracking who, when and what.""" """An occurrence for a group, used for tracking who, when and what."""
group = ForeignKey(Group) 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) type = models.CharField(max_length=50, choices=GROUP_EVENT_CHOICES)
by = ForeignKey(Person) by = ForeignKey(Person)
desc = models.TextField() desc = models.TextField()

View file

@ -13,6 +13,7 @@ from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.db.models import Q from django.db.models import Q
from django.test import Client from django.test import Client
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -115,8 +116,8 @@ class GenerateGroupAliasesTests(TestCase):
super().tearDown() super().tearDown()
def testManagementCommand(self): def testManagementCommand(self):
a_month_ago = datetime.datetime.now() - datetime.timedelta(30) a_month_ago = timezone.now() - datetime.timedelta(30)
a_decade_ago = datetime.datetime.now() - datetime.timedelta(3650) a_decade_ago = timezone.now() - datetime.timedelta(3650)
role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active') role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active')
area = role1.group area = role1.group
ad = role1.person ad = role1.person

View file

@ -1900,7 +1900,7 @@ class StatusUpdateTests(TestCase):
def test_view_status_update_for_meeting(self): def test_view_status_update_for_meeting(self):
chair = RoleFactory(name_id='chair',group__type_id='wg') chair = RoleFactory(name_id='chair',group__type_id='wg')
GroupEventFactory(type='status_update',group=chair.group) 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}) url = urlreverse('ietf.group.views.group_about_status_meeting',kwargs={'acronym':chair.group.acronym,'num':sess.meeting.number})
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)

View file

@ -8,6 +8,7 @@ import debug # pyflakes:ignore
from pyquery import PyQuery from pyquery import PyQuery
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from ietf.review.policies import get_reviewer_queue_policy from ietf.review.policies import get_reviewer_queue_policy
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, reload_db_objects from ietf.utils.test_utils import login_testing_unauthorized, TestCase, reload_db_objects
@ -25,6 +26,7 @@ from ietf.person.factories import PersonFactory, EmailFactory
from ietf.doc.factories import DocumentFactory from ietf.doc.factories import DocumentFactory
from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory
from ietf.utils.timezone import date_today, datetime_today, DEADLINE_TZINFO
from django.utils.html import escape from django.utils.html import escape
class ReviewTests(TestCase): class ReviewTests(TestCase):
@ -131,7 +133,7 @@ class ReviewTests(TestCase):
doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True)) doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True))
LastCallDocEvent.objects.create( LastCallDocEvent.objects.create(
doc=doc, doc=doc,
expires=datetime.datetime.now() + datetime.timedelta(days=365), expires=timezone.now() + datetime.timedelta(days=365),
by=Person.objects.get(name="(System)"), by=Person.objects.get(name="(System)"),
rev=doc.rev rev=doc.rev
) )
@ -155,7 +157,7 @@ class ReviewTests(TestCase):
review_request__doc=review_req1.doc, review_request__doc=review_req1.doc,
review_request__team=review_req1.team, review_request__team=review_req1.team,
review_request__type_id="early", review_request__type_id="early",
review_request__deadline=datetime.date.today() + datetime.timedelta(days=30), review_request__deadline=date_today(DEADLINE_TZINFO) + datetime.timedelta(days=30),
review_request__state_id="assigned", review_request__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
@ -165,7 +167,7 @@ class ReviewTests(TestCase):
UnavailablePeriod.objects.create( UnavailablePeriod.objects.create(
team=review_req1.team, team=review_req1.team,
person=reviewer, person=reviewer,
start_date=datetime.date.today() - datetime.timedelta(days=10), start_date=date_today() - datetime.timedelta(days=10),
availability="unavailable", availability="unavailable",
) )
@ -210,7 +212,7 @@ class ReviewTests(TestCase):
review_request__doc=review_req2.doc, review_request__doc=review_req2.doc,
review_request__team=review_req2.team, review_request__team=review_req2.team,
review_request__type_id="lc", review_request__type_id="lc",
review_request__deadline=datetime.date.today() - datetime.timedelta(days=30), review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=30),
review_request__state_id="assigned", review_request__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "no-response", state_id = "no-response",
@ -231,15 +233,15 @@ class ReviewTests(TestCase):
review_req3 = ReviewRequestFactory(state_id='completed', team=team) review_req3 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req3.doc, review_request__doc=review_req3.doc,
review_request__time=datetime.date.today() - datetime.timedelta(days=30), review_request__time=datetime_today() - datetime.timedelta(days=30),
review_request__team=review_req3.team, review_request__team=review_req3.team,
review_request__type_id="telechat", review_request__type_id="telechat",
review_request__deadline=datetime.date.today() - datetime.timedelta(days=25), review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=25),
review_request__state_id="completed", review_request__state_id="completed",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "completed", state_id = "completed",
reviewer=reviewer.email_set.first(), reviewer=reviewer.email_set.first(),
assigned_on=datetime.date.today() - datetime.timedelta(days=30) assigned_on=datetime_today() - datetime.timedelta(days=30)
) )
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -252,15 +254,15 @@ class ReviewTests(TestCase):
for i in range(10): for i in range(10):
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=reqs[i].doc, review_request__doc=reqs[i].doc,
review_request__time=datetime.date.today() - datetime.timedelta(days=i*30), review_request__time=datetime_today() - datetime.timedelta(days=i*30),
review_request__team=reqs[i].team, review_request__team=reqs[i].team,
review_request__type_id="telechat", review_request__type_id="telechat",
review_request__deadline=datetime.date.today() - datetime.timedelta(days=i*20), review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=i*20),
review_request__state_id="completed", review_request__state_id="completed",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "completed", state_id = "completed",
reviewer=reviewer.email_set.first(), reviewer=reviewer.email_set.first(),
assigned_on=datetime.date.today() - datetime.timedelta(days=i*30) assigned_on=datetime_today() - datetime.timedelta(days=i*30)
) )
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -304,28 +306,28 @@ class ReviewTests(TestCase):
review_req4 = ReviewRequestFactory(state_id='completed', team=team) review_req4 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req4.doc, review_request__doc=review_req4.doc,
review_request__time=datetime.date.today() - datetime.timedelta(days=80), review_request__time=datetime_today() - datetime.timedelta(days=80),
review_request__team=review_req4.team, review_request__team=review_req4.team,
review_request__type_id="lc", review_request__type_id="lc",
review_request__deadline=datetime.date.today() - datetime.timedelta(days=60), review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=60),
review_request__state_id="assigned", review_request__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
reviewer=reviewer.email_set.first(), reviewer=reviewer.email_set.first(),
assigned_on=datetime.date.today() - datetime.timedelta(days=80) assigned_on=datetime_today() - datetime.timedelta(days=80)
) )
review_req5 = ReviewRequestFactory(state_id='completed', team=team) review_req5 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req5.doc, review_request__doc=review_req5.doc,
review_request__time=datetime.date.today() - datetime.timedelta(days=120), review_request__time=datetime_today() - datetime.timedelta(days=120),
review_request__team=review_req5.team, review_request__team=review_req5.team,
review_request__type_id="lc", review_request__type_id="lc",
review_request__deadline=datetime.date.today() - datetime.timedelta(days=100), review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=100),
review_request__state_id="assigned", review_request__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
reviewer=reviewer.email_set.first(), reviewer=reviewer.email_set.first(),
assigned_on=datetime.date.today() - datetime.timedelta(days=120) assigned_on=datetime_today() - datetime.timedelta(days=120)
) )
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -429,7 +431,7 @@ class ReviewTests(TestCase):
doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True)) doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True))
LastCallDocEvent.objects.create( LastCallDocEvent.objects.create(
doc=doc, doc=doc,
expires=datetime.datetime.now() + datetime.timedelta(days=365), expires=timezone.now() + datetime.timedelta(days=365),
by=Person.objects.get(name="(System)"), by=Person.objects.get(name="(System)"),
rev=doc.rev rev=doc.rev
) )

View file

@ -53,6 +53,7 @@ from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonRespons
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
from django.views.decorators.cache import cache_page, cache_control from django.views.decorators.cache import cache_page, cache_control
@ -118,6 +119,7 @@ from ietf.settings import MAILING_LIST_INFO_URL
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.text import strip_suffix from ietf.utils.text import strip_suffix
from ietf.utils import markdown from ietf.utils import markdown
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
# --- Helpers ---------------------------------------------------------- # --- Helpers ----------------------------------------------------------
@ -566,7 +568,7 @@ def all_status(request):
if e: if e:
wg_reports.append(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 = [] rg_reports = []
for rg in rgs: for rg in rgs:
@ -808,7 +810,7 @@ def email_aliases(request, acronym=None, group_type=None):
def meetings(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 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( sessions = add_event_info_to_session_qs(
group.session_set.filter( group.session_set.filter(
@ -972,7 +974,7 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None):
try: try:
group = Group.objects.get(acronym=clean["acronym"]) group = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(group) save_group_in_history(group)
group.time = datetime.datetime.now() group.time = timezone.now()
group.save() group.save()
except Group.DoesNotExist: except Group.DoesNotExist:
group = Group.objects.create(name=clean["name"], group = Group.objects.create(name=clean["name"],
@ -1071,7 +1073,7 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None):
) )
)) ))
group.time = datetime.datetime.now() group.time = timezone.now()
if changes and not new_group: if changes and not new_group:
for attr, new, desc in changes: for attr, new, desc in changes:
@ -1420,11 +1422,14 @@ def review_requests(request, acronym, group_type=None):
}[since] }[since]
closed_review_requests = closed_review_requests.filter( closed_review_requests = closed_review_requests.filter(
Q(reviewrequestdocevent__type='closed_review_request', reviewrequestdocevent__time__gte=datetime.date.today() - date_limit) Q(reviewrequestdocevent__type='closed_review_request',
| Q(reviewrequestdocevent__isnull=True, time__gte=datetime.date.today() - date_limit) reviewrequestdocevent__time__gte=datetime_today(DEADLINE_TZINFO) - date_limit)
| Q(reviewrequestdocevent__isnull=True, time__gte=datetime_today(DEADLINE_TZINFO) - date_limit)
).distinct() ).distinct()
closed_review_assignments = closed_review_assignments.filter(completed_on__gte = datetime.date.today() - date_limit) closed_review_assignments = closed_review_assignments.filter(
completed_on__gte = datetime_today(DEADLINE_TZINFO) - date_limit,
)
return render(request, 'group/review_requests.html', return render(request, 'group/review_requests.html',
construct_group_menu_context(request, group, "review requests", group_type, { construct_group_menu_context(request, group, "review requests", group_type, {
@ -1509,7 +1514,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)) 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.state in ["completed", "completed_in_time", "completed_late"]:
if d.assigned_time is not None: 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: if d.assignment_to_closure_days is not None:
days = int(delta.days - d.assignment_to_closure_days) days = int(delta.days - d.assignment_to_closure_days)
if days_since > days: days_since = days if days_since > days: days_since = days

View file

@ -11,6 +11,7 @@ import pytz
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -296,6 +297,6 @@ def id_index_txt(with_abstracts=False):
return render_to_string("idindex/id_index.txt", { return render_to_string("idindex/id_index.txt", {
'groups': groups, '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, 'with_abstracts': with_abstracts,
}) })

View file

@ -7,6 +7,7 @@ import datetime
from pathlib import Path from pathlib import Path
from django.conf import settings from django.conf import settings
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -120,7 +121,7 @@ class IndexTests(TestCase):
draft.set_state(State.objects.get(type="draft", slug="active")) draft.set_state(State.objects.get(type="draft", slug="active"))
draft.set_state(State.objects.get(type="draft-iesg", slug="lc")) 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()) t = get_fields(all_id2_txt())
self.assertEqual(t[11], e.expires.strftime("%Y-%m-%d")) self.assertEqual(t[11], e.expires.strftime("%Y-%m-%d"))

View file

@ -63,6 +63,7 @@ from ietf.iesg.utils import telechat_page_count
from ietf.ietfauth.utils import has_role, role_required, user_is_person from ietf.ietfauth.utils import has_role, role_required, user_is_person
from ietf.person.models import Person from ietf.person.models import Person
from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date
from ietf.utils.timezone import date_today, datetime_from_date
def review_decisions(request, year=None): def review_decisions(request, year=None):
events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved")) events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved"))
@ -73,9 +74,9 @@ def review_decisions(request, year=None):
year = int(year) year = int(year)
events = events.filter(time__year=year) events = events.filter(time__year=year)
else: else:
d = datetime.date.today() - datetime.timedelta(days=185) d = date_today() - datetime.timedelta(days=185)
d = datetime.date(d.year, d.month, 1) d = datetime.date(d.year, d.month, 1)
events = events.filter(time__gte=d) events = events.filter(time__gte=datetime_from_date(d))
events = events.select_related("doc", "doc__intended_std_level").order_by("-time", "-id") events = events.select_related("doc", "doc__intended_std_level").order_by("-time", "-id")

View file

@ -8,6 +8,7 @@ from textwrap import dedent
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -37,7 +38,7 @@ class Command(BaseCommand):
keys = PersonalApiKey.objects.filter(valid=True) keys = PersonalApiKey.objects.filter(valid=True)
for key in keys: 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) events = PersonApiKeyEvent.objects.filter(key=key, time__gt=earliest)
count = events.count() count = events.count()
events = events[:32] events = events[:32]

View file

@ -28,6 +28,7 @@ from django.urls import reverse as urlreverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -750,11 +751,11 @@ class IetfAuthTests(TestCase):
self.assertContains(r, 'Invalid apikey', status_code=403) self.assertContains(r, 'Invalid apikey', status_code=403)
# too long since regular login # 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() person.user.save()
r = self.client.post(key.endpoint, {'apikey':key.hash(), 'dummy':'dummy',}) r = self.client.post(key.endpoint, {'apikey':key.hash(), 'dummy':'dummy',})
self.assertContains(r, 'Too long since last regular login', status_code=400) 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() person.user.save()
# endpoint mismatch # endpoint mismatch
@ -783,7 +784,7 @@ class IetfAuthTests(TestCase):
# apikey usage will be registered) # apikey usage will be registered)
count = 2 count = 2
# avoid usage across dates # 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) time.sleep(2)
for i in range(count): for i in range(count):
for key in person.apikeys.all(): for key in person.apikeys.all():

View file

@ -34,9 +34,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import datetime
import importlib import importlib
from datetime import date as Date, datetime as DateTime
# needed if we revert to higher barrier for account creation # needed if we revert to higher barrier for account creation
#from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date #from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
from collections import defaultdict from collections import defaultdict
@ -79,6 +79,7 @@ from ietf.doc.fields import SearchableDocumentField
from ietf.utils.decorators import person_required from ietf.utils.decorators import person_required
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.utils.validators import validate_external_resource_value from ietf.utils.validators import validate_external_resource_value
from ietf.utils.timezone import date_today, DEADLINE_TZINFO
# These are needed if we revert to the higher bar for account creation # These are needed if we revert to the higher bar for account creation
@ -224,7 +225,7 @@ def profile(request):
emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time') emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time')
new_email_forms = [] new_email_forms = []
nc = NomCom.objects.filter(group__acronym__icontains=Date.today().year).first() nc = NomCom.objects.filter(group__acronym__icontains=date_today().year).first()
if nc and nc.volunteer_set.filter(person=person).exists(): if nc and nc.volunteer_set.filter(person=person).exists():
volunteer_status = 'volunteered' volunteer_status = 'volunteered'
elif nc and nc.is_accepting_volunteers: elif nc and nc.is_accepting_volunteers:
@ -456,7 +457,7 @@ def confirm_password_reset(request, auth):
password = data['password'] password = data['password']
last_login = None last_login = None
if data['last_login']: if data['last_login']:
last_login = DateTime.fromtimestamp(data['last_login']) last_login = datetime.datetime.fromtimestamp(data['last_login'], datetime.timezone.utc)
except django.core.signing.BadSignature: except django.core.signing.BadSignature:
raise Http404("Invalid or expired auth") raise Http404("Invalid or expired auth")
@ -558,7 +559,7 @@ def review_overview(request):
reviewer__person__user=request.user, reviewer__person__user=request.user,
state__in=["assigned", "accepted"], state__in=["assigned", "accepted"],
) )
today = Date.today() today = date_today(DEADLINE_TZINFO)
for r in open_review_assignments: for r in open_review_assignments:
r.due = max(0, (today - r.review_request.deadline).days) r.due = max(0, (today - r.review_request.deadline).days)

View file

@ -5,6 +5,7 @@
import datetime import datetime
import factory import factory
from django.utils import timezone
from ietf.ipr.models import ( from ietf.ipr.models import (
IprDisclosureBase, HolderIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure, IprDisclosureBase, HolderIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure,
@ -13,7 +14,7 @@ from ietf.ipr.models import (
def _fake_patent_info(): def _fake_patent_info():
return "Date: %s\nNotes: %s\nTitle: %s\nNumber: %s\nInventor: %s\n" % ( 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('paragraph'),
factory.Faker('sentence', nb_words=8), factory.Faker('sentence', nb_words=8),
'US9999999', 'US9999999',

View file

@ -3,13 +3,14 @@
import base64 import base64
import email
import datetime import datetime
from dateutil.tz import tzoffset from dateutil.tz import tzoffset
import os import os
import pytz
import re import re
from email import message_from_bytes
from email.utils import parsedate_tz
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import force_text, force_bytes from django.utils.encoding import force_text, force_bytes
@ -50,7 +51,7 @@ def parsedate_to_datetime(date):
http://python.readthedocs.org/en/latest/library/email.util.html http://python.readthedocs.org/en/latest/library/email.util.html
""" """
try: try:
tuple = email.utils.parsedate_tz(date) tuple = parsedate_tz(date)
if not tuple: if not tuple:
return None return None
tz = tuple[-1] tz = tuple[-1]
@ -62,10 +63,12 @@ def parsedate_to_datetime(date):
def utc_from_string(s): def utc_from_string(s):
date = parsedate_to_datetime(s) date = parsedate_to_datetime(s)
if is_aware(date): if date is None:
return date.astimezone(pytz.utc).replace(tzinfo=None) return None
elif is_aware(date):
return date.astimezone(datetime.timezone.utc)
else: else:
return date return date.replace(tzinfo=datetime.timezone.utc)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# Email Functions # Email Functions
@ -174,7 +177,7 @@ def process_response_email(msg):
a matching value in the reply_to field, associated to an IPR disclosure through a matching value in the reply_to field, associated to an IPR disclosure through
IprEvent. Create a Message object for the incoming message and associate it to IprEvent. Create a Message object for the incoming message and associate it to
the original message via new IprEvent""" the original message via new IprEvent"""
message = email.message_from_bytes(force_bytes(msg)) message = message_from_bytes(force_bytes(msg))
to = message.get('To', '') to = message.get('To', '')
# exit if this isn't a response we're interested in (with plus addressing) # exit if this isn't a response we're interested in (with plus addressing)

View file

@ -2,11 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from ietf.doc.models import DocAlias, DocEvent from ietf.doc.models import DocAlias, DocEvent
from ietf.name.models import DocRelationshipName,IprDisclosureStateName,IprLicenseTypeName,IprEventTypeName from ietf.name.models import DocRelationshipName,IprDisclosureStateName,IprLicenseTypeName,IprEventTypeName
@ -220,7 +219,7 @@ class IprEvent(models.Model):
"""Returns true if it's beyond the response_due date and no response has been """Returns true if it's beyond the response_due date and no response has been
received""" received"""
qs = IprEvent.objects.filter(disclosure=self.disclosure,in_reply_to=self.message) 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 return True
else: else:
return False return False

View file

@ -8,8 +8,9 @@ import datetime
from pyquery import PyQuery from pyquery import PyQuery
from urllib.parse import quote, urlparse from urllib.parse import quote, urlparse
from django.urls import reverse as urlreverse
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -640,7 +641,7 @@ I would like to revoke this declaration.
message_string.format( message_string.format(
to=addrs.to, to=addrs.to,
cc=addrs.cc, cc=addrs.cc,
date=datetime.datetime.now().ctime() date=timezone.now().ctime()
) )
) )
self.assertIsNone(result) self.assertIsNone(result)
@ -650,7 +651,7 @@ I would like to revoke this declaration.
From: joe@test.com From: joe@test.com
Date: {} Date: {}
Subject: test Subject: test
""".format(reply_to, datetime.datetime.now().ctime()) """.format(reply_to, timezone.now().ctime())
result = process_response_email(message_string) result = process_response_email(message_string)
self.assertIsInstance(result, Message) self.assertIsInstance(result, Message)
@ -664,7 +665,7 @@ Subject: test
From: joe@test.com From: joe@test.com
Date: {} Date: {}
Subject: test 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' message_bytes = message_string.encode('utf8') + b'\nInvalid stuff: \xfe\xff\n'
result = process_response_email(message_bytes) result = process_response_email(message_bytes)
self.assertIsInstance(result, Message) self.assertIsInstance(result, Message)
@ -680,7 +681,7 @@ Subject: test
message_bytes = message_string.format( message_bytes = message_string.format(
to=addrs.to, to=addrs.to,
cc=addrs.cc, cc=addrs.cc,
date=datetime.datetime.now().ctime(), date=timezone.now().ctime(),
).encode('utf8') + b'\nInvalid stuff: \xfe\xff\n' ).encode('utf8') + b'\nInvalid stuff: \xfe\xff\n'
result = process_response_email(message_bytes) result = process_response_email(message_bytes)
self.assertIsNone(result) self.assertIsNone(result)

View file

@ -43,6 +43,7 @@ from ietf.utils.draft_search import normalize_draftname
from ietf.utils.mail import send_mail, send_mail_message from ietf.utils.mail import send_mail, send_mail_message
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.text import text_to_dict from ietf.utils.text import text_to_dict
from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# Globals # Globals
@ -147,15 +148,14 @@ def ipr_rfc_number(disclosureDate, thirdPartyDisclosureFlag):
# made on 1993-07-23, which is more than a year after RFC 1310. # made on 1993-07-23, which is more than a year after RFC 1310.
# RFC publication date comes from the RFC Editor announcement # RFC publication date comes from the RFC Editor announcement
# TODO: These times are tzinfo=pytz.utc, but disclosure times are offset-naive
ipr_rfc_pub_datetime = { ipr_rfc_pub_datetime = {
1310 : datetime.datetime(1992, 3, 13, 0, 0), 1310 : datetime.datetime(1992, 3, 13, 0, 0, tzinfo=datetime.timezone.utc),
1802 : datetime.datetime(1994, 3, 23, 0, 0), 1802 : datetime.datetime(1994, 3, 23, 0, 0, tzinfo=datetime.timezone.utc),
2026 : datetime.datetime(1996, 10, 29, 0, 0), 2026 : datetime.datetime(1996, 10, 29, 0, 0, tzinfo=datetime.timezone.utc),
3668 : datetime.datetime(2004, 2, 18, 0, 0), 3668 : datetime.datetime(2004, 2, 18, 0, 0, tzinfo=datetime.timezone.utc),
3979 : datetime.datetime(2005, 3, 2, 2, 23), 3979 : datetime.datetime(2005, 3, 2, 2, 23, tzinfo=datetime.timezone.utc),
4879 : datetime.datetime(2007, 4, 10, 18, 21), 4879 : datetime.datetime(2007, 4, 10, 18, 21, tzinfo=datetime.timezone.utc),
8179 : datetime.datetime(2017, 5, 31, 23, 1), 8179 : datetime.datetime(2017, 5, 31, 23, 1, tzinfo=datetime.timezone.utc),
} }
if disclosureDate < ipr_rfc_pub_datetime[1310]: if disclosureDate < ipr_rfc_pub_datetime[1310]:
@ -396,7 +396,7 @@ def email(request, id):
type_id = 'msgout', type_id = 'msgout',
by = request.user.person, by = request.user.person,
disclosure = ipr, disclosure = ipr,
response_due = form.cleaned_data['response_due'], response_due = datetime_from_date(form.cleaned_data['response_due'], DEADLINE_TZINFO),
message = msg, message = msg,
) )
@ -590,7 +590,7 @@ def notify(request, id, type):
type_id = form.cleaned_data['type'], type_id = form.cleaned_data['type'],
by = request.user.person, by = request.user.person,
disclosure = ipr, disclosure = ipr,
response_due = datetime.datetime.now().date() + datetime.timedelta(days=30), response_due = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=30),
message = message, message = message,
) )
messages.success(request,'Notifications sent') messages.success(request,'Notifications sent')

View file

@ -3,7 +3,7 @@
import io import io
import datetime, os import os
import operator import operator
from typing import Union # pyflakes:ignore from typing import Union # pyflakes:ignore
@ -34,6 +34,7 @@ from ietf.person.models import Email
from ietf.person.fields import SearchableEmailField from ietf.person.fields import SearchableEmailField
from ietf.doc.models import Document, DocAlias from ietf.doc.models import Document, DocAlias
from ietf.utils.fields import DatepickerDateField from ietf.utils.fields import DatepickerDateField
from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO
from functools import reduce from functools import reduce
''' '''
@ -185,9 +186,12 @@ class SearchLiaisonForm(forms.Form):
end_date = self.cleaned_data.get('end_date') end_date = self.cleaned_data.get('end_date')
events = None events = None
if start_date: if start_date:
events = LiaisonStatementEvent.objects.filter(type='posted', time__gte=start_date) events = LiaisonStatementEvent.objects.filter(
type='posted',
time__gte=datetime_from_date(start_date, DEADLINE_TZINFO),
)
if end_date: if end_date:
events = events.filter(time__lte=end_date) events = events.filter(time__lte=datetime_from_date(end_date, DEADLINE_TZINFO))
elif end_date: elif end_date:
events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date) events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date)
if events: if events:
@ -222,7 +226,7 @@ class LiaisonModelForm(BetterModelForm):
to_groups.widget.attrs['data-minimum-input-length'] = 0 to_groups.widget.attrs['data-minimum-input-length'] = 0
deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True) deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True)
related_to = SearchableLiaisonStatementsField(label='Related Liaison Statement', required=False) related_to = SearchableLiaisonStatementsField(label='Related Liaison Statement', required=False)
submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today()) submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=date_today(DEADLINE_TZINFO))
attachments = CustomModelMultipleChoiceField(queryset=Document.objects,label='Attachments', widget=ShowAttachmentsWidget, required=False) attachments = CustomModelMultipleChoiceField(queryset=Document.objects,label='Attachments', widget=ShowAttachmentsWidget, required=False)
attach_title = forms.CharField(label='Title', required=False) attach_title = forms.CharField(label='Title', required=False)
attach_file = forms.FileField(label='File', required=False) attach_file = forms.FileField(label='File', required=False)
@ -538,7 +542,7 @@ class EditLiaisonForm(LiaisonModelForm):
super(EditLiaisonForm, self).save(*args,**kwargs) super(EditLiaisonForm, self).save(*args,**kwargs)
if self.has_changed() and 'submitted_date' in self.changed_data: if self.has_changed() and 'submitted_date' in self.changed_data:
event = self.instance.liaisonstatementevent_set.filter(type='submitted').first() event = self.instance.liaisonstatementevent_set.filter(type='submitted').first()
event.time = self.cleaned_data.get('submitted_date') event.time = datetime_from_date(self.cleaned_data.get('submitted_date'), DEADLINE_TZINFO)
event.save() event.save()
return self.instance return self.instance

View file

@ -14,6 +14,8 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.db.models import Q from django.db.models import Q
from django.utils import timezone
from io import StringIO from io import StringIO
from pyquery import PyQuery from pyquery import PyQuery
@ -50,7 +52,7 @@ def get_liaison_post_data(type='incoming'):
to_contacts='to_contacts@example.com', to_contacts='to_contacts@example.com',
purpose="info", purpose="info",
title="title", title="title",
submitted_date=datetime.datetime.today().strftime("%Y-%m-%d"), submitted_date=timezone.now().strftime("%Y-%m-%d"),
body="body", body="body",
send="1" ) send="1" )
@ -385,7 +387,7 @@ class LiaisonManagementTests(TestCase):
def test_edit_liaison(self): def test_edit_liaison(self):
liaison = LiaisonStatementFactory(deadline=datetime.date.today()+datetime.timedelta(days=1)) 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') LiaisonStatementEventFactory(statement=liaison,type_id='posted')
from_group = liaison.from_groups.first() from_group = liaison.from_groups.first()
to_group = liaison.to_groups.first() to_group = liaison.to_groups.first()
@ -1021,7 +1023,7 @@ class LiaisonManagementTests(TestCase):
LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),]) LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),])
# Statement 2 # Statement 2
s2 = LiaisonStatementEventFactory(type_id='posted', statement__body="That word does not occur here", statement__title="Nor does it occur here") s2 = LiaisonStatementEventFactory(type_id='posted', statement__body="That word does not occur here", statement__title="Nor does it occur here")
s2.time=datetime.datetime(2010,1,1) s2.time=datetime.datetime(2010, 1, 1, tzinfo=datetime.timezone.utc)
s2.save() s2.save()
# test list only, no search filters # test list only, no search filters

View file

@ -187,7 +187,9 @@ class TimeSlotFactory(factory.django.DjangoModelFactory):
@factory.lazy_attribute @factory.lazy_attribute
def time(self): def time(self):
return datetime.datetime.combine(self.meeting.date,datetime.time(11,0)) return self.meeting.tz().localize(
datetime.datetime.combine(self.meeting.date, datetime.time(11, 0))
)
@factory.lazy_attribute @factory.lazy_attribute
def duration(self): def duration(self):

View file

@ -32,7 +32,7 @@
"comments": "", "comments": "",
"list_subscribe": "", "list_subscribe": "",
"state": "active", "state": "active",
"time": "2012-02-26T00:21:36", "time": "2012-02-26T00:21:36Z",
"unused_tags": [], "unused_tags": [],
"list_archive": "", "list_archive": "",
"type": "ietf", "type": "ietf",

View file

@ -17,6 +17,7 @@ from django.contrib.auth.models import AnonymousUser
from django.urls import reverse from django.urls import reverse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -42,7 +43,7 @@ def get_meeting(num=None,type_in=['ietf',],days=28):
if type_in: if type_in:
meetings = meetings.filter(type__in=type_in) meetings = meetings.filter(type__in=type_in)
if num == None: 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: else:
meetings = meetings.filter(number=num) meetings = meetings.filter(number=num)
if meetings.exists(): 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) raise Http404("No such meeting found: %s" % num)
def get_current_ietf_meeting(): 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() return meetings.first()
def get_current_ietf_meeting_num(): def get_current_ietf_meeting_num():
@ -117,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 = list(assignments_queryset) # make sure we're set in stone
assignments = assignments_queryset 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 # replace groups with historic counterparts
groups = [ ] groups = [ ]
@ -1148,11 +1152,15 @@ def sessions_post_cancel(request, sessions):
def update_interim_session_assignment(form): def update_interim_session_assignment(form):
"""Helper function to create / update timeslot assigned to interim session""" """Helper function to create / update timeslot assigned to interim session
time = datetime.datetime.combine(
form.cleaned_data['date'], form is an InterimSessionModelForm
form.cleaned_data['time']) """
session = form.instance 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(): if session.official_timeslotassignment():
slot = session.official_timeslotassignment().timeslot slot = session.official_timeslotassignment().timeslot
slot.time = time slot.time = time
@ -1160,14 +1168,14 @@ def update_interim_session_assignment(form):
slot.save() slot.save()
else: else:
slot = TimeSlot.objects.create( slot = TimeSlot.objects.create(
meeting=session.meeting, meeting=meeting,
type_id='regular', type_id='regular',
duration=session.requested_duration, duration=session.requested_duration,
time=time) time=time)
SchedTimeSessAssignment.objects.create( SchedTimeSessAssignment.objects.create(
timeslot=slot, timeslot=slot,
session=session, session=session,
schedule=session.meeting.schedule) schedule=meeting.schedule)
def populate_important_dates(meeting): def populate_important_dates(meeting):
assert ImportantDate.objects.filter(meeting=meeting).exists() is False assert ImportantDate.objects.filter(meeting=meeting).exists() is False

View file

@ -48,7 +48,7 @@ import socket
import datetime import datetime
import pytz 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 import transaction
from django.db.models import Q from django.db.models import Q
@ -75,10 +75,12 @@ class Command(BaseCommand):
def _meeting_datetime(self, day, *time_args): def _meeting_datetime(self, day, *time_args):
"""Generate a datetime on a meeting day""" """Generate a datetime on a meeting day"""
return datetime.datetime.combine( return self.meeting_tz.localize(
self.start_date, datetime.datetime.combine(
datetime.time(*time_args) self.start_date,
) + datetime.timedelta(days=day) datetime.time(*time_args)
) + datetime.timedelta(days=day)
)
def handle(self, *args, **options): def handle(self, *args, **options):
if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]: if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]:
@ -87,10 +89,7 @@ class Command(BaseCommand):
opt_delete = options.get('delete', False) opt_delete = options.get('delete', False)
opt_use_old_conflicts = options.get('old_conflicts', False) opt_use_old_conflicts = options.get('old_conflicts', False)
self.start_date = options['start_date'] self.start_date = options['start_date']
meeting_tz = options['tz'] meeting_tzname = 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))
if opt_delete: if opt_delete:
if Meeting.objects.filter(number='999').exists(): if Meeting.objects.filter(number='999').exists():
Meeting.objects.filter(number='999').delete() Meeting.objects.filter(number='999').delete()
@ -98,6 +97,11 @@ class Command(BaseCommand):
else: else:
self.stderr.write("Dummy meeting IETF 999 does not exist; nothing to do.\n") self.stderr.write("Dummy meeting IETF 999 does not exist; nothing to do.\n")
else: 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(): if Meeting.objects.filter(number='999').exists():
self.stderr.write("Dummy meeting IETF 999 already exists; nothing to do.\n") self.stderr.write("Dummy meeting IETF 999 already exists; nothing to do.\n")
else: else:
@ -111,7 +115,7 @@ class Command(BaseCommand):
type_id='IETF', type_id='IETF',
date=self._meeting_datetime(0).date(), date=self._meeting_datetime(0).date(),
days=7, days=7,
time_zone=meeting_tz, time_zone=meeting_tzname,
) )
# Set enabled constraints # Set enabled constraints

View file

@ -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', '0055_pytz_2022_2_1'),
]
operations = [
migrations.AlterField(
model_name='schedulingevent',
name='time',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'),
),
]

View file

@ -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', '0056_use_timezone_now_for_meeting_models'),
]
operations = [
migrations.RunPython(forward, reverse),
]

File diff suppressed because one or more lines are too long

View file

@ -24,6 +24,7 @@ from django.db.models import Max, Subquery, OuterRef, TextField, Value, Q
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.conf import settings from django.conf import settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -40,7 +41,7 @@ from ietf.person.models import Person
from ietf.utils.decorators import memoize from ietf.utils.decorators import memoize
from ietf.utils.storage import NoLocationMigrationFileSystemStorage from ietf.utils.storage import NoLocationMigrationFileSystemStorage
from ietf.utils.text import xslugify from ietf.utils.text import xslugify
from ietf.utils.timezone import date2datetime from ietf.utils.timezone import datetime_from_date
from ietf.utils.models import ForeignKey from ietf.utils.models import ForeignKey
from ietf.utils.validators import ( from ietf.utils.validators import (
MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type, MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type,
@ -85,7 +86,7 @@ class Meeting(models.Model):
# We can't derive time-zone from country, as there are some that have # 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 # more than one timezone, and the pytz module doesn't provide timezone
# lookup information for all relevant city/country combinations. # 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, idsubmit_cutoff_day_offset_00 = models.IntegerField(blank=True,
default=settings.IDSUBMIT_DEFAULT_CUTOFF_DAY_OFFSET_00, 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.") help_text = "The number of days before the meeting start date when the submission of -00 drafts will be closed.")
@ -137,6 +138,14 @@ class Meeting(models.Model):
def end_date(self): def end_date(self):
return self.get_meeting_date(self.days-1) return self.get_meeting_date(self.days-1)
def start_datetime(self):
"""Start-of-day on meeting.date in meeting time zone"""
return datetime_from_date(self.date, self.tz())
def end_datetime(self):
"""Datetime of the first instant _after_ the meeting's last day in meeting time zone"""
return datetime_from_date(self.get_meeting_date(self.days), self.tz())
def get_00_cutoff(self): def get_00_cutoff(self):
start_date = datetime.datetime(year=self.date.year, month=self.date.month, day=self.date.day, tzinfo=pytz.utc) 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() importantdate = self.importantdate_set.filter(name_id='idcutoff').first()
@ -146,7 +155,7 @@ class Meeting(models.Model):
cutoff_date = importantdate.date cutoff_date = importantdate.date
else: else:
cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days)
cutoff_time = date2datetime(cutoff_date) + self.idsubmit_cutoff_time_utc cutoff_time = datetime_from_date(cutoff_date) + self.idsubmit_cutoff_time_utc
return cutoff_time return cutoff_time
def get_01_cutoff(self): def get_01_cutoff(self):
@ -158,7 +167,7 @@ class Meeting(models.Model):
cutoff_date = importantdate.date cutoff_date = importantdate.date
else: else:
cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days)
cutoff_time = date2datetime(cutoff_date) + self.idsubmit_cutoff_time_utc cutoff_time = datetime_from_date(cutoff_date) + self.idsubmit_cutoff_time_utc
return cutoff_time return cutoff_time
def get_reopen_time(self): def get_reopen_time(self):
@ -176,7 +185,7 @@ class Meeting(models.Model):
@classmethod @classmethod
def get_current_meeting(cls, type="ietf"): 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): def get_first_cut_off(self):
return self.get_00_cutoff() return self.get_00_cutoff()
@ -321,7 +330,7 @@ class Meeting(models.Model):
for ts in self.timeslot_set.all(): for ts in self.timeslot_set.all():
if ts.location_id is None: if ts.location_id is None:
continue continue
ymd = ts.time.date() ymd = ts.local_start_time().date()
if ymd not in time_slices: if ymd not in time_slices:
time_slices[ymd] = [] time_slices[ymd] = []
slots[ymd] = [] slots[ymd] = []
@ -329,15 +338,15 @@ class Meeting(models.Model):
if ymd in time_slices: if ymd in time_slices:
# only keep unique entries # only keep unique entries
if [ts.time, ts.time + ts.duration, ts.duration.seconds] not in time_slices[ymd]: if [ts.local_start_time(), ts.local_end_time(), ts.duration.seconds] not in time_slices[ymd]:
time_slices[ymd].append([ts.time, ts.time + ts.duration, ts.duration.seconds]) time_slices[ymd].append([ts.local_start_time(), ts.local_end_time(), ts.duration.seconds])
slots[ymd].append(ts) slots[ymd].append(ts)
days.sort() days.sort()
for ymd in time_slices: for ymd in time_slices:
# Make sure these sort the same way # Make sure these sort the same way
time_slices[ymd].sort() time_slices[ymd].sort()
slots[ymd].sort(key=lambda x: (x.time, x.duration)) slots[ymd].sort(key=lambda x: (x.local_start_time(), x.duration))
return days,time_slices,slots return days,time_slices,slots
# this functions makes a list of timeslices and rooms, and # this functions makes a list of timeslices and rooms, and
@ -353,20 +362,24 @@ class Meeting(models.Model):
# SchedTimeSessAssignment.objects.create(schedule = sched, # SchedTimeSessAssignment.objects.create(schedule = sched,
# timeslot = ts) # 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): def vtimezone(self):
if self.time_zone: try:
try: tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics") if os.path.exists(tzfn):
if os.path.exists(tzfn): with io.open(tzfn) as tzf:
with io.open(tzfn) as tzf: icstext = tzf.read()
icstext = tzf.read() vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip()
vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip() if vtimezone:
if vtimezone: vtimezone += "\n"
vtimezone += "\n" return vtimezone
return vtimezone except IOError:
except IOError: pass
pass return None
return ''
def set_official_schedule(self, schedule): def set_official_schedule(self, schedule):
if self.schedule != schedule: if self.schedule != schedule:
@ -374,16 +387,14 @@ class Meeting(models.Model):
self.save() self.save()
def updated(self): 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 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 sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
assignments_updated = min_time assignments_updated = min_time
if self.schedule: 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 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) return max(timeslots_updated, sessions_updated, assignments_updated)
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
ts = tz.localize(ts)
return ts
@memoize @memoize
def previous_meeting(self): def previous_meeting(self):
@ -604,41 +615,22 @@ class TimeSlot(models.Model):
return self._cached_html_location return self._cached_html_location
def tz(self): def tz(self):
if not hasattr(self, '_cached_tz'): return self.meeting.tz()
if self.meeting.time_zone:
self._cached_tz = pytz.timezone(self.meeting.time_zone)
else:
self._cached_tz = None
return self._cached_tz
def tzname(self): def tzname(self):
if self.tz(): return self.tz().tzname(self.time)
return self.tz().tzname(self.time)
else:
return ""
def utc_start_time(self): def utc_start_time(self):
if self.tz(): return self.time.astimezone(pytz.utc) # USE_TZ is True, so time is aware
local_start_time = self.tz().localize(self.time)
return local_start_time.astimezone(pytz.utc)
else:
return None
def utc_end_time(self): def utc_end_time(self):
utc_start = self.utc_start_time() return self.time.astimezone(pytz.utc) + self.duration # USE_TZ is True, so time is aware
# 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
def local_start_time(self): def local_start_time(self):
if self.tz(): return self.time.astimezone(self.tz())
return self.tz().localize(self.time)
else:
return None
def local_end_time(self): def local_end_time(self):
local_start = self.local_start_time() return (self.time.astimezone(pytz.utc) + self.duration).astimezone(self.tz())
# 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
@property @property
def js_identifier(self): def js_identifier(self):
@ -1280,7 +1272,7 @@ class Session(models.Model):
class SchedulingEvent(models.Model): class SchedulingEvent(models.Model):
session = ForeignKey(Session) 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) status = ForeignKey(SessionStatusName)
by = ForeignKey(Person) by = ForeignKey(Person)

View file

@ -21,10 +21,12 @@ from ietf.person.factories import PersonFactory
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.test_data import make_test_data 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)") system_person = Person.objects.get(name="(System)")
time = datetime.datetime.combine(date, datetime.time(9)) meeting = create_interim_meeting(group=group,date=date,timezone=tz)
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, session = SessionFactory(meeting=meeting, group=group,
attendees=10, attendees=10,
requested_duration=datetime.timedelta(minutes=20), requested_duration=datetime.timedelta(minutes=20),
@ -102,24 +104,37 @@ def make_meeting_test_data(meeting=None, create_interims=False):
# slots # slots
session_date = meeting.date + datetime.timedelta(days=1) session_date = meeting.date + datetime.timedelta(days=1)
tz = meeting.tz()
slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room, slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60), 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, slot2 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60), 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, breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", location=breakfast_room,
duration=datetime.timedelta(minutes=90), 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, reg_slot = TimeSlot.objects.create(meeting=meeting, type_id="reg", location=reg_room,
duration=datetime.timedelta(minutes=480), 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, break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
duration=datetime.timedelta(minutes=90), 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, plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
duration=datetime.timedelta(minutes=60), 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 WG
mars = Group.objects.get(acronym='mars') mars = Group.objects.get(acronym='mars')
mars_session = SessionFactory(meeting=meeting, group=mars, mars_session = SessionFactory(meeting=meeting, group=mars,
@ -213,7 +228,7 @@ def make_meeting_test_data(meeting=None, create_interims=False):
return meeting return meeting
def make_interim_test_data(): def make_interim_test_data(meeting_tz='UTC'):
date = datetime.date.today() + datetime.timedelta(days=365) date = datetime.date.today() + datetime.timedelta(days=365)
date2 = datetime.date.today() + datetime.timedelta(days=1000) date2 = datetime.date.today() + datetime.timedelta(days=1000)
PersonFactory(user__username='plain') PersonFactory(user__username='plain')
@ -225,10 +240,10 @@ def make_interim_test_data():
RoleFactory(group=mars,person__user__username='marschairman',name_id='chair') RoleFactory(group=mars,person__user__username='marschairman',name_id='chair')
RoleFactory(group=ames,person__user__username='ameschairman',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=date,status='sched',tz=meeting_tz)
make_interim_meeting(group=mars,date=date2,status='apprw') make_interim_meeting(group=mars,date=date2,status='apprw',tz=meeting_tz)
make_interim_meeting(group=ames,date=date,status='canceled') make_interim_meeting(group=ames,date=date,status='canceled',tz=meeting_tz)
make_interim_meeting(group=ames,date=date2,status='apprw') make_interim_meeting(group=ames,date=date2,status='apprw',tz=meeting_tz)
return return

View file

@ -11,8 +11,8 @@ from unittest import skipIf
import urllib.parse import urllib.parse
import django import django
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import now
from django.db.models import F from django.db.models import F
import pytz import pytz
@ -36,6 +36,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.utils.test_utils import assert_ical_response_is_valid from ietf.utils.test_utils import assert_ical_response_is_valid
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled, from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
presence_of_element_child_by_css_selector ) presence_of_element_child_by_css_selector )
from ietf.utils.timezone import datetime_today, datetime_from_date, date_today
if selenium_enabled(): if selenium_enabled():
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
@ -307,7 +308,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
room = RoomFactory(meeting=meeting) room = RoomFactory(meeting=meeting)
# get current time in meeting time zone # get current time in meeting time zone
right_now = now().astimezone( right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone) pytz.timezone(meeting.time_zone)
) )
if not settings.USE_TZ: if not settings.USE_TZ:
@ -394,11 +395,11 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
def test_past_swap_days_buttons(self): def test_past_swap_days_buttons(self):
"""Swap days buttons should be hidden for past items""" """Swap days buttons should be hidden for past items"""
wait = WebDriverWait(self.driver, 2) 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) room = RoomFactory(meeting=meeting)
# get current time in meeting time zone # get current time in meeting time zone
right_now = now().astimezone( right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone) pytz.timezone(meeting.time_zone)
) )
if not settings.USE_TZ: if not settings.USE_TZ:
@ -518,11 +519,11 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
def test_past_swap_timeslot_col_buttons(self): def test_past_swap_timeslot_col_buttons(self):
"""Swap timeslot column buttons should be hidden for past items""" """Swap timeslot column buttons should be hidden for past items"""
wait = WebDriverWait(self.driver, 2) 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) room = RoomFactory(meeting=meeting)
# get current time in meeting time zone # get current time in meeting time zone
right_now = now().astimezone( right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone) pytz.timezone(meeting.time_zone)
) )
if not settings.USE_TZ: if not settings.USE_TZ:
@ -1434,13 +1435,16 @@ class AgendaTests(IetfSeleniumTestCase):
# for others (break, reg, other): # for others (break, reg, other):
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug> # row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug>
meeting_number = components[1] meeting_number = components[1]
start_time = datetime.datetime( start_time = pytz.utc.localize(
year=int(components[2]), datetime.datetime(
month=int(components[3]), year=int(components[2]),
day=int(components[4]), month=int(components[3]),
hour=int(components[6][0:2]), day=int(components[4]),
minute=int(components[6][2:4]), hour=int(components[6][0:2]),
minute=int(components[6][2:4]),
)
) )
# If labeled as plenary, it's plenary... # If labeled as plenary, it's plenary...
if components[7] == '1plenary': if components[7] == '1plenary':
session_type = 'plenary' session_type = 'plenary'
@ -1904,10 +1908,9 @@ class WeekviewTests(IetfSeleniumTestCase):
# Session during a single day in meeting local time but multi-day UTC # 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 # 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)) 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( daytime_session = SessionFactory(
meeting=self.meeting, meeting=self.meeting,
@ -1916,7 +1919,7 @@ class WeekviewTests(IetfSeleniumTestCase):
) )
daytime_timeslot = TimeSlotFactory( daytime_timeslot = TimeSlotFactory(
meeting=self.meeting, meeting=self.meeting,
time=start_time_local.replace(tzinfo=None), # drop timezone for Django time=start_time_utc,
duration=duration, duration=duration,
) )
daytime_session.timeslotassignments.create(timeslot=daytime_timeslot, schedule=self.meeting.schedule) daytime_session.timeslotassignments.create(timeslot=daytime_timeslot, schedule=self.meeting.schedule)
@ -1929,11 +1932,12 @@ class WeekviewTests(IetfSeleniumTestCase):
) )
overnight_timeslot = TimeSlotFactory( overnight_timeslot = TimeSlotFactory(
meeting=self.meeting, 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, duration=duration,
) )
overnight_session.timeslotassignments.create(timeslot=overnight_timeslot, schedule=self.meeting.schedule) overnight_session.timeslotassignments.create(timeslot=overnight_timeslot, schedule=self.meeting.schedule)
# Check assumptions about events overlapping midnight # Check assumptions about events overlapping midnight
self.assertEqual(daytime_timeslot.local_start_time().day, self.assertEqual(daytime_timeslot.local_start_time().day,
daytime_timeslot.local_end_time().day, daytime_timeslot.local_end_time().day,
@ -2048,7 +2052,7 @@ class InterimTests(IetfSeleniumTestCase):
Session.objects.filter( Session.objects.filter(
meeting__type_id='interim', meeting__type_id='interim',
timeslotassignments__schedule=F('meeting__schedule'), timeslotassignments__schedule=F('meeting__schedule'),
timeslotassignments__timeslot__time__gte=datetime.datetime.today() timeslotassignments__timeslot__time__gte=timezone.now()
) )
).filter(current_status__in=('sched','canceled')) ).filter(current_status__in=('sched','canceled'))
meetings = [] meetings = []
@ -2061,7 +2065,7 @@ class InterimTests(IetfSeleniumTestCase):
def all_ietf_meetings(self): def all_ietf_meetings(self):
meetings = Meeting.objects.filter( meetings = Meeting.objects.filter(
type_id='ietf', type_id='ietf',
date__gte=datetime.datetime.today()-datetime.timedelta(days=7) date__gte=timezone.now()-datetime.timedelta(days=7)
) )
for m in meetings: for m in meetings:
m.calendar_label = 'IETF %s' % m.number m.calendar_label = 'IETF %s' % m.number
@ -2191,7 +2195,7 @@ class InterimTests(IetfSeleniumTestCase):
expected_assignments = list(SchedTimeSessAssignment.objects.filter( expected_assignments = list(SchedTimeSessAssignment.objects.filter(
schedule__in=expected_schedules, schedule__in=expected_schedules,
session__in=expected_interim_sessions, 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 # The UID formats should match those in the upcoming.ics template
expected_uids = [ expected_uids = [
@ -2609,7 +2613,7 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
self.meeting: Meeting = MeetingFactory( self.meeting: Meeting = MeetingFactory(
type_id='ietf', type_id='ietf',
number=120, number=120,
date=datetime.datetime.today() + datetime.timedelta(days=10), date=date_today() + datetime.timedelta(days=10),
populate_schedule=False, populate_schedule=False,
) )
self.edit_timeslot_url = self.absreverse( self.edit_timeslot_url = self.absreverse(
@ -2679,22 +2683,23 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
self.do_delete_timeslot_test(cancel=True) self.do_delete_timeslot_test(cancel=True)
def do_delete_time_interval_test(self, cancel=False): def do_delete_time_interval_test(self, cancel=False):
delete_day = self.meeting.date.date() delete_day = self.meeting.date
delete_time = datetime.time(hour=10) delete_time = datetime.time(hour=10)
other_day = self.meeting.get_meeting_date(1).date() other_day = self.meeting.get_meeting_date(1)
other_time = datetime.time(hour=12) other_time = datetime.time(hour=12)
duration = datetime.timedelta(minutes=60) duration = datetime.timedelta(minutes=60)
delete: [TimeSlot] = TimeSlotFactory.create_batch( delete: [TimeSlot] = TimeSlotFactory.create_batch(
2, 2,
meeting=self.meeting, meeting=self.meeting,
time=datetime.datetime.combine(delete_day, delete_time), time=datetime_from_date(delete_day, self.meeting.tz()).replace(hour=delete_time.hour),
duration=duration) duration=duration,
)
keep: [TimeSlot] = [ keep: [TimeSlot] = [
TimeSlotFactory( TimeSlotFactory(
meeting=self.meeting, meeting=self.meeting,
time=datetime.datetime.combine(day, time), time=datetime_from_date(day, self.meeting.tz()).replace(hour=time.hour),
duration=duration duration=duration
) )
for (day, time) in ( for (day, time) in (
@ -2711,7 +2716,9 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
'[data-col-id="{}T{}-{}"]'.format( '[data-col-id="{}T{}-{}"]'.format(
delete_day.isoformat(), delete_day.isoformat(),
delete_time.strftime('%H:%M'), delete_time.strftime('%H:%M'),
(datetime.datetime.combine(delete_day, delete_time) + duration).strftime( self.meeting.tz().localize(
datetime.datetime.combine(delete_day, delete_time) + duration
).strftime(
'%H:%M' '%H:%M'
)) ))
) )
@ -2726,22 +2733,22 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
self.do_delete_time_interval_test(cancel=True) self.do_delete_time_interval_test(cancel=True)
def do_delete_day_test(self, cancel=False): def do_delete_day_test(self, cancel=False):
delete_day = self.meeting.date.date() delete_day = self.meeting.date
times = [datetime.time(hour=10), datetime.time(hour=12)] hours = [10, 12]
other_days = [self.meeting.get_meeting_date(d).date() for d in range(1, 3)] other_days = [self.meeting.get_meeting_date(d) for d in range(1, 3)]
delete: [TimeSlot] = [ delete: [TimeSlot] = [
TimeSlotFactory( TimeSlotFactory(
meeting=self.meeting, meeting=self.meeting,
time=datetime.datetime.combine(delete_day, time), time=datetime_from_date(delete_day, self.meeting.tz()).replace(hour=hour),
) for time in times ) for hour in hours
] ]
keep: [TimeSlot] = [ keep: [TimeSlot] = [
TimeSlotFactory( TimeSlotFactory(
meeting=self.meeting, meeting=self.meeting,
time=datetime.datetime.combine(day, time), time=datetime_from_date(day, self.meeting.tz()).replace(hour=hour),
) for day in other_days for time in times ) for day in other_days for hour in hours
] ]
selector = ( selector = (

View file

@ -3,6 +3,8 @@
"""Tests of models in the Meeting application""" """Tests of models in the Meeting application"""
import datetime import datetime
from mock import patch
from ietf.meeting.factories import MeetingFactory, SessionFactory from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.stats.factories import MeetingRegistrationFactory from ietf.stats.factories import MeetingRegistrationFactory
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
@ -52,6 +54,22 @@ class MeetingTests(TestCase):
self.assertEqual(attendance.online, 0) self.assertEqual(attendance.online, 0)
self.assertEqual(attendance.onsite, 5) self.assertEqual(attendance.onsite, 5)
def test_vtimezone(self):
# normal time zone that should have a zoneinfo file
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', 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', populate_schedule=False)
with patch('ietf.meeting.models.io.open', side_effect=IOError):
vtz = meeting.vtimezone()
self.assertIsNone(vtz)
class SessionTests(TestCase): class SessionTests(TestCase):
def test_chat_archive_url_with_jabber(self): def test_chat_archive_url_with_jabber(self):

View file

@ -1,6 +1,7 @@
# Copyright The IETF Trust 2020, All Rights Reserved # Copyright The IETF Trust 2020, All Rights Reserved
import calendar import calendar
import datetime import datetime
import pytz
from io import StringIO from io import StringIO
from django.core.management.base import CommandError from django.core.management.base import CommandError
@ -36,9 +37,11 @@ class ScheduleGeneratorTest(TestCase):
t = TimeSlotFactory( t = TimeSlotFactory(
meeting=self.meeting, meeting=self.meeting,
location=room, location=room,
time=datetime.datetime.combine( time=self.meeting.tz().localize(
self.meeting.date + datetime.timedelta(days=day), datetime.datetime.combine(
datetime.time(hour, 0), self.meeting.date + datetime.timedelta(days=day),
datetime.time(hour, 0),
)
), ),
duration=datetime.timedelta(minutes=60), duration=datetime.timedelta(minutes=60),
) )
@ -306,8 +309,11 @@ class ScheduleGeneratorTest(TestCase):
add_to_schedule=False add_to_schedule=False
) )
# use a timeslot not on Sunday # 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( 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, location__capacity__lt=base_reg_session.attendees,
).order_by( ).order_by(
'time' 'time'

View file

@ -29,8 +29,8 @@ from django.test import Client, override_settings
from django.db.models import F, Max from django.db.models import F, Max
from django.http import QueryDict, FileResponse from django.http import QueryDict, FileResponse
from django.template import Context, Template from django.template import Context, Template
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import now
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -52,6 +52,7 @@ from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
from ietf.utils.text import xslugify from ietf.utils.text import xslugify
from ietf.utils.timezone import date_today, time_now
from ietf.person.factories import PersonFactory from ietf.person.factories import PersonFactory
from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory
@ -155,7 +156,7 @@ class MeetingTests(BaseMeetingTestCase):
# #
self.write_materials_files(meeting, session) 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_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', 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') city="Panama City", country="PA", time_zone='America/Panama')
@ -228,7 +229,10 @@ class MeetingTests(BaseMeetingTestCase):
'Time zone indicator should be in nav sidebar') 'Time zone indicator should be in nav sidebar')
# plain # plain
time_interval = r"%s<span.*/span>-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0")) time_interval = r"{}<span.*/span>-{}".format(
slot.time.astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
slot.end_time().astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
)
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number))) r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -430,7 +434,9 @@ class MeetingTests(BaseMeetingTestCase):
session_date = meeting.date + datetime.timedelta(days=1) session_date = meeting.date + datetime.timedelta(days=1)
slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room, slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60), 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) SchedTimeSessAssignment.objects.create(timeslot=slot3, session=venus_session, schedule=meeting.schedule)
url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number)) url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number))
r = self.client.get(url) r = self.client.get(url)
@ -604,7 +610,7 @@ class MeetingTests(BaseMeetingTestCase):
def test_interim_materials(self): def test_interim_materials(self):
make_meeting_test_data() make_meeting_test_data()
group = Group.objects.get(acronym='mars') 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') meeting = make_interim_meeting(group=group, date=date, status='sched')
session = meeting.session_set.first() session = meeting.session_set.first()
@ -801,7 +807,12 @@ class MeetingTests(BaseMeetingTestCase):
a1 = s1.official_timeslotassignment() a1 = s1.official_timeslotassignment()
t1 = a1.timeslot t1 = a1.timeslot
# Create an extra session # 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) s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule) SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
# #
@ -811,16 +822,16 @@ class MeetingTests(BaseMeetingTestCase):
r, r,
expected_event_summaries=['mars - Martian Special Interest Group'], expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=2) expected_event_count=2)
self.assertContains(r, t1.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.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, }) url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
r = self.client.get(url) r = self.client.get(url)
assert_ical_response_is_valid(self, r, assert_ical_response_is_valid(self, r,
expected_event_summaries=['mars - Martian Special Interest Group'], expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=1) expected_event_count=1)
self.assertContains(r, t1.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.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): def test_meeting_agenda_has_static_ical_links(self):
"""Links to the agenda_ical view must appear on the agenda page """Links to the agenda_ical view must appear on the agenda page
@ -960,7 +971,7 @@ class MeetingTests(BaseMeetingTestCase):
url = urlreverse('ietf.meeting.views.current_materials') url = urlreverse('ietf.meeting.views.current_materials')
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 404) 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) response = self.client.get(url)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -1105,7 +1116,9 @@ class EditMeetingScheduleTests(TestCase):
TimeSlotFactory( TimeSlotFactory(
meeting=meeting, meeting=meeting,
location=room, location=room,
time=datetime.datetime.combine(meeting.date, time), time=meeting.tz().localize(
datetime.datetime.combine(meeting.date, time)
),
duration=datetime.timedelta(minutes=duration), duration=datetime.timedelta(minutes=duration),
) )
@ -1189,7 +1202,11 @@ class EditMeetingScheduleTests(TestCase):
] ]
# Set up different sets of timeslots # 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) dur = datetime.timedelta(hours=2)
for room in room_groups[0]: for room in room_groups[0]:
TimeSlotFactory(meeting=meeting, location=room, duration=dur, time=t0) TimeSlotFactory(meeting=meeting, location=room, duration=dur, time=t0)
@ -1284,7 +1301,7 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password') self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past # 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]: for room in room_groups[0]:
ts = room.timeslot_set.last() ts = room.timeslot_set.last()
ts.time = right_now - datetime.timedelta(minutes=5) ts.time = right_now - datetime.timedelta(minutes=5)
@ -1466,12 +1483,18 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password') self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past # 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())
yesterday = (right_now - datetime.timedelta(days=1)).date() yesterday = right_now.date() - datetime.timedelta(days=1)
day_before = (right_now - datetime.timedelta(days=2)).date() day_before = right_now.date() - datetime.timedelta(days=2)
for room in room_groups[0]: for room in room_groups[0]:
ts = room.timeslot_set.last() 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() ts.save()
# timeslot_set is ordered by -time, so check that we know which is past/future # 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) self.assertTrue(room_groups[0][0].timeslot_set.last().time < right_now)
@ -1505,7 +1528,12 @@ class EditMeetingScheduleTests(TestCase):
# now with both in the past # now with both in the past
for room in room_groups[0]: for room in room_groups[0]:
ts = room.timeslot_set.first() 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() ts.save()
past_slots = room_groups[0][0].timeslot_set.filter(time__lt=right_now) 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!') self.assertEqual(len(past_slots), 2, 'Need two timeslots in the past!')
@ -1528,8 +1556,8 @@ class EditMeetingScheduleTests(TestCase):
self.fail('Response was not valid JSON: {}'.format(err)) self.fail('Response was not valid JSON: {}'.format(err))
@staticmethod @staticmethod
def _right_now_in(tzname): def _right_now_in(tzinfo):
right_now = now().astimezone(pytz.timezone(tzname)) right_now = timezone.now().astimezone(tzinfo)
if not settings.USE_TZ: if not settings.USE_TZ:
right_now = right_now.replace(tzinfo=None) right_now = right_now.replace(tzinfo=None)
return right_now return right_now
@ -1538,10 +1566,10 @@ class EditMeetingScheduleTests(TestCase):
"""Allow assignment to future timeslots only for official schedule""" """Allow assignment to future timeslots only for official schedule"""
meeting = MeetingFactory( meeting = MeetingFactory(
type_id='ietf', type_id='ietf',
date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3, days=3,
) )
right_now = self._right_now_in(meeting.time_zone) right_now = self._right_now_in(meeting.tz())
schedules = dict( schedules = dict(
official=meeting.schedule, official=meeting.schedule,
@ -1598,10 +1626,10 @@ class EditMeetingScheduleTests(TestCase):
"""Do not allow assignment of past sessions for official schedule""" """Do not allow assignment of past sessions for official schedule"""
meeting = MeetingFactory( meeting = MeetingFactory(
type_id='ietf', type_id='ietf',
date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3, days=3,
) )
right_now = self._right_now_in(meeting.time_zone) right_now = self._right_now_in(meeting.tz())
schedules = dict( schedules = dict(
official=meeting.schedule, official=meeting.schedule,
@ -1733,10 +1761,10 @@ class EditMeetingScheduleTests(TestCase):
"""Allow unassignment only of future timeslots for official schedule""" """Allow unassignment only of future timeslots for official schedule"""
meeting = MeetingFactory( meeting = MeetingFactory(
type_id='ietf', type_id='ietf',
date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(), date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3, days=3,
) )
right_now = self._right_now_in(meeting.time_zone) right_now = self._right_now_in(meeting.tz())
schedules = dict( schedules = dict(
official=meeting.schedule, official=meeting.schedule,
@ -1857,7 +1885,7 @@ class EditTimeslotsTests(TestCase):
return MeetingFactory( return MeetingFactory(
type_id='ietf', type_id='ietf',
number=number, number=number,
date=datetime.datetime.today() + datetime.timedelta(days=10), date=date_today() + datetime.timedelta(days=10),
populate_schedule=False, populate_schedule=False,
) )
@ -1889,7 +1917,8 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_bare_meeting(number=number) meeting = self.create_bare_meeting(number=number)
RoomFactory.create_batch(8, meeting=meeting) RoomFactory.create_batch(8, meeting=meeting)
self.create_initial_schedule(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): def test_view_permissions(self):
"""Only the secretary should be able to edit timeslots""" """Only the secretary should be able to edit timeslots"""
@ -2058,7 +2087,7 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_meeting() meeting = self.create_meeting()
# add some timeslots # add some timeslots
times = [datetime.time(hour=h) for h in (11, 14)] 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 = [] timeslots = []
duration = datetime.timedelta(minutes=90) duration = datetime.timedelta(minutes=90)
@ -2068,7 +2097,7 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory( TimeSlotFactory(
meeting=meeting, meeting=meeting,
location=room, location=room,
time=datetime.datetime.combine(day, t), time=meeting.tz().localize(datetime.datetime.combine(day, t)),
duration=duration, duration=duration,
) )
for t in times for t in times
@ -2149,17 +2178,21 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory( TimeSlotFactory(
meeting=meeting, meeting=meeting,
location=meeting.room_set.first(), location=meeting.room_set.first(),
time=datetime.datetime.combine( time=meeting.tz().localize(
meeting.get_meeting_date(day).date(), datetime.datetime.combine(
datetime.time(hour=11) meeting.get_meeting_date(day),
datetime.time(hour=11),
)
), ),
) )
TimeSlotFactory( TimeSlotFactory(
meeting=meeting, meeting=meeting,
location=meeting.room_set.first(), location=meeting.room_set.first(),
time=datetime.datetime.combine( time=meeting.tz().localize(
meeting.get_meeting_date(day).date(), datetime.datetime.combine(
datetime.time(hour=14) meeting.get_meeting_date(day),
datetime.time(hour=14),
)
), ),
) )
@ -2258,10 +2291,8 @@ class EditTimeslotsTests(TestCase):
name_before = 'Name Classic (tm)' name_before = 'Name Classic (tm)'
type_before = 'regular' type_before = 'regular'
time_before = datetime.datetime.combine( time_utc = pytz.utc.localize(datetime.datetime.combine(meeting.date, datetime.time(hour=10)))
meeting.date, time_before = time_utc.astimezone(meeting.tz())
datetime.time(hour=10),
)
duration_before = datetime.timedelta(minutes=60) duration_before = datetime.timedelta(minutes=60)
show_location_before = True show_location_before = True
location_before = meeting.room_set.first() location_before = meeting.room_set.first()
@ -2278,7 +2309,7 @@ class EditTimeslotsTests(TestCase):
self.login() self.login()
name_after = 'New Name (tm)' name_after = 'New Name (tm)'
type_after = 'plenary' type_after = 'plenary'
time_after = time_before + datetime.timedelta(days=1, hours=2) time_after = (time_utc + datetime.timedelta(days=1, hours=2)).astimezone(meeting.tz())
duration_after = duration_before * 2 duration_after = duration_before * 2
show_location_after = False show_location_after = False
location_after = meeting.room_set.last() location_after = meeting.room_set.last()
@ -2458,8 +2489,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1 ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name']) self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type']) self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(str(ts.time.date().toordinal()), post_data['days']) self.assertEqual(str(ts.local_start_time().date().toordinal()), post_data['days'])
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time']) self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location']) self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations']) self.assertEqual(str(ts.location.pk), post_data['locations'])
@ -2468,7 +2499,7 @@ class EditTimeslotsTests(TestCase):
"""Creating a single timeslot outside the official meeting days should work""" """Creating a single timeslot outside the official meeting days should work"""
meeting = self.create_meeting() meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all()) 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( post_data = dict(
name='some name', name='some name',
type='regular', type='regular',
@ -2491,8 +2522,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1 ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name']) self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type']) self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(ts.time.date(), other_date) self.assertEqual(ts.local_start_time().date(), other_date)
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time']) self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location']) self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations']) self.assertEqual(str(ts.location.pk), post_data['locations'])
@ -2706,8 +2737,8 @@ class EditTimeslotsTests(TestCase):
"""Creating multiple timeslots should work""" """Creating multiple timeslots should work"""
meeting = self.create_meeting() meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all()) 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)] days = [meeting.get_meeting_date(n) for n in range(meeting.days)]
other_date = meeting.get_meeting_date(-1).date() # date before start of meeting other_date = meeting.get_meeting_date(-1) # date before start of meeting
self.assertNotIn(other_date, days) self.assertNotIn(other_date, days)
locations = meeting.room_set.all() locations = meeting.room_set.all()
post_data = dict( post_data = dict(
@ -2737,10 +2768,10 @@ class EditTimeslotsTests(TestCase):
for ts in meeting.timeslot_set.exclude(pk__in=timeslots_before): for ts in meeting.timeslot_set.exclude(pk__in=timeslots_before):
self.assertEqual(ts.name, post_data['name']) self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type']) self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time']) self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location']) self.assertEqual(ts.show_location, post_data['show_location'])
self.assertIn(ts.time.date(), days) self.assertIn(ts.local_start_time().date(), days)
self.assertIn(ts.location, locations) self.assertIn(ts.location, locations)
self.assertIn((ts.time.date(), ts.location), day_locs, self.assertIn((ts.time.date(), ts.location), day_locs,
'Duplicated day / location found') 'Duplicated day / location found')
@ -3288,7 +3319,9 @@ class EditTests(TestCase):
room = Room.objects.get(meeting=meeting, session_types='regular') room = Room.objects.get(meeting=meeting, session_types='regular')
base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room, base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=50), 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')) timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time'))
@ -3510,7 +3543,12 @@ class EditTests(TestCase):
self.assertIn("#scroll=1234", r['Location']) self.assertIn("#scroll=1234", r['Location'])
test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing") 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.duration, datetime.timedelta(hours=1, minutes=30))
self.assertEqual(test_timeslot.location_id, break_room.pk) self.assertEqual(test_timeslot.location_id, break_room.pk)
self.assertEqual(test_timeslot.show_location, True) self.assertEqual(test_timeslot.show_location, True)
@ -3552,7 +3590,12 @@ class EditTests(TestCase):
}) })
self.assertNoFormPostErrors(r) self.assertNoFormPostErrors(r)
test_timeslot.refresh_from_db() 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.duration, datetime.timedelta(hours=1))
self.assertEqual(test_timeslot.location_id, breakfast_room.pk) self.assertEqual(test_timeslot.location_id, breakfast_room.pk)
self.assertEqual(test_timeslot.show_location, False) self.assertEqual(test_timeslot.show_location, False)
@ -4329,12 +4372,10 @@ class InterimTests(TestCase):
self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True, base_session=True) 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): 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( session = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first() meeting__type='interim', group__acronym='mars', current_status='apprw').first()
meeting = session.meeting meeting = session.meeting
meeting.time_zone = 'America/Los_Angeles'
meeting.save()
if base_session: if base_session:
base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False) base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False)
@ -4679,9 +4720,9 @@ class InterimTests(TestCase):
def do_interim_request_single_virtual(self, emails_expected): def do_interim_request_single_virtual(self, emails_expected):
make_meeting_test_data() make_meeting_test_data()
group = Group.objects.get(acronym='mars') group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30) date = date_today() + datetime.timedelta(days=30)
time = datetime.datetime.now().time().replace(microsecond=0,second=0) time = time_now().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time) dt = pytz.utc.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3) duration = datetime.timedelta(hours=3)
remote_instructions = 'Use webex' remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.' agenda = 'Intro. Slides. Discuss.'
@ -4750,13 +4791,14 @@ class InterimTests(TestCase):
def test_interim_request_single_in_person(self): def test_interim_request_single_in_person(self):
make_meeting_test_data() make_meeting_test_data()
group = Group.objects.get(acronym='mars') group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30) date = date_today() + datetime.timedelta(days=30)
time = datetime.datetime.now().time().replace(microsecond=0,second=0) time = time_now().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time) time_zone = 'America/Los_Angeles'
tz = pytz.timezone(time_zone)
dt = tz.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3) duration = datetime.timedelta(hours=3)
city = 'San Francisco' city = 'San Francisco'
country = 'US' country = 'US'
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex' remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.' agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level' agenda_note = 'On second level'
@ -4797,16 +4839,17 @@ class InterimTests(TestCase):
def test_interim_request_multi_day(self): def test_interim_request_multi_day(self):
make_meeting_test_data() 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) date2 = date + datetime.timedelta(days=1)
time = datetime.datetime.now().time().replace(microsecond=0,second=0) time = time_now().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time) time_zone = 'America/Los_Angeles'
dt2 = datetime.datetime.combine(date2, time) 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) duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars') group = Group.objects.get(acronym='mars')
city = 'San Francisco' city = 'San Francisco'
country = 'US' country = 'US'
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex' remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.' agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level' agenda_note = 'On second level'
@ -4865,7 +4908,7 @@ class InterimTests(TestCase):
make_meeting_test_data() make_meeting_test_data()
date = datetime.date.today() + datetime.timedelta(days=30) date = datetime.date.today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=2) 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') group = Group.objects.get(acronym='mars')
city = 'San Francisco' city = 'San Francisco'
country = 'US' country = 'US'
@ -4923,7 +4966,7 @@ class InterimTests(TestCase):
def test_interim_request_series(self): def test_interim_request_series(self):
make_meeting_test_data() make_meeting_test_data()
meeting_count_before = Meeting.objects.filter(type='interim').count() 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): if (date.month, date.day) == (12, 31):
# Avoid date and date2 in separate years # Avoid date and date2 in separate years
# (otherwise the test will fail if run on December 1st) # (otherwise the test will fail if run on December 1st)
@ -4933,14 +4976,15 @@ class InterimTests(TestCase):
if date.year != date2.year: if date.year != date2.year:
date += datetime.timedelta(days=1) date += datetime.timedelta(days=1)
date2 += datetime.timedelta(days=1) date2 += datetime.timedelta(days=1)
time = datetime.datetime.now().time().replace(microsecond=0,second=0) time = time_now().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time) time_zone = 'America/Los_Angeles'
dt2 = datetime.datetime.combine(date2, time) 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) duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars') group = Group.objects.get(acronym='mars')
city = '' city = ''
country = '' country = ''
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex' remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.' agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level' agenda_note = 'On second level'
@ -5082,14 +5126,14 @@ class InterimTests(TestCase):
self.assertFalse(can_manage_group(user=user,group=group)) self.assertFalse(can_manage_group(user=user,group=group))
def test_interim_request_details(self): 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 = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
login_testing_unauthorized(self,"secretary",url) login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) 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') utc_start_time = meeting.session_set.first().official_timeslotassignment().timeslot.utc_start_time().strftime('%H:%M')
self.assertIn(start_time, unicontent(r)) self.assertIn(start_time, unicontent(r))
self.assertIn(utc_start_time, unicontent(r)) self.assertIn(utc_start_time, unicontent(r))
@ -5615,7 +5659,7 @@ class InterimTests(TestCase):
def test_send_interim_minutes_reminder(self): def test_send_interim_minutes_reminder(self):
make_meeting_test_data() make_meeting_test_data()
group = Group.objects.get(acronym='mars') 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') meeting = make_interim_meeting(group=group, date=date, status='sched')
length_before = len(outbox) length_before = len(outbox)
send_interim_minutes_reminder(meeting=meeting) send_interim_minutes_reminder(meeting=meeting)
@ -5630,7 +5674,11 @@ class InterimTests(TestCase):
a1 = s1.official_timeslotassignment() a1 = s1.official_timeslotassignment()
t1 = a1.timeslot t1 = a1.timeslot
# Create an extra session # 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) s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule) SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
# #
@ -5640,8 +5688,8 @@ class InterimTests(TestCase):
self.assertContains(r, 'BEGIN:VEVENT') self.assertContains(r, 'BEGIN:VEVENT')
self.assertEqual(r.content.count(b'UID'), 2) self.assertEqual(r.content.count(b'UID'), 2)
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group') self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
self.assertContains(r, t1.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.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') self.assertContains(r, 'END:VEVENT')
# #
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, }) url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
@ -6563,7 +6611,7 @@ class HasMeetingsTests(TestCase):
q = PyQuery(r.content) q = PyQuery(r.content)
self.assertTrue(q('#id_group option[value="%d"]'%group.pk)) self.assertTrue(q('#id_group option[value="%d"]'%group.pk))
date = datetime.date.today() + datetime.timedelta(days=30+meeting_count) 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' remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.' agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level' agenda_note = 'On second level'
@ -6659,7 +6707,7 @@ class HasMeetingsTests(TestCase):
session = SessionFactory( session = SessionFactory(
group__type_id = gf.type_id, group__type_id = gf.type_id,
meeting__type_id='interim', meeting__type_id='interim',
meeting__date = datetime.datetime.today()+datetime.timedelta(days=30), meeting__date = timezone.now()+datetime.timedelta(days=30),
status_id='sched', status_id='sched',
) )
sessions.append(session) sessions.append(session)
@ -6675,7 +6723,7 @@ class HasMeetingsTests(TestCase):
sessions=[] sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True): for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id) 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( session = SessionFactory(
group=group, group=group,
meeting__type_id='interim', meeting__type_id='interim',
@ -6696,7 +6744,7 @@ class HasMeetingsTests(TestCase):
sessions=[] sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True): for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id) 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( session = SessionFactory(
group=group, group=group,
meeting__type_id='interim', meeting__type_id='interim',
@ -7422,7 +7470,7 @@ class ProceedingsTests(BaseMeetingTestCase):
def test_proceedings_no_agenda(self): def test_proceedings_no_agenda(self):
# Meeting number must be larger than the last special-cased proceedings (currently 96) # 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') url = urlreverse('ietf.meeting.views.proceedings')
r = self.client.get(url) r = self.client.get(url)
self.assertRedirects(r, urlreverse('ietf.meeting.views.materials')) self.assertRedirects(r, urlreverse('ietf.meeting.views.materials'))
@ -7531,7 +7579,7 @@ class ProceedingsTests(BaseMeetingTestCase):
"""Generate a meeting for proceedings material test""" """Generate a meeting for proceedings material test"""
# meeting number 123 avoids various legacy cases that affect these tests # meeting number 123 avoids various legacy cases that affect these tests
# (as of Aug 2021, anything above 96 is probably ok) # (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): def _secretary_only_permission_test(self, url, include_post=True):
self.client.logout() self.client.logout()

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime import datetime
import itertools import itertools
import pytz
import requests import requests
import subprocess import subprocess
@ -12,6 +13,7 @@ from urllib.error import HTTPError
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -33,13 +35,17 @@ def session_time_for_sorting(session, use_meeting_date):
if official_timeslot: if official_timeslot:
return official_timeslot.time return official_timeslot.time
elif use_meeting_date and session.meeting.date: 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: else:
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first() first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
if first_event: if first_event:
return first_event.time return first_event.time
else: 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): def session_requested_by(session):
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first() first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
@ -158,7 +164,12 @@ def create_proceedings_templates(meeting):
def finalize(meeting): def finalize(meeting):
end_date = meeting.end_date() 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 session in meeting.session_set.all():
for sp in session.sessionpresentation_set.filter(document__type='draft',rev=None): 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 ] rev_before_end = [e for e in sp.document.docevent_set.filter(newrevisiondocevent__isnull=False).order_by('-time') if e.time <= end_time ]
@ -322,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' # synthesize AD constraints - we can treat them as a special kind of 'bethere'
responsible_ad_for_group = {} 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') 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 # 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'): 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'):
@ -511,7 +524,7 @@ def swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, targe
if max_overlap > datetime.timedelta(minutes=5): if max_overlap > datetime.timedelta(minutes=5):
for a in lts_assignments: for a in lts_assignments:
a.timeslot = most_overlapping_rts a.timeslot = most_overlapping_rts
a.modified = datetime.datetime.now() a.modified = timezone.now()
a.save() a.save()
swapped = True swapped = True

View file

@ -36,10 +36,10 @@ from django.db.models import F, Max, Q
from django.forms.models import modelform_factory, inlineformset_factory from django.forms.models import modelform_factory, inlineformset_factory
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import now
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -96,6 +96,7 @@ from ietf.utils.pipe import pipe
from ietf.utils.pdf import pdf_pages from ietf.utils.pdf import pdf_pages
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.text import xslugify from ietf.utils.text import xslugify
from ietf.utils.timezone import datetime_today, date_today
from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm, from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm, InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
@ -127,8 +128,8 @@ def materials(request, num=None):
begin_date = meeting.get_submission_start_date() begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date() cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date() cor_cut_off_date = meeting.get_submission_correction_date()
now = datetime.date.today() now = 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: if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET:
pass pass
elif now > cor_cut_off_date: elif now > cor_cut_off_date:
@ -142,7 +143,7 @@ def materials(request, num=None):
'cor_cut_off_date': cor_cut_off_date 'cor_cut_off_date': cor_cut_off_date
}) })
past_cutoff_date = datetime.date.today() > meeting.get_submission_correction_date() past_cutoff_date = date_today() > meeting.get_submission_correction_date()
schedule = get_schedule(meeting, None) schedule = get_schedule(meeting, None)
@ -192,7 +193,7 @@ def materials(request, num=None):
}) })
def current_materials(request): 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') meetings = Meeting.objects.exclude(number__startswith='interim-').filter(date__lte=today).order_by('-date')
if meetings: if meetings:
return redirect(materials, meetings[0].number) return redirect(materials, meetings[0].number)
@ -281,60 +282,63 @@ def materials_editable_groups(request, num=None):
def edit_timeslots(request, num=None): def edit_timeslots(request, num=None):
meeting = get_meeting(num) meeting = get_meeting(num)
timezone.activate(meeting.tz())
try:
if request.method == 'POST':
# handle AJAX requests
action = request.POST.get('action')
if action == 'delete':
# delete a timeslot
# Parameters:
# slot_id: comma-separated list of TimeSlot PKs to delete
slot_id = request.POST.get('slot_id')
if slot_id is None:
return HttpResponseBadRequest('missing slot_id')
slot_ids = [id.strip() for id in slot_id.split(',')]
try:
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
except ValueError:
return HttpResponseBadRequest('invalid slot_id specification')
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
if len(missing_ids) != 0:
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
meeting.number,
', '.join(sorted(missing_ids))
))
timeslots.delete()
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
else:
return HttpResponseBadRequest('unknown action')
if request.method == 'POST': # Labels here differ from those in the build_timeslices() method. The labels here are
# handle AJAX requests # relative to the table: time_slices are the row headings (ie, days), date_slices are
action = request.POST.get('action') # the column headings (i.e., time intervals), and slots are the per-day list of timeslots
if action == 'delete': # (with only one timeslot per unique time/duration)
# delete a timeslot time_slices, date_slices, slots = meeting.build_timeslices()
# Parameters:
# slot_id: comma-separated list of TimeSlot PKs to delete
slot_id = request.POST.get('slot_id')
if slot_id is None:
return HttpResponseBadRequest('missing slot_id')
slot_ids = [id.strip() for id in slot_id.split(',')]
try:
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
except ValueError:
return HttpResponseBadRequest('invalid slot_id specification')
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
if len(missing_ids) != 0:
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
meeting.number,
', '.join(sorted(missing_ids))
))
timeslots.delete()
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
else:
return HttpResponseBadRequest('unknown action')
# Labels here differ from those in the build_timeslices() method. The labels here are ts_list = deque()
# relative to the table: time_slices are the row headings (ie, days), date_slices are rooms = meeting.room_set.order_by("capacity","name","id")
# the column headings (i.e., time intervals), and slots are the per-day list of timeslots for room in rooms:
# (with only one timeslot per unique time/duration) for day in time_slices:
time_slices, date_slices, slots = meeting.build_timeslices() for slice in date_slices[day]:
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
ts_list = deque() # Grab these in one query each to identify sessions that are in use and should be handled with care
rooms = meeting.room_set.order_by("capacity","name","id") ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
for room in rooms: ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
for day in time_slices:
for slice in date_slices[day]:
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
# Grab these in one query each to identify sessions that are in use and should be handled with care return render(request, "meeting/timeslot_edit.html",
ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule) {"rooms":rooms,
ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False) "time_slices":time_slices,
"slot_slices": slots,
return render(request, "meeting/timeslot_edit.html", "date_slices":date_slices,
{"rooms":rooms, "meeting":meeting,
"time_slices":time_slices, "ts_list":ts_list,
"slot_slices": slots, "ts_with_official_assignments": ts_with_official_assignments,
"date_slices":date_slices, "ts_with_any_assignments": ts_with_any_assignments,
"meeting":meeting, })
"ts_list":ts_list, finally:
"ts_with_official_assignments": ts_with_official_assignments, timezone.deactivate()
"ts_with_any_assignments": ts_with_any_assignments,
})
class NewScheduleForm(forms.ModelForm): class NewScheduleForm(forms.ModelForm):
class Meta: class Meta:
@ -442,7 +446,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
lock_time = settings.MEETING_SESSION_LOCK_TIME lock_time = settings.MEETING_SESSION_LOCK_TIME
def timeslot_locked(ts): 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: if not settings.USE_TZ:
meeting_now = meeting_now.replace(tzinfo=None) meeting_now = meeting_now.replace(tzinfo=None)
return schedule.is_official and (ts.time - meeting_now < lock_time) return schedule.is_official and (ts.time - meeting_now < lock_time)
@ -785,7 +789,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
timeslot=old_timeslot, timeslot=old_timeslot,
) )
existing_assignments.update(timeslot=timeslot, modified=datetime.datetime.now()) existing_assignments.update(timeslot=timeslot, modified=timezone.now())
else: else:
SchedTimeSessAssignment.objects.create( SchedTimeSessAssignment.objects.create(
session=session, session=session,
@ -1151,7 +1155,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
meeting=meeting, meeting=meeting,
type=c['type'], type=c['type'],
name=c['name'], 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'], duration=c['duration'],
location=c['location'], location=c['location'],
show_location=c['show_location'], show_location=c['show_location'],
@ -1200,7 +1204,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
timeslot.type = c['type'] timeslot.type = c['type']
timeslot.name = c['name'] 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.duration = c['duration']
timeslot.location = c['location'] timeslot.location = c['location']
timeslot.show_location = c['show_location'] timeslot.show_location = c['show_location']
@ -1287,8 +1291,9 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
for t in timeslot_qs: for t in timeslot_qs:
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t) 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)]) # Calculate full time range for display in meeting-local time, always showing at least 8am to 10pm
max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)]) 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) min_max_delta = datetime.datetime.combine(meeting.date, max_time) - datetime.datetime.combine(meeting.date, min_time)
day_grid = [] day_grid = []
@ -1310,7 +1315,14 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
if s: if s:
t.assigned_sessions.append(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) t.layout_width = min(100.0 * t.duration / min_max_delta, 100 - t.left_offset)
ts.append(t) ts.append(t)
@ -1553,19 +1565,29 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num()) is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
rendered_page = render(request, "meeting/"+base+ext, { display_timezone = 'UTC' if utc else meeting.time_zone
"personalize": False, timezone.activate(display_timezone)
"schedule": schedule, try:
"filtered_assignments": filtered_assignments, rendered_page = render(
"updated": updated, request,
"filter_categories": filter_organizer.get_filter_categories(), "meeting/" + base + ext,
"non_area_keywords": filter_organizer.get_non_area_keywords(), {
"now": datetime.datetime.now().astimezone(pytz.UTC), "personalize": False,
"timezone": meeting.time_zone, "schedule": schedule,
"is_current_meeting": is_current_meeting, "filtered_assignments": filtered_assignments,
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False, "updated": updated,
"cache_time": 150 if is_current_meeting else 3600, "filter_categories": filter_organizer.get_filter_categories(),
}, content_type=mimetype[ext]) "non_area_keywords": filter_organizer.get_non_area_keywords(),
"now": timezone.now().astimezone(pytz.utc),
"display_timezone": display_timezone,
"is_current_meeting": is_current_meeting,
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
"cache_time": 150 if is_current_meeting else 3600,
},
content_type=mimetype[ext],
)
finally:
timezone.deactivate()
return rendered_page return rendered_page
@ -1917,7 +1939,7 @@ def agenda_personalize(request, num):
'filtered_assignments': filtered_assignments, 'filtered_assignments': filtered_assignments,
'filter_categories': filter_organizer.get_filter_categories(), 'filter_categories': filter_organizer.get_filter_categories(),
'non_area_labels': filter_organizer.get_non_area_keywords(), 'non_area_labels': filter_organizer.get_non_area_keywords(),
'timezone': meeting.time_zone, 'display_timezone': meeting.time_zone,
'is_current_meeting': is_current_meeting, 'is_current_meeting': is_current_meeting,
'cache_time': 150 if is_current_meeting else 3600, 'cache_time': 150 if is_current_meeting else 3600,
} }
@ -2305,16 +2327,14 @@ def agenda_json(request, num=None):
meetinfo.sort(key=lambda x: x['modified'],reverse=True) meetinfo.sort(key=lambda x: x['modified'],reverse=True)
last_modified = meetinfo and meetinfo[0]['modified'] last_modified = meetinfo and meetinfo[0]['modified']
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
for obj in meetinfo: 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} data = {"%s"%num: meetinfo}
response = HttpResponse(json.dumps(data, indent=2, sort_keys=True), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET) response = HttpResponse(json.dumps(data, indent=2, sort_keys=True), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET)
if last_modified: 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())) response['Last-Modified'] = format_date_time(timegm(last_modified.timetuple()))
return response return response
@ -2386,7 +2406,9 @@ def session_details(request, num, acronym):
# Find the time of the meeting, so that we can look back historically # Find the time of the meeting, so that we can look back historically
# for what the group was called at the time. # 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 ])) groups = list(set([ s.group for s in sessions ]))
group_replacements = find_history_replacements_active_at(groups, meeting_time) group_replacements = find_history_replacements_active_at(groups, meeting_time)
@ -2453,8 +2475,8 @@ def session_details(request, num, acronym):
'is_materials_manager' : session.group.has_role(request.user, session.group.features.matman_roles), 'is_materials_manager' : session.group.has_role(request.user, session.group.features.matman_roles),
'can_manage_materials' : can_manage, 'can_manage_materials' : can_manage,
'can_view_request': can_view_request, 'can_view_request': can_view_request,
'thisweek': datetime.date.today()-datetime.timedelta(days=7), 'thisweek': datetime_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, 'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
}) })
@ -3542,7 +3564,7 @@ def interim_request_edit(request, number):
@cache_page(60*60) @cache_page(60*60)
def past(request): def past(request):
'''List of past meetings''' '''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')) meetings = data_for_meetings_overview(Meeting.objects.filter(date__lte=today).order_by('-date'))
@ -3552,7 +3574,7 @@ def past(request):
def upcoming(request): def upcoming(request):
'''List of upcoming meetings''' '''List of upcoming meetings'''
today = datetime.date.today() today = datetime_today()
# Get ietf meetings starting 7 days ago, and interim meetings starting 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)) ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
@ -3615,8 +3637,8 @@ def upcoming(request):
'menu_actions': actions, 'menu_actions': actions,
'menu_entries': menu_entries, 'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry, '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, 'use_codimd': (date_today() >= settings.MEETING_USES_CODIMD_DATE),
}) })
@ -3630,7 +3652,7 @@ def upcoming_ical(request):
except ValueError as e: except ValueError as e:
return HttpResponseBadRequest(str(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 # 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')) meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).prefetch_related('schedule').order_by('date'))
@ -3662,9 +3684,12 @@ def upcoming_ical(request):
ietfs = [m for m in meetings if m.type_id == 'ietf'] ietfs = [m for m in meetings if m.type_id == 'ietf']
preprocess_meeting_important_dates(ietfs) preprocess_meeting_important_dates(ietfs)
meeting_vtz = {meeting.vtimezone() for meeting in meetings}
meeting_vtz.discard(None)
# icalendar response file should have '\r\n' line endings per RFC5545 # icalendar response file should have '\r\n' line endings per RFC5545
response = render_to_string('meeting/upcoming.ics', { response = render_to_string('meeting/upcoming.ics', {
'vtimezones': ''.join(sorted(list({meeting.vtimezone() for meeting in meetings if meeting.vtimezone()}))), 'vtimezones': ''.join(sorted(meeting_vtz)),
'assignments': assignments, 'assignments': assignments,
'ietfs': ietfs, 'ietfs': ietfs,
}, request=request) }, request=request)
@ -3677,7 +3702,7 @@ def upcoming_ical(request):
def upcoming_json(request): def upcoming_json(request):
'''Return Upcoming meetings in json format''' '''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 # 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')) meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).order_by('date'))
@ -3726,7 +3751,7 @@ def proceedings(request, num=None):
begin_date = meeting.get_submission_start_date() begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date() cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date() cor_cut_off_date = meeting.get_submission_correction_date()
now = datetime.date.today() now = date_today()
schedule = get_schedule(meeting, None) schedule = get_schedule(meeting, None)
sessions = add_event_info_to_session_qs( sessions = add_event_info_to_session_qs(
@ -4023,7 +4048,7 @@ def important_dates(request, num=None, output_format=None):
base_num = int(meeting.number) base_num = int(meeting.number)
user = request.user user = request.user
today = datetime.date.today() today = datetime_today()
meetings = [] meetings = []
if meeting.show_important_dates or meeting.date < today: if meeting.show_important_dates or meeting.date < today:
meetings.append(meeting) meetings.append(meeting)
@ -4075,23 +4100,27 @@ def edit_timeslot(request, num, slot_id):
meeting = get_object_or_404(Meeting, number=num) meeting = get_object_or_404(Meeting, number=num)
if timeslot.meeting != meeting: if timeslot.meeting != meeting:
raise Http404() raise Http404()
if request.method == 'POST': timezone.activate(meeting.tz()) # specifies current_timezone used for rendering and form handling
form = TimeSlotEditForm(instance=timeslot, data=request.POST) try:
if form.is_valid(): if request.method == 'POST':
form.save() form = TimeSlotEditForm(instance=timeslot, data=request.POST)
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num})) if form.is_valid():
else: form.save()
form = TimeSlotEditForm(instance=timeslot) return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
else:
form = TimeSlotEditForm(instance=timeslot)
sessions = timeslot.sessions.filter( sessions = timeslot.sessions.filter(
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]) timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
return render( return render(
request, request,
'meeting/edit_timeslot.html', 'meeting/edit_timeslot.html',
{'timeslot': timeslot, 'form': form, 'sessions': sessions}, {'timeslot': timeslot, 'form': form, 'sessions': sessions},
status=400 if form.errors else 200, status=400 if form.errors else 200,
) )
finally:
timezone.deactivate()
@role_required('Secretariat') @role_required('Secretariat')
@ -4102,7 +4131,7 @@ def create_timeslot(request, num):
if form.is_valid(): if form.is_valid():
bulk_create_timeslots( bulk_create_timeslots(
meeting, 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', [])], for day in form.cleaned_data.get('days', [])],
form.cleaned_data['locations'], form.cleaned_data['locations'],
dict( dict(

View file

@ -6,6 +6,7 @@ import email
import datetime import datetime
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -25,7 +26,7 @@ class Command(BaseCommand):
""" """
def add_arguments(self, parser): 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( parser.add_argument(
'-t', '--start', '--from', type=str, default=default_start.strftime('%Y-%m-%d %H:%M'), '-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).', help='Limit the list to messages saved after the given time (default %(default)s).',

View file

@ -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),
),
]

View file

@ -2,10 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
import email.utils import email.utils
from django.db import models from django.db import models
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -17,7 +17,7 @@ from ietf.utils.models import ForeignKey
from ietf.utils.mail import get_email_addresses_from_text from ietf.utils.mail import get_email_addresses_from_text
class Message(models.Model): class Message(models.Model):
time = models.DateTimeField(default=datetime.datetime.now) time = models.DateTimeField(default=timezone.now)
by = ForeignKey(Person) by = ForeignKey(Person)
subject = models.CharField(max_length=255) subject = models.CharField(max_length=255)
@ -62,7 +62,7 @@ class MessageAttachment(models.Model):
class SendQueue(models.Model): class SendQueue(models.Model):
time = models.DateTimeField(default=datetime.datetime.now) time = models.DateTimeField(default=timezone.now)
by = ForeignKey(Person) by = ForeignKey(Person)
message = ForeignKey(Message) message = ForeignKey(Message)

View file

@ -5,6 +5,7 @@
import datetime import datetime
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -87,7 +88,7 @@ class SendScheduledAnnouncementsTests(TestCase):
q = SendQueue.objects.create( q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"), by=Person.objects.get(name="(System)"),
message=msg, message=msg,
send_at=datetime.datetime.now() + datetime.timedelta(hours=12) send_at=timezone.now() + datetime.timedelta(hours=12)
) )
mailbox_before = len(outbox) mailbox_before = len(outbox)
@ -113,7 +114,7 @@ class SendScheduledAnnouncementsTests(TestCase):
q = SendQueue.objects.create( q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"), by=Person.objects.get(name="(System)"),
message=msg, message=msg,
send_at=datetime.datetime.now() + datetime.timedelta(hours=12) send_at=timezone.now() + datetime.timedelta(hours=12)
) )
mailbox_before = len(outbox) mailbox_before = len(outbox)

View file

@ -2,8 +2,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re, datetime, email import re, email
from django.utils import timezone
from django.utils.encoding import force_str from django.utils.encoding import force_str
from ietf.utils.mail import send_mail_text, send_mail_mime 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, send_mail_mime(None, message.to, message.frm, message.subject,
msg, cc=message.cc, bcc=message.bcc) 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.save()
queue_item.message.sent = queue_item.sent_at queue_item.message.sent = queue_item.sent_at

View file

@ -15998,7 +15998,7 @@
"fields": { "fields": {
"command": "xym", "command": "xym",
"switch": "--version", "switch": "--version",
"time": "2022-07-13T00:09:29.108", "time": "2022-07-13T00:09:29.108Z",
"used": true, "used": true,
"version": "xym 0.5" "version": "xym 0.5"
}, },
@ -16009,7 +16009,7 @@
"fields": { "fields": {
"command": "pyang", "command": "pyang",
"switch": "--version", "switch": "--version",
"time": "2022-07-13T00:09:29.475", "time": "2022-07-13T00:09:29.475Z",
"used": true, "used": true,
"version": "pyang 2.5.3" "version": "pyang 2.5.3"
}, },
@ -16020,7 +16020,7 @@
"fields": { "fields": {
"command": "yanglint", "command": "yanglint",
"switch": "--version", "switch": "--version",
"time": "2022-07-13T00:09:29.497", "time": "2022-07-13T00:09:29.497Z",
"used": true, "used": true,
"version": "yanglint SO 1.9.2" "version": "yanglint SO 1.9.2"
}, },
@ -16031,7 +16031,7 @@
"fields": { "fields": {
"command": "xml2rfc", "command": "xml2rfc",
"switch": "--version", "switch": "--version",
"time": "2022-07-13T00:09:30.513", "time": "2022-07-13T00:09:30.513Z",
"used": true, "used": true,
"version": "xml2rfc 3.13.0" "version": "xml2rfc 3.13.0"
}, },

View file

@ -17,6 +17,7 @@ from django.conf import settings
from django.core.files import File from django.core.files import File
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import force_str from django.utils.encoding import force_str
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -49,6 +50,8 @@ from ietf.stats.models import MeetingRegistration
from ietf.stats.factories import MeetingRegistrationFactory from ietf.stats.factories import MeetingRegistrationFactory
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent
from ietf.utils.timezone import datetime_today, datetime_from_date, DEADLINE_TZINFO
client_test_cert_files = None client_test_cert_files = None
@ -1091,7 +1094,7 @@ class ReminderTest(TestCase):
rai = Position.objects.get(nomcom=self.nomcom,name='RAI') rai = Position.objects.get(nomcom=self.nomcom,name='RAI')
iab = Position.objects.get(nomcom=self.nomcom,name='IAB') iab = Position.objects.get(nomcom=self.nomcom,name='IAB')
today = datetime.date.today() today = datetime_today()
t_minus_3 = today - datetime.timedelta(days=3) t_minus_3 = today - datetime.timedelta(days=3)
t_minus_4 = today - datetime.timedelta(days=4) t_minus_4 = today - datetime.timedelta(days=4)
e1 = EmailFactory(address="nominee1@example.org", person=PersonFactory(name="Nominee 1"), origin='test') e1 = EmailFactory(address="nominee1@example.org", person=PersonFactory(name="Nominee 1"), origin='test')
@ -1389,7 +1392,7 @@ class FeedbackLastSeenTests(TestCase):
f.nominees.add(self.nominee) f.nominees.add(self.nominee)
f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id='comment') f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id='comment')
f.topics.add(self.topic) f.topics.add(self.topic)
now = datetime.datetime.now() now = timezone.now()
self.hour_ago = now - datetime.timedelta(hours=1) self.hour_ago = now - datetime.timedelta(hours=1)
self.half_hour_ago = now - datetime.timedelta(minutes=30) self.half_hour_ago = now - datetime.timedelta(minutes=30)
self.second_from_now = now + datetime.timedelta(seconds=1) self.second_from_now = now + datetime.timedelta(seconds=1)
@ -2417,7 +2420,8 @@ class rfc8989EligibilityTests(TestCase):
nobody=PersonFactory() nobody=PersonFactory()
for nomcom in self.nomcoms: for nomcom in self.nomcoms:
before_elig_date = nomcom.first_call_for_volunteers - datetime.timedelta(days=5) elig_datetime = datetime_from_date(nomcom.first_call_for_volunteers, DEADLINE_TZINFO)
before_elig_date = elig_datetime - datetime.timedelta(days=5)
chair = RoleFactory(name_id='chair',group__time=before_elig_date).person chair = RoleFactory(name_id='chair',group__time=before_elig_date).person
@ -2435,7 +2439,7 @@ class rfc8989EligibilityTests(TestCase):
def test_elig_by_office_edge(self): def test_elig_by_office_edge(self):
for nomcom in self.nomcoms: for nomcom in self.nomcoms:
elig_date=get_eligibility_date(nomcom) elig_date = datetime_from_date(get_eligibility_date(nomcom), DEADLINE_TZINFO)
day_after = elig_date + datetime.timedelta(days=1) day_after = elig_date + datetime.timedelta(days=1)
two_days_after = elig_date + datetime.timedelta(days=2) two_days_after = elig_date + datetime.timedelta(days=2)
@ -2450,15 +2454,15 @@ class rfc8989EligibilityTests(TestCase):
def test_elig_by_office_closed_groups(self): def test_elig_by_office_closed_groups(self):
for nomcom in self.nomcoms: for nomcom in self.nomcoms:
elig_date=get_eligibility_date(nomcom) elig_date=datetime_from_date(get_eligibility_date(nomcom), DEADLINE_TZINFO)
day_before = elig_date-datetime.timedelta(days=1) day_before = elig_date-datetime.timedelta(days=1)
# special case for Feb 29 # special case for Feb 29
if elig_date.month == 2 and elig_date.day == 29: if elig_date.month == 2 and elig_date.day == 29:
year_before = datetime.date(elig_date.year - 1, 2, 28) year_before = elig_date.replace(year=elig_date.year - 1, day=28)
three_years_before = datetime.date(elig_date.year - 3, 2, 28) three_years_before = elig_date.replace(year=elig_date.year - 3, day=28)
else: else:
year_before = datetime.date(elig_date.year - 1, elig_date.month, elig_date.day) year_before = elig_date.replace(year=elig_date.year - 1)
three_years_before = datetime.date(elig_date.year - 3, elig_date.month, elig_date.day) three_years_before = elig_date.replace(year=elig_date.year - 3)
just_after_three_years_before = three_years_before + datetime.timedelta(days=1) just_after_three_years_before = three_years_before + datetime.timedelta(days=1)
just_before_three_years_before = three_years_before - datetime.timedelta(days=1) just_before_three_years_before = three_years_before - datetime.timedelta(days=1)
@ -2517,14 +2521,14 @@ class rfc8989EligibilityTests(TestCase):
for nomcom in self.nomcoms: for nomcom in self.nomcoms:
elig_date = get_eligibility_date(nomcom) elig_date = get_eligibility_date(nomcom)
last_date = elig_date last_date = datetime_from_date(elig_date, DEADLINE_TZINFO)
# special case for Feb 29 # special case for Feb 29
if last_date.month == 2 and last_date.day == 29: if last_date.month == 2 and last_date.day == 29:
first_date = datetime.date(last_date.year - 5, 2, 28) first_date = last_date.replace(year = last_date.year - 5, day=28)
middle_date = datetime.date(last_date.year - 3, 2, 28) middle_date = last_date.replace(year=first_date.year - 3, day=28)
else: else:
first_date = datetime.date(last_date.year - 5, last_date.month, last_date.day) first_date = last_date.replace(year=last_date.year - 5)
middle_date = datetime.date(last_date.year - 3, last_date.month, last_date.day) middle_date = last_date.replace(year=first_date.year - 3)
day_after_last_date = last_date+datetime.timedelta(days=1) day_after_last_date = last_date+datetime.timedelta(days=1)
day_before_first_date = first_date-datetime.timedelta(days=1) day_before_first_date = first_date-datetime.timedelta(days=1)
@ -2664,7 +2668,7 @@ class VolunteerDecoratorUnitTests(TestCase):
office_person = PersonFactory() office_person = PersonFactory()
RoleHistoryFactory( RoleHistoryFactory(
name_id='chair', name_id='chair',
group__time= elig_date - datetime.timedelta(days=365), group__time=datetime_from_date(elig_date) - datetime.timedelta(days=365),
group__group__state_id='conclude', group__group__state_id='conclude',
person=office_person, person=office_person,
) )
@ -2676,11 +2680,13 @@ class VolunteerDecoratorUnitTests(TestCase):
DocEventFactory( DocEventFactory(
type='published_rfc', type='published_rfc',
doc=da.document, doc=da.document,
time=datetime.date( time=datetime.datetime(
elig_date.year - 3, elig_date.year - 3,
elig_date.month, elig_date.month,
28 if elig_date.month == 2 and elig_date.day == 29 else elig_date.day, 28 if elig_date.month == 2 and elig_date.day == 29 else elig_date.day,
)) tzinfo=datetime.timezone.utc,
)
)
nomcom.volunteer_set.create(person=author_person) nomcom.volunteer_set.create(person=author_person)
volunteers = nomcom.volunteer_set.all() volunteers = nomcom.volunteer_set.all()

View file

@ -31,6 +31,7 @@ from ietf.utils.pipe import pipe
from ietf.utils.mail import send_mail_text, send_mail, get_payload_text from ietf.utils.mail import send_mail_text, send_mail, get_payload_text
from ietf.utils.log import log from ietf.utils.log import log
from ietf.person.name import unidecode_name from ietf.person.name import unidecode_name
from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -536,34 +537,35 @@ def get_8989_eligibility_querysets(date, base_qs):
base_qs = Person.objects.all() base_qs = Person.objects.all()
previous_five = previous_five_meetings(date) previous_five = previous_five_meetings(date)
date_as_dt = datetime_from_date(date, DEADLINE_TZINFO)
three_of_five_qs = new_three_of_five_eligible(previous_five=previous_five, queryset=base_qs) three_of_five_qs = new_three_of_five_eligible(previous_five=previous_five, queryset=base_qs)
# If date is Feb 29, neither 3 nor 5 years ago has a Feb 29. Use Feb 28 instead. # If date is Feb 29, neither 3 nor 5 years ago has a Feb 29. Use Feb 28 instead.
if date.month == 2 and date.day == 29: if date.month == 2 and date.day == 29:
three_years_ago = datetime.date(date.year - 3, 2, 28) three_years_ago = datetime.datetime(date.year - 3, 2, 28, tzinfo=DEADLINE_TZINFO)
five_years_ago = datetime.date(date.year - 5, 2, 28) five_years_ago = datetime.datetime(date.year - 5, 2, 28, tzinfo=DEADLINE_TZINFO)
else: else:
three_years_ago = datetime.date(date.year - 3, date.month, date.day) three_years_ago = datetime.datetime(date.year - 3, date.month, date.day, tzinfo=DEADLINE_TZINFO)
five_years_ago = datetime.date(date.year - 5, date.month, date.day) five_years_ago = datetime.datetime(date.year - 5, date.month, date.day, tzinfo=DEADLINE_TZINFO)
officer_qs = base_qs.filter( officer_qs = base_qs.filter(
# is currently an officer # is currently an officer
Q(role__name_id__in=('chair','secr'), Q(role__name_id__in=('chair','secr'),
role__group__state_id='active', role__group__state_id='active',
role__group__type_id='wg', role__group__type_id='wg',
role__group__time__lte=date, role__group__time__lte=date_as_dt,
) )
# was an officer since the given date (I think this is wrong - it looks at when roles _start_, not when roles end) # was an officer since the given date (I think this is wrong - it looks at when roles _start_, not when roles end)
| Q(rolehistory__group__time__gte=three_years_ago, | Q(rolehistory__group__time__gte=three_years_ago,
rolehistory__group__time__lte=date, rolehistory__group__time__lte=date_as_dt,
rolehistory__name_id__in=('chair','secr'), rolehistory__name_id__in=('chair','secr'),
rolehistory__group__state_id='active', rolehistory__group__state_id='active',
rolehistory__group__type_id='wg', rolehistory__group__type_id='wg',
) )
).distinct() ).distinct()
rfc_pks = set(DocEvent.objects.filter(type='published_rfc',time__gte=five_years_ago,time__lte=date).values_list('doc__pk',flat=True)) rfc_pks = set(DocEvent.objects.filter(type='published_rfc', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk', flat=True))
iesgappr_pks = set(DocEvent.objects.filter(type='iesg_approved',time__gte=five_years_ago,time__lte=date).values_list('doc__pk',flat=True)) iesgappr_pks = set(DocEvent.objects.filter(type='iesg_approved', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk',flat=True))
qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks)) qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks))
author_qs = base_qs.filter( author_qs = base_qs.filter(
documentauthor__document__pk__in=qualifying_pks documentauthor__document__pk__in=qualifying_pks
@ -604,7 +606,7 @@ def get_eligibility_date(nomcom=None, date=None):
else: else:
return datetime.date(next_nomcom_year,5,1) return datetime.date(next_nomcom_year,5,1)
else: else:
return datetime.date(datetime.date.today().year,5,1) return datetime.date(datetime_today().year,5,1)
def previous_five_meetings(date = None): def previous_five_meetings(date = None):
if date is None: if date is None:

View file

@ -951,7 +951,7 @@ def view_feedback_topic(request, year, topic_id):
feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',]) feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',])
last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first() last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first()
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1) last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc)
if last_seen: if last_seen:
last_seen.save() last_seen.save()
else: else:
@ -973,7 +973,7 @@ def view_feedback_nominee(request, year, nominee_id):
feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES) feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES)
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first() last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1) last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc)
if last_seen: if last_seen:
last_seen.save() last_seen.save()
else: else:

View file

@ -1,9 +1,10 @@
# Copyright The IETF Trust 2021, All Rights Reserved # Copyright The IETF Trust 2021, All Rights Reserved
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import datetime, timedelta from datetime import timedelta
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db.models import Max, Min from django.db.models import Max, Min
from django.utils import timezone
from ietf.person.models import PersonApiKeyEvent from ietf.person.models import PersonApiKeyEvent
@ -33,7 +34,7 @@ class Command(BaseCommand):
self.stdout.write('Finding events older than {}\n'.format(_format_count(keep_days))) self.stdout.write('Finding events older than {}\n'.format(_format_count(keep_days)))
self.stdout.flush() self.stdout.flush()
now = datetime.now() now = timezone.now()
old_events = PersonApiKeyEvent.objects.filter( old_events = PersonApiKeyEvent.objects.filter(
time__lt=now - timedelta(days=keep_days) time__lt=now - timedelta(days=keep_days)
) )

View file

@ -5,6 +5,7 @@ import datetime
from io import StringIO from io import StringIO
from django.core.management import call_command, CommandError from django.core.management import call_command, CommandError
from django.utils import timezone
from ietf.person.factories import PersonApiKeyEventFactory from ietf.person.factories import PersonApiKeyEventFactory
from ietf.person.models import PersonApiKeyEvent, PersonEvent from ietf.person.models import PersonApiKeyEvent, PersonEvent
@ -51,7 +52,7 @@ class CommandTests(TestCase):
# Remember how many PersonEvents were present so we can verify they're cleaned up properly. # Remember how many PersonEvents were present so we can verify they're cleaned up properly.
personevents_before = PersonEvent.objects.count() 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 # 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. # days ago by the time we call the management command, so will just barely chosen for purge.
old_events = [ old_events = [
@ -101,7 +102,7 @@ class CommandTests(TestCase):
def test_purge_old_personal_api_key_events_rejects_invalid_arguments(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""" """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): with self.assertRaises(CommandError):
self._call_command('purge_old_personal_api_key_events') self._call_command('purge_old_personal_api_key_events')

View file

@ -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'),
),
]

View file

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
import email.utils import email.utils
import email.header import email.header
import jsonfield import jsonfield
@ -18,6 +17,7 @@ from django.core.validators import validate_email
from django.db import models from django.db import models
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.encoding import smart_bytes from django.utils.encoding import smart_bytes
from django.utils.text import slugify from django.utils.text import slugify
@ -38,7 +38,7 @@ from ietf.utils.models import ForeignKey, OneToOneField
class Person(models.Model): class Person(models.Model):
history = HistoricalRecords() history = HistoricalRecords()
user = OneToOneField(User, blank=True, null=True, on_delete=models.SET_NULL) 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 # The normal unicode form of the name. This must be
# set to the same value as the ascii-form if equal. # 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.") name = models.CharField("Full Name (Unicode)", max_length=255, db_index=True, help_text="Preferred long form of name.")
@ -377,7 +377,7 @@ PERSON_API_KEY_ENDPOINTS = sorted(list(set([ (v, n) for (v, n, r) in PERSON_API_
class PersonalApiKey(models.Model): class PersonalApiKey(models.Model):
person = ForeignKey(Person, related_name='apikeys') person = ForeignKey(Person, related_name='apikeys')
endpoint = models.CharField(max_length=128, null=False, blank=False, choices=PERSON_API_KEY_ENDPOINTS) 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) valid = models.BooleanField(default=True)
salt = models.BinaryField(default=salt, max_length=12, null=False, blank=False) salt = models.BinaryField(default=salt, max_length=12, null=False, blank=False)
count = models.IntegerField(default=0, null=False, blank=False) count = models.IntegerField(default=0, null=False, blank=False)
@ -427,7 +427,7 @@ PERSON_EVENT_CHOICES = [
class PersonEvent(models.Model): class PersonEvent(models.Model):
person = ForeignKey(Person) 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) type = models.CharField(max_length=50, choices=PERSON_EVENT_CHOICES)
desc = models.TextField() desc = models.TextField()

View file

@ -13,6 +13,7 @@ from pyquery import PyQuery
from django.http import HttpRequest from django.http import HttpRequest
from django.test import override_settings from django.test import override_settings
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.encoding import iri_to_uri from django.utils.encoding import iri_to_uri
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -211,7 +212,7 @@ class PersonUtilsTests(TestCase):
self.assertEqual(results,(p1,p3)) self.assertEqual(results,(p1,p3))
# both have User # both have User
today = datetime.datetime.today() today = timezone.now()
p2.user.last_login = today p2.user.last_login = today
p2.user.save() p2.user.save()
p4.user.last_login = today - datetime.timedelta(days=30) p4.user.last_login = today - datetime.timedelta(days=30)

View file

@ -2,6 +2,8 @@
import factory import factory
import datetime import datetime
from django.utils import timezone
from ietf.review.models import ReviewTeamSettings, ReviewRequest, ReviewAssignment, ReviewerSettings from ietf.review.models import ReviewTeamSettings, ReviewRequest, ReviewAssignment, ReviewerSettings
from ietf.name.models import ReviewTypeName, ReviewResultName from ietf.name.models import ReviewTypeName, ReviewResultName
@ -39,7 +41,7 @@ class ReviewRequestFactory(factory.django.DjangoModelFactory):
type_id = 'lc' type_id = 'lc'
doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft') doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft')
team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='review') 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') requested_by = factory.SubFactory('ietf.person.factories.PersonFactory')
class ReviewAssignmentFactory(factory.django.DjangoModelFactory): class ReviewAssignmentFactory(factory.django.DjangoModelFactory):
@ -49,7 +51,7 @@ class ReviewAssignmentFactory(factory.django.DjangoModelFactory):
review_request = factory.SubFactory('ietf.review.factories.ReviewRequestFactory') review_request = factory.SubFactory('ietf.review.factories.ReviewRequestFactory')
state_id = 'assigned' state_id = 'assigned'
reviewer = factory.SubFactory('ietf.person.factories.EmailFactory') reviewer = factory.SubFactory('ietf.person.factories.EmailFactory')
assigned_on = datetime.datetime.now() assigned_on = timezone.now()
class ReviewerSettingsFactory(factory.django.DjangoModelFactory): class ReviewerSettingsFactory(factory.django.DjangoModelFactory):
class Meta: class Meta:

View file

@ -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),
),
]

View file

@ -7,6 +7,7 @@ import datetime
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from django.db import models from django.db import models
from django.utils import timezone
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -95,7 +96,7 @@ class UnavailablePeriod(models.Model):
class ReviewWish(models.Model): class ReviewWish(models.Model):
"""Reviewer wishes to review a document when it becomes available for review.""" """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)) team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None))
person = ForeignKey(Person) person = ForeignKey(Person)
doc = ForeignKey(Document) doc = ForeignKey(Document)
@ -125,7 +126,7 @@ class ReviewRequest(models.Model):
# Fields filled in on the initial record creation - these # Fields filled in on the initial record creation - these
# constitute the request part. # constitute the request part.
time = models.DateTimeField(default=datetime.datetime.now) time = models.DateTimeField(default=timezone.now)
type = ForeignKey(ReviewTypeName) type = ForeignKey(ReviewTypeName)
doc = ForeignKey(Document, related_name='reviewrequest_set') doc = ForeignKey(Document, related_name='reviewrequest_set')
team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None)) team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None))

View file

@ -5,6 +5,7 @@ import datetime
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
from ietf.utils.mail import empty_outbox, get_payload_text, outbox from ietf.utils.mail import empty_outbox, get_payload_text, outbox
from ietf.utils.test_utils import TestCase, reload_db_objects from ietf.utils.test_utils import TestCase, reload_db_objects
from ietf.utils.timezone import datetime_from_date
from .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory from .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory
from .mailarch import hash_list_message_id from .mailarch import hash_list_message_id
from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod
@ -408,7 +409,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=self.deadline, review_request__deadline=self.deadline,
state_id='assigned', state_id='assigned',
assigned_on=self.deadline, assigned_on=datetime_from_date(self.deadline),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
).review_request.team ).review_request.team
second_team.reviewteamsettings.delete() # prevent it from being sent reminders second_team.reviewteamsettings.delete() # prevent it from being sent reminders
@ -420,7 +421,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=not_overdue, review_request__deadline=not_overdue,
state_id='assigned', state_id='assigned',
assigned_on=not_overdue, assigned_on=datetime_from_date(not_overdue),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
ReviewAssignmentFactory( ReviewAssignmentFactory(
@ -428,7 +429,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=not_overdue, review_request__deadline=not_overdue,
state_id='assigned', state_id='assigned',
assigned_on=not_overdue, assigned_on=datetime_from_date(not_overdue),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
@ -439,7 +440,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=in_grace_period, review_request__deadline=in_grace_period,
state_id='assigned', state_id='assigned',
assigned_on=in_grace_period, assigned_on=datetime_from_date(in_grace_period),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
ReviewAssignmentFactory( ReviewAssignmentFactory(
@ -447,7 +448,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=in_grace_period, review_request__deadline=in_grace_period,
state_id='assigned', state_id='assigned',
assigned_on=in_grace_period, assigned_on=datetime_from_date(in_grace_period),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )

View file

@ -12,6 +12,8 @@ from django.template.defaultfilters import pluralize
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils import timezone
from simple_history.utils import update_change_reason from simple_history.utils import update_change_reason
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -30,6 +32,8 @@ from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewRequestSt
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs
from ietf.utils import log from ietf.utils import log
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
# The origin date is used to have a single reference date for "every X days". # The origin date is used to have a single reference date for "every X days".
# This date is arbitrarily chosen and has no special meaning, but should be consistent. # This date is arbitrarily chosen and has no special meaning, but should be consistent.
@ -119,7 +123,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")) min_intervals = dict(ReviewerSettings.objects.filter(team=team).values_list("person_id", "min_interval"))
now = datetime.datetime.now() now = timezone.now()
res = {} res = {}
for person_id, latest_assignment_time in latest_assignments.items(): for person_id, latest_assignment_time in latest_assignments.items():
@ -192,7 +196,10 @@ def extract_review_assignment_data(teams=None, reviewers=None, time_from=None, t
assigned_time = assigned_on assigned_time = assigned_on
closed_time = completed_on closed_time = completed_on
late_days = positive_days(datetime.datetime.combine(deadline, datetime.time.max), closed_time) late_days = positive_days(
datetime.datetime.combine(deadline, datetime.time.max, tzinfo=DEADLINE_TZINFO),
closed_time,
)
request_to_assignment_days = positive_days(requested_time, assigned_time) request_to_assignment_days = positive_days(requested_time, assigned_time)
assignment_to_closure_days = positive_days(assigned_time, closed_time) assignment_to_closure_days = positive_days(assigned_time, closed_time)
request_to_closure_days = positive_days(requested_time, closed_time) request_to_closure_days = positive_days(requested_time, closed_time)
@ -283,7 +290,7 @@ def latest_review_assignments_for_reviewers(team, days_back=365):
extracted_data = extract_review_assignment_data( extracted_data = extract_review_assignment_data(
teams=[team], teams=[team],
time_from=datetime.date.today() - datetime.timedelta(days=days_back), time_from=datetime_today(DEADLINE_TZINFO) - datetime.timedelta(days=days_back),
ordering=["reviewer"], ordering=["reviewer"],
) )
@ -495,7 +502,7 @@ def suggested_review_requests_for_team(team):
requests = {} requests = {}
now = datetime.datetime.now() now = timezone.now()
reviewable_docs_qs = Document.objects.filter(type="draft").exclude(stream="ise") reviewable_docs_qs = Document.objects.filter(type="draft").exclude(stream="ise")

View file

@ -14,6 +14,7 @@ import debug # pyflakes:ignore
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from ietf.group.models import Group, GroupEvent from ietf.group.models import Group, GroupEvent
from ietf.meeting.factories import MeetingFactory from ietf.meeting.factories import MeetingFactory
@ -137,7 +138,10 @@ class SecrMeetingTestCase(TestCase):
"Edit Meeting" "Edit Meeting"
meeting = make_meeting_test_data() meeting = make_meeting_test_data()
url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number}) url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number})
post_data = dict(number=meeting.number,date='2014-07-20',city='Toronto', post_data = dict(number=meeting.number,
date='2014-07-20',
city='Toronto',
time_zone='America/Toronto',
days=7, days=7,
idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_00=13,
idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_day_offset_01=20,
@ -212,8 +216,8 @@ class SecrMeetingTestCase(TestCase):
self.assertEqual(q('#id_notification_list').html(),'ames, mars') self.assertEqual(q('#id_notification_list').html(),'ames, mars')
# test that only changes since last notification show up # test that only changes since last notification show up
now = datetime.datetime.now() now = timezone.now()
then = datetime.datetime.now()+datetime.timedelta(hours=1) then = timezone.now()+datetime.timedelta(hours=1)
person = Person.objects.get(name="(System)") person = Person.objects.get(name="(System)")
GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification', GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification',
by=person,desc='sent scheduled notification for %s' % meeting) by=person,desc='sent scheduled notification for %s' % meeting)
@ -285,7 +289,7 @@ class SecrMeetingTestCase(TestCase):
url = reverse('ietf.secr.meetings.views.times_delete',kwargs={ url = reverse('ietf.secr.meetings.views.times_delete',kwargs={
'meeting_id':meeting.number, 'meeting_id':meeting.number,
'schedule_name':meeting.schedule.name, 'schedule_name':meeting.schedule.name,
'time':qs.first().time.strftime("%Y:%m:%d:%H:%M") 'time':qs.first().time.astimezone(meeting.tz()).strftime("%Y:%m:%d:%H:%M")
}) })
redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={ redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={
'meeting_id':meeting.number, 'meeting_id':meeting.number,
@ -305,7 +309,7 @@ class SecrMeetingTestCase(TestCase):
url = reverse('ietf.secr.meetings.views.times_edit',kwargs={ url = reverse('ietf.secr.meetings.views.times_edit',kwargs={
'meeting_id':72, 'meeting_id':72,
'schedule_name':'test-schedule', 'schedule_name':'test-schedule',
'time':timeslot.time.strftime("%Y:%m:%d:%H:%M") 'time':timeslot.time.astimezone(meeting.tz()).strftime("%Y:%m:%d:%H:%M")
}) })
self.client.login(username="secretary", password="secretary+password") self.client.login(username="secretary", password="secretary+password")
response = self.client.post(url, { response = self.client.post(url, {
@ -371,7 +375,7 @@ class SecrMeetingTestCase(TestCase):
timeslot = session.official_timeslotassignment().timeslot timeslot = session.official_timeslotassignment().timeslot
url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk}) url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk})
redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'}) redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
new_time = timeslot.time + datetime.timedelta(days=1) new_time = (timeslot.time + datetime.timedelta(days=1)).astimezone(meeting.tz())
self.client.login(username="secretary", password="secretary+password") self.client.login(username="secretary", password="secretary+password")
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -389,7 +393,7 @@ class SecrMeetingTestCase(TestCase):
}) })
self.assertRedirects(response, redirect_url) self.assertRedirects(response, redirect_url)
timeslot = session.official_timeslotassignment().timeslot timeslot = session.official_timeslotassignment().timeslot
self.assertEqual(timeslot.time,new_time) self.assertEqual(timeslot.time, new_time)
def test_meetings_misc_session_delete(self): def test_meetings_misc_session_delete(self):
meeting = make_meeting_test_data() meeting = make_meeting_test_data()

View file

@ -11,6 +11,7 @@ from django.db.models import IntegerField
from django.db.models.functions import Cast from django.db.models.functions import Cast
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -30,6 +31,7 @@ from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm,
from ietf.secr.sreq.views import get_initial_session from ietf.secr.sreq.views import get_initial_session
from ietf.secr.utils.meeting import get_session, get_timeslot from ietf.secr.utils.meeting import get_session, get_timeslot
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.timezone import make_aware
# prep for agenda changes # prep for agenda changes
@ -121,7 +123,7 @@ def send_notifications(meeting, groups, person):
Send session scheduled email notifications for each group in groups. Person is the Send session scheduled email notifications for each group in groups. Person is the
user who initiated this action, request.uesr.get_profile(). user who initiated this action, request.uesr.get_profile().
''' '''
now = datetime.datetime.now() now = timezone.now()
for group in groups: for group in groups:
sessions = group.session_set.filter(meeting=meeting) sessions = group.session_set.filter(meeting=meeting)
addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0]) addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0])
@ -341,7 +343,7 @@ def edit_meeting(request, meeting_id):
else: else:
form = MeetingModelForm(instance=meeting) form = MeetingModelForm(instance=meeting)
debug.show('form.errors')
return render(request, 'meetings/edit_meeting.html', { return render(request, 'meetings/edit_meeting.html', {
'meeting': meeting, 'meeting': meeting,
'form' : form, }, 'form' : form, },
@ -798,7 +800,8 @@ def get_timeslot_time(form, meeting):
day = form.cleaned_data['day'] day = form.cleaned_data['day']
date = meeting.date + datetime.timedelta(days=int(day)) date = meeting.date + datetime.timedelta(days=int(day))
return datetime.datetime(date.year,date.month,date.day,time.hour,time.minute) return make_aware(datetime.datetime(date.year,date.month,date.day,time.hour,time.minute), meeting.tz())
@role_required('Secretariat') @role_required('Secretariat')
def times_edit(request, meeting_id, schedule_name, time): def times_edit(request, meeting_id, schedule_name, time):
@ -809,7 +812,7 @@ def times_edit(request, meeting_id, schedule_name, time):
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name) schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
parts = [ int(x) for x in time.split(':') ] parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts) dtime = make_aware(datetime.datetime(*parts), meeting.tz())
timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime) timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime)
if request.method == 'POST': if request.method == 'POST':
@ -860,7 +863,7 @@ def times_delete(request, meeting_id, schedule_name, time):
meeting = get_object_or_404(Meeting, number=meeting_id) meeting = get_object_or_404(Meeting, number=meeting_id)
parts = [ int(x) for x in time.split(':') ] parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts) dtime = make_aware(datetime.datetime(*parts), meeting.tz())
status = SessionStatusName.objects.get(slug='schedw') status = SessionStatusName.objects.get(slug='schedw')
if request.method == 'POST' and request.POST['post'] == 'yes': if request.method == 'POST' and request.POST['post'] == 'yes':

View file

@ -9,6 +9,7 @@ This module contains all the functions for generating static proceedings pages
''' '''
import datetime import datetime
import os import os
import pytz
import re import re
import subprocess import subprocess
from urllib.parse import urlencode from urllib.parse import urlencode
@ -24,6 +25,7 @@ from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTim
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.log import log from ietf.utils.log import log
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.utils.timezone import make_aware
AUDIO_FILE_RE = re.compile(r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})') AUDIO_FILE_RE = re.compile(r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})')
VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})-(?P<time>\d{4})') VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})-(?P<time>\d{4})')
@ -32,7 +34,7 @@ VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})
def _get_session(number,name,date,time): def _get_session(number,name,date,time):
'''Lookup session using data from video title''' '''Lookup session using data from video title'''
meeting = Meeting.objects.get(number=number) meeting = Meeting.objects.get(number=number)
timeslot_time = datetime.datetime.strptime(date + time,'%Y%m%d%H%M') timeslot_time = make_aware(datetime.datetime.strptime(date + time,'%Y%m%d%H%M'), meeting.tz())
try: try:
assignment = SchedTimeSessAssignment.objects.get( assignment = SchedTimeSessAssignment.objects.get(
schedule__in = [meeting.schedule, meeting.schedule.base], schedule__in = [meeting.schedule, meeting.schedule.base],
@ -102,7 +104,7 @@ def get_timeslot_for_filename(filename):
try: try:
meeting = Meeting.objects.get(number=match.groupdict()['number']) meeting = Meeting.objects.get(number=match.groupdict()['number'])
room_mapping = {normalize_room_name(room.name): room.name for room in meeting.room_set.all()} room_mapping = {normalize_room_name(room.name): room.name for room in meeting.room_set.all()}
time = datetime.datetime.strptime(match.groupdict()['time'],'%Y%m%d-%H%M') time = make_aware(datetime.datetime.strptime(match.groupdict()['time'],'%Y%m%d-%H%M'), meeting.tz())
slots = TimeSlot.objects.filter( slots = TimeSlot.objects.filter(
meeting=meeting, meeting=meeting,
location__name=room_mapping[match.groupdict()['room']], location__name=room_mapping[match.groupdict()['room']],
@ -201,17 +203,22 @@ def send_audio_import_warning(unmatched_files):
# End Recording Functions # 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 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 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. 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 = {}
data['sdate'] = sdate data['sdate'] = sdate
data['edate'] = edate 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['actions_count'] = events.filter(type='iesg_approved').count()
data['last_calls_count'] = events.filter(type='sent_last_call').count() data['last_calls_count'] = events.filter(type='sent_last_call').count()
@ -226,7 +233,7 @@ def get_progress_stats(sdate,edate):
data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ])) data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ]))
# Calculate Final Four Weeks stats (ffw) # 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() ffw_new_count = events.filter(time__gte=ffwdate,newrevisiondocevent__rev='00').count()
try: try:
ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%') ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%')
@ -257,14 +264,14 @@ def get_progress_stats(sdate,edate):
data['new_groups'] = Group.objects.filter( data['new_groups'] = Group.objects.filter(
type='wg', type='wg',
groupevent__changestategroupevent__state='active', groupevent__changestategroupevent__state='active',
groupevent__time__gte=sdate, groupevent__time__gte=sdatetime,
groupevent__time__lt=edate) groupevent__time__lt=edatetime)
data['concluded_groups'] = Group.objects.filter( data['concluded_groups'] = Group.objects.filter(
type='wg', type='wg',
groupevent__changestategroupevent__state='conclude', groupevent__changestategroupevent__state='conclude',
groupevent__time__gte=sdate, groupevent__time__gte=sdatetime,
groupevent__time__lt=edate) groupevent__time__lt=edatetime)
return data return data

View file

@ -1,20 +1,23 @@
import datetime import datetime
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from ietf.meeting.models import Meeting from ietf.meeting.models import Meeting
from ietf.doc.models import DocEvent, Document from ietf.doc.models import DocEvent, Document
from ietf.secr.proceedings.proc_utils import get_progress_stats from ietf.secr.proceedings.proc_utils import get_progress_stats
from ietf.utils.timezone import datetime_from_date
def report_id_activity(start,end): def report_id_activity(start,end):
# get previous meeting # 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('-') syear,smonth,sday = start.split('-')
eyear,emonth,eday = end.split('-') eyear,emonth,eday = end.split('-')
sdate = datetime.datetime(int(syear),int(smonth),int(sday)) sdate = datetime_from_date(datetime.date(int(syear),int(smonth),int(sday)), meeting.tz())
edate = datetime.datetime(int(eyear),int(emonth),int(eday)) edate = datetime_from_date(datetime.date(int(eyear),int(emonth),int(eday)), meeting.tz())
#queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time')) #queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time'))
new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision', new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
docevent__newrevisiondocevent__rev='00', docevent__newrevisiondocevent__rev='00',
@ -44,7 +47,7 @@ def report_id_activity(start,end):
approved = events.filter(type='iesg_approved').count() approved = events.filter(type='iesg_approved').count()
# get 4 weeks # get 4 weeks
monday = Meeting.get_current_meeting().get_ietf_monday() monday = datetime_from_date(Meeting.get_current_meeting().get_ietf_monday(), meeting.tz())
cutoff = monday + datetime.timedelta(days=3) cutoff = monday + datetime.timedelta(days=3)
ff1_date = cutoff - datetime.timedelta(days=28) ff1_date = cutoff - datetime.timedelta(days=28)
#ff2_date = cutoff - datetime.timedelta(days=21) #ff2_date = cutoff - datetime.timedelta(days=21)

View file

@ -38,20 +38,6 @@ def display_duration(value):
x=int(value) x=int(value)
return "%d Hours %d Minutes %d Seconds"%(x//3600,(x%3600)//60,x%60) return "%d Hours %d Minutes %d Seconds"%(x//3600,(x%3600)//60,x%60)
@register.filter
def get_published_date(doc):
'''
Returns the published date for a RFC Document
'''
event = doc.latest_event(type='published_rfc')
if event:
return event.time
event = doc.latest_event(type='new_revision')
if event:
return event.time
else:
return None
@register.filter @register.filter
def is_ppt(value): def is_ppt(value):
''' '''

View file

@ -50,8 +50,9 @@ class VideoRecordingTestCase(TestCase):
meeting = session.meeting meeting = session.meeting
number = meeting.number number = meeting.number
name = session.group.acronym name = session.group.acronym
date = session.official_timeslotassignment().timeslot.time.strftime('%Y%m%d') ts_time = session.official_timeslotassignment().timeslot.local_start_time()
time = session.official_timeslotassignment().timeslot.time.strftime('%H%M') date = ts_time.strftime('%Y%m%d')
time = ts_time.strftime('%H%M')
self.assertEqual(_get_session(number,name,date,time),session) self.assertEqual(_get_session(number,name,date,time),session)
def test_get_urls_from_json(self): def test_get_urls_from_json(self):
@ -113,7 +114,7 @@ class RecordingTestCase(TestCase):
return "{prefix}-{room}-{date}.mp3".format( return "{prefix}-{room}-{date}.mp3".format(
prefix=timeslot.meeting.type.slug + timeslot.meeting.number, prefix=timeslot.meeting.type.slug + timeslot.meeting.number,
room=normalize_room_name(timeslot.location.name), room=normalize_room_name(timeslot.location.name),
date=timeslot.time.strftime('%Y%m%d-%H%M')) date=timeslot.local_start_time().strftime('%Y%m%d-%H%M'))
def test_import_audio_files_shared_timeslot(self): def test_import_audio_files_shared_timeslot(self):
meeting = MeetingFactory(type_id='ietf',number='72') meeting = MeetingFactory(type_id='ietf',number='72')

View file

@ -1,6 +1,8 @@
import datetime import datetime
import debug # pyflakes:ignore import debug # pyflakes:ignore
from django.utils import timezone
from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory
from ietf.secr.proceedings.reports import report_id_activity, report_progress_report from ietf.secr.proceedings.reports import report_id_activity, report_progress_report
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
@ -10,7 +12,7 @@ class ReportsTestCase(TestCase):
def test_report_id_activity(self): def test_report_id_activity(self):
today = datetime.datetime.today() today = timezone.now()
yesterday = today - datetime.timedelta(days=1) yesterday = today - datetime.timedelta(days=1)
last_quarter = today - datetime.timedelta(days=3*30) last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7) next_week = today+datetime.timedelta(days=7)
@ -24,7 +26,7 @@ class ReportsTestCase(TestCase):
self.assertTrue('IETF Activity since last IETF Meeting' in result) self.assertTrue('IETF Activity since last IETF Meeting' in result)
def test_report_progress_report(self): def test_report_progress_report(self):
today = datetime.datetime.today() today = timezone.now()
last_quarter = today - datetime.timedelta(days=3*30) last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7) next_week = today+datetime.timedelta(days=7)

View file

@ -42,20 +42,6 @@ def display_duration(value):
else: else:
return "%d Hours %d Minutes %d Seconds"%(value//3600,(value%3600)//60,value%60) return "%d Hours %d Minutes %d Seconds"%(value//3600,(value%3600)//60,value%60)
@register.filter
def get_published_date(doc):
'''
Returns the published date for a RFC Document
'''
event = doc.latest_event(type='published_rfc')
if event:
return event.time
event = doc.latest_event(type='new_revision')
if event:
return event.time
else:
return None
@register.filter @register.filter
def is_ppt(value): def is_ppt(value):
''' '''

Some files were not shown because too many files have changed in this diff Show more