Skip to content

Commit c0acadf

Browse files
committed
Working checkpoint
- Legacy-Id: 9985
1 parent 1fb8f5a commit c0acadf

19 files changed

Lines changed: 421 additions & 15 deletions

ietf/doc/mails.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ietf.person.models import Person
1515
from ietf.group.models import Group, Role
1616
from ietf.doc.models import Document
17+
from ietf.eventmail.utils import gather_addresses
1718

1819
def email_state_changed(request, doc, text):
1920
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
@@ -170,16 +171,13 @@ def generate_approval_mail_approved(request, doc):
170171
else:
171172
action_type = "Document"
172173

173-
cc = []
174-
cc.extend(settings.DOC_APPROVAL_EMAIL_CC)
174+
to = gather_addresses('ballot_approved_ietf_stream',doc=doc)
175+
cc = gather_addresses('ballot_approved_ietf_stream_cc',doc=doc)
175176

176177
# the second check catches some area working groups (like
177178
# Transport Area Working Group)
178179
if doc.group.type_id not in ("area", "individ", "ag") and not doc.group.name.endswith("Working Group"):
179180
doc.group.name_with_wg = doc.group.name + " Working Group"
180-
if doc.group.list_email:
181-
cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email))
182-
cc.append("%s chair <%s-chairs@tools.ietf.org>" % (doc.group.acronym, doc.group.acronym))
183181
else:
184182
doc.group.name_with_wg = doc.group.name
185183

@@ -206,6 +204,7 @@ def generate_approval_mail_approved(request, doc):
206204
dict(doc=doc,
207205
docs=[doc],
208206
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
207+
to=",\n ".join(to),
209208
cc=",\n ".join(cc),
210209
doc_type=doc_type,
211210
made_by=made_by,
@@ -309,7 +308,7 @@ def email_resurrection_completed(request, doc, requester):
309308
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
310309

311310
def email_ballot_deferred(request, doc, by, telechat_date):
312-
to = "iesg@ietf.org"
311+
to = gather_addresses('ballot_deferred',doc=doc)
313312
frm = "DraftTracker Mail System <iesg-secretary@ietf.org>"
314313
send_mail(request, to, frm,
315314
"IESG Deferred Ballot notification: %s" % doc.file_tag(),
@@ -320,7 +319,7 @@ def email_ballot_deferred(request, doc, by, telechat_date):
320319
telechat_date=telechat_date))
321320

322321
def email_ballot_undeferred(request, doc, by, telechat_date):
323-
to = "iesg@ietf.org"
322+
to = gather_addresses('ballot_deferred',doc=doc)
324323
frm = "DraftTracker Mail System <iesg-secretary@ietf.org>"
325324
send_mail(request, to, frm,
326325
"IESG Undeferred Ballot notification: %s" % doc.file_tag(),

ietf/doc/tests_ballot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ietf.utils.mail import outbox
1616
from ietf.utils.test_data import make_test_data
1717
from ietf.utils.test_utils import login_testing_unauthorized
18+
from ietf.eventmail.utils import gather_addresses
1819

1920

2021
class EditPositionTests(TestCase):
@@ -171,7 +172,7 @@ def test_send_ballot_comment(self):
171172
self.assertEqual(r.status_code, 302)
172173
self.assertEqual(len(outbox), mailbox_before + 2)
173174
m = outbox[-1]
174-
self.assertEqual(m['Cc'],None)
175+
self.assertEqual(m['Cc'],','.join(gather_addresses('ballot_saved_cc',doc=draft)))
175176

176177

177178
class BallotWriteupsTests(TestCase):

ietf/doc/views_ballot.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from ietf.name.models import BallotPositionName
2828
from ietf.person.models import Person
2929
from ietf.utils.mail import send_mail_text, send_mail_preformatted
30+
from ietf.eventmail.utils import gather_addresses
3031

3132
BALLOT_CHOICES = (("yes", "Yes"),
3233
("noobj", "No Objection"),
@@ -284,10 +285,13 @@ def send_ballot_comment(request, name, ballot_id):
284285
blocking_name=blocking_name,
285286
settings=settings))
286287
frm = ad.role_email("ad").formatted_email()
287-
to = "The IESG <iesg@ietf.org>"
288+
to = gather_addresses('ballot_saved',doc=doc)
288289

289290
if request.method == 'POST':
290-
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
291+
cc = gather_addresses('ballot_saved_cc',doc=doc)
292+
explicit_cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
293+
if explicit_cc:
294+
cc.extend(explicit_cc)
291295
if request.POST.get("cc_state_change") and doc.notify:
292296
cc.extend(doc.notify.split(','))
293297
if request.POST.get("cc_group_list") and doc.group.list_email:
@@ -712,7 +716,7 @@ def approve_ballot(request, name):
712716

713717
if action == "to_announcement_list":
714718
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
715-
override={ "To": "IANA <%s>"%settings.IANA_APPROVE_EMAIL, "CC": None, "Bcc": None, "Reply-To": None})
719+
override={ "To": ",".join(gather_addresses('ballot_approved_ietf_stream_iana')), "CC": None, "Bcc": None, "Reply-To": None})
716720

717721
msg = infer_message(announcement)
718722
msg.by = login

ietf/doc/views_draft.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from ietf.secr.lib.template import jsonapi
3838
from ietf.utils.mail import send_mail, send_mail_message
3939
from ietf.utils.textupload import get_cleaned_text_file_content
40+
from ietf.eventmail.utils import gather_addresses
4041

4142
class ChangeStateForm(forms.Form):
4243
state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True)
@@ -1162,7 +1163,7 @@ def request_publication(request, name):
11621163
send_mail_message(request, m)
11631164

11641165
# IANA copy
1165-
m.to = settings.IANA_APPROVE_EMAIL
1166+
m.to = ",".join(gather_addresses('ballot_approved_ietf_stream_iana'))
11661167
send_mail_message(request, m, extra=extra_automation_headers(doc))
11671168

11681169
e = DocEvent(doc=doc, type="requested_publication", by=request.user.person)

ietf/eventmail/__init__.py

Whitespace-only changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models, migrations
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='Ingredient',
15+
fields=[
16+
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
17+
('desc', models.TextField(blank=True)),
18+
('template', models.CharField(max_length=512, null=True, blank=True)),
19+
],
20+
options={
21+
},
22+
bases=(models.Model,),
23+
),
24+
migrations.CreateModel(
25+
name='Recipe',
26+
fields=[
27+
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
28+
('desc', models.TextField(blank=True)),
29+
('ingredients', models.ManyToManyField(to='eventmail.Ingredient', null=True, blank=True)),
30+
],
31+
options={
32+
},
33+
bases=(models.Model,),
34+
),
35+
]

ietf/eventmail/migrations/__init__.py

Whitespace-only changes.

ietf/eventmail/models.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Copyright The IETF Trust 2015, All Rights Reserved
2+
3+
from django.db import models
4+
from django.template import Template, Context
5+
6+
class Recipe(models.Model):
7+
slug = models.CharField(max_length=32, primary_key=True)
8+
desc = models.TextField(blank=True)
9+
ingredients = models.ManyToManyField('Ingredient', null=True, blank=True)
10+
11+
class Ingredient(models.Model):
12+
slug = models.CharField(max_length=32, primary_key=True)
13+
desc = models.TextField(blank=True)
14+
template = models.CharField(max_length=512, null=True, blank=True)
15+
16+
def gather(self, **kwargs):
17+
retval = []
18+
if hasattr(self,'gather_%s'%self.slug):
19+
retval.extend(eval('self.gather_%s(**kwargs)'%self.slug))
20+
if self.template:
21+
rendering = Template(self.template).render(Context(kwargs))
22+
if rendering:
23+
retval.extend(rendering.split(','))
24+
25+
return retval
26+
27+
def gather_doc_group_chairs(self, **kwargs):
28+
addrs = []
29+
if 'doc' in kwargs:
30+
doc=kwargs['doc']
31+
if doc.group.type.slug in ['wg','rg']:
32+
addrs.append('%s-chairs@ietf.org'%doc.group.acronym)
33+
return addrs
34+
35+
def gather_doc_group_mail_list(self, **kwargs):
36+
addrs = []
37+
if 'doc' in kwargs:
38+
doc=kwargs['doc']
39+
if doc.group.type.slug in ['wg','rg']:
40+
if doc.group.list_email:
41+
addrs.append(doc.group.list_email)
42+
return addrs
43+
44+
def gather_doc_affecteddoc_authors(self, **kwargs):
45+
addrs = []
46+
if 'doc' in kwargs:
47+
for reldoc in kwargs['doc'].related_that_doc(['conflrev','tohist','tois','tops']):
48+
addrs.extend(Ingredient.objects.get(slug='doc_authors').gather(**{'doc':reldoc.document}))
49+
return addrs
50+
51+
def gather_doc_affecteddoc_group_chairs(self, **kwargs):
52+
addrs = []
53+
if 'doc' in kwargs:
54+
for reldoc in kwargs['doc'].related_that_doc(['conflrev','tohist','tois','tops']):
55+
addrs.extend(Ingredient.objects.get(slug='doc_group_chairs').gather(**{'doc':reldoc.document}))
56+
return addrs
57+
58+
def make_ingredients():
59+
60+
Ingredient.objects.all().delete()
61+
Ingredient.objects.create(slug='iesg',
62+
desc='The IESG',
63+
template='The IESG <iesg@ietf.org>')
64+
Ingredient.objects.create(slug='ietf_announce',
65+
desc='The IETF Announce list',
66+
template='IETF-Announce <ietf-announce@ietf.org>')
67+
Ingredient.objects.create(slug='rfc_editor',
68+
desc='The RFC Editor',
69+
template='<rfc-editor@rfc-editor.org>')
70+
Ingredient.objects.create(slug='iesg_secretary',
71+
desc='The Secretariat',
72+
template='<iesg-secretary@ietf.org>')
73+
Ingredient.objects.create(slug='doc_authors',
74+
desc="The document's authors",
75+
template='{{doc.name}}@ietf.org')
76+
Ingredient.objects.create(slug='doc_notify',
77+
desc="The addresses in the document's notify field",
78+
template='{{doc.notify}}')
79+
Ingredient.objects.create(slug='doc_group_chairs',
80+
desc="The document's group chairs (if the document is assigned to a working or research group)",
81+
template=None)
82+
Ingredient.objects.create(slug='doc_affecteddoc_authors',
83+
desc="The authors of the subject documents of a conflict-review or status-change",
84+
template=None)
85+
Ingredient.objects.create(slug='doc_affecteddoc_group_chairs',
86+
desc="The chairs of groups of the subject documents of a conflict-review or status-change",
87+
template=None)
88+
Ingredient.objects.create(slug='doc_shepherd',
89+
desc="The document's shepherd",
90+
template='{% if doc.shepherd %}{{doc.shepherd.address}}{% endif %}' )
91+
Ingredient.objects.create(slug='doc_ad',
92+
desc="The document's responsible Area Director",
93+
template='{% if doc.ad %}{{doc.ad.email_address}}{% endif %}' )
94+
Ingredient.objects.create(slug='doc_group_mail_list',
95+
desc="The list address of the document's group",
96+
template=None )
97+
Ingredient.objects.create(slug='conflict_review_stream_owner',
98+
desc="The stream owner of a document being reviewed for IETF stream conflicts",
99+
template='{% ifequal doc.type_id "conflrev" %}{% ifequal doc.stream_id "ise" %}<rfc-ise@rfc-editor.org>{% endifequal %}{% ifequal doc.stream_id "irtf" %}<irtf-chair@irtf.org>{% endifequal %}{% endifequal %}')
100+
Ingredient.objects.create(slug='iana_approve',
101+
desc="IANA's draft approval address",
102+
template='IANA <drafts-approval@icann.org>')
103+
104+
def make_recipes():
105+
106+
Recipe.objects.all().delete()
107+
108+
r = Recipe.objects.create(slug='ballot_saved',
109+
desc='Recipients when a new ballot position (with discusses, other blocking positions, or comments) is saved')
110+
r.ingredients = Ingredient.objects.filter(slug__in=['iesg'])
111+
112+
r = Recipe.objects.create(slug='ballot_saved_cc',
113+
desc='Copied when a new ballot position (with discusses, other blocking positions, or comments) is saved')
114+
r.ingredients = Ingredient.objects.filter(slug__in=['doc_authors',
115+
'doc_group_chairs',
116+
'doc_shepherd',
117+
'doc_affecteddoc_authors',
118+
'doc_affecteddoc_group_chairs',
119+
'conflict_review_stream_owner',
120+
])
121+
122+
r = Recipe.objects.create(slug='ballot_deferred',
123+
desc='Recipients when a ballot is deferred to or undeferred from a future telechat')
124+
r.ingredients = Ingredient.objects.filter(slug__in=['iesg',
125+
'iesg_secretary',
126+
'doc_group_chairs',
127+
'doc_notify',
128+
'doc_authors',
129+
'doc_shepherd',
130+
'doc_affecteddoc_authors',
131+
'doc_affecteddoc_group_chairs',
132+
'conflict_review_stream_owner',
133+
])
134+
135+
r = Recipe.objects.create(slug='ballot_approved_ietf_stream',
136+
desc='Recipients when an IETF stream document ballot is approved')
137+
r.ingredients = Ingredient.objects.filter(slug__in=['ietf_announce'])
138+
139+
r = Recipe.objects.create(slug='ballot_approved_ietf_stream_cc',
140+
desc='Copied when an IETF stream document ballot is approved')
141+
r.ingredients = Ingredient.objects.filter(slug__in=['iesg',
142+
'doc_notify',
143+
'doc_ad',
144+
'doc_authors',
145+
'doc_shepherd',
146+
'doc_group_mail_list',
147+
'doc_group_chairs',
148+
'rfc_editor',
149+
])
150+
151+
r = Recipe.objects.create(slug='ballot_approved_ietf_stream_iana',
152+
desc='Recipients for IANA message when an IETF stream document ballot is approved')
153+
r.ingredients = Ingredient.objects.filter(slug__in=['iana_approve'])
154+
155+

ietf/eventmail/resources.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Autogenerated by the makeresources management command 2015-08-06 11:00 PDT
2+
from tastypie.resources import ModelResource
3+
from tastypie.fields import ToOneField, ToManyField # pyflakes:ignore
4+
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
5+
6+
from ietf import api
7+
8+
from ietf.eventmail.models import * # pyflakes:ignore
9+
10+
11+
class IngredientResource(ModelResource):
12+
class Meta:
13+
queryset = Ingredient.objects.all()
14+
#resource_name = 'ingredient'
15+
filtering = {
16+
"slug": ALL,
17+
"desc": ALL,
18+
"template": ALL,
19+
}
20+
api.eventmail.register(IngredientResource())
21+
22+
class RecipeResource(ModelResource):
23+
ingredients = ToManyField(IngredientResource, 'ingredients', null=True)
24+
class Meta:
25+
queryset = Recipe.objects.all()
26+
#resource_name = 'recipe'
27+
filtering = {
28+
"slug": ALL,
29+
"desc": ALL,
30+
"ingredients": ALL_WITH_RELATIONS,
31+
}
32+
api.eventmail.register(RecipeResource())
33+

ietf/eventmail/tests.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from django.core.urlresolvers import reverse as urlreverse
2+
3+
from ietf.utils.test_utils import TestCase
4+
from ietf.utils.test_data import make_test_data
5+
from ietf.eventmail.models import Ingredient
6+
7+
class EventMailTests(TestCase):
8+
9+
def setUp(self):
10+
make_test_data()
11+
12+
def test_show_patterns(self):
13+
14+
url = urlreverse('ietf.eventmail.views.show_patterns')
15+
r = self.client.get(url)
16+
self.assertEqual(r.status_code, 200)
17+
self.assertTrue('ballot_saved_cc' in r.content)
18+
19+
url = urlreverse('ietf.eventmail.views.show_patterns',kwargs=dict(eventmail_slug='ballot_saved_cc'))
20+
r = self.client.get(url)
21+
self.assertEqual(r.status_code, 200)
22+
self.assertTrue('ballot_saved_cc' in r.content)
23+
24+
def test_show_recipients(self):
25+
26+
url = urlreverse('ietf.eventmail.views.show_ingredients')
27+
r = self.client.get(url)
28+
self.assertEqual(r.status_code, 200)
29+
self.assertTrue('bogus' in r.content)
30+
31+
url = urlreverse('ietf.eventmail.views.show_ingredients',kwargs=dict(ingredient_slug='bogus'))
32+
r = self.client.get(url)
33+
self.assertEqual(r.status_code, 200)
34+
self.assertTrue('bogus' in r.content)
35+
36+
class IngredientTests(TestCase):
37+
38+
def test_ingredient_functions(self):
39+
draft = make_test_data()
40+
ingredient = Ingredient.objects.first()
41+
for funcname in [name for name in dir(ingredient) if name.startswith('gather_')]:
42+
func=getattr(ingredient,funcname)
43+
func(**{'doc':draft,'group':draft.group})

0 commit comments

Comments
 (0)