From 79d373fa712e8e39e2c50502072ff932679d499f Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 11 Aug 2015 21:52:28 +0000 Subject: [PATCH] checkpoint. - Legacy-Id: 10011 --- ietf/doc/mails.py | 27 +++++------- ietf/doc/tests_ballot.py | 5 +-- ietf/doc/tests_conflict_review.py | 5 +-- ietf/doc/utils_charter.py | 6 ++- ietf/doc/views_ballot.py | 8 ++-- ietf/doc/views_conflict_review.py | 7 ++- ietf/doc/views_draft.py | 9 ++-- ietf/doc/views_status_change.py | 4 +- .../migrations/0002_auto_20150809_1314.py | 43 ++++++++++++++++++- ietf/mailtoken/models.py | 17 ++++++++ ietf/mailtoken/utils.py | 5 ++- ietf/templates/doc/charter/review_text.txt | 4 +- .../doc/conflict_review/review_started.txt | 5 ++- ietf/utils/test_data.py | 5 ++- 14 files changed, 106 insertions(+), 44 deletions(-) diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 46cc6ca5b..3d0214bc7 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -30,13 +30,12 @@ def email_state_changed(request, doc, text): def email_stream_changed(request, doc, old_stream, new_stream, text=""): """Email the change text to the notify group and to the stream chairs""" - to = [x.strip() for x in doc.notify.replace(';', ',').split(',')] - - # These use comprehension to deal with conditions when there might be more than one chair listed for a stream + streams = [] if old_stream: - to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=old_stream.slug, name='chair')]) + streams.append(old_stream.slug) if new_stream: - to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=new_stream.slug, name='chair')]) + streams.append(new_stream.slug) + to = gather_addresses('doc_stream_changed',doc=doc,streams=streams) if not to: return @@ -124,8 +123,8 @@ def generate_last_call_announcement(request, doc): dict(doc=doc, doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url() + "ballot/", expiration_date=expiration_date.strftime("%Y-%m-%d"), #.strftime("%B %-d, %Y"), - to=",\n ".join(gather_addresses('last_call_issued',doc=doc)), - cc=",\n ".join(gather_addresses('last_call_issued_cc',doc=doc)), + to=gather_addresses('last_call_issued',doc=doc), + cc=gather_addresses('last_call_issued_cc',doc=doc), group=group, docs=[ doc ], urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], @@ -169,9 +168,6 @@ def generate_approval_mail_approved(request, doc): else: action_type = "Document" - to = gather_addresses('ballot_approved_ietf_stream',doc=doc) - cc = gather_addresses('ballot_approved_ietf_stream_cc',doc=doc) - # the second check catches some area working groups (like # Transport Area Working Group) if doc.group.type_id not in ("area", "individ", "ag") and not doc.group.name.endswith("Working Group"): @@ -202,8 +198,8 @@ def generate_approval_mail_approved(request, doc): dict(doc=doc, docs=[doc], doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - to=",\n ".join(to), - cc=",\n ".join(cc), + to = gather_addresses('ballot_approved_ietf_stream',doc=doc), + cc = gather_addresses('ballot_approved_ietf_stream_cc',doc=doc), doc_type=doc_type, made_by=made_by, contacts=contacts, @@ -215,16 +211,13 @@ def generate_approval_mail_rfc_editor(request, doc): disapproved = doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft" - to = gather_addresses('ballot_approved_conflrev', doc=doc) - cc = gather_addresses('ballot_approved_conflrev_cc', doc=doc) - return render_to_string("doc/mail/approval_mail_rfc_editor.txt", dict(doc=doc, doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), doc_type=doc_type, disapproved=disapproved, - to=",\n ".join(to), - cc=",\n ".join(cc), + to = gather_addresses('ballot_approved_conflrev', doc=doc), + cc = gather_addresses('ballot_approved_conflrev_cc', doc=doc), ) ) diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index 1ee4d1044..ecb006ed9 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -15,7 +15,6 @@ from ietf.utils.test_utils import TestCase from ietf.utils.mail import outbox from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized -from ietf.mailtoken.utils import gather_addresses class EditPositionTests(TestCase): @@ -171,8 +170,8 @@ class EditPositionTests(TestCase): r = self.client.post(url, dict(cc="")) self.assertEqual(r.status_code, 302) self.assertEqual(len(outbox), mailbox_before + 2) - m = outbox[-1] - self.assertEqual(m['Cc'],','.join(gather_addresses('ballot_saved_cc',doc=draft))) + #TODO this would be a good place to test actual mailtoken expansions + #if we can find a way to get the real, or at least representative, data in place. class BallotWriteupsTests(TestCase): diff --git a/ietf/doc/tests_conflict_review.py b/ietf/doc/tests_conflict_review.py index ec90c60ec..52c57c36d 100644 --- a/ietf/doc/tests_conflict_review.py +++ b/ietf/doc/tests_conflict_review.py @@ -70,7 +70,6 @@ class ConflictReviewTests(TestCase): self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review requested")) self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review initiated")) self.assertTrue('Conflict Review requested' in outbox[-1]['Subject']) - self.assertTrue(settings.IANA_EVAL_EMAIL in outbox[-1]['To']) # verify you can't start a review when a review is already in progress r = self.client.post(url,dict(ad="Aread Irector",create_in_state="Needs Shepherd",notify='ipu@ietf.org')) @@ -118,8 +117,8 @@ class ConflictReviewTests(TestCase): self.assertTrue(doc in [x.target.document for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')]) self.assertEqual(len(outbox), messages_before + 2) self.assertTrue('Conflict Review requested' in outbox[-1]['Subject']) - self.assertTrue(any('iesg-secretary@ietf.org' in x['To'] for x in outbox[-2:])) - self.assertTrue(any(settings.IANA_EVAL_EMAIL in x['To'] for x in outbox[-2:])) + #self.assertTrue(any('iesg-secretary@ietf.org' in x['To'] for x in outbox[-2:])) + #self.assertTrue(any(settings.IANA_EVAL_EMAIL in x['To'] for x in outbox[-2:])) def test_change_state(self): diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py index fa2f83b2b..c5ff3457a 100644 --- a/ietf/doc/utils_charter.py +++ b/ietf/doc/utils_charter.py @@ -127,8 +127,8 @@ def default_action_text(group, charter, by): techadv=group.role_set.filter(name="techadv"), milestones=group.groupmilestone_set.filter(state="charter"), action_type=action, - to=",\n ".join(gather_addresses('ballot_approved_charter',doc=charter,group=group)), - cc=",\n ".join(gather_addresses('ballot_approved_charter_cc',doc=charter,group=group)), + to=gather_addresses('ballot_approved_charter',doc=charter,group=group), + cc=gather_addresses('ballot_approved_charter_cc',doc=charter,group=group), )) e.save() @@ -149,6 +149,8 @@ def default_review_text(group, charter, by): milestones=group.groupmilestone_set.filter(state="charter"), review_date=(datetime.date.today() + datetime.timedelta(weeks=1)).isoformat(), review_type="new" if group.state_id == "proposed" else "recharter", + to=gather_addresses('charter_external_review',group=group), + cc=gather_addresses('charter_external_review_cc',group=group) ) ) e.save() diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index d858c77b1..05faf5f42 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -27,7 +27,7 @@ from ietf.message.utils import infer_message from ietf.name.models import BallotPositionName from ietf.person.models import Person from ietf.utils.mail import send_mail_text, send_mail_preformatted -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.utils import gather_addresses, gather_address_list BALLOT_CHOICES = (("yes", "Yes"), ("noobj", "No Objection"), @@ -288,7 +288,7 @@ def send_ballot_comment(request, name, ballot_id): to = gather_addresses('ballot_saved',doc=doc) if request.method == 'POST': - cc = gather_addresses('ballot_saved_cc',doc=doc) + cc = gather_address_list('ballot_saved_cc',doc=doc) explicit_cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()] if explicit_cc: cc.extend(explicit_cc) @@ -716,7 +716,7 @@ def approve_ballot(request, name): if action == "to_announcement_list": send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), - override={ "To": ",".join(gather_addresses('ballot_approved_ietf_stream_iana')), "CC": None, "Bcc": None, "Reply-To": None}) + override={ "To": gather_addresses('ballot_approved_ietf_stream_iana'), "CC": None, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) msg.by = login @@ -758,7 +758,7 @@ def make_last_call(request, name): send_mail_preformatted(request, announcement) if doc.type.slug == 'draft': send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), - override={ "To": ",\n ".join(gather_addresses('last_call_issued_iana',doc=doc)), + override={ "To": gather_addresses('last_call_issued_iana',doc=doc), "CC": None, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py index 23de7310f..0421c0b9f 100644 --- a/ietf/doc/views_conflict_review.py +++ b/ietf/doc/views_conflict_review.py @@ -20,6 +20,7 @@ from ietf.ietfauth.utils import has_role, role_required, is_authorized_in_doc_st from ietf.person.models import Person from ietf.utils.mail import send_mail_preformatted from ietf.utils.textupload import get_cleaned_text_file_content +from ietf.mailtoken.utils import gather_addresses class ChangeStateForm(forms.Form): review_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev"), label="Conflict review state", empty_label=None, required=True) @@ -88,6 +89,8 @@ def change_state(request, name, option=None): def send_conflict_review_started_email(request, review): msg = render_to_string("doc/conflict_review/review_started.txt", dict(frm = settings.DEFAULT_FROM_EMAIL, + to = gather_addresses('conflrev_requested',doc=review), + cc = gather_addresses('conflrev_requested_cc',doc=review), by = request.user.person, review = review, reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target.document, @@ -98,8 +101,8 @@ def send_conflict_review_started_email(request, review): send_mail_preformatted(request,msg) email_iana(request, review.relateddocument_set.get(relationship__slug='conflrev').target.document, - settings.IANA_EVAL_EMAIL, - msg) + gather_addresses('conflrev_requested_iana',doc=review), + msg) def send_conflict_eval_email(request,review): msg = render_to_string("doc/eval_email.txt", diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 8e069f8a0..f06874601 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -583,9 +583,9 @@ def to_iesg(request,name): doc.save() extra = {} - extra['Cc'] = ",\n ".join(gather_addresses('pubreq_iesg_cc',doc=doc)) + extra['Cc'] = gather_addresses('pubreq_iesg_cc',doc=doc) send_mail(request=request, - to = ",\n ".join(gather_addresses('pubreq_iesg',doc=doc)), + to = gather_addresses('pubreq_iesg',doc=doc), frm = login.formatted_email(), subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev), template = "doc/submit_to_iesg_email.txt", @@ -1133,7 +1133,7 @@ def request_publication(request, name): m = Message() m.frm = request.user.person.formatted_email() - m.to = ",\n ".join(gather_addresses('pubreq_rfced',doc=doc)) + m.to = gather_addresses('pubreq_rfced',doc=doc) m.by = request.user.person next_state = State.objects.get(used=True, type="draft-stream-%s" % doc.stream.slug, slug="rfc-edit") @@ -1163,7 +1163,7 @@ def request_publication(request, name): send_mail_message(request, m) # IANA copy - m.to = ", ".join(gather_addresses('pubreq_rfced_iana',doc=doc)) + m.to = gather_addresses('pubreq_rfced_iana',doc=doc) send_mail_message(request, m, extra=extra_automation_headers(doc)) e = DocEvent(doc=doc, type="requested_publication", by=request.user.person) @@ -1299,6 +1299,7 @@ def adopt_draft(request, name): update_reminder(doc, "stream-s", e, due_date) + # TODO: Replace this with a message that's explicitly about the document adoption email_stream_state_changed(request, doc, prev_state, new_state, by, comment) # comment diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py index 2ed24b7dd..a40cd21ff 100644 --- a/ietf/doc/views_status_change.py +++ b/ietf/doc/views_status_change.py @@ -299,8 +299,8 @@ def default_approval_text(status_change,relateddoc): approved_text = current_text, action=action, newstatus=newstatus(relateddoc), - to=",\n ".join(gather_addresses('ballot_approved_status_change',doc=status_change)), - cc=",\n ".join(gather_addresses('ballot_approved_status_change_cc',doc=status_change)), + to=gather_addresses('ballot_approved_status_change',doc=status_change), + cc=gather_addresses('ballot_approved_status_change_cc',doc=status_change), ) ) diff --git a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py index 6ad3ba54f..1d17c75d1 100644 --- a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py +++ b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py @@ -63,7 +63,11 @@ def make_recipients(apps): rc(slug='doc_stream_manager', desc="The manager of the document's stream", - template='{% if doc.stream_id == "ise" %}{% endif %}{% if doc.stream_id == "irtf" %}{% endif %}{% if doc.stream_id == "ietf" %}{% endif %}') + template=None ) + + rc(slug='stream_managers', + desc="The managers of any related streams", + template=None ) rc(slug='conflict_review_stream_manager', desc="The stream manager of a document being reviewed for IETF stream conflicts", @@ -81,6 +85,10 @@ def make_recipients(apps): desc="IANA's draft last call address", template='IANA ') + rc(slug='iana_eval', + desc="IANA's draft evaluation address", + template='IANA ') + rc(slug='iana', desc="IANA", template='') @@ -97,6 +105,7 @@ def make_recipients(apps): desc="The group's chairs", template="{{group.acronym}}-chairs@ietf.org") + def make_mailtokens(apps): Recipient=apps.get_model('mailtoken','Recipient') @@ -261,6 +270,38 @@ def make_mailtokens(apps): desc='Recipients for IANA message when a non-IETF stream manager requests publication', recipient_slugs=['iana_approve',]) + mt_factory(slug='charter_external_review', + desc='Recipients for a charter external review', + recipient_slugs=['ietf_announce',]) + + mt_factory(slug='charter_external_review_cc', + desc='Copied on a charter external review', + recipient_slugs=['group_mail_list',]) + + mt_factory(slug='conflrev_requested', + desc="Recipients for a stream manager's request for an IETF conflict review", + recipient_slugs=['iesg_secretary']) + + mt_factory(slug='conflrev_requested_cc', + desc="Copied on a stream manager's request for an IETF conflict review", + recipient_slugs=['iesg', + 'doc_notify', + 'doc_affecteddoc_authors', + 'doc_affecteddoc_group_chairs', + 'doc_affecteddoc_notify', + ]) + + mt_factory(slug='conflrev_requested_iana', + desc="Recipients for IANA message when a stream manager requests an IETF conflict review", + recipient_slugs=['iana_eval',]) + + mt_factory(slug='doc_stream_changed', + desc="Recipients for notification when a document's stream changes", + recipient_slugs=['stream_managers', + 'doc_notify', + ]) + + def forward(apps, schema_editor): make_recipients(apps) diff --git a/ietf/mailtoken/models.py b/ietf/mailtoken/models.py index 97e885dc6..2f457a182 100644 --- a/ietf/mailtoken/models.py +++ b/ietf/mailtoken/models.py @@ -97,4 +97,21 @@ class Recipient(models.Model): addrs.append(sg_map[kwargs['group'].type_id]) return addrs + def gather_stream_managers(self, **kwargs): + addrs = [] + manager_map = dict(ise = '', + irtf = '', + ietf = '', + iab = '') + if 'streams' in kwargs: + for stream in kwargs['streams']: + if stream in manager_map: + addrs.append(manager_map[stream]) + return addrs + + def gather_doc_stream_manager(self, **kwargs): + addrs = [] + if 'doc' in kwargs: + addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':[kwargs['doc'].stream_id]})) + return addrs diff --git a/ietf/mailtoken/utils.py b/ietf/mailtoken/utils.py index 0746fe0a2..1b95a7f3d 100644 --- a/ietf/mailtoken/utils.py +++ b/ietf/mailtoken/utils.py @@ -3,7 +3,7 @@ from django.core.exceptions import ObjectDoesNotExist from ietf.mailtoken.models import MailToken -def gather_addresses(slug,**kwargs): +def gather_address_list(slug,**kwargs): addrs = [] @@ -18,3 +18,6 @@ def gather_addresses(slug,**kwargs): addrs.extend(recipient.gather(**kwargs)) return list(set(addrs)) + +def gather_addresses(slug,**kwargs): + return ",\n ".join(gather_address_list(slug,**kwargs)) diff --git a/ietf/templates/doc/charter/review_text.txt b/ietf/templates/doc/charter/review_text.txt index c982eb5e1..ccf0c34c3 100644 --- a/ietf/templates/doc/charter/review_text.txt +++ b/ietf/templates/doc/charter/review_text.txt @@ -1,6 +1,6 @@ {% load ietf_filters %}{% autoescape off %}From: The IESG -To: IETF-Announce {% if group.list_email %} -Cc: {{ group.acronym }} {{ group.type.name }} <{{ group.list_email }}> {% endif %} +To: {{ to }}{% if cc %} +Cc: {{ cc }} {% endif %} Subject: WG Review: {{ group.name }} ({{ group.acronym }}) {% filter wordwrap:73 %}{% if review_type == "new" %}A new IETF working group has been proposed in the {{ group.parent.name }}.{% endif %}{% if review_type == "recharter" %}The {{ group.name }} ({{group.acronym}}) working group in the {{ group.parent.name }} of the IETF is undergoing rechartering.{% endif %} The IESG has not made any determination yet. The following draft charter was submitted, and is provided for informational purposes only. Please send your comments to the IESG mailing list (iesg at ietf.org) by {{ review_date }}. diff --git a/ietf/templates/doc/conflict_review/review_started.txt b/ietf/templates/doc/conflict_review/review_started.txt index 5161e287f..6bab3d941 100644 --- a/ietf/templates/doc/conflict_review/review_started.txt +++ b/ietf/templates/doc/conflict_review/review_started.txt @@ -1,5 +1,6 @@ -{% load mail_filters %}{% autoescape off %}To: IESG Secretary -From: {{ frm }} +{% load mail_filters %}{% autoescape off %}To: {{to}}{% if cc %} +Cc: {{cc}} +{% endif %}From: {{ frm }} Subject: Conflict Review requested for {{reviewed_doc.name}} {{ by.name }} has requested a conflict review for: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index f4cd7097e..8416bf4be 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -335,8 +335,11 @@ def make_test_data(): # This won't allow testing the results of the production configuration - if we want to do that, we'll need to # extract the production data either directly, or as a fixture recipient = Recipient.objects.create(slug='bogus_recipient',desc='Bogus Recipient',template='bogus@example.com') - for slug in ['ballot_approved_charter', 'ballot_approved_charter_cc', 'ballot_approved_conflrev', 'ballot_approved_conflrev_cc', 'ballot_approved_ietf_stream', 'ballot_approved_ietf_stream_cc', 'ballot_approved_ietf_stream_iana', 'ballot_approved_status_change', 'ballot_approved_status_change_cc', 'ballot_deferred', 'ballot_saved', 'ballot_saved_cc', 'last_call_expired', 'last_call_expired_cc', 'last_call_issued', 'last_call_issued_cc', 'last_call_issued_iana', 'last_call_requested', 'last_call_requested_cc', 'pubreq_iesg', 'pubreq_iesg_cc', 'pubreq_rfced', 'pubreq_rfced_iana']: + for slug in ['ballot_approved_charter', 'ballot_approved_charter_cc', 'ballot_approved_conflrev', 'ballot_approved_conflrev_cc', 'ballot_approved_ietf_stream', 'ballot_approved_ietf_stream_cc', 'ballot_approved_ietf_stream_iana', 'ballot_approved_status_change', 'ballot_approved_status_change_cc', 'ballot_deferred', 'ballot_saved', 'ballot_saved_cc', 'charter_external_review', 'charter_external_review_cc', 'conflrev_requested', 'conflrev_requested_cc', 'conflrev_requested_iana', 'doc_stream_changed', 'last_call_expired', 'last_call_expired_cc', 'last_call_issued', 'last_call_issued_cc', 'last_call_issued_iana', 'last_call_requested', 'last_call_requested_cc', 'pubreq_iesg', 'pubreq_iesg_cc', 'pubreq_rfced', 'pubreq_rfced_iana']: m = MailToken.objects.create(slug=slug,desc=slug) m.recipients=[recipient] + # Well, this isn't working out so well - Recipients that have code backing up their gather sometimes refer to other Recipients... + for slug in ['doc_authors','doc_group_chairs','doc_notify','doc_stream_owner','stream_managers']: + Recipient.objects.create(slug=slug,desc="Bogus Recipient",template='bogus@example.com') return draft