Notify IRSG when an IRSG ballot is created. Fixes #2978. Commit ready for merge.

- Legacy-Id: 18162
This commit is contained in:
Jennifer Richards 2020-07-13 16:45:45 +00:00
parent 24140fac50
commit 6e97a89786
11 changed files with 189 additions and 21 deletions

View file

@ -13,6 +13,7 @@ from django.urls import reverse as urlreverse
from django.utils.encoding import force_text
import debug # pyflakes:ignore
from ietf.doc.templatetags.mail_filters import std_level_prompt
from ietf.utils.mail import send_mail, send_mail_text
from ietf.ipr.utils import iprs_from_docs, related_docs
@ -401,7 +402,7 @@ def generate_issue_ballot_mail(request, doc, ballot):
last_call_expires = e.expires if e else None
last_call_has_expired = last_call_expires and last_call_expires < datetime.datetime.now()
return render_to_string("doc/mail/issue_ballot_mail.txt",
return render_to_string("doc/mail/issue_iesg_ballot_mail.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
last_call_expires=last_call_expires,
@ -413,6 +414,58 @@ def generate_issue_ballot_mail(request, doc, ballot):
)
)
def _send_irsg_ballot_email(request, doc, ballot, subject, template):
"""Send email notification when IRSG ballot is issued"""
(to, cc) = gather_address_lists('irsg_ballot_issued', doc=doc)
sender = 'IESG Secretary <iesg-secretary@ietf.org>'
ballot_expired = ballot.duedate < datetime.datetime.now()
active_ballot = doc.active_ballot()
if active_ballot is None:
needed_bps = ''
else:
needed_bps = needed_ballot_positions(
doc,
list(active_ballot.active_balloter_positions().values())
)
return send_mail(
request=request,
frm=sender,
to=to,
cc=cc,
subject=subject,
extra={'Reply-To': [sender]},
template=template,
context=dict(
doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
ballot_duedate=ballot.duedate,
ballot_expired=ballot_expired,
needed_ballot_positions=needed_bps,
))
def email_irsg_ballot_issued(request, doc, ballot):
"""Send email notification when IRSG ballot is issued"""
return _send_irsg_ballot_email(
request,
doc,
ballot,
'IRSG ballot issued: %s to %s'%(doc.file_tag(), std_level_prompt(doc)),
'doc/mail/issue_irsg_ballot_mail.txt',
)
def email_irsg_ballot_closed(request, doc, ballot):
"""Send email notification when IRSG ballot is closed"""
return _send_irsg_ballot_email(
request,
doc,
ballot,
'IRSG ballot closed: %s to %s'%(doc.file_tag(), std_level_prompt(doc)),
"doc/mail/close_irsg_ballot_mail.txt",
)
def email_iana(request, doc, to, msg, cc=None):
# fix up message and send it with extra info on doc in headers
import email

View file

@ -334,10 +334,22 @@ class BaseManipulationTests():
self.assertEqual(r.status_code, 302)
self.assertIsNone(draft.ballot_open('irsg-approve'))
# No notifications should have been generated yet
self.assertEqual(len(outbox), 0)
r = self.client.post(url,{'irsg_button':'Yes', 'duedate':due })
self.assertEqual(r.status_code,302)
self.assertIsNotNone(draft.ballot_open('irsg-approve'))
self.assertEqual(len(outbox),0)
# Should have sent a notification about the new ballot
self.assertEqual(len(outbox), 1)
msg = outbox[0]
self.assertIn('IRSG ballot issued', msg['Subject'])
self.assertIn('iesg-secretary@ietf.org', msg['From'])
# Notifications are also sent to various doc-related addresses, not tested here
self.assertIn('irsg@irtf.org', msg['To'])
self.assertIn('irtf-chair@irtf.org', msg['CC'])
self.assertIn(str(due), get_payload_text(msg)) # ensure duedate is included
def test_take_and_email_position(self):
draft = RgDraftFactory()
@ -379,11 +391,21 @@ class BaseManipulationTests():
self.assertEqual(r.status_code, 302)
self.assertIsNotNone(draft.ballot_open('irsg-approve'))
# Should not have generated a notification yet
self.assertEqual(len(outbox), 0)
r = self.client.post(url,dict(irsg_button='Yes'))
self.assertEqual(r.status_code, 302)
self.assertIsNone(draft.ballot_open('irsg-approve'))
self.assertEqual(len(outbox), 0)
# Closing the ballot should have generated a notification
self.assertEqual(len(outbox), 1)
msg = outbox[0]
self.assertIn('IRSG ballot closed', msg['Subject'])
self.assertIn('iesg-secretary@ietf.org', msg['From'])
# Notifications are also sent to various doc-related addresses, not tested here
self.assertIn('irsg@irtf.org', msg['To'])
self.assertIn('irtf-chair@irtf.org', msg['CC'])
def test_view_outstanding_ballots(self):
draft = RgDraftFactory()

View file

@ -235,7 +235,7 @@ def default_review_text(group, charter, by):
def generate_issue_ballot_mail(request, doc, ballot):
addrs=gather_address_lists('ballot_issued',doc=doc).as_strings()
addrs=gather_address_lists('iesg_ballot_issued',doc=doc).as_strings()
return render_to_string("doc/charter/issue_ballot_mail.txt",
dict(doc=doc,

View file

@ -26,7 +26,7 @@ from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ba
from ietf.doc.mails import ( email_ballot_deferred, email_ballot_undeferred,
extra_automation_headers, generate_last_call_announcement,
generate_issue_ballot_mail, generate_ballot_writeup, generate_ballot_rfceditornote,
generate_approval_mail )
generate_approval_mail, email_irsg_ballot_closed, email_irsg_ballot_issued )
from ietf.doc.lastcall import request_last_call
from ietf.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, role_required, is_authorized_in_doc_stream
@ -635,7 +635,7 @@ def ballot_writeupnotes(request, name):
msg = generate_issue_ballot_mail(request, doc, ballot)
addrs = gather_address_lists('ballot_issued',doc=doc).as_strings()
addrs = gather_address_lists('iesg_ballot_issued',doc=doc).as_strings()
override = {'To':addrs.to}
if addrs.cc:
override['CC'] = addrs.cc
@ -1095,6 +1095,8 @@ def issue_irsg_ballot(request, name):
prev_tags = []
new_tags = []
email_irsg_ballot_issued(request, doc, ballot=e) # Send notification email
if doc.type_id == 'draft':
new_state = State.objects.get(used=True, type="draft-stream-irtf", slug='irsgpoll')
@ -1130,7 +1132,10 @@ def close_irsg_ballot(request, name):
if request.method == 'POST':
button = request.POST.get("irsg_button")
if button == 'Yes':
close_ballot(doc, by, "irsg-approve")
ballot = close_ballot(doc, by, "irsg-approve")
email_irsg_ballot_closed(request,
doc=doc,
ballot=IRSGBallotDocEvent.objects.get(pk=ballot.pk))
return HttpResponseRedirect(doc.get_absolute_url())

View file

@ -147,7 +147,7 @@ def send_conflict_eval_email(request,review):
doc_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
)
)
addrs = gather_address_lists('ballot_issued',doc=review).as_strings()
addrs = gather_address_lists('iesg_ballot_issued',doc=review).as_strings()
override = {'To':addrs.to}
if addrs.cc:
override['Cc']=addrs.cc

View file

@ -129,7 +129,7 @@ def send_status_change_eval_email(request,doc):
doc_url = settings.IDTRACKER_BASE_URL+doc.get_absolute_url(),
)
)
addrs = gather_address_lists('ballot_issued',doc=doc)
addrs = gather_address_lists('iesg_ballot_issued',doc=doc)
override = {'To':addrs.to }
if addrs.cc:
override['Cc'] = addrs.cc

View file

@ -0,0 +1,57 @@
# Copyright The IETF Trust 2019-2020, All Rights Reserved
# -*- coding: utf-8 -*-
from django.db import migrations
def replace_mailtrigger(MailTrigger, old_slug, new_slug):
"""Replace a MailTrigger with an equivalent using a different slug"""
# Per 0013_add_irsg_ballot_saved.py, can't just modify the existing because that
# will lose the many-to-many relations.
orig_mailtrigger = MailTrigger.objects.get(slug=old_slug)
new_mailtrigger = MailTrigger.objects.create(slug=new_slug)
new_mailtrigger.to.set(orig_mailtrigger.to.all())
new_mailtrigger.cc.set(orig_mailtrigger.cc.all())
new_mailtrigger.desc = orig_mailtrigger.desc
new_mailtrigger.save()
orig_mailtrigger.delete() # get rid of the obsolete MailTrigger
def forward(apps, schema_editor):
"""Forward migration: create irsg_ballot_issued and rename ballot_issued to iesg_ballot_issued"""
# Load historical models
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
Recipient = apps.get_model('mailtrigger', 'Recipient')
# Create the new MailTrigger
irsg_ballot_issued = MailTrigger.objects.create(
slug='irsg_ballot_issued',
desc='Recipients when a new IRSG ballot is issued',
)
irsg_ballot_issued.to.set(Recipient.objects.filter(slug='irsg'))
irsg_ballot_issued.cc.set(Recipient.objects.filter(slug__in=[
'doc_stream_manager', 'doc_affecteddoc_authors', 'doc_affecteddoc_group_chairs',
'doc_affecteddoc_notify', 'doc_authors', 'doc_group_chairs', 'doc_group_mail_list',
'doc_notify', 'doc_shepherd'
]))
# Replace existing 'ballot_issued' object with an 'iesg_ballot_issued'
replace_mailtrigger(MailTrigger, 'ballot_issued', 'iesg_ballot_issued')
def reverse(apps, shema_editor):
"""Reverse migration: rename iesg_ballot_issued to ballot_issued and remove irsg_ballot_issued"""
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
MailTrigger.objects.filter(slug='irsg_ballot_issued').delete()
replace_mailtrigger(MailTrigger, 'iesg_ballot_issued', 'ballot_issued')
class Migration(migrations.Migration):
dependencies = [
('mailtrigger', '0013_add_irsg_ballot_saved'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -3264,18 +3264,6 @@
"model": "mailtrigger.mailtrigger",
"pk": "ballot_ednote_changed_late"
},
{
"fields": {
"cc": [],
"desc": "Recipients when a ballot is issued",
"to": [
"iesg",
"iesg_secretary"
]
},
"model": "mailtrigger.mailtrigger",
"pk": "ballot_issued"
},
{
"fields": {
"cc": [],
@ -3686,6 +3674,18 @@
"model": "mailtrigger.mailtrigger",
"pk": "group_personnel_change"
},
{
"fields": {
"cc": [],
"desc": "Recipients when a ballot is issued",
"to": [
"iesg",
"iesg_secretary"
]
},
"model": "mailtrigger.mailtrigger",
"pk": "iesg_ballot_issued"
},
{
"fields": {
"cc": [
@ -3798,6 +3798,27 @@
"model": "mailtrigger.mailtrigger",
"pk": "ipr_posting_confirmation"
},
{
"fields": {
"cc": [
"doc_affecteddoc_authors",
"doc_affecteddoc_group_chairs",
"doc_affecteddoc_notify",
"doc_authors",
"doc_group_chairs",
"doc_group_mail_list",
"doc_notify",
"doc_shepherd",
"doc_stream_manager"
],
"desc": "Recipients when a new IRSG ballot is issued",
"to": [
"irsg"
]
},
"model": "mailtrigger.mailtrigger",
"pk": "irsg_ballot_issued"
},
{
"fields": {
"cc": [

View file

@ -0,0 +1,3 @@
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %} {% filter wordwrap:78 %}The IRSG ballot for {{ doc.file_tag }} has been closed. The evaluation for this document can be found at {{ doc_url }}{% endfilter %}
{% endautoescape%}

View file

@ -0,0 +1,7 @@
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %}{% filter wordwrap:78 %}Evaluation for {{ doc.file_tag }} can be found at {{ doc_url }}
Ballot expire{% if ballot_expired %}d{% else %}s{% endif %} on: {{ ballot_duedate }}
{% endfilter %}
{% filter wordwrap:78 %}{{ needed_ballot_positions }}{% endfilter %}
{% endautoescape%}