This release improves visibility into where and when the datatracker
sends email. It makes where the datatracker sends email for a given action data driven, and it shows where email will be sent for actions on documents and groups. Commit ready for merge. - Legacy-Id: 10186
This commit is contained in:
commit
1a277b00ec
163
2015-08-28-scrub-notify.py
Normal file
163
2015-08-28-scrub-notify.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, re
|
||||
|
||||
from copy import copy
|
||||
|
||||
import datetime
|
||||
|
||||
# boilerplate
|
||||
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../web/"))
|
||||
sys.path = [ basedir ] + sys.path
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings")
|
||||
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
|
||||
from django.db.models import F
|
||||
from django.template import Template,Context
|
||||
from ietf.doc.models import Document, DocEvent
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.mail import send_mail_text
|
||||
|
||||
def message_body_template():
|
||||
return Template("""{% filter wordwrap:72 %}The Notify field for the document{{ count|pluralize }} listed at the end of this message {% if count > 1 %}were{% else %}was{% endif %} changed by removing the chair, shepherd, author, and similar addresses (in direct or alias form) to the point they could be identified.
|
||||
|
||||
The Datatracker now includes those addresses explicitly in each message it sends as appropriate. You can review where the datatracker sends messages for a given action in general using <https://datatracker.ietf.org/mailtoken/token>. You can review the expansions for a specific document by the new Email expansions tab on the document's page. Note that the addresses included for any given action are much more comprehensive than they were before this release.
|
||||
|
||||
Please review each new Notify field, and help remove any remaining addresses that will normally be copied per the configuration shown at https://datatracker.ietf.org/mailtoken/token. The field should now only contain exceptional addresses - parties you wish to be notified that aren't part of the new normal recipient set.
|
||||
|
||||
You can see exactly how the Notify field was changed for a given document by looking in the document's history.{% endfilter %}
|
||||
|
||||
{% if non_empty%}The document{{non_empty|length|pluralize }} with non-empty new Notify fields are:{% for doc in non_empty %}
|
||||
https://datatracker.ietf.org{{doc.get_absolute_url}}{% endfor %}{% endif %}
|
||||
|
||||
{% if empty%}The document{{non_empty|length|pluralize }} with empty new Notify fields are:{% for doc in empty %}
|
||||
https://datatracker.ietf.org{{doc.get_absolute_url}}{% endfor %}{% endif %}
|
||||
|
||||
""")
|
||||
|
||||
def other_addrs(addr):
|
||||
person = Person.objects.filter(email__address__iexact=addr).first()
|
||||
if not person:
|
||||
return None
|
||||
return [x.lower() for x in person.email_set.values_list('address',flat=True)]
|
||||
|
||||
def prep(item):
|
||||
retval = item.lower()
|
||||
if '<' in retval:
|
||||
if not '>' in retval:
|
||||
raise "Bad item: "+item
|
||||
start=retval.index('<')+1
|
||||
stop=retval.index('>')
|
||||
retval = retval[start:stop]
|
||||
return retval
|
||||
|
||||
def is_management(item, doc):
|
||||
|
||||
item = prep(item)
|
||||
|
||||
if any([
|
||||
item == '%s.chairs@ietf.org'%doc.name,
|
||||
item == '%s.ad@ietf.org'%doc.name,
|
||||
item == '%s.shepherd@ietf.org'%doc.name,
|
||||
item == '%s.chairs@tools.ietf.org'%doc.name,
|
||||
item == '%s.ad@tools.ietf.org'%doc.name,
|
||||
item == '%s.shepherd@tools.ietf.org'%doc.name,
|
||||
doc.ad and item == doc.ad.email_address().lower(),
|
||||
doc.shepherd and item == doc.shepherd.address.lower(),
|
||||
]):
|
||||
return True
|
||||
|
||||
if doc.group:
|
||||
if any([
|
||||
item == '%s-chairs@ietf.org'%doc.group.acronym,
|
||||
item == '%s-ads@ietf.org'%doc.group.acronym,
|
||||
item == '%s-chairs@tools.ietf.org'%doc.group.acronym,
|
||||
item == '%s-ads@tools.ietf.org'%doc.group.acronym,
|
||||
]):
|
||||
return True
|
||||
for addr in doc.group.role_set.filter(name__in=['chair','ad','delegate']).values_list('email__address',flat=True):
|
||||
other = other_addrs(addr)
|
||||
if item == addr.lower() or item in other:
|
||||
return True
|
||||
if doc.group.parent:
|
||||
if item == '%s-ads@ietf.org'%doc.group.parent.acronym or item == '%s-ads@tools.ietf.org'%doc.group.parent.acronym:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_author(item, doc):
|
||||
item = prep(item)
|
||||
|
||||
if item == '%s@ietf.org' % doc.name or item == '%s@tools.ietf.org' % doc.name:
|
||||
return True
|
||||
|
||||
for addr in doc.authors.values_list('address',flat=True):
|
||||
other = other_addrs(addr)
|
||||
if item == addr.lower() or item in other:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
msg_template = message_body_template()
|
||||
by = Person.objects.get(name="(System)")
|
||||
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area"))
|
||||
|
||||
affected = set()
|
||||
empty = dict()
|
||||
non_empty = dict()
|
||||
changed = 0
|
||||
emptied = 0
|
||||
|
||||
qs = Document.objects.exclude(notify__isnull=True).exclude(notify='')
|
||||
for doc in qs:
|
||||
doc.notify = doc.notify.replace(';', ',')
|
||||
items = set([ i.strip() for i in doc.notify.split(',') if i.strip() and '@' in i])
|
||||
original_items = copy(items)
|
||||
for item in original_items:
|
||||
if any([
|
||||
doc.group and doc.group.list_email and item.lower() == doc.group.list_email.lower(),
|
||||
is_management(item,doc),
|
||||
is_author(item,doc),
|
||||
]):
|
||||
items.discard(item)
|
||||
if original_items != items:
|
||||
changed += 1
|
||||
if len(list(items))==0:
|
||||
emptied += 1
|
||||
|
||||
to = []
|
||||
if doc.ad and doc.ad in active_ads:
|
||||
to.append(doc.ad.email_address())
|
||||
if doc.group and doc.group.state_id=='active':
|
||||
to.extend(doc.group.role_set.filter(name__in=['chair','ad']).values_list('email__address',flat=True))
|
||||
if not to:
|
||||
to = ['iesg@ietf.org']
|
||||
|
||||
to = ", ".join(sorted(to))
|
||||
affected.add(to)
|
||||
empty.setdefault(to,[])
|
||||
non_empty.setdefault(to,[])
|
||||
if len(list(items))==0:
|
||||
empty[to].append(doc)
|
||||
else:
|
||||
non_empty[to].append(doc)
|
||||
original_notify = doc.notify
|
||||
new_notify = ', '.join(list(items))
|
||||
doc.notify = new_notify
|
||||
doc.time = datetime.datetime.now()
|
||||
doc.save()
|
||||
e = DocEvent(type="added_comment",doc=doc,time=doc.time,by=by)
|
||||
e.desc = "Notify list changed from %s to %s"% (original_notify, new_notify if new_notify else '(None)')
|
||||
e.save()
|
||||
|
||||
for a in list(affected):
|
||||
|
||||
txt = msg_template.render(Context({'count':len(empty[a])+len(non_empty[a]),'empty':empty[a],'non_empty':non_empty[a]}))
|
||||
send_mail_text(None, to=a, frm=None, subject='Document Notify fields changed to match new Datatracker addressing defaults',txt =txt)
|
||||
|
||||
print "Changed",changed,"documents.",emptied,"of those had their notify field emptied"
|
||||
print "Sent email to ",len(affected),"different sets of addresses"
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# This script will send various milestone reminders. It's supposed to
|
||||
# be run daily, and will then send reminders weekly/monthly as
|
||||
# appropriate.
|
||||
|
||||
import datetime, os
|
||||
import syslog
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings")
|
||||
|
||||
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from ietf.group.mails import *
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
MONDAY = 1
|
||||
FIRST_DAY_OF_MONTH = 1
|
||||
|
||||
if today.isoweekday() == MONDAY:
|
||||
# send milestone review reminders - ideally we'd keep track of
|
||||
# exactly when we sent one last time for a group, but it's a bit
|
||||
# complicated because people can change the milestones in the mean
|
||||
# time, so dodge all of this by simply sending once a week only
|
||||
for g in groups_with_milestones_needing_review():
|
||||
mail_sent = email_milestone_review_reminder(g, grace_period=7)
|
||||
if mail_sent:
|
||||
syslog.syslog("Sent milestone review reminder for %s %s" % (g.acronym, g.type.name))
|
||||
|
||||
|
||||
early_warning_days = 30
|
||||
|
||||
# send any milestones due reminders
|
||||
for g in groups_needing_milestones_due_reminder(early_warning_days):
|
||||
email_milestones_due(g, early_warning_days)
|
||||
syslog.syslog("Sent milestones due reminder for %s %s" % (g.acronym, g.type.name))
|
||||
|
||||
if today.day == FIRST_DAY_OF_MONTH:
|
||||
# send milestone overdue reminders - once a month
|
||||
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
|
||||
email_milestones_overdue(g)
|
||||
syslog.syslog("Sent milestones overdue reminder for %s %s" % (g.acronym, g.type.name))
|
|
@ -7,10 +7,10 @@ from pathlib import Path
|
|||
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.doc.models import Document, DocEvent, State, save_document_in_history, IESG_SUBSTATE_TAGS
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.person.models import Person
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.doc.utils import add_state_change_event
|
||||
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
|
||||
def expirable_draft(draft):
|
||||
|
@ -70,10 +70,7 @@ def send_expire_warning_for_draft(doc):
|
|||
|
||||
expiration = doc.expires.date()
|
||||
|
||||
to = [e.formatted_email() for e in doc.authors.all() if not e.address.startswith("unknown-email")]
|
||||
cc = None
|
||||
if doc.group.type_id in ("wg", "rg"):
|
||||
cc = [e.formatted_email() for e in Email.objects.filter(role__group=doc.group, role__name="chair") if not e.address.startswith("unknown-email")]
|
||||
(to,cc) = gather_address_lists('doc_expires_soon',doc=doc)
|
||||
|
||||
s = doc.get_state("draft-iesg")
|
||||
state = s.name if s else "I-D Exists"
|
||||
|
@ -91,21 +88,22 @@ def send_expire_warning_for_draft(doc):
|
|||
cc=cc)
|
||||
|
||||
def send_expire_notice_for_draft(doc):
|
||||
if not doc.ad or doc.get_state_slug("draft-iesg") == "dead":
|
||||
if doc.get_state_slug("draft-iesg") == "dead":
|
||||
return
|
||||
|
||||
s = doc.get_state("draft-iesg")
|
||||
state = s.name if s else "I-D Exists"
|
||||
|
||||
request = None
|
||||
to = doc.ad.role_email("ad").formatted_email()
|
||||
(to,cc) = gather_address_lists('doc_expired',doc=doc)
|
||||
send_mail(request, to,
|
||||
"I-D Expiring System <ietf-secretariat-reply@ietf.org>",
|
||||
u"I-D was expired %s" % doc.file_tag(),
|
||||
"doc/draft/id_expired_email.txt",
|
||||
dict(doc=doc,
|
||||
state=state,
|
||||
))
|
||||
),
|
||||
cc=cc)
|
||||
|
||||
def move_draft_files_to_archive(doc, rev):
|
||||
def move_file(f):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# generation of mails
|
||||
|
||||
import os
|
||||
import textwrap, datetime
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
@ -9,14 +10,14 @@ 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, DocTagName
|
||||
from ietf.doc.utils import needed_ballot_positions
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
|
||||
from ietf.doc.utils import needed_ballot_positions, get_document_content
|
||||
from ietf.group.models import Role
|
||||
from ietf.doc.models import Document
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
def email_state_changed(request, doc, text):
|
||||
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
|
||||
def email_state_changed(request, doc, text, mailtrigger_id=None):
|
||||
(to,cc) = gather_address_lists(mailtrigger_id or 'doc_state_edited',doc=doc)
|
||||
if not to:
|
||||
return
|
||||
|
||||
|
@ -25,17 +26,17 @@ def email_state_changed(request, doc, text):
|
|||
"ID Tracker State Update Notice: %s" % doc.file_tag(),
|
||||
"doc/mail/state_changed_email.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
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,cc) = gather_address_lists('doc_stream_changed',doc=doc,streams=streams)
|
||||
|
||||
if not to:
|
||||
return
|
||||
|
@ -48,12 +49,14 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""):
|
|||
"ID Tracker Stream Change Notice: %s" % doc.file_tag(),
|
||||
"doc/mail/stream_changed_email.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state):
|
||||
extra=extra_automation_headers(doc)
|
||||
extra['Cc'] = 'iesg-secretary@ietf.org'
|
||||
send_mail(request, ["IANA <iana@iana.org>", "RFC Editor <rfc-editor@rfc-editor.org>"], None,
|
||||
addrs = gather_address_lists('doc_pulled_from_rfc_queue',doc=doc)
|
||||
extra['Cc'] = addrs.as_strings().cc
|
||||
send_mail(request, addrs.to , None,
|
||||
"%s changed state from %s to %s" % (doc.name, prev_state.name, next_state.name),
|
||||
"doc/mail/pulled_from_rfc_queue_email.txt",
|
||||
dict(doc=doc,
|
||||
|
@ -63,31 +66,18 @@ def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state):
|
|||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
extra=extra)
|
||||
|
||||
|
||||
def email_authors(request, doc, subject, text):
|
||||
to = [x.strip() for x in doc.author_list().split(',')]
|
||||
if not to:
|
||||
return
|
||||
|
||||
send_mail_text(request, to, None, subject, text)
|
||||
def email_iesg_processing_document(request, doc, changes):
|
||||
addrs = gather_address_lists('doc_iesg_processing_started',doc=doc)
|
||||
send_mail(request, addrs.to, None,
|
||||
'IESG processing details changed for %s' % doc.name,
|
||||
'doc/mail/email_iesg_processing.txt',
|
||||
dict(doc=doc,
|
||||
changes=changes),
|
||||
cc=addrs.cc)
|
||||
|
||||
def html_to_text(html):
|
||||
return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace("<br>", "\n"))
|
||||
|
||||
def email_ad(request, doc, ad, changed_by, text, subject=None):
|
||||
if not ad or not changed_by or ad == changed_by:
|
||||
return
|
||||
|
||||
to = ad.role_email("ad").formatted_email()
|
||||
send_mail(request, to,
|
||||
"DraftTracker Mail System <iesg-secretary@ietf.org>",
|
||||
"%s updated by %s" % (doc.file_tag(), changed_by.plain_name()),
|
||||
"doc/mail/change_notice.txt",
|
||||
dict(text=html_to_text(text),
|
||||
doc=doc,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
|
||||
|
||||
def generate_ballot_writeup(request, doc):
|
||||
e = doc.latest_event(type="iana_review")
|
||||
iana = e.desc if e else ""
|
||||
|
@ -104,14 +94,11 @@ def generate_ballot_writeup(request, doc):
|
|||
|
||||
def generate_last_call_announcement(request, doc):
|
||||
expiration_date = datetime.date.today() + datetime.timedelta(days=14)
|
||||
cc = []
|
||||
if doc.group.type_id in ("individ", "area"):
|
||||
group = "an individual submitter"
|
||||
expiration_date += datetime.timedelta(days=14)
|
||||
else:
|
||||
group = "the %s WG (%s)" % (doc.group.name, doc.group.acronym)
|
||||
if doc.group.list_email:
|
||||
cc.append(doc.group.list_email)
|
||||
|
||||
doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
|
||||
|
||||
|
@ -122,11 +109,14 @@ def generate_last_call_announcement(request, doc):
|
|||
else:
|
||||
ipr_links = None
|
||||
|
||||
|
||||
addrs = gather_address_lists('last_call_issued',doc=doc).as_strings()
|
||||
mail = render_to_string("doc/mail/last_call_announcement.txt",
|
||||
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"),
|
||||
cc=", ".join("<%s>" % e for e in cc),
|
||||
to=addrs.to,
|
||||
cc=addrs.cc,
|
||||
group=group,
|
||||
docs=[ doc ],
|
||||
urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ],
|
||||
|
@ -170,16 +160,10 @@ def generate_approval_mail_approved(request, doc):
|
|||
else:
|
||||
action_type = "Document"
|
||||
|
||||
cc = []
|
||||
cc.extend(settings.DOC_APPROVAL_EMAIL_CC)
|
||||
|
||||
# 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"):
|
||||
doc.group.name_with_wg = doc.group.name + " Working Group"
|
||||
if doc.group.list_email:
|
||||
cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email))
|
||||
cc.append("%s chair <%s-chairs@ietf.org>" % (doc.group.acronym, doc.group.acronym))
|
||||
else:
|
||||
doc.group.name_with_wg = doc.group.name
|
||||
|
||||
|
@ -202,11 +186,13 @@ def generate_approval_mail_approved(request, doc):
|
|||
|
||||
doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft"
|
||||
|
||||
addrs = gather_address_lists('ballot_approved_ietf_stream',doc=doc).as_strings()
|
||||
return render_to_string("doc/mail/approval_mail.txt",
|
||||
dict(doc=doc,
|
||||
docs=[doc],
|
||||
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
|
||||
cc=",\n ".join(cc),
|
||||
to = addrs.to,
|
||||
cc = addrs.cc,
|
||||
doc_type=doc_type,
|
||||
made_by=made_by,
|
||||
contacts=contacts,
|
||||
|
@ -215,30 +201,19 @@ def generate_approval_mail_approved(request, doc):
|
|||
)
|
||||
|
||||
def generate_approval_mail_rfc_editor(request, doc):
|
||||
# This is essentially dead code - it is only exercised if the IESG ballots on some other stream's document,
|
||||
# which does not happen now that we have conflict reviews.
|
||||
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 = []
|
||||
if doc.group.type_id != "individ":
|
||||
for r in doc.group.role_set.filter(name="chair").select_related():
|
||||
to.append(r.formatted_email())
|
||||
|
||||
if doc.stream_id in ("ise", "irtf"):
|
||||
# include ISE/IRTF chairs
|
||||
g = Group.objects.get(acronym=doc.stream_id)
|
||||
for r in g.role_set.filter(name="chair").select_related():
|
||||
to.append(r.formatted_email())
|
||||
|
||||
if doc.stream_id == "irtf":
|
||||
# include IRSG
|
||||
to.append('"Internet Research Steering Group" <irsg@irtf.org>')
|
||||
addrs = gather_address_lists('ballot_approved_conflrev', doc=doc).as_strings()
|
||||
|
||||
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=", ".join(to),
|
||||
to = addrs.to,
|
||||
cc = addrs.cc,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -268,17 +243,19 @@ def generate_publication_request(request, doc):
|
|||
)
|
||||
|
||||
def send_last_call_request(request, doc):
|
||||
to = "iesg-secretary@ietf.org"
|
||||
(to, cc) = gather_address_lists('last_call_requested',doc=doc)
|
||||
frm = '"DraftTracker Mail System" <iesg-secretary@ietf.org>'
|
||||
|
||||
send_mail(request, to, frm,
|
||||
"Last Call: %s" % doc.file_tag(),
|
||||
"doc/mail/last_call_request.txt",
|
||||
dict(docs=[doc],
|
||||
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
|
||||
),
|
||||
cc=cc)
|
||||
|
||||
def email_resurrect_requested(request, doc, by):
|
||||
to = "I-D Administrator <internet-drafts@ietf.org>"
|
||||
(to, cc) = gather_address_lists('resurrection_requested',doc=doc)
|
||||
|
||||
if by.role_set.filter(name="secr", group__acronym="secretariat"):
|
||||
e = by.role_email("secr", group="secretariat")
|
||||
|
@ -291,25 +268,22 @@ def email_resurrect_requested(request, doc, by):
|
|||
"doc/mail/resurrect_request_email.txt",
|
||||
dict(doc=doc,
|
||||
by=frm,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
def email_resurrection_completed(request, doc, requester):
|
||||
if requester.role_set.filter(name="secr", group__acronym="secretariat"):
|
||||
e = requester.role_email("secr", group="secretariat")
|
||||
else:
|
||||
e = requester.role_email("ad")
|
||||
|
||||
to = e.formatted_email()
|
||||
(to, cc) = gather_address_lists('resurrection_completed',doc=doc)
|
||||
frm = "I-D Administrator <internet-drafts-reply@ietf.org>"
|
||||
send_mail(request, to, frm,
|
||||
"I-D Resurrection Completed - %s" % doc.file_tag(),
|
||||
"doc/mail/resurrect_completed_email.txt",
|
||||
dict(doc=doc,
|
||||
by=frm,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
def email_ballot_deferred(request, doc, by, telechat_date):
|
||||
to = "iesg@ietf.org"
|
||||
(to, cc) = gather_address_lists('ballot_deferred',doc=doc)
|
||||
frm = "DraftTracker Mail System <iesg-secretary@ietf.org>"
|
||||
send_mail(request, to, frm,
|
||||
"IESG Deferred Ballot notification: %s" % doc.file_tag(),
|
||||
|
@ -317,10 +291,11 @@ def email_ballot_deferred(request, doc, by, telechat_date):
|
|||
dict(doc=doc,
|
||||
by=by,
|
||||
action='deferred',
|
||||
telechat_date=telechat_date))
|
||||
telechat_date=telechat_date),
|
||||
cc=cc)
|
||||
|
||||
def email_ballot_undeferred(request, doc, by, telechat_date):
|
||||
to = "iesg@ietf.org"
|
||||
(to, cc) = gather_address_lists('ballot_deferred',doc=doc)
|
||||
frm = "DraftTracker Mail System <iesg-secretary@ietf.org>"
|
||||
send_mail(request, to, frm,
|
||||
"IESG Undeferred Ballot notification: %s" % doc.file_tag(),
|
||||
|
@ -328,69 +303,18 @@ def email_ballot_undeferred(request, doc, by, telechat_date):
|
|||
dict(doc=doc,
|
||||
by=by,
|
||||
action='undeferred',
|
||||
telechat_date=telechat_date))
|
||||
telechat_date=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()
|
||||
|
@ -398,7 +322,7 @@ def generate_issue_ballot_mail(request, doc, ballot):
|
|||
)
|
||||
)
|
||||
|
||||
def email_iana(request, doc, to, msg):
|
||||
def email_iana(request, doc, to, msg, cc=None):
|
||||
# fix up message and send it with extra info on doc in headers
|
||||
import email
|
||||
parsed_msg = email.message_from_string(msg.encode("utf-8"))
|
||||
|
@ -411,7 +335,8 @@ def email_iana(request, doc, to, msg):
|
|||
send_mail_text(request, "IANA <%s>" % to,
|
||||
parsed_msg["From"], parsed_msg["Subject"],
|
||||
parsed_msg.get_payload(),
|
||||
extra=extra)
|
||||
extra=extra,
|
||||
cc=cc)
|
||||
|
||||
def extra_automation_headers(doc):
|
||||
extra = {}
|
||||
|
@ -422,45 +347,70 @@ def extra_automation_headers(doc):
|
|||
|
||||
def email_last_call_expired(doc):
|
||||
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state("draft-iesg").name
|
||||
addrs = gather_address_lists('last_call_expired',doc=doc)
|
||||
|
||||
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
|
||||
to.insert(0, "iesg@ietf.org")
|
||||
|
||||
send_mail(None,
|
||||
to,
|
||||
addrs.to,
|
||||
"DraftTracker Mail System <iesg-secretary@ietf.org>",
|
||||
"Last Call Expired: %s" % doc.file_tag(),
|
||||
"doc/mail/change_notice.txt",
|
||||
dict(text=text,
|
||||
doc=doc,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc="iesg-secretary@ietf.org")
|
||||
cc = addrs.cc)
|
||||
|
||||
def stream_state_email_recipients(doc, extra_recipients=[]):
|
||||
persons = set()
|
||||
res = []
|
||||
for r in Role.objects.filter(group=doc.group, name__in=("chair", "delegate")).select_related("person", "email"):
|
||||
res.append(r.formatted_email())
|
||||
persons.add(r.person)
|
||||
def email_intended_status_changed(request, doc, text):
|
||||
(to,cc) = gather_address_lists('doc_intended_status_changed',doc=doc)
|
||||
|
||||
for email in doc.authors.all():
|
||||
if email.person not in persons:
|
||||
res.append(email.formatted_email())
|
||||
persons.add(email.person)
|
||||
if not to:
|
||||
return
|
||||
|
||||
text = strip_tags(text)
|
||||
send_mail(request, to, None,
|
||||
"Intended Status for %s changed to %s" % (doc.file_tag(),doc.intended_std_level),
|
||||
"doc/mail/intended_status_changed_email.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
for e in extra_recipients:
|
||||
if e.person not in persons:
|
||||
res.append(e.formatted_email())
|
||||
persons.add(e.person)
|
||||
def email_comment(request, doc, comment):
|
||||
(to, cc) = gather_address_lists('doc_added_comment',doc=doc)
|
||||
|
||||
return res
|
||||
send_mail(request, to, None, "Comment added to %s history"%doc.name,
|
||||
"doc/mail/comment_added_email.txt",
|
||||
dict(
|
||||
comment=comment,
|
||||
doc=doc,
|
||||
by=request.user.person,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
|
||||
),
|
||||
cc = cc)
|
||||
|
||||
def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""):
|
||||
recipients = stream_state_email_recipients(doc)
|
||||
|
||||
def email_adopted(request, doc, prev_state, new_state, by, comment=""):
|
||||
(to, cc) = gather_address_lists('doc_adopted_by_group',doc=doc)
|
||||
|
||||
state_type = (prev_state or new_state).type
|
||||
|
||||
send_mail(request, recipients, settings.DEFAULT_FROM_EMAIL,
|
||||
send_mail(request, to, settings.DEFAULT_FROM_EMAIL,
|
||||
u"The %s %s has adopted %s" %
|
||||
(doc.group.acronym.upper(),doc.group.type_id.upper(), doc.name),
|
||||
'doc/mail/doc_adopted_email.txt',
|
||||
dict(doc=doc,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
|
||||
state_type=state_type,
|
||||
prev_state=prev_state,
|
||||
new_state=new_state,
|
||||
by=by,
|
||||
comment=comment),
|
||||
cc=cc)
|
||||
|
||||
def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""):
|
||||
(to, cc)= gather_address_lists('doc_stream_state_edited',doc=doc)
|
||||
|
||||
state_type = (prev_state or new_state).type
|
||||
|
||||
send_mail(request, to, settings.DEFAULT_FROM_EMAIL,
|
||||
u"%s changed for %s" % (state_type.label, doc.name),
|
||||
'doc/mail/stream_state_changed_email.txt',
|
||||
dict(doc=doc,
|
||||
|
@ -469,17 +419,14 @@ def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=
|
|||
prev_state=prev_state,
|
||||
new_state=new_state,
|
||||
by=by,
|
||||
comment=comment))
|
||||
comment=comment),
|
||||
cc=cc)
|
||||
|
||||
def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment=""):
|
||||
extra_recipients = []
|
||||
|
||||
if DocTagName.objects.get(slug="sheph-u") in added_tags and doc.shepherd:
|
||||
extra_recipients.append(doc.shepherd)
|
||||
(to, cc) = gather_address_lists('doc_stream_state_edited',doc=doc)
|
||||
|
||||
recipients = stream_state_email_recipients(doc, extra_recipients)
|
||||
|
||||
send_mail(request, recipients, settings.DEFAULT_FROM_EMAIL,
|
||||
send_mail(request, to, settings.DEFAULT_FROM_EMAIL,
|
||||
u"Tags changed for %s" % doc.name,
|
||||
'doc/mail/stream_tags_changed_email.txt',
|
||||
dict(doc=doc,
|
||||
|
@ -487,32 +434,46 @@ def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, commen
|
|||
added=added_tags,
|
||||
removed=removed_tags,
|
||||
by=by,
|
||||
comment=comment))
|
||||
comment=comment),
|
||||
cc=cc)
|
||||
|
||||
def send_review_possibly_replaces_request(request, doc):
|
||||
to_email = []
|
||||
|
||||
if doc.stream_id == "ietf":
|
||||
to_email.extend(r.formatted_email() for r in Role.objects.filter(group=doc.group, name="chair").select_related("email", "person"))
|
||||
elif doc.stream_id == "iab":
|
||||
to_email.append("IAB Stream <iab-stream@iab.org>")
|
||||
elif doc.stream_id == "ise":
|
||||
to_email.append("Independent Submission Editor <rfc-ise@rfc-editor.org>")
|
||||
elif doc.stream_id == "irtf":
|
||||
to_email.append("IRSG <irsg@irtf.org>")
|
||||
addrs = gather_address_lists('doc_replacement_suggested',doc=doc)
|
||||
to = set(addrs.to)
|
||||
cc = set(addrs.cc)
|
||||
|
||||
possibly_replaces = Document.objects.filter(name__in=[alias.name for alias in doc.related_that_doc("possibly-replaces")])
|
||||
other_chairs = Role.objects.filter(group__in=[other.group for other in possibly_replaces], name="chair").select_related("email", "person")
|
||||
to_email.extend(r.formatted_email() for r in other_chairs)
|
||||
for other_doc in possibly_replaces:
|
||||
(other_to, other_cc) = gather_address_lists('doc_replacement_suggested',doc=other_doc)
|
||||
to.update(other_to)
|
||||
cc.update(other_cc)
|
||||
|
||||
if not to_email:
|
||||
to_email.append("internet-drafts@ietf.org")
|
||||
send_mail(request, list(to), settings.DEFAULT_FROM_EMAIL,
|
||||
'Review of suggested possible replacements for %s-%s needed' % (doc.name, doc.rev),
|
||||
'doc/mail/review_possibly_replaces_request.txt',
|
||||
dict(doc= doc,
|
||||
possibly_replaces=doc.related_that_doc("possibly-replaces"),
|
||||
review_url=settings.IDTRACKER_BASE_URL + urlreverse("doc_review_possibly_replaces", kwargs={ "name": doc.name })),
|
||||
cc=list(cc),)
|
||||
|
||||
if to_email:
|
||||
send_mail(request, list(set(to_email)), settings.DEFAULT_FROM_EMAIL,
|
||||
'Review of suggested possible replacements for %s-%s needed' % (doc.name, doc.rev),
|
||||
'doc/mail/review_possibly_replaces_request.txt', {
|
||||
'doc': doc,
|
||||
'possibly_replaces': doc.related_that_doc("possibly-replaces"),
|
||||
'review_url': settings.IDTRACKER_BASE_URL + urlreverse("doc_review_possibly_replaces", kwargs={ "name": doc.name }),
|
||||
})
|
||||
def email_charter_internal_review(request, charter):
|
||||
addrs = gather_address_lists('charter_internal_review',doc=charter,group=charter.group)
|
||||
filename = '%s-%s.txt' % (charter.canonical_name(),charter.rev)
|
||||
charter_text = get_document_content(
|
||||
filename,
|
||||
os.path.join(settings.CHARTER_PATH,filename),
|
||||
split=False,
|
||||
markup=False,
|
||||
)
|
||||
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||
'Internal WG Review: %s (%s)'%(charter.group.name,charter.group.acronym),
|
||||
'doc/mail/charter_internal_review.txt',
|
||||
dict(charter=charter,
|
||||
chairs=charter.group.role_set.filter(name='chair').values_list('person__name',flat=True),
|
||||
ads=charter.group.role_set.filter(name='ad').values_list('person__name',flat=True),
|
||||
charter_text=charter_text,
|
||||
milestones=charter.group.groupmilestone_set.filter(state="charter"),
|
||||
),
|
||||
cc=addrs.cc,
|
||||
extra={'Reply-To':"iesg@ietf.org"},
|
||||
)
|
||||
|
|
|
@ -718,8 +718,9 @@ class AddCommentTestCase(TestCase):
|
|||
self.assertEqual("This is a test.", draft.latest_event().desc)
|
||||
self.assertEqual("added_comment", draft.latest_event().type)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("updated" in outbox[-1]['Subject'])
|
||||
self.assertTrue("Comment added" in outbox[-1]['Subject'])
|
||||
self.assertTrue(draft.name in outbox[-1]['Subject'])
|
||||
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To'])
|
||||
|
||||
# Make sure we can also do it as IANA
|
||||
self.client.login(username="iana", password="iana+password")
|
||||
|
@ -788,14 +789,20 @@ expand-draft-ietf-ames-test.all@virtual.ietf.org ames-author@example.ames, ames
|
|||
os.unlink(self.doc_alias_file.name)
|
||||
|
||||
def testAliases(self):
|
||||
url = urlreverse('ietf.doc.views_doc.email_aliases', kwargs=dict(name="draft-ietf-mars-test"))
|
||||
url = urlreverse('doc_specific_email_aliases', kwargs=dict(name="draft-ietf-mars-test"))
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(all([x in r.content for x in ['mars-test@','mars-test.authors@','mars-test.chairs@']]))
|
||||
self.assertFalse(any([x in r.content for x in ['ames-test@','ames-test.authors@','ames-test.chairs@']]))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.email_aliases', kwargs=dict())
|
||||
login_testing_unauthorized(self, "plain", url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(all([x in r.content for x in ['mars-test@','mars-test.authors@','mars-test.chairs@']]))
|
||||
self.assertTrue(all([x in r.content for x in ['ames-test@','ames-test.authors@','ames-test.chairs@']]))
|
||||
|
||||
def testExpansions(self):
|
||||
url = urlreverse('ietf.doc.views_doc.document_email', kwargs=dict(name="draft-ietf-mars-test"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('draft-ietf-mars-test.all@ietf.org' in r.content)
|
||||
self.assertTrue('ballot_saved' in r.content)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -147,12 +147,12 @@ class EditPositionTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form input[name="cc"]')) > 0)
|
||||
self.assertTrue(len(q('form input[name="extra_cc"]')) > 0)
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1",cc_group_list="1"))
|
||||
r = self.client.post(url, dict(extra_cc="test298347@example.com", cc_choices=['doc_notify','doc_group_chairs']))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
|
@ -162,16 +162,22 @@ class EditPositionTests(TestCase):
|
|||
self.assertTrue(draft.name in m['Subject'])
|
||||
self.assertTrue("clearer title" in str(m))
|
||||
self.assertTrue("Test!" in str(m))
|
||||
self.assertTrue("iesg@" in m['To'])
|
||||
# cc_choice doc_group_chairs
|
||||
self.assertTrue("mars-chairs@" in m['Cc'])
|
||||
# cc_choice doc_notify
|
||||
self.assertTrue("somebody@example.com" in m['Cc'])
|
||||
self.assertTrue("test@example.com" in m['Cc'])
|
||||
self.assertTrue(draft.group.list_email)
|
||||
self.assertTrue(draft.group.list_email in m['Cc'])
|
||||
# cc_choice doc_group_email_list was not selected
|
||||
self.assertFalse(draft.group.list_email in m['Cc'])
|
||||
# extra-cc
|
||||
self.assertTrue("test298347@example.com" in m['Cc'])
|
||||
|
||||
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'],None)
|
||||
self.assertTrue("iesg@" in m['To'])
|
||||
self.assertFalse(m['Cc'] and draft.group.list_email in m['Cc'])
|
||||
|
||||
|
||||
class BallotWriteupsTests(TestCase):
|
||||
|
@ -232,9 +238,11 @@ class BallotWriteupsTests(TestCase):
|
|||
send_last_call_request="1"))
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "lc-req")
|
||||
self.assertEqual(len(outbox), mailbox_before + 3)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Last Call" in outbox[-1]['Subject'])
|
||||
self.assertTrue(draft.name in outbox[-1]['Subject'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[-1]['To'])
|
||||
self.assertTrue('aread@' in outbox[-1]['Cc'])
|
||||
|
||||
def test_edit_ballot_writeup(self):
|
||||
draft = make_test_data()
|
||||
|
@ -270,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.",
|
||||
|
@ -308,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()
|
||||
|
@ -387,14 +364,20 @@ class ApproveBallotTests(TestCase):
|
|||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "ann")
|
||||
self.assertEqual(len(outbox), mailbox_before + 4)
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("Protocol Action" in outbox[-2]['Subject'])
|
||||
self.assertTrue("ietf-announce" in outbox[-2]['To'])
|
||||
self.assertTrue("rfc-editor" in outbox[-2]['Cc'])
|
||||
# the IANA copy
|
||||
self.assertTrue("Protocol Action" in outbox[-1]['Subject'])
|
||||
self.assertTrue(not outbox[-1]['CC'])
|
||||
self.assertTrue('drafts-approval@icann.org' in outbox[-1]['To'])
|
||||
self.assertTrue("Protocol Action" in draft.message_set.order_by("-time")[0].subject)
|
||||
|
||||
def test_disapprove_ballot(self):
|
||||
# This tests a codepath that is not used in production
|
||||
# and that has already had some drift from usefulness (it results in a
|
||||
# older-style conflict review response).
|
||||
draft = make_test_data()
|
||||
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw"))
|
||||
|
||||
|
@ -409,10 +392,9 @@ class ApproveBallotTests(TestCase):
|
|||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "dead")
|
||||
self.assertEqual(len(outbox), mailbox_before + 3)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("NOT be published" in str(outbox[-1]))
|
||||
|
||||
|
||||
class MakeLastCallTests(TestCase):
|
||||
def test_make_last_call(self):
|
||||
draft = make_test_data()
|
||||
|
@ -441,11 +423,17 @@ class MakeLastCallTests(TestCase):
|
|||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "lc")
|
||||
self.assertEqual(draft.latest_event(LastCallDocEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date)
|
||||
self.assertEqual(len(outbox), mailbox_before + 4)
|
||||
|
||||
self.assertTrue("Last Call" in outbox[-4]['Subject'])
|
||||
# the IANA copy
|
||||
self.assertTrue("Last Call" in outbox[-3]['Subject'])
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
|
||||
self.assertTrue("Last Call" in outbox[-2]['Subject'])
|
||||
self.assertTrue("ietf-announce@" in outbox[-2]['To'])
|
||||
for prefix in ['draft-ietf-mars-test','mars-chairs','aread']:
|
||||
self.assertTrue(prefix+"@" in outbox[-2]['Cc'])
|
||||
|
||||
self.assertTrue("Last Call" in outbox[-1]['Subject'])
|
||||
self.assertTrue("drafts-lastcall@icann.org" in outbox[-1]['To'])
|
||||
|
||||
self.assertTrue("Last Call" in draft.message_set.order_by("-time")[0].subject)
|
||||
|
||||
class DeferUndeferTestCase(TestCase):
|
||||
|
@ -491,11 +479,16 @@ class DeferUndeferTestCase(TestCase):
|
|||
if doc.type_id in defer_states:
|
||||
self.assertEqual(doc.get_state(defer_states[doc.type_id][0]).slug,defer_states[doc.type_id][1])
|
||||
self.assertTrue(doc.active_defer_event())
|
||||
self.assertEqual(len(outbox), mailbox_before + 3)
|
||||
self.assertTrue("State Update" in outbox[-3]['Subject'])
|
||||
self.assertTrue("Telechat update" in outbox[-2]['Subject'])
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
|
||||
self.assertTrue('Telechat update' in outbox[-2]['Subject'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[-2]['To'])
|
||||
self.assertTrue('iesg@' in outbox[-2]['To'])
|
||||
|
||||
self.assertTrue("Deferred" in outbox[-1]['Subject'])
|
||||
self.assertTrue(doc.file_tag() in outbox[-1]['Subject'])
|
||||
self.assertTrue('iesg@' in outbox[-1]['To'])
|
||||
|
||||
# Ensure it's not possible to defer again
|
||||
r = self.client.get(url)
|
||||
|
@ -546,11 +539,13 @@ class DeferUndeferTestCase(TestCase):
|
|||
if doc.type_id in undefer_states:
|
||||
self.assertEqual(doc.get_state(undefer_states[doc.type_id][0]).slug,undefer_states[doc.type_id][1])
|
||||
self.assertFalse(doc.active_defer_event())
|
||||
self.assertEqual(len(outbox), mailbox_before + 3)
|
||||
self.assertTrue("Telechat update" in outbox[-3]['Subject'])
|
||||
self.assertTrue("State Update" in outbox[-2]['Subject'])
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("Telechat update" in outbox[-2]['Subject'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[-2]['To'])
|
||||
self.assertTrue('iesg@' in outbox[-2]['To'])
|
||||
self.assertTrue("Undeferred" in outbox[-1]['Subject'])
|
||||
self.assertTrue(doc.file_tag() in outbox[-1]['Subject'])
|
||||
self.assertTrue('iesg@' in outbox[-1]['To'])
|
||||
|
||||
# Ensure it's not possible to undefer again
|
||||
r = self.client.get(url)
|
||||
|
|
|
@ -9,12 +9,12 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
|
||||
from ietf.doc.models import ( Document, State, BallotDocEvent, BallotType, NewRevisionDocEvent,
|
||||
TelechatDocEvent, WriteupDocEvent )
|
||||
from ietf.doc.utils_charter import next_revision, default_review_text, default_action_text
|
||||
from ietf.doc.utils_charter import next_revision, default_review_text, default_action_text
|
||||
from ietf.group.models import Group, GroupMilestone
|
||||
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
|
||||
|
||||
|
@ -78,7 +78,8 @@ class EditCharterTests(TestCase):
|
|||
for slug in ("intrev", "extrev", "iesgrev"):
|
||||
s = State.objects.get(used=True, type="charter", slug=slug)
|
||||
events_before = charter.docevent_set.count()
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
empty_outbox()
|
||||
|
||||
r = self.client.post(url, dict(charter_state=str(s.pk), message="test message"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -96,8 +97,17 @@ class EditCharterTests(TestCase):
|
|||
if slug in ("intrev", "iesgrev"):
|
||||
self.assertTrue(find_event("created_ballot"))
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("state changed" in outbox[-1]['Subject'].lower())
|
||||
self.assertEqual(len(outbox), 3 if slug=="intrev" else 2 )
|
||||
|
||||
if slug=="intrev":
|
||||
self.assertTrue("Internal WG Review" in outbox[-3]['Subject'])
|
||||
self.assertTrue(all([x in outbox[-3]['To'] for x in ['iab@','iesg@']]))
|
||||
|
||||
self.assertTrue("state changed" in outbox[-2]['Subject'].lower())
|
||||
self.assertTrue("iesg-secretary@" in outbox[-2]['To'])
|
||||
|
||||
self.assertTrue("State Update Notice" in outbox[-1]['Subject'])
|
||||
self.assertTrue("ames-chairs@" in outbox[-1]['To'])
|
||||
|
||||
def test_edit_telechat_date(self):
|
||||
make_test_data()
|
||||
|
@ -196,8 +206,7 @@ class EditCharterTests(TestCase):
|
|||
self.assertEqual(charter.notify,newlist)
|
||||
q = PyQuery(r.content)
|
||||
formlist = q('form input[name=notify]')[0].value
|
||||
self.assertTrue('marschairman@ietf.org' in formlist)
|
||||
self.assertFalse('someone@example.com' in formlist)
|
||||
self.assertEqual(formlist, None)
|
||||
|
||||
def test_edit_ad(self):
|
||||
make_test_data()
|
||||
|
@ -264,43 +273,101 @@ class EditCharterTests(TestCase):
|
|||
self.assertEqual(f.read(),
|
||||
"Windows line\nMac line\nUnix line\n" + utf_8_snippet)
|
||||
|
||||
def test_edit_announcement_text(self):
|
||||
def test_edit_review_announcement_text(self):
|
||||
draft = make_test_data()
|
||||
charter = draft.group.charter
|
||||
|
||||
for ann in ("action", "review"):
|
||||
url = urlreverse('ietf.doc.views_charter.announcement_text', kwargs=dict(name=charter.name, ann=ann))
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
url = urlreverse('ietf.doc.views_charter.review_announcement_text', kwargs=dict(name=charter.name))
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=announcement_text]')), 1)
|
||||
# as Secretariat, we can send
|
||||
if ann == "review":
|
||||
mailbox_before = len(outbox)
|
||||
by = Person.objects.get(user__username="secretary")
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text=default_review_text(draft.group, charter, by).text,
|
||||
send_text="1"))
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=announcement_text]')), 1)
|
||||
self.assertEqual(len(q('textarea[name=new_work_text]')), 1)
|
||||
|
||||
# save
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
save_text="1"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann).text)
|
||||
by = Person.objects.get(user__username="secretary")
|
||||
|
||||
# test regenerate
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
regenerate_text="1"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann).text)
|
||||
(e1, e2) = default_review_text(draft.group, charter, by)
|
||||
announcement_text = e1.text
|
||||
new_work_text = e2.text
|
||||
|
||||
empty_outbox()
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text=announcement_text,
|
||||
new_work_text=new_work_text,
|
||||
send_both="1"))
|
||||
self.assertEqual(len(outbox), 2)
|
||||
self.assertTrue(all(['WG Review' in m['Subject'] for m in outbox]))
|
||||
self.assertTrue('ietf-announce@' in outbox[0]['To'])
|
||||
self.assertTrue('mars-wg@' in outbox[0]['Cc'])
|
||||
self.assertTrue('new-work@' in outbox[1]['To'])
|
||||
|
||||
empty_outbox()
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text=announcement_text,
|
||||
new_work_text=new_work_text,
|
||||
send_annc_only="1"))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('ietf-announce@' in outbox[0]['To'])
|
||||
|
||||
empty_outbox()
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text=announcement_text,
|
||||
new_work_text=new_work_text,
|
||||
send_nw_only="1"))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('new-work@' in outbox[0]['To'])
|
||||
|
||||
# save
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
new_work_text="New work gets something different.",
|
||||
save_text="1"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text)
|
||||
self.assertTrue("New work gets something different." in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text)
|
||||
|
||||
# test regenerate
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
new_work_text="Too simple perhaps?",
|
||||
regenerate_text="1"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text)
|
||||
self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text)
|
||||
|
||||
def test_edit_action_announcement_text(self):
|
||||
draft = make_test_data()
|
||||
charter = draft.group.charter
|
||||
|
||||
url = urlreverse('ietf.doc.views_charter.action_announcement_text', kwargs=dict(name=charter.name))
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=announcement_text]')), 1)
|
||||
|
||||
# save
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
save_text="1"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text)
|
||||
|
||||
# test regenerate
|
||||
r = self.client.post(url, dict(
|
||||
announcement_text="This is a simple test.",
|
||||
regenerate_text="1"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text)
|
||||
|
||||
def test_edit_ballot_writeupnotes(self):
|
||||
draft = make_test_data()
|
||||
|
@ -334,11 +401,12 @@ class EditCharterTests(TestCase):
|
|||
self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text)
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
empty_outbox()
|
||||
r = self.client.post(url, dict(
|
||||
ballot_writeup="This is a simple test.",
|
||||
send_ballot="1"))
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('Evaluation' in outbox[0]['Subject'])
|
||||
|
||||
def test_approve(self):
|
||||
make_test_data()
|
||||
|
@ -394,7 +462,7 @@ class EditCharterTests(TestCase):
|
|||
self.assertEqual(len(q('pre')), 1)
|
||||
|
||||
# approve
|
||||
mailbox_before = len(outbox)
|
||||
empty_outbox()
|
||||
|
||||
r = self.client.post(url, dict())
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -406,9 +474,12 @@ class EditCharterTests(TestCase):
|
|||
self.assertEqual(charter.rev, "01")
|
||||
self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev))))
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("WG Action" in outbox[-1]['Subject'])
|
||||
self.assertTrue("approved" in outbox[-2]['Subject'].lower())
|
||||
self.assertEqual(len(outbox), 2)
|
||||
self.assertTrue("approved" in outbox[0]['Subject'].lower())
|
||||
self.assertTrue("iesg-secretary" in outbox[0]['To'])
|
||||
self.assertTrue("WG Action" in outbox[1]['Subject'])
|
||||
self.assertTrue("ietf-announce" in outbox[1]['To'])
|
||||
self.assertTrue("ames-wg@ietf.org" in outbox[1]['Cc'])
|
||||
|
||||
self.assertEqual(group.groupmilestone_set.filter(state="charter").count(), 0)
|
||||
self.assertEqual(group.groupmilestone_set.filter(state="active").count(), 2)
|
||||
|
|
|
@ -15,7 +15,7 @@ from ietf.group.models import Person
|
|||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.name.models import StreamName
|
||||
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
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -116,10 +115,14 @@ class ConflictReviewTests(TestCase):
|
|||
self.assertEquals(review_doc.notify,u'ipu@ietf.org')
|
||||
doc = Document.objects.get(name='draft-imaginary-independent-submission')
|
||||
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('drafts-eval@icann.org' in outbox[-1]['To'])
|
||||
|
||||
self.assertTrue('Conflict Review requested' in outbox[-2]['Subject'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[-2]['To'])
|
||||
|
||||
|
||||
def test_change_state(self):
|
||||
|
@ -189,9 +192,7 @@ class ConflictReviewTests(TestCase):
|
|||
# Regenerate does not save!
|
||||
self.assertEqual(doc.notify,newlist)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue('draft-imaginary-irtf-submission@ietf.org' in q('form input[name=notify]')[0].value)
|
||||
self.assertTrue('irtf-chair@ietf.org' in q('form input[name=notify]')[0].value)
|
||||
self.assertTrue('foo@bar.baz.com' not in q('form input[name=notify]')[0].value)
|
||||
self.assertEqual(None,q('form input[name=notify]')[0].value)
|
||||
|
||||
def test_edit_ad(self):
|
||||
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
|
||||
|
@ -281,7 +282,7 @@ class ConflictReviewTests(TestCase):
|
|||
self.assertTrue( 'NOT be published' in ''.join(wrap(r.content,2**16)))
|
||||
|
||||
# submit
|
||||
messages_before = len(outbox)
|
||||
empty_outbox()
|
||||
r = self.client.post(url,dict(announcement_text=default_approval_text(doc)))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
@ -289,12 +290,15 @@ class ConflictReviewTests(TestCase):
|
|||
self.assertEqual(doc.get_state_slug(),approve_type+'-sent')
|
||||
self.assertFalse(doc.ballot_open("conflrev"))
|
||||
|
||||
self.assertEqual(len(outbox), messages_before + 1)
|
||||
self.assertTrue('Results of IETF-conflict review' in outbox[-1]['Subject'])
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('Results of IETF-conflict review' in outbox[0]['Subject'])
|
||||
self.assertTrue('irtf-chair' in outbox[0]['To'])
|
||||
self.assertTrue('ietf-announce@' in outbox[0]['Cc'])
|
||||
self.assertTrue('iana@' in outbox[0]['Cc'])
|
||||
if approve_type == 'appr-noprob':
|
||||
self.assertTrue( 'IESG has no problem' in ''.join(wrap(unicode(outbox[-1]),2**16)))
|
||||
self.assertTrue( 'IESG has no problem' in ''.join(wrap(unicode(outbox[0]),2**16)))
|
||||
else:
|
||||
self.assertTrue( 'NOT be published' in ''.join(wrap(unicode(outbox[-1]),2**16)))
|
||||
self.assertTrue( 'NOT be published' in ''.join(wrap(unicode(outbox[0]),2**16)))
|
||||
|
||||
|
||||
def test_approve_reqnopub(self):
|
||||
|
|
|
@ -20,7 +20,7 @@ from ietf.meeting.models import Meeting, MeetingTypeName
|
|||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.utils.test_utils import login_testing_unauthorized
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
||||
|
||||
|
@ -72,10 +72,11 @@ class ChangeStateTests(TestCase):
|
|||
self.assertEqual(draft.docevent_set.count(), events_before + 2)
|
||||
self.assertTrue("Test comment" in draft.docevent_set.all()[0].desc)
|
||||
self.assertTrue("IESG state changed" in draft.docevent_set.all()[1].desc)
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("State Update Notice" in outbox[-2]['Subject'])
|
||||
self.assertTrue(draft.name in outbox[-1]['Subject'])
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("State Update Notice" in outbox[-1]['Subject'])
|
||||
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To'])
|
||||
self.assertTrue('mars-chairs@' in outbox[-1]['To'])
|
||||
self.assertTrue('aread@' in outbox[-1]['To'])
|
||||
|
||||
# check that we got a previous state now
|
||||
r = self.client.get(url)
|
||||
|
@ -101,11 +102,19 @@ class ChangeStateTests(TestCase):
|
|||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "review-e")
|
||||
self.assertEqual(len(outbox), mailbox_before + 2 + 1)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
|
||||
self.assertTrue(draft.name in outbox[-1]['Subject'])
|
||||
self.assertTrue("changed state" in outbox[-1]['Subject'])
|
||||
self.assertTrue("is no longer" in str(outbox[-1]))
|
||||
self.assertTrue("Test comment" in str(outbox[-1]))
|
||||
self.assertTrue("rfc-editor@" in outbox[-1]['To'])
|
||||
self.assertTrue("iana@" in outbox[-1]['To'])
|
||||
|
||||
self.assertTrue("ID Tracker State Update Notice:" in outbox[-2]['Subject'])
|
||||
self.assertTrue("aread@" in outbox[-2]['To'])
|
||||
|
||||
|
||||
def test_change_iana_state(self):
|
||||
draft = make_test_data()
|
||||
|
@ -145,8 +154,8 @@ class ChangeStateTests(TestCase):
|
|||
self.client.login(username="secretary", password="secretary+password")
|
||||
url = urlreverse('doc_change_state', kwargs=dict(name=draft.name))
|
||||
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
empty_outbox()
|
||||
|
||||
self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text"))
|
||||
r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk))
|
||||
self.assertTrue("Your request to issue" in r.content)
|
||||
|
@ -171,8 +180,14 @@ class ChangeStateTests(TestCase):
|
|||
self.assertTrue("Technical Summary" in e.text)
|
||||
|
||||
# mail notice
|
||||
self.assertTrue(len(outbox) > mailbox_before)
|
||||
self.assertTrue("Last Call:" in outbox[-1]['Subject'])
|
||||
self.assertEqual(len(outbox), 2)
|
||||
|
||||
self.assertTrue("ID Tracker State Update" in outbox[0]['Subject'])
|
||||
self.assertTrue("aread@" in outbox[0]['To'])
|
||||
|
||||
self.assertTrue("Last Call:" in outbox[1]['Subject'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[1]['To'])
|
||||
self.assertTrue('aread@' in outbox[1]['Cc'])
|
||||
|
||||
# comment
|
||||
self.assertTrue("Last call was requested" in draft.latest_event().desc)
|
||||
|
@ -252,6 +267,8 @@ class EditInfoTests(TestCase):
|
|||
self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date)
|
||||
self.assertEqual(len(outbox),mailbox_before+1)
|
||||
self.assertTrue("Telechat update" in outbox[-1]['Subject'])
|
||||
self.assertTrue('iesg@' in outbox[-1]['To'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[-1]['To'])
|
||||
|
||||
# change telechat
|
||||
mailbox_before=len(outbox)
|
||||
|
@ -330,7 +347,7 @@ class EditInfoTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form select[name=intended_std_level]')), 1)
|
||||
self.assertTrue('@' in q('form input[name=notify]')[0].get('value'))
|
||||
self.assertEqual(None,q('form input[name=notify]')[0].value)
|
||||
|
||||
# add
|
||||
events_before = draft.docevent_set.count()
|
||||
|
@ -356,7 +373,9 @@ class EditInfoTests(TestCase):
|
|||
self.assertEqual(draft.docevent_set.count(), events_before + 3)
|
||||
events = list(draft.docevent_set.order_by('time', 'id'))
|
||||
self.assertEqual(events[-3].type, "started_iesg_process")
|
||||
self.assertEqual(len(outbox), mailbox_before)
|
||||
self.assertEqual(len(outbox), mailbox_before+1)
|
||||
self.assertTrue('IESG processing' in outbox[-1]['Subject'])
|
||||
self.assertTrue('draft-ietf-mars-test2@' in outbox[-1]['To'])
|
||||
|
||||
# Redo, starting in publication requested to make sure WG state is also set
|
||||
draft.unset_state('draft-iesg')
|
||||
|
@ -431,6 +450,7 @@ class ResurrectTests(TestCase):
|
|||
self.assertTrue("Resurrection" in e.desc)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Resurrection" in outbox[-1]['Subject'])
|
||||
self.assertTrue('internet-drafts@' in outbox[-1]['To'])
|
||||
|
||||
def test_resurrect(self):
|
||||
draft = make_test_data()
|
||||
|
@ -463,6 +483,9 @@ class ResurrectTests(TestCase):
|
|||
self.assertEqual(draft.get_state_slug(), "active")
|
||||
self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1))
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue('Resurrection Completed' in outbox[-1]['Subject'])
|
||||
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
|
||||
self.assertTrue('aread' in outbox[-1]['To'])
|
||||
|
||||
|
||||
class ExpireIDsTests(TestCase):
|
||||
|
@ -524,8 +547,9 @@ class ExpireIDsTests(TestCase):
|
|||
send_expire_warning_for_draft(draft)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("aread@ietf.org" in str(outbox[-1])) # author
|
||||
self.assertTrue("marschairman@ietf.org" in str(outbox[-1]))
|
||||
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) # Gets the authors
|
||||
self.assertTrue('mars-chairs@ietf.org' in outbox[-1]['Cc'])
|
||||
self.assertTrue('aread@' in outbox[-1]['Cc'])
|
||||
|
||||
def test_expire_drafts(self):
|
||||
from ietf.doc.expire import get_expired_drafts, send_expire_notice_for_draft, expire_draft
|
||||
|
@ -556,6 +580,9 @@ class ExpireIDsTests(TestCase):
|
|||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("expired" in outbox[-1]["Subject"])
|
||||
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) # gets authors
|
||||
self.assertTrue('mars-chairs@ietf.org' in outbox[-1]['Cc'])
|
||||
self.assertTrue('aread@' in outbox[-1]['Cc'])
|
||||
|
||||
# test expiry
|
||||
txt = "%s-%s.txt" % (draft.name, draft.rev)
|
||||
|
@ -680,7 +707,9 @@ class ExpireLastCallTests(TestCase):
|
|||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Last Call Expired" in outbox[-1]["Subject"])
|
||||
|
||||
self.assertTrue('iesg-secretary@' in outbox[-1]['Cc'])
|
||||
self.assertTrue('aread@' in outbox[-1]['To'])
|
||||
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To'])
|
||||
|
||||
class IndividualInfoFormsTests(TestCase):
|
||||
def test_doc_change_stream(self):
|
||||
|
@ -694,22 +723,25 @@ class IndividualInfoFormsTests(TestCase):
|
|||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# shift to ISE stream
|
||||
messages_before = len(outbox)
|
||||
empty_outbox()
|
||||
r = self.client.post(url,dict(stream="ise",comment="7gRMTjBM"))
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.doc = Document.objects.get(name=self.docname)
|
||||
self.assertEqual(self.doc.stream_id,'ise')
|
||||
self.assertEqual(len(outbox),messages_before+1)
|
||||
self.assertTrue('Stream Change Notice' in outbox[-1]['Subject'])
|
||||
self.assertTrue('7gRMTjBM' in str(outbox[-1]))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('Stream Change Notice' in outbox[0]['Subject'])
|
||||
self.assertTrue('rfc-ise@' in outbox[0]['To'])
|
||||
self.assertTrue('iesg@' in outbox[0]['To'])
|
||||
self.assertTrue('7gRMTjBM' in str(outbox[0]))
|
||||
self.assertTrue('7gRMTjBM' in self.doc.latest_event(DocEvent,type='added_comment').desc)
|
||||
# Would be nice to test that the stream managers were in the To header...
|
||||
|
||||
# shift to an unknown stream (it must be possible to throw a document out of any stream)
|
||||
empty_outbox()
|
||||
r = self.client.post(url,dict(stream=""))
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.doc = Document.objects.get(name=self.docname)
|
||||
self.assertEqual(self.doc.stream,None)
|
||||
self.assertTrue('rfc-ise@' in outbox[0]['To'])
|
||||
|
||||
def test_doc_change_notify(self):
|
||||
url = urlreverse('doc_change_notify', kwargs=dict(name=self.docname))
|
||||
|
@ -734,7 +766,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
# Regenerate does not save!
|
||||
self.assertEqual(self.doc.notify,'TJ2APh2P@ietf.org')
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue('TJ2Aph2P' not in q('form input[name=notify]')[0].value)
|
||||
self.assertEqual(None,q('form input[name=notify]')[0].value)
|
||||
|
||||
def test_doc_change_intended_status(self):
|
||||
url = urlreverse('doc_change_intended_status', kwargs=dict(name=self.docname))
|
||||
|
@ -759,7 +791,10 @@ class IndividualInfoFormsTests(TestCase):
|
|||
self.doc = Document.objects.get(name=self.docname)
|
||||
self.assertEqual(self.doc.intended_std_level_id,'bcp')
|
||||
self.assertEqual(len(outbox),messages_before+1)
|
||||
self.assertTrue('Intended Status ' in outbox[-1]['Subject'])
|
||||
self.assertTrue('mars-chairs@' in outbox[-1]['To'])
|
||||
self.assertTrue('ZpyQFGmA' in str(outbox[-1]))
|
||||
|
||||
self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc)
|
||||
|
||||
def test_doc_change_telechat_date(self):
|
||||
|
@ -773,12 +808,17 @@ class IndividualInfoFormsTests(TestCase):
|
|||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# set a date
|
||||
empty_outbox()
|
||||
self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
|
||||
telechat_date = TelechatDate.objects.active().order_by('date')[0].date
|
||||
r = self.client.post(url,dict(telechat_date=telechat_date.isoformat()))
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.doc = Document.objects.get(name=self.docname)
|
||||
self.assertEqual(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date)
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('Telechat update notice' in outbox[0]['Subject'])
|
||||
self.assertTrue('iesg@' in outbox[0]['To'])
|
||||
self.assertTrue('iesg-secretary@' in outbox[0]['To'])
|
||||
|
||||
# Take the doc back off any telechat
|
||||
r = self.client.post(url,dict(telechat_date=""))
|
||||
|
@ -964,7 +1004,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
|
||||
|
||||
class SubmitToIesgTests(TestCase):
|
||||
def verify_permissions(self):
|
||||
def test_verify_permissions(self):
|
||||
|
||||
def verify_fail(username):
|
||||
if username:
|
||||
|
@ -987,7 +1027,7 @@ class SubmitToIesgTests(TestCase):
|
|||
for username in ['marschairman','secretary','ad']:
|
||||
verify_can_see(username)
|
||||
|
||||
def cancel_submission(self):
|
||||
def test_cancel_submission(self):
|
||||
url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname))
|
||||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
|
||||
|
@ -997,7 +1037,7 @@ class SubmitToIesgTests(TestCase):
|
|||
doc = Document.objects.get(pk=self.doc.pk)
|
||||
self.assertTrue(doc.get_state('draft-iesg')==None)
|
||||
|
||||
def confirm_submission(self):
|
||||
def test_confirm_submission(self):
|
||||
url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname))
|
||||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
|
||||
|
@ -1014,6 +1054,8 @@ class SubmitToIesgTests(TestCase):
|
|||
self.assertTrue(doc.docevent_set.count() != docevent_count_pre)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Publication has been requested" in outbox[-1]['Subject'])
|
||||
self.assertTrue("aread@" in outbox[-1]['To'])
|
||||
self.assertTrue("iesg-secretary@" in outbox[-1]['Cc'])
|
||||
|
||||
def setUp(self):
|
||||
make_test_data()
|
||||
|
@ -1052,12 +1094,16 @@ class RequestPublicationTests(TestCase):
|
|||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state_slug("draft-stream-iab"), "rfc-edit")
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
||||
|
||||
self.assertTrue("Document Action" in outbox[-2]['Subject'])
|
||||
self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject)
|
||||
# the IANA copy
|
||||
self.assertTrue("rfc-editor@" in outbox[-2]['To'])
|
||||
|
||||
self.assertTrue("Document Action" in outbox[-1]['Subject'])
|
||||
self.assertTrue(not outbox[-1]['CC'])
|
||||
self.assertTrue("drafts-approval@icann.org" in outbox[-1]['To'])
|
||||
|
||||
self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject)
|
||||
|
||||
class AdoptDraftTests(TestCase):
|
||||
def test_adopt_document(self):
|
||||
|
@ -1092,13 +1138,12 @@ class AdoptDraftTests(TestCase):
|
|||
self.assertEqual(draft.group.acronym, "mars")
|
||||
self.assertEqual(draft.stream_id, "ietf")
|
||||
self.assertEqual(draft.docevent_set.count() - events_before, 5)
|
||||
self.assertTrue('draft-ietf-mars-test@ietf.org' in draft.notify)
|
||||
self.assertTrue('draft-ietf-mars-test.ad@ietf.org' in draft.notify)
|
||||
self.assertTrue('draft-ietf-mars-test.shepherd@ietf.org' in draft.notify)
|
||||
self.assertEqual(draft.notify,"aliens@example.mars")
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("state changed" in outbox[-1]["Subject"].lower())
|
||||
self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("has adopted" in outbox[-1]["Subject"].lower())
|
||||
self.assertTrue("mars-chairs@ietf.org" in outbox[-1]['To'])
|
||||
self.assertTrue("draft-ietf-mars-test@" in outbox[-1]['To'])
|
||||
self.assertTrue("mars-wg@" in outbox[-1]['To'])
|
||||
|
||||
self.assertFalse(mars.list_email in draft.notify)
|
||||
|
||||
|
@ -1139,7 +1184,7 @@ class ChangeStreamStateTests(TestCase):
|
|||
self.assertEqual(draft.docevent_set.count() - events_before, 2)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("tags changed" in outbox[-1]["Subject"].lower())
|
||||
self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("mars-chairs@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("plain@example.com" in unicode(outbox[-1]))
|
||||
|
||||
|
@ -1163,7 +1208,7 @@ class ChangeStreamStateTests(TestCase):
|
|||
old_state = draft.get_state("draft-stream-%s" % draft.stream_id )
|
||||
new_state = State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="parked")
|
||||
self.assertNotEqual(old_state, new_state)
|
||||
mailbox_before = len(outbox)
|
||||
empty_outbox()
|
||||
events_before = draft.docevent_set.count()
|
||||
|
||||
r = self.client.post(url,
|
||||
|
@ -1181,10 +1226,10 @@ class ChangeStreamStateTests(TestCase):
|
|||
self.assertEqual(len(reminder), 1)
|
||||
due = datetime.datetime.now() + datetime.timedelta(weeks=10)
|
||||
self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("state changed" in outbox[-1]["Subject"].lower())
|
||||
self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1]))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue("state changed" in outbox[0]["Subject"].lower())
|
||||
self.assertTrue("mars-chairs@ietf.org" in unicode(outbox[0]))
|
||||
self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[0]))
|
||||
|
||||
class ChangeReplacesTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -1255,6 +1300,7 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# Post that says replacea replaces base a
|
||||
empty_outbox()
|
||||
RelatedDocument.objects.create(source=self.replacea, target=self.basea.docalias_set.first(),
|
||||
relationship=DocRelationshipName.objects.get(slug="possibly-replaces"))
|
||||
self.assertEqual(self.basea.get_state().slug,'active')
|
||||
|
@ -1263,7 +1309,12 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('replacement status updated' in outbox[-1]['Subject'])
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-a@' in outbox[-1]['To'])
|
||||
|
||||
empty_outbox()
|
||||
# Post that says replaceboth replaces both base a and base b
|
||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
|
||||
self.assertEqual(self.baseb.get_state().slug,'expired')
|
||||
|
@ -1271,18 +1322,31 @@ class ChangeReplacesTests(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('base-b@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-both@' in outbox[-1]['To'])
|
||||
|
||||
# Post that undoes replaceboth
|
||||
empty_outbox()
|
||||
r = self.client.post(url, dict(replaces=""))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('base-b@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-both@' in outbox[-1]['To'])
|
||||
|
||||
# Post that undoes replacea
|
||||
empty_outbox()
|
||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name))
|
||||
r = self.client.post(url, dict(replaces=""))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
||||
self.assertTrue('base-a@' in outbox[-1]['To'])
|
||||
self.assertTrue('replace-a@' in outbox[-1]['To'])
|
||||
|
||||
|
||||
def test_review_possibly_replaces(self):
|
||||
replaced = self.basea.docalias_set.first()
|
||||
|
|
|
@ -111,7 +111,6 @@ class StatusChangeTests(TestCase):
|
|||
doc = Document.objects.get(name='status-change-imaginary-mid-review')
|
||||
self.assertEquals(doc.get_state('statchg').slug,'lc-req')
|
||||
self.assertEquals(len(outbox), messages_before + 1)
|
||||
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
|
||||
self.assertTrue('Last Call:' in outbox[-1]['Subject'])
|
||||
|
||||
# successful change to IESG Evaluation
|
||||
|
@ -157,9 +156,7 @@ class StatusChangeTests(TestCase):
|
|||
self.assertEqual(doc.notify,newlist)
|
||||
q = PyQuery(r.content)
|
||||
formlist = q('form input[name=notify]')[0].value
|
||||
self.assertTrue('draft-ietf-random-thing@ietf.org' in formlist)
|
||||
self.assertTrue('draft-ietf-random-otherthing@ietf.org' in formlist)
|
||||
self.assertFalse('foo@bar.baz.com' in formlist)
|
||||
self.assertEqual(None,formlist)
|
||||
|
||||
def test_edit_title(self):
|
||||
doc = Document.objects.get(name='status-change-imaginary-mid-review')
|
||||
|
@ -285,7 +282,6 @@ class StatusChangeTests(TestCase):
|
|||
self.assertEqual(r.status_code,200)
|
||||
self.assertTrue( 'Last call requested' in ''.join(wrap(r.content,2**16)))
|
||||
self.assertEqual(len(outbox), messages_before + 1)
|
||||
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
|
||||
self.assertTrue('Last Call:' in outbox[-1]['Subject'])
|
||||
self.assertTrue('Last Call Request has been submitted' in ''.join(wrap(unicode(outbox[-1]),2**16)))
|
||||
|
||||
|
@ -326,8 +322,10 @@ class StatusChangeTests(TestCase):
|
|||
|
||||
self.assertEqual(len(outbox), messages_before + 2)
|
||||
self.assertTrue('Action:' in outbox[-1]['Subject'])
|
||||
self.assertTrue('(rfc9999) to Internet Standard' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16)))
|
||||
self.assertTrue('ietf-announce' in outbox[-1]['To'])
|
||||
self.assertTrue('rfc-editor' in outbox[-1]['Cc'])
|
||||
self.assertTrue('(rfc9998) to Historic' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16)))
|
||||
self.assertTrue('(rfc9999) to Internet Standard' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16)))
|
||||
|
||||
self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('The following approval message was sent'))
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ urlpatterns = patterns('',
|
|||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?$', views_doc.document_main, name="doc_view"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email/$', views_doc.document_email, name="doc_email"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/shepherdwriteup/$', views_doc.document_shepherd_writeup, name="doc_shepherd_writeup"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/references/$', views_doc.document_references, name="doc_references"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/referencedby/$', views_doc.document_referenced_by, name="doc_referenced_by"),
|
||||
|
@ -65,7 +66,7 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<name>[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballotpopup/(?P<ballot_id>[0-9]+)/$', views_doc.ballot_popup),
|
||||
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email-aliases/$', views_doc.email_aliases),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
|
||||
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/$', views_draft.change_state, name='doc_change_state'), # IESG state
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/(?P<state_type>iana-action|iana-review)/$', views_draft.change_iana_state, name='doc_change_iana_state'),
|
||||
|
|
|
@ -9,7 +9,8 @@ urlpatterns = patterns('',
|
|||
url(r'^telechat/$', "ietf.doc.views_doc.telechat_date", name='charter_telechat_date'),
|
||||
url(r'^notify/$', "ietf.doc.views_doc.edit_notify", name='charter_edit_notify'),
|
||||
url(r'^ad/$', "ietf.doc.views_charter.edit_ad", name='charter_edit_ad'),
|
||||
url(r'^(?P<ann>action|review)/$', "ietf.doc.views_charter.announcement_text", name="charter_edit_announcement"),
|
||||
url(r'^action/$', "ietf.doc.views_charter.action_announcement_text"),
|
||||
url(r'^review/$', "ietf.doc.views_charter.review_announcement_text"),
|
||||
url(r'^ballotwriteupnotes/$', "ietf.doc.views_charter.ballot_writeupnotes"),
|
||||
url(r'^approve/$', "ietf.doc.views_charter.approve", name='charter_approve'),
|
||||
url(r'^submit/(?:(?P<option>initcharter|recharter)/)?$', "ietf.doc.views_charter.submit", name='charter_submit'),
|
||||
|
|
|
@ -5,7 +5,6 @@ import math
|
|||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.db.models.query import EmptyQuerySet
|
||||
from django.forms import ValidationError
|
||||
from django.utils.html import strip_tags, escape
|
||||
|
@ -13,29 +12,29 @@ from django.utils.html import strip_tags, escape
|
|||
from ietf.doc.models import Document, DocHistory, State
|
||||
from ietf.doc.models import DocAlias, RelatedDocument, BallotType, DocReminder
|
||||
from ietf.doc.models import DocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
|
||||
from ietf.doc.models import save_document_in_history, STATUSCHANGE_RELATIONS
|
||||
from ietf.doc.models import save_document_in_history
|
||||
from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
||||
from ietf.group.models import Role
|
||||
from ietf.person.models import Email
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.utils import draft, markup_txt
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
#FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
|
||||
#TODO FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
|
||||
# an import order issue to work out.
|
||||
def email_update_telechat(request, doc, text):
|
||||
to = set(['iesg@ietf.org','iesg-secretary@ietf.org'])
|
||||
to.update(set([x.strip() for x in doc.notify.replace(';', ',').split(',')]))
|
||||
(to, cc) = gather_address_lists('doc_telechat_details_changed',doc=doc)
|
||||
|
||||
if not to:
|
||||
return
|
||||
|
||||
text = strip_tags(text)
|
||||
send_mail(request, list(to), None,
|
||||
send_mail(request, to, None,
|
||||
"Telechat update notice: %s" % doc.file_tag(),
|
||||
"doc/mail/update_telechat.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=cc)
|
||||
|
||||
def get_state_types(doc):
|
||||
res = []
|
||||
|
@ -458,35 +457,19 @@ def rebuild_reference_relations(doc,filename=None):
|
|||
|
||||
return ret
|
||||
|
||||
def collect_email_addresses(emails, doc):
|
||||
for author in doc.authors.all():
|
||||
if author.address not in emails:
|
||||
emails[author.address] = '"%s"' % (author.person.name)
|
||||
if doc.group and doc.group.acronym != 'none':
|
||||
for role in doc.group.role_set.filter(name='chair'):
|
||||
if role.email.address not in emails:
|
||||
emails[role.email.address] = '"%s"' % (role.person.name)
|
||||
if doc.group.type.slug == 'wg':
|
||||
address = '%s-ads@ietf.org' % doc.group.acronym
|
||||
if address not in emails:
|
||||
emails[address] = '"%s-ads"' % (doc.group.acronym)
|
||||
elif doc.group.type.slug == 'rg':
|
||||
for role in doc.group.parent.role_set.filter(name='chair'):
|
||||
if role.email.address not in emails:
|
||||
emails[role.email.address] = '"%s"' % (role.person.name)
|
||||
if doc.shepherd and doc.shepherd.address not in emails:
|
||||
emails[doc.shepherd.address] = u'"%s"' % (doc.shepherd.person.name or "")
|
||||
|
||||
def set_replaces_for_document(request, doc, new_replaces, by, email_subject, email_comment=""):
|
||||
emails = {}
|
||||
collect_email_addresses(emails, doc)
|
||||
addrs = gather_address_lists('doc_replacement_changed',doc=doc)
|
||||
to = set(addrs.to)
|
||||
cc = set(addrs.cc)
|
||||
|
||||
relationship = DocRelationshipName.objects.get(slug='replaces')
|
||||
old_replaces = doc.related_that_doc("replaces")
|
||||
|
||||
for d in old_replaces:
|
||||
if d not in new_replaces:
|
||||
collect_email_addresses(emails, d.document)
|
||||
other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document)
|
||||
to.update(other_addrs.to)
|
||||
cc.update(other_addrs.cc)
|
||||
RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete()
|
||||
if not RelatedDocument.objects.filter(target=d, relationship=relationship):
|
||||
s = 'active' if d.document.expires > datetime.datetime.now() else 'expired'
|
||||
|
@ -494,7 +477,9 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema
|
|||
|
||||
for d in new_replaces:
|
||||
if d not in old_replaces:
|
||||
collect_email_addresses(emails, d.document)
|
||||
other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document)
|
||||
to.update(other_addrs.to)
|
||||
cc.update(other_addrs.cc)
|
||||
RelatedDocument.objects.create(source=doc, target=d, relationship=relationship)
|
||||
d.document.set_state(State.objects.get(type='draft', slug='repl'))
|
||||
|
||||
|
@ -512,20 +497,16 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema
|
|||
if email_comment:
|
||||
email_desc += "\n" + email_comment
|
||||
|
||||
to = [
|
||||
u'%s <%s>' % (emails[email], email) if emails[email] else u'<%s>' % email
|
||||
for email in sorted(emails)
|
||||
]
|
||||
|
||||
from ietf.doc.mails import html_to_text
|
||||
|
||||
send_mail(request, to,
|
||||
send_mail(request, list(to),
|
||||
"DraftTracker Mail System <iesg-secretary@ietf.org>",
|
||||
email_subject,
|
||||
"doc/mail/change_notice.txt",
|
||||
dict(text=html_to_text(email_desc),
|
||||
doc=doc,
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
|
||||
cc=list(cc))
|
||||
|
||||
def check_common_doc_name_rules(name):
|
||||
"""Check common rules for document names for use in forms, throws
|
||||
|
@ -543,41 +524,9 @@ def check_common_doc_name_rules(name):
|
|||
raise ValidationError(errors)
|
||||
|
||||
def get_initial_notify(doc,extra=None):
|
||||
# set change state notice to something sensible
|
||||
# With the mailtrigger based changes, a document's notify should start empty
|
||||
receivers = []
|
||||
|
||||
if doc.type.slug=='draft':
|
||||
if doc.group.type_id in ("individ", "area"):
|
||||
for a in doc.authors.all():
|
||||
receivers.append(a.address)
|
||||
else:
|
||||
receivers.append("%s-chairs@%s" % (doc.group.acronym, settings.DRAFT_ALIAS_DOMAIN))
|
||||
for editor in Email.objects.filter(role__name="editor", role__group=doc.group):
|
||||
receivers.append(editor.address)
|
||||
|
||||
receivers.append("%s@%s" % (doc.name, settings.DRAFT_ALIAS_DOMAIN))
|
||||
receivers.append("%s.ad@%s" % (doc.name, settings.DRAFT_ALIAS_DOMAIN))
|
||||
receivers.append("%s.shepherd@%s" % (doc.name, settings.DRAFT_ALIAS_DOMAIN))
|
||||
|
||||
elif doc.type.slug=='charter':
|
||||
receivers.extend([role.person.formatted_email() for role in doc.group.role_set.filter(name__slug__in=['ad','chair','secr','techadv'])])
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
for relation in doc.relateddocument_set.filter(Q(relationship='conflrev')|Q(relationship__in=STATUSCHANGE_RELATIONS)):
|
||||
if relation.relationship.slug=='conflrev':
|
||||
doc_to_review = relation.target.document
|
||||
receivers.extend([x.person.formatted_email() for x in Role.objects.filter(group__acronym=doc_to_review.stream.slug,name='chair')])
|
||||
receivers.append("%s@%s" % (doc_to_review.name, settings.DRAFT_ALIAS_DOMAIN))
|
||||
elif relation.relationship.slug in STATUSCHANGE_RELATIONS:
|
||||
affected_doc = relation.target.document
|
||||
if affected_doc.notify:
|
||||
receivers.extend(affected_doc.notify.split(','))
|
||||
|
||||
if doc.shepherd:
|
||||
receivers.append(doc.shepherd.email_address())
|
||||
|
||||
if extra:
|
||||
if isinstance(extra,basestring):
|
||||
extra = extra.split(', ')
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import re, datetime, os
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
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 send_mail_text
|
||||
from ietf.utils.mail import parse_preformatted
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
def charter_name_for_group(group):
|
||||
if group.type_id == "rg":
|
||||
|
@ -83,19 +82,6 @@ def historic_milestones_for_charter(charter, rev):
|
|||
|
||||
return res
|
||||
|
||||
def email_state_changed(request, doc, text):
|
||||
to = [e.strip() for e in doc.notify.replace(';', ',').split(',')]
|
||||
if not to:
|
||||
return
|
||||
|
||||
text = strip_tags(text)
|
||||
text += "\n\n"
|
||||
text += "URL: %s" % (settings.IDTRACKER_BASE_URL + doc.get_absolute_url())
|
||||
|
||||
send_mail_text(request, to, None,
|
||||
"State changed: %s-%s" % (doc.canonical_name(), doc.rev),
|
||||
text)
|
||||
|
||||
def generate_ballot_writeup(request, doc):
|
||||
e = WriteupDocEvent()
|
||||
e.type = "changed_ballot_writeup_text"
|
||||
|
@ -113,6 +99,7 @@ def default_action_text(group, charter, by):
|
|||
else:
|
||||
action = "Rechartered"
|
||||
|
||||
addrs = gather_address_lists('ballot_approved_charter',doc=charter,group=group).as_strings(compact=False)
|
||||
e = WriteupDocEvent(doc=charter, by=by)
|
||||
e.by = by
|
||||
e.type = "changed_action_announcement"
|
||||
|
@ -126,93 +113,68 @@ 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=addrs.to,
|
||||
cc=addrs.cc,
|
||||
))
|
||||
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def derive_new_work_text(review_text,group):
|
||||
addrs= gather_address_lists('charter_external_review_new_work',group=group).as_strings()
|
||||
(m,_,_) = parse_preformatted(review_text,
|
||||
override={'To':addrs.to,
|
||||
'Cc':addrs.cc,
|
||||
'From':'The IESG <iesg@ietf.org>',
|
||||
'Reply_to':'<iesg@ietf.org>'})
|
||||
if not addrs.cc:
|
||||
del m['Cc']
|
||||
return m.as_string()
|
||||
|
||||
def default_review_text(group, charter, by):
|
||||
e = WriteupDocEvent(doc=charter, by=by)
|
||||
e.by = by
|
||||
e.type = "changed_review_announcement"
|
||||
e.desc = "%s review text was changed" % group.type.name
|
||||
e.text = render_to_string("doc/charter/review_text.txt",
|
||||
now = datetime.datetime.now()
|
||||
addrs=gather_address_lists('charter_external_review',group=group).as_strings(compact=False)
|
||||
|
||||
e1 = WriteupDocEvent(doc=charter, by=by)
|
||||
e1.by = by
|
||||
e1.type = "changed_review_announcement"
|
||||
e1.desc = "%s review text was changed" % group.type.name
|
||||
e1.text = render_to_string("doc/charter/review_text.txt",
|
||||
dict(group=group,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
charter_text=read_charter_text(charter),
|
||||
chairs=group.role_set.filter(name="chair"),
|
||||
secr=group.role_set.filter(name="secr"),
|
||||
techadv=group.role_set.filter(name="techadv"),
|
||||
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",
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
charter_text=read_charter_text(charter),
|
||||
chairs=group.role_set.filter(name="chair"),
|
||||
secr=group.role_set.filter(name="secr"),
|
||||
techadv=group.role_set.filter(name="techadv"),
|
||||
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=addrs.to,
|
||||
cc=addrs.cc,
|
||||
)
|
||||
)
|
||||
e.save()
|
||||
return e
|
||||
e1.time = now
|
||||
e1.save()
|
||||
|
||||
e2 = WriteupDocEvent(doc=charter, by=by)
|
||||
e2.by = by
|
||||
e2.type = "changed_new_work_text"
|
||||
e2.desc = "%s review text was changed" % group.type.name
|
||||
e2.text = derive_new_work_text(e1.text,group)
|
||||
e2.time = now
|
||||
e2.save()
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent, BallotP
|
|||
BallotType, LastCallDocEvent, WriteupDocEvent, save_document_in_history, IESG_SUBSTATE_TAGS )
|
||||
from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ballots,
|
||||
create_ballot_if_not_open, update_telechat )
|
||||
from ietf.doc.mails import ( email_ad, email_ballot_deferred, email_ballot_undeferred,
|
||||
email_state_changed, extra_automation_headers, generate_last_call_announcement,
|
||||
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_approval_mail )
|
||||
from ietf.doc.lastcall import request_last_call
|
||||
from ietf.iesg.models import TelechatDate
|
||||
|
@ -27,6 +27,8 @@ 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.mailtrigger.utils import gather_address_lists
|
||||
from ietf.mailtrigger.forms import CcSelectForm
|
||||
|
||||
BALLOT_CHOICES = (("yes", "Yes"),
|
||||
("noobj", "No Objection"),
|
||||
|
@ -68,8 +70,6 @@ def do_undefer_ballot(request, doc):
|
|||
doc.save()
|
||||
|
||||
update_telechat(request, doc, login, telechat_date)
|
||||
if e:
|
||||
email_state_changed(request, doc, e.desc)
|
||||
email_ballot_undeferred(request, doc, login.plain_name(), telechat_date)
|
||||
|
||||
def position_to_ballot_choice(position):
|
||||
|
@ -284,28 +284,36 @@ def send_ballot_comment(request, name, ballot_id):
|
|||
blocking_name=blocking_name,
|
||||
settings=settings))
|
||||
frm = ad.role_email("ad").formatted_email()
|
||||
to = "The IESG <iesg@ietf.org>"
|
||||
|
||||
addrs = gather_address_lists('ballot_saved',doc=doc)
|
||||
|
||||
if request.method == 'POST':
|
||||
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
|
||||
if request.POST.get("cc_state_change") and doc.notify:
|
||||
cc.extend(doc.notify.split(','))
|
||||
if request.POST.get("cc_group_list") and doc.group.list_email:
|
||||
cc.append(doc.group.list_email)
|
||||
cc = []
|
||||
cc_select_form = CcSelectForm(data=request.POST,mailtrigger_slug='ballot_saved',mailtrigger_context={'doc':doc})
|
||||
if cc_select_form.is_valid():
|
||||
cc.extend(cc_select_form.get_selected_addresses())
|
||||
extra_cc = [x.strip() for x in request.POST.get("extra_cc","").split(',') if x.strip()]
|
||||
if extra_cc:
|
||||
cc.extend(extra_cc)
|
||||
|
||||
send_mail_text(request, to, frm, subject, body, cc=u", ".join(cc))
|
||||
send_mail_text(request, addrs.to, frm, subject, body, cc=u", ".join(cc))
|
||||
|
||||
return HttpResponseRedirect(return_to_url)
|
||||
|
||||
else:
|
||||
|
||||
cc_select_form = CcSelectForm(mailtrigger_slug='ballot_saved',mailtrigger_context={'doc':doc})
|
||||
|
||||
return render_to_response('doc/ballot/send_ballot_comment.html',
|
||||
dict(doc=doc,
|
||||
subject=subject,
|
||||
body=body,
|
||||
frm=frm,
|
||||
to=to,
|
||||
to=addrs.as_strings().to,
|
||||
ad=ad,
|
||||
can_send=d or c,
|
||||
back_url=back_url,
|
||||
cc_select_form = cc_select_form,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
@ -363,9 +371,6 @@ def defer_ballot(request, name):
|
|||
doc.time = (e and e.time) or datetime.datetime.now()
|
||||
doc.save()
|
||||
|
||||
if e:
|
||||
email_state_changed(request, doc, e.desc)
|
||||
|
||||
update_telechat(request, doc, login, telechat_date)
|
||||
email_ballot_deferred(request, doc, login.plain_name(), telechat_date)
|
||||
|
||||
|
@ -458,10 +463,6 @@ def lastcalltext(request, name):
|
|||
doc.time = (e and e.time) or datetime.datetime.now()
|
||||
doc.save()
|
||||
|
||||
if e:
|
||||
email_state_changed(request, doc, e.desc)
|
||||
email_ad(request, doc, doc.ad, login, e.desc)
|
||||
|
||||
request_last_call(request, doc)
|
||||
|
||||
return render_to_response('doc/draft/last_call_requested.html',
|
||||
|
@ -538,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})
|
||||
|
||||
|
@ -696,23 +709,19 @@ def approve_ballot(request, name):
|
|||
|
||||
e.save()
|
||||
|
||||
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
|
||||
|
||||
e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[])
|
||||
|
||||
doc.time = (e and e.time) or datetime.datetime.now()
|
||||
doc.save()
|
||||
|
||||
email_state_changed(request, doc, change_description)
|
||||
email_ad(request, doc, doc.ad, login, change_description)
|
||||
|
||||
# send announcement
|
||||
|
||||
send_mail_preformatted(request, announcement)
|
||||
|
||||
if action == "to_announcement_list":
|
||||
addrs = gather_address_lists('ballot_approved_ietf_stream_iana').as_strings(compact=False)
|
||||
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
|
||||
override={ "To": "IANA <%s>"%settings.IANA_APPROVE_EMAIL, "CC": None, "Bcc": None, "Reply-To": None})
|
||||
override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None})
|
||||
|
||||
msg = infer_message(announcement)
|
||||
msg.by = login
|
||||
|
@ -753,8 +762,9 @@ def make_last_call(request, name):
|
|||
if form.is_valid():
|
||||
send_mail_preformatted(request, announcement)
|
||||
if doc.type.slug == 'draft':
|
||||
addrs = gather_address_lists('last_call_issued_iana',doc=doc).as_strings(compact=False)
|
||||
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
|
||||
override={ "To": "IANA <drafts-lastcall@icann.org>", "CC": None, "Bcc": None, "Reply-To": None})
|
||||
override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None})
|
||||
|
||||
msg = infer_message(announcement)
|
||||
msg.by = login
|
||||
|
@ -782,11 +792,6 @@ def make_last_call(request, name):
|
|||
doc.time = (e and e.time) or datetime.datetime.now()
|
||||
doc.save()
|
||||
|
||||
change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, new_state.name)
|
||||
|
||||
email_state_changed(request, doc, change_description)
|
||||
email_ad(request, doc, doc.ad, login, change_description)
|
||||
|
||||
e = LastCallDocEvent(doc=doc, by=login)
|
||||
e.type = "sent_last_call"
|
||||
e.desc = "The following Last Call announcement was sent out:<br><br>"
|
||||
|
|
|
@ -18,17 +18,18 @@ from ietf.doc.models import ( Document, DocHistory, State, DocEvent, BallotDocEv
|
|||
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
|
||||
create_ballot_if_not_open, get_chartering_type )
|
||||
from ietf.doc.utils_charter import ( historic_milestones_for_charter,
|
||||
approved_revision, default_review_text, default_action_text, email_state_changed,
|
||||
approved_revision, default_review_text, default_action_text,
|
||||
generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision )
|
||||
from ietf.doc.mails import email_state_changed, email_charter_internal_review
|
||||
from ietf.group.models import ChangeStateGroupEvent, MilestoneGroupEvent
|
||||
from ietf.group.utils import save_group_in_history, save_milestone_in_history, can_manage_group_type
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.name.models import GroupStateName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.history import find_history_active_at
|
||||
from ietf.utils.mail import send_mail_preformatted
|
||||
from ietf.utils.mail import send_mail_preformatted
|
||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||
from ietf.group.mails import email_iesg_secretary_re_charter
|
||||
from ietf.group.mails import email_admin_re_charter
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
|
||||
|
@ -139,10 +140,14 @@ def change_state(request, name, option=None):
|
|||
charter.time = datetime.datetime.now()
|
||||
charter.save()
|
||||
|
||||
if message or charter_state.slug == "intrev" or charter_state.slug == "extrev":
|
||||
email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message)
|
||||
if charter_state.slug == 'intrev':
|
||||
email_charter_internal_review(request,charter)
|
||||
|
||||
email_state_changed(request, charter, "State changed to %s." % charter_state)
|
||||
if message or charter_state.slug == "intrev" or charter_state.slug == "extrev":
|
||||
email_admin_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message,'charter_state_edit_admin_needed')
|
||||
|
||||
# TODO - do we need a seperate set of recipients for state changes to charters vrs other kind of documents
|
||||
email_state_changed(request, charter, "State changed to %s." % charter_state, 'doc_state_edited')
|
||||
|
||||
if charter_state.slug == "intrev" and group.type_id == "wg":
|
||||
if request.POST.get("ballot_wo_extern"):
|
||||
|
@ -202,9 +207,9 @@ def change_state(request, name, option=None):
|
|||
|
||||
info_msg = {}
|
||||
if group.type_id == "wg":
|
||||
info_msg[state_pk("infrev")] = 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("intrev")] = 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("extrev")] = 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("infrev")] = 'The proposed charter for %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("intrev")] = 'The proposed charter for %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat if it has not already been placed.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("extrev")] = 'The proposed charter for %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
|
||||
states_for_ballot_wo_extern = State.objects.none()
|
||||
if group.type_id == "wg":
|
||||
|
@ -265,8 +270,8 @@ def change_title(request, name, option=None):
|
|||
charter.time = datetime.datetime.now()
|
||||
charter.save()
|
||||
if message:
|
||||
email_iesg_secretary_re_charter(request, group, "Charter title changed to %s" % new_title, message)
|
||||
email_state_changed(request, charter, "Title changed to %s." % new_title)
|
||||
email_admin_re_charter(request, group, "Charter title changed to %s" % new_title, message,'charter_state_edit_admin_needed')
|
||||
email_state_changed(request, charter, "Title changed to %s." % new_title,'doc_state_edited')
|
||||
return redirect('doc_view', name=charter.name)
|
||||
else:
|
||||
form = ChangeTitleForm(charter=charter)
|
||||
|
@ -422,42 +427,124 @@ def submit(request, name=None, option=None):
|
|||
'name': name },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
class AnnouncementTextForm(forms.Form):
|
||||
class ActionAnnouncementTextForm(forms.Form):
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
def clean_announcement_text(self):
|
||||
return self.cleaned_data["announcement_text"].replace("\r", "")
|
||||
|
||||
|
||||
class ReviewAnnouncementTextForm(forms.Form):
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, required=True)
|
||||
new_work_text = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
def clean_announcement_text(self):
|
||||
return self.cleaned_data["announcement_text"].replace("\r", "")
|
||||
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def announcement_text(request, name, ann):
|
||||
"""Editing of announcement text"""
|
||||
def review_announcement_text(request, name):
|
||||
"""Editing of review announcement text"""
|
||||
charter = get_object_or_404(Document, type="charter", name=name)
|
||||
group = charter.group
|
||||
|
||||
login = request.user.person
|
||||
|
||||
if ann in ("action", "review"):
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann)
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
|
||||
existing_new_work = charter.latest_event(WriteupDocEvent, type="changed_new_work_text")
|
||||
|
||||
if not existing:
|
||||
if ann == "action":
|
||||
existing = default_action_text(group, charter, login)
|
||||
elif ann == "review":
|
||||
existing = default_review_text(group, charter, login)
|
||||
(existing, existing_new_work) = default_review_text(group, charter, login)
|
||||
|
||||
if not existing:
|
||||
raise Http404
|
||||
|
||||
form = AnnouncementTextForm(initial=dict(announcement_text=existing.text))
|
||||
new_work_text = existing_new_work.text
|
||||
|
||||
form = ReviewAnnouncementTextForm(initial=dict(announcement_text=existing.text,new_work_text=new_work_text))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AnnouncementTextForm(request.POST)
|
||||
form = ReviewAnnouncementTextForm(request.POST)
|
||||
if "save_text" in request.POST and form.is_valid():
|
||||
|
||||
now = datetime.datetime.now()
|
||||
(e1, e2) = (None, None)
|
||||
|
||||
t = form.cleaned_data['announcement_text']
|
||||
if t != existing.text:
|
||||
e1 = WriteupDocEvent(doc=charter, by=login)
|
||||
e1.by = login
|
||||
e1.type = "changed_review_announcement"
|
||||
e1.desc = "%s review text was changed" % (group.type.name)
|
||||
e1.text = t
|
||||
e1.time = now
|
||||
e1.save()
|
||||
|
||||
t = form.cleaned_data['new_work_text']
|
||||
if t != new_work_text:
|
||||
e2 = WriteupDocEvent(doc=charter, by=login)
|
||||
e2.by = login
|
||||
e2.type = "changed_new_work_text"
|
||||
e2.desc = "%s new work message text was changed" % (group.type.name)
|
||||
e2.text = t
|
||||
e2.time = now
|
||||
e2.save()
|
||||
|
||||
if e1 or e2:
|
||||
charter.time = now
|
||||
charter.save()
|
||||
|
||||
if request.GET.get("next", "") == "approve":
|
||||
return redirect('charter_approve', name=charter.canonical_name())
|
||||
|
||||
return redirect('doc_writeup', name=charter.canonical_name())
|
||||
|
||||
if "regenerate_text" in request.POST:
|
||||
(e1, e2) = default_review_text(group, charter, login)
|
||||
form = ReviewAnnouncementTextForm(initial=dict(announcement_text=e1.text,new_work_text=e2.text))
|
||||
|
||||
if any([x in request.POST for x in ['send_annc_only','send_nw_only','send_both']]) and form.is_valid():
|
||||
if any([x in request.POST for x in ['send_annc_only','send_both']]):
|
||||
parsed_msg = send_mail_preformatted(request, form.cleaned_data['announcement_text'])
|
||||
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
|
||||
if any([x in request.POST for x in ['send_nw_only','send_both']]):
|
||||
parsed_msg = send_mail_preformatted(request, form.cleaned_data['new_work_text'])
|
||||
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
|
||||
return redirect('doc_writeup', name=charter.name)
|
||||
|
||||
return render_to_response('doc/charter/review_announcement_text.html',
|
||||
dict(charter=charter,
|
||||
back_url=urlreverse("doc_writeup", kwargs=dict(name=charter.name)),
|
||||
announcement_text_form=form,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def action_announcement_text(request, name):
|
||||
"""Editing of action announcement text"""
|
||||
charter = get_object_or_404(Document, type="charter", name=name)
|
||||
group = charter.group
|
||||
|
||||
login = request.user.person
|
||||
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
if not existing:
|
||||
existing = default_action_text(group, charter, login)
|
||||
|
||||
if not existing:
|
||||
raise Http404
|
||||
|
||||
form = ActionAnnouncementTextForm(initial=dict(announcement_text=existing.text))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ActionAnnouncementTextForm(request.POST)
|
||||
if "save_text" in request.POST and form.is_valid():
|
||||
t = form.cleaned_data['announcement_text']
|
||||
if t != existing.text:
|
||||
e = WriteupDocEvent(doc=charter, by=login)
|
||||
e.by = login
|
||||
e.type = "changed_%s_announcement" % ann
|
||||
e.desc = "%s %s text was changed" % (group.type.name, ann)
|
||||
e.type = "changed_action_announcement"
|
||||
e.desc = "%s action text was changed" % group.type.name
|
||||
e.text = t
|
||||
e.save()
|
||||
|
||||
|
@ -470,21 +557,16 @@ def announcement_text(request, name, ann):
|
|||
return redirect('doc_writeup', name=charter.canonical_name())
|
||||
|
||||
if "regenerate_text" in request.POST:
|
||||
if ann == "action":
|
||||
e = default_action_text(group, charter, login)
|
||||
elif ann == "review":
|
||||
e = default_review_text(group, charter, login)
|
||||
# make sure form has the updated text
|
||||
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
|
||||
e = default_action_text(group, charter, login)
|
||||
form = ActionAnnouncementTextForm(initial=dict(announcement_text=e.text))
|
||||
|
||||
if "send_text" in request.POST and form.is_valid():
|
||||
parsed_msg = send_mail_preformatted(request, form.cleaned_data['announcement_text'])
|
||||
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
|
||||
return redirect('doc_writeup', name=charter.name)
|
||||
|
||||
return render_to_response('doc/charter/announcement_text.html',
|
||||
return render_to_response('doc/charter/action_announcement_text.html',
|
||||
dict(charter=charter,
|
||||
announcement=ann,
|
||||
back_url=urlreverse("doc_writeup", kwargs=dict(name=charter.name)),
|
||||
announcement_text_form=form,
|
||||
),
|
||||
|
@ -540,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)
|
||||
|
@ -643,7 +726,7 @@ def approve(request, name):
|
|||
|
||||
fix_charter_revision_after_approval(charter, login)
|
||||
|
||||
email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description)
|
||||
email_admin_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description,'charter_state_edit_admin_needed')
|
||||
|
||||
# move milestones over
|
||||
milestones_to_delete = list(group.groupmilestone_set.filter(state__in=("active", "review")))
|
||||
|
|
|
@ -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.mailtrigger.utils import gather_address_lists
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
review_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev"), label="Conflict review state", empty_label=None, required=True)
|
||||
|
@ -68,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)
|
||||
|
||||
|
||||
|
@ -86,8 +88,11 @@ def change_state(request, name, option=None):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
def send_conflict_review_started_email(request, review):
|
||||
addrs = gather_address_lists('conflrev_requested',doc=review).as_strings(compact=False)
|
||||
msg = render_to_string("doc/conflict_review/review_started.txt",
|
||||
dict(frm = settings.DEFAULT_FROM_EMAIL,
|
||||
to = addrs.to,
|
||||
cc = addrs.cc,
|
||||
by = request.user.person,
|
||||
review = review,
|
||||
reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target.document,
|
||||
|
@ -96,10 +101,13 @@ def send_conflict_review_started_email(request, review):
|
|||
)
|
||||
if not has_role(request.user,"Secretariat"):
|
||||
send_mail_preformatted(request,msg)
|
||||
|
||||
addrs = gather_address_lists('conflrev_requested_iana',doc=review).as_strings(compact=False)
|
||||
email_iana(request,
|
||||
review.relateddocument_set.get(relationship__slug='conflrev').target.document,
|
||||
settings.IANA_EVAL_EMAIL,
|
||||
msg)
|
||||
addrs.to,
|
||||
msg,
|
||||
cc=addrs.cc)
|
||||
|
||||
def send_conflict_eval_email(request,review):
|
||||
msg = render_to_string("doc/eval_email.txt",
|
||||
|
@ -107,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)
|
||||
|
@ -251,13 +265,16 @@ def default_approval_text(review):
|
|||
receiver = 'IRTF'
|
||||
else:
|
||||
receiver = 'recipient'
|
||||
addrs = gather_address_lists('ballot_approved_conflrev',doc=review).as_strings(compact=False)
|
||||
text = render_to_string("doc/conflict_review/approval_text.txt",
|
||||
dict(review=review,
|
||||
review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
|
||||
conflictdoc = conflictdoc,
|
||||
conflictdoc_url = settings.IDTRACKER_BASE_URL+conflictdoc.get_absolute_url(),
|
||||
receiver=receiver,
|
||||
approved_review = current_text
|
||||
approved_review = current_text,
|
||||
to = addrs.to,
|
||||
cc = addrs.cc,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_wi
|
|||
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
||||
get_initial_notify, make_notify_changed_event )
|
||||
from ietf.community.models import CommunityList
|
||||
from ietf.doc.mails import email_ad
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import can_manage_group_type, can_manage_materials
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person, role_required
|
||||
|
@ -59,6 +58,8 @@ from ietf.name.models import StreamName, BallotPositionName
|
|||
from ietf.person.models import Email
|
||||
from ietf.utils.history import find_history_active_at
|
||||
from ietf.doc.forms import TelechatForm, NotifyForm
|
||||
from ietf.doc.mails import email_comment
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
|
||||
def render_document_top(request, doc, tab, name):
|
||||
tabs = []
|
||||
|
@ -73,6 +74,7 @@ def render_document_top(request, doc, tab, name):
|
|||
if doc.type_id == "draft" or (doc.type_id == "charter" and doc.group.type_id == "wg"):
|
||||
tabs.append(("IESG Writeups", "writeup", urlreverse("doc_writeup", kwargs=dict(name=name)), True))
|
||||
|
||||
tabs.append(("Email expansions","email",urlreverse("doc_email", kwargs=dict(name=name)), True))
|
||||
tabs.append(("History", "history", urlreverse("doc_history", kwargs=dict(name=name)), True))
|
||||
|
||||
if name.startswith("rfc"):
|
||||
|
@ -569,9 +571,36 @@ def document_main(request, name, rev=None):
|
|||
raise Http404
|
||||
|
||||
|
||||
def get_email_aliases(name):
|
||||
if name:
|
||||
pattern = re.compile('^expand-(%s)(\..*?)?@.*? +(.*)$'%name)
|
||||
else:
|
||||
pattern = re.compile('^expand-(.*?)(\..*?)?@.*? +(.*)$')
|
||||
aliases = []
|
||||
with open(settings.DRAFT_VIRTUAL_PATH,"r") as virtual_file:
|
||||
for line in virtual_file.readlines():
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
aliases.append({'doc_name':m.group(1),'alias_type':m.group(2),'expansion':m.group(3)})
|
||||
return aliases
|
||||
|
||||
|
||||
def document_email(request,name):
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
top = render_document_top(request, doc, "email", name)
|
||||
|
||||
aliases = get_email_aliases(name) if doc.type_id=='draft' else None
|
||||
|
||||
expansions = gather_relevant_expansions(doc=doc)
|
||||
|
||||
return render(request, "doc/document_email.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
aliases=aliases,
|
||||
expansions=expansions,
|
||||
ietf_domain=settings.IETF_DOMAIN,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def document_history(request, name):
|
||||
|
@ -658,14 +687,14 @@ def document_writeup(request, name):
|
|||
"",
|
||||
[("WG Review Announcement",
|
||||
text_from_writeup("changed_review_announcement"),
|
||||
urlreverse("ietf.doc.views_charter.announcement_text", kwargs=dict(name=doc.name, ann="review")))]
|
||||
urlreverse("ietf.doc.views_charter.review_announcement_text", kwargs=dict(name=doc.name)))]
|
||||
))
|
||||
|
||||
sections.append(("WG Action Announcement",
|
||||
"",
|
||||
[("WG Action Announcement",
|
||||
text_from_writeup("changed_action_announcement"),
|
||||
urlreverse("ietf.doc.views_charter.announcement_text", kwargs=dict(name=doc.name, ann="action")))]
|
||||
urlreverse("ietf.doc.views_charter.action_announcement_text", kwargs=dict(name=doc.name)))]
|
||||
))
|
||||
|
||||
if doc.latest_event(BallotDocEvent, type="created_ballot"):
|
||||
|
@ -877,9 +906,8 @@ def add_comment(request, name):
|
|||
e.desc = c
|
||||
e.save()
|
||||
|
||||
if doc.type_id == "draft":
|
||||
email_ad(request, doc, doc.ad, login,
|
||||
"A new comment added by %s" % login.name)
|
||||
email_comment(request, doc, e)
|
||||
|
||||
return redirect("doc_history", name=doc.name)
|
||||
else:
|
||||
form = AddCommentForm()
|
||||
|
@ -984,20 +1012,12 @@ def edit_notify(request, name):
|
|||
|
||||
def email_aliases(request,name=''):
|
||||
doc = get_object_or_404(Document, name=name) if name else None
|
||||
if name:
|
||||
pattern = re.compile('^expand-(%s)(\..*?)?@.*? +(.*)$'%name)
|
||||
else:
|
||||
if not name:
|
||||
# require login for the overview page, but not for the
|
||||
# document-specific pages handled above
|
||||
# document-specific pages
|
||||
if not request.user.is_authenticated():
|
||||
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||
pattern = re.compile('^expand-(.*?)(\..*?)?@.*? +(.*)$')
|
||||
aliases = []
|
||||
with open(settings.DRAFT_VIRTUAL_PATH,"r") as virtual_file:
|
||||
for line in virtual_file.readlines():
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
aliases.append({'doc_name':m.group(1),'alias_type':m.group(2),'expansion':m.group(3)})
|
||||
aliases = get_email_aliases(name)
|
||||
|
||||
return render(request,'doc/email_aliases.html',{'aliases':aliases,'ietf_domain':settings.IETF_DOMAIN,'doc':doc})
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ import debug # pyflakes:ignore
|
|||
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
|
||||
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS,
|
||||
save_document_in_history )
|
||||
from ietf.doc.mails import ( email_ad, email_pulled_from_rfc_queue, email_resurrect_requested,
|
||||
from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested,
|
||||
email_resurrection_completed, email_state_changed, email_stream_changed,
|
||||
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
|
||||
generate_publication_request )
|
||||
generate_publication_request, email_adopted, email_intended_status_changed,
|
||||
email_iesg_processing_document )
|
||||
from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
|
||||
get_tags_for_stream_id, nice_consensus,
|
||||
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify,
|
||||
|
@ -39,6 +40,7 @@ from ietf.person.models import Person, Email
|
|||
from ietf.secr.lib.template import jsonapi
|
||||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True)
|
||||
|
@ -111,8 +113,7 @@ def change_state(request, name):
|
|||
doc.time = e.time
|
||||
doc.save()
|
||||
|
||||
email_state_changed(request, doc, msg)
|
||||
email_ad(request, doc, doc.ad, login, msg)
|
||||
email_state_changed(request, doc, msg,'doc_state_edited')
|
||||
|
||||
|
||||
if prev_state and prev_state.slug in ("ann", "rfcqueue") and new_state.slug not in ("rfcqueue", "pub"):
|
||||
|
@ -451,7 +452,7 @@ def change_intention(request, name):
|
|||
doc.time = e.time
|
||||
doc.save()
|
||||
|
||||
email_ad(request, doc, doc.ad, login, email_desc)
|
||||
email_intended_status_changed(request, doc, email_desc)
|
||||
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
|
||||
|
@ -583,10 +584,11 @@ def to_iesg(request,name):
|
|||
|
||||
doc.save()
|
||||
|
||||
addrs= gather_address_lists('pubreq_iesg',doc=doc)
|
||||
extra = {}
|
||||
extra['Cc'] = "%s-chairs@ietf.org, iesg-secretary@ietf.org, %s" % (doc.group.acronym,doc.notify)
|
||||
extra['Cc'] = addrs.as_strings().cc
|
||||
send_mail(request=request,
|
||||
to = doc.ad.email_address(),
|
||||
to = addrs.to,
|
||||
frm = login.formatted_email(),
|
||||
subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev),
|
||||
template = "doc/submit_to_iesg_email.txt",
|
||||
|
@ -670,8 +672,6 @@ def edit_info(request, name):
|
|||
e.desc = "IESG process started in state <b>%s</b>" % doc.get_state("draft-iesg").name
|
||||
e.save()
|
||||
|
||||
orig_ad = doc.ad
|
||||
|
||||
changes = []
|
||||
|
||||
def desc(attr, new, old):
|
||||
|
@ -721,13 +721,14 @@ def edit_info(request, name):
|
|||
e.type = "changed_document"
|
||||
e.save()
|
||||
|
||||
# Todo - chase this
|
||||
update_telechat(request, doc, login,
|
||||
r['telechat_date'], r['returning_item'])
|
||||
|
||||
doc.time = datetime.datetime.now()
|
||||
|
||||
if changes and not new_document:
|
||||
email_ad(request, doc, orig_ad, login, "\n".join(changes))
|
||||
if changes:
|
||||
email_iesg_processing_document(request, doc, changes)
|
||||
|
||||
doc.save()
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
|
@ -1134,7 +1135,7 @@ def request_publication(request, name):
|
|||
|
||||
m = Message()
|
||||
m.frm = request.user.person.formatted_email()
|
||||
m.to = "RFC Editor <rfc-editor@rfc-editor.org>"
|
||||
(m.to, m.cc) = gather_address_lists('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")
|
||||
|
@ -1164,7 +1165,7 @@ def request_publication(request, name):
|
|||
send_mail_message(request, m)
|
||||
|
||||
# IANA copy
|
||||
m.to = settings.IANA_APPROVE_EMAIL
|
||||
(m.to, m.cc) = gather_address_lists('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)
|
||||
|
@ -1300,7 +1301,7 @@ def adopt_draft(request, name):
|
|||
|
||||
update_reminder(doc, "stream-s", e, due_date)
|
||||
|
||||
email_stream_state_changed(request, doc, prev_state, new_state, by, comment)
|
||||
email_adopted(request, doc, prev_state, new_state, by, comment)
|
||||
|
||||
# comment
|
||||
if comment:
|
||||
|
|
|
@ -22,6 +22,7 @@ from ietf.name.models import DocRelationshipName, StdLevelName
|
|||
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.mailtrigger.utils import gather_address_lists
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
new_state = forms.ModelChoiceField(State.objects.filter(type="statchg", used=True), label="Status Change Evaluation State", empty_label=None, required=True)
|
||||
|
@ -106,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)
|
||||
|
@ -289,7 +294,8 @@ def default_approval_text(status_change,relateddoc):
|
|||
else:
|
||||
action = "Document Action"
|
||||
|
||||
|
||||
|
||||
addrs = gather_address_lists('ballot_approved_status_change',doc=status_change).as_strings(compact=False)
|
||||
text = render_to_string("doc/status_change/approval_text.txt",
|
||||
dict(status_change=status_change,
|
||||
status_change_url = settings.IDTRACKER_BASE_URL+status_change.get_absolute_url(),
|
||||
|
@ -298,6 +304,8 @@ def default_approval_text(status_change,relateddoc):
|
|||
approved_text = current_text,
|
||||
action=action,
|
||||
newstatus=newstatus(relateddoc),
|
||||
to=addrs.to,
|
||||
cc=addrs.cc,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -21,8 +21,7 @@ from ietf.group.utils import get_group_or_404
|
|||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.person.fields import SearchableEmailsField
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.group.mails import ( email_iesg_secretary_re_charter, email_iesg_secretary_personnel_change,
|
||||
email_interested_parties_re_changed_delegates )
|
||||
from ietf.group.mails import ( email_admin_re_charter, email_personnel_change)
|
||||
from ietf.utils.ordereddict import insert_after_in_ordered_dict
|
||||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
@ -270,6 +269,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
personnel_change_text=""
|
||||
changed_personnel = set()
|
||||
# update roles
|
||||
for attr, slug, title in [('ad','ad','Shepherding AD'), ('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]:
|
||||
new = clean[attr]
|
||||
|
@ -291,10 +291,10 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
if deleted:
|
||||
change_text=title + ' deleted: ' + ", ".join(x.formatted_email() for x in deleted)
|
||||
personnel_change_text+=change_text+"\n"
|
||||
email_interested_parties_re_changed_delegates(request, group, title, added, deleted)
|
||||
changed_personnel.update(set(old)^set(new))
|
||||
|
||||
if personnel_change_text!="":
|
||||
email_iesg_secretary_personnel_change(request, group, personnel_change_text)
|
||||
email_personnel_change(request, group, personnel_change_text, changed_personnel)
|
||||
|
||||
# update urls
|
||||
new_urls = clean['urls']
|
||||
|
@ -372,7 +372,7 @@ def conclude(request, acronym, group_type=None):
|
|||
if form.is_valid():
|
||||
instructions = form.cleaned_data['instructions']
|
||||
|
||||
email_iesg_secretary_re_charter(request, group, "Request closing of group", instructions)
|
||||
email_admin_re_charter(request, group, "Request closing of group", instructions, 'group_closure_requested')
|
||||
|
||||
e = GroupEvent(group=group, by=request.user.person)
|
||||
e.type = "requested_close"
|
||||
|
|
|
@ -57,6 +57,7 @@ from ietf.group.utils import get_charter_text, can_manage_group_type, milestone_
|
|||
from ietf.group.utils import can_manage_materials, get_group_or_404
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.settings import MAILING_LIST_INFO_URL
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
|
||||
def roles(group, role_name):
|
||||
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")
|
||||
|
@ -332,6 +333,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
|||
entries.append(("About", urlreverse("group_about", kwargs=kwargs)))
|
||||
if group.features.has_materials and get_group_materials(group).exists():
|
||||
entries.append(("Materials", urlreverse("ietf.group.info.materials", kwargs=kwargs)))
|
||||
entries.append(("Email expansions", urlreverse("ietf.group.info.email", kwargs=kwargs)))
|
||||
entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs)))
|
||||
if group.features.has_documents:
|
||||
entries.append((mark_safe("Dependency graph »"), urlreverse("ietf.group.info.dependencies_pdf", kwargs=kwargs)))
|
||||
|
@ -480,6 +482,34 @@ def group_about(request, acronym, group_type=None):
|
|||
"can_manage": can_manage,
|
||||
}))
|
||||
|
||||
def get_email_aliases(acronym, group_type):
|
||||
if acronym:
|
||||
pattern = re.compile('expand-(%s)(-\w+)@.*? +(.*)$'%acronym)
|
||||
else:
|
||||
pattern = re.compile('expand-(.*?)(-\w+)@.*? +(.*)$')
|
||||
|
||||
aliases = []
|
||||
with open(settings.GROUP_VIRTUAL_PATH,"r") as virtual_file:
|
||||
for line in virtual_file.readlines():
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
if acronym or not group_type or Group.objects.filter(acronym=m.group(1),type__slug=group_type):
|
||||
aliases.append({'acronym':m.group(1),'alias_type':m.group(2),'expansion':m.group(3)})
|
||||
return aliases
|
||||
|
||||
def email(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
|
||||
aliases = get_email_aliases(acronym, group_type)
|
||||
expansions = gather_relevant_expansions(group=group)
|
||||
|
||||
return render(request, 'group/email.html',
|
||||
construct_group_menu_context(request, group, "email expansions", group_type, {
|
||||
'expansions':expansions,
|
||||
'aliases':aliases,
|
||||
'group':group,
|
||||
'ietf_domain':settings.IETF_DOMAIN,
|
||||
}))
|
||||
|
||||
def history(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
|
@ -674,22 +704,13 @@ def dependencies_pdf(request, acronym, group_type=None):
|
|||
def email_aliases(request, acronym=None, group_type=None):
|
||||
group = get_group_or_404(acronym,group_type) if acronym else None
|
||||
|
||||
if acronym:
|
||||
pattern = re.compile('expand-(%s)(-\w+)@.*? +(.*)$'%acronym)
|
||||
else:
|
||||
if not acronym:
|
||||
# require login for the overview page, but not for the group-specific
|
||||
# pages handled above
|
||||
# pages
|
||||
if not request.user.is_authenticated():
|
||||
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||
pattern = re.compile('expand-(.*?)(-\w+)@.*? +(.*)$')
|
||||
|
||||
aliases = []
|
||||
with open(settings.GROUP_VIRTUAL_PATH,"r") as virtual_file:
|
||||
for line in virtual_file.readlines():
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
if acronym or not group_type or Group.objects.filter(acronym=m.group(1),type__slug=group_type):
|
||||
aliases.append({'acronym':m.group(1),'alias_type':m.group(2),'expansion':m.group(3)})
|
||||
aliases = get_email_aliases(acronym, group_type)
|
||||
|
||||
return render(request,'group/email_aliases.html',{'aliases':aliases,'ietf_domain':settings.IETF_DOMAIN,'group':group})
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# generation of mails
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
|
||||
|
@ -10,11 +9,10 @@ from django.conf import settings
|
|||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.utils.mail import send_mail, send_mail_text
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import milestone_reviewer_for_group_type
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
def email_iesg_secretary_re_charter(request, group, subject, text):
|
||||
to = ["iesg-secretary@ietf.org"]
|
||||
def email_admin_re_charter(request, group, subject, text, mailtrigger):
|
||||
(to,cc) = gather_address_lists(mailtrigger,group=group)
|
||||
full_subject = u"Regarding %s %s: %s" % (group.type.name, group.acronym, subject)
|
||||
text = strip_tags(text)
|
||||
|
||||
|
@ -24,48 +22,18 @@ def email_iesg_secretary_re_charter(request, group, subject, text):
|
|||
group=group,
|
||||
group_url=settings.IDTRACKER_BASE_URL + group.about_url(),
|
||||
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)) if group.charter else "[no charter]",
|
||||
)
|
||||
)
|
||||
),
|
||||
cc=cc,
|
||||
)
|
||||
|
||||
def email_iesg_secretary_personnel_change(request, group, text):
|
||||
to = ["iesg-secretary@ietf.org"]
|
||||
def email_personnel_change(request, group, text, changed_personnel):
|
||||
(to, cc) = gather_address_lists('group_personnel_change',group=group,changed_personnel=changed_personnel)
|
||||
full_subject = u"Personnel change for %s working group" % (group.acronym)
|
||||
send_mail_text(request, to, None, full_subject,text)
|
||||
|
||||
def email_interested_parties_re_changed_delegates(request, group, title, added, deleted):
|
||||
|
||||
# Send to management and chairs
|
||||
to = []
|
||||
if group.ad_role():
|
||||
to.append(group.ad_role().email.formatted_email())
|
||||
elif group.type_id == "rg":
|
||||
to.append("IRTF Chair <irtf-chair@irtf.org>")
|
||||
|
||||
for r in group.role_set.filter(name="chair"):
|
||||
to.append(r.formatted_email())
|
||||
|
||||
# Send to the delegates who were added or deleted
|
||||
for delegate in added:
|
||||
to.append(delegate.formatted_email())
|
||||
|
||||
for delegate in deleted:
|
||||
to.append(delegate.formatted_email())
|
||||
|
||||
personnel_change_text=""
|
||||
if added:
|
||||
change_text=title + ' added: ' + ", ".join(x.formatted_email() for x in added)
|
||||
personnel_change_text+=change_text+"\n"
|
||||
if deleted:
|
||||
change_text=title + ' deleted: ' + ", ".join(x.formatted_email() for x in deleted)
|
||||
personnel_change_text+=change_text+"\n"
|
||||
|
||||
if to:
|
||||
full_subject = u"%s changed for %s working group" % (title, group.acronym)
|
||||
send_mail_text(request, to, None, full_subject,personnel_change_text)
|
||||
send_mail_text(request, to, None, full_subject, text, cc=cc)
|
||||
|
||||
|
||||
def email_milestones_changed(request, group, changes):
|
||||
def wrap_up_email(to, text):
|
||||
def wrap_up_email(addrs, text):
|
||||
|
||||
subject = u"Milestones changed for %s %s" % (group.acronym, group.type.name)
|
||||
if re.search("Added .* for review, due",text):
|
||||
|
@ -75,123 +43,18 @@ def email_milestones_changed(request, group, changes):
|
|||
text += "\n\n"
|
||||
text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + group.about_url())
|
||||
|
||||
send_mail_text(request, to, None, subject, text)
|
||||
send_mail_text(request, addrs.to, None, subject, text, cc=addrs.cc)
|
||||
|
||||
# first send to management and chairs
|
||||
to = []
|
||||
if group.ad_role():
|
||||
to.append(group.ad_role().email.formatted_email())
|
||||
elif group.type_id == "rg":
|
||||
to.append("IRTF Chair <irtf-chair@irtf.org>")
|
||||
# first send to those who should see any edits (such as management and chairs)
|
||||
addrs = gather_address_lists('group_milestones_edited',group=group)
|
||||
if addrs.to or addrs.cc:
|
||||
wrap_up_email(addrs, u"\n\n".join(c + "." for c in changes))
|
||||
|
||||
for r in group.role_set.filter(name="chair"):
|
||||
to.append(r.formatted_email())
|
||||
|
||||
if to:
|
||||
wrap_up_email(to, u"\n\n".join(c + "." for c in changes))
|
||||
|
||||
# then send to group
|
||||
if group.list_email:
|
||||
review_re = re.compile("Added .* for review, due")
|
||||
to = [ group.list_email ]
|
||||
msg = u"\n\n".join(c + "." for c in changes if not review_re.match(c))
|
||||
if msg:
|
||||
wrap_up_email(to, msg)
|
||||
|
||||
|
||||
def email_milestone_review_reminder(group, grace_period=7):
|
||||
"""Email reminders about milestones needing review to management."""
|
||||
to = []
|
||||
|
||||
if group.ad_role():
|
||||
to.append(group.ad_role().email.formatted_email())
|
||||
elif group.type_id == "rg":
|
||||
to.append("IRTF Chair <irtf-chair@irtf.org>")
|
||||
|
||||
if not to:
|
||||
return False
|
||||
|
||||
cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
|
||||
|
||||
now = datetime.datetime.now()
|
||||
too_early = True
|
||||
|
||||
milestones = group.groupmilestone_set.filter(state="review")
|
||||
for m in milestones:
|
||||
e = m.milestonegroupevent_set.filter(type="changed_milestone").order_by("-time")[:1]
|
||||
m.days_ready = (now - e[0].time).days if e else None
|
||||
|
||||
if m.days_ready == None or m.days_ready >= grace_period:
|
||||
too_early = False
|
||||
|
||||
if too_early:
|
||||
return False
|
||||
|
||||
subject = u"Reminder: Milestone%s needing review in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
|
||||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"group/reminder_milestones_need_review.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
reviewer=milestone_reviewer_for_group_type(group.type_id),
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_edit_milestones", kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
|
||||
cc=cc,
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def groups_with_milestones_needing_review():
|
||||
return Group.objects.filter(groupmilestone__state="review").distinct()
|
||||
|
||||
def email_milestones_due(group, early_warning_days):
|
||||
to = [r.formatted_email() for r in group.role_set.filter(name="chair")]
|
||||
|
||||
today = datetime.date.today()
|
||||
early_warning = today + datetime.timedelta(days=early_warning_days)
|
||||
|
||||
milestones = group.groupmilestone_set.filter(due__in=[today, early_warning],
|
||||
resolved="", state="active")
|
||||
|
||||
subject = u"Reminder: Milestone%s are soon due in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
|
||||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"group/reminder_milestones_due.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
today=today,
|
||||
early_warning_days=early_warning_days,
|
||||
url=settings.IDTRACKER_BASE_URL + group.about_url(),
|
||||
))
|
||||
|
||||
def groups_needing_milestones_due_reminder(early_warning_days):
|
||||
"""Return groups having milestones that are either
|
||||
early_warning_days from being due or are due today."""
|
||||
today = datetime.date.today()
|
||||
return Group.objects.filter(state="active", groupmilestone__due__in=[today, today + datetime.timedelta(days=early_warning_days)], groupmilestone__resolved="", groupmilestone__state="active").distinct()
|
||||
|
||||
def email_milestones_overdue(group):
|
||||
to = [r.formatted_email() for r in group.role_set.filter(name="chair")]
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
milestones = group.groupmilestone_set.filter(due__lt=today, resolved="", state="active")
|
||||
for m in milestones:
|
||||
m.months_overdue = (today - m.due).days // 30
|
||||
|
||||
subject = u"Reminder: Milestone%s overdue in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
|
||||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"group/reminder_milestones_overdue.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
url=settings.IDTRACKER_BASE_URL + group.about_url(),
|
||||
))
|
||||
|
||||
def groups_needing_milestones_overdue_reminder(grace_period=30):
|
||||
cut_off = datetime.date.today() - datetime.timedelta(days=grace_period)
|
||||
return Group.objects.filter(state="active", groupmilestone__due__lt=cut_off, groupmilestone__resolved="", groupmilestone__state="active").distinct()
|
||||
# then send only the approved milestones to those who shouldn't be
|
||||
# bothered with milestones pending approval
|
||||
review_re = re.compile("Added .* for review, due")
|
||||
addrs = gather_address_lists('group_approved_milestones_edited',group=group)
|
||||
msg = u"\n\n".join(c + "." for c in changes if not review_re.match(c))
|
||||
if (addrs.to or addrs.cc) and msg:
|
||||
wrap_up_email(addrs, msg)
|
||||
|
||||
|
|
|
@ -127,6 +127,25 @@ class Group(GroupInfo):
|
|||
def has_tools_page(self):
|
||||
return self.type_id in ['wg', ] and self.state_id in ['active', 'dormant', 'replaced', 'conclude']
|
||||
|
||||
def liaison_approvers(self):
|
||||
'''Returns roles that have liaison statement approval authority for group'''
|
||||
|
||||
# a list of tuples, group query kwargs, role query kwargs
|
||||
GROUP_APPROVAL_MAPPING = [
|
||||
({'acronym':'ietf'},{'name':'chair'}),
|
||||
({'acronym':'iab'},{'name':'chair'}),
|
||||
({'type':'area'},{'name':'ad'}),
|
||||
({'type':'wg'},{'name':'ad'}), ]
|
||||
|
||||
for group_kwargs,role_kwargs in GROUP_APPROVAL_MAPPING:
|
||||
if self in Group.objects.filter(**group_kwargs):
|
||||
# TODO is there a cleaner way?
|
||||
if self.type == 'wg':
|
||||
return self.parent.role_set.filter(**role_kwargs)
|
||||
else:
|
||||
return self.role_set.filter(**role_kwargs)
|
||||
return self.role_set.none()
|
||||
|
||||
class GroupHistory(GroupInfo):
|
||||
group = models.ForeignKey(Group, related_name='history_set')
|
||||
acronym = models.CharField(max_length=40)
|
||||
|
|
|
@ -13,17 +13,14 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
from django.core.urlresolvers import NoReverseMatch
|
||||
|
||||
from ietf.doc.models import Document, DocAlias, DocEvent, State
|
||||
from ietf.group.models import Group, GroupEvent, GroupMilestone, GroupStateTransitions, MilestoneGroupEvent
|
||||
from ietf.group.models import Group, GroupEvent, GroupMilestone, GroupStateTransitions
|
||||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.name.models import DocTagName, GroupStateName, GroupTypeName
|
||||
from ietf.person.models import Person, Email
|
||||
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
|
||||
from ietf.group.mails import ( email_milestone_review_reminder, email_milestones_due,
|
||||
email_milestones_overdue, groups_needing_milestones_due_reminder,
|
||||
groups_needing_milestones_overdue_reminder, groups_with_milestones_needing_review )
|
||||
|
||||
class GroupPagesTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -482,6 +479,7 @@ class GroupEditTests(TestCase):
|
|||
area = group.parent
|
||||
ad = Person.objects.get(name="Aread Irector")
|
||||
state = GroupStateName.objects.get(slug="bof")
|
||||
empty_outbox()
|
||||
r = self.client.post(url,
|
||||
dict(name="Mars Not Special Interest Group",
|
||||
acronym="mars",
|
||||
|
@ -512,6 +510,10 @@ class GroupEditTests(TestCase):
|
|||
self.assertEqual(group.groupurl_set.all()[0].url, "http://mars.mars")
|
||||
self.assertEqual(group.groupurl_set.all()[0].name, "MARS site")
|
||||
self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('Personnel change' in outbox[0]['Subject'])
|
||||
for prefix in ['ad1','ad2','aread','marschairman','marsdelegate']:
|
||||
self.assertTrue(prefix+'@' in outbox[0]['To'])
|
||||
|
||||
def test_initial_charter(self):
|
||||
make_test_data()
|
||||
|
@ -551,6 +553,7 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(instructions="Test instructions"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue('iesg-secretary@' in outbox[-1]['To'])
|
||||
# the WG remains active until the Secretariat takes action
|
||||
group = Group.objects.get(acronym=group.acronym)
|
||||
self.assertEqual(group.state_id, "active")
|
||||
|
@ -653,6 +656,11 @@ class MilestoneTests(TestCase):
|
|||
self.assertTrue("Added milestone" in m.milestonegroupevent_set.all()[0].desc)
|
||||
self.assertEqual(len(outbox),mailbox_before+2)
|
||||
self.assertFalse(any('Review Required' in x['Subject'] for x in outbox[-2:]))
|
||||
self.assertTrue('Milestones changed' in outbox[-2]['Subject'])
|
||||
self.assertTrue('mars-chairs@' in outbox[-2]['To'])
|
||||
self.assertTrue('aread@' in outbox[-2]['To'])
|
||||
self.assertTrue('Milestones changed' in outbox[-1]['Subject'])
|
||||
self.assertTrue('mars-wg@' in outbox[-1]['To'])
|
||||
|
||||
def test_add_milestone_as_chair(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
@ -826,137 +834,6 @@ class MilestoneTests(TestCase):
|
|||
|
||||
self.assertEqual(group.charter.docevent_set.count(), events_before + 2) # 1 delete, 1 add
|
||||
|
||||
def test_send_review_needed_reminders(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
person = Person.objects.get(user__username="marschairman")
|
||||
|
||||
m1 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 1",
|
||||
due=datetime.date.today(),
|
||||
resolved="",
|
||||
state_id="review")
|
||||
MilestoneGroupEvent.objects.create(
|
||||
group=group, type="changed_milestone",
|
||||
by=person, desc='Added milestone "%s"' % m1.desc, milestone=m1,
|
||||
time=datetime.datetime.now() - datetime.timedelta(seconds=60))
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_with_milestones_needing_review():
|
||||
email_milestone_review_reminder(g)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before) # too early to send reminder
|
||||
|
||||
|
||||
# add earlier added milestone
|
||||
m2 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 2",
|
||||
due=datetime.date.today(),
|
||||
resolved="",
|
||||
state_id="review")
|
||||
MilestoneGroupEvent.objects.create(
|
||||
group=group, type="changed_milestone",
|
||||
by=person, desc='Added milestone "%s"' % m2.desc, milestone=m2,
|
||||
time=datetime.datetime.now() - datetime.timedelta(days=10))
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_with_milestones_needing_review():
|
||||
email_milestone_review_reminder(g)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue(group.acronym in outbox[-1]["Subject"])
|
||||
self.assertTrue(m1.desc in unicode(outbox[-1]))
|
||||
self.assertTrue(m2.desc in unicode(outbox[-1]))
|
||||
|
||||
def test_send_milestones_due_reminders(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
early_warning_days = 30
|
||||
|
||||
# due dates here aren't aligned on the last day of the month,
|
||||
# but everything should still work
|
||||
|
||||
m1 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 1",
|
||||
due=datetime.date.today(),
|
||||
resolved="Done",
|
||||
state_id="active")
|
||||
m2 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 2",
|
||||
due=datetime.date.today() + datetime.timedelta(days=early_warning_days - 10),
|
||||
resolved="",
|
||||
state_id="active")
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_needing_milestones_due_reminder(early_warning_days):
|
||||
email_milestones_due(g, early_warning_days)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before) # none found
|
||||
|
||||
m1.resolved = ""
|
||||
m1.save()
|
||||
|
||||
m2.due = datetime.date.today() + datetime.timedelta(days=early_warning_days)
|
||||
m2.save()
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_needing_milestones_due_reminder(early_warning_days):
|
||||
email_milestones_due(g, early_warning_days)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue(group.acronym in outbox[-1]["Subject"])
|
||||
self.assertTrue(m1.desc in unicode(outbox[-1]))
|
||||
self.assertTrue(m2.desc in unicode(outbox[-1]))
|
||||
|
||||
def test_send_milestones_overdue_reminders(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
# due dates here aren't aligned on the last day of the month,
|
||||
# but everything should still work
|
||||
|
||||
m1 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 1",
|
||||
due=datetime.date.today() - datetime.timedelta(days=200),
|
||||
resolved="Done",
|
||||
state_id="active")
|
||||
m2 = GroupMilestone.objects.create(group=group,
|
||||
desc="Test 2",
|
||||
due=datetime.date.today() - datetime.timedelta(days=10),
|
||||
resolved="",
|
||||
state_id="active")
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
|
||||
email_milestones_overdue(g)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before) # none found
|
||||
|
||||
m1.resolved = ""
|
||||
m1.save()
|
||||
|
||||
m2.due = self.last_day_of_month(datetime.date.today() - datetime.timedelta(days=300))
|
||||
m2.save()
|
||||
|
||||
# send
|
||||
mailbox_before = len(outbox)
|
||||
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
|
||||
email_milestones_overdue(g)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue(group.acronym in outbox[-1]["Subject"])
|
||||
self.assertTrue(m1.desc in unicode(outbox[-1]))
|
||||
self.assertTrue(m2.desc in unicode(outbox[-1]))
|
||||
|
||||
class CustomizeWorkflowTests(TestCase):
|
||||
def test_customize_workflow(self):
|
||||
make_test_data()
|
||||
|
@ -1049,27 +926,42 @@ expand-ames-chairs@virtual.ietf.org mars_chair@ietf
|
|||
def tearDown(self):
|
||||
os.unlink(self.group_alias_file.name)
|
||||
|
||||
def testEmailAliases(self):
|
||||
def testAliases(self):
|
||||
url = urlreverse('old_group_email_aliases', kwargs=dict(acronym="mars"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
for testdict in [dict(acronym="mars"),dict(acronym="mars",group_type="wg")]:
|
||||
url = urlreverse('ietf.group.info.email_aliases', kwargs=testdict)
|
||||
r = self.client.get(url)
|
||||
url = urlreverse('old_group_email_aliases', kwargs=testdict)
|
||||
r = self.client.get(url,follow=True)
|
||||
self.assertTrue(all([x in r.content for x in ['mars-ads@','mars-chairs@']]))
|
||||
self.assertFalse(any([x in r.content for x in ['ames-ads@','ames-chairs@']]))
|
||||
|
||||
url = urlreverse('ietf.group.info.email_aliases', kwargs=dict())
|
||||
login_testing_unauthorized(self, "plain", url)
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(r.status_code,200)
|
||||
self.assertTrue(all([x in r.content for x in ['mars-ads@','mars-chairs@','ames-ads@','ames-chairs@']]))
|
||||
|
||||
url = urlreverse('ietf.group.info.email_aliases', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertTrue('mars-ads@' in r.content)
|
||||
|
||||
url = urlreverse('ietf.group.info.email_aliases', kwargs=dict(group_type="rg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertFalse('mars-ads@' in r.content)
|
||||
|
||||
def testExpansions(self):
|
||||
url = urlreverse('ietf.group.info.email', kwargs=dict(acronym="mars"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertTrue('Email Aliases' in r.content)
|
||||
self.assertTrue('mars-ads@ietf.org' in r.content)
|
||||
self.assertTrue('group_personnel_change' in r.content)
|
||||
|
||||
|
||||
|
||||
class AjaxTests(TestCase):
|
||||
def test_group_menu_data(self):
|
||||
|
|
|
@ -15,4 +15,3 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.conf.urls import patterns
|
||||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', 'ietf.group.info.group_home', None, "group_home"),
|
||||
|
@ -7,6 +8,7 @@ urlpatterns = patterns('',
|
|||
(r'^charter/$', 'ietf.group.info.group_about', None, 'group_charter'),
|
||||
(r'^about/$', 'ietf.group.info.group_about', None, 'group_about'),
|
||||
(r'^history/$','ietf.group.info.history'),
|
||||
(r'^email/$', 'ietf.group.info.email'),
|
||||
(r'^deps/dot/$', 'ietf.group.info.dependencies_dot'),
|
||||
(r'^deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
|
||||
(r'^init-charter/', 'ietf.group.edit.submit_initial_charter'),
|
||||
|
@ -19,5 +21,5 @@ urlpatterns = patterns('',
|
|||
(r'^materials/$', 'ietf.group.info.materials', None, "group_materials"),
|
||||
(r'^materials/new/$', 'ietf.doc.views_material.choose_material_type'),
|
||||
(r'^materials/new/(?P<doc_type>[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"),
|
||||
(r'^/email-aliases/$', 'ietf.group.info.email_aliases'),
|
||||
url(r'^email-aliases/$', RedirectView.as_view(pattern_name='ietf.group.info.email',permanent=False),name='old_group_email_aliases'),
|
||||
)
|
||||
|
|
|
@ -5,13 +5,13 @@ from dateutil.tz import tzoffset
|
|||
import os
|
||||
import pytz
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.ipr.models import IprEvent
|
||||
from ietf.message.models import Message
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.log import log
|
||||
from ietf.mailtrigger.utils import get_base_ipr_request_address
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Date Functions
|
||||
|
@ -89,7 +89,7 @@ def get_pseudo_submitter(ipr):
|
|||
def get_reply_to():
|
||||
"""Returns a new reply-to address for use with an outgoing message. This is an
|
||||
address with "plus addressing" using a random string. Guaranteed to be unique"""
|
||||
local,domain = settings.IPR_EMAIL_TO.split('@')
|
||||
local,domain = get_base_ipr_request_address().split('@')
|
||||
while True:
|
||||
rand = base64.urlsafe_b64encode(os.urandom(12))
|
||||
address = "{}+{}@{}".format(local,rand,domain)
|
||||
|
@ -129,6 +129,8 @@ def get_update_submitter_emails(ipr):
|
|||
else:
|
||||
email_to_iprs[email] = [related.target]
|
||||
|
||||
# TODO: This has not been converted to use mailtrigger. It is complicated.
|
||||
# When converting it, it will need something like ipr_submitter_ietfer_or_holder perhaps
|
||||
for email in email_to_iprs:
|
||||
context = dict(
|
||||
to_email=email,
|
||||
|
@ -167,7 +169,7 @@ def process_response_email(msg):
|
|||
to = message.get('To')
|
||||
|
||||
# exit if this isn't a response we're interested in (with plus addressing)
|
||||
local,domain = settings.IPR_EMAIL_TO.split('@')
|
||||
local,domain = get_base_ipr_request_address().split('@')
|
||||
if not re.match(r'^{}\+[a-zA-Z0-9_\-]{}@{}'.format(local,'{16}',domain),to):
|
||||
return None
|
||||
|
||||
|
@ -193,4 +195,4 @@ def process_response_email(msg):
|
|||
)
|
||||
|
||||
log(u"Received IPR email from %s" % ietf_message.frm)
|
||||
return ietf_message
|
||||
return ietf_message
|
||||
|
|
|
@ -92,6 +92,18 @@ class IprDisclosureBase(models.Model):
|
|||
else:
|
||||
return None
|
||||
|
||||
def recursively_updates(self,disc_set=None):
|
||||
"""Returns the set of disclosures updated directly or transitively by this disclosure"""
|
||||
if disc_set == None:
|
||||
disc_set = set()
|
||||
new_candidates = set([y.target.get_child() for y in self.updates])
|
||||
unseen = new_candidates - disc_set
|
||||
disc_set.update(unseen)
|
||||
for disc in unseen:
|
||||
disc_set.update(disc.recursively_updates(disc_set))
|
||||
return disc_set
|
||||
|
||||
|
||||
class HolderIprDisclosure(IprDisclosureBase):
|
||||
ietfer_name = models.CharField(max_length=255, blank=True) # "Whose Personal Belief Triggered..."
|
||||
ietfer_contact_email = models.EmailField(blank=True)
|
||||
|
|
|
@ -3,7 +3,6 @@ import urllib
|
|||
|
||||
from pyquery import PyQuery
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.doc.models import DocAlias
|
||||
|
@ -15,7 +14,8 @@ from ietf.ipr.utils import get_genitive, get_ipr_summary
|
|||
from ietf.message.models import Message
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
|
||||
class IprTests(TestCase):
|
||||
|
@ -251,6 +251,7 @@ class IprTests(TestCase):
|
|||
self.assertTrue(len(q("form .has-error")) > 0)
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
"holder_legal_name": "Test Legal",
|
||||
"holder_contact_name": "Test Holder",
|
||||
|
@ -262,6 +263,9 @@ class IprTests(TestCase):
|
|||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("Your IPR disclosure has been submitted" in r.content)
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
|
||||
self.assertTrue('ietf-ipr@' in outbox[0]['To'])
|
||||
|
||||
iprs = IprDisclosureBase.objects.filter(title__icontains="General License Statement")
|
||||
self.assertEqual(len(iprs), 1)
|
||||
|
@ -277,6 +281,7 @@ class IprTests(TestCase):
|
|||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
"holder_legal_name": "Test Legal",
|
||||
"holder_contact_name": "Test Holder",
|
||||
|
@ -305,6 +310,9 @@ class IprTests(TestCase):
|
|||
self.assertEqual(ipr.holder_legal_name, "Test Legal")
|
||||
self.assertEqual(ipr.state.slug, 'pending')
|
||||
self.assertTrue(isinstance(ipr.get_child(),HolderIprDisclosure))
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
|
||||
self.assertTrue('ietf-ipr@' in outbox[0]['To'])
|
||||
|
||||
def test_new_thirdparty(self):
|
||||
"""Add a new third-party disclosure. Note: submitter does not need to be logged in.
|
||||
|
@ -313,6 +321,7 @@ class IprTests(TestCase):
|
|||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "third-party" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
"holder_legal_name": "Test Legal",
|
||||
"ietfer_name": "Test Participant",
|
||||
|
@ -338,6 +347,9 @@ class IprTests(TestCase):
|
|||
self.assertEqual(ipr.holder_legal_name, "Test Legal")
|
||||
self.assertEqual(ipr.state.slug, "pending")
|
||||
self.assertTrue(isinstance(ipr.get_child(),ThirdPartyIprDisclosure))
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
|
||||
self.assertTrue('ietf-ipr@' in outbox[0]['To'])
|
||||
|
||||
def test_update(self):
|
||||
draft = make_test_data()
|
||||
|
@ -345,6 +357,7 @@ class IprTests(TestCase):
|
|||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
"updates": str(original_ipr.pk),
|
||||
"holder_legal_name": "Test Legal",
|
||||
|
@ -374,6 +387,9 @@ class IprTests(TestCase):
|
|||
self.assertEqual(ipr.state.slug, 'pending')
|
||||
|
||||
self.assertTrue(ipr.relatedipr_source_set.filter(target=original_ipr))
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
|
||||
self.assertTrue('ietf-ipr@' in outbox[0]['To'])
|
||||
|
||||
def test_addcomment(self):
|
||||
make_test_data()
|
||||
|
@ -484,12 +500,15 @@ I would like to revoke this declaration.
|
|||
name = 'form-%d-type' % i
|
||||
data[name] = q('form input[name=%s]'%name).val()
|
||||
text_name = 'form-%d-text' % i
|
||||
data[text_name] = q('form textarea[name=%s]'%text_name).text()
|
||||
data[text_name] = q('form textarea[name=%s]'%text_name).html().strip()
|
||||
# Do not try to use
|
||||
#data[text_name] = q('form textarea[name=%s]'%text_name).text()
|
||||
# .text does not work - the field will likely contain <> characters
|
||||
r = self.client.post(url, data )
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.assertEqual(len(outbox),len_before+2)
|
||||
self.assertTrue('george@acme.com' in outbox[len_before]['To'])
|
||||
self.assertTrue('aread@ietf.org' in outbox[len_before+1]['To'])
|
||||
self.assertTrue('draft-ietf-mars-test@ietf.org' in outbox[len_before+1]['To'])
|
||||
self.assertTrue('mars-wg@ietf.org' in outbox[len_before+1]['Cc'])
|
||||
|
||||
def test_process_response_email(self):
|
||||
|
@ -506,6 +525,7 @@ I would like to revoke this declaration.
|
|||
reply_to=get_reply_to(),
|
||||
body='Testing.',
|
||||
response_due=yesterday.isoformat())
|
||||
empty_outbox()
|
||||
r = self.client.post(url,data,follow=True)
|
||||
#print r.content
|
||||
self.assertEqual(r.status_code,200)
|
||||
|
@ -513,13 +533,17 @@ I would like to revoke this declaration.
|
|||
self.assertEqual(q.count(),1)
|
||||
event = q[0].msgevents.first()
|
||||
self.assertTrue(event.response_past_due())
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue('joe@test.com' in outbox[0]['To'])
|
||||
|
||||
# test process response uninteresting message
|
||||
addrs = gather_address_lists('ipr_disclosure_submitted').as_strings()
|
||||
message_string = """To: {}
|
||||
Cc: {}
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: test
|
||||
""".format(settings.IPR_EMAIL_TO,datetime.datetime.now().ctime())
|
||||
""".format(addrs.to, addrs.cc, datetime.datetime.now().ctime())
|
||||
result = process_response_email(message_string)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@ from django.template.loader import render_to_string
|
|||
from ietf.doc.models import DocAlias
|
||||
from ietf.group.models import Role, Group
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails,
|
||||
get_update_cc_addrs)
|
||||
from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails)
|
||||
from ietf.ipr.fields import select2_id_ipr_title_json
|
||||
from ietf.ipr.forms import (HolderIprDisclosureForm, GenericDisclosureForm,
|
||||
ThirdPartyIprDisclosureForm, DraftForm, SearchForm, MessageModelForm,
|
||||
|
@ -35,6 +34,7 @@ from ietf.person.models import Person
|
|||
from ietf.secr.utils.document import get_rfc_num, is_draft
|
||||
from ietf.utils.draft_search import normalize_draftname
|
||||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Globals
|
||||
|
@ -79,13 +79,12 @@ def get_document_emails(ipr):
|
|||
else:
|
||||
cc_list = get_wg_email_list(doc.group)
|
||||
|
||||
author_emails = ','.join([a.address for a in authors])
|
||||
(to_list,cc_list) = gather_address_lists('ipr_posted_on_doc',doc=doc)
|
||||
author_names = ', '.join([a.person.name for a in authors])
|
||||
cc_list += ", ipr-announce@ietf.org"
|
||||
|
||||
context = dict(
|
||||
doc_info=doc_info,
|
||||
to_email=author_emails,
|
||||
to_email=to_list,
|
||||
to_name=author_names,
|
||||
cc_email=cc_list,
|
||||
ipr=ipr)
|
||||
|
@ -98,16 +97,15 @@ def get_posted_emails(ipr):
|
|||
"""Return a list of messages suitable to initialize a NotifyFormset for
|
||||
the notify view when a new disclosure is posted"""
|
||||
messages = []
|
||||
# NOTE 1000+ legacy iprs have no submitter_email
|
||||
# add submitter message
|
||||
if True:
|
||||
context = dict(
|
||||
to_email=ipr.submitter_email,
|
||||
to_name=ipr.submitter_name,
|
||||
cc_email=get_update_cc_addrs(ipr),
|
||||
ipr=ipr)
|
||||
text = render_to_string('ipr/posted_submitter_email.txt',context)
|
||||
messages.append(text)
|
||||
|
||||
addrs = gather_address_lists('ipr_posting_confirmation',ipr=ipr).as_strings(compact=False)
|
||||
context = dict(
|
||||
to_email=addrs.to,
|
||||
to_name=ipr.submitter_name,
|
||||
cc_email=addrs.cc,
|
||||
ipr=ipr)
|
||||
text = render_to_string('ipr/posted_submitter_email.txt',context)
|
||||
messages.append(text)
|
||||
|
||||
# add email to related document authors / parties
|
||||
if ipr.iprdocrel_set.all():
|
||||
|
@ -377,9 +375,11 @@ def email(request, id):
|
|||
|
||||
else:
|
||||
reply_to = get_reply_to()
|
||||
addrs = gather_address_lists('ipr_disclosure_followup',ipr=ipr).as_strings(compact=False)
|
||||
initial = {
|
||||
'to': ipr.submitter_email,
|
||||
'frm': settings.IPR_EMAIL_TO,
|
||||
'to': addrs.to,
|
||||
'cc': addrs.cc,
|
||||
'frm': settings.IPR_EMAIL_FROM,
|
||||
'subject': 'Regarding {}'.format(ipr.title),
|
||||
'reply_to': reply_to,
|
||||
}
|
||||
|
@ -474,10 +474,12 @@ def new(request, type, updates=None):
|
|||
desc="Disclosure Submitted")
|
||||
|
||||
# send email notification
|
||||
send_mail(request, settings.IPR_EMAIL_TO, ('IPR Submitter App', 'ietf-ipr@ietf.org'),
|
||||
(to, cc) = gather_address_lists('ipr_disclosure_submitted')
|
||||
send_mail(request, to, ('IPR Submitter App', 'ietf-ipr@ietf.org'),
|
||||
'New IPR Submission Notification',
|
||||
"ipr/new_update_email.txt",
|
||||
{"ipr": disclosure,})
|
||||
{"ipr": disclosure,},
|
||||
cc=cc)
|
||||
|
||||
return render(request, "ipr/submitted.html")
|
||||
|
||||
|
|
|
@ -4,18 +4,13 @@ from django.conf import settings
|
|||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.utils.mail import send_mail_text
|
||||
from ietf.liaisons.utils import approval_roles
|
||||
from ietf.group.models import Role
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
def send_liaison_by_email(request, liaison):
|
||||
subject = u'New Liaison Statement, "%s"' % (liaison.title)
|
||||
from_email = settings.LIAISON_UNIVERSAL_FROM
|
||||
to_email = liaison.to_contacts.split(',')
|
||||
cc = liaison.cc_contacts.split(',')
|
||||
if liaison.technical_contacts:
|
||||
cc += liaison.technical_contacts.split(',')
|
||||
if liaison.response_contacts:
|
||||
cc += liaison.response_contacts.split(',')
|
||||
(to_email, cc) = gather_address_lists('liaison_statement_posted',liaison=liaison)
|
||||
bcc = ['statements@ietf.org']
|
||||
body = render_to_string('liaisons/liaison_mail.txt', dict(liaison=liaison))
|
||||
|
||||
|
@ -25,16 +20,11 @@ def notify_pending_by_email(request, liaison):
|
|||
'''Send mail requesting approval of pending liaison statement. Send mail to
|
||||
the intersection of approvers for all from_groups
|
||||
'''
|
||||
approval_set = set(approval_roles(liaison.from_groups.first()))
|
||||
if liaison.from_groups.count() > 1:
|
||||
for group in liaison.from_groups.all():
|
||||
approval_set.intersection_update(approval_roles(group))
|
||||
to_emails = [ r.email.address for r in approval_set ]
|
||||
|
||||
subject = u'New Liaison Statement, "%s" needs your approval' % (liaison.title)
|
||||
from_email = settings.LIAISON_UNIVERSAL_FROM
|
||||
(to, cc) = gather_address_lists('liaison_approval_requested',liaison=liaison)
|
||||
body = render_to_string('liaisons/pending_liaison_mail.txt', dict(liaison=liaison))
|
||||
send_mail_text(request, to_emails, from_email, subject, body)
|
||||
send_mail_text(request, to, from_email, subject, body, cc=cc)
|
||||
|
||||
def send_sdo_reminder(sdo):
|
||||
roles = Role.objects.filter(name="liaiman", group=sdo)
|
||||
|
@ -43,7 +33,7 @@ def send_sdo_reminder(sdo):
|
|||
manager_role = roles[0]
|
||||
|
||||
subject = 'Request for update of list of authorized individuals'
|
||||
to_email = manager_role.email.address
|
||||
(to_email,cc) = gather_address_lists('liaison_manager_update_request',group=sdo)
|
||||
name = manager_role.person.plain_name()
|
||||
authorized_list = Role.objects.filter(group=sdo, name='auth').select_related("person").distinct()
|
||||
body = render_to_string('liaisons/sdo_reminder.txt', dict(
|
||||
|
@ -52,7 +42,7 @@ def send_sdo_reminder(sdo):
|
|||
individuals=authorized_list,
|
||||
))
|
||||
|
||||
send_mail_text(None, to_email, settings.LIAISON_UNIVERSAL_FROM, subject, body)
|
||||
send_mail_text(None, to_email, settings.LIAISON_UNIVERSAL_FROM, subject, body, cc=cc)
|
||||
|
||||
return body
|
||||
|
||||
|
@ -79,12 +69,7 @@ def possibly_send_deadline_reminder(liaison):
|
|||
days_msg = 'expires %s' % PREVIOUS_DAYS[days_to_go]
|
||||
|
||||
from_email = settings.LIAISON_UNIVERSAL_FROM
|
||||
to_email = liaison.to_contacts.split(',')
|
||||
cc = liaison.cc_contacts.split(',')
|
||||
if liaison.technical_contacts:
|
||||
cc += liaison.technical_contacts.split(',')
|
||||
if liaison.response_contacts:
|
||||
cc += liaison.response_contacts.split(',')
|
||||
(to_email, cc) = gather_address_lists('liaison_deadline_soon',liaison=liaison)
|
||||
bcc = 'statements@ietf.org'
|
||||
body = render_to_string('liaisons/liaison_deadline_mail.txt',
|
||||
dict(liaison=liaison,days_msg=days_msg,))
|
||||
|
|
|
@ -165,6 +165,17 @@ class LiaisonStatement(models.Model):
|
|||
self.state = state
|
||||
self.save()
|
||||
|
||||
def approver_emails(self):
|
||||
'''Send mail requesting approval of pending liaison statement. Send mail to
|
||||
the intersection of approvers for all from_groups
|
||||
'''
|
||||
approval_set = set()
|
||||
if self.from_groups.first():
|
||||
approval_set.update(self.from_groups.first().liaison_approvers())
|
||||
if self.from_groups.count() > 1:
|
||||
for group in self.from_groups.all():
|
||||
approval_set.intersection_update(group.liaison_approvers())
|
||||
return list(set([ r.email.address for r in approval_set ]))
|
||||
|
||||
class LiaisonStatementAttachment(models.Model):
|
||||
statement = models.ForeignKey(LiaisonStatement)
|
||||
|
|
|
@ -769,6 +769,9 @@ class LiaisonManagementTests(TestCase):
|
|||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
|
||||
|
||||
self.assertTrue('to_contacts@' in outbox[-1]['To'])
|
||||
self.assertTrue('cc@' in outbox[-1]['Cc'])
|
||||
|
||||
def test_add_outgoing_liaison(self):
|
||||
make_test_data()
|
||||
|
@ -843,6 +846,7 @@ class LiaisonManagementTests(TestCase):
|
|||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
|
||||
self.assertTrue('aread@' in outbox[-1]['To'])
|
||||
|
||||
def test_add_outgoing_liaison_unapproved_post_only(self):
|
||||
make_test_data()
|
||||
|
@ -1153,6 +1157,7 @@ class LiaisonManagementTests(TestCase):
|
|||
send_sdo_reminder(Group.objects.filter(type="sdo")[0])
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("authorized individuals" in outbox[-1]["Subject"])
|
||||
self.assertTrue('ulm-liaiman@' in outbox[-1]['To'])
|
||||
|
||||
def test_send_liaison_deadline_reminder(self):
|
||||
make_test_data()
|
||||
|
|
|
@ -1,30 +1,13 @@
|
|||
from itertools import chain
|
||||
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.group.models import Role
|
||||
from ietf.liaisons.models import LiaisonStatement
|
||||
from ietf.ietfauth.utils import has_role, passes_test_decorator
|
||||
|
||||
# a list of tuples, group query kwargs, role query kwargs
|
||||
GROUP_APPROVAL_MAPPING = [
|
||||
({'acronym':'ietf'},{'name':'chair'}),
|
||||
({'acronym':'iab'},{'name':'chair'}),
|
||||
({'type':'area'},{'name':'ad'}),
|
||||
({'type':'wg'},{'name':'ad'})]
|
||||
|
||||
can_submit_liaison_required = passes_test_decorator(
|
||||
lambda u, *args, **kwargs: can_add_liaison(u),
|
||||
"Restricted to participants who are authorized to submit liaison statements on behalf of the various IETF entities")
|
||||
|
||||
def approval_roles(group):
|
||||
'''Returns roles that have approval authority for group'''
|
||||
for group_kwargs,role_kwargs in GROUP_APPROVAL_MAPPING:
|
||||
if group in Group.objects.filter(**group_kwargs):
|
||||
# TODO is there a cleaner way?
|
||||
if group.type == 'wg':
|
||||
return Role.objects.filter(group=group.parent,**role_kwargs)
|
||||
else:
|
||||
return Role.objects.filter(group=group,**role_kwargs)
|
||||
|
||||
def approvable_liaison_statements(user):
|
||||
'''Returns a queryset of Liaison Statements in pending state that user has authority
|
||||
to approve'''
|
||||
|
@ -36,7 +19,7 @@ def approvable_liaison_statements(user):
|
|||
approvable_liaisons = []
|
||||
for liaison in liaisons:
|
||||
for group in liaison.from_groups.all():
|
||||
if person not in [ r.person for r in approval_roles(group) ]:
|
||||
if person not in [ r.person for r in group.liaison_approvers() ]:
|
||||
break
|
||||
else:
|
||||
approvable_liaisons.append(liaison.pk)
|
||||
|
|
0
ietf/mailtrigger/__init__.py
Normal file
0
ietf/mailtrigger/__init__.py
Normal file
17
ietf/mailtrigger/admin.py
Normal file
17
ietf/mailtrigger/admin.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from ietf.mailtrigger.models import MailTrigger, Recipient
|
||||
|
||||
class RecipientAdmin(admin.ModelAdmin):
|
||||
list_display = [ 'slug', 'desc', 'template', 'has_code', ]
|
||||
def has_code(self, obj):
|
||||
return hasattr(obj,'gather_%s'%obj.slug)
|
||||
has_code.boolean = True
|
||||
admin.site.register(Recipient, RecipientAdmin)
|
||||
|
||||
|
||||
class MailTriggerAdmin(admin.ModelAdmin):
|
||||
list_display = [ 'slug', 'desc', ]
|
||||
filter_horizontal = [ 'to', 'cc', ]
|
||||
admin.site.register(MailTrigger, MailTriggerAdmin)
|
||||
|
31
ietf/mailtrigger/forms.py
Normal file
31
ietf/mailtrigger/forms.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from django import forms
|
||||
|
||||
from ietf.mailtrigger.models import MailTrigger
|
||||
|
||||
class CcSelectForm(forms.Form):
|
||||
expansions = dict()
|
||||
cc_choices = forms.MultipleChoiceField(
|
||||
label='Cc',
|
||||
choices=[],
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
)
|
||||
|
||||
def __init__(self, mailtrigger_slug, mailtrigger_context, *args, **kwargs):
|
||||
super(CcSelectForm,self).__init__(*args,**kwargs)
|
||||
mailtrigger = MailTrigger.objects.get(slug=mailtrigger_slug)
|
||||
|
||||
for r in mailtrigger.cc.all():
|
||||
self.expansions[r.slug] = r.gather(**mailtrigger_context)
|
||||
|
||||
non_empty_expansions = [x for x in self.expansions if self.expansions[x]]
|
||||
self.fields['cc_choices'].initial = non_empty_expansions
|
||||
self.fields['cc_choices'].choices = [(t,'%s: %s'%(t,", ".join(self.expansions[t]))) for t in non_empty_expansions]
|
||||
|
||||
def get_selected_addresses(self):
|
||||
if self.is_valid():
|
||||
addrs = []
|
||||
for t in self.cleaned_data['cc_choices']:
|
||||
addrs.extend(self.expansions[t])
|
||||
return addrs
|
||||
else:
|
||||
raise forms.ValidationError('Cannot get selected addresses from an invalid form.')
|
48
ietf/mailtrigger/migrations/0001_initial.py
Normal file
48
ietf/mailtrigger/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MailTrigger',
|
||||
fields=[
|
||||
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
|
||||
('desc', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['slug'],
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Recipient',
|
||||
fields=[
|
||||
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
|
||||
('desc', models.TextField(blank=True)),
|
||||
('template', models.TextField(null=True, blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['slug'],
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailtrigger',
|
||||
name='cc',
|
||||
field=models.ManyToManyField(related_name='used_in_cc', null=True, to='mailtrigger.Recipient', blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailtrigger',
|
||||
name='to',
|
||||
field=models.ManyToManyField(related_name='used_in_to', null=True, to='mailtrigger.Recipient', blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
849
ietf/mailtrigger/migrations/0002_auto_20150809_1314.py
Normal file
849
ietf/mailtrigger/migrations/0002_auto_20150809_1314.py
Normal file
|
@ -0,0 +1,849 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def make_recipients(apps):
|
||||
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
|
||||
rc = Recipient.objects.create
|
||||
|
||||
rc(slug='iesg',
|
||||
desc='The IESG',
|
||||
template='The IESG <iesg@ietf.org>')
|
||||
|
||||
rc(slug='iab',
|
||||
desc='The IAB',
|
||||
template='The IAB <iab@iab.org>')
|
||||
|
||||
rc(slug='ietf_announce',
|
||||
desc='The IETF Announce list',
|
||||
template='IETF-Announce <ietf-announce@ietf.org>')
|
||||
|
||||
rc(slug='rfc_editor',
|
||||
desc='The RFC Editor',
|
||||
template='<rfc-editor@rfc-editor.org>')
|
||||
|
||||
rc(slug='iesg_secretary',
|
||||
desc='The Secretariat',
|
||||
template='<iesg-secretary@ietf.org>')
|
||||
|
||||
rc(slug='ietf_secretariat',
|
||||
desc='The Secretariat',
|
||||
template='<ietf-secretariat-reply@ietf.org>')
|
||||
|
||||
rc(slug='doc_authors',
|
||||
desc="The document's authors",
|
||||
template='{% if doc.type_id == "draft" %}<{{doc.name}}@ietf.org>{% endif %}')
|
||||
|
||||
rc(slug='doc_notify',
|
||||
desc="The addresses in the document's notify field",
|
||||
template='{{doc.notify}}')
|
||||
|
||||
rc(slug='doc_group_chairs',
|
||||
desc="The document's group chairs (if the document is assigned to a working or research group)",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_group_delegates',
|
||||
desc="The document's group delegates (if the document is assigned to a working or research group)",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_affecteddoc_authors',
|
||||
desc="The authors of the subject documents of a conflict-review or status-change",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_affecteddoc_group_chairs',
|
||||
desc="The chairs of groups of the subject documents of a conflict-review or status-change",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_affecteddoc_notify',
|
||||
desc="The notify field of the subject documents of a conflict-review or status-change",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_shepherd',
|
||||
desc="The document's shepherd",
|
||||
template='{% if doc.shepherd %}<{{doc.shepherd.address}}>{% endif %}' )
|
||||
|
||||
rc(slug='doc_ad',
|
||||
desc="The document's responsible Area Director",
|
||||
template='{% if doc.ad %}<{{doc.ad.email_address}}>{% endif %}' )
|
||||
|
||||
rc(slug='doc_group_mail_list',
|
||||
desc="The list address of the document's group",
|
||||
template=None )
|
||||
|
||||
rc(slug='doc_stream_manager',
|
||||
desc="The manager of the document's stream",
|
||||
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",
|
||||
template=None )
|
||||
|
||||
rc(slug='conflict_review_steering_group',
|
||||
desc="The steering group (e.g. IRSG) of a document being reviewed for IETF stream conflicts",
|
||||
template = None)
|
||||
|
||||
rc(slug='iana_approve',
|
||||
desc="IANA's draft approval address",
|
||||
template='IANA <drafts-approval@icann.org>')
|
||||
|
||||
rc(slug='iana_last_call',
|
||||
desc="IANA's draft last call address",
|
||||
template='IANA <drafts-lastcall@icann.org>')
|
||||
|
||||
rc(slug='iana_eval',
|
||||
desc="IANA's draft evaluation address",
|
||||
template='IANA <drafts-eval@icann.org>')
|
||||
|
||||
rc(slug='iana',
|
||||
desc="IANA",
|
||||
template='<iana@iana.org>')
|
||||
|
||||
rc(slug='group_mail_list',
|
||||
desc="The group's mailing list",
|
||||
template='{% if group.list_email %}<{{ group.list_email }}>{% endif %}')
|
||||
|
||||
rc(slug='group_steering_group',
|
||||
desc="The group's steering group (IESG or IRSG)",
|
||||
template=None)
|
||||
|
||||
rc(slug='group_chairs',
|
||||
desc="The group's chairs",
|
||||
template="{% if group and group.acronym %}<{{group.acronym}}-chairs@ietf.org>{% endif %}")
|
||||
|
||||
rc(slug='group_responsible_directors',
|
||||
desc="The group's responsible AD(s) or IRTF chair",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_group_responsible_directors',
|
||||
desc="The document's group's responsible AD(s) or IRTF chair",
|
||||
template=None)
|
||||
|
||||
rc(slug='internet_draft_requests',
|
||||
desc="The internet drafts ticketing system",
|
||||
template='<internet-drafts@ietf.org>')
|
||||
|
||||
rc(slug='submission_submitter',
|
||||
desc="The person that submitted a draft",
|
||||
template='{{submission.submitter}}')
|
||||
|
||||
rc(slug='submission_authors',
|
||||
desc="The authors of a submitted draft",
|
||||
template=None)
|
||||
|
||||
rc(slug='submission_group_chairs',
|
||||
desc="The chairs of a submitted draft belonging to a group",
|
||||
template=None)
|
||||
|
||||
rc(slug='submission_confirmers',
|
||||
desc="The people who can confirm a draft submission",
|
||||
template=None)
|
||||
|
||||
rc(slug='submission_group_mail_list',
|
||||
desc="The people who can confirm a draft submission",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_non_ietf_stream_manager',
|
||||
desc="The document's stream manager if the document is not in the IETF stream",
|
||||
template=None)
|
||||
|
||||
rc(slug='rfc_editor_if_doc_in_queue',
|
||||
desc="The RFC Editor if a document is in the RFC Editor queue",
|
||||
template=None)
|
||||
|
||||
rc(slug='doc_discussing_ads',
|
||||
desc="Any ADs holding an active DISCUSS position on a given document",
|
||||
template=None)
|
||||
|
||||
rc(slug='group_changed_personnel',
|
||||
desc="Any personnel who were added or deleted when a group's personnel changes",
|
||||
template='{{ changed_personnel | join:", " }}')
|
||||
|
||||
rc(slug='session_requests',
|
||||
desc="The session request ticketing system",
|
||||
template='<session-request@ietf.org>')
|
||||
|
||||
rc(slug='session_requester',
|
||||
desc="The person that requested a meeting slot for a given group",
|
||||
template=None)
|
||||
|
||||
rc(slug='logged_in_person',
|
||||
desc="The person currently logged into the datatracker who initiated a given action",
|
||||
template='{% if person and person.email_address %}<{{ person.email_address }}>{% endif %}')
|
||||
|
||||
rc(slug='ipr_requests',
|
||||
desc="The ipr disclosure handling system",
|
||||
template='<ietf-ipr@ietf.org>')
|
||||
|
||||
rc(slug='ipr_submitter',
|
||||
desc="The submitter of an IPR disclosure",
|
||||
template='{% if ipr.submitter_email %}{{ ipr.submitter_email }}{% endif %}')
|
||||
|
||||
rc(slug='ipr_updatedipr_contacts',
|
||||
desc="The submitter (or ietf participant if the submitter is not available) "
|
||||
"of all IPR disclosures updated directly by this disclosure, without recursing "
|
||||
"to what the updated disclosures might have updated.",
|
||||
template=None)
|
||||
|
||||
rc(slug='ipr_updatedipr_holders',
|
||||
desc="The holders of all IPR disclosures updated by disclosure and disclosures updated by those and so on.",
|
||||
template=None)
|
||||
|
||||
rc(slug='ipr_announce',
|
||||
desc="The IETF IPR announce list",
|
||||
template='ipr-announce@ietf.org')
|
||||
|
||||
rc(slug='doc_ipr_group_or_ad',
|
||||
desc="Leadership for a document that has a new IPR disclosure",
|
||||
template=None)
|
||||
|
||||
rc(slug='liaison_to_contacts',
|
||||
desc="The addresses captured in the To field of the liaison statement form",
|
||||
template='{{liaison.to_contacts}}')
|
||||
|
||||
rc(slug='liaison_cc',
|
||||
desc="The addresses captured in the Cc field of the liaison statement form",
|
||||
template='{{liaison.cc_contacts}}')
|
||||
|
||||
rc(slug='liaison_technical_contacts',
|
||||
desc="The addresses captured in the technical contact field of the liaison statement form",
|
||||
template='{{liaison.technical_contacts}}')
|
||||
|
||||
rc(slug='liaison_response_contacts',
|
||||
desc="The addresses captured in the response contact field of the liaison statement form",
|
||||
template='{{liaison.response_contacts}}')
|
||||
|
||||
rc(slug='liaison_approvers',
|
||||
desc="The set of people who can approve this liasion statemetns",
|
||||
template='{{liaison.approver_emails|join:", "}}')
|
||||
|
||||
rc(slug='liaison_manager',
|
||||
desc="The assigned liaison manager for an external group ",
|
||||
template=None)
|
||||
|
||||
rc(slug='nominator',
|
||||
desc="The person that submitted a nomination to nomcom",
|
||||
template='{{nominator}}')
|
||||
|
||||
rc(slug='nominee',
|
||||
desc="The person nominated for a position",
|
||||
template='{{nominee}}')
|
||||
|
||||
rc(slug='nomcom_chair',
|
||||
desc="The chair of a given nomcom",
|
||||
template='{{nomcom.group.get_chair.email.address}}')
|
||||
|
||||
rc(slug='commenter',
|
||||
desc="The person providing a comment to nomcom",
|
||||
template='{{commenter}}')
|
||||
|
||||
rc(slug='new_work',
|
||||
desc="The IETF New Work list",
|
||||
template='<new-work@ietf.org>')
|
||||
|
||||
def make_mailtriggers(apps):
|
||||
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
|
||||
def mt_factory(slug,desc,to_slugs,cc_slugs=[]):
|
||||
|
||||
# Try to protect ourselves from typos
|
||||
all_slugs = to_slugs[:]
|
||||
all_slugs.extend(cc_slugs)
|
||||
for recipient_slug in all_slugs:
|
||||
try:
|
||||
Recipient.objects.get(slug=recipient_slug)
|
||||
except Recipient.DoesNotExist:
|
||||
print "****Some rule tried to use",recipient_slug
|
||||
raise
|
||||
|
||||
m = MailTrigger.objects.create(slug=slug, desc=desc)
|
||||
m.to = Recipient.objects.filter(slug__in=to_slugs)
|
||||
m.cc = Recipient.objects.filter(slug__in=cc_slugs)
|
||||
|
||||
mt_factory(slug='ballot_saved',
|
||||
desc="Recipients when a new ballot position "
|
||||
"(with discusses, other blocking positions, "
|
||||
"or comments) is saved",
|
||||
to_slugs=['iesg'],
|
||||
cc_slugs=['doc_notify',
|
||||
'doc_group_mail_list',
|
||||
'doc_authors',
|
||||
'doc_group_chairs',
|
||||
'doc_shepherd',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
'conflict_review_stream_manager',
|
||||
]
|
||||
)
|
||||
|
||||
mt_factory(slug='ballot_deferred',
|
||||
desc="Recipients when a ballot is deferred to "
|
||||
"or undeferred from a future telechat",
|
||||
to_slugs=['iesg',
|
||||
'iesg_secretary',
|
||||
'doc_group_chairs',
|
||||
'doc_notify',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
'conflict_review_stream_manager',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ballot_approved_ietf_stream',
|
||||
desc="Recipients when an IETF stream document ballot is approved",
|
||||
to_slugs=['ietf_announce'],
|
||||
cc_slugs=['iesg',
|
||||
'doc_notify',
|
||||
'doc_ad',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_group_mail_list',
|
||||
'doc_group_chairs',
|
||||
'rfc_editor',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ballot_approved_ietf_stream_iana',
|
||||
desc="Recipients for IANA message when an IETF stream document ballot is approved",
|
||||
to_slugs=['iana_approve'])
|
||||
|
||||
mt_factory(slug='ballot_approved_conflrev',
|
||||
desc="Recipients when a conflict review ballot is approved",
|
||||
to_slugs=['conflict_review_stream_manager',
|
||||
'conflict_review_steering_group',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
'doc_notify',
|
||||
],
|
||||
cc_slugs=['iesg',
|
||||
'ietf_announce',
|
||||
'iana',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ballot_approved_charter',
|
||||
desc="Recipients when a charter is approved",
|
||||
to_slugs=['ietf_announce',],
|
||||
cc_slugs=['group_mail_list',
|
||||
'group_steering_group',
|
||||
'group_chairs',
|
||||
'doc_notify',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ballot_approved_status_change',
|
||||
desc="Recipients when a status change is approved",
|
||||
to_slugs=['ietf_announce',],
|
||||
cc_slugs=['iesg',
|
||||
'rfc_editor',
|
||||
'doc_notify',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
],
|
||||
)
|
||||
|
||||
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',],
|
||||
cc_slugs=['doc_ad',
|
||||
'doc_shepherd',
|
||||
'doc_notify',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='last_call_issued',
|
||||
desc="Recipients when a last call is issued",
|
||||
to_slugs=['ietf_announce',],
|
||||
cc_slugs=['doc_ad',
|
||||
'doc_shepherd',
|
||||
'doc_authors',
|
||||
'doc_notify',
|
||||
'doc_group_mail_list',
|
||||
'doc_group_chairs',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
]
|
||||
)
|
||||
|
||||
mt_factory(slug='last_call_issued_iana',
|
||||
desc="Recipients for IANA message when a last call is issued",
|
||||
to_slugs=['iana_last_call'])
|
||||
|
||||
mt_factory(slug='last_call_expired',
|
||||
desc="Recipients when a last call has expired",
|
||||
to_slugs=['doc_ad',
|
||||
'doc_notify',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
],
|
||||
cc_slugs=['iesg_secretary',],
|
||||
)
|
||||
|
||||
mt_factory(slug='pubreq_iesg',
|
||||
desc="Recipients when a draft is submitted to the IESG",
|
||||
to_slugs=['doc_ad',],
|
||||
cc_slugs=['iesg_secretary',
|
||||
'doc_notify',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='pubreq_rfced',
|
||||
desc="Recipients when a non-IETF stream manager requests publication",
|
||||
to_slugs=['rfc_editor',])
|
||||
|
||||
mt_factory(slug='pubreq_rfced_iana',
|
||||
desc="Recipients for IANA message when a non-IETF stream manager "
|
||||
"requests publication",
|
||||
to_slugs=['iana_approve',])
|
||||
|
||||
mt_factory(slug='charter_internal_review',
|
||||
desc="Recipients for message noting that internal review has "
|
||||
"started on a charter",
|
||||
to_slugs=['iesg',
|
||||
'iab',
|
||||
])
|
||||
|
||||
mt_factory(slug='charter_external_review',
|
||||
desc="Recipients for a charter external review",
|
||||
to_slugs=['ietf_announce',],
|
||||
cc_slugs=['group_mail_list',],
|
||||
)
|
||||
|
||||
mt_factory(slug='charter_external_review_new_work',
|
||||
desc="Recipients for a message to new-work about a charter review",
|
||||
to_slugs=['new_work',])
|
||||
|
||||
mt_factory(slug='conflrev_requested',
|
||||
desc="Recipients for a stream manager's request for an IETF conflict review",
|
||||
to_slugs=['iesg_secretary'],
|
||||
cc_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",
|
||||
to_slugs=['iana_eval',])
|
||||
|
||||
mt_factory(slug='doc_stream_changed',
|
||||
desc="Recipients for notification when a document's stream changes",
|
||||
to_slugs=['doc_authors',
|
||||
'stream_managers',
|
||||
'doc_notify',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_stream_state_edited',
|
||||
desc="Recipients when the stream state of a document is manually edited",
|
||||
to_slugs=['doc_group_chairs',
|
||||
'doc_group_delegates',
|
||||
'doc_shepherd',
|
||||
'doc_authors',
|
||||
])
|
||||
|
||||
mt_factory(slug='group_milestones_edited',
|
||||
desc="Recipients when any of a group's milestones are edited",
|
||||
to_slugs=['group_responsible_directors',
|
||||
'group_chairs',
|
||||
])
|
||||
|
||||
mt_factory(slug='group_approved_milestones_edited',
|
||||
desc="Recipients when the set of approved milestones for a group are edited",
|
||||
to_slugs=['group_mail_list',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_state_edited',
|
||||
desc="Recipients when a document's state is manually edited",
|
||||
to_slugs=['doc_notify',
|
||||
'doc_ad',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_group_responsible_directors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_iana_state_changed',
|
||||
desc="Recipients when IANA state information for a document changes ",
|
||||
to_slugs=['doc_notify',
|
||||
'doc_ad',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_telechat_details_changed',
|
||||
desc="Recipients when a document's telechat date or other "
|
||||
"telechat specific details are changed",
|
||||
to_slugs=['iesg',
|
||||
'iesg_secretary',
|
||||
'doc_notify',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_affecteddoc_authors',
|
||||
'doc_affecteddoc_group_chairs',
|
||||
'doc_affecteddoc_notify',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_pulled_from_rfc_queue',
|
||||
desc="Recipients when a document is taken out of the RFC's editor queue "
|
||||
"before publication",
|
||||
to_slugs=['iana',
|
||||
'rfc_editor',
|
||||
],
|
||||
cc_slugs=['iesg_secretary',
|
||||
'iesg',
|
||||
'doc_notify',
|
||||
'doc_authors',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='doc_replacement_changed',
|
||||
desc="Recipients when what a document replaces or is replaced by changes",
|
||||
to_slugs=['doc_authors',
|
||||
'doc_notify',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_group_responsible_directors',
|
||||
])
|
||||
|
||||
mt_factory(slug='charter_state_edit_admin_needed',
|
||||
desc="Recipients for message to adminstrators when a charter state edit "
|
||||
"needs followon administrative action",
|
||||
to_slugs=['iesg_secretary'])
|
||||
|
||||
mt_factory(slug='group_closure_requested',
|
||||
desc="Recipients for message requesting closure of a group",
|
||||
to_slugs=['iesg_secretary'])
|
||||
|
||||
mt_factory(slug='doc_expires_soon',
|
||||
desc="Recipients for notification of impending expiration of a document",
|
||||
to_slugs=['doc_authors'],
|
||||
cc_slugs=['doc_notify',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_group_responsible_directors',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='doc_expired',
|
||||
desc="Recipients for notification of a document's expiration",
|
||||
to_slugs=['doc_authors'],
|
||||
cc_slugs=['doc_notify',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
'doc_group_responsible_directors',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='resurrection_requested',
|
||||
desc="Recipients of a request to change the state of a draft away from 'Dead'",
|
||||
to_slugs=['internet_draft_requests',])
|
||||
|
||||
mt_factory(slug='resurrection_completed',
|
||||
desc="Recipients when a draft resurrection request has been completed",
|
||||
to_slugs=['iesg_secretary',
|
||||
'doc_ad',
|
||||
])
|
||||
|
||||
mt_factory(slug='sub_manual_post_requested',
|
||||
desc="Recipients for a manual post request for a draft submission",
|
||||
to_slugs=['internet_draft_requests',],
|
||||
cc_slugs=['submission_submitter',
|
||||
'submission_authors',
|
||||
'submission_group_chairs',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='sub_chair_approval_requested',
|
||||
desc="Recipients for a message requesting group chair approval of "
|
||||
"a draft submission",
|
||||
to_slugs=['submission_group_chairs',])
|
||||
|
||||
mt_factory(slug='sub_confirmation_requested',
|
||||
desc="Recipients for a message requesting confirmation of a draft submission",
|
||||
to_slugs=['submission_confirmers',])
|
||||
|
||||
mt_factory(slug='sub_management_url_requested',
|
||||
desc="Recipients for a message with the full URL for managing a draft submission",
|
||||
to_slugs=['submission_confirmers',])
|
||||
|
||||
mt_factory(slug='sub_announced',
|
||||
desc="Recipients for the announcement of a successfully submitted draft",
|
||||
to_slugs=['ietf_announce',
|
||||
],
|
||||
cc_slugs=['submission_group_mail_list',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='sub_announced_to_authors',
|
||||
desc="Recipients for the announcement to the authors of a successfully "
|
||||
"submitted draft",
|
||||
to_slugs=['submission_authors',
|
||||
'submission_confirmers',
|
||||
])
|
||||
|
||||
mt_factory(slug='sub_new_version',
|
||||
desc="Recipients for notification of a new version of an existing document",
|
||||
to_slugs=['doc_notify',
|
||||
'doc_ad',
|
||||
'doc_non_ietf_stream_manager',
|
||||
'rfc_editor_if_doc_in_queue',
|
||||
'doc_discussing_ads',
|
||||
])
|
||||
|
||||
mt_factory(slug='group_personnel_change',
|
||||
desc="Recipients for a message noting changes in a group's personnel",
|
||||
to_slugs=['iesg_secretary',
|
||||
'group_responsible_directors',
|
||||
'group_chairs',
|
||||
'group_changed_personnel',
|
||||
])
|
||||
|
||||
mt_factory(slug='session_requested',
|
||||
desc="Recipients for a normal meeting session request",
|
||||
to_slugs=['session_requests', ],
|
||||
cc_slugs=['group_mail_list',
|
||||
'group_chairs',
|
||||
'group_responsible_directors',
|
||||
'logged_in_person',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='session_requested_long',
|
||||
desc="Recipients for a meeting session request for more than 2 sessions",
|
||||
to_slugs=['group_responsible_directors', ],
|
||||
cc_slugs=['session_requests',
|
||||
'group_chairs',
|
||||
'logged_in_person',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='session_request_cancelled',
|
||||
desc="Recipients for a message cancelling a session request",
|
||||
to_slugs=['session_requests', ],
|
||||
cc_slugs=['group_mail_list',
|
||||
'group_chairs',
|
||||
'group_responsible_directors',
|
||||
'logged_in_person',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='session_request_not_meeting',
|
||||
desc="Recipients for a message noting a group plans to not meet",
|
||||
to_slugs=['session_requests', ],
|
||||
cc_slugs=['group_mail_list',
|
||||
'group_chairs',
|
||||
'group_responsible_directors',
|
||||
'logged_in_person',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='session_scheduled',
|
||||
desc="Recipients for details when a session has been scheduled",
|
||||
to_slugs=['session_requester',
|
||||
'group_chairs',
|
||||
],
|
||||
cc_slugs=['group_mail_list',
|
||||
'group_responsible_directors',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ipr_disclosure_submitted',
|
||||
desc="Recipients when an IPR disclosure is submitted",
|
||||
to_slugs=['ipr_requests', ])
|
||||
|
||||
mt_factory(slug='ipr_disclosure_followup',
|
||||
desc="Recipients when the secretary follows up on an IPR disclosure submission",
|
||||
to_slugs=['ipr_submitter', ],)
|
||||
|
||||
mt_factory(slug='ipr_posting_confirmation',
|
||||
desc="Recipients for a message confirming that a disclosure has been posted",
|
||||
to_slugs=['ipr_submitter', ],
|
||||
cc_slugs=['ipr_updatedipr_contacts',
|
||||
'ipr_updatedipr_holders',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='ipr_posted_on_doc',
|
||||
desc="Recipients when an IPR disclosure calls out a given document",
|
||||
to_slugs=['doc_authors', ],
|
||||
cc_slugs=['doc_ipr_group_or_ad',
|
||||
'ipr_announce',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='liaison_statement_posted',
|
||||
desc="Recipient for a message when a new liaison statement is posted",
|
||||
to_slugs=['liaison_to_contacts', ],
|
||||
cc_slugs=['liaison_cc',
|
||||
'liaison_technical_contacts',
|
||||
'liaison_response_contacts',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='liaison_approval_requested',
|
||||
desc="Recipients for a message that a pending liaison statement needs approval",
|
||||
to_slugs=['liaison_approvers',
|
||||
])
|
||||
|
||||
mt_factory(slug='liaison_deadline_soon',
|
||||
desc="Recipients for a message about a liaison statement deadline that is "
|
||||
"approaching.",
|
||||
to_slugs=['liaison_to_contacts',
|
||||
],
|
||||
cc_slugs=['liaison_cc',
|
||||
'liaison_technical_contacts',
|
||||
'liaison_response_contacts',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='liaison_manager_update_request',
|
||||
desc="Recipients for a message requesting an updated list of authorized individuals",
|
||||
to_slugs=['liaison_manager', ])
|
||||
|
||||
mt_factory(slug='nomination_received',
|
||||
desc="Recipients for a message noting a new nomination has been received",
|
||||
to_slugs=['nomcom_chair', ])
|
||||
|
||||
mt_factory(slug='nomination_receipt_requested',
|
||||
desc="Recipients for a message confirming a nomination was made",
|
||||
to_slugs=['nominator', ])
|
||||
|
||||
mt_factory(slug='nomcom_comment_receipt_requested',
|
||||
desc="Recipients for a message confirming a comment was made",
|
||||
to_slugs=['commenter', ])
|
||||
|
||||
mt_factory(slug='nomination_created_person',
|
||||
desc="Recipients for a message noting that a nomination caused a "
|
||||
"new Person record to be created in the datatracker",
|
||||
to_slugs=['ietf_secretariat',
|
||||
'nomcom_chair',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='nomination_new_nominee',
|
||||
desc="Recipients the first time a person is nominated for a position, "
|
||||
"asking them to accept or decline the nomination",
|
||||
to_slugs=['nominee', ])
|
||||
|
||||
mt_factory(slug='nomination_accept_reminder',
|
||||
desc="Recipeints of message reminding a nominee to accept or decline a nomination",
|
||||
to_slugs=['nominee', ])
|
||||
|
||||
mt_factory(slug='nomcom_questionnaire',
|
||||
desc="Recipients for the questionairre that nominees should complete",
|
||||
to_slugs=['nominee', ])
|
||||
|
||||
mt_factory(slug='nomcom_questionnaire_reminder',
|
||||
desc="Recipients for a message reminding a nominee to return a "
|
||||
"completed questionairre response",
|
||||
to_slugs=['nominee', ])
|
||||
|
||||
mt_factory(slug='doc_replacement_suggested',
|
||||
desc="Recipients for suggestions that this doc replaces or is replace by "
|
||||
"some other document",
|
||||
to_slugs=['doc_group_chairs',
|
||||
'doc_group_responsible_directors',
|
||||
'doc_non_ietf_stream_manager',
|
||||
'iesg_secretary',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_adopted_by_group',
|
||||
desc="Recipients for notification that a document has been adopted by a group",
|
||||
to_slugs=['doc_authors',
|
||||
'doc_group_chairs',
|
||||
'doc_group_mail_list',
|
||||
],
|
||||
cc_slugs=['doc_ad',
|
||||
'doc_shepherd',
|
||||
'doc_notify',
|
||||
],
|
||||
)
|
||||
|
||||
mt_factory(slug='doc_added_comment',
|
||||
desc="Recipients for a message when a new comment is manually entered into the document's history",
|
||||
to_slugs=['doc_authors',
|
||||
'doc_group_chairs',
|
||||
'doc_shepherd',
|
||||
'doc_group_responsible_directors',
|
||||
'doc_non_ietf_stream_manager',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_intended_status_changed',
|
||||
desc="Recipients for a message when a document's intended "
|
||||
"publication status changes",
|
||||
to_slugs=['doc_authors',
|
||||
'doc_group_chairs',
|
||||
'doc_shepherd',
|
||||
'doc_group_responsible_directors',
|
||||
'doc_non_ietf_stream_manager',
|
||||
])
|
||||
|
||||
mt_factory(slug='doc_iesg_processing_started',
|
||||
desc="Recipients for a message when the IESG begins processing a document ",
|
||||
to_slugs=['doc_authors',
|
||||
'doc_ad',
|
||||
'doc_shepherd',
|
||||
'doc_group_chairs',
|
||||
])
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
|
||||
make_recipients(apps)
|
||||
make_mailtriggers(apps)
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
MailTrigger=apps.get_model('mailtrigger','MailTrigger')
|
||||
|
||||
Recipient.objects.all().delete()
|
||||
MailTrigger.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
0
ietf/mailtrigger/migrations/__init__.py
Normal file
0
ietf/mailtrigger/migrations/__init__.py
Normal file
262
ietf/mailtrigger/models.py
Normal file
262
ietf/mailtrigger/models.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
# Copyright The IETF Trust 2015, All Rights Reserved
|
||||
|
||||
from django.db import models
|
||||
from django.template import Template, Context
|
||||
|
||||
from ietf.group.models import Role
|
||||
|
||||
class MailTrigger(models.Model):
|
||||
slug = models.CharField(max_length=32, primary_key=True)
|
||||
desc = models.TextField(blank=True)
|
||||
to = models.ManyToManyField('Recipient', null=True, blank=True, related_name='used_in_to')
|
||||
cc = models.ManyToManyField('Recipient', null=True, blank=True, related_name='used_in_cc')
|
||||
|
||||
class Meta:
|
||||
ordering = ["slug"]
|
||||
|
||||
def __unicode__(self):
|
||||
return self.slug
|
||||
|
||||
class Recipient(models.Model):
|
||||
slug = models.CharField(max_length=32, primary_key=True)
|
||||
desc = models.TextField(blank=True)
|
||||
template = models.TextField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["slug"]
|
||||
|
||||
def __unicode__(self):
|
||||
return self.slug
|
||||
|
||||
def gather(self, **kwargs):
|
||||
retval = []
|
||||
if hasattr(self,'gather_%s'%self.slug):
|
||||
retval.extend(eval('self.gather_%s(**kwargs)'%self.slug))
|
||||
if self.template:
|
||||
rendering = Template('{%% autoescape off %%}%s{%% endautoescape %%}'%self.template).render(Context(kwargs))
|
||||
if rendering:
|
||||
retval.extend([x.strip() for x in rendering.split(',')])
|
||||
|
||||
retval = list(set(retval))
|
||||
return retval
|
||||
|
||||
def gather_doc_group_chairs(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
doc=kwargs['doc']
|
||||
if doc.group and doc.group.type.slug in ['wg','rg']:
|
||||
addrs.append('%s-chairs@ietf.org'%doc.group.acronym)
|
||||
return addrs
|
||||
|
||||
def gather_doc_group_delegates(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
doc=kwargs['doc']
|
||||
if doc.group and doc.group.type.slug in ['wg','rg']:
|
||||
addrs.extend(doc.group.role_set.filter(name='delegate').values_list('email__address',flat=True))
|
||||
return addrs
|
||||
|
||||
def gather_doc_group_mail_list(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
doc=kwargs['doc']
|
||||
if doc.group.type.slug in ['wg','rg']:
|
||||
if doc.group.list_email:
|
||||
addrs.append(doc.group.list_email)
|
||||
return addrs
|
||||
|
||||
def gather_doc_affecteddoc_authors(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
for reldoc in kwargs['doc'].related_that_doc(['conflrev','tohist','tois','tops']):
|
||||
addrs.extend(Recipient.objects.get(slug='doc_authors').gather(**{'doc':reldoc.document}))
|
||||
return addrs
|
||||
|
||||
def gather_doc_affecteddoc_group_chairs(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
for reldoc in kwargs['doc'].related_that_doc(['conflrev','tohist','tois','tops']):
|
||||
addrs.extend(Recipient.objects.get(slug='doc_group_chairs').gather(**{'doc':reldoc.document}))
|
||||
return addrs
|
||||
|
||||
def gather_doc_affecteddoc_notify(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
for reldoc in kwargs['doc'].related_that_doc(['conflrev','tohist','tois','tops']):
|
||||
addrs.extend(Recipient.objects.get(slug='doc_notify').gather(**{'doc':reldoc.document}))
|
||||
return addrs
|
||||
|
||||
def gather_conflict_review_stream_manager(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
for reldoc in kwargs['doc'].related_that_doc(['conflrev']):
|
||||
addrs.extend(Recipient.objects.get(slug='doc_stream_manager').gather(**{'doc':reldoc.document}))
|
||||
return addrs
|
||||
|
||||
def gather_conflict_review_steering_group(self,**kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
for reldoc in kwargs['doc'].related_that_doc(['conflrev']):
|
||||
if reldoc.document.stream_id=='irsg':
|
||||
addrs.append('"Internet Research Steering Group" <irsg@ietf.org>')
|
||||
return addrs
|
||||
|
||||
def gather_group_steering_group(self,**kwargs):
|
||||
addrs = []
|
||||
sg_map = dict( wg='"The IESG" <iesg@ietf.org>', rg='"Internet Research Steering Group" <irsg@ietf.org>' )
|
||||
if 'group' in kwargs and kwargs['group'].type_id in sg_map:
|
||||
addrs.append(sg_map[kwargs['group'].type_id])
|
||||
return addrs
|
||||
|
||||
def gather_stream_managers(self, **kwargs):
|
||||
addrs = []
|
||||
manager_map = dict(ise = '<rfc-ise@rfc-editor.org>',
|
||||
irtf = '<irtf-chair@irtf.org>',
|
||||
ietf = '<iesg@ietf.org>',
|
||||
iab = '<iab-chair@iab.org>')
|
||||
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
|
||||
|
||||
def gather_doc_non_ietf_stream_manager(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
doc = kwargs['doc']
|
||||
if doc.stream_id and doc.stream_id != 'ietf':
|
||||
addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':[doc.stream_id,]}))
|
||||
return addrs
|
||||
|
||||
def gather_group_responsible_directors(self, **kwargs):
|
||||
addrs = []
|
||||
if 'group' in kwargs:
|
||||
group = kwargs['group']
|
||||
if not group.acronym=='none':
|
||||
addrs.extend(group.role_set.filter(name='ad').values_list('email__address',flat=True))
|
||||
if group.type_id=='rg':
|
||||
addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':['irtf']}))
|
||||
return addrs
|
||||
|
||||
def gather_doc_group_responsible_directors(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
group = kwargs['doc'].group
|
||||
if group and not group.acronym=='none':
|
||||
addrs.extend(Recipient.objects.get(slug='group_responsible_directors').gather(**{'group':group}))
|
||||
return addrs
|
||||
|
||||
def gather_submission_authors(self, **kwargs):
|
||||
addrs = []
|
||||
if 'submission' in kwargs:
|
||||
submission = kwargs['submission']
|
||||
addrs.extend(["%s <%s>" % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]])
|
||||
return addrs
|
||||
|
||||
def gather_submission_group_chairs(self, **kwargs):
|
||||
addrs = []
|
||||
if 'submission' in kwargs:
|
||||
submission = kwargs['submission']
|
||||
if submission.group:
|
||||
addrs.extend(Recipient.objects.get(slug='group_chairs').gather(**{'group':submission.group}))
|
||||
return addrs
|
||||
|
||||
def gather_submission_confirmers(self, **kwargs):
|
||||
"""If a submitted document is revising an existing document, the confirmers
|
||||
are the authors of that existing document. Otherwise, the confirmers
|
||||
are the authors and submitter of the submitted document."""
|
||||
|
||||
addrs=[]
|
||||
if 'submission' in kwargs:
|
||||
submission = kwargs['submission']
|
||||
doc=submission.existing_document()
|
||||
if doc:
|
||||
addrs.extend([i.author.formatted_email() for i in doc.documentauthor_set.all() if not i.author.invalid_address()])
|
||||
else:
|
||||
addrs.extend([u"%s <%s>" % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]])
|
||||
if submission.submitter_parsed()["email"]:
|
||||
addrs.append(submission.submitter)
|
||||
return addrs
|
||||
|
||||
def gather_submission_group_mail_list(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'submission' in kwargs:
|
||||
submission = kwargs['submission']
|
||||
if submission.group:
|
||||
addrs.extend(Recipient.objects.get(slug='group_mail_list').gather(**{'group':submission.group}))
|
||||
return addrs
|
||||
|
||||
def gather_rfc_editor_if_doc_in_queue(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'doc' in kwargs:
|
||||
doc = kwargs['doc']
|
||||
if doc.get_state_slug("draft-rfceditor") is not None:
|
||||
addrs.extend(Recipient.objects.get(slug='rfc_editor').gather(**{}))
|
||||
return addrs
|
||||
|
||||
def gather_doc_discussing_ads(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'doc' in kwargs:
|
||||
doc = kwargs['doc']
|
||||
active_ballot = doc.active_ballot()
|
||||
if active_ballot:
|
||||
for ad, pos in active_ballot.active_ad_positions().iteritems():
|
||||
if pos and pos.pos_id == "discuss":
|
||||
addrs.append(ad.role_email("ad").address)
|
||||
return addrs
|
||||
|
||||
def gather_ipr_updatedipr_contacts(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'ipr' in kwargs:
|
||||
ipr = kwargs['ipr']
|
||||
for rel in ipr.updates:
|
||||
if rel.target.submitter_email:
|
||||
addrs.append(rel.target.submitter_email)
|
||||
elif hasattr(rel.target,'ietfer_email') and rel.target.ietfer_email:
|
||||
addrs.append(rel.target.ietfer_email)
|
||||
return addrs
|
||||
|
||||
def gather_ipr_updatedipr_holders(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'ipr' in kwargs:
|
||||
ipr = kwargs['ipr']
|
||||
for disc in ipr.recursively_updates():
|
||||
if hasattr(ipr,'holder_contact_email') and ipr.holder_contact_email:
|
||||
addrs.append(ipr.holder_contact_email)
|
||||
return addrs
|
||||
|
||||
def gather_doc_ipr_group_or_ad(self, **kwargs):
|
||||
"""A document's group email list if the document is a group document,
|
||||
otherwise, the document's AD if the document is active, otherwise
|
||||
the IETF chair"""
|
||||
addrs=[]
|
||||
if 'doc' in kwargs:
|
||||
doc=kwargs['doc']
|
||||
if doc.group and doc.group.acronym == 'none':
|
||||
if doc.ad and doc.get_state_slug('draft')=='active':
|
||||
addrs.extend(Recipient.objects.get(slug='doc_ad').gather(**kwargs))
|
||||
else:
|
||||
addrs.extend(Role.objects.filter(group__acronym='gen',name='ad').values_list('email__address',flat=True))
|
||||
else:
|
||||
addrs.extend(Recipient.objects.get(slug='doc_group_mail_list').gather(**kwargs))
|
||||
return addrs
|
||||
|
||||
def gather_liaison_manager(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'group' in kwargs:
|
||||
group=kwargs['group']
|
||||
addrs.extend(group.role_set.filter(name='liaiman').values_list('email__address',flat=True))
|
||||
return addrs
|
||||
|
||||
def gather_session_requester(self, **kwargs):
|
||||
addrs=[]
|
||||
if 'session' in kwargs:
|
||||
session = kwargs['session']
|
||||
addrs.append(session.requested_by.role_email('chair').address)
|
||||
return addrs
|
35
ietf/mailtrigger/resources.py
Normal file
35
ietf/mailtrigger/resources.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Autogenerated by the makeresources management command 2015-08-06 11:00 PDT
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.fields import ToOneField, ToManyField # pyflakes:ignore
|
||||
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
|
||||
|
||||
from ietf import api
|
||||
|
||||
from ietf.mailtrigger.models import * # pyflakes:ignore
|
||||
|
||||
|
||||
class RecipientResource(ModelResource):
|
||||
class Meta:
|
||||
queryset = Recipient.objects.all()
|
||||
#resource_name = 'recipient'
|
||||
filtering = {
|
||||
"slug": ALL,
|
||||
"desc": ALL,
|
||||
"template": ALL,
|
||||
}
|
||||
api.mailtrigger.register(RecipientResource())
|
||||
|
||||
class MailTriggerResource(ModelResource):
|
||||
to = ToManyField(RecipientResource, 'to', null=True)
|
||||
cc = ToManyField(RecipientResource, 'cc', null=True)
|
||||
class Meta:
|
||||
queryset = MailTrigger.objects.all()
|
||||
#resource_name = 'mailtrigger'
|
||||
filtering = {
|
||||
"slug": ALL,
|
||||
"desc": ALL,
|
||||
"to": ALL_WITH_RELATIONS,
|
||||
"cc": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.mailtrigger.register(MailTriggerResource())
|
||||
|
34
ietf/mailtrigger/tests.py
Normal file
34
ietf/mailtrigger/tests.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.utils.test_data import make_test_data
|
||||
|
||||
class EventMailTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
make_test_data()
|
||||
|
||||
def test_show_triggers(self):
|
||||
|
||||
url = urlreverse('ietf.mailtrigger.views.show_triggers')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('ballot_saved' in r.content)
|
||||
|
||||
url = urlreverse('ietf.mailtrigger.views.show_triggers',kwargs=dict(mailtrigger_slug='ballot_saved'))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('ballot_saved' in r.content)
|
||||
|
||||
def test_show_recipients(self):
|
||||
|
||||
url = urlreverse('ietf.mailtrigger.views.show_recipients')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('doc_group_mail_list' in r.content)
|
||||
|
||||
url = urlreverse('ietf.mailtrigger.views.show_recipients',kwargs=dict(recipient_slug='doc_group_mail_list'))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('doc_group_mail_list' in r.content)
|
||||
|
11
ietf/mailtrigger/urls.py
Normal file
11
ietf/mailtrigger/urls.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import RedirectView
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
urlpatterns = patterns('ietf.mailtrigger.views',
|
||||
url(r'^$', RedirectView.as_view(url=reverse_lazy('mailtrigger_show_triggers'), permanent=True)),
|
||||
url(r'^name/$', 'show_triggers', name='mailtrigger_show_triggers' ),
|
||||
url(r'^name/(?P<mailtrigger_slug>[-\w]+)/$', 'show_triggers' ),
|
||||
url(r'^recipient/$', 'show_recipients' ),
|
||||
url(r'^recipient/(?P<recipient_slug>[-\w]+)/$', 'show_recipients' ),
|
||||
)
|
86
ietf/mailtrigger/utils.py
Normal file
86
ietf/mailtrigger/utils.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from collections import namedtuple
|
||||
from ietf.mailtrigger.models import MailTrigger, Recipient
|
||||
from ietf.submit.models import Submission
|
||||
|
||||
class AddrLists(namedtuple('AddrLists',['to','cc'])):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def as_strings(self,compact=True):
|
||||
|
||||
separator = ", " if compact else ",\n "
|
||||
to_string = separator.join(self.to)
|
||||
cc_string = separator.join(self.cc)
|
||||
|
||||
return namedtuple('AddrListsAsStrings',['to','cc'])(to=to_string,cc=cc_string)
|
||||
|
||||
def gather_address_lists(slug, **kwargs):
|
||||
mailtrigger = MailTrigger.objects.get(slug=slug)
|
||||
|
||||
to = set()
|
||||
for recipient in mailtrigger.to.all():
|
||||
to.update(recipient.gather(**kwargs))
|
||||
to.discard('')
|
||||
|
||||
cc = set()
|
||||
for recipient in mailtrigger.cc.all():
|
||||
cc.update(recipient.gather(**kwargs))
|
||||
cc.discard('')
|
||||
|
||||
return AddrLists(to=list(to),cc=list(cc))
|
||||
|
||||
def gather_relevant_expansions(**kwargs):
|
||||
|
||||
def starts_with(prefix):
|
||||
return MailTrigger.objects.filter(slug__startswith=prefix).values_list('slug',flat=True)
|
||||
|
||||
relevant = set()
|
||||
|
||||
if 'doc' in kwargs:
|
||||
|
||||
doc = kwargs['doc']
|
||||
|
||||
relevant.update(['doc_state_edited','doc_telechat_details_changed','ballot_deferred','ballot_saved'])
|
||||
|
||||
if doc.type_id in ['draft','statchg']:
|
||||
relevant.update(starts_with('last_call_'))
|
||||
|
||||
if doc.type_id == 'draft':
|
||||
relevant.update(starts_with('doc_'))
|
||||
relevant.update(starts_with('resurrection_'))
|
||||
relevant.update(['ipr_posted_on_doc',])
|
||||
if doc.stream_id == 'ietf':
|
||||
relevant.update(['ballot_approved_ietf_stream','pubreq_iesg'])
|
||||
else:
|
||||
relevant.update(['pubreq_rfced'])
|
||||
last_submission = Submission.objects.filter(name=doc.name,state='posted').order_by('-rev').first()
|
||||
if last_submission and 'submission' not in kwargs:
|
||||
kwargs['submission'] = last_submission
|
||||
|
||||
if doc.type_id == 'conflrev':
|
||||
relevant.update(['conflrev_requested','ballot_approved_conflrev'])
|
||||
if doc.type_id == 'charter':
|
||||
relevant.update(['charter_external_review','ballot_approved_charter'])
|
||||
|
||||
if 'group' in kwargs:
|
||||
|
||||
relevant.update(starts_with('group_'))
|
||||
relevant.update(starts_with('milestones_'))
|
||||
relevant.update(starts_with('session_'))
|
||||
relevant.update(['charter_external_review',])
|
||||
|
||||
if 'submission' in kwargs:
|
||||
|
||||
relevant.update(starts_with('sub_'))
|
||||
|
||||
rule_list = []
|
||||
for mailtrigger in MailTrigger.objects.filter(slug__in=relevant):
|
||||
addrs = gather_address_lists(mailtrigger.slug,**kwargs)
|
||||
if addrs.to or addrs.cc:
|
||||
rule_list.append((mailtrigger.slug,mailtrigger.desc,addrs.to,addrs.cc))
|
||||
return sorted(rule_list)
|
||||
|
||||
def get_base_ipr_request_address():
|
||||
return Recipient.objects.get(slug='ipr_requests').gather()[0]
|
||||
|
||||
|
26
ietf/mailtrigger/views.py
Normal file
26
ietf/mailtrigger/views.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright The IETF Trust 2015, All Rights Reserved
|
||||
|
||||
from inspect import getsourcelines
|
||||
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
from ietf.mailtrigger.models import MailTrigger, Recipient
|
||||
|
||||
def show_triggers(request, mailtrigger_slug=None):
|
||||
mailtriggers = MailTrigger.objects.all()
|
||||
if mailtrigger_slug:
|
||||
get_object_or_404(MailTrigger,slug=mailtrigger_slug)
|
||||
mailtriggers = mailtriggers.filter(slug=mailtrigger_slug)
|
||||
return render(request,'mailtrigger/trigger.html',{'mailtrigger_slug':mailtrigger_slug,
|
||||
'mailtriggers':mailtriggers})
|
||||
def show_recipients(request, recipient_slug=None):
|
||||
recipients = Recipient.objects.all()
|
||||
if recipient_slug:
|
||||
get_object_or_404(Recipient,slug=recipient_slug)
|
||||
recipients = recipients.filter(slug=recipient_slug)
|
||||
for recipient in recipients:
|
||||
fname = 'gather_%s'%recipient.slug
|
||||
if hasattr(recipient,fname):
|
||||
recipient.code = ''.join(getsourcelines(getattr(recipient,fname))[0])
|
||||
return render(request,'mailtrigger/recipient.html',{'recipient_slug':recipient_slug,
|
||||
'recipients':recipients})
|
File diff suppressed because it is too large
Load diff
|
@ -41,5 +41,9 @@ objects += ietf.doc.models.StateType.objects.all()
|
|||
objects += ietf.doc.models.State.objects.all()
|
||||
objects += ietf.doc.models.BallotType.objects.all()
|
||||
|
||||
import ietf.mailtrigger.models
|
||||
objects += ietf.mailtrigger.models.Recipient.objects.all()
|
||||
objects += ietf.mailtrigger.models.MailTrigger.objects.all()
|
||||
|
||||
output("names", objects)
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ from ietf.person.models import Email
|
|||
from ietf.person.fields import SearchableEmailField
|
||||
from ietf.utils.fields import MultiEmailField
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
|
||||
ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None)
|
||||
|
@ -413,12 +414,12 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
if author:
|
||||
subject = 'Nomination receipt'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = author.address
|
||||
(to_email, cc) = gather_address_lists('nomination_receipt_requested',nominator=author.address)
|
||||
context = {'nominee': nominee.email.person.name,
|
||||
'comments': comments,
|
||||
'position': position.name}
|
||||
path = nomcom_template_path + NOMINATION_RECEIPT_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context)
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
return nomination
|
||||
|
||||
|
@ -531,12 +532,12 @@ class FeedbackForm(BaseNomcomForm, forms.ModelForm):
|
|||
if author:
|
||||
subject = "NomCom comment confirmation"
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = author.address
|
||||
(to_email, cc) = gather_address_lists('nomcom_comment_receipt_requested',commenter=author.address)
|
||||
context = {'nominee': self.nominee.email.person.name,
|
||||
'comments': comments,
|
||||
'position': self.position.name}
|
||||
path = nomcom_template_path + FEEDBACK_RECEIPT_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context)
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
class Meta:
|
||||
model = Feedback
|
||||
|
|
|
@ -14,7 +14,7 @@ from django.contrib.auth.models import User
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.group.models import Group
|
||||
|
@ -466,19 +466,60 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
def test_public_nominate(self):
|
||||
login_testing_unauthorized(self, COMMUNITY_USER, self.public_nominate_url)
|
||||
return self.nominate_view(public=True)
|
||||
self.client.logout()
|
||||
|
||||
messages_before = len(outbox)
|
||||
|
||||
self.nominate_view(public=True,confirmation=True)
|
||||
|
||||
self.assertEqual(len(outbox), messages_before + 4)
|
||||
|
||||
self.assertTrue('New person' in outbox[-4]['Subject'])
|
||||
self.assertTrue('nomcomchair' in outbox[-4]['To'])
|
||||
self.assertTrue('secretariat' in outbox[-4]['To'])
|
||||
|
||||
self.assertEqual('IETF Nomination Information', outbox[-3]['Subject'])
|
||||
self.assertTrue('nominee' in outbox[-3]['To'])
|
||||
|
||||
self.assertEqual('Nomination Information', outbox[-2]['Subject'])
|
||||
self.assertTrue('nomcomchair' in outbox[-2]['To'])
|
||||
|
||||
self.assertEqual('Nomination receipt', outbox[-1]['Subject'])
|
||||
self.assertTrue('plain' in outbox[-1]['To'])
|
||||
self.assertTrue(u'Comments with accents äöå' in unicode(outbox[-1].get_payload(decode=True),"utf-8","replace"))
|
||||
|
||||
# Nominate the same person for the same position again without asking for confirmation
|
||||
|
||||
messages_before = len(outbox)
|
||||
|
||||
self.nominate_view(public=True)
|
||||
self.assertEqual(len(outbox), messages_before + 1)
|
||||
self.assertEqual('Nomination Information', outbox[-1]['Subject'])
|
||||
self.assertTrue('nomcomchair' in outbox[-1]['To'])
|
||||
|
||||
def test_private_nominate(self):
|
||||
self.access_member_url(self.private_nominate_url)
|
||||
return self.nominate_view(public=False)
|
||||
self.client.logout()
|
||||
|
||||
def test_public_nominate_with_automatic_questionnaire(self):
|
||||
nomcom = get_nomcom_by_year(self.year)
|
||||
nomcom.send_questionnaire = True
|
||||
nomcom.save()
|
||||
login_testing_unauthorized(self, COMMUNITY_USER, self.public_nominate_url)
|
||||
empty_outbox()
|
||||
self.nominate_view(public=True)
|
||||
self.assertEqual(len(outbox), 4)
|
||||
# test_public_nominate checks the other messages
|
||||
self.assertTrue('Questionnaire' in outbox[2]['Subject'])
|
||||
self.assertTrue('nominee@' in outbox[2]['To'])
|
||||
|
||||
|
||||
def nominate_view(self, *args, **kwargs):
|
||||
public = kwargs.pop('public', True)
|
||||
nominee_email = kwargs.pop('nominee_email', u'nominee@example.com')
|
||||
nominator_email = kwargs.pop('nominator_email', "%s%s" % (COMMUNITY_USER, EMAIL_DOMAIN))
|
||||
position_name = kwargs.pop('position', 'IAOC')
|
||||
confirmation = kwargs.pop('confirmation', False)
|
||||
|
||||
if public:
|
||||
nominate_url = self.public_nominate_url
|
||||
|
@ -511,7 +552,8 @@ class NomcomViewsTest(TestCase):
|
|||
'candidate_email': candidate_email,
|
||||
'candidate_phone': candidate_phone,
|
||||
'position': position.id,
|
||||
'comments': comments}
|
||||
'comments': comments,
|
||||
'confirmation': confirmation}
|
||||
if not public:
|
||||
test_data['nominator_email'] = nominator_email
|
||||
|
||||
|
@ -599,8 +641,20 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
def test_public_feedback(self):
|
||||
login_testing_unauthorized(self, COMMUNITY_USER, self.public_feedback_url)
|
||||
return self.feedback_view(public=True)
|
||||
self.client.logout()
|
||||
|
||||
empty_outbox()
|
||||
self.feedback_view(public=True,confirmation=True)
|
||||
# feedback_view does a nomination internally: there is a lot of email related to that - tested elsewhere
|
||||
# We're interested in the confirmation receipt here
|
||||
self.assertEqual(len(outbox),4)
|
||||
self.assertEqual('NomCom comment confirmation', outbox[3]['Subject'])
|
||||
self.assertTrue('plain' in outbox[3]['To'])
|
||||
self.assertTrue(u'Comments with accents äöå' in unicode(outbox[3].get_payload(decode=True),"utf-8","replace"))
|
||||
|
||||
empty_outbox()
|
||||
self.feedback_view(public=True)
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertFalse('confirmation' in outbox[0]['Subject'])
|
||||
|
||||
def test_private_feedback(self):
|
||||
self.access_member_url(self.private_feedback_url)
|
||||
|
@ -612,6 +666,7 @@ class NomcomViewsTest(TestCase):
|
|||
nominee_email = kwargs.pop('nominee_email', u'nominee@example.com')
|
||||
nominator_email = kwargs.pop('nominator_email', "%s%s" % (COMMUNITY_USER, EMAIL_DOMAIN))
|
||||
position_name = kwargs.pop('position', 'IAOC')
|
||||
confirmation = kwargs.pop('confirmation', False)
|
||||
|
||||
self.nominate_view(public=public,
|
||||
nominee_email=nominee_email,
|
||||
|
@ -645,7 +700,8 @@ class NomcomViewsTest(TestCase):
|
|||
test_data = {'comments': comments,
|
||||
'position_name': position.name,
|
||||
'nominee_name': nominee.email.person.name,
|
||||
'nominee_email': nominee.email.address}
|
||||
'nominee_email': nominee.email.address,
|
||||
'confirmation': confirmation}
|
||||
|
||||
if public:
|
||||
test_data['nominator_email'] = nominator_email
|
||||
|
@ -854,6 +910,8 @@ class ReminderTest(TestCase):
|
|||
response = self.client.post(url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(outbox), messages_before + 2)
|
||||
self.assertTrue('nominee1@' in outbox[-2]['To'])
|
||||
self.assertTrue('nominee2@' in outbox[-1]['To'])
|
||||
|
||||
def test_remind_questionnaire_view(self):
|
||||
url = reverse('nomcom_send_reminder_mail', kwargs={'year': NOMCOM_YEAR,'type':'questionnaire'})
|
||||
|
@ -863,4 +921,5 @@ class ReminderTest(TestCase):
|
|||
response = self.client.post(url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(outbox), messages_before + 1)
|
||||
self.assertTrue('nominee1@' in outbox[-1]['To'])
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ from django.utils.encoding import smart_str
|
|||
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils import unaccent
|
||||
from ietf.utils.mail import send_mail_text, send_mail
|
||||
|
@ -207,7 +208,7 @@ def send_accept_reminder_to_nominee(nominee_position):
|
|||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
mail_path = nomcom_template_path + NOMINEE_ACCEPT_REMINDER_TEMPLATE
|
||||
nominee = nominee_position.nominee
|
||||
to_email = nominee.email.address
|
||||
(to_email, cc) = gather_address_lists('nomination_accept_reminder',nominee=nominee.email.address)
|
||||
|
||||
hash = get_hash_nominee_position(today, nominee_position.id)
|
||||
accept_url = reverse('nomcom_process_nomination_status',
|
||||
|
@ -233,7 +234,7 @@ def send_accept_reminder_to_nominee(nominee_position):
|
|||
body = render_to_string(mail_path, context)
|
||||
path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE)
|
||||
body += '\n\n%s' % render_to_string(path, context)
|
||||
send_mail_text(None, to_email, from_email, subject, body)
|
||||
send_mail_text(None, to_email, from_email, subject, body, cc=cc)
|
||||
|
||||
def send_questionnaire_reminder_to_nominee(nominee_position):
|
||||
subject = 'Reminder: please complete the Nomcom questionnaires for your nomination.'
|
||||
|
@ -244,7 +245,7 @@ def send_questionnaire_reminder_to_nominee(nominee_position):
|
|||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
mail_path = nomcom_template_path + NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE
|
||||
nominee = nominee_position.nominee
|
||||
to_email = nominee.email.address
|
||||
(to_email,cc) = gather_address_lists('nomcom_questionnaire_reminder',nominee=nominee.email.address)
|
||||
|
||||
context = {'nominee': nominee,
|
||||
'position': position,
|
||||
|
@ -253,7 +254,7 @@ def send_questionnaire_reminder_to_nominee(nominee_position):
|
|||
body = render_to_string(mail_path, context)
|
||||
path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE)
|
||||
body += '\n\n%s' % render_to_string(path, context)
|
||||
send_mail_text(None, to_email, from_email, subject, body)
|
||||
send_mail_text(None, to_email, from_email, subject, body, cc=cc)
|
||||
|
||||
def send_reminder_to_nominees(nominees,type):
|
||||
addrs = []
|
||||
|
@ -274,8 +275,6 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
from ietf.nomcom.models import Nominee, NomineePosition
|
||||
|
||||
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
||||
nomcom_chair = nomcom.group.get_chair()
|
||||
nomcom_chair_mail = nomcom_chair and nomcom_chair.email.address or None
|
||||
|
||||
# Create person and email if candidate email does't exist and send email
|
||||
email, created_email = Email.objects.get_or_create(address=candidate_email)
|
||||
|
@ -296,20 +295,18 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
# send email to secretariat and nomcomchair to warn about the new person
|
||||
subject = 'New person is created'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = [settings.NOMCOM_ADMIN_EMAIL]
|
||||
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
|
||||
context = {'email': email.address,
|
||||
'fullname': email.person.name,
|
||||
'person_id': email.person.id}
|
||||
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
|
||||
if nomcom_chair_mail:
|
||||
to_email.append(nomcom_chair_mail)
|
||||
send_mail(None, to_email, from_email, subject, path, context)
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
if nominee_position_created:
|
||||
# send email to nominee
|
||||
subject = 'IETF Nomination Information'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = email.address
|
||||
(to_email, cc) = gather_address_lists('nomination_new_nominee',nominee=email.address)
|
||||
domain = Site.objects.get_current().domain
|
||||
today = datetime.date.today().strftime('%Y%m%d')
|
||||
hash = get_hash_nominee_position(today, nominee_position.id)
|
||||
|
@ -335,13 +332,13 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
'decline_url': decline_url}
|
||||
|
||||
path = nomcom_template_path + NOMINEE_EMAIL_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context)
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
# send email to nominee with questionnaire
|
||||
if nomcom.send_questionnaire:
|
||||
subject = '%s Questionnaire' % position
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = email.address
|
||||
(to_email, cc) = gather_address_lists('nomcom_questionnaire',nominee=email.address)
|
||||
context = {'nominee': email.person.name,
|
||||
'position': position.name}
|
||||
path = '%s%d/%s' % (nomcom_template_path,
|
||||
|
@ -350,12 +347,12 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
path = '%s%d/%s' % (nomcom_template_path,
|
||||
position.id, QUESTIONNAIRE_TEMPLATE)
|
||||
body += '\n\n%s' % render_to_string(path, context)
|
||||
send_mail_text(None, to_email, from_email, subject, body)
|
||||
send_mail_text(None, to_email, from_email, subject, body, cc=cc)
|
||||
|
||||
# send emails to nomcom chair
|
||||
subject = 'Nomination Information'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = nomcom_chair_mail
|
||||
(to_email, cc) = gather_address_lists('nomination_received',nomcom=nomcom)
|
||||
context = {'nominee': email.person.name,
|
||||
'nominee_email': email.address,
|
||||
'position': position.name}
|
||||
|
@ -368,7 +365,7 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut
|
|||
'nominator_email': ''})
|
||||
|
||||
path = nomcom_template_path + NOMINATION_EMAIL_TEMPLATE
|
||||
send_mail(None, to_email, from_email, subject, path, context)
|
||||
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
||||
|
||||
return nominee
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm,
|
|||
from ietf.secr.proceedings.views import build_choices, handle_upload_file, make_directories
|
||||
from ietf.secr.sreq.forms import GroupSelectForm
|
||||
from ietf.secr.sreq.views import get_initial_session
|
||||
from ietf.secr.utils.mail import get_cc_list
|
||||
from ietf.secr.utils.meeting import get_session, get_timeslot
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
|
||||
# prep for agenda changes
|
||||
|
@ -188,9 +188,7 @@ def send_notifications(meeting, groups, person):
|
|||
now = datetime.datetime.now()
|
||||
for group in groups:
|
||||
sessions = group.session_set.filter(meeting=meeting)
|
||||
to_email = sessions[0].requested_by.role_email('chair').address
|
||||
# TODO confirm list, remove requested_by from cc, add session-request@ietf.org?
|
||||
cc_list = get_cc_list(group)
|
||||
addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0])
|
||||
from_email = ('"IETF Secretariat"','agenda@ietf.org')
|
||||
if len(sessions) == 1:
|
||||
subject = '%s - Requested session has been scheduled for IETF %s' % (group.acronym, meeting.number)
|
||||
|
@ -223,12 +221,12 @@ def send_notifications(meeting, groups, person):
|
|||
context['login'] = sessions[0].requested_by
|
||||
|
||||
send_mail(None,
|
||||
to_email,
|
||||
addrs.to,
|
||||
from_email,
|
||||
subject,
|
||||
template,
|
||||
context,
|
||||
cc=cc_list)
|
||||
cc=addrs.cc)
|
||||
|
||||
# create sent_notification event
|
||||
GroupEvent.objects.create(group=group,time=now,type='sent_notification',
|
||||
|
|
|
@ -5,6 +5,7 @@ from ietf.group.models import Group
|
|||
#from ietf.meeting.models import Session
|
||||
#from ietf.utils.test_data import make_test_data
|
||||
from ietf.meeting.test_data import make_meeting_test_data as make_test_data
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
|
||||
from pyquery import PyQuery
|
||||
|
||||
|
@ -108,6 +109,8 @@ class NotMeetingCase(TestCase):
|
|||
url = reverse('sessions_no_session',kwargs={'acronym':group.acronym})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
empty_outbox()
|
||||
|
||||
r = self.client.get(url,follow=True)
|
||||
# If the view invoked by that get throws an exception (such as an integrity error),
|
||||
# the traceback from this test will talk about a TransactionManagementError and
|
||||
|
@ -121,6 +124,10 @@ class NotMeetingCase(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('is already marked as not meeting' in r.content)
|
||||
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertTrue('Not having a session' in outbox[0]['Subject'])
|
||||
self.assertTrue('session-request@' in outbox[0]['To'])
|
||||
|
||||
class RetrievePreviousCase(TestCase):
|
||||
pass
|
||||
|
||||
|
|
|
@ -14,13 +14,14 @@ from ietf.name.models import SessionStatusName, ConstraintName
|
|||
from ietf.secr.sreq.forms import SessionForm, GroupSelectForm, ToolStatusForm
|
||||
from ietf.secr.utils.decorators import check_permissions
|
||||
from ietf.secr.utils.group import groups_by_session
|
||||
from ietf.secr.utils.mail import get_ad_email_list, get_chair_email_list, get_cc_list
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.person.models import Person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
# -------------------------------------------------
|
||||
# Globals
|
||||
# -------------------------------------------------
|
||||
#TODO: DELETE
|
||||
SESSION_REQUEST_EMAIL = 'session-request@ietf.org'
|
||||
AUTHORIZED_ROLES=('WG Chair','WG Secretary','RG Chair','IAB Group Chair','Area Director','Secretariat','Team Chair','IRTF Chair')
|
||||
|
||||
|
@ -112,8 +113,7 @@ def send_notification(group,meeting,login,session,action):
|
|||
session argument is a dictionary of fields from the session request form
|
||||
action argument is a string [new|update].
|
||||
'''
|
||||
to_email = SESSION_REQUEST_EMAIL
|
||||
cc_list = get_cc_list(group, login)
|
||||
(to_email, cc_list) = gather_address_lists('session_requested',group=group,person=login)
|
||||
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
|
||||
subject = '%s - New Meeting Session Request for IETF %s' % (group.acronym, meeting.number)
|
||||
template = 'sreq/session_request_notification.txt'
|
||||
|
@ -136,11 +136,7 @@ def send_notification(group,meeting,login,session,action):
|
|||
# change headers TO=ADs, CC=session-request, submitter and cochairs
|
||||
if session.get('length_session3',None):
|
||||
context['session']['num_session'] = 3
|
||||
to_email = get_ad_email_list(group)
|
||||
cc_list = get_chair_email_list(group)
|
||||
cc_list.append(SESSION_REQUEST_EMAIL)
|
||||
if login.role_email(role_name='wg').address not in cc_list:
|
||||
cc_list.append(login.role_email(role_name='wg').address)
|
||||
(to_email, cc_list) = gather_address_lists('session_requested_long',group=group,person=login)
|
||||
subject = '%s - Request for meeting session approval for IETF %s' % (group.acronym, meeting.number)
|
||||
template = 'sreq/session_approval_notification.txt'
|
||||
#status_text = 'the %s Directors for approval' % group.parent
|
||||
|
@ -211,8 +207,7 @@ def cancel(request, acronym):
|
|||
session.scheduledsession_set.all().delete()
|
||||
|
||||
# send notifitcation
|
||||
to_email = SESSION_REQUEST_EMAIL
|
||||
cc_list = get_cc_list(group, login)
|
||||
(to_email, cc_list) = gather_address_lists('session_request_cancelled',group=group,person=login)
|
||||
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
|
||||
subject = '%s - Cancelling a meeting request for IETF %s' % (group.acronym, meeting.number)
|
||||
send_mail(request, to_email, from_email, subject, 'sreq/session_cancel_notification.txt',
|
||||
|
@ -628,8 +623,7 @@ def no_session(request, acronym):
|
|||
session_save(session)
|
||||
|
||||
# send notification
|
||||
to_email = SESSION_REQUEST_EMAIL
|
||||
cc_list = get_cc_list(group, login)
|
||||
(to_email, cc_list) = gather_address_lists('session_request_not_meeting',group=group,person=login)
|
||||
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
|
||||
subject = '%s - Not having a session at IETF %s' % (group.acronym, meeting.number)
|
||||
send_mail(request, to_email, from_email, subject, 'sreq/not_meeting_notification.txt',
|
||||
|
|
|
@ -10,7 +10,7 @@ from ietf.doc.models import DocEvent, Document, BallotDocEvent, BallotPositionDo
|
|||
from ietf.doc.utils import get_document_content, add_state_change_event
|
||||
from ietf.person.models import Person
|
||||
from ietf.doc.lastcall import request_last_call
|
||||
from ietf.doc.mails import email_ad, email_state_changed
|
||||
from ietf.doc.mails import email_state_changed
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaItem, Telechat
|
||||
from ietf.iesg.agenda import agenda_data, get_doc_section
|
||||
from ietf.ietfauth.utils import role_required
|
||||
|
@ -261,8 +261,7 @@ def doc_detail(request, date, name):
|
|||
doc.time = (e and e.time) or datetime.datetime.now()
|
||||
doc.save()
|
||||
|
||||
email_state_changed(request, doc, e.desc)
|
||||
email_ad(request, doc, doc.ad, login, e.desc)
|
||||
email_state_changed(request, doc, e.desc, 'doc_state_edited')
|
||||
|
||||
if new_state.slug == "lc-req":
|
||||
request_last_call(request, doc)
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
def get_ad_email_list(group):
|
||||
'''
|
||||
This function takes a group and returns the Area Director email as a list.
|
||||
NOTE: we still have custom logic here for IRTF groups, where the "Area Director"
|
||||
is the chair of the parent group, 'irtf'.
|
||||
'''
|
||||
emails = []
|
||||
if group.type.slug == 'wg':
|
||||
emails.append('%s-ads@ietf.org' % group.acronym)
|
||||
elif group.type.slug == 'rg' and group.parent:
|
||||
emails.append(group.parent.role_set.filter(name='chair')[0].email.address)
|
||||
return emails
|
||||
|
||||
def get_cc_list(group, person=None):
|
||||
'''
|
||||
This function takes a Group and Person. It returns a list of emails for the ads and chairs of
|
||||
the group and the person's email if it isn't already in the list.
|
||||
|
||||
Per Pete Resnick, at IETF 80 meeting, session request notifications
|
||||
should go to chairs,ads lists not individuals.
|
||||
'''
|
||||
emails = []
|
||||
emails.extend(get_ad_email_list(group))
|
||||
emails.extend(get_chair_email_list(group))
|
||||
if person and person.email_address() not in emails:
|
||||
emails.append(person.email_address())
|
||||
return emails
|
||||
|
||||
def get_chair_email_list(group):
|
||||
'''
|
||||
This function takes a group and returns chair email(s) as a list.
|
||||
'''
|
||||
return [ r.email.address for r in group.role_set.filter(name='chair') ]
|
||||
|
|
@ -258,6 +258,7 @@ INSTALLED_APPS = (
|
|||
'ietf.ipr',
|
||||
'ietf.liaisons',
|
||||
'ietf.mailinglists',
|
||||
'ietf.mailtrigger',
|
||||
'ietf.meeting',
|
||||
'ietf.message',
|
||||
'ietf.name',
|
||||
|
@ -428,11 +429,9 @@ CACHES = {
|
|||
}
|
||||
}
|
||||
|
||||
IPR_EMAIL_TO = 'ietf-ipr@ietf.org'
|
||||
DOC_APPROVAL_EMAIL_CC = ["RFC Editor <rfc-editor@rfc-editor.org>", ]
|
||||
IPR_EMAIL_FROM = 'ietf-ipr@ietf.org'
|
||||
|
||||
IANA_EVAL_EMAIL = "drafts-eval@icann.org"
|
||||
IANA_APPROVE_EMAIL = "drafts-approval@icann.org"
|
||||
|
||||
# Put real password in settings_local.py
|
||||
IANA_SYNC_PASSWORD = "secret"
|
||||
|
@ -450,7 +449,6 @@ RFC_EDITOR_INDEX_URL = "https://www.rfc-editor.org/rfc/rfc-index.xml"
|
|||
ROLODEX_URL = ""
|
||||
NOMCOM_PUBLIC_KEYS_DIR = '/a/www/nomcom/public_keys/'
|
||||
NOMCOM_FROM_EMAIL = 'nomcom-chair@ietf.org'
|
||||
NOMCOM_ADMIN_EMAIL = DEFAULT_FROM_EMAIL
|
||||
OPENSSL_COMMAND = '/usr/bin/openssl'
|
||||
DAYS_TO_EXPIRE_NOMINATION_LINK = ''
|
||||
DEFAULT_FEEDBACK_TYPE = 'offtopic'
|
||||
|
@ -458,7 +456,6 @@ NOMINEE_FEEDBACK_TYPES = ['comment', 'questio', 'nomina']
|
|||
|
||||
# ID Submission Tool settings
|
||||
IDSUBMIT_FROM_EMAIL = 'IETF I-D Submission Tool <idsubmission@ietf.org>'
|
||||
IDSUBMIT_TO_EMAIL = 'internet-drafts@ietf.org'
|
||||
IDSUBMIT_ANNOUNCE_FROM_EMAIL = 'internet-drafts@ietf.org'
|
||||
IDSUBMIT_ANNOUNCE_LIST_EMAIL = 'i-d-announce@ietf.org'
|
||||
|
||||
|
|
|
@ -6,76 +6,68 @@ from django.template.loader import render_to_string
|
|||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
from ietf.doc.models import Document
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Role
|
||||
from ietf.message.models import Message
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
|
||||
def submission_confirmation_email_list(submission):
|
||||
try:
|
||||
doc = Document.objects.get(name=submission.name)
|
||||
email_list = [i.author.formatted_email() for i in doc.documentauthor_set.all() if not i.author.invalid_address()]
|
||||
except Document.DoesNotExist:
|
||||
email_list = [u'"%s" <%s>' % (author["name"], author["email"])
|
||||
for author in submission.authors_parsed() if author["email"]]
|
||||
if submission.submitter_parsed()["email"] and submission.submitter not in email_list:
|
||||
email_list.append(submission.submitter)
|
||||
return email_list
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
def send_submission_confirmation(request, submission):
|
||||
subject = 'Confirm submission of I-D %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
to_email = submission_confirmation_email_list(submission)
|
||||
(to_email, cc) = gather_address_lists('sub_confirmation_requested',submission=submission)
|
||||
|
||||
confirm_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_confirm_submission', kwargs=dict(submission_id=submission.pk, auth_token=generate_access_token(submission.auth_key)))
|
||||
status_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'submit/confirm_submission.txt', {
|
||||
'submission': submission,
|
||||
'confirm_url': confirm_url,
|
||||
'status_url': status_url,
|
||||
})
|
||||
send_mail(request, to_email, from_email, subject, 'submit/confirm_submission.txt',
|
||||
{
|
||||
'submission': submission,
|
||||
'confirm_url': confirm_url,
|
||||
'status_url': status_url,
|
||||
},
|
||||
cc=cc)
|
||||
|
||||
return to_email
|
||||
all_addrs = to_email
|
||||
all_addrs.extend(cc)
|
||||
return all_addrs
|
||||
|
||||
def send_full_url(request, submission):
|
||||
subject = 'Full URL for managing submission of draft %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
to_email = submission_confirmation_email_list(submission)
|
||||
(to_email, cc) = gather_address_lists('sub_management_url_requested',submission=submission)
|
||||
url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'submit/full_url.txt', {
|
||||
'submission': submission,
|
||||
'url': url,
|
||||
})
|
||||
send_mail(request, to_email, from_email, subject, 'submit/full_url.txt',
|
||||
{
|
||||
'submission': submission,
|
||||
'url': url,
|
||||
},
|
||||
cc=cc)
|
||||
|
||||
return to_email
|
||||
all_addrs = to_email
|
||||
all_addrs.extend(cc)
|
||||
return all_addrs
|
||||
|
||||
def send_approval_request_to_group(request, submission):
|
||||
subject = 'New draft waiting for approval: %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
to_email = [r.formatted_email() for r in Role.objects.filter(group=submission.group, name="chair").select_related("email", "person")]
|
||||
(to_email,cc) = gather_address_lists('sub_chair_approval_requested',submission=submission)
|
||||
if not to_email:
|
||||
return to_email
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'submit/approval_request.txt', {
|
||||
'submission': submission,
|
||||
'domain': Site.objects.get_current().domain,
|
||||
})
|
||||
|
||||
return to_email
|
||||
send_mail(request, to_email, from_email, subject, 'submit/approval_request.txt',
|
||||
{
|
||||
'submission': submission,
|
||||
'domain': Site.objects.get_current().domain,
|
||||
},
|
||||
cc=cc)
|
||||
all_addrs = to_email
|
||||
all_addrs.extend(cc)
|
||||
return all_addrs
|
||||
|
||||
def send_manual_post_request(request, submission, errors):
|
||||
subject = u'Manual Post Requested for %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
to_email = settings.IDSUBMIT_TO_EMAIL
|
||||
|
||||
cc = [submission.submitter]
|
||||
cc += [u'"%s" <%s>' % (author["name"], author["email"])
|
||||
for author in submission.authors_parsed() if author["email"]]
|
||||
if submission.group:
|
||||
cc += [r.formatted_email() for r in Role.objects.filter(group=submission.group, name="chair").select_related("email", "person")]
|
||||
cc = list(set(cc))
|
||||
|
||||
(to_email,cc) = gather_address_lists('sub_manual_post_requested',submission=submission)
|
||||
send_mail(request, to_email, from_email, subject, 'submit/manual_post_request.txt', {
|
||||
'submission': submission,
|
||||
'url': settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status', kwargs=dict(submission_id=submission.pk)),
|
||||
|
@ -93,9 +85,7 @@ def announce_to_lists(request, submission):
|
|||
pass
|
||||
m.subject = 'I-D Action: %s-%s.txt' % (submission.name, submission.rev)
|
||||
m.frm = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
|
||||
m.to = settings.IDSUBMIT_ANNOUNCE_LIST_EMAIL
|
||||
if submission.group and submission.group.list_email:
|
||||
m.cc = submission.group.list_email
|
||||
(m.to, m.cc) = gather_address_lists('sub_announced',submission=submission)
|
||||
m.body = render_to_string('submit/announce_to_lists.txt',
|
||||
dict(submission=submission,
|
||||
settings=settings))
|
||||
|
@ -106,39 +96,18 @@ def announce_to_lists(request, submission):
|
|||
|
||||
|
||||
def announce_new_version(request, submission, draft, state_change_msg):
|
||||
to_email = []
|
||||
if draft.notify:
|
||||
to_email.append(draft.notify)
|
||||
if draft.ad:
|
||||
to_email.append(draft.ad.role_email("ad").address)
|
||||
|
||||
if draft.stream_id == "iab":
|
||||
to_email.append("IAB Stream <iab-stream@iab.org>")
|
||||
elif draft.stream_id == "ise":
|
||||
to_email.append("Independent Submission Editor <rfc-ise@rfc-editor.org>")
|
||||
elif draft.stream_id == "irtf":
|
||||
to_email.append("IRSG <irsg@irtf.org>")
|
||||
|
||||
# if it has been sent to the RFC Editor, keep them in the loop
|
||||
if draft.get_state_slug("draft-rfceditor") is not None:
|
||||
to_email.append("RFC Editor <rfc-editor@rfc-editor.org>")
|
||||
|
||||
active_ballot = draft.active_ballot()
|
||||
if active_ballot:
|
||||
for ad, pos in active_ballot.active_ad_positions().iteritems():
|
||||
if pos and pos.pos_id == "discuss":
|
||||
to_email.append(ad.role_email("ad").address)
|
||||
(to_email,cc) = gather_address_lists('sub_new_version',doc=draft,submission=submission)
|
||||
|
||||
if to_email:
|
||||
subject = 'New Version Notification - %s-%s.txt' % (submission.name, submission.rev)
|
||||
from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
|
||||
send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt',
|
||||
{'submission': submission,
|
||||
'msg': state_change_msg})
|
||||
'msg': state_change_msg},
|
||||
cc=cc)
|
||||
|
||||
def announce_to_authors(request, submission):
|
||||
authors = [u'"%s" <%s>' % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]]
|
||||
to_email = list(set(submission_confirmation_email_list(submission) + authors))
|
||||
(to_email, cc) = gather_address_lists('sub_announced_to_authors',submission=submission)
|
||||
from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
|
||||
subject = 'New Version Notification for %s-%s.txt' % (submission.name, submission.rev)
|
||||
if submission.group:
|
||||
|
@ -149,4 +118,5 @@ def announce_to_authors(request, submission):
|
|||
group = 'Individual Submission'
|
||||
send_mail(request, to_email, from_email, subject, 'submit/announce_to_authors.txt',
|
||||
{'submission': submission,
|
||||
'group': group})
|
||||
'group': group},
|
||||
cc=cc)
|
||||
|
|
|
@ -3,6 +3,7 @@ import datetime
|
|||
|
||||
from django.db import models
|
||||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Group
|
||||
from ietf.name.models import DraftSubmissionStateName
|
||||
|
@ -62,6 +63,8 @@ class Submission(models.Model):
|
|||
def access_token(self):
|
||||
return generate_access_token(self.access_key)
|
||||
|
||||
def existing_document(self):
|
||||
return Document.objects.filter(name=self.name).first()
|
||||
|
||||
class SubmissionEvent(models.Model):
|
||||
submission = models.ForeignKey(Submission)
|
||||
|
|
|
@ -221,8 +221,8 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue("review" in outbox[-1]["Subject"].lower())
|
||||
self.assertTrue(name in unicode(outbox[-1]))
|
||||
self.assertTrue(sug_replaced_alias.name in unicode(outbox[-1]))
|
||||
self.assertTrue("ameschairman" in outbox[-1]["To"].lower())
|
||||
self.assertTrue("marschairman" in outbox[-1]["To"].lower())
|
||||
self.assertTrue("ames-chairs@" in outbox[-1]["To"].lower())
|
||||
self.assertTrue("mars-chairs@" in outbox[-1]["To"].lower())
|
||||
|
||||
def test_submit_new_wg_txt(self):
|
||||
self.submit_new_wg(["txt"])
|
||||
|
@ -324,6 +324,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue((u"I-D Action: %s" % name) in outbox[-3]["Subject"])
|
||||
self.assertTrue((u"I-D Action: %s" % name) in draft.message_set.order_by("-time")[0].subject)
|
||||
self.assertTrue("Author Name" in unicode(outbox[-3]))
|
||||
self.assertTrue("ietf-announce@" in outbox[-3]['To'])
|
||||
self.assertTrue("New Version Notification" in outbox[-2]["Subject"])
|
||||
self.assertTrue(name in unicode(outbox[-2]))
|
||||
self.assertTrue("mars" in unicode(outbox[-2]))
|
||||
|
@ -648,6 +649,9 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue("Full URL for managing submission" in outbox[-1]["Subject"])
|
||||
self.assertTrue(name in outbox[-1]["Subject"])
|
||||
|
||||
# This could use a test on an 01 from a new author to make sure the logic on
|
||||
# who gets the management url behaves as expected
|
||||
|
||||
def test_submit_all_file_types(self):
|
||||
make_test_data()
|
||||
|
||||
|
@ -710,7 +714,7 @@ class SubmitTests(TestCase):
|
|||
url = urlreverse('submit_upload_submission')
|
||||
# set meeting to today so we're in blackout period
|
||||
meeting = Meeting.get_current_meeting()
|
||||
meeting.date = datetime.datetime.today()
|
||||
meeting.date = datetime.datetime.utcnow()
|
||||
meeting.save()
|
||||
|
||||
# regular user, no access
|
||||
|
|
|
@ -16,7 +16,7 @@ from ietf.doc.utils import prettify_std_name
|
|||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.submit.forms import SubmissionUploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm, ReplacesForm
|
||||
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, submission_confirmation_email_list, send_manual_post_request
|
||||
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, send_manual_post_request
|
||||
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName
|
||||
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
|
||||
from ietf.submit.utils import check_idnits, found_idnits, validate_submission, create_submission_event
|
||||
|
@ -24,6 +24,7 @@ from ietf.submit.utils import post_submission, cancel_submission, rename_submiss
|
|||
from ietf.utils.accesstoken import generate_random_key, generate_access_token
|
||||
from ietf.utils.draft import Draft
|
||||
from ietf.utils.log import log
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
||||
|
||||
def upload_submission(request):
|
||||
|
@ -185,7 +186,9 @@ def submission_status(request, submission_id, access_token=None):
|
|||
can_force_post = is_secretariat and submission.state.next_states.filter(slug="posted")
|
||||
show_send_full_url = not key_matched and not is_secretariat and submission.state_id not in ("cancel", "posted")
|
||||
|
||||
confirmation_list = submission_confirmation_email_list(submission)
|
||||
addrs = gather_address_lists('sub_confirmation_requested',submission=submission)
|
||||
confirmation_list = addrs.to
|
||||
confirmation_list.extend(addrs.cc)
|
||||
|
||||
requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())
|
||||
|
||||
|
@ -210,7 +213,7 @@ def submission_status(request, submission_id, access_token=None):
|
|||
action = request.POST.get('action')
|
||||
if action == "autopost" and submission.state_id == "uploaded":
|
||||
if not can_edit:
|
||||
return HttpResponseForbidden("You do not have permission to perfom this action")
|
||||
return HttpResponseForbidden("You do not have permission to perform this action")
|
||||
|
||||
submitter_form = NameEmailForm(request.POST, prefix="submitter")
|
||||
replaces_form = ReplacesForm(request.POST, name=submission.name)
|
||||
|
|
|
@ -8,7 +8,7 @@ import urllib2
|
|||
from django.utils.http import urlquote
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.doc.mails import email_ad, email_state_changed
|
||||
from ietf.doc.mails import email_state_changed
|
||||
from ietf.doc.models import Document, DocEvent, State, StateDocEvent, StateType, save_document_in_history
|
||||
from ietf.doc.utils import add_state_change_event
|
||||
from ietf.person.models import Person
|
||||
|
@ -205,8 +205,7 @@ def update_history_with_changes(changes, send_email=True):
|
|||
doc.set_state(state)
|
||||
|
||||
if send_email and (state != prev_state):
|
||||
email_state_changed(None, doc, "IANA %s state changed to %s" % (kind, state.name))
|
||||
email_ad(None, doc, doc.ad, system, "IANA %s state changed to %s" % (kind, state.name))
|
||||
email_state_changed(None, doc, "IANA %s state changed to %s" % (kind, state.name),'doc_iana_state_changed')
|
||||
|
||||
if doc.time < timestamp:
|
||||
doc.time = timestamp
|
||||
|
|
|
@ -12,7 +12,7 @@ from ietf.doc.models import Document, DocAlias, DocEvent, DeletedEvent, DocTagNa
|
|||
from ietf.doc.utils import add_state_change_event
|
||||
from ietf.person.models import Person
|
||||
from ietf.sync import iana, rfceditor
|
||||
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
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
@ -71,7 +71,7 @@ class IANASyncTests(TestCase):
|
|||
# check sorting
|
||||
self.assertEqual(changes[0]["time"], "2011-10-09 11:00:00")
|
||||
|
||||
mailbox_before = len(outbox)
|
||||
empty_outbox()
|
||||
added_events, warnings = iana.update_history_with_changes(changes)
|
||||
|
||||
self.assertEqual(len(added_events), 3)
|
||||
|
@ -81,7 +81,9 @@ class IANASyncTests(TestCase):
|
|||
e = draft.latest_event(StateDocEvent, type="changed_state", state_type="draft-iana-action")
|
||||
self.assertEqual(e.desc, "IANA Action state changed to <b>Waiting on RFC Editor</b> from In Progress")
|
||||
# self.assertEqual(e.time, datetime.datetime(2011, 10, 9, 5, 0)) # check timezone handling
|
||||
self.assertEqual(len(outbox), mailbox_before + 3 * 2)
|
||||
self.assertEqual(len(outbox), 3 )
|
||||
for m in outbox:
|
||||
self.assertTrue('aread@' in m['To'])
|
||||
|
||||
# make sure it doesn't create duplicates
|
||||
added_events, warnings = iana.update_history_with_changes(changes)
|
||||
|
|
|
@ -25,30 +25,16 @@
|
|||
<input class="form-control" type="text" placeholder="{{ to }}" disabled>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{% bootstrap_form cc_select_form %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Cc</label>
|
||||
<input class="form-control" type="email" name="cc">
|
||||
<label>Additional Cc Addresses</label>
|
||||
<input class="form-control" type="email" name="extra_cc">
|
||||
<div class="help-block">Separate email addresses with commas.</div>
|
||||
</div>
|
||||
|
||||
{% if doc.notify %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="cc_state_change" value="1" checked>
|
||||
<b>Cc:</b> {{ doc.notify }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if doc.group.list_email %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="cc_group_list" value="1" checked>
|
||||
<b>Cc:</b> {{ doc.group.list_email }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Subject</label>
|
||||
<input class="form-control" type="text" placeholder="{{ subject }}" disabled>
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
{% load bootstrap3 %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block title %}WG {{ announcement }} announcement writeup for {{ charter.chartered_group.acronym }}{% endblock %}
|
||||
{% block title %}WG Action announcement }} announcement writeup for {{ charter.chartered_group.acronym }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>WG {{ announcement }} announcement writeup<br><small>{{ charter.chartered_group.acronym }}</small></h1>
|
||||
<h1>WG Action announcement writeup<br><small>{{ charter.chartered_group.acronym }}</small></h1>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
|
@ -22,11 +22,7 @@
|
|||
<button type="submit" class="btn btn-warning" name="regenerate_text" value="Reenerate"">Regenerate</button>
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
{% if announcement == "action" %}
|
||||
<a type="submit" class="btn btn-default" href="{% url "charter_approve" name=charter.canonical_name %}">Charter approval page</a>
|
||||
{% else %}
|
||||
<input type="submit" type="submit" class="btn btn-primary" name="send_text" value="Send WG {{ announcement }} announcement" />
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<a class="btn btn-default pull-right" href="{{ back_url }}">Back</a>
|
|
@ -1,6 +1,6 @@
|
|||
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>{% if group.list_email %}
|
||||
Cc: {{ group.acronym }} {{ group.type.name }} <{{ group.list_email }}> {% endif %}
|
||||
To: {{ to }}{% if cc %}
|
||||
Cc: {{ cc }} {% endif %}
|
||||
Subject: WG Action: {{ action_type }} {{ group.name }} ({{ group.acronym }})
|
||||
|
||||
{% filter wordwrap:73 %}{% if action_type == "Formed" %}A new IETF working group has been formed in the {{ group.parent.name }}.{% endif %}{% if action_type == "Rechartered" %}The {{ group.name }} ({{ group.acronym }}) working group in the {{ group.parent.name }} of the IETF has been rechartered.{% endif %} For additional information please contact the Area Directors or the {{ group.type.name }} Chair{{ chairs|pluralize}}.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Send announcement, close ballot & update revision</button>
|
||||
<a class="btn btn-warning" href="{% url "charter_edit_announcement" name=charter.canonical_name ann="action" %}?next=approve">Edit/regenerate announcement</a>
|
||||
<a class="btn btn-warning" href="{% url "ietf.doc.views_charter.action_announcement_text" name=charter.canonical_name %}?next=approve">Edit/regenerate announcement</a>
|
||||
<a class="btn btn-default pull-right" href="{% url "doc_view" name=charter.name %}">Back</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
|
|
@ -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%}
|
||||
|
|
34
ietf/templates/doc/charter/review_announcement_text.html
Normal file
34
ietf/templates/doc/charter/review_announcement_text.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block title %}WG Review announcement }} announcement writeup for {{ charter.chartered_group.acronym }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>WG Review announcement writeup<br><small>{{ charter.chartered_group.acronym }}</small></h1>
|
||||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form announcement_text_form %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary" name="save_text" value="Save">Submit</button>
|
||||
<button type="submit" class="btn btn-warning" name="regenerate_text" value="Regenerate"">Regenerate</button>
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<input type="submit" type="submit" class="btn btn-default" name="send_annc_only" value="Send only to IETF-Announce" />
|
||||
<input type="submit" type="submit" class="btn btn-default" name="send_nw_only" value="Send only to New-Work" />
|
||||
<input type="submit" type="submit" class="btn btn-default" name="send_both" value="Send to both" />
|
||||
{% endif %}
|
||||
|
||||
<a class="btn btn-default pull-right" href="{{ back_url }}">Back</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock%}
|
|
@ -1,6 +1,6 @@
|
|||
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>{% 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 }}.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: {{ review.notify }}
|
||||
Cc: The IESG <iesg@ietf.org>, <iana@iana.org>, <ietf-announce@ietf.org>
|
||||
To: {{ to }}
|
||||
Cc: {{ cc }}
|
||||
Subject: Results of IETF-conflict review for {{conflictdoc.canonical_name}}-{{conflictdoc.rev}}
|
||||
|
||||
{% filter wordwrap:73 %}The IESG has completed a review of {{conflictdoc.canonical_name}}-{{conflictdoc.rev}} consistent with RFC5742.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% load mail_filters %}{% autoescape off %}To: IESG Secretary <iesg-secretary@ietf.org>
|
||||
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:
|
||||
|
|
49
ietf/templates/doc/document_email.html
Normal file
49
ietf/templates/doc/document_email.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load ietf_filters %}
|
||||
{% load future %}
|
||||
|
||||
{% block title %}Email expansions for {{ doc.name }}-{{ doc.rev }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
{{ top|safe }}
|
||||
|
||||
{% if aliases %}
|
||||
<h2>Email Aliases</h2>
|
||||
|
||||
<table class="table table-condensed table-striped ietf">
|
||||
<tbody>
|
||||
{% for alias in aliases %}
|
||||
<tr>
|
||||
<td>{{ doc.name }}{{ alias.alias_type|default:''}}@{{ietf_domain}}</td>
|
||||
<td>{{ alias.expansion }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h2>Recipient Expansions</h2>
|
||||
|
||||
<table class="table table-condensed table-striped ietf">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mail Trigger</th>
|
||||
<th>To</th>
|
||||
<th>Cc</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for trigger,desc,to,cc in expansions %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ietf.mailtrigger.views.show_triggers' trigger %}"
|
||||
title="{{desc}}">{{trigger}}</a></td>
|
||||
<td> {{to|join:', '}}</td>
|
||||
<td> {{cc|join:', '}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock content %}
|
|
@ -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}}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% load ietf_filters %}{%load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>{% if cc %}
|
||||
To: {{ to }}{% if cc %}
|
||||
Cc: {{ cc }}{% endif %}
|
||||
Subject: {{ action_type }} Action: '{{ doc.title|clean_whitespace }}' to {{ doc|std_level_prompt }} ({{ doc.filename_with_rev }})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: {{to}}
|
||||
Cc: The IESG <iesg@ietf.org>, <iana@iana.org>, <ietf-announce@ietf.org>
|
||||
Cc: {{cc}}
|
||||
Subject: Results of IETF-conflict review for {{ doc.file_tag }}
|
||||
{% filter wordwrap:73 %}
|
||||
The IESG has completed a review of <{{ doc.name }}> consistent with RFC5742. This review is applied to all non-IETF streams.
|
||||
|
|
28
ietf/templates/doc/mail/charter_internal_review.txt
Normal file
28
ietf/templates/doc/mail/charter_internal_review.txt
Normal file
|
@ -0,0 +1,28 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}
|
||||
|
||||
A new IETF working group is being considered in the {{charter.group.parent.name}}. The draft charter for this working group is provided below for your review and comment.
|
||||
|
||||
Review time is one week.
|
||||
|
||||
The IETF Secretariat
|
||||
|
||||
{{charter.group.name}} ({{ charter.group.acronym }})
|
||||
--------------------------------------------------
|
||||
Current Status: {{ charter.group.state.name }} {% if charter.group.state_id != 'bof' %}Working Group{% endif %}
|
||||
|
||||
Chairs : {{ chairs|join:', '|default:'TBD' }}
|
||||
|
||||
Area Director: {{ ads|join:', '|default:'TBD' }}
|
||||
|
||||
Mailing List: {{ charter.group.list_email|default:'TBD' }}
|
||||
|
||||
{{ charter.name }}-{{ charter.rev }}
|
||||
|
||||
{{ charter_text }}
|
||||
|
||||
Proposed milestones
|
||||
|
||||
{%if milestones %}{% for milestone in milestones reversed %}{{ milestone.due|date:"M Y" }} {{ milestone.desc }}
|
||||
{% endfor %}{% else %}TBD{% endif %}
|
||||
|
||||
{% endfilter %}{% endautoescape%}
|
12
ietf/templates/doc/mail/comment_added_email.txt
Normal file
12
ietf/templates/doc/mail/comment_added_email.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% autoescape off %}
|
||||
Please DO NOT reply to this email.
|
||||
|
||||
{{by}} added the following comment to the history of {{doc.name}}
|
||||
|
||||
{{ comment.desc }}
|
||||
|
||||
The document can be found at
|
||||
I-D: {{ doc.file_tag|safe }}
|
||||
ID Tracker URL: {{ url }}
|
||||
|
||||
{% endautoescape%}
|
10
ietf/templates/doc/mail/doc_adopted_email.txt
Normal file
10
ietf/templates/doc/mail/doc_adopted_email.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}
|
||||
The {{ doc.group.acronym|upper }} {{ doc.group.type_id|upper }} has adopted {{ doc }} (entered by {{by}})
|
||||
|
||||
{% if prev_state %}The document was previously in state {{prev_state.name}}
|
||||
|
||||
{% endif %}The document is available at {{ url }}
|
||||
{% if comment %}
|
||||
|
||||
Comment:
|
||||
{{ comment }}{% endif %}{% endfilter %}{% endautoescape %}
|
15
ietf/templates/doc/mail/email_iesg_processing.txt
Normal file
15
ietf/templates/doc/mail/email_iesg_processing.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% autoescape off %}
|
||||
Please DO NOT reply to this email.
|
||||
|
||||
The IESG is processing {{doc.name}}.
|
||||
|
||||
The following changes have been made:
|
||||
|
||||
{% for change in changes %}{{change}}
|
||||
{% endfor %}
|
||||
|
||||
The document can be found here:
|
||||
I-D: {{ doc.file_tag|safe }}
|
||||
ID Tracker URL: {{ url }}
|
||||
|
||||
{% endautoescape%}
|
|
@ -0,0 +1,5 @@
|
|||
{% autoescape off %}{{ text }}
|
||||
|
||||
The document can be found at
|
||||
ID Tracker URL: {{ url }}
|
||||
{% endautoescape %}
|
|
@ -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%}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>{% if cc %}
|
||||
To: {{ to }}{% if cc %}
|
||||
CC: {{ cc }}{% endif %}
|
||||
Reply-To: ietf@ietf.org
|
||||
Sender: <iesg-secretary@ietf.org>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>
|
||||
Cc: RFC Editor <rfc-editor@rfc-editor.org>, {{status_change.notify}}
|
||||
To: {{ to }}{% if cc %}
|
||||
Cc: {{ cc }}{% endif %}
|
||||
Subject: {{action}}: {{relateddoc.target.document.title}} to {{newstatus}}
|
||||
|
||||
{% filter wordwrap:73 %}The IESG has approved changing the status of the following document:
|
||||
|
|
47
ietf/templates/group/email.html
Normal file
47
ietf/templates/group/email.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
{% extends "group/group_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load ietf_filters %}
|
||||
{% load future %}
|
||||
|
||||
{% block group_content %}
|
||||
{% origin %}
|
||||
|
||||
{% if aliases %}
|
||||
<h2>Email Aliases</h2>
|
||||
|
||||
<table class="table table-condensed table-striped ietf">
|
||||
<tbody>
|
||||
{% for alias in aliases %}
|
||||
<tr>
|
||||
<td>{{ group.acronym }}{{ alias.alias_type|default:''}}@{{ietf_domain}}</td>
|
||||
<td>{{ alias.expansion }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h2>Recipient Expansions</h2>
|
||||
|
||||
<table class="table table-condensed table-striped ietf">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mail Trigger</th>
|
||||
<th>To</th>
|
||||
<th>Cc</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for trigger,desc,to,cc in expansions %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ietf.mailtrigger.views.show_triggers' trigger %}"
|
||||
title="{{desc}}">{{trigger}}</a></td>
|
||||
<td> {{to|join:', '}}</td>
|
||||
<td> {{cc|join:', '}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}This is a reminder that milestones in "{{ group.name }}" are soon due.
|
||||
|
||||
{% for m in milestones %}"{{ m.desc }}" is due {% if m.due == today %}today!{% else %}in {{ early_warning_days }} days.{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
URL: {{ url }}
|
||||
{% endfilter %}{% endautoescape %}
|
|
@ -1,10 +0,0 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} review by the {{ reviewer }}:
|
||||
|
||||
{% for m in milestones %}"{{ m.desc }}"{% if m.days_ready != None %}
|
||||
Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
Go here to either accept or reject the new milestones:
|
||||
|
||||
{{ url }}
|
||||
{% endfilter %}{% endautoescape %}
|
|
@ -1,7 +0,0 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}This is a reminder that milestones in "{{ group.name }}" are overdue.
|
||||
|
||||
{% for m in milestones %}"{{ m.desc }}" is overdue{% if m.months_overdue > 0 %} with {{ m.months_overdue }} month{{ m.months_overdue|pluralize }}{% endif %}!
|
||||
|
||||
{% endfor %}
|
||||
URL: {{ url }}
|
||||
{% endfilter %}{% endautoescape %}
|
40
ietf/templates/mailtrigger/recipient.html
Normal file
40
ietf/templates/mailtrigger/recipient.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}Mail Recipients{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Mail Recipients</h1>
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Recipient</th>
|
||||
<th>Triggers</th>
|
||||
<th>Template</th>
|
||||
<th>Code</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for recipient in recipients %}
|
||||
<tr>
|
||||
<td><span title="{{recipient.desc}}">{{recipient.slug}}</span></td>
|
||||
<td>
|
||||
{% for mailtrigger in recipient.used_in_to.all %}
|
||||
<a href="{% url 'ietf.mailtrigger.views.show_triggers' mailtrigger.slug %}" title="{{mailtrigger.desc}}">{{mailtrigger.slug}}</a>{% if not forloop.last %}, {%endif%}
|
||||
{% endfor %}{% if recipient.used_in_to.exists and recipient.used_in_cc.exists %},{% endif %}
|
||||
{% for mailtrigger in recipient.used_in_cc.all %}
|
||||
<a href="{% url 'ietf.mailtrigger.views.show_triggers' mailtrigger.slug %}" title="{{mailtrigger.desc}}">{{mailtrigger.slug}}</a>{% if not forloop.last %}, {%endif%}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{recipient.template}}</td>
|
||||
<td>{% if recipient.code %}<pre>{{recipient.code}}</pre>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
41
ietf/templates/mailtrigger/trigger.html
Normal file
41
ietf/templates/mailtrigger/trigger.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}Mail Triggers{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Mail Triggers</h1>
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Trigger</th>
|
||||
<th>Recipients</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mailtrigger in mailtriggers %}
|
||||
<tr>
|
||||
<td><span title="{{mailtrigger.desc}}">{{mailtrigger.slug}}</span></td>
|
||||
<td>To:
|
||||
{% for recipient in mailtrigger.to.all %}
|
||||
{% comment %}<span title="{{recipient.desc}}">{{recipient.slug}}</span>{% endcomment %}
|
||||
<a href="{% url 'ietf.mailtrigger.views.show_recipients' recipient.slug %}" title="{{recipient.desc}}">{{recipient.slug}}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% if mailtrigger.cc.exists %}
|
||||
<br/>Cc:
|
||||
{% for recipient in mailtrigger.cc.all %}
|
||||
{% comment %}<span title="{{recipient.desc}}">{{recipient.slug}}</span>{% endcomment %}
|
||||
<a href="{% url 'ietf.mailtrigger.views.show_recipients' recipient.slug %}" title="{{recipient.desc}}">{{recipient.slug}}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -35,6 +35,7 @@ urlpatterns = patterns('',
|
|||
(r'^accounts/settings/', include('ietf.cookies.urls')),
|
||||
(r'^doc/', include('ietf.doc.urls')),
|
||||
(r'^drafts/', include('ietf.doc.redirect_drafts_urls')),
|
||||
(r'^mailtrigger/',include('ietf.mailtrigger.urls')),
|
||||
(r'^feed/', include('ietf.feed_urls')),
|
||||
(r'^group/', include('ietf.group.urls')),
|
||||
(r'^help/', include('ietf.help.urls')),
|
||||
|
|
Loading…
Reference in a new issue