Merged in [12482] from rjsparks@nostrum.com:
Improves control of email headers for review summary messages. Provides team-specific templates for review summary messages. Fixes #2092 and #2082. - Legacy-Id: 12483 Note: SVN reference [12482] has been migrated to Git commit 06179c7485dac6c69b2aa30e66095a23a4460209
This commit is contained in:
parent
ef301c0f31
commit
83a260c6c0
152
ietf/dbtemplate/migrations/0003_review_summary_email.py
Normal file
152
ietf/dbtemplate/migrations/0003_review_summary_email.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
Group = apps.get_model('group','Group')
|
||||
|
||||
DBTemplate.objects.create(
|
||||
path='/group/defaults/email/open_assignments.txt',
|
||||
title='Default template for review team open assignment summary email',
|
||||
type_id='django',
|
||||
group=None,
|
||||
content="""{% autoescape off %}Subject: Open review assignments in {{group.acronym}}
|
||||
|
||||
The following reviewers have assignments:{% for r in review_requests %}{% ifchanged r.section %}
|
||||
|
||||
{{r.section}}
|
||||
|
||||
{% if r.section == 'Early review requests:' %}Reviewer Due Draft{% else %}Reviewer LC end Draft{% endif %}{% endifchanged %}
|
||||
{{ r.reviewer.person.plain_name|ljust:"22" }} {% if r.section == 'Early review requests:' %}{{ r.deadline|date:"Y-m-d" }}{% else %}{{ r.lastcall_ends|default:"None " }}{% endif %} {{ r.doc_id }}-{% if r.requested_rev %}{{ r.requested_rev }}{% else %}{{ r.doc.rev }}{% endif %} {{ r.earlier_review_mark }}{% endfor %}
|
||||
|
||||
* Other revision previously reviewed
|
||||
** This revision already reviewed
|
||||
|
||||
{% if rotation_list %}Next in the reviewer rotation:
|
||||
|
||||
{% for p in rotation_list %} {{ p }}
|
||||
{% endfor %}{% endif %}{% endautoescape %}
|
||||
"""
|
||||
)
|
||||
|
||||
DBTemplate.objects.create(
|
||||
path='/group/genart/email/open_assignments.txt',
|
||||
title='Genart open assignment summary',
|
||||
type_id='django',
|
||||
group=Group.objects.get(acronym='genart'),
|
||||
content="""{% autoescape off %}Subject: Review Assignments
|
||||
|
||||
Hi all,
|
||||
|
||||
The following reviewers have assignments:{% for r in review_requests %}{% ifchanged r.section %}
|
||||
|
||||
{{r.section}}
|
||||
|
||||
{% if r.section == 'Early review requests:' %}Reviewer Due Draft{% else %}Reviewer LC end Draft{% endif %}{% endifchanged %}
|
||||
{{ r.reviewer.person.plain_name|ljust:"22" }} {% if r.section == 'Early review requests:' %}{{ r.deadline|date:"Y-m-d" }}{% else %}{{ r.lastcall_ends|default:"None " }}{% endif %} {{ r.doc_id }}-{% if r.requested_rev %}{{ r.requested_rev }}{% else %}{{ r.doc.rev }}{% endif %} {{ r.earlier_review_mark }}{% endfor %}
|
||||
|
||||
* Other revision previously reviewed
|
||||
** This revision already reviewed
|
||||
|
||||
{% if rotation_list %}Next in the reviewer rotation:
|
||||
|
||||
{% for p in rotation_list %} {{ p }}
|
||||
{% endfor %}{% endif %}
|
||||
The LC and Telechat review templates are included below:
|
||||
-------------------------------------------------------
|
||||
|
||||
-- Begin LC Template --
|
||||
I am the assigned Gen-ART reviewer for this draft. The General Area
|
||||
Review Team (Gen-ART) reviews all IETF documents being processed
|
||||
by the IESG for the IETF Chair. Please treat these comments just
|
||||
like any other last call comments.
|
||||
|
||||
For more information, please see the FAQ at
|
||||
|
||||
<https://trac.ietf.org/trac/gen/wiki/GenArtfaq>.
|
||||
|
||||
Document:
|
||||
Reviewer:
|
||||
Review Date:
|
||||
IETF LC End Date:
|
||||
IESG Telechat date: (if known)
|
||||
|
||||
Summary:
|
||||
|
||||
Major issues:
|
||||
|
||||
Minor issues:
|
||||
|
||||
Nits/editorial comments:
|
||||
|
||||
-- End LC Template --
|
||||
|
||||
-- Begin Telechat Template --
|
||||
I am the assigned Gen-ART reviewer for this draft. The General Area
|
||||
Review Team (Gen-ART) reviews all IETF documents being processed
|
||||
by the IESG for the IETF Chair. Please wait for direction from your
|
||||
document shepherd or AD before posting a new version of the draft.
|
||||
|
||||
For more information, please see the FAQ at
|
||||
|
||||
<https://trac.ietf.org/trac/gen/wiki/GenArtfaq>.
|
||||
|
||||
Document:
|
||||
Reviewer:
|
||||
Review Date:
|
||||
IETF LC End Date:
|
||||
IESG Telechat date: (if known)
|
||||
|
||||
Summary:
|
||||
|
||||
Major issues:
|
||||
|
||||
Minor issues:
|
||||
|
||||
Nits/editorial comments:
|
||||
|
||||
-- End Telechat Template --
|
||||
{% endautoescape %}
|
||||
"""
|
||||
)
|
||||
|
||||
DBTemplate.objects.create(
|
||||
path='/group/secdir/email/open_assignments.txt',
|
||||
title='Secdir open assignment summary',
|
||||
type_id='django',
|
||||
group=Group.objects.get(acronym='secdir'),
|
||||
content="""{% autoescape off %}Subject: Assignments
|
||||
|
||||
Review instructions and related resources are at:
|
||||
http://tools.ietf.org/area/sec/trac/wiki/SecDirReview{% for r in review_requests %}{% ifchanged r.section %}
|
||||
|
||||
{{r.section}}
|
||||
|
||||
{% if r.section == 'Early review requests:' %}Reviewer Due Draft{% else %}Reviewer LC end Draft{% endif %}{% endifchanged %}
|
||||
{{ r.reviewer.person.plain_name|ljust:"22" }}{{ r.earlier_review|yesno:'R, , ' }}{% if r.section == 'Early review requests:' %}{{ r.deadline|date:"Y-m-d" }}{% else %}{{ r.lastcall_ends|default:"None " }}{% endif %} {{ r.doc_id }}-{% if r.requested_rev %}{{ r.requested_rev }}{% else %}{{ r.doc.rev }}{% endif %}{% endfor %}
|
||||
|
||||
{% if rotation_list %}Next in the reviewer rotation:
|
||||
|
||||
{% for p in rotation_list %} {{ p }}
|
||||
{% endfor %}{% endif %}{% endautoescape %}
|
||||
"""
|
||||
)
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
DBTemplate = apps.get_model('dbtemplate','DBTemplate')
|
||||
DBTemplate.objects.filter(path__in=['/group/defaults/email/open_assignments.txt',
|
||||
'/group/genart/email/open_assignments.txt',
|
||||
'/group/secdir/email/open_assignments.txt',]).delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dbtemplate', '0002_auto_20141222_1749'),
|
||||
('group', '0009_auto_20150930_0758'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward,reverse)
|
||||
]
|
|
@ -19,6 +19,7 @@ from ietf.review.utils import (
|
|||
from ietf.name.models import ReviewTypeName, ReviewResultName, ReviewRequestStateName
|
||||
import ietf.group.views_review
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.dbtemplate.factories import DBTemplateFactory
|
||||
|
||||
class ReviewTests(TestCase):
|
||||
def test_review_requests(self):
|
||||
|
@ -284,6 +285,19 @@ class ReviewTests(TestCase):
|
|||
def test_email_open_review_assignments(self):
|
||||
doc = make_test_data()
|
||||
review_req1 = make_review_data(doc)
|
||||
DBTemplateFactory.create(path='/group/defaults/email/open_assignments.txt',
|
||||
type_id='django',
|
||||
content = """
|
||||
{% autoescape off %}
|
||||
Reviewer Deadline Draft
|
||||
{% for r in review_requests %}{{ r.reviewer.person.plain_name|ljust:"22" }} {{ r.deadline|date:"Y-m-d" }} {{ r.doc_id }}-{% if r.requested_rev %}{{ r.requested_rev }}{% else %}{{ r.doc.rev }}{% endif %}
|
||||
{% endfor %}
|
||||
{% if rotation_list %}Next in the reviewer rotation:
|
||||
|
||||
{% for p in rotation_list %} {{ p }}
|
||||
{% endfor %}{% endif %}
|
||||
{% endautoescape %}
|
||||
""")
|
||||
|
||||
group = review_req1.team
|
||||
|
||||
|
@ -302,14 +316,20 @@ class ReviewTests(TestCase):
|
|||
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
"to": group.list_email,
|
||||
"to": 'toaddr@bogus.test',
|
||||
"cc": 'ccaddr@bogus.test',
|
||||
"reply_to": 'replytoaddr@bogus.test',
|
||||
"frm" : 'fromaddr@bogus.test',
|
||||
"subject": "Test subject",
|
||||
"body": "Test body",
|
||||
"action": "email",
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue(group.list_email in outbox[0]["To"])
|
||||
self.assertTrue('toaddr' in outbox[0]["To"])
|
||||
self.assertTrue('ccaddr' in outbox[0]["Cc"])
|
||||
self.assertTrue('replytoaddr' in outbox[0]["Reply-To"])
|
||||
self.assertTrue('fromaddr' in outbox[0]["From"])
|
||||
self.assertEqual(outbox[0]["subject"], "Test subject")
|
||||
self.assertTrue("Test body" in outbox[0].get_payload(decode=True).decode("utf-8"))
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import datetime, math
|
||||
from collections import defaultdict
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.db.models import Max
|
||||
from django import forms
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
@ -23,13 +26,17 @@ from ietf.review.utils import (can_manage_review_requests_for_team,
|
|||
reviewer_rotation_list,
|
||||
latest_review_requests_for_reviewers,
|
||||
augment_review_requests_with_events)
|
||||
from ietf.doc.models import LastCallDocEvent
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import get_group_or_404, construct_group_menu_context
|
||||
from ietf.person.fields import PersonEmailChoiceField
|
||||
from ietf.name.models import ReviewRequestStateName
|
||||
from ietf.utils.mail import send_mail_text
|
||||
from ietf.utils.fields import DatepickerDateField
|
||||
from ietf.utils.mail import send_mail_text, parse_preformatted
|
||||
from ietf.utils.fields import DatepickerDateField, MultiEmailField
|
||||
from ietf.ietfauth.utils import user_is_person
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.mailtrigger.models import Recipient
|
||||
|
||||
def get_open_review_requests_for_team(team, assignment_status=None):
|
||||
open_review_requests = ReviewRequest.objects.filter(
|
||||
|
@ -327,7 +334,10 @@ def manage_review_requests(request, acronym, group_type=None, assignment_status=
|
|||
})
|
||||
|
||||
class EmailOpenAssignmentsForm(forms.Form):
|
||||
to = forms.EmailField(widget=forms.EmailInput(attrs={ "readonly": True }))
|
||||
frm = forms.CharField(label="From", widget=forms.EmailInput(attrs={"readonly":True}))
|
||||
to = MultiEmailField()
|
||||
cc = MultiEmailField(required=False)
|
||||
reply_to = MultiEmailField(required=False)
|
||||
subject = forms.CharField()
|
||||
body = forms.CharField(widget=forms.Textarea)
|
||||
|
||||
|
@ -345,7 +355,32 @@ def email_open_review_assignments(request, acronym, group_type=None):
|
|||
state__in=("requested", "accepted"),
|
||||
).exclude(
|
||||
reviewer=None,
|
||||
).prefetch_related("reviewer", "type", "state", "doc").distinct().order_by("deadline", "reviewer"))
|
||||
).prefetch_related("reviewer", "type", "state", "doc").distinct().order_by("reviewer","-deadline"))
|
||||
|
||||
review_requests.sort(key=lambda r:r.reviewer.person.last_name()+r.reviewer.person.first_name())
|
||||
|
||||
for r in review_requests:
|
||||
if r.doc.telechat_date():
|
||||
r.section = 'For telechat %s' % r.doc.telechat_date().isoformat()
|
||||
r.section_order='0'+r.section
|
||||
elif r.type_id == 'early':
|
||||
r.section = 'Early review requests:'
|
||||
r.section_order='2'
|
||||
else:
|
||||
r.section = 'Last calls:'
|
||||
r.section_order='1'
|
||||
e = r.doc.latest_event(LastCallDocEvent, type="sent_last_call")
|
||||
r.lastcall_ends = e and e.expires.date().isoformat()
|
||||
r.earlier_review = ReviewRequest.objects.filter(doc=r.doc,reviewer__in=r.reviewer.person.email_set.all(),state="completed")
|
||||
if r.earlier_review:
|
||||
req_rev = r.requested_rev or r.doc.rev
|
||||
earlier_review_rev = r.earlier_review.aggregate(Max('reviewed_rev'))['reviewed_rev__max']
|
||||
if req_rev == earlier_review_rev:
|
||||
r.earlier_review_mark = '**'
|
||||
else:
|
||||
r.earlier_review_mark = '*'
|
||||
|
||||
review_requests.sort(key=lambda r: r.section_order)
|
||||
|
||||
back_url = request.GET.get("next")
|
||||
if not back_url:
|
||||
|
@ -359,20 +394,35 @@ def email_open_review_assignments(request, acronym, group_type=None):
|
|||
if request.method == "POST" and request.POST.get("action") == "email":
|
||||
form = EmailOpenAssignmentsForm(request.POST)
|
||||
if form.is_valid():
|
||||
send_mail_text(request, form.cleaned_data["to"], None, form.cleaned_data["subject"], form.cleaned_data["body"])
|
||||
|
||||
send_mail_text(request, form.cleaned_data["to"], form.cleaned_data["frm"], form.cleaned_data["subject"], form.cleaned_data["body"],cc=form.cleaned_data["cc"],extra={"Reply-to":", ".join(form.cleaned_data["reply_to"])})
|
||||
return HttpResponseRedirect(back_url)
|
||||
else:
|
||||
to = group.list_email
|
||||
subject = "Open review assignments in {}".format(group.acronym)
|
||||
(to,cc) = gather_address_lists('review_assignments_summarized',group=group)
|
||||
reply_to = Recipient.objects.get(slug='group_secretaries').gather(group=group)
|
||||
frm = request.user.person.formatted_email()
|
||||
|
||||
body = render_to_string("group/email_open_review_assignments.txt", {
|
||||
templateqs = DBTemplate.objects.filter(path="/group/%s/email/open_assignments.txt" % group.acronym)
|
||||
if templateqs.exists():
|
||||
template = templateqs.first()
|
||||
else:
|
||||
template = DBTemplate.objects.get(path="/group/defaults/email/open_assignments.txt")
|
||||
|
||||
partial_msg = render_to_string(template.path, {
|
||||
"review_requests": review_requests,
|
||||
"rotation_list": reviewer_rotation_list(group)[:10],
|
||||
"group" : group,
|
||||
})
|
||||
|
||||
(msg,_,_) = parse_preformatted(partial_msg)
|
||||
|
||||
body = msg.get_payload()
|
||||
subject = msg['Subject']
|
||||
|
||||
form = EmailOpenAssignmentsForm(initial={
|
||||
"to": to,
|
||||
"to": ", ".join(to),
|
||||
"cc": ", ".join(cc),
|
||||
"reply_to": ", ".join(reply_to),
|
||||
"frm": frm,
|
||||
"subject": subject,
|
||||
"body": body,
|
||||
})
|
||||
|
|
30
ietf/mailtrigger/migrations/0008_review_summary_triggers.py
Normal file
30
ietf/mailtrigger/migrations/0008_review_summary_triggers.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
|
||||
annc = MailTrigger.objects.create(
|
||||
slug='review_assignments_summarized',
|
||||
desc='Recipients when an review team secretary send a summary of open review assignments',
|
||||
)
|
||||
annc.to = Recipient.objects.filter(slug__in=['group_mail_list',])
|
||||
annc.cc = []
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
MailTrigger.objects.filter(slug__in=['review_assignments_summarized']).delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0007_add_interim_announce'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
|
@ -1641,6 +1641,16 @@
|
|||
"model": "name.liaisonstatementeventtypename",
|
||||
"pk": "private_comment"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 11,
|
||||
"used": true,
|
||||
"name": "Re-sent",
|
||||
"desc": ""
|
||||
},
|
||||
"model": "name.liaisonstatementeventtypename",
|
||||
"pk": "resent"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 1,
|
||||
|
@ -1891,6 +1901,16 @@
|
|||
"model": "name.reviewrequeststatename",
|
||||
"pk": "completed"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 20,
|
||||
"used": false,
|
||||
"name": "Unknown",
|
||||
"desc": ""
|
||||
},
|
||||
"model": "name.reviewrequeststatename",
|
||||
"pk": "unknown"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 1,
|
||||
|
@ -2466,7 +2486,7 @@
|
|||
"order": 3,
|
||||
"used": true,
|
||||
"name": "IRTF",
|
||||
"desc": "Independent Submission Editor stream"
|
||||
"desc": "IRTF Stream"
|
||||
},
|
||||
"model": "name.streamname",
|
||||
"pk": "irtf"
|
||||
|
@ -6199,6 +6219,17 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "resurrection_requested"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [],
|
||||
"to": [
|
||||
"group_mail_list"
|
||||
],
|
||||
"desc": "Recipients when an review team secretary send a summary of open review assignments"
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "review_assignments_summarized"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
{% buttons %}
|
||||
<a href="{{ back_url }}" class="btn btn-default pull-right">Cancel</a>
|
||||
<button class="btn btn-primary" type="submit" name="action" value="email">Send to team mailing list</button>
|
||||
<button class="btn btn-primary" type="submit" name="action" value="email">Send</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% else %}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{% autoescape off %}
|
||||
Reviewer Deadline Draft
|
||||
{% for r in review_requests %}{{ r.reviewer.person.plain_name|ljust:"22" }} {{ r.deadline|date:"Y-m-d" }} {{ r.doc_id }}-{% if r.requested_rev %}{{ r.requested_rev }}{% else %}{{ r.doc.rev }}{% endif %}
|
||||
{% endfor %}
|
||||
{% if rotation_list %}Next in the reviewer rotation:
|
||||
|
||||
{% for p in rotation_list %} {{ p }}
|
||||
{% endfor %}{% endif %}{% endautoescape %}
|
Loading…
Reference in a new issue