Skip to content

Commit eafcdcc

Browse files
committed
Merged from log:branch/iesg-tracker@2571: IOLA's port of cron-scripts.
- Legacy-Id: 2578
1 parent 7254611 commit eafcdcc

23 files changed

Lines changed: 648 additions & 63 deletions

ietf/announcements/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class ScheduledAnnouncement(models.Model):
7474
cc_val = models.TextField(blank=True,null=True)
7575
body = models.TextField(blank=True)
7676
actual_sent_date = models.DateField(null=True, blank=True)
77-
actual_sent_time = models.CharField(blank=True, max_length=50)
77+
actual_sent_time = models.CharField(blank=True, max_length=50) # should be time, but database contains oddities
7878
first_q = models.IntegerField(null=True, blank=True)
7979
second_q = models.IntegerField(null=True, blank=True)
8080
note = models.TextField(blank=True,null=True)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import re, datetime, email
2+
3+
from ietf.utils.mail import send_mail_text, send_mail_mime
4+
5+
first_dot_on_line_re = re.compile(r'^\.', re.MULTILINE)
6+
7+
def send_scheduled_announcement(announcement):
8+
# for some reason, the old Perl code base substituted away . on line starts
9+
body = first_dot_on_line_re.sub("", announcement.body)
10+
11+
announcement.content_type
12+
extra = {}
13+
if announcement.replyto:
14+
extra['Reply-To'] = announcement.replyto
15+
16+
content_type = announcement.content_type.lower()
17+
if not content_type or 'text/plain' in content_type:
18+
send_mail_text(None, announcement.to_val, announcement.from_val, announcement.subject,
19+
body, cc=announcement.cc_val, bcc=announcement.bcc_val)
20+
elif 'multipart' in content_type:
21+
# make body a real message so we can parse it
22+
body = ("MIME-Version: 1.0\r\nContent-Type: %s\r\n" % content_type) + body
23+
24+
msg = email.message_from_string(body.encode("utf-8"))
25+
send_mail_mime(None, announcement.to_val, announcement.from_val, announcement.subject,
26+
msg, cc=announcement.cc_val, bcc=announcement.bcc_val)
27+
28+
now = datetime.datetime.now()
29+
announcement.actual_sent_date = now.date()
30+
announcement.actual_sent_time = str(now.time())
31+
announcement.mail_sent = True
32+
announcement.save()

ietf/announcements/tests.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import django.test
12

23
from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_sitemap
4+
from ietf.utils.test_runner import mail_outbox
5+
6+
from ietf.announcements.models import ScheduledAnnouncement
37

48
class AnnouncementsUrlTestCase(SimpleUrlTestCase):
59
def testUrls(self):
@@ -10,3 +14,46 @@ def doCanonicalize(self, url, content):
1014
else:
1115
return content
1216

17+
class SendScheduledAnnouncementsTestCase(django.test.TestCase):
18+
def test_send_plain_announcement(self):
19+
a = ScheduledAnnouncement.objects.create(
20+
mail_sent=False,
21+
subject="This is a test",
22+
to_val="test@example.com",
23+
from_val="testmonkey@example.com",
24+
cc_val="cc.a@example.com, cc.b@example.com",
25+
bcc_val="bcc@example.com",
26+
body="Hello World!",
27+
content_type="",
28+
)
29+
30+
mailbox_before = len(mail_outbox)
31+
32+
from ietf.announcements.send_scheduled import send_scheduled_announcement
33+
send_scheduled_announcement(a)
34+
35+
self.assertEquals(len(mail_outbox), mailbox_before + 1)
36+
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
37+
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)
38+
39+
def test_send_mime_announcement(self):
40+
a = ScheduledAnnouncement.objects.create(
41+
mail_sent=False,
42+
subject="This is a test",
43+
to_val="test@example.com",
44+
from_val="testmonkey@example.com",
45+
cc_val="cc.a@example.com, cc.b@example.com",
46+
bcc_val="bcc@example.com",
47+
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--',
48+
content_type='Multipart/Mixed; Boundary="NextPart"',
49+
)
50+
51+
mailbox_before = len(mail_outbox)
52+
53+
from ietf.announcements.send_scheduled import send_scheduled_announcement
54+
send_scheduled_announcement(a)
55+
56+
self.assertEquals(len(mail_outbox), mailbox_before + 1)
57+
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
58+
self.assertTrue("--NextPart" in mail_outbox[-1].as_string())
59+
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)

ietf/bin/expire-ids

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env python
2+
3+
import datetime, os
4+
import syslog
5+
6+
from ietf import settings
7+
from django.core import management
8+
management.setup_environ(settings)
9+
10+
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
11+
12+
from ietf.idrfc.expire import *
13+
14+
if not in_id_expire_freeze():
15+
for doc in get_expired_ids():
16+
send_expire_notice_for_id(doc)
17+
expire_id(doc)
18+
syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.id_document_tag, " in the ID Tracker" if doc.idinternal else ""))
19+
20+
clean_up_id_files()

ietf/bin/expire-last-calls

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python
2+
3+
import datetime, os
4+
import syslog
5+
6+
from ietf import settings
7+
from django.core import management
8+
management.setup_environ(settings)
9+
10+
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
11+
12+
from ietf.idrfc.lastcall import *
13+
14+
drafts = get_expired_last_calls()
15+
for doc in drafts:
16+
expire_last_call(doc)
17+
syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.id_document_tag))

ietf/bin/send-scheduled-mail

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,42 @@
11
#!/usr/bin/env python
2-
#
3-
from django.core.management import setup_environ
2+
3+
import datetime, os, sys
4+
import syslog
5+
46
from ietf import settings
7+
from django.core import management
8+
management.setup_environ(settings)
9+
10+
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
511

6-
setup_environ(settings)
712

8-
from django.db.models import Q
9-
from django.db import connection
1013
from ietf.announcements.models import ScheduledAnnouncement
11-
from ietf.utils.mail import send_mail_text
12-
import datetime
14+
from ietf.announcements.send_scheduled import *
15+
from django.db.models import Q
16+
17+
if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'):
18+
print "USAGE: %s <all | rsync | specific>" % os.path.basename(__file__)
19+
print "'all' means all not sent"
20+
print "'rsync' means all not sent where to-be-sent-date is null"
21+
print "'specific' means all not sent that are due to be sent"
22+
sys.exit(1)
23+
24+
mode = sys.argv[1]
1325

1426
now = datetime.datetime.now()
15-
candidates = ScheduledAnnouncement.objects.filter(to_be_sent_date__isnull=True) | ScheduledAnnouncement.objects.exclude(to_be_sent_date__gt=now.date())
16-
candidates = candidates.filter(mail_sent=False) #.exclude(to_be_sent_date__gt=now.date())
17-
for c in candidates:
18-
if c.to_be_sent_time:
19-
tbst = datetime.time( *[int(t) for t in c.to_be_sent_time.split(":")] )
20-
if tbst > now.time():
21-
# To be sent today, but not yet.
22-
continue
23-
extra={}
24-
if c.content_type:
25-
extra['Content-Type'] = c.content_type
26-
if c.replyto:
27-
extra['Reply-To'] = c.replyto
28-
send_mail_text(None, to=c.to_val, frm=c.from_val, subject=c.subject,
29-
txt=c.body, cc=c.cc_val, extra=extra, bcc=c.bcc_val)
30-
c.actual_sent_date=now.date()
31-
c.actual_sent_time=str(now.time()) # sigh
32-
c.mail_sent=True
33-
c.save()
27+
now = datetime.datetime(2010, 8, 5)
28+
29+
announcements = ScheduledAnnouncement.objects.filter(mail_sent=False)
30+
if mode == "rsync":
31+
# include bogus 0000-00-00 entries
32+
announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
33+
elif mode == "specific":
34+
# exclude null/bogus entries
35+
announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
36+
announcements = announcements.filter(to_be_sent_date__lte=now.date(),
37+
to_be_sent_time__lte=now.time())
38+
39+
for announcement in announcements:
40+
send_scheduled_announcement(announcement)
41+
42+
syslog.syslog('Sent scheduled announcement %s "%s"' % (announcement.id, announcement.subject))

ietf/idrfc/expire.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# expiry of Internet Drafts
2+
3+
from django.conf import settings
4+
from django.template.loader import render_to_string
5+
from django.db.models import Q
6+
7+
import datetime, os, shutil, glob, re
8+
9+
from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment
10+
from ietf.utils.mail import send_mail
11+
from ietf.idrfc.utils import log_state_changed, add_document_comment
12+
13+
def in_id_expire_freeze(when=None):
14+
if when == None:
15+
when = datetime.datetime.now()
16+
17+
d = IDDates.objects.get(id=IDDates.SECOND_CUT_OFF).date
18+
# for some reason, the old Perl code started at 9 am
19+
second_cut_off = datetime.datetime.combine(d, datetime.time(9, 0))
20+
21+
d = IDDates.objects.get(id=IDDates.IETF_MONDAY).date
22+
ietf_monday = datetime.datetime.combine(d, datetime.time(0, 0))
23+
24+
return second_cut_off <= when < ietf_monday
25+
26+
def get_expired_ids():
27+
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
28+
29+
return InternetDraft.objects.filter(
30+
revision_date__lte=cut_off,
31+
status__status="Active",
32+
review_by_rfc_editor=0).filter(
33+
Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42))
34+
35+
def send_expire_notice_for_id(doc):
36+
doc.dunn_sent_date = datetime.date.today()
37+
doc.save()
38+
39+
if not doc.idinternal:
40+
return
41+
42+
request = None
43+
to = u"%s <%s>" % doc.idinternal.job_owner.person.email()
44+
send_mail(request, to,
45+
"I-D Expiring System <ietf-secretariat-reply@ietf.org>",
46+
u"I-D was expired %s" % doc.file_tag(),
47+
"idrfc/id_expired_email.txt",
48+
dict(doc=doc))
49+
50+
def expire_id(doc):
51+
def move_file(f):
52+
src = os.path.join(settings.INTERNET_DRAFT_PATH, f)
53+
dst = os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, f)
54+
55+
if os.path.exists(src):
56+
shutil.move(src, dst)
57+
58+
move_file("%s-%s.txt" % (doc.filename, doc.revision_display()))
59+
move_file("%s-%s.ps" % (doc.filename, doc.revision_display()))
60+
move_file("%s-%s.pdf" % (doc.filename, doc.revision_display()))
61+
62+
new_revision = "%02d" % (int(doc.revision) + 1)
63+
64+
new_file = open(os.path.join(settings.INTERNET_DRAFT_PATH, "%s-%s.txt" % (doc.filename, new_revision)), 'w')
65+
txt = render_to_string("idrfc/expire_text.txt",
66+
dict(doc=doc,
67+
authors=[a.person.email() for a in doc.authors.all()],
68+
expire_days=InternetDraft.DAYS_TO_EXPIRE))
69+
new_file.write(txt)
70+
new_file.close()
71+
72+
doc.revision = new_revision
73+
doc.expiration_date = datetime.date.today()
74+
doc.last_modified_date = datetime.date.today()
75+
doc.status = IDStatus.objects.get(status="Expired")
76+
doc.save()
77+
78+
if doc.idinternal:
79+
if doc.idinternal.cur_state_id != IDState.DEAD:
80+
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.DEAD), None)
81+
log_state_changed(None, doc, "system")
82+
83+
add_document_comment(None, doc, "Document is expired by system")
84+
85+
def clean_up_id_files():
86+
"""Move unidentified and old files out of the Internet Draft directory."""
87+
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
88+
89+
pattern = os.path.join(settings.INTERNET_DRAFT_PATH, "draft-*.*")
90+
files = []
91+
filename_re = re.compile('^(.*)-(\d+)$')
92+
for path in glob.glob(pattern):
93+
basename = os.path.basename(path)
94+
stem, ext = os.path.splitext(basename)
95+
match = filename_re.search(stem)
96+
if not match:
97+
filename, revision = ("UNKNOWN", "00")
98+
else:
99+
filename, revision = match.groups()
100+
101+
def move_file_to(subdir):
102+
shutil.move(path,
103+
os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, subdir, basename))
104+
105+
try:
106+
doc = InternetDraft.objects.get(filename=filename, revision=revision)
107+
108+
if doc.status_id == 3:
109+
if ext != ".txt":
110+
move_file_to("unknown_ids")
111+
elif doc.status_id in (2, 4, 5, 6) and doc.expiration_date and doc.expiration_date < cut_off:
112+
if os.path.getsize(path) < 1500:
113+
move_file_to("deleted_tombstones")
114+
# revert version after having deleted tombstone
115+
doc.revision = "%02d" % (int(revision) - 1)
116+
doc.expired_tombstone = True
117+
doc.save()
118+
else:
119+
move_file_to("expired_without_tombstone")
120+
121+
except InternetDraft.DoesNotExist:
122+
move_file_to("unknown_ids")

ietf/idrfc/fixtures/base.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,4 +1283,34 @@ withdrawn, etc.)</field>
12831283
<field type="CharField" name="name">Individual Submissions</field>
12841284
<field type="CharField" name="name_key">INDIVIDUAL SUBMISSIONS</field>
12851285
</object>
1286+
<object pk="1" model="idtracker.iddates">
1287+
<field type="DateField" name="date">2010-07-05</field>
1288+
<field type="CharField" name="description">1st Cut-Off Date (Initial version)</field>
1289+
<field type="CharField" name="f_name">first</field>
1290+
</object>
1291+
<object pk="2" model="idtracker.iddates">
1292+
<field type="DateField" name="date">2010-07-12</field>
1293+
<field type="CharField" name="description">2nd Cut-Off Date (Update version)</field>
1294+
<field type="CharField" name="f_name">second</field>
1295+
</object>
1296+
<object pk="3" model="idtracker.iddates">
1297+
<field type="DateField" name="date">2010-07-26</field>
1298+
<field type="CharField" name="description">Date of Monday of IETF</field>
1299+
<field type="CharField" name="f_name">third</field>
1300+
</object>
1301+
<object pk="4" model="idtracker.iddates">
1302+
<field type="DateField" name="date">2010-07-29</field>
1303+
<field type="CharField" name="description">All I-Ds will be processed by</field>
1304+
<field type="CharField" name="f_name">fourth</field>
1305+
</object>
1306+
<object pk="5" model="idtracker.iddates">
1307+
<field type="DateField" name="date">2010-08-02</field>
1308+
<field type="CharField" name="description">Date of Monday after IETF</field>
1309+
<field type="CharField" name="f_name">fifth</field>
1310+
</object>
1311+
<object pk="6" model="idtracker.iddates">
1312+
<field type="DateField" name="date">2010-06-28</field>
1313+
<field type="CharField" name="description">Date for List of Approved V-00 Submissions from WG Chairs</field>
1314+
<field type="CharField" name="f_name">sixth</field>
1315+
</object>
12861316
</django-objects>

ietf/idrfc/generate_fixtures.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def output(name, qs):
4545
base.extend(WGType.objects.all())
4646
base.extend(TelechatDates.objects.all())
4747
base.extend(Acronym.objects.filter(acronym_id=Acronym.INDIVIDUAL_SUBMITTER))
48+
base.extend(IDDates.objects.all())
4849

4950
output("base", base)
5051

0 commit comments

Comments
 (0)