Instrumented issuing ballots. Simplified the ballot-issued email significantly. Deferred adding ballot_saved mail for the automatic yes positions.

- Legacy-Id: 10068
This commit is contained in:
Robert Sparks 2015-08-28 17:33:49 +00:00
parent f68b46972e
commit e5306eda00
12 changed files with 79 additions and 217 deletions

View file

@ -10,9 +10,8 @@ from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail, send_mail_text
from ietf.ipr.utils import iprs_from_docs, related_docs
from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
from ietf.doc.utils import needed_ballot_positions, get_document_content
from ietf.person.models import Person
from ietf.group.models import Role
from ietf.doc.models import Document
from ietf.mailtoken.utils import gather_address_lists
@ -308,66 +307,14 @@ def email_ballot_undeferred(request, doc, by, telechat_date):
cc=cc)
def generate_issue_ballot_mail(request, doc, ballot):
active_ads = Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area").distinct()
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).order_by("-time", '-id').select_related('ad')
# format positions and setup discusses and comments
ad_feedback = []
seen = set()
active_ad_positions = []
inactive_ad_positions = []
for p in positions:
if p.ad in seen:
continue
seen.add(p.ad)
def formatted(val):
if val:
return "[ X ]"
else:
return "[ ]"
fmt = u"%-21s%-10s%-11s%-9s%-10s" % (
p.ad.plain_name()[:21],
formatted(p.pos_id == "yes"),
formatted(p.pos_id == "noobj"),
formatted(p.pos_id == "discuss"),
"[ R ]" if p.pos_id == "recuse" else formatted(p.pos_id == "abstain"),
)
if p.ad in active_ads:
active_ad_positions.append(fmt)
if not p.pos_id == "discuss":
p.discuss = ""
if p.comment or p.discuss:
ad_feedback.append(p)
else:
inactive_ad_positions.append(fmt)
active_ad_positions.sort()
inactive_ad_positions.sort()
ad_feedback.sort(key=lambda p: p.ad.plain_name())
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
last_call_expires = e.expires if e else None
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
approval_text = e.text if e else ""
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
ballot_writeup = e.text if e else ""
return render_to_string("doc/mail/issue_ballot_mail.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
active_ad_positions=active_ad_positions,
inactive_ad_positions=inactive_ad_positions,
ad_feedback=ad_feedback,
last_call_expires=last_call_expires,
approval_text=approval_text,
ballot_writeup=ballot_writeup,
needed_ballot_positions=
needed_ballot_positions(doc,
doc.active_ballot().active_ad_positions().values()

View file

@ -12,7 +12,7 @@ from ietf.name.models import BallotPositionName
from ietf.iesg.models import TelechatDate
from ietf.person.models import Person
from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox
from ietf.utils.mail import outbox, empty_outbox
from ietf.utils.test_data import make_test_data
from ietf.utils.test_utils import login_testing_unauthorized
@ -278,36 +278,8 @@ class BallotWriteupsTests(TestCase):
url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "ad", url)
ballot = draft.latest_event(BallotDocEvent, type="created_ballot")
def create_pos(num, vote, comment="", discuss=""):
ad = Person.objects.get(name="Ad No%s" % num)
e = BallotPositionDocEvent()
e.doc = draft
e.ballot = ballot
e.by = ad
e.ad = ad
e.pos = BallotPositionName.objects.get(slug=vote)
e.type = "changed_ballot_position"
e.comment = comment
if e.comment:
e.comment_time = datetime.datetime.now()
e.discuss = discuss
if e.discuss:
e.discuss_time = datetime.datetime.now()
e.save()
# active
create_pos(1, "yes", discuss="discuss1 " * 20)
create_pos(2, "noobj", comment="comment2 " * 20)
create_pos(3, "discuss", discuss="discuss3 " * 20, comment="comment3 " * 20)
create_pos(4, "abstain")
create_pos(5, "recuse")
# inactive
create_pos(9, "yes")
mailbox_before = len(outbox)
empty_outbox()
r = self.client.post(url, dict(
ballot_writeup="This is a test.",
@ -316,15 +288,12 @@ class BallotWriteupsTests(TestCase):
draft = Document.objects.get(name=draft.name)
self.assertTrue(draft.latest_event(type="sent_ballot_announcement"))
self.assertEqual(len(outbox), mailbox_before + 2)
issue_email = outbox[-2]
self.assertTrue("Evaluation:" in issue_email['Subject'])
self.assertTrue("comment1" not in str(issue_email))
self.assertTrue("comment2" in str(issue_email))
self.assertTrue("comment3" in str(issue_email))
self.assertTrue("discuss3" in str(issue_email))
self.assertTrue("This is a test" in str(issue_email))
self.assertTrue("The IESG has approved" in str(issue_email))
self.assertEqual(len(outbox), 2)
self.assertTrue('Evaluation:' in outbox[-2]['Subject'])
self.assertTrue('iesg@' in outbox[-2]['To'])
self.assertTrue('Evaluation:' in outbox[-1]['Subject'])
self.assertTrue('drafts-eval@' in outbox[-1]['To'])
self.assertTrue('X-IETF-Draft-string' in outbox[-1])
def test_edit_approval_text(self):
draft = make_test_data()

View file

@ -3,8 +3,7 @@ import re, datetime, os
from django.template.loader import render_to_string
from django.conf import settings
from ietf.doc.models import NewRevisionDocEvent, WriteupDocEvent, BallotPositionDocEvent
from ietf.person.models import Person
from ietf.doc.models import NewRevisionDocEvent, WriteupDocEvent
from ietf.utils.history import find_history_active_at
from ietf.utils.mail import parse_preformatted
from ietf.mailtoken.utils import gather_address_lists
@ -168,67 +167,14 @@ def default_review_text(group, charter, by):
return (e1,e2)
def generate_issue_ballot_mail(request, doc, ballot):
active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active", email__role__group__type="area").distinct()
seen = []
positions = []
for p in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).order_by("-time", '-id').select_related('ad'):
if p.ad not in seen:
positions.append(p)
seen.append(p.ad)
# format positions and setup blocking and non-blocking comments
ad_feedback = []
seen = set()
active_ad_positions = []
inactive_ad_positions = []
for p in positions:
if p.ad in seen:
continue
seen.add(p.ad)
def formatted(val):
if val:
return "[ X ]"
else:
return "[ ]"
fmt = u"%-21s%-6s%-6s%-8s%-7s" % (
p.ad.plain_name(),
formatted(p.pos_id == "yes"),
formatted(p.pos_id == "no"),
formatted(p.pos_id == "block"),
formatted(p.pos_id == "abstain"),
)
if p.ad in active_ads:
active_ad_positions.append(fmt)
if not p.pos or not p.pos.blocking:
p.discuss = ""
if p.comment or p.discuss:
ad_feedback.append(p)
else:
inactive_ad_positions.append(fmt)
active_ad_positions.sort()
inactive_ad_positions.sort()
ad_feedback.sort(key=lambda p: p.ad.plain_name())
e = doc.latest_event(WriteupDocEvent, type="changed_action_announcement")
approval_text = e.text if e else ""
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
ballot_writeup = e.text if e else ""
addrs=gather_address_lists('ballot_issued',doc=doc).as_strings()
return render_to_string("doc/charter/issue_ballot_mail.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
active_ad_positions=active_ad_positions,
inactive_ad_positions=inactive_ad_positions,
ad_feedback=ad_feedback,
approval_text=approval_text,
ballot_writeup=ballot_writeup,
to = addrs.to,
cc = addrs.cc,
)
)

View file

@ -539,12 +539,24 @@ def ballot_writeupnotes(request, name):
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
pos.save()
# Consider mailing this position to 'ballot_saved'
approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not approval:
approval = generate_approval_mail(request, doc)
msg = generate_issue_ballot_mail(request, doc, ballot)
send_mail_preformatted(request, msg)
addrs = gather_address_lists('ballot_issued',doc=doc).as_strings()
override = {'To':addrs.to}
if addrs.cc:
override['CC'] = addrs.cc
send_mail_preformatted(request, msg, override=override)
addrs = gather_address_lists('ballot_issued_iana',doc=doc).as_strings()
override={ "To": "IANA <%s>"%settings.IANA_EVAL_EMAIL, "Bcc": None , "Reply-To": None}
if addrs.cc:
override['CC'] = addrs.cc
send_mail_preformatted(request, msg, extra=extra_automation_headers(doc),
override={ "To": "IANA <%s>"%settings.IANA_EVAL_EMAIL, "CC": None, "Bcc": None , "Reply-To": None})

View file

@ -622,6 +622,7 @@ def ballot_writeupnotes(request, name):
pos.pos_id = "yes"
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
pos.save()
# Consider mailing this position to 'ballot_saved'
msg = generate_issue_ballot_mail(request, charter, ballot)
send_mail_preformatted(request, msg)

View file

@ -69,6 +69,7 @@ def change_state(request, name, option=None):
pos.pos_id = "yes"
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
pos.save()
# Consider mailing that position to 'ballot_saved'
send_conflict_eval_email(request,review)
@ -114,11 +115,17 @@ def send_conflict_eval_email(request,review):
doc_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
)
)
send_mail_preformatted(request,msg)
addrs = gather_address_lists('ballot_issued',doc=review).as_strings()
override = {'To':addrs.to}
if addrs.cc:
override['Cc']=addrs.cc
send_mail_preformatted(request,msg,override=override)
addrs = gather_address_lists('ballot_issued_iana',doc=review).as_strings()
email_iana(request,
review.relateddocument_set.get(relationship__slug='conflrev').target.document,
settings.IANA_EVAL_EMAIL,
msg)
addrs.to,
msg,
addrs.cc)
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Conflict review response", help_text="Edit the conflict review response.", required=False)

View file

@ -107,7 +107,11 @@ def send_status_change_eval_email(request,doc):
doc_url = settings.IDTRACKER_BASE_URL+doc.get_absolute_url(),
)
)
send_mail_preformatted(request,msg)
addrs = gather_address_lists('ballot_issued',doc=doc)
override = {'To':addrs.to }
if addrs.cc:
override['Cc'] = addrs.cc
send_mail_preformatted(request,msg,override=override)
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Status change text", help_text="Edit the status change text.", required=False)

View file

@ -356,6 +356,14 @@ def make_mailtokens(apps):
],
)
mt_factory(slug='ballot_issued',
desc="Recipients when a ballot is issued",
to_slugs=['iesg',])
mt_factory(slug='ballot_issued_iana',
desc="Recipients for IANA message when a ballot is issued",
to_slugs=['iana_eval',])
mt_factory(slug='last_call_requested',
desc="Recipients when AD requests a last call",
to_slugs=['iesg_secretary',],

View file

@ -4904,6 +4904,28 @@
"model": "mailtoken.mailtoken",
"pk": "ballot_deferred"
},
{
"fields": {
"cc": [],
"to": [
"iesg"
],
"desc": "Recipients when a ballot is issued"
},
"model": "mailtoken.mailtoken",
"pk": "ballot_issued"
},
{
"fields": {
"cc": [],
"to": [
"iana_eval"
],
"desc": "Recipients for IANA message when a ballot is issued"
},
"model": "mailtoken.mailtoken",
"pk": "ballot_issued_iana"
},
{
"fields": {
"cc": [

View file

@ -1,38 +1,10 @@
{% autoescape off %}To: Internet Engineering Steering Group <iesg@ietf.org>
From: IESG Secretary <iesg-secretary@ietf.org>
{% autoescape off %}To: {{ to }} {% if cc %}
Cc: {{ cc }}
{% endif %}From: IESG Secretary <iesg-secretary@ietf.org>
Reply-To: IESG Secretary <iesg-secretary@ietf.org>
Subject: Evaluation: {{ doc.name }}
{% filter wordwrap:73 %}Evaluation for {{ doc.title }} can be found at {{ doc_url }}
{% endfilter %}
Please return the full line with your position.
Yes No Block Abstain
{% for fmt in active_ad_positions %}{{ fmt }}
{% endfor %}{% if inactive_ad_positions %}
{% for fmt in inactive_ad_positions %}{{ fmt }}
{% endfor %}{% endif %}
No "Block" positions, are needed for approval.
BLOCKING AND NON-BLOCKING COMMENTS
==================================
{% filter wordwrap:79 %}{% for p in ad_feedback %}{{ p.ad }}:
{% if p.discuss %}Blocking comment [{{ p.time }}]:
{{ p.discuss }}
{% endif %}{% if p.comment %}Comment [{{ p.time }}]:
{{ p.comment }}
{% endif %}
{% endfor %}{% endfilter %}
---- following is a DRAFT of message to be sent AFTER approval ---
{{ approval_text }}
---- ballot text ----
{{ ballot_writeup }}
{% endautoescape%}

View file

@ -1,5 +1,6 @@
{% load mail_filters %}{% autoescape off %}To: Internet Engineering Steering Group <iesg@ietf.org>
From: IESG Secretary <iesg-secretary@ietf.org>
{% load mail_filters %}{% autoescape off %}To: {{to}} {% if cc %}
Cc: {{cc}}
{%endif%}From: IESG Secretary <iesg-secretary@ietf.org>
Reply-To: IESG Secretary <iesg-secretary@ietf.org>
Subject: Evaluation: {{doc.title}}

View file

@ -1,5 +1,4 @@
{% load mail_filters %}{% autoescape off %}To: Internet Engineering Steering Group <iesg@ietf.org>
From: IESG Secretary <iesg-secretary@ietf.org>
{% load mail_filters %}{% autoescape off %}From: IESG Secretary <iesg-secretary@ietf.org>
Reply-To: IESG Secretary <iesg-secretary@ietf.org>
Subject: Evaluation: {{ doc.file_tag }} to {{ doc|std_level_prompt }}
@ -8,32 +7,6 @@ Subject: Evaluation: {{ doc.file_tag }} to {{ doc|std_level_prompt }}
{% if last_call_expires %}Last call to expire on: {{ last_call_expires }}
{% endif %}{% endfilter %}
Please return the full line with your position.
Yes No-Objection Discuss Abstain
{% for fmt in active_ad_positions %}{{ fmt }}
{% endfor %}{% if inactive_ad_positions %}
{% for fmt in inactive_ad_positions %}{{ fmt }}
{% endfor %}{% endif %}
{% filter wordwrap:73 %}{{ needed_ballot_positions }}{% endfilter %}
DISCUSSES AND COMMENTS
======================
{% filter wordwrap:79 %}{% for pos in ad_feedback %}{{ pos.ad }}:
{% if pos.discuss %}Discuss [{{ pos.discuss_time|date:"Y-m-d" }}]:
{{ pos.discuss }}
{% endif %}{% if pos.comment %}Comment [{{ pos.comment_time|date:"Y-m-d" }}]:
{{ pos.comment }}
{% endif %}
{% endfor %}{% endfilter %}
---- following is a DRAFT of message to be sent AFTER approval ---
{{ approval_text }}{% if ballot_writeup %}
{{ ballot_writeup }}
{% endif %}
{% endautoescape%}