Merged from log:branch/iesg-tracker@2571: IOLA's port of cron-scripts.

- Legacy-Id: 2578
This commit is contained in:
Henrik Levkowetz 2010-10-26 14:49:12 +00:00
parent 7254611df4
commit eafcdccd3d
23 changed files with 648 additions and 63 deletions

View file

@ -74,7 +74,7 @@ class ScheduledAnnouncement(models.Model):
cc_val = models.TextField(blank=True,null=True)
body = models.TextField(blank=True)
actual_sent_date = models.DateField(null=True, blank=True)
actual_sent_time = models.CharField(blank=True, max_length=50)
actual_sent_time = models.CharField(blank=True, max_length=50) # should be time, but database contains oddities
first_q = models.IntegerField(null=True, blank=True)
second_q = models.IntegerField(null=True, blank=True)
note = models.TextField(blank=True,null=True)

View file

@ -0,0 +1,32 @@
import re, datetime, email
from ietf.utils.mail import send_mail_text, send_mail_mime
first_dot_on_line_re = re.compile(r'^\.', re.MULTILINE)
def send_scheduled_announcement(announcement):
# for some reason, the old Perl code base substituted away . on line starts
body = first_dot_on_line_re.sub("", announcement.body)
announcement.content_type
extra = {}
if announcement.replyto:
extra['Reply-To'] = announcement.replyto
content_type = announcement.content_type.lower()
if not content_type or 'text/plain' in content_type:
send_mail_text(None, announcement.to_val, announcement.from_val, announcement.subject,
body, cc=announcement.cc_val, bcc=announcement.bcc_val)
elif 'multipart' in content_type:
# make body a real message so we can parse it
body = ("MIME-Version: 1.0\r\nContent-Type: %s\r\n" % content_type) + body
msg = email.message_from_string(body.encode("utf-8"))
send_mail_mime(None, announcement.to_val, announcement.from_val, announcement.subject,
msg, cc=announcement.cc_val, bcc=announcement.bcc_val)
now = datetime.datetime.now()
announcement.actual_sent_date = now.date()
announcement.actual_sent_time = str(now.time())
announcement.mail_sent = True
announcement.save()

View file

@ -1,5 +1,9 @@
import django.test
from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_sitemap
from ietf.utils.test_runner import mail_outbox
from ietf.announcements.models import ScheduledAnnouncement
class AnnouncementsUrlTestCase(SimpleUrlTestCase):
def testUrls(self):
@ -10,3 +14,46 @@ class AnnouncementsUrlTestCase(SimpleUrlTestCase):
else:
return content
class SendScheduledAnnouncementsTestCase(django.test.TestCase):
def test_send_plain_announcement(self):
a = ScheduledAnnouncement.objects.create(
mail_sent=False,
subject="This is a test",
to_val="test@example.com",
from_val="testmonkey@example.com",
cc_val="cc.a@example.com, cc.b@example.com",
bcc_val="bcc@example.com",
body="Hello World!",
content_type="",
)
mailbox_before = len(mail_outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(a)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)
def test_send_mime_announcement(self):
a = ScheduledAnnouncement.objects.create(
mail_sent=False,
subject="This is a test",
to_val="test@example.com",
from_val="testmonkey@example.com",
cc_val="cc.a@example.com, cc.b@example.com",
bcc_val="bcc@example.com",
body='--NextPart\r\n\r\nA New Internet-Draft is available from the on-line Internet-Drafts directories.\r\n--NextPart\r\nContent-Type: Message/External-body;\r\n\tname="draft-huang-behave-bih-01.txt";\r\n\tsite="ftp.ietf.org";\r\n\taccess-type="anon-ftp";\r\n\tdirectory="internet-drafts"\r\n\r\nContent-Type: text/plain\r\nContent-ID: <2010-07-30001541.I-D@ietf.org>\r\n\r\n--NextPart--',
content_type='Multipart/Mixed; Boundary="NextPart"',
)
mailbox_before = len(mail_outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(a)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
self.assertTrue("--NextPart" in mail_outbox[-1].as_string())
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)

20
ietf/bin/expire-ids Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env python
import datetime, os
import syslog
from ietf import settings
from django.core import management
management.setup_environ(settings)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.idrfc.expire import *
if not in_id_expire_freeze():
for doc in get_expired_ids():
send_expire_notice_for_id(doc)
expire_id(doc)
syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.id_document_tag, " in the ID Tracker" if doc.idinternal else ""))
clean_up_id_files()

17
ietf/bin/expire-last-calls Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env python
import datetime, os
import syslog
from ietf import settings
from django.core import management
management.setup_environ(settings)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.idrfc.lastcall import *
drafts = get_expired_last_calls()
for doc in drafts:
expire_last_call(doc)
syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.id_document_tag))

View file

@ -1,33 +1,42 @@
#!/usr/bin/env python
#
from django.core.management import setup_environ
import datetime, os, sys
import syslog
from ietf import settings
from django.core import management
management.setup_environ(settings)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
setup_environ(settings)
from django.db.models import Q
from django.db import connection
from ietf.announcements.models import ScheduledAnnouncement
from ietf.utils.mail import send_mail_text
import datetime
from ietf.announcements.send_scheduled import *
from django.db.models import Q
if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'):
print "USAGE: %s <all | rsync | specific>" % os.path.basename(__file__)
print "'all' means all not sent"
print "'rsync' means all not sent where to-be-sent-date is null"
print "'specific' means all not sent that are due to be sent"
sys.exit(1)
mode = sys.argv[1]
now = datetime.datetime.now()
candidates = ScheduledAnnouncement.objects.filter(to_be_sent_date__isnull=True) | ScheduledAnnouncement.objects.exclude(to_be_sent_date__gt=now.date())
candidates = candidates.filter(mail_sent=False) #.exclude(to_be_sent_date__gt=now.date())
for c in candidates:
if c.to_be_sent_time:
tbst = datetime.time( *[int(t) for t in c.to_be_sent_time.split(":")] )
if tbst > now.time():
# To be sent today, but not yet.
continue
extra={}
if c.content_type:
extra['Content-Type'] = c.content_type
if c.replyto:
extra['Reply-To'] = c.replyto
send_mail_text(None, to=c.to_val, frm=c.from_val, subject=c.subject,
txt=c.body, cc=c.cc_val, extra=extra, bcc=c.bcc_val)
c.actual_sent_date=now.date()
c.actual_sent_time=str(now.time()) # sigh
c.mail_sent=True
c.save()
now = datetime.datetime(2010, 8, 5)
announcements = ScheduledAnnouncement.objects.filter(mail_sent=False)
if mode == "rsync":
# include bogus 0000-00-00 entries
announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
elif mode == "specific":
# exclude null/bogus entries
announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
announcements = announcements.filter(to_be_sent_date__lte=now.date(),
to_be_sent_time__lte=now.time())
for announcement in announcements:
send_scheduled_announcement(announcement)
syslog.syslog('Sent scheduled announcement %s "%s"' % (announcement.id, announcement.subject))

122
ietf/idrfc/expire.py Normal file
View file

@ -0,0 +1,122 @@
# expiry of Internet Drafts
from django.conf import settings
from django.template.loader import render_to_string
from django.db.models import Q
import datetime, os, shutil, glob, re
from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment
from ietf.utils.mail import send_mail
from ietf.idrfc.utils import log_state_changed, add_document_comment
def in_id_expire_freeze(when=None):
if when == None:
when = datetime.datetime.now()
d = IDDates.objects.get(id=IDDates.SECOND_CUT_OFF).date
# for some reason, the old Perl code started at 9 am
second_cut_off = datetime.datetime.combine(d, datetime.time(9, 0))
d = IDDates.objects.get(id=IDDates.IETF_MONDAY).date
ietf_monday = datetime.datetime.combine(d, datetime.time(0, 0))
return second_cut_off <= when < ietf_monday
def get_expired_ids():
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
return InternetDraft.objects.filter(
revision_date__lte=cut_off,
status__status="Active",
review_by_rfc_editor=0).filter(
Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42))
def send_expire_notice_for_id(doc):
doc.dunn_sent_date = datetime.date.today()
doc.save()
if not doc.idinternal:
return
request = None
to = u"%s <%s>" % doc.idinternal.job_owner.person.email()
send_mail(request, to,
"I-D Expiring System <ietf-secretariat-reply@ietf.org>",
u"I-D was expired %s" % doc.file_tag(),
"idrfc/id_expired_email.txt",
dict(doc=doc))
def expire_id(doc):
def move_file(f):
src = os.path.join(settings.INTERNET_DRAFT_PATH, f)
dst = os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, f)
if os.path.exists(src):
shutil.move(src, dst)
move_file("%s-%s.txt" % (doc.filename, doc.revision_display()))
move_file("%s-%s.ps" % (doc.filename, doc.revision_display()))
move_file("%s-%s.pdf" % (doc.filename, doc.revision_display()))
new_revision = "%02d" % (int(doc.revision) + 1)
new_file = open(os.path.join(settings.INTERNET_DRAFT_PATH, "%s-%s.txt" % (doc.filename, new_revision)), 'w')
txt = render_to_string("idrfc/expire_text.txt",
dict(doc=doc,
authors=[a.person.email() for a in doc.authors.all()],
expire_days=InternetDraft.DAYS_TO_EXPIRE))
new_file.write(txt)
new_file.close()
doc.revision = new_revision
doc.expiration_date = datetime.date.today()
doc.last_modified_date = datetime.date.today()
doc.status = IDStatus.objects.get(status="Expired")
doc.save()
if doc.idinternal:
if doc.idinternal.cur_state_id != IDState.DEAD:
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.DEAD), None)
log_state_changed(None, doc, "system")
add_document_comment(None, doc, "Document is expired by system")
def clean_up_id_files():
"""Move unidentified and old files out of the Internet Draft directory."""
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
pattern = os.path.join(settings.INTERNET_DRAFT_PATH, "draft-*.*")
files = []
filename_re = re.compile('^(.*)-(\d+)$')
for path in glob.glob(pattern):
basename = os.path.basename(path)
stem, ext = os.path.splitext(basename)
match = filename_re.search(stem)
if not match:
filename, revision = ("UNKNOWN", "00")
else:
filename, revision = match.groups()
def move_file_to(subdir):
shutil.move(path,
os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, subdir, basename))
try:
doc = InternetDraft.objects.get(filename=filename, revision=revision)
if doc.status_id == 3:
if ext != ".txt":
move_file_to("unknown_ids")
elif doc.status_id in (2, 4, 5, 6) and doc.expiration_date and doc.expiration_date < cut_off:
if os.path.getsize(path) < 1500:
move_file_to("deleted_tombstones")
# revert version after having deleted tombstone
doc.revision = "%02d" % (int(revision) - 1)
doc.expired_tombstone = True
doc.save()
else:
move_file_to("expired_without_tombstone")
except InternetDraft.DoesNotExist:
move_file_to("unknown_ids")

View file

@ -1283,4 +1283,34 @@ withdrawn, etc.)</field>
<field type="CharField" name="name">Individual Submissions</field>
<field type="CharField" name="name_key">INDIVIDUAL SUBMISSIONS</field>
</object>
<object pk="1" model="idtracker.iddates">
<field type="DateField" name="date">2010-07-05</field>
<field type="CharField" name="description">1st Cut-Off Date (Initial version)</field>
<field type="CharField" name="f_name">first</field>
</object>
<object pk="2" model="idtracker.iddates">
<field type="DateField" name="date">2010-07-12</field>
<field type="CharField" name="description">2nd Cut-Off Date (Update version)</field>
<field type="CharField" name="f_name">second</field>
</object>
<object pk="3" model="idtracker.iddates">
<field type="DateField" name="date">2010-07-26</field>
<field type="CharField" name="description">Date of Monday of IETF</field>
<field type="CharField" name="f_name">third</field>
</object>
<object pk="4" model="idtracker.iddates">
<field type="DateField" name="date">2010-07-29</field>
<field type="CharField" name="description">All I-Ds will be processed by</field>
<field type="CharField" name="f_name">fourth</field>
</object>
<object pk="5" model="idtracker.iddates">
<field type="DateField" name="date">2010-08-02</field>
<field type="CharField" name="description">Date of Monday after IETF</field>
<field type="CharField" name="f_name">fifth</field>
</object>
<object pk="6" model="idtracker.iddates">
<field type="DateField" name="date">2010-06-28</field>
<field type="CharField" name="description">Date for List of Approved V-00 Submissions from WG Chairs</field>
<field type="CharField" name="f_name">sixth</field>
</object>
</django-objects>

View file

@ -45,6 +45,7 @@ base.extend(IDState.objects.all())
base.extend(WGType.objects.all())
base.extend(TelechatDates.objects.all())
base.extend(Acronym.objects.filter(acronym_id=Acronym.INDIVIDUAL_SUBMITTER))
base.extend(IDDates.objects.all())
output("base", base)

38
ietf/idrfc/lastcall.py Normal file
View file

@ -0,0 +1,38 @@
# helpers for handling last calls on Internet Drafts
import datetime
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
def request_last_call(request, doc):
try:
ballot = doc.idinternal.ballot
except BallotInfo.DoesNotExist:
ballot = generate_ballot(request, doc)
send_last_call_request(request, doc, ballot)
add_document_comment(request, doc, "Last Call was requested")
def get_expired_last_calls():
return InternetDraft.objects.filter(lc_expiration_date__lte=datetime.date.today(),
idinternal__cur_state__document_state_id=IDState.IN_LAST_CALL)
def expire_last_call(doc):
state = IDState.WAITING_FOR_WRITEUP
try:
ballot = doc.idinternal.ballot
if ballot.ballot_writeup and "What does this protocol do and why" not in ballot.ballot_writeup:
state = IDState.WAITING_FOR_AD_GO_AHEAD
except BallotInfo.DoesNotExist:
pass
doc.idinternal.change_state(IDState.objects.get(document_state_id=state), None)
doc.idinternal.event_date = datetime.date.today()
doc.idinternal.save()
log_state_changed(None, doc, by="system", email_watch_list=False)
email_last_call_expired(doc)

View file

@ -5,6 +5,7 @@ from datetime import datetime, date, time, timedelta
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from ietf.utils.mail import send_mail, send_mail_text
from ietf.idtracker.models import *
@ -15,12 +16,12 @@ def email_state_changed(request, doc, text):
"ID Tracker State Update Notice: %s" % doc.file_tag(),
"idrfc/state_changed_email.txt",
dict(text=text,
url=request.build_absolute_uri(doc.idinternal.get_absolute_url())))
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def html_to_text(html):
return strip_tags(html.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&").replace("<br>", "\n"))
def email_owner(request, doc, owner, changed_by, text):
def email_owner(request, doc, owner, changed_by, text, subject=None):
if not owner or not changed_by or owner == changed_by:
return
@ -28,10 +29,10 @@ def email_owner(request, doc, owner, changed_by, text):
send_mail(request, to,
"DraftTracker Mail System <iesg-secretary@ietf.org>",
"%s updated by %s" % (doc.file_tag(), changed_by),
"idrfc/changes_by_other.txt",
"idrfc/change_notice.txt",
dict(text=html_to_text(text),
doc=doc,
url=request.build_absolute_uri(doc.idinternal.get_absolute_url())))
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def full_intended_status(intended_status):
s = str(intended_status)
@ -70,11 +71,11 @@ def generate_last_call_announcement(request, doc):
for d in docs:
d.full_status = full_intended_status(d.intended_status)
d.filled_title = textwrap.fill(d.title, width=70, subsequent_indent=" " * 3)
urls.append(request.build_absolute_uri(d.idinternal.get_absolute_url()))
urls.append(settings.IDTRACKER_BASE_URL + d.idinternal.get_absolute_url())
return render_to_string("idrfc/last_call_announcement.txt",
dict(doc=doc,
doc_url=request.build_absolute_uri(doc.idinternal.get_absolute_url()),
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url(),
expiration_date=expiration_date.strftime("%Y-%m-%d"), #.strftime("%B %-d, %Y"),
cc=", ".join("<%s>" % e for e in cc),
group=group,
@ -131,7 +132,7 @@ def generate_approval_mail(request, doc):
return render_to_string("idrfc/approval_mail.txt",
dict(doc=doc,
doc_url=request.build_absolute_uri(doc.idinternal.get_absolute_url()),
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url(),
cc=",\n ".join(cc),
docs=docs,
doc_type=doc_type,
@ -150,7 +151,7 @@ def generate_approval_mail_rfc_editor(request, doc):
return render_to_string("idrfc/approval_mail_rfc_editor.txt",
dict(doc=doc,
doc_url=request.build_absolute_uri(doc.idinternal.get_absolute_url()),
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url(),
doc_type=doc_type,
status=status,
full_status=full_status,
@ -167,7 +168,7 @@ def send_last_call_request(request, doc, ballot):
"Last Call: %s" % doc.file_tag(),
"idrfc/last_call_request.txt",
dict(docs=docs,
doc_url=request.build_absolute_uri(doc.idinternal.get_absolute_url())))
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_resurrect_requested(request, doc, by):
to = "I-D Administrator <internet-drafts@ietf.org>"
@ -177,7 +178,7 @@ def email_resurrect_requested(request, doc, by):
"idrfc/resurrect_request_email.txt",
dict(doc=doc,
by=frm,
url=request.build_absolute_uri(doc.idinternal.get_absolute_url())))
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_resurrection_completed(request, doc):
to = u"%s <%s>" % doc.idinternal.resurrect_requested_by.person.email()
@ -187,7 +188,7 @@ def email_resurrection_completed(request, doc):
"idrfc/resurrect_completed_email.txt",
dict(doc=doc,
by=frm,
url=request.build_absolute_uri(doc.idinternal.get_absolute_url())))
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_ballot_deferred(request, doc, by, telechat_date):
to = "iesg@ietf.org"
@ -248,7 +249,7 @@ def generate_issue_ballot_mail(request, doc):
return render_to_string("idrfc/issue_ballot_mail.txt",
dict(doc=doc,
doc_url=request.build_absolute_uri(doc.idinternal.get_absolute_url()),
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url(),
status=status,
active_ad_positions=active_ad_positions,
inactive_ad_positions=inactive_ad_positions,
@ -273,3 +274,16 @@ def email_iana(request, doc, to, msg):
extra=extra,
bcc="fenner@research.att.com")
def email_last_call_expired(doc):
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.idinternal.cur_state.state
send_mail(None,
"iesg@ietf.org",
"DraftTracker Mail System <iesg-secretary@ietf.org>",
"Last Call Expired: %s" % doc.file_tag(),
"idrfc/change_notice.txt",
dict(text=text,
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()),
cc="iesg-secretary@ietf.org")

View file

@ -32,10 +32,12 @@
import unittest
import StringIO
import os, shutil
from datetime import date, timedelta
import django.test
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from pyquery import PyQuery
@ -101,7 +103,7 @@ class ChangeStateTestCase(django.test.TestCase):
self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
def test_make_last_call(self):
def test_request_last_call(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.client.login(remote_user="klm")
@ -111,7 +113,8 @@ class ChangeStateTestCase(django.test.TestCase):
self.assertRaises(BallotInfo.DoesNotExist, lambda: draft.idinternal.ballot)
r = self.client.post(url,
dict(state="15", substate=""))
dict(state=str(IDState.LAST_CALL_REQUESTED),
substate=""))
self.assertContains(r, "Your request to issue the Last Call")
# last call text
@ -133,6 +136,7 @@ class ChangeStateTestCase(django.test.TestCase):
# comment
self.assertTrue("Last Call was requested" in draft.idinternal.comments()[0].comment_text)
class EditInfoTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
@ -704,6 +708,11 @@ class MakeLastCallTestCase(django.test.TestCase):
def test_make_last_call(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.idinternal.cur_state = IDState.objects.get(document_state_id=IDState.LAST_CALL_REQUESTED)
draft.idinternal.save()
draft.lc_expiration_date = None
draft.save()
url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.filename))
login_testing_unauthorized(self, "klm", url)
@ -715,23 +724,226 @@ class MakeLastCallTestCase(django.test.TestCase):
# make last call
mailbox_before = len(mail_outbox)
expire_date = q('input[name=last_call_expiration_date]')[0].get("value")
r = self.client.post(url,
dict(last_call_sent_date=q('input[name=last_call_sent_date]')[0].get("value"),
last_call_expiration_date=q('input[name=last_call_expiration_date]')[0].get("value")
last_call_expiration_date=expire_date
))
self.assertEquals(r.status_code, 302)
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state_id, IDState.IN_LAST_CALL)
self.assertEquals(draft.lc_expiration_date.strftime("%Y-%m-%d"), expire_date)
self.assertEquals(len(mail_outbox), mailbox_before + 4)
self.assertTrue("Last Call" in mail_outbox[-4]['Subject'])
# the IANA copy
self.assertTrue("Last Call" in mail_outbox[-3]['Subject'])
class ExpireIDsTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
def setUp(self):
self.id_dir = os.path.abspath("tmp-id-dir")
self.archive_dir = os.path.abspath("tmp-id-archive")
os.mkdir(self.id_dir)
os.mkdir(self.archive_dir)
os.mkdir(os.path.join(self.archive_dir, "unknown_ids"))
os.mkdir(os.path.join(self.archive_dir, "deleted_tombstones"))
os.mkdir(os.path.join(self.archive_dir, "expired_without_tombstone"))
settings.INTERNET_DRAFT_PATH = self.id_dir
settings.INTERNET_DRAFT_ARCHIVE_DIR = self.archive_dir
def tearDown(self):
shutil.rmtree(self.id_dir)
shutil.rmtree(self.archive_dir)
def write_id_file(self, name, size):
f = open(os.path.join(self.id_dir, name), 'w')
f.write("a" * size)
f.close()
def test_in_id_expire_freeze(self):
from ietf.idrfc.expire import in_id_expire_freeze
self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 11, 0, 0)))
self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 12, 8, 0)))
self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 12, 10, 0)))
self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 25, 0, 0)))
self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 26, 0, 0)))
def test_expire_ids(self):
from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id
# hack into expirable state
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.status = IDStatus.objects.get(status="Active")
draft.review_by_rfc_editor = 0
draft.revision_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
draft.idinternal.cur_state_id = IDState.AD_WATCHING
draft.idinternal.save()
draft.save()
draft = InternetDraft.objects.get(filename="draft-ah-rfc2141bis-urn")
self.assertTrue(draft.idinternal == None)
draft.status = IDStatus.objects.get(status="Active")
draft.review_by_rfc_editor = 0
draft.revision_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
draft.save()
# test query
documents = get_expired_ids()
self.assertEquals(len(documents), 2)
for d in documents:
# test notice
mailbox_before = len(mail_outbox)
send_expire_notice_for_id(d)
self.assertEquals(InternetDraft.objects.get(filename=d.filename).dunn_sent_date, datetime.date.today())
if d.idinternal:
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("expired" in mail_outbox[-1]["Subject"])
# test expiry
txt = "%s-%s.txt" % (d.filename, d.revision_display())
self.write_id_file(txt, 5000)
revision_before = d.revision
expire_id(d)
draft = InternetDraft.objects.get(filename=d.filename)
self.assertEquals(draft.status.status, "Expired")
self.assertEquals(int(draft.revision), int(revision_before) + 1)
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, txt)))
new_txt = "%s-%s.txt" % (draft.filename, draft.revision)
self.assertTrue(os.path.exists(os.path.join(self.id_dir, new_txt)))
def test_clean_up_id_files(self):
from ietf.idrfc.expire import clean_up_id_files
# put unknown file
unknown = "draft-i-am-unknown-01.txt"
self.write_id_file(unknown, 5000)
clean_up_id_files()
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, unknown)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", unknown)))
# put file with malformed name (no revision)
malformed = "draft-ietf-mipshop-pfmipv6.txt"
self.write_id_file(malformed, 5000)
clean_up_id_files()
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, malformed)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", malformed)))
# RFC draft
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.status_id = 3
draft.save()
txt = "%s-%s.txt" % (draft.filename, draft.revision)
self.write_id_file(txt, 5000)
pdf = "%s-%s.pdf" % (draft.filename, draft.revision)
self.write_id_file(pdf, 5000)
clean_up_id_files()
# txt files shouldn't be moved (for some reason)
self.assertTrue(os.path.exists(os.path.join(self.id_dir, txt)))
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, pdf)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", pdf)))
# expired without tombstone
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.status_id = 2
draft.expiration_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
draft.save()
txt = "%s-%s.txt" % (draft.filename, draft.revision)
self.write_id_file(txt, 5000)
clean_up_id_files()
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "expired_without_tombstone", txt)))
# expired with tombstone
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.status_id = 2
draft.expiration_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
draft.expired_tombstone = False
draft.save()
revision_before = draft.revision
txt = "%s-%s.txt" % (draft.filename, draft.revision)
self.write_id_file(txt, 1000)
clean_up_id_files()
self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt)))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "deleted_tombstones", txt)))
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(int(draft.revision), int(revision_before) - 1)
self.assertTrue(draft.expired_tombstone)
class ExpireLastCallTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
def test_expire_last_call(self):
from ietf.idrfc.lastcall import get_expired_last_calls, expire_last_call
# check that not expired drafts aren't expired
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.idinternal.cur_state = IDState.objects.get(document_state_id=IDState.IN_LAST_CALL)
draft.idinternal.cur_substate = None
draft.idinternal.save()
draft.lc_expiration_date = datetime.date.today() + datetime.timedelta(days=2)
draft.save()
self.assertEquals(len(get_expired_last_calls()), 0)
draft.lc_expiration_date = None
draft.save()
self.assertEquals(len(get_expired_last_calls()), 0)
# test expired
draft.lc_expiration_date = datetime.date.today()
draft.save()
drafts = get_expired_last_calls()
self.assertEquals(len(drafts), 1)
mailbox_before = len(mail_outbox)
comments_before = draft.idinternal.comments().count()
expire_last_call(drafts[0])
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state.document_state_id, IDState.WAITING_FOR_WRITEUP)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"])
TEST_RFC_INDEX = '''<?xml version="1.0" encoding="UTF-8"?>
<rfc-index xmlns="http://www.rfc-editor.org/rfc-index"

View file

@ -1,10 +1,15 @@
from ietf.idtracker.models import DocumentComment, BallotInfo, IESGLogin
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
def add_document_comment(request, doc, text, include_by=True, ballot=None):
login = IESGLogin.objects.get(login_name=request.user.username)
if include_by:
text += " by %s" % login
if request:
login = IESGLogin.objects.get(login_name=request.user.username)
if include_by:
text += " by %s" % login
else:
login = None
if include_by:
text += " by %s" % "system"
c = DocumentComment()
c.document = doc.idinternal
@ -28,20 +33,11 @@ def generate_ballot(request, doc):
doc.idinternal.ballot = ballot
return ballot
def request_last_call(request, doc):
try:
ballot = doc.idinternal.ballot
except BallotInfo.DoesNotExist:
ballot = generate_ballot(request, doc)
send_last_call_request(request, doc, ballot)
add_document_comment(request, doc, "Last Call was requested")
def log_state_changed(request, doc, by):
def log_state_changed(request, doc, by, email_watch_list=True):
change = u"State changed to <b>%s</b> from <b>%s</b> by %s" % (
doc.idinternal.docstate(),
format_document_state(doc.idinternal.prev_state, doc.
idinternal.prev_sub_state),
format_document_state(doc.idinternal.prev_state,
doc.idinternal.prev_sub_state),
by)
c = DocumentComment()
@ -49,13 +45,15 @@ def log_state_changed(request, doc, by):
c.public_flag = True
c.version = doc.revision_display()
c.comment_text = change
c.created_by = by
if isinstance(by, IESGLogin):
c.created_by = by
c.result_state = doc.idinternal.cur_state
c.origin_state = doc.idinternal.prev_state
c.rfc_flag = doc.idinternal.rfc_flag
c.save()
email_state_changed(request, doc, strip_tags(change))
if email_watch_list:
email_state_changed(request, doc, strip_tags(change))
return change

View file

@ -20,7 +20,7 @@ from ietf.iesg.models import *
from ietf.ipr.models import IprDetail
from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
from ietf.idrfc.lastcall import request_last_call
BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"),

View file

@ -20,6 +20,7 @@ from ietf.idtracker.models import *
from ietf.iesg.models import *
from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
from ietf.idrfc.lastcall import request_last_call
class ChangeStateForm(forms.Form):

View file

@ -40,9 +40,12 @@ class IDState(models.Model):
PUBLICATION_REQUESTED = 10
LAST_CALL_REQUESTED = 15
IN_LAST_CALL = 16
WAITING_FOR_WRITEUP = 18
WAITING_FOR_AD_GO_AHEAD = 19
IESG_EVALUATION = 20
IESG_EVALUATION_DEFER = 21
APPROVED_ANNOUNCEMENT_SENT = 30
AD_WATCHING = 42
DEAD = 99
DO_NOT_PUBLISH_STATES = (33, 34)
@ -1053,6 +1056,21 @@ class IRTFChair(models.Model):
db_table = 'irtf_chairs'
verbose_name="IRTF Research Group Chair"
class IDDates(models.Model):
FIRST_CUT_OFF = 1
SECOND_CUT_OFF = 2
IETF_MONDAY = 3
ALL_IDS_PROCESSED_BY = 4
IETF_MONDAY_AFTER = 5
APPROVED_V00_SUBMISSIONS = 6
date = models.DateField(db_column="id_date")
description = models.CharField(max_length=255, db_column="date_name")
f_name = models.CharField(max_length=255)
class Meta:
db_table = 'id_dates'
# Not a model, but it's related.
# This is used in the view to represent documents
# in "I-D Exists".

1
ietf/liaisons/management/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/*.pyc

1
ietf/liaisons/migrations/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/*.pyc

View file

@ -143,6 +143,9 @@ INTERNAL_IPS = (
'::1',
)
# no slash at end
IDTRACKER_BASE_URL = "http://datatracker.ietf.org"
# Valid values:
# 'production', 'test', 'development'
# Override this in settings_local.py if it's not true
@ -165,6 +168,7 @@ IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'
IESG_WG_EVALUATION_DIR = "/a/www/www6/iesg/evaluation"
INTERNET_DRAFT_ARCHIVE_DIR = '/a/www/ietf/DRAFT_ARCHIVE'
# Override this in settings_local.py if needed
CACHE_MIDDLEWARE_SECONDS = 300

View file

@ -1,4 +1,4 @@
Please DO NOT reply on this email.
Please DO NOT reply to this email.
I-D: {{ doc.file_tag|safe }}
ID Tracker URL: {{ url }}

View file

@ -0,0 +1,7 @@
{% filter wordwrap:73 %}This Internet-Draft, {{ doc.filename }}-{{ doc.revision }}.txt, has expired, and has been deleted from the Internet-Drafts directory. An Internet-Draft expires {{ expire_days }} days from the date that it is posted unless it is replaced by an updated version, or the Secretariat has been notified that the document is under official review by the IESG or has been passed to the RFC Editor for review and/or publication as an RFC. This Internet-Draft was not published as an RFC.
Internet-Drafts are not archival documents, and copies of Internet-Drafts that have been deleted from the directory are not available. The Secretariat does not have any information regarding the future plans of the author{{ authors|pluralize}} or working group, if applicable, with respect to this deleted Internet-Draft. For more information, or to request a copy of the document, please contact the author{{ authors|pluralize}} directly.{% endfilter %}
Draft Author{{ authors|pluralize}}:
{% for name, email in authors %}{{ name }}<{{ email }}>
{% endfor %}

View file

@ -0,0 +1,6 @@
{{ doc.file_tag|safe }} was just expired.
This draft is in the state {{ doc.idstate }} in ID Tracker.
Thanks,
IETF Secretariat.

View file

@ -120,11 +120,18 @@ def send_mail(request, to, frm, subject, template, context, *args, **kwargs):
txt = render_to_string(template, context, context_instance=mail_context(request))
return send_mail_text(request, to, frm, subject, txt, *args, **kwargs)
def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=None, bcc=None):
"""Send plain text message."""
if isinstance(txt, unicode):
msg = MIMEText(txt.encode('utf-8'), 'plain', 'UTF-8')
else:
msg = MIMEText(txt)
send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=None, bcc=None)
def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=None, bcc=None):
"""Send MIME message with content already filled in."""
if isinstance(frm, tuple):
frm = formataddr(frm)
if isinstance(to, list) or isinstance(to, tuple):