From 0c520b0a015c3719a1784ccac338fbe184b13f10 Mon Sep 17 00:00:00 2001 From: Seth Birkholz Date: Sat, 15 Jul 2017 16:49:22 +0000 Subject: [PATCH 1/4] This is a bug fix for Ticket #1309 - Legacy-Id: 13917 --- ietf/doc/mails.py | 757 +++++---- ietf/doc/views_draft.py | 1157 +++++++++----- .../migrations/0002_auto_20150809_1314.py | 1417 ++++++++++------- .../migrations/0011_edit_shepherd_email.py | 51 + .../doc/mail/email_edit_shepherd.txt | 3 + 5 files changed, 2107 insertions(+), 1278 deletions(-) create mode 100644 ietf/mailtrigger/migrations/0011_edit_shepherd_email.py create mode 100644 ietf/templates/doc/mail/email_edit_shepherd.txt diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index b3cd75f1d..e34e769cb 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -1,4 +1,4 @@ -# generation of mails +# generation of mails import os import textwrap, datetime @@ -16,30 +16,40 @@ 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, mailtrigger_id=None): - (to,cc) = gather_address_lists(mailtrigger_id or 'doc_state_edited',doc=doc) + (to, cc) = gather_address_lists( + mailtrigger_id or 'doc_state_edited', doc=doc) if not to: return - + text = strip_tags(text) - send_mail(request, to, None, - "Datatracker 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()), - cc=cc) - + send_mail( + request, + to, + None, + "Datatracker 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()), + cc=cc) + + def email_ad_approved_doc(request, doc, text): - to = "iesg@iesg.org" - bcc = "iesg-secretary@ietf.org" - frm = request.user.person.formatted_email() - send_mail(request, to, frm, - "Approved: %s" % doc.filename_with_rev(), - "doc/mail/ad_approval_email.txt", - dict(text=text, - docname=doc.filename_with_rev()), - bcc=bcc) - + to = "iesg@iesg.org" + bcc = "iesg-secretary@ietf.org" + frm = request.user.person.formatted_email() + send_mail( + request, + to, + frm, + "Approved: %s" % doc.filename_with_rev(), + "doc/mail/ad_approval_email.txt", + dict(text=text, docname=doc.filename_with_rev()), + bcc=bcc) + + def email_stream_changed(request, doc, old_stream, new_stream, text=""): """Email the change text to the notify group and to the stream chairs""" streams = [] @@ -47,61 +57,109 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""): streams.append(old_stream.slug) if new_stream: streams.append(new_stream.slug) - (to,cc) = gather_address_lists('doc_stream_changed',doc=doc,streams=streams) + (to, cc) = gather_address_lists( + 'doc_stream_changed', doc=doc, streams=streams) if not to: return - + if not text: - text = u"Stream changed to %s from %s" % (new_stream, old_stream) + text = u"Stream changed to %s from %s" % (new_stream, + old_stream) text = strip_tags(text) - send_mail(request, to, None, - "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()), - cc=cc) + send_mail( + request, + to, + None, + "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()), + cc=cc) + + +def email_edit_shepherd(request, doc, old_shepherd_email, text=""): + """Email the change text to the notify group and to the stream chairs""" + (to, cc) = gather_address_lists('email_edit_shepherd', doc=doc) + to.append(old_shepherd_email) + + if not to: + return + + if not text: + text = u"mailing sheperds" + + text = strip_tags(text) + + send_mail( + request, + to, + None, + "Shepherd Edited: %s" % doc.file_tag(), + "doc/mail/email_edit_shepherd.txt", + dict( + text=text, + 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) - addrs = gather_address_lists('doc_pulled_from_rfc_queue',doc=doc) + extra = extra_automation_headers(doc) + 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, - prev_state=prev_state, - next_state=next_state, - comment=comment, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - extra=extra) + 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, + prev_state=prev_state, + next_state=next_state, + comment=comment, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + extra=extra) + 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) + 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("
", "\n")) - + return strip_tags( + html.replace("<", "<").replace(">", ">").replace("&", "&") + .replace("
", "\n")) + + def email_update_telechat(request, doc, text): - (to, cc) = gather_address_lists('doc_telechat_details_changed',doc=doc) + (to, cc) = gather_address_lists('doc_telechat_details_changed', doc=doc) if not to: return - + text = strip_tags(text) - 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()), - cc=cc) + 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()), + cc=cc) def generate_ballot_writeup(request, doc): @@ -114,11 +172,13 @@ def generate_ballot_writeup(request, doc): e.doc = doc e.rev = doc.rev e.desc = u"Ballot writeup was generated" - e.text = unicode(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana})) + e.text = unicode( + render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana})) # caller is responsible for saving, if necessary return e - + + def generate_ballot_rfceditornote(request, doc): e = WriteupDocEvent() e.type = "changed_ballot_rfceditornote_text" @@ -128,42 +188,57 @@ def generate_ballot_rfceditornote(request, doc): e.desc = u"RFC Editor Note for ballot was generated" e.text = unicode(render_to_string("doc/mail/ballot_rfceditornote.txt")) e.save() - + return e + def generate_last_call_announcement(request, doc): expiration_date = datetime.date.today() + datetime.timedelta(days=14) if doc.group.type_id in ("individ", "area"): group = "an individual submitter" expiration_date += datetime.timedelta(days=14) else: - group = "the %s %s (%s)" % (doc.group.name, doc.group.type.name, doc.group.acronym) + group = "the %s %s (%s)" % (doc.group.name, doc.group.type.name, + doc.group.acronym) - doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) - - iprs = iprs_from_docs(related_docs(DocAlias.objects.get(name=doc.canonical_name()))) + doc.filled_title = textwrap.fill( + doc.title, width=70, subsequent_indent=" " * 3) + + iprs = iprs_from_docs( + related_docs(DocAlias.objects.get(name=doc.canonical_name()))) if iprs: - ipr_links = [ urlreverse("ietf.ipr.views.show", kwargs=dict(id=i.id)) for i in iprs] - ipr_links = [ settings.IDTRACKER_BASE_URL+url if not url.startswith("http") else url for url in ipr_links ] + ipr_links = [ + urlreverse("ietf.ipr.views.show", kwargs=dict(id=i.id)) + for i in iprs + ] + ipr_links = [ + settings.IDTRACKER_BASE_URL + url + if not url.startswith("http") else url for url in ipr_links + ] else: ipr_links = None - downrefs = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref()] + downrefs = [ + rel for rel in doc.relateddocument_set.all() + if rel.is_downref() and not rel.is_approved_downref() + ] - 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"), - to=addrs.to, - cc=addrs.cc, - group=group, - docs=[ doc ], - urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], - ipr_links=ipr_links, - downrefs=downrefs, - ) - ) + 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"), + to=addrs.to, + cc=addrs.cc, + group=group, + docs=[doc], + urls=[settings.IDTRACKER_BASE_URL + doc.get_absolute_url()], + ipr_links=ipr_links, + downrefs=downrefs, )) e = WriteupDocEvent() e.type = "changed_last_call_text" @@ -175,12 +250,15 @@ def generate_last_call_announcement(request, doc): # caller is responsible for saving, if necessary return e - + DO_NOT_PUBLISH_IESG_STATES = ("nopubadw", "nopubanw") + def generate_approval_mail(request, doc): - if doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES or doc.stream_id in ('ise','irtf'): + if doc.get_state_slug( + "draft-iesg") in DO_NOT_PUBLISH_IESG_STATES or doc.stream_id in ( + 'ise', 'irtf'): mail = generate_approval_mail_rfc_editor(request, doc) else: mail = generate_approval_mail_approved(request, doc) @@ -196,6 +274,7 @@ def generate_approval_mail(request, doc): # caller is responsible for saving, if necessary return e + def generate_approval_mail_approved(request, doc): if doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): @@ -205,60 +284,77 @@ def generate_approval_mail_approved(request, doc): # the second check catches some area working groups (like # Transport Area Working Group) - if doc.group.type_id not in ("area", "individ", "ag", "rg") and not doc.group.name.endswith("Working Group"): + if doc.group.type_id not in ( + "area", "individ", "ag", + "rg") and not doc.group.name.endswith("Working Group"): doc.group.name_with_wg = doc.group.name + " Working Group" else: doc.group.name_with_wg = doc.group.name - doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) + doc.filled_title = textwrap.fill( + doc.title, width=70, subsequent_indent=" " * 3) if doc.group.type_id in ("individ", "area"): made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group." else: made_by = "This document is the product of the %s." % doc.group.name_with_wg - - responsible_directors = set([doc.ad,]) - if doc.group.type_id not in ("individ","area"): - responsible_directors.update([x.person for x in Role.objects.filter(group=doc.group.parent,name='ad')]) - responsible_directors = [x.plain_name() for x in responsible_directors if x] - - if len(responsible_directors)>1: - contacts = "The IESG contact persons are "+", ".join(responsible_directors[:-1])+" and "+responsible_directors[-1]+"." + + responsible_directors = set([ + doc.ad, + ]) + if doc.group.type_id not in ("individ", "area"): + responsible_directors.update([ + x.person + for x in Role.objects.filter(group=doc.group.parent, name='ad') + ]) + responsible_directors = [ + x.plain_name() for x in responsible_directors if x + ] + + if len(responsible_directors) > 1: + contacts = "The IESG contact persons are " + ", ".join( + responsible_directors[:-1] + ) + " and " + responsible_directors[-1] + "." else: contacts = "The IESG contact person is %s." % responsible_directors[0] 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(), - to = addrs.to, - cc = addrs.cc, - doc_type=doc_type, - made_by=made_by, - contacts=contacts, - action_type=action_type, - ) - ) + + 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(), + to=addrs.to, + cc=addrs.cc, + doc_type=doc_type, + made_by=made_by, + contacts=contacts, + action_type=action_type, )) + 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 + 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" - addrs = gather_address_lists('ballot_approved_conflrev', doc=doc).as_strings() + 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=addrs.to, + cc=addrs.cc, )) - 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 = addrs.to, - cc = addrs.cc, - ) - ) def generate_publication_request(request, doc): group_description = "" @@ -278,31 +374,36 @@ def generate_publication_request(request, doc): e = doc.latest_event(WriteupDocEvent, type="changed_rfc_editor_note_text") rfcednote = e.text if e else "" - return render_to_string("doc/mail/publication_request.txt", - dict(doc=doc, - doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - group_description=group_description, - approving_body=approving_body, - consensus_body=consensus_body, - consensus=consensus, - rfc_editor_note=rfcednote, - ) - ) + return render_to_string( + "doc/mail/publication_request.txt", + dict( + doc=doc, + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + group_description=group_description, + approving_body=approving_body, + consensus_body=consensus_body, + consensus=consensus, + rfc_editor_note=rfcednote, )) + def send_last_call_request(request, doc): - (to, cc) = gather_address_lists('last_call_requested',doc=doc) + (to, cc) = gather_address_lists('last_call_requested', doc=doc) frm = '"DraftTracker Mail System" ' - - 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(), - ), - cc=cc) + + 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(), ), + cc=cc) + def email_resurrect_requested(request, doc, by): - (to, cc) = gather_address_lists('resurrection_requested',doc=doc) + (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") @@ -310,64 +411,75 @@ def email_resurrect_requested(request, doc, by): e = by.role_email("ad") frm = e.address - send_mail(request, to, e.formatted_email(), - "I-D Resurrection Request", - "doc/mail/resurrect_request_email.txt", - dict(doc=doc, - by=frm, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - cc=cc) + send_mail( + request, + to, + e.formatted_email(), + "I-D Resurrection Request", + "doc/mail/resurrect_request_email.txt", + dict( + doc=doc, + by=frm, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) + def email_resurrection_completed(request, doc, requester): - (to, cc) = gather_address_lists('resurrection_completed',doc=doc) + (to, cc) = gather_address_lists('resurrection_completed', doc=doc) frm = "I-D Administrator " - 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()), - cc=cc) + 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()), + cc=cc) + def email_ballot_deferred(request, doc, by, telechat_date): - (to, cc) = gather_address_lists('ballot_deferred',doc=doc) + (to, cc) = gather_address_lists('ballot_deferred', doc=doc) frm = "DraftTracker Mail System " - send_mail(request, to, frm, - "IESG Deferred Ballot notification: %s" % doc.file_tag(), - "doc/mail/ballot_deferred_email.txt", - dict(doc=doc, - by=by, - action='deferred', - telechat_date=telechat_date), - cc=cc) + send_mail( + request, + to, + frm, + "IESG Deferred Ballot notification: %s" % doc.file_tag(), + "doc/mail/ballot_deferred_email.txt", + dict(doc=doc, by=by, action='deferred', telechat_date=telechat_date), + cc=cc) + def email_ballot_undeferred(request, doc, by, telechat_date): - (to, cc) = gather_address_lists('ballot_deferred',doc=doc) + (to, cc) = gather_address_lists('ballot_deferred', doc=doc) frm = "DraftTracker Mail System " - send_mail(request, to, frm, - "IESG Undeferred Ballot notification: %s" % doc.file_tag(), - "doc/mail/ballot_deferred_email.txt", - dict(doc=doc, - by=by, - action='undeferred', - telechat_date=telechat_date), - cc=cc) + send_mail( + request, + to, + frm, + "IESG Undeferred Ballot notification: %s" % doc.file_tag(), + "doc/mail/ballot_deferred_email.txt", + dict(doc=doc, by=by, action='undeferred', telechat_date=telechat_date), + cc=cc) + def generate_issue_ballot_mail(request, doc, ballot): - + e = doc.latest_event(LastCallDocEvent, type="sent_last_call") last_call_expires = e.expires if e else None - return render_to_string("doc/mail/issue_ballot_mail.txt", - dict(doc=doc, - doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - last_call_expires=last_call_expires, - needed_ballot_positions= - needed_ballot_positions(doc, - doc.active_ballot().active_ad_positions().values() - ), - ) - ) + return render_to_string( + "doc/mail/issue_ballot_mail.txt", + dict( + doc=doc, + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + last_call_expires=last_call_expires, + needed_ballot_positions=needed_ballot_positions( + doc, doc.active_ballot().active_ad_positions().values()), )) + def email_iana(request, doc, to, msg, cc=None): # fix up message and send it with extra info on doc in headers @@ -379,12 +491,16 @@ def email_iana(request, doc, to, msg, cc=None): extra["Reply-To"] = "noreply@ietf.org" extra["X-IETF-Draft-string"] = doc.name extra["X-IETF-Draft-revision"] = doc.rev - - send_mail_text(request, to, - parsed_msg["From"], parsed_msg["Subject"], - parsed_msg.get_payload().decode(str(parsed_msg.get_charset())), - extra=extra, - cc=cc) + + send_mail_text( + request, + to, + parsed_msg["From"], + parsed_msg["Subject"], + parsed_msg.get_payload().decode(str(parsed_msg.get_charset())), + extra=extra, + cc=cc) + def extra_automation_headers(doc): extra = {} @@ -393,143 +509,200 @@ def extra_automation_headers(doc): return extra + def email_last_call_expired(doc): - if not doc.type_id in ['draft','statchg']: + if not doc.type_id in ['draft', 'statchg']: return - text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state("draft-iesg" if doc.type_id == 'draft' else "statchg").name - addrs = gather_address_lists('last_call_expired',doc=doc) - - send_mail(None, - addrs.to, - "DraftTracker Mail System ", - "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 = addrs.cc) + text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state( + "draft-iesg" if doc.type_id == 'draft' else "statchg").name + addrs = gather_address_lists('last_call_expired', doc=doc) + + send_mail( + None, + addrs.to, + "DraftTracker Mail System ", + "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=addrs.cc) + def email_intended_status_changed(request, doc, text): - (to,cc) = gather_address_lists('doc_intended_status_changed',doc=doc) + (to, cc) = gather_address_lists('doc_intended_status_changed', doc=doc) 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) + 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) + def email_comment(request, doc, comment): - (to, cc) = gather_address_lists('doc_added_comment',doc=doc) + (to, cc) = gather_address_lists('doc_added_comment', doc=doc) - 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) + 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_adopted(request, doc, prev_state, new_state, by, comment=""): - (to, cc) = gather_address_lists('doc_adopted_by_group',doc=doc) + (to, cc) = gather_address_lists('doc_adopted_by_group', doc=doc) state_type = (prev_state or new_state).type - send_mail(request, to, settings.DEFAULT_FROM_EMAIL, - u'The %s %s has placed %s in state "%s"' % - (doc.group.acronym.upper(),doc.group.type_id.upper(), doc.name, new_state or "None"), - '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) + send_mail( + request, + to, + settings.DEFAULT_FROM_EMAIL, + u'The %s %s has placed %s in state "%s"' % + (doc.group.acronym.upper(), doc.group.type_id.upper(), doc.name, + new_state or "None"), + '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) + +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, - 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) + 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, + 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_tags_changed(request, doc, added_tags, removed_tags, by, comment=""): - (to, cc) = gather_address_lists('doc_stream_state_edited',doc=doc) +def email_stream_tags_changed(request, + doc, + added_tags, + removed_tags, + by, + comment=""): + + (to, cc) = gather_address_lists('doc_stream_state_edited', doc=doc) + + 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, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + added=added_tags, + removed=removed_tags, + by=by, + comment=comment), + cc=cc) - 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, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - added=added_tags, - removed=removed_tags, - by=by, - comment=comment), - cc=cc) def send_review_possibly_replaces_request(request, doc, submitter_info): - addrs = gather_address_lists('doc_replacement_suggested',doc=doc) + 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")]) + possibly_replaces = Document.objects.filter(name__in=[ + alias.name for alias in doc.related_that_doc("possibly-replaces") + ]) for other_doc in possibly_replaces: - (other_to, other_cc) = gather_address_lists('doc_replacement_suggested',doc=other_doc) + (other_to, other_cc) = gather_address_lists( + 'doc_replacement_suggested', doc=other_doc) to.update(other_to) cc.update(other_cc) - 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, - submitter_info=submitter_info, - possibly_replaces=doc.related_that_doc("possibly-replaces"), - review_url=settings.IDTRACKER_BASE_URL + urlreverse('ietf.doc.views_draft.review_possibly_replaces', kwargs={ "name": doc.name })), - cc=list(cc),) + 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, + submitter_info=submitter_info, + possibly_replaces=doc.related_that_doc("possibly-replaces"), + review_url=settings.IDTRACKER_BASE_URL + urlreverse( + 'ietf.doc.views_draft.review_possibly_replaces', + kwargs={"name": doc.name})), + cc=list(cc), ) + 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) + 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 %s Review: %s (%s)'%(charter.group.type.name,charter.group.name,charter.group.acronym), - 'doc/mail/charter_internal_review.txt', - dict(charter=charter, - charter_text=charter_text, - review_type = "new" if charter.group.state_id == "proposed" else "recharter", - charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(), - chairs=charter.group.role_set.filter(name="chair"), - secr=charter.group.role_set.filter(name="secr"), - ads=charter.group.role_set.filter(name='ad'), - parent_ads=charter.group.parent.role_set.filter(name='ad'), - techadv=charter.group.role_set.filter(name="techadv"), - milestones=charter.group.groupmilestone_set.filter(state="charter"), - ), - cc=addrs.cc, - extra={'Reply-To':"iesg@ietf.org"}, - ) + filename, + os.path.join(settings.CHARTER_PATH, filename), + split=False, + markup=False, ) + send_mail( + request, + addrs.to, + settings.DEFAULT_FROM_EMAIL, + 'Internal %s Review: %s (%s)' % + (charter.group.type.name, charter.group.name, charter.group.acronym), + 'doc/mail/charter_internal_review.txt', + dict( + charter=charter, + charter_text=charter_text, + review_type="new" + if charter.group.state_id == "proposed" else "recharter", + charter_url=settings.IDTRACKER_BASE_URL + + charter.get_absolute_url(), + chairs=charter.group.role_set.filter(name="chair"), + secr=charter.group.role_set.filter(name="secr"), + ads=charter.group.role_set.filter(name='ad'), + parent_ads=charter.group.parent.role_set.filter(name='ad'), + techadv=charter.group.role_set.filter(name="techadv"), + milestones=charter.group.groupmilestone_set.filter( + state="charter"), ), + cc=addrs.cc, + extra={'Reply-To': "iesg@ietf.org"}, ) diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 30046b267..2218be4d1 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -12,19 +12,23 @@ from django.contrib.auth.decorators import login_required from django.template.defaultfilters import pluralize from django.contrib import messages -import debug # pyflakes:ignore +import debug # pyflakes:ignore -from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State, - StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS) -from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested, +from ietf.doc.models import ( + Document, DocAlias, RelatedDocument, State, StateType, DocEvent, + ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS) +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, email_adopted, email_intended_status_changed, - email_iesg_processing_document, email_ad_approved_doc ) -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, - set_replaces_for_document, default_consensus ) + email_edit_shepherd, email_stream_state_changed, email_stream_tags_changed, + extra_automation_headers, generate_publication_request, email_adopted, + email_intended_status_changed, email_iesg_processing_document, + email_ad_approved_doc) +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, + set_replaces_for_document, default_consensus) from ietf.doc.lastcall import request_last_call from ietf.doc.fields import SearchableDocAliasesField from ietf.group.models import Group, Role @@ -39,42 +43,52 @@ 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) - substate = forms.ModelChoiceField(DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS), required=False) - comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) + state = forms.ModelChoiceField( + State.objects.filter(used=True, type="draft-iesg"), + empty_label=None, + required=True) + substate = forms.ModelChoiceField( + DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS), required=False) + comment = forms.CharField( + widget=forms.Textarea, required=False, strip=False) def clean(self): retclean = self.cleaned_data state = self.cleaned_data.get('state', '(None)') - tag = self.cleaned_data.get('substate','') - comment = self.cleaned_data['comment'].strip() # pyflakes:ignore + tag = self.cleaned_data.get('substate', '') + comment = self.cleaned_data['comment'].strip() # pyflakes:ignore doc = get_object_or_404(Document, docalias__name=self.docname) prev = doc.get_state("draft-iesg") - + # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if state == prev and tag == prev_tag: - self._errors['comment'] = ErrorList([u'State not changed. Comments entered will be lost with no state change. Please go back and use the Add Comment feature on the history tab to add comments without changing state.']) + self._errors['comment'] = ErrorList([ + u'State not changed. Comments entered will be lost with no state change. Please go back and use the Add Comment feature on the history tab to add comments without changing state.' + ]) return retclean -@role_required('Area Director','Secretariat') + +@role_required('Area Director', 'Secretariat') def change_state(request, name): """Change IESG state of Internet Draft, notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) - if (not doc.latest_event(type="started_iesg_process")) or doc.get_state_slug() == "expired": + if (not doc.latest_event( + type="started_iesg_process")) or doc.get_state_slug() == "expired": raise Http404 login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) - form.docname=name + form.docname = name if form.is_valid(): new_state = form.cleaned_data['state'] @@ -83,7 +97,6 @@ def change_state(request, name): tag = form.cleaned_data['substate'] comment = form.cleaned_data['comment'].strip() - # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) @@ -96,8 +109,13 @@ def change_state(request, name): events = [] - e = add_state_change_event(doc, login, prev_state, new_state, - prev_tags=prev_tags, new_tags=new_tags) + e = add_state_change_event( + doc, + login, + prev_state, + new_state, + prev_tags=prev_tags, + new_tags=new_tags) events.append(e) @@ -115,55 +133,69 @@ def change_state(request, name): msg = u"\n".join(e.desc for e in events) - email_state_changed(request, doc, msg,'doc_state_edited') - - if new_state.slug == "approved" and new_tags == [] and has_role(request.user, "Area Director"): - email_ad_approved_doc(request, doc, comment) + 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"): - email_pulled_from_rfc_queue(request, doc, comment, prev_state, new_state) + if new_state.slug == "approved" and new_tags == [] and has_role( + request.user, "Area Director"): + email_ad_approved_doc(request, doc, comment) + + if prev_state and prev_state.slug in ( + "ann", + "rfcqueue") and new_state.slug not in ("rfcqueue", + "pub"): + email_pulled_from_rfc_queue(request, doc, comment, + prev_state, new_state) if new_state.slug in ("iesg-eva", "lc"): if not doc.get_state_slug("draft-iana-review"): - doc.set_state(State.objects.get(used=True, type="draft-iana-review", slug="need-rev")) + doc.set_state( + State.objects.get( + used=True, + type="draft-iana-review", + slug="need-rev")) if new_state.slug == "lc-req": request_last_call(request, doc) - return render(request, 'doc/draft/last_call_requested.html', - dict(doc=doc, - url=doc.get_absolute_url())) - + return render(request, + 'doc/draft/last_call_requested.html', + dict(doc=doc, url=doc.get_absolute_url())) + return HttpResponseRedirect(doc.get_absolute_url()) else: state = doc.get_state("draft-iesg") t = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) - form = ChangeStateForm(initial=dict(state=state.pk if state else None, - substate=t[0].pk if t else None)) - form.docname=name + form = ChangeStateForm(initial=dict( + state=state.pk if state else None, substate=t[0].pk + if t else None)) + form.docname = name state = doc.get_state("draft-iesg") next_states = state.next_states.all() if state else None prev_state = None - hists = doc.history_set.exclude(states=doc.get_state("draft-iesg")).order_by('-time')[:1] + hists = doc.history_set.exclude( + states=doc.get_state("draft-iesg")).order_by('-time')[:1] if hists: prev_state = hists[0].get_state("draft-iesg") to_iesg_eval = None if not doc.latest_event(type="sent_ballot_announcement"): if next_states and next_states.filter(slug="iesg-eva"): - to_iesg_eval = State.objects.get(used=True, type="draft-iesg", slug="iesg-eva") + to_iesg_eval = State.objects.get( + used=True, type="draft-iesg", slug="iesg-eva") next_states = next_states.exclude(slug="iesg-eva") return render(request, 'doc/draft/change_state.html', - dict(form=form, - doc=doc, - state=state, - prev_state=prev_state, - next_states=next_states, - to_iesg_eval=to_iesg_eval)) + dict( + form=form, + doc=doc, + state=state, + prev_state=prev_state, + next_states=next_states, + to_iesg_eval=to_iesg_eval)) + class ChangeIanaStateForm(forms.Form): state = forms.ModelChoiceField(State.objects.all(), required=False) @@ -171,9 +203,12 @@ class ChangeIanaStateForm(forms.Form): def __init__(self, state_type, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) - choices = State.objects.filter(used=True, type=state_type).order_by("order").values_list("pk", "name") + choices = State.objects.filter( + used=True, type=state_type).order_by("order").values_list( + "pk", "name") self.fields['state'].choices = [("", "-------")] + list(choices) + @role_required('Secretariat', 'IANA') def change_iana_state(request, name, state_type): """Change IANA review state of Internet Draft. Normally, this is done via @@ -192,39 +227,47 @@ def change_iana_state(request, name, state_type): if new_state != prev_state: doc.set_state(new_state) - events = [add_state_change_event(doc, request.user.person, prev_state, new_state)] + events = [ + add_state_change_event(doc, request.user.person, + prev_state, new_state) + ] doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) else: - form = ChangeIanaStateForm(state_type, initial=dict(state=prev_state.pk if prev_state else None)) + form = ChangeIanaStateForm( + state_type, + initial=dict(state=prev_state.pk if prev_state else None)) return render(request, 'doc/draft/change_iana_state.html', - dict(form=form, - doc=doc)) + dict(form=form, doc=doc)) - class ChangeStreamForm(forms.Form): - stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False) - comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) + stream = forms.ModelChoiceField( + StreamName.objects.exclude(slug="legacy"), required=False) + comment = forms.CharField( + widget=forms.Textarea, required=False, strip=False) + @login_required def change_stream(request, name): """Change the stream of a Document of type 'draft', notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) - if not doc.type_id=='draft': + if not doc.type_id == 'draft': raise Http404 if not (has_role(request.user, ("Area Director", "Secretariat")) or - (request.user.is_authenticated and - Role.objects.filter(name="chair", - group__acronym__in=StreamName.objects.values_list("slug", flat=True), - person__user=request.user))): - return HttpResponseForbidden("You do not have permission to view this page") + (request.user.is_authenticated and Role.objects.filter( + name="chair", + group__acronym__in=StreamName.objects.values_list( + "slug", flat=True), + person__user=request.user))): + return HttpResponseForbidden( + "You do not have permission to view this page") login = request.user.person @@ -247,14 +290,17 @@ def change_stream(request, name): events = [] - e = DocEvent(doc=doc, rev=doc.rev, by=login, type='changed_document') - e.desc = u"Stream changed to %s from %s"% (new_stream, old_stream or "None") + e = DocEvent( + doc=doc, rev=doc.rev, by=login, type='changed_document') + e.desc = u"Stream changed to %s from %s" % ( + new_stream, old_stream or "None") e.save() events.append(e) if comment: - c = DocEvent(doc=doc, rev=doc.rev, by=login, type="added_comment") + c = DocEvent( + doc=doc, rev=doc.rev, by=login, type="added_comment") c.desc = comment c.save() events.append(c) @@ -272,13 +318,15 @@ def change_stream(request, name): form = ChangeStreamForm(initial=dict(stream=stream)) return render(request, 'doc/draft/change_stream.html', - dict(form=form, - doc=doc, - )) + dict( + form=form, + doc=doc, )) + class ReplacesForm(forms.Form): replaces = SearchableDocAliasesField(required=False) - comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) + comment = forms.CharField( + widget=forms.Textarea, required=False, strip=False) def __init__(self, *args, **kwargs): self.doc = kwargs.pop('doc') @@ -289,19 +337,24 @@ class ReplacesForm(forms.Form): for d in self.cleaned_data['replaces']: if d.document == self.doc: raise forms.ValidationError("A draft can't replace itself") - if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc": + if d.document.type_id == "draft" and d.document.get_state_slug( + ) == "rfc": raise forms.ValidationError("A draft can't replace an RFC") return self.cleaned_data['replaces'] + def replaces(request, name): - """Change 'replaces' set of a Document of type 'draft' , notifying parties + """Change 'replaces' set of a Document of type 'draft' , notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, ("Secretariat", "Area Director", "WG Chair", "RG Chair", "WG Secretary", "RG Secretary")) - or is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + if not (has_role(request.user, + ("Secretariat", "Area Director", "WG Chair", "RG Chair", + "WG Secretary", "RG Secretary")) or + is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") if request.method == 'POST': form = ReplacesForm(request.POST, doc=doc) @@ -312,9 +365,14 @@ def replaces(request, name): by = request.user.person if new_replaces != old_replaces: - events = set_replaces_for_document(request, doc, new_replaces, by=by, - email_subject="%s replacement status updated by %s" % (doc.name, by), - comment=comment) + events = set_replaces_for_document( + request, + doc, + new_replaces, + by=by, + email_subject="%s replacement status updated by %s" % + (doc.name, by), + comment=comment) doc.save_with_history(events) @@ -322,30 +380,42 @@ def replaces(request, name): else: form = ReplacesForm(doc=doc) return render(request, 'doc/draft/change_replaces.html', - dict(form=form, - doc=doc, - )) + dict( + form=form, + doc=doc, )) + class SuggestedReplacesForm(forms.Form): - replaces = forms.ModelMultipleChoiceField(queryset=DocAlias.objects.all(), - label="Suggestions", required=False, widget=forms.CheckboxSelectMultiple, - help_text="Select only the documents that are replaced by this document") - comment = forms.CharField(label="Optional comment", widget=forms.Textarea, required=False, strip=False) + replaces = forms.ModelMultipleChoiceField( + queryset=DocAlias.objects.all(), + label="Suggestions", + required=False, + widget=forms.CheckboxSelectMultiple, + help_text="Select only the documents that are replaced by this document" + ) + comment = forms.CharField( + label="Optional comment", + widget=forms.Textarea, + required=False, + strip=False) def __init__(self, suggested, *args, **kwargs): super(SuggestedReplacesForm, self).__init__(*args, **kwargs) pks = [d.pk for d in suggested] self.fields["replaces"].initial = pks - self.fields["replaces"].queryset = self.fields["replaces"].queryset.filter(pk__in=pks) + self.fields["replaces"].queryset = self.fields[ + "replaces"].queryset.filter(pk__in=pks) self.fields["replaces"].choices = [(d.pk, d.name) for d in suggested] + def review_possibly_replaces(request, name): doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, ("Secretariat", "Area Director")) - or is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) or + is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") suggested = list(doc.related_that_doc("possibly-replaces")) if not suggested: @@ -364,17 +434,38 @@ def review_possibly_replaces(request, name): events = [] # all suggestions reviewed, so get rid of them - events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, type="reviewed_suggested_replaces", - desc="Reviewed suggested replacement relationships: %s" % ", ".join(d.name for d in suggested))) - RelatedDocument.objects.filter(source=doc, target__in=suggested,relationship__slug='possibly-replaces').delete() + events.append( + DocEvent.objects.create( + doc=doc, + rev=doc.rev, + by=by, + type="reviewed_suggested_replaces", + desc="Reviewed suggested replacement relationships: %s" % + ", ".join(d.name for d in suggested))) + RelatedDocument.objects.filter( + source=doc, + target__in=suggested, + relationship__slug='possibly-replaces').delete() if new_replaces != old_replaces: - events.extend(set_replaces_for_document(request, doc, new_replaces, by=by, - email_subject="%s replacement status updated by %s" % (doc.name, by), - comment=comment)) + events.extend( + set_replaces_for_document( + request, + doc, + new_replaces, + by=by, + email_subject="%s replacement status updated by %s" % ( + doc.name, by), + comment=comment)) if comment: - events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, type="added_comment", desc=comment)) + events.append( + DocEvent.objects.create( + doc=doc, + rev=doc.rev, + by=by, + type="added_comment", + desc=comment)) doc.save_with_history(events) @@ -383,25 +474,32 @@ def review_possibly_replaces(request, name): form = SuggestedReplacesForm(suggested) return render(request, 'doc/draft/review_possibly_replaces.html', - dict(form=form, - doc=doc, - )) + dict( + form=form, + doc=doc, )) class ChangeIntentionForm(forms.Form): - intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status") - comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) + intended_std_level = forms.ModelChoiceField( + IntendedStdLevelName.objects.filter(used=True), + empty_label="(None)", + required=True, + label="Intended RFC status") + comment = forms.CharField( + widget=forms.Textarea, required=False, strip=False) + def change_intention(request, name): - """Change the intended publication status of a Document of type 'draft' , notifying parties + """Change the intended publication status of a Document of type 'draft' , notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, ("Secretariat", "Area Director")) - or is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) or + is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") login = request.user.person @@ -416,24 +514,33 @@ def change_intention(request, name): doc.intended_std_level = new_level events = [] - e = DocEvent(doc=doc, rev=doc.rev, by=login, type='changed_document') - e.desc = u"Intended Status changed to %s from %s"% (new_level,old_level) + e = DocEvent( + doc=doc, rev=doc.rev, by=login, type='changed_document') + e.desc = u"Intended Status changed to %s from %s" % ( + new_level, old_level) e.save() events.append(e) if comment: - c = DocEvent(doc=doc, rev=doc.rev, by=login, type="added_comment") + c = DocEvent( + doc=doc, rev=doc.rev, by=login, type="added_comment") c.desc = comment c.save() events.append(c) - de = doc.latest_event(ConsensusDocEvent, type="changed_consensus") + de = doc.latest_event( + ConsensusDocEvent, type="changed_consensus") prev_consensus = de and de.consensus - if not prev_consensus and doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): - ce = ConsensusDocEvent(doc=doc, rev=doc.rev, by=login, type="changed_consensus") + if not prev_consensus and doc.intended_std_level_id in ( + "std", "ds", "ps", "bcp"): + ce = ConsensusDocEvent( + doc=doc, + rev=doc.rev, + by=login, + type="changed_consensus") ce.consensus = True - ce.desc = "Changed consensus to %s from %s" % (nice_consensus(True), - nice_consensus(prev_consensus)) + ce.desc = "Changed consensus to %s from %s" % ( + nice_consensus(True), nice_consensus(prev_consensus)) ce.save() events.append(ce) @@ -447,21 +554,51 @@ def change_intention(request, name): else: intended_std_level = doc.intended_std_level - form = ChangeIntentionForm(initial=dict(intended_std_level=intended_std_level)) + form = ChangeIntentionForm(initial=dict( + intended_std_level=intended_std_level)) return render(request, 'doc/draft/change_intended_status.html', - dict(form=form, - doc=doc, - )) + dict( + form=form, + doc=doc, )) + class EditInfoForm(forms.Form): - intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status") - area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area") - ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True) - create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False) - notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False) - note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False) - telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'})) + intended_std_level = forms.ModelChoiceField( + IntendedStdLevelName.objects.filter(used=True), + empty_label="(None)", + required=True, + label="Intended RFC status") + area = forms.ModelChoiceField( + Group.objects.filter(type="area", state="active"), + empty_label="(None - individual submission)", + required=False, + label="Assigned to area") + ad = forms.ModelChoiceField( + Person.objects.filter( + role__name="ad", + role__group__state="active", + role__group__type='area').order_by('name'), + label="Responsible AD", + empty_label="(None)", + required=True) + create_in_state = forms.ModelChoiceField( + State.objects.filter( + used=True, type="draft-iesg", slug__in=("pub-req", "watching")), + empty_label=None, + required=False) + notify = forms.CharField( + max_length=255, + label="Notice emails", + help_text="Separate email addresses with commas.", + required=False) + note = forms.CharField( + widget=forms.Textarea, label="IESG note", required=False, strip=False) + telechat_date = forms.TypedChoiceField( + coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), + empty_value=None, + required=False, + widget=forms.Select(attrs={'onchange': 'make_bold()'})) returning_item = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): @@ -471,48 +608,58 @@ class EditInfoForm(forms.Form): ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices if ad_pk and ad_pk not in [pk for pk, name in choices]: - self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] - + self.fields['ad'].choices = list(choices) + [ + ("", "-------"), + (ad_pk, Person.objects.get(pk=ad_pk).plain_name()) + ] + # telechat choices - dates = [d.date for d in TelechatDate.objects.active().order_by('date')] + dates = [ + d.date for d in TelechatDate.objects.active().order_by('date') + ] init = kwargs['initial']['telechat_date'] if init and init not in dates: dates.insert(0, init) - self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] + self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [ + (d, d.strftime("%Y-%m-%d")) for d in dates + ] # returning item is rendered non-standard - self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)] + self.standard_fields = [ + x for x in self.visible_fields() + if x.name not in ('returning_item', ) + ] def clean_note(self): return self.cleaned_data['note'].replace('\r', '').strip() -def to_iesg(request,name): - """ Submit an IETF stream document to the IESG for publication """ + +def to_iesg(request, name): + """ Submit an IETF stream document to the IESG for publication """ doc = get_object_or_404(Document, docalias__name=name, stream='ietf') - if doc.get_state_slug('draft') == "expired" or doc.get_state_slug('draft-iesg') == 'pub-req' : + if doc.get_state_slug('draft') == "expired" or doc.get_state_slug( + 'draft-iesg') == 'pub-req': raise Http404 if not is_authorized_in_doc_stream(request.user, doc): raise Http404 - - target_state={ - 'iesg' : State.objects.get(type='draft-iesg',slug='pub-req'), - 'wg' : State.objects.get(type='draft-stream-ietf',slug='sub-pub'), + + target_state = { + 'iesg': State.objects.get(type='draft-iesg', slug='pub-req'), + 'wg': State.objects.get(type='draft-stream-ietf', slug='sub-pub'), } - target_map={ - 'draft-iesg' : 'iesg', - 'draft-stream-ietf' : 'wg' - } + target_map = {'draft-iesg': 'iesg', 'draft-stream-ietf': 'wg'} - warn={} + warn = {} if not doc.intended_std_level: warn['intended_std_level'] = True if not doc.shepherd: warn['shepherd'] = True - shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup") + shepherd_writeup = doc.latest_event( + WriteupDocEvent, type="changed_protocol_writeup") if not shepherd_writeup: warn['shepherd_writeup'] = True tags = doc.tags.filter(slug__in=get_tags_for_stream_id(doc.stream_id)) @@ -521,11 +668,11 @@ def to_iesg(request,name): notify = doc.notify if not notify: notify = get_initial_notify(doc) - ad = doc.ad or getattr(doc.group.ad_role(),'person',None) + ad = doc.ad or getattr(doc.group.ad_role(), 'person', None) if request.method == 'POST': - if request.POST.get("confirm", ""): + if request.POST.get("confirm", ""): by = request.user.person @@ -539,27 +686,35 @@ def to_iesg(request,name): e.by = by e.doc = doc e.rev = doc.rev - e.desc = "IESG process started in state %s" % target_state['iesg'].name + e.desc = "IESG process started in state %s" % target_state[ + 'iesg'].name e.save() events.append(e) - for state_type in ['draft-iesg','draft-stream-ietf']: - prev_state=doc.get_state(state_type) + for state_type in ['draft-iesg', 'draft-stream-ietf']: + prev_state = doc.get_state(state_type) new_state = target_state[target_map[state_type]] - if not prev_state==new_state: + if not prev_state == new_state: doc.set_state(new_state) - events.append(add_state_change_event(doc=doc,by=by,prev_state=prev_state,new_state=new_state)) + events.append( + add_state_change_event( + doc=doc, + by=by, + prev_state=prev_state, + new_state=new_state)) - if not doc.ad == ad : + if not doc.ad == ad: doc.ad = ad changes.append("Responsible AD changed to %s" % doc.ad) - if not doc.notify == notify : + if not doc.notify == notify: doc.notify = notify - changes.append("State Change Notice email list changed to %s" % doc.notify) + changes.append("State Change Notice email list changed to %s" % + doc.notify) - # Get the last available writeup - previous_writeup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") +# Get the last available writeup + previous_writeup = doc.latest_event( + WriteupDocEvent, type="changed_protocol_writeup") if previous_writeup != None: changes.append(previous_writeup.text) @@ -572,30 +727,37 @@ def to_iesg(request,name): doc.save_with_history(events) - addrs= gather_address_lists('pubreq_iesg',doc=doc) + addrs = gather_address_lists('pubreq_iesg', doc=doc) extra = {} extra['Cc'] = addrs.as_strings().cc - send_mail(request=request, - to = addrs.to, - frm = by.formatted_email(), - subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev), - template = "doc/submit_to_iesg_email.txt", - context = dict(doc=doc,by=by,url="%s%s"%(settings.IDTRACKER_BASE_URL,doc.get_absolute_url()),), - extra = extra) + send_mail( + request=request, + to=addrs.to, + frm=by.formatted_email(), + subject="Publication has been requested for %s-%s" % (doc.name, + doc.rev), + template="doc/submit_to_iesg_email.txt", + context=dict( + doc=doc, + by=by, + url="%s%s" % (settings.IDTRACKER_BASE_URL, + doc.get_absolute_url()), ), + extra=extra) return HttpResponseRedirect(doc.get_absolute_url()) return render(request, 'doc/submit_to_iesg.html', - dict(doc=doc, - warn=warn, - target_state=target_state, - ad=ad, - shepherd_writeup=shepherd_writeup, - tags=tags, - notify=notify, - )) + dict( + doc=doc, + warn=warn, + target_state=target_state, + ad=ad, + shepherd_writeup=shepherd_writeup, + tags=tags, + notify=notify, )) -@role_required('Area Director','Secretariat') + +@role_required('Area Director', 'Secretariat') def edit_info(request, name): """Edit various Internet Draft attributes, notifying parties as necessary and logging changes as document events.""" @@ -604,7 +766,9 @@ def edit_info(request, name): raise Http404 new_document = False - if not doc.get_state("draft-iesg"): # FIXME: should probably receive "new document" as argument to view instead of this + if not doc.get_state( + "draft-iesg" + ): # FIXME: should probably receive "new document" as argument to view instead of this new_document = True doc.notify = get_initial_notify(doc) @@ -613,9 +777,9 @@ def edit_info(request, name): initial_returning_item = bool(e and e.returning_item) if request.method == 'POST': - form = EditInfoForm(request.POST, - initial=dict(ad=doc.ad_id, - telechat_date=initial_telechat_date)) + form = EditInfoForm( + request.POST, + initial=dict(ad=doc.ad_id, telechat_date=initial_telechat_date)) if form.is_valid(): by = request.user.person @@ -626,9 +790,10 @@ def edit_info(request, name): doc.set_state(r['create_in_state']) # Is setting the WG state here too much of a hidden side-effect? - if r['create_in_state'].slug=='pub-req': - if doc.stream and doc.stream.slug=='ietf' and doc.group and doc.group.type_id == 'wg': - submitted_state = State.objects.get(type='draft-stream-ietf',slug='sub-pub') + if r['create_in_state'].slug == 'pub-req': + if doc.stream and doc.stream.slug == 'ietf' and doc.group and doc.group.type_id == 'wg': + submitted_state = State.objects.get( + type='draft-stream-ietf', slug='sub-pub') doc.set_state(submitted_state) e = DocEvent() e.type = "changed_document" @@ -639,7 +804,9 @@ def edit_info(request, name): e.save() events.append(e) - replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces") + replaces = Document.objects.filter( + docalias__relateddocument__source=doc, + docalias__relateddocument__relationship="replaces") if replaces: # this should perhaps be somewhere else, e.g. the # place where the replace relationship is established? @@ -648,7 +815,8 @@ def edit_info(request, name): e.by = Person.objects.get(name="(System)") e.doc = doc e.rev = doc.rev - e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url()) + e.desc = "Earlier history may be found in the Comment Log for %s" % ( + replaces[0], replaces[0].get_absolute_url()) e.save() events.append(e) @@ -657,7 +825,8 @@ def edit_info(request, name): e.by = by e.doc = doc e.rev = doc.rev - e.desc = "IESG process started in state %s" % doc.get_state("draft-iesg").name + e.desc = "IESG process started in state %s" % doc.get_state( + "draft-iesg").name e.save() events.append(e) @@ -667,9 +836,9 @@ def edit_info(request, name): entry = "%(attr)s changed to %(new)s from %(old)s" if new_document: entry = "%(attr)s changed to %(new)s" - + return entry % dict(attr=attr, new=new, old=old) - + def diff(attr, name): v = getattr(doc, attr) if r[attr] != v: @@ -690,7 +859,7 @@ def edit_info(request, name): changes.append("Note changed to '%s'" % r['note']) else: changes.append("Note added '%s'" % r['note']) - + doc.note = r['note'] if doc.group.type_id in ("individ", "area"): @@ -699,17 +868,24 @@ def edit_info(request, name): if r["area"] != doc.group: if r["area"].type_id == "area": - changes.append(u"Assigned to %s" % r["area"].name) + changes.append( + u"Assigned to %s" % r["area"].name) else: changes.append(u"No longer assigned to any area") doc.group = r["area"] for c in changes: - events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, desc=c, type="changed_document")) + events.append( + DocEvent.objects.create( + doc=doc, + rev=doc.rev, + by=by, + desc=c, + type="changed_document")) # Todo - chase this - e = update_telechat(request, doc, by, - r['telechat_date'], r['returning_item']) + e = update_telechat(request, doc, by, r['telechat_date'], + r['returning_item']) if e: events.append(e) @@ -718,33 +894,40 @@ def edit_info(request, name): if changes: email_iesg_processing_document(request, doc, changes) - + return HttpResponseRedirect(doc.get_absolute_url()) else: - init = dict(intended_std_level=doc.intended_std_level_id, - area=doc.group_id, - ad=doc.ad_id, - notify=doc.notify, - note=doc.note, - telechat_date=initial_telechat_date, - returning_item=initial_returning_item, - ) + init = dict( + intended_std_level=doc.intended_std_level_id, + area=doc.group_id, + ad=doc.ad_id, + notify=doc.notify, + note=doc.note, + telechat_date=initial_telechat_date, + returning_item=initial_returning_item, ) form = EditInfoForm(initial=init) # optionally filter out some fields if not new_document: - form.standard_fields = [x for x in form.standard_fields if x.name != "create_in_state"] + form.standard_fields = [ + x for x in form.standard_fields if x.name != "create_in_state" + ] if doc.group.type_id not in ("individ", "area"): - form.standard_fields = [x for x in form.standard_fields if x.name != "area"] + form.standard_fields = [ + x for x in form.standard_fields if x.name != "area" + ] return render(request, 'doc/draft/edit_info.html', - dict(doc=doc, - form=form, - user=request.user, - ballot_issued=doc.latest_event(type="sent_ballot_announcement"))) + dict( + doc=doc, + form=form, + user=request.user, + ballot_issued=doc.latest_event( + type="sent_ballot_announcement"))) -@role_required('Area Director','Secretariat') + +@role_required('Area Director', 'Secretariat') def request_resurrect(request, name): """Request resurrect of expired Internet Draft.""" doc = get_object_or_404(Document, docalias__name=name) @@ -755,17 +938,17 @@ def request_resurrect(request, name): by = request.user.person email_resurrect_requested(request, doc, by) - + e = DocEvent(doc=doc, rev=doc.rev, by=by) e.type = "requested_resurrect" e.desc = "Resurrection was requested" e.save() - + return HttpResponseRedirect(doc.get_absolute_url()) - + return render(request, 'doc/draft/request_resurrect.html', - dict(doc=doc, - back_url=doc.get_absolute_url())) + dict(doc=doc, back_url=doc.get_absolute_url())) + @role_required('Secretariat') def resurrect(request, name): @@ -775,13 +958,15 @@ def resurrect(request, name): raise Http404 resurrect_requested_by = None - e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect")) + e = doc.latest_event(type__in=('requested_resurrect', + "completed_resurrect")) if e and e.type == 'requested_resurrect': resurrect_requested_by = e.by if request.method == 'POST': if resurrect_requested_by: - email_resurrection_completed(request, doc, requester=resurrect_requested_by) + email_resurrection_completed( + request, doc, requester=resurrect_requested_by) events = [] e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person) @@ -790,25 +975,31 @@ def resurrect(request, name): e.save() events.append(e) - doc.set_state(State.objects.get(used=True, type="draft", slug="active")) - doc.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) + doc.set_state( + State.objects.get(used=True, type="draft", slug="active")) + doc.expires = datetime.datetime.now() + datetime.timedelta( + settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) - + return render(request, 'doc/draft/resurrect.html', - dict(doc=doc, - resurrect_requested_by=resurrect_requested_by, - back_url=doc.get_absolute_url())) + dict( + doc=doc, + resurrect_requested_by=resurrect_requested_by, + back_url=doc.get_absolute_url())) + class IESGNoteForm(forms.Form): - note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False) + note = forms.CharField( + widget=forms.Textarea, label="IESG note", required=False, strip=False) def clean_note(self): # not munging the database content to use html line breaks -- # that has caused a lot of pain in the past. return self.cleaned_data['note'].replace('\r', '').strip() + @role_required("Area Director", "Secretariat") def edit_iesg_note(request, name): doc = get_object_or_404(Document, type="draft", name=name) @@ -831,7 +1022,8 @@ def edit_iesg_note(request, name): else: log_message = "Note added '%s'" % new_note - c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=login) + c = DocEvent( + type="added_comment", doc=doc, rev=doc.rev, by=login) c.desc = log_message c.save() @@ -843,13 +1035,22 @@ def edit_iesg_note(request, name): form = IESGNoteForm(initial=initial) return render(request, 'doc/draft/edit_iesg_note.html', - dict(doc=doc, - form=form, - )) + dict( + doc=doc, + form=form, )) + class ShepherdWriteupUploadForm(forms.Form): - content = forms.CharField(widget=forms.Textarea, label="Shepherd writeup", help_text="Edit the shepherd writeup.", required=False, strip=False) - txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file.", required=False) + content = forms.CharField( + widget=forms.Textarea, + label="Shepherd writeup", + help_text="Edit the shepherd writeup.", + required=False, + strip=False) + txt = forms.FileField( + label=".txt format", + help_text="Or upload a .txt file.", + required=False) def clean_content(self): return self.cleaned_data["content"].replace("\r", "") @@ -857,18 +1058,21 @@ class ShepherdWriteupUploadForm(forms.Form): def clean_txt(self): return get_cleaned_text_file_content(self.cleaned_data["txt"]) + @login_required def edit_shepherd_writeup(request, name): """Change this document's shepherd writeup""" doc = get_object_or_404(Document, type="draft", name=name) can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - can_edit_shepherd_writeup = ( can_edit_stream_info - or (doc.shepherd and user_is_person(request.user, doc.shepherd.person)) - or has_role(request.user, ["Area Director"])) + can_edit_shepherd_writeup = ( + can_edit_stream_info or + (doc.shepherd and user_is_person(request.user, doc.shepherd.person)) or + has_role(request.user, ["Area Director"])) if not can_edit_shepherd_writeup: - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") login = request.user.person @@ -876,30 +1080,39 @@ def edit_shepherd_writeup(request, name): if "submit_response" in request.POST: form = ShepherdWriteupUploadForm(request.POST, request.FILES) if form.is_valid(): - + from_file = form.cleaned_data['txt'] if from_file: - writeup = from_file + writeup = from_file else: - writeup = form.cleaned_data['content'] - e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login, type="changed_protocol_writeup") + writeup = form.cleaned_data['content'] + e = WriteupDocEvent( + doc=doc, + rev=doc.rev, + by=login, + type="changed_protocol_writeup") - # Add the shepherd writeup to description if the document is in submitted for publication state + # Add the shepherd writeup to description if the document is in submitted for publication state stream_state = doc.get_state("draft-stream-%s" % doc.stream_id) - iesg_state = doc.get_state("draft-iesg") - if (iesg_state or (stream_state and stream_state.slug=='sub-pub')): + iesg_state = doc.get_state("draft-iesg") + if (iesg_state or + (stream_state and stream_state.slug == 'sub-pub')): e.desc = writeup else: e.desc = "Changed document writeup" e.text = writeup e.save() - - return redirect('ietf.doc.views_doc.document_main', name=doc.name) + + return redirect( + 'ietf.doc.views_doc.document_main', name=doc.name) elif "reset_text" in request.POST: - init = { "content": render_to_string("doc/shepherd_writeup.txt",dict(doc=doc))} + init = { + "content": + render_to_string("doc/shepherd_writeup.txt", dict(doc=doc)) + } form = ShepherdWriteupUploadForm(initial=init) # Protect against handcrufted malicious posts @@ -910,33 +1123,39 @@ def edit_shepherd_writeup(request, name): form = None if not form: - init = { "content": ""} + init = {"content": ""} - previous_writeup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") + previous_writeup = doc.latest_event( + WriteupDocEvent, type="changed_protocol_writeup") if previous_writeup: init["content"] = previous_writeup.text else: - init["content"] = render_to_string("doc/shepherd_writeup.txt", - dict(doc=doc), - ) + init["content"] = render_to_string( + "doc/shepherd_writeup.txt", + dict(doc=doc), ) form = ShepherdWriteupUploadForm(initial=init) - return render(request, 'doc/draft/change_shepherd_writeup.html', - {'form': form, - 'doc' : doc, - }) + return render(request, 'doc/draft/change_shepherd_writeup.html', { + 'form': form, + 'doc': doc, + }) + class ShepherdForm(forms.Form): shepherd = SearchableEmailField(required=False, only_users=True) + def edit_shepherd(request, name): """Change the shepherd for a Document""" # TODO - this shouldn't be type="draft" specific + doc = get_object_or_404(Document, type="draft", name=name) + old_shepherd_email = doc.shepherd.address can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) if not can_edit_stream_info: - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") if request.method == 'POST': form = ShepherdForm(request.POST) @@ -947,41 +1166,59 @@ def edit_shepherd(request, name): doc.shepherd = form.cleaned_data['shepherd'] - c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) - c.desc = "Document shepherd changed to "+ (doc.shepherd.person.name if doc.shepherd else "(None)") + c = DocEvent( + type="added_comment", + doc=doc, + rev=doc.rev, + by=request.user.person) + c.desc = "Document shepherd changed to " + ( + doc.shepherd.person.name if doc.shepherd else "(None)") c.save() events.append(c) - - if doc.shepherd and (doc.shepherd.formatted_email() not in doc.notify): + + if doc.shepherd and ( + doc.shepherd.formatted_email() not in doc.notify): addrs = doc.notify if addrs: addrs += ', ' addrs += doc.shepherd.formatted_email() - events.append(make_notify_changed_event(request, doc, request.user.person, addrs, c.time)) + events.append( + make_notify_changed_event( + request, doc, request.user.person, addrs, c.time)) doc.notify = addrs - + + email_edit_shepherd(request, doc, old_shepherd_email) + doc.save_with_history(events) else: - messages.info(request,"The selected shepherd was already assigned - no changes have been made.") + messages.info( + request, + "The selected shepherd was already assigned - no changes have been made." + ) return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - form = ShepherdForm(initial={ "shepherd": doc.shepherd_id }) + form = ShepherdForm(initial={"shepherd": doc.shepherd_id}) return render(request, 'doc/change_shepherd.html', { 'form': form, 'doc': doc, }) + class ChangeShepherdEmailForm(forms.Form): - shepherd = forms.ModelChoiceField(queryset=Email.objects.all(), label="Shepherd email", empty_label=None) + shepherd = forms.ModelChoiceField( + queryset=Email.objects.all(), label="Shepherd email", empty_label=None) def __init__(self, *args, **kwargs): super(ChangeShepherdEmailForm, self).__init__(*args, **kwargs) - self.fields["shepherd"].queryset = self.fields["shepherd"].queryset.filter(person__email=self.initial["shepherd"]).distinct() - + self.fields[ + "shepherd"].queryset = self.fields["shepherd"].queryset.filter( + person__email=self.initial["shepherd"]).distinct() + + def change_shepherd_email(request, name): """Change the shepherd email address for a Document""" doc = get_object_or_404(Document, name=name) @@ -990,11 +1227,13 @@ def change_shepherd_email(request, name): raise Http404 can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - is_shepherd = user_is_person(request.user, doc.shepherd and doc.shepherd.person) + is_shepherd = user_is_person(request.user, doc.shepherd and + doc.shepherd.person) if not can_edit_stream_info and not is_shepherd: - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") - initial = { "shepherd": doc.shepherd_id } + initial = {"shepherd": doc.shepherd_id} if request.method == 'POST': form = ChangeShepherdEmailForm(request.POST, initial=initial) if form.is_valid(): @@ -1002,14 +1241,21 @@ def change_shepherd_email(request, name): doc.shepherd = form.cleaned_data['shepherd'] events = [] - c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) + c = DocEvent( + type="added_comment", + doc=doc, + rev=doc.rev, + by=request.user.person) c.desc = "Document shepherd email changed" c.save() events.append(c) doc.save_with_history(events) else: - messages.info(request,"The selected shepherd address was already assigned - no changes have been made.") + messages.info( + request, + "The selected shepherd address was already assigned - no changes have been made." + ) return redirect('ietf.doc.views_doc.document_main', name=doc.name) @@ -1021,9 +1267,16 @@ def change_shepherd_email(request, name): 'doc': doc, }) + class AdForm(forms.Form): - ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area").order_by('name'), - label="Shepherding AD", empty_label="(None)", required=True) + ad = forms.ModelChoiceField( + Person.objects.filter( + role__name="ad", + role__group__state="active", + role__group__type="area").order_by('name'), + label="Shepherding AD", + empty_label="(None)", + required=True) def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) @@ -1032,7 +1285,11 @@ class AdForm(forms.Form): ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices if ad_pk and ad_pk not in [pk for pk, name in choices]: - self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] + self.fields['ad'].choices = list(choices) + [ + ("", "-------"), + (ad_pk, Person.objects.get(pk=ad_pk).plain_name()) + ] + @role_required("Area Director", "Secretariat") def edit_ad(request, name): @@ -1045,36 +1302,49 @@ def edit_ad(request, name): if form.is_valid(): doc.ad = form.cleaned_data['ad'] - c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) - c.desc = "Shepherding AD changed to "+doc.ad.name + c = DocEvent( + type="added_comment", + doc=doc, + rev=doc.rev, + by=request.user.person) + c.desc = "Shepherding AD changed to " + doc.ad.name c.save() doc.save_with_history([c]) - + return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - init = { "ad" : doc.ad_id } + init = {"ad": doc.ad_id} form = AdForm(initial=init) - return render(request, 'doc/draft/change_ad.html', - {'form': form, - 'doc': doc, - }, - ) + return render( + request, + 'doc/draft/change_ad.html', + { + 'form': form, + 'doc': doc, + }, ) + class ConsensusForm(forms.Form): - consensus = forms.ChoiceField(choices=(("Unknown", "Unknown"), ("Yes", "Yes"), ("No", "No")), - required=True, label="When published as an RFC, should the consensus boilerplate be included?") + consensus = forms.ChoiceField( + choices=(("Unknown", "Unknown"), ("Yes", "Yes"), ("No", "No")), + required=True, + label= + "When published as an RFC, should the consensus boilerplate be included?" + ) + def edit_consensus(request, name): """When this draft is published as an RFC, should it include the consensus boilerplate or not.""" doc = get_object_or_404(Document, type="draft", name=name) - if not (has_role(request.user, ("Secretariat", "Area Director")) - or is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) or + is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") e = doc.latest_event(ConsensusDocEvent, type="changed_consensus") prev_consensus = e.consensus if e else default_consensus(doc) @@ -1083,48 +1353,69 @@ def edit_consensus(request, name): form = ConsensusForm(request.POST) if form.is_valid(): if form.cleaned_data["consensus"] != prev_consensus: - e = ConsensusDocEvent(doc=doc, rev=doc.rev, type="changed_consensus", by=request.user.person) - e.consensus = {"Unknown":None,"Yes":True,"No":False}[form.cleaned_data["consensus"]] - if not e.consensus and doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): - return HttpResponseForbidden("BCPs and Standards Track documents must include the consensus boilerplate") + e = ConsensusDocEvent( + doc=doc, + rev=doc.rev, + type="changed_consensus", + by=request.user.person) + e.consensus = { + "Unknown": None, + "Yes": True, + "No": False + }[form.cleaned_data["consensus"]] + if not e.consensus and doc.intended_std_level_id in ( + "std", "ds", "ps", "bcp"): + return HttpResponseForbidden( + "BCPs and Standards Track documents must include the consensus boilerplate" + ) - e.desc = "Changed consensus to %s from %s" % (nice_consensus(e.consensus), - nice_consensus(prev_consensus)) + e.desc = "Changed consensus to %s from %s" % ( + nice_consensus(e.consensus), + nice_consensus(prev_consensus)) e.save() return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - form = ConsensusForm(initial=dict(consensus=nice_consensus(prev_consensus))) + form = ConsensusForm(initial=dict( + consensus=nice_consensus(prev_consensus))) + + return render( + request, + 'doc/draft/change_consensus.html', + { + 'form': form, + 'doc': doc, + }, ) - return render(request, 'doc/draft/change_consensus.html', - {'form': form, - 'doc': doc, - }, - ) class PublicationForm(forms.Form): subject = forms.CharField(max_length=200, required=True) body = forms.CharField(widget=forms.Textarea, required=True, strip=False) + def request_publication(request, name): """Request publication by RFC Editor for a document which hasn't been through the IESG ballot process.""" - doc = get_object_or_404(Document, type="draft", name=name, stream__in=("iab", "ise", "irtf")) + doc = get_object_or_404( + Document, type="draft", name=name, stream__in=("iab", "ise", "irtf")) if not is_authorized_in_doc_stream(request.user, doc): - return HttpResponseForbidden("You do not have the necessary permissions to view this page") + return HttpResponseForbidden( + "You do not have the necessary permissions to view this page") - consensus_event = doc.latest_event(ConsensusDocEvent, type="changed_consensus") + consensus_event = doc.latest_event( + ConsensusDocEvent, type="changed_consensus") m = Message() m.frm = request.user.person.formatted_email() - (m.to, m.cc) = gather_address_lists('pubreq_rfced',doc=doc) + (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") + next_state = State.objects.get( + used=True, type="draft-stream-%s" % doc.stream.slug, slug="rfc-edit") if request.method == 'POST' and not request.POST.get("reset"): form = PublicationForm(request.POST) @@ -1134,12 +1425,13 @@ def request_publication(request, name): if not request.POST.get("skiprfceditorpost"): # start by notifying the RFC Editor import ietf.sync.rfceditor - response, error = ietf.sync.rfceditor.post_approved_draft(settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) + response, error = ietf.sync.rfceditor.post_approved_draft( + settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) if error: - return render(request, 'doc/draft/rfceditor_post_approved_draft_failed.html', - dict(name=doc.name, - response=response, - error=error)) + return render( + request, + 'doc/draft/rfceditor_post_approved_draft_failed.html', + dict(name=doc.name, response=response, error=error)) m.subject = form.cleaned_data["subject"] m.body = form.cleaned_data["body"] @@ -1152,10 +1444,14 @@ def request_publication(request, name): send_mail_message(request, m) # IANA copy - (m.to, m.cc) = gather_address_lists('pubreq_rfced_iana',doc=doc) + (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", rev=doc.rev, by=request.user.person) + e = DocEvent( + doc=doc, + type="requested_publication", + rev=doc.rev, + by=request.user.person) e.desc = "Sent request for publication to the RFC Editor" e.save() events.append(e) @@ -1164,7 +1460,8 @@ def request_publication(request, name): prev_state = doc.get_state(next_state.type_id) if next_state != prev_state: doc.set_state(next_state) - e = add_state_change_event(doc, request.user.person, prev_state, next_state) + e = add_state_change_event(doc, request.user.person, + prev_state, next_state) if e: events.append(e) doc.save_with_history(events) @@ -1179,29 +1476,50 @@ def request_publication(request, name): from ietf.doc.templatetags.mail_filters import std_level_prompt - subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, std_level_prompt(doc), doc.name, doc.rev) + subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, + std_level_prompt(doc), + doc.name, doc.rev) body = generate_publication_request(request, doc) - form = PublicationForm(initial=dict(subject=subject, - body=body)) + form = PublicationForm(initial=dict(subject=subject, body=body)) + + return render( + request, + 'doc/draft/request_publication.html', + dict( + form=form, + doc=doc, + message=m, + next_state=next_state, + consensus_filled_in=(True if (doc.stream_id and + doc.stream_id == 'ietf') else + (consensus_event != None and + consensus_event.consensus != None)), ), ) - return render(request, 'doc/draft/request_publication.html', - dict(form=form, - doc=doc, - message=m, - next_state=next_state, - consensus_filled_in=( - True if (doc.stream_id and doc.stream_id=='ietf') - else (consensus_event != None and consensus_event.consensus != None)), - ), - ) class AdoptDraftForm(forms.Form): - group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=["wg", "rg"], state="active").order_by("-type", "acronym"), required=True, empty_label=None) - newstate = forms.ModelChoiceField(queryset=State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'], used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING), required=True, label="State") - comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for the adoption.", strip=False) - weeks = forms.IntegerField(required=False, label="Expected weeks in adoption state") + group = forms.ModelChoiceField( + queryset=Group.objects.filter(type__in=["wg", "rg"], + state="active").order_by( + "-type", "acronym"), + required=True, + empty_label=None) + newstate = forms.ModelChoiceField( + queryset=State.objects.filter( + type__in=['draft-stream-ietf', 'draft-stream-irtf'], + used=True).exclude( + slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING), + required=True, + label="State") + comment = forms.CharField( + widget=forms.Textarea, + required=False, + label="Comment", + help_text="Optional comment explaining the reasons for the adoption.", + strip=False) + weeks = forms.IntegerField( + required=False, label="Expected weeks in adoption state") def __init__(self, *args, **kwargs): user = kwargs.pop("user") @@ -1209,39 +1527,70 @@ class AdoptDraftForm(forms.Form): super(AdoptDraftForm, self).__init__(*args, **kwargs) if has_role(user, "Secretariat"): - state_choices = State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'], used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + state_choices = State.objects.filter( + type__in=['draft-stream-ietf', 'draft-stream-irtf'], + used=True).exclude( + slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) elif has_role(user, "IRTF Chair"): #The IRTF chair can adopt a draft into any RG - group_ids = list(Group.objects.filter(type="rg", state="active").values_list('id', flat=True)) - group_ids.extend(list(Group.objects.filter(type="wg", state="active", role__person__user=user, role__name__in=("chair", "delegate", "secr")).values_list('id', flat=True))) - self.fields["group"].queryset = self.fields["group"].queryset.filter(id__in=group_ids).distinct() - state_choices = State.objects.filter(type='draft-stream-irtf', used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + group_ids = list( + Group.objects.filter(type="rg", state="active").values_list( + 'id', flat=True)) + group_ids.extend( + list( + Group.objects.filter( + type="wg", + state="active", + role__person__user=user, + role__name__in=("chair", "delegate", "secr")) + .values_list('id', flat=True))) + self.fields["group"].queryset = self.fields[ + "group"].queryset.filter(id__in=group_ids).distinct() + state_choices = State.objects.filter( + type='draft-stream-irtf', used=True).exclude( + slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) else: - self.fields["group"].queryset = self.fields["group"].queryset.filter(role__person__user=user, role__name__in=("chair", "delegate", "secr")).distinct() - state_choices = State.objects.filter(type='draft-stream-ietf', used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + self.fields["group"].queryset = self.fields[ + "group"].queryset.filter( + role__person__user=user, + role__name__in=("chair", "delegate", "secr")).distinct() + state_choices = State.objects.filter( + type='draft-stream-ietf', used=True).exclude( + slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) - self.fields['group'].choices = [(g.pk, '%s - %s' % (g.acronym, g.name)) for g in self.fields["group"].queryset] - self.fields['newstate'].choices = [('','-- Pick a state --')] - self.fields['newstate'].choices.extend([(x.pk,x.name + " (IETF)") for x in state_choices if x.type_id == 'draft-stream-ietf']) - self.fields['newstate'].choices.extend([(x.pk,x.name + " (IRTF)") for x in state_choices if x.type_id == 'draft-stream-irtf']) + self.fields['group'].choices = [(g.pk, '%s - %s' % (g.acronym, g.name)) + for g in self.fields["group"].queryset] + self.fields['newstate'].choices = [('', '-- Pick a state --')] + self.fields['newstate'].choices.extend( + [(x.pk, x.name + " (IETF)") for x in state_choices + if x.type_id == 'draft-stream-ietf']) + self.fields['newstate'].choices.extend( + [(x.pk, x.name + " (IRTF)") for x in state_choices + if x.type_id == 'draft-stream-irtf']) def clean_newstate(self): group = self.cleaned_data['group'] newstate = self.cleaned_data['newstate'] - - if (newstate.type_id == 'draft-stream-ietf') and (group.type_id == 'rg'): - raise forms.ValidationError('Cannot assign IETF WG state to IRTF group') - elif (newstate.type_id == 'draft-stream-irtf') and (group.type_id == 'wg'): - raise forms.ValidationError('Cannot assign IRTF RG state to IETF group') + + if (newstate.type_id == 'draft-stream-ietf') and ( + group.type_id == 'rg'): + raise forms.ValidationError( + 'Cannot assign IETF WG state to IRTF group') + elif (newstate.type_id == 'draft-stream-irtf') and ( + group.type_id == 'wg'): + raise forms.ValidationError( + 'Cannot assign IRTF RG state to IETF group') else: return newstate - + + @login_required def adopt_draft(request, name): doc = get_object_or_404(Document, type="draft", name=name) if not can_adopt_draft(request.user, doc): - return HttpResponseForbidden("You don't have permission to access this page") + return HttpResponseForbidden( + "You don't have permission to access this page") if request.method == 'POST': form = AdoptDraftForm(request.POST, user=request.user) @@ -1253,15 +1602,16 @@ def adopt_draft(request, name): group = form.cleaned_data["group"] if group.type.slug == "rg": - new_stream = StreamName.objects.get(slug="irtf") + new_stream = StreamName.objects.get(slug="irtf") else: - new_stream = StreamName.objects.get(slug="ietf") + new_stream = StreamName.objects.get(slug="ietf") new_state = form.cleaned_data["newstate"] # stream if doc.stream != new_stream: - e = DocEvent(type="changed_stream", doc=doc, rev=doc.rev, by=by) + e = DocEvent( + type="changed_stream", doc=doc, rev=doc.rev, by=by) e.desc = u"Changed stream to %s" % new_stream.name if doc.stream: e.desc += u" from %s" % doc.stream.name @@ -1275,15 +1625,18 @@ def adopt_draft(request, name): # group if group != doc.group: e = DocEvent(type="changed_group", doc=doc, rev=doc.rev, by=by) - e.desc = u"Changed group to %s (%s)" % (group.name, group.acronym.upper()) + e.desc = u"Changed group to %s (%s)" % ( + group.name, group.acronym.upper()) if doc.group.type_id != "individ": - e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym.upper()) + e.desc += " from %s (%s)" % (doc.group.name, + doc.group.acronym.upper()) e.save() events.append(e) doc.group = group - new_notify = get_initial_notify(doc,extra=doc.notify) - events.append(make_notify_changed_event(request, doc, by, new_notify)) + new_notify = get_initial_notify(doc, extra=doc.notify) + events.append( + make_notify_changed_event(request, doc, by, new_notify)) doc.notify = new_notify comment = form.cleaned_data["comment"].strip() @@ -1297,7 +1650,8 @@ def adopt_draft(request, name): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) + due_date = datetime.date.today() + datetime.timedelta( + weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) @@ -1316,16 +1670,25 @@ def adopt_draft(request, name): else: form = AdoptDraftForm(user=request.user) - return render(request, 'doc/draft/adopt_draft.html', - {'doc': doc, - 'form': form, - }) + return render(request, 'doc/draft/adopt_draft.html', { + 'doc': doc, + 'form': form, + }) + class ChangeStreamStateForm(forms.Form): - new_state = forms.ModelChoiceField(queryset=State.objects.filter(used=True), label='State' ) - weeks = forms.IntegerField(label='Expected weeks in state',required=False) - comment = forms.CharField(widget=forms.Textarea, required=False, help_text="Optional comment for the document history.", strip=False) - tags = forms.ModelMultipleChoiceField(queryset=DocTagName.objects.filter(used=True), widget=forms.CheckboxSelectMultiple, required=False) + new_state = forms.ModelChoiceField( + queryset=State.objects.filter(used=True), label='State') + weeks = forms.IntegerField(label='Expected weeks in state', required=False) + comment = forms.CharField( + widget=forms.Textarea, + required=False, + help_text="Optional comment for the document history.", + strip=False) + tags = forms.ModelMultipleChoiceField( + queryset=DocTagName.objects.filter(used=True), + widget=forms.CheckboxSelectMultiple, + required=False) def __init__(self, *args, **kwargs): doc = kwargs.pop("doc") @@ -1337,7 +1700,8 @@ class ChangeStreamStateForm(forms.Form): f = self.fields["new_state"] f.queryset = f.queryset.filter(type=state_type) if doc.group: - unused_states = doc.group.unused_states.values_list("pk", flat=True) + unused_states = doc.group.unused_states.values_list( + "pk", flat=True) f.queryset = f.queryset.exclude(pk__in=unused_states) f.label = state_type.label if self.stream.slug == 'ietf': @@ -1347,17 +1711,20 @@ class ChangeStreamStateForm(forms.Form): f.help_text = u"You may not set the 'Submitted to IESG for Publication' using this form - Use the document's main page to request publication." f = self.fields['tags'] - f.queryset = f.queryset.filter(slug__in=get_tags_for_stream_id(doc.stream_id)) + f.queryset = f.queryset.filter( + slug__in=get_tags_for_stream_id(doc.stream_id)) if doc.group: unused_tags = doc.group.unused_tags.values_list("pk", flat=True) f.queryset = f.queryset.exclude(pk__in=unused_tags) def clean_new_state(self): new_state = self.cleaned_data.get('new_state') - if new_state.slug=='sub-pub' and not self.can_set_sub_pub: - raise forms.ValidationError('You may not set the %s state using this form. Use the "Submit to IESG for publication" button on the document\'s main page instead. If that button does not appear, the document may already have IESG state. Ask your Area Director or the Secretariat for help.'%new_state.name) + if new_state.slug == 'sub-pub' and not self.can_set_sub_pub: + raise forms.ValidationError( + 'You may not set the %s state using this form. Use the "Submit to IESG for publication" button on the document\'s main page instead. If that button does not appear, the document may already have IESG state. Ask your Area Director or the Secretariat for help.' + % new_state.name) return new_state - + def next_states_for_stream_state(doc, state_type, current_state): # find next states @@ -1366,12 +1733,14 @@ def next_states_for_stream_state(doc, state_type, current_state): next_states = current_state.next_states.all() if doc.stream_id == "ietf" and doc.group: - transitions = doc.group.groupstatetransitions_set.filter(state=current_state) + transitions = doc.group.groupstatetransitions_set.filter( + state=current_state) if transitions: next_states = transitions[0].next_states.all() else: # return the initial state - states = State.objects.filter(used=True, type=state_type).order_by('order') + states = State.objects.filter( + used=True, type=state_type).order_by('order') if states: next_states = states[:1] @@ -1381,6 +1750,7 @@ def next_states_for_stream_state(doc, state_type, current_state): return next_states + @login_required def change_stream_state(request, name, state_type): doc = get_object_or_404(Document, type="draft", name=name) @@ -1390,15 +1760,24 @@ def change_stream_state(request, name, state_type): state_type = get_object_or_404(StateType, slug=state_type) if not is_authorized_in_doc_stream(request.user, doc): - return HttpResponseForbidden("You don't have permission to access this page") + return HttpResponseForbidden( + "You don't have permission to access this page") prev_state = doc.get_state(state_type.slug) next_states = next_states_for_stream_state(doc, state_type, prev_state) - can_set_sub_pub = has_role(request.user,('Secretariat','Area Director')) or (prev_state and prev_state.slug=='sub-pub') + can_set_sub_pub = has_role( + request.user, + ('Secretariat', 'Area Director')) or (prev_state and + prev_state.slug == 'sub-pub') if request.method == 'POST': - form = ChangeStreamStateForm(request.POST, doc=doc, state_type=state_type,can_set_sub_pub=can_set_sub_pub,stream=doc.stream) + form = ChangeStreamStateForm( + request.POST, + doc=doc, + state_type=state_type, + can_set_sub_pub=can_set_sub_pub, + stream=doc.stream) if form.is_valid(): by = request.user.person events = [] @@ -1414,11 +1793,13 @@ def change_stream_state(request, name, state_type): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) + due_date = datetime.date.today() + datetime.timedelta( + weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) - email_stream_state_changed(request, doc, prev_state, new_state, by, comment) + email_stream_state_changed(request, doc, prev_state, new_state, + by, comment) # tags existing_tags = set(doc.tags.all()) @@ -1427,19 +1808,25 @@ def change_stream_state(request, name, state_type): if existing_tags != new_tags: doc.tags = new_tags - e = DocEvent(type="changed_document", doc=doc, rev=doc.rev, by=by) + e = DocEvent( + type="changed_document", doc=doc, rev=doc.rev, by=by) added_tags = new_tags - existing_tags removed_tags = existing_tags - new_tags l = [] if added_tags: - l.append(u"Tag%s %s set." % (pluralize(added_tags), ", ".join(t.name for t in added_tags))) + l.append(u"Tag%s %s set." % + (pluralize(added_tags), + ", ".join(t.name for t in added_tags))) if removed_tags: - l.append(u"Tag%s %s cleared." % (pluralize(removed_tags), ", ".join(t.name for t in removed_tags))) + l.append(u"Tag%s %s cleared." % + (pluralize(removed_tags), + ", ".join(t.name for t in removed_tags))) e.desc = " ".join(l) e.save() events.append(e) - email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment) + email_stream_tags_changed(request, doc, added_tags, + removed_tags, by, comment) # comment if comment: @@ -1452,18 +1839,26 @@ def change_stream_state(request, name, state_type): doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) else: - form.add_error(None, "No change in state or tags found, and no comment provided -- nothing to do.") + form.add_error( + None, + "No change in state or tags found, and no comment provided -- nothing to do." + ) else: - form = ChangeStreamStateForm(initial=dict(new_state=prev_state.pk if prev_state else None, tags= doc.tags.all()), - doc=doc, state_type=state_type, can_set_sub_pub = can_set_sub_pub,stream = doc.stream) + form = ChangeStreamStateForm( + initial=dict( + new_state=prev_state.pk if prev_state else None, + tags=doc.tags.all()), + doc=doc, + state_type=state_type, + can_set_sub_pub=can_set_sub_pub, + stream=doc.stream) milestones = doc.groupmilestone_set.all() - - return render(request, "doc/draft/change_stream_state.html", - {"doc": doc, - "form": form, - "milestones": milestones, - "state_type": state_type, - "next_states": next_states, - }) + return render(request, "doc/draft/change_stream_state.html", { + "doc": doc, + "form": form, + "milestones": milestones, + "state_type": state_type, + "next_states": next_states, + }) diff --git a/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py b/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py index 72be0899b..b55d22944 100644 --- a/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py +++ b/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py @@ -3,19 +3,16 @@ from __future__ import unicode_literals from django.db import migrations + def make_recipients(apps): - Recipient=apps.get_model('mailtrigger','Recipient') + Recipient = apps.get_model('mailtrigger', 'Recipient') rc = Recipient.objects.create - rc(slug='iesg', - desc='The IESG', - template='The IESG ') + rc(slug='iesg', desc='The IESG', template='The IESG ') - rc(slug='iab', - desc='The IAB', - template='The IAB ') + rc(slug='iab', desc='The IAB', template='The IAB ') rc(slug='ietf_announce', desc='The IETF Announce list', @@ -35,59 +32,67 @@ def make_recipients(apps): rc(slug='doc_authors', desc="The document's authors", - template='{% if doc.type_id == "draft" %}<{{doc.name}}@ietf.org>{% endif %}') + 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)", + 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)", + 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", + 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", + 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", + 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 %}' ) + 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 %}' ) + 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 ) + template=None) rc(slug='doc_stream_manager', desc="The manager of the document's stream", - template=None ) + template=None) rc(slug='stream_managers', desc="The managers of any related streams", - template=None ) + template=None) rc(slug='conflict_review_stream_manager', - desc="The stream manager of a document being reviewed for IETF stream conflicts", - template=None ) + 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) + 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", @@ -101,9 +106,7 @@ def make_recipients(apps): desc="IANA's draft evaluation address", template='IANA ') - rc(slug='iana', - desc="IANA", - template='') + rc(slug='iana', desc="IANA", template='') rc(slug='group_mail_list', desc="The group's mailing list", @@ -115,7 +118,9 @@ def make_recipients(apps): rc(slug='group_chairs', desc="The group's chairs", - template="{% if group and group.acronym %}<{{group.acronym}}-chairs@ietf.org>{% endif %}") + 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", @@ -150,7 +155,8 @@ def make_recipients(apps): template=None) rc(slug='doc_non_ietf_stream_manager', - desc="The document's stream manager if the document is not in the IETF stream", + 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', @@ -162,7 +168,8 @@ def make_recipients(apps): template=None) rc(slug='group_changed_personnel', - desc="Any personnel who were added or deleted when a group's personnel changes", + desc= + "Any personnel who were added or deleted when a group's personnel changes", template='{{ changed_personnel | join:", " }}') rc(slug='session_requests', @@ -174,8 +181,11 @@ def make_recipients(apps): 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 %}') + 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", @@ -183,16 +193,19 @@ def make_recipients(apps): rc(slug='ipr_submitter', desc="The submitter of an IPR disclosure", - template='{% if ipr.submitter_email %}{{ ipr.submitter_email }}{% endif %}') + 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.", + 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.", + desc= + "The holders of all IPR disclosures updated by disclosure and disclosures updated by those and so on.", template=None) rc(slug='ipr_announce', @@ -204,21 +217,25 @@ def make_recipients(apps): template=None) rc(slug='liaison_to_contacts', - desc="The addresses captured in the To field of the liaison statement form", + 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", + 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", + 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", + 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:", "}}') @@ -247,12 +264,13 @@ def make_recipients(apps): desc="The IETF New Work list", template='') + def make_mailtriggers(apps): - Recipient=apps.get_model('mailtrigger','Recipient') - MailTrigger=apps.get_model('mailtrigger','MailTrigger') + Recipient = apps.get_model('mailtrigger', 'Recipient') + MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') - def mt_factory(slug,desc,to_slugs,cc_slugs=[]): + def mt_factory(slug, desc, to_slugs, cc_slugs=[]): # Try to protect ourselves from typos all_slugs = to_slugs[:] @@ -261,578 +279,769 @@ def make_mailtriggers(apps): try: Recipient.objects.get(slug=recipient_slug) except Recipient.DoesNotExist: - print "****Some rule tried to use",recipient_slug + 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', - ]) + 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 = apps.get_model('mailtrigger', 'Recipient') + MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') Recipient.objects.all().delete() MailTrigger.objects.all().delete() @@ -844,6 +1053,4 @@ class Migration(migrations.Migration): ('mailtrigger', '0001_initial'), ] - operations = [ - migrations.RunPython(forward, reverse) - ] + operations = [migrations.RunPython(forward, reverse)] diff --git a/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py b/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py new file mode 100644 index 000000000..77b8f0cd1 --- /dev/null +++ b/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +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) + m = MailTrigger.objects.create(slug=slug, desc=desc) + m.to = Recipient.objects.filter(slug__in=to_slugs) + + mt_factory( + slug='email_edit_shepherd', + desc="Recipients when a sheperd has changed", + to_slugs=[ + 'doc_group_chairs', + 'doc_notify', + 'doc_group_delegates', + 'doc_shepherd', + ], ) + + +def forward(apps, schema_editor): + # make_recipients(apps) + make_mailtriggers(apps) + + +def reverse(apps, schema_editor): + MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') + + MailTrigger.objects.filter(slug__in=['email_edit_shepherd']).delete() + # recipients are not deleted because they are all created + # from a previous migration + + +class Migration(migrations.Migration): + + dependencies = [ + ('mailtrigger', '0010_auto_20161207_1104'), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] diff --git a/ietf/templates/doc/mail/email_edit_shepherd.txt b/ietf/templates/doc/mail/email_edit_shepherd.txt new file mode 100644 index 000000000..2042d9b4f --- /dev/null +++ b/ietf/templates/doc/mail/email_edit_shepherd.txt @@ -0,0 +1,3 @@ +{% autoescape off %}{{ text }} +Datatracker URL: {{ url }} +{% endautoescape %} From bffc7114ebd6287ffba2721a5b94ada0679c80ad Mon Sep 17 00:00:00 2001 From: Seth Birkholz Date: Wed, 19 Jul 2017 16:48:28 +0000 Subject: [PATCH 2/4] Added Person object to ietf/stats/utils.py in get_meeting_registration_data - Legacy-Id: 13949 --- ietf/stats/utils.py | 57 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/ietf/stats/utils.py b/ietf/stats/utils.py index d700395d6..4e4b19029 100644 --- a/ietf/stats/utils.py +++ b/ietf/stats/utils.py @@ -6,6 +6,11 @@ from django.conf import settings from ietf.stats.models import AffiliationAlias, AffiliationIgnoredEnding, CountryAlias, MeetingRegistration from ietf.name.models import CountryName +from ietf.person.models import Person +from django.contrib.auth.models import User +from unidecode import unidecode + + def compile_affiliation_ending_stripping_regexp(): parts = [] @@ -226,8 +231,12 @@ def get_meeting_registration_data(meeting): else: raise RuntimeError("Could not decode response from registrations API: '%s...'" % (response.content[:64], )) + + # for each user identified in the Registration system + # Create a DataTracker MeetingRegistration object for registration in decoded: - object, created = MeetingRegistration.objects.get_or_create( + person = None + obj, created = MeetingRegistration.objects.get_or_create( meeting_id=meeting.pk, first_name=registration['FirstName'], last_name=registration['LastName'], @@ -235,6 +244,52 @@ def get_meeting_registration_data(meeting): country_code=registration['Country'], email=registration['Email'], ) + + # Add a Person object to MeetingRegistration object + # if valid email is available + if not obj.person and "Email" in registration and '@' in registration["Email"]:\ + # If the person already exists do not try to create a new one + persons = Person.objects.filter(user__username=registration["Email"]) + if len(persons) > 0: + person = persons[0] + # Create a new Person object + else: + # ascii_name - convert from unicode if necessary + regname = "%s %s" % (registration["FirstName"], registration["LastName"]) + # if there are any unicode characters decode the string to ascii + if not all(ord(c) < 128 for c in regname): + ascii_name = unidecode(regname).strip() + # it is already ascii, no need to convert + else: + ascii_name = regname + + # Create a new user object if it does not exist already + # if the user already exists do not try ot create a new one + users = User.objects.filter(username=registration["Email"]) + if len(users) > 0: + user = users[0] + else: + # Create a new user. + user = User.objects.create( + first_name=registration["FirstName"], + last_name=registration["LastName"], + username=registration["Email"], + email=registration["Email"] + ) + + # Create the new Person object. + person = Person.objects.create( + name=registration["Email"], + ascii=ascii_name, + affiliation=registration["Company"], + user=user + ) + person.save() + + # update the person object to an actual value + obj.person = person + obj.save() + if created: num_created += 1 num_processed += 1 From 95d3ac3b3c685442485e3f6f07c0f83d4c9bd70a Mon Sep 17 00:00:00 2001 From: Seth Birkholz Date: Fri, 21 Jul 2017 07:26:58 +0000 Subject: [PATCH 3/4] Branch ready to merge. added Person object to a MeetingRegistration object in ietf/stats/utils.py method get_meeting_registration_data - Legacy-Id: 13954 --- ietf/doc/mails.py | 757 ++++----- ietf/doc/views_draft.py | 1157 +++++--------- .../migrations/0002_auto_20150809_1314.py | 1417 +++++++---------- .../migrations/0011_edit_shepherd_email.py | 51 - .../doc/mail/email_edit_shepherd.txt | 3 - 5 files changed, 1278 insertions(+), 2107 deletions(-) delete mode 100644 ietf/mailtrigger/migrations/0011_edit_shepherd_email.py delete mode 100644 ietf/templates/doc/mail/email_edit_shepherd.txt diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index e34e769cb..b3cd75f1d 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -1,4 +1,4 @@ -# generation of mails +# generation of mails import os import textwrap, datetime @@ -16,40 +16,30 @@ 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, mailtrigger_id=None): - (to, cc) = gather_address_lists( - mailtrigger_id or 'doc_state_edited', doc=doc) + (to,cc) = gather_address_lists(mailtrigger_id or 'doc_state_edited',doc=doc) if not to: return - + text = strip_tags(text) - send_mail( - request, - to, - None, - "Datatracker 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()), - cc=cc) - - + send_mail(request, to, None, + "Datatracker 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()), + cc=cc) + def email_ad_approved_doc(request, doc, text): - to = "iesg@iesg.org" - bcc = "iesg-secretary@ietf.org" - frm = request.user.person.formatted_email() - send_mail( - request, - to, - frm, - "Approved: %s" % doc.filename_with_rev(), - "doc/mail/ad_approval_email.txt", - dict(text=text, docname=doc.filename_with_rev()), - bcc=bcc) - - + to = "iesg@iesg.org" + bcc = "iesg-secretary@ietf.org" + frm = request.user.person.formatted_email() + send_mail(request, to, frm, + "Approved: %s" % doc.filename_with_rev(), + "doc/mail/ad_approval_email.txt", + dict(text=text, + docname=doc.filename_with_rev()), + bcc=bcc) + def email_stream_changed(request, doc, old_stream, new_stream, text=""): """Email the change text to the notify group and to the stream chairs""" streams = [] @@ -57,109 +47,61 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""): streams.append(old_stream.slug) if new_stream: streams.append(new_stream.slug) - (to, cc) = gather_address_lists( - 'doc_stream_changed', doc=doc, streams=streams) + (to,cc) = gather_address_lists('doc_stream_changed',doc=doc,streams=streams) if not to: return - + if not text: - text = u"Stream changed to %s from %s" % (new_stream, - old_stream) + text = u"Stream changed to %s from %s" % (new_stream, old_stream) text = strip_tags(text) - send_mail( - request, - to, - None, - "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()), - cc=cc) - - -def email_edit_shepherd(request, doc, old_shepherd_email, text=""): - """Email the change text to the notify group and to the stream chairs""" - (to, cc) = gather_address_lists('email_edit_shepherd', doc=doc) - to.append(old_shepherd_email) - - if not to: - return - - if not text: - text = u"mailing sheperds" - - text = strip_tags(text) - - send_mail( - request, - to, - None, - "Shepherd Edited: %s" % doc.file_tag(), - "doc/mail/email_edit_shepherd.txt", - dict( - text=text, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - cc=cc) - + send_mail(request, to, None, + "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()), + cc=cc) def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state): - extra = extra_automation_headers(doc) - addrs = gather_address_lists('doc_pulled_from_rfc_queue', doc=doc) + extra=extra_automation_headers(doc) + 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, - prev_state=prev_state, - next_state=next_state, - comment=comment, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - extra=extra) - + 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, + prev_state=prev_state, + next_state=next_state, + comment=comment, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + extra=extra) 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) - + 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("
", "\n")) - - + return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace("
", "\n")) + def email_update_telechat(request, doc, text): - (to, cc) = gather_address_lists('doc_telechat_details_changed', doc=doc) + (to, cc) = gather_address_lists('doc_telechat_details_changed',doc=doc) if not to: return - + text = strip_tags(text) - 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()), - cc=cc) + 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()), + cc=cc) def generate_ballot_writeup(request, doc): @@ -172,13 +114,11 @@ def generate_ballot_writeup(request, doc): e.doc = doc e.rev = doc.rev e.desc = u"Ballot writeup was generated" - e.text = unicode( - render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana})) + e.text = unicode(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana})) # caller is responsible for saving, if necessary return e - - + def generate_ballot_rfceditornote(request, doc): e = WriteupDocEvent() e.type = "changed_ballot_rfceditornote_text" @@ -188,57 +128,42 @@ def generate_ballot_rfceditornote(request, doc): e.desc = u"RFC Editor Note for ballot was generated" e.text = unicode(render_to_string("doc/mail/ballot_rfceditornote.txt")) e.save() - + return e - def generate_last_call_announcement(request, doc): expiration_date = datetime.date.today() + datetime.timedelta(days=14) if doc.group.type_id in ("individ", "area"): group = "an individual submitter" expiration_date += datetime.timedelta(days=14) else: - group = "the %s %s (%s)" % (doc.group.name, doc.group.type.name, - doc.group.acronym) + group = "the %s %s (%s)" % (doc.group.name, doc.group.type.name, doc.group.acronym) - doc.filled_title = textwrap.fill( - doc.title, width=70, subsequent_indent=" " * 3) - - iprs = iprs_from_docs( - related_docs(DocAlias.objects.get(name=doc.canonical_name()))) + doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) + + iprs = iprs_from_docs(related_docs(DocAlias.objects.get(name=doc.canonical_name()))) if iprs: - ipr_links = [ - urlreverse("ietf.ipr.views.show", kwargs=dict(id=i.id)) - for i in iprs - ] - ipr_links = [ - settings.IDTRACKER_BASE_URL + url - if not url.startswith("http") else url for url in ipr_links - ] + ipr_links = [ urlreverse("ietf.ipr.views.show", kwargs=dict(id=i.id)) for i in iprs] + ipr_links = [ settings.IDTRACKER_BASE_URL+url if not url.startswith("http") else url for url in ipr_links ] else: ipr_links = None - downrefs = [ - rel for rel in doc.relateddocument_set.all() - if rel.is_downref() and not rel.is_approved_downref() - ] + downrefs = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref()] - 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"), - to=addrs.to, - cc=addrs.cc, - group=group, - docs=[doc], - urls=[settings.IDTRACKER_BASE_URL + doc.get_absolute_url()], - ipr_links=ipr_links, - downrefs=downrefs, )) + 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"), + to=addrs.to, + cc=addrs.cc, + group=group, + docs=[ doc ], + urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], + ipr_links=ipr_links, + downrefs=downrefs, + ) + ) e = WriteupDocEvent() e.type = "changed_last_call_text" @@ -250,15 +175,12 @@ def generate_last_call_announcement(request, doc): # caller is responsible for saving, if necessary return e - + DO_NOT_PUBLISH_IESG_STATES = ("nopubadw", "nopubanw") - def generate_approval_mail(request, doc): - if doc.get_state_slug( - "draft-iesg") in DO_NOT_PUBLISH_IESG_STATES or doc.stream_id in ( - 'ise', 'irtf'): + if doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES or doc.stream_id in ('ise','irtf'): mail = generate_approval_mail_rfc_editor(request, doc) else: mail = generate_approval_mail_approved(request, doc) @@ -274,7 +196,6 @@ def generate_approval_mail(request, doc): # caller is responsible for saving, if necessary return e - def generate_approval_mail_approved(request, doc): if doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): @@ -284,77 +205,60 @@ def generate_approval_mail_approved(request, doc): # the second check catches some area working groups (like # Transport Area Working Group) - if doc.group.type_id not in ( - "area", "individ", "ag", - "rg") and not doc.group.name.endswith("Working Group"): + if doc.group.type_id not in ("area", "individ", "ag", "rg") and not doc.group.name.endswith("Working Group"): doc.group.name_with_wg = doc.group.name + " Working Group" else: doc.group.name_with_wg = doc.group.name - doc.filled_title = textwrap.fill( - doc.title, width=70, subsequent_indent=" " * 3) + doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) if doc.group.type_id in ("individ", "area"): made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group." else: made_by = "This document is the product of the %s." % doc.group.name_with_wg - - responsible_directors = set([ - doc.ad, - ]) - if doc.group.type_id not in ("individ", "area"): - responsible_directors.update([ - x.person - for x in Role.objects.filter(group=doc.group.parent, name='ad') - ]) - responsible_directors = [ - x.plain_name() for x in responsible_directors if x - ] - - if len(responsible_directors) > 1: - contacts = "The IESG contact persons are " + ", ".join( - responsible_directors[:-1] - ) + " and " + responsible_directors[-1] + "." + + responsible_directors = set([doc.ad,]) + if doc.group.type_id not in ("individ","area"): + responsible_directors.update([x.person for x in Role.objects.filter(group=doc.group.parent,name='ad')]) + responsible_directors = [x.plain_name() for x in responsible_directors if x] + + if len(responsible_directors)>1: + contacts = "The IESG contact persons are "+", ".join(responsible_directors[:-1])+" and "+responsible_directors[-1]+"." else: contacts = "The IESG contact person is %s." % responsible_directors[0] 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(), - to=addrs.to, - cc=addrs.cc, - doc_type=doc_type, - made_by=made_by, - contacts=contacts, - action_type=action_type, )) - + + 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(), + to = addrs.to, + cc = addrs.cc, + doc_type=doc_type, + made_by=made_by, + contacts=contacts, + action_type=action_type, + ) + ) 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 + 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" - 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=addrs.to, - cc=addrs.cc, )) + 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 = addrs.to, + cc = addrs.cc, + ) + ) def generate_publication_request(request, doc): group_description = "" @@ -374,36 +278,31 @@ def generate_publication_request(request, doc): e = doc.latest_event(WriteupDocEvent, type="changed_rfc_editor_note_text") rfcednote = e.text if e else "" - return render_to_string( - "doc/mail/publication_request.txt", - dict( - doc=doc, - doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - group_description=group_description, - approving_body=approving_body, - consensus_body=consensus_body, - consensus=consensus, - rfc_editor_note=rfcednote, )) - + return render_to_string("doc/mail/publication_request.txt", + dict(doc=doc, + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + group_description=group_description, + approving_body=approving_body, + consensus_body=consensus_body, + consensus=consensus, + rfc_editor_note=rfcednote, + ) + ) def send_last_call_request(request, doc): - (to, cc) = gather_address_lists('last_call_requested', doc=doc) + (to, cc) = gather_address_lists('last_call_requested',doc=doc) frm = '"DraftTracker Mail System" ' - - 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(), ), - cc=cc) - + + 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(), + ), + cc=cc) def email_resurrect_requested(request, doc, by): - (to, cc) = gather_address_lists('resurrection_requested', doc=doc) + (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") @@ -411,75 +310,64 @@ def email_resurrect_requested(request, doc, by): e = by.role_email("ad") frm = e.address - send_mail( - request, - to, - e.formatted_email(), - "I-D Resurrection Request", - "doc/mail/resurrect_request_email.txt", - dict( - doc=doc, - by=frm, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - cc=cc) - + send_mail(request, to, e.formatted_email(), + "I-D Resurrection Request", + "doc/mail/resurrect_request_email.txt", + dict(doc=doc, + by=frm, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_resurrection_completed(request, doc, requester): - (to, cc) = gather_address_lists('resurrection_completed', doc=doc) + (to, cc) = gather_address_lists('resurrection_completed',doc=doc) frm = "I-D Administrator " - 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()), - cc=cc) - + 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()), + cc=cc) def email_ballot_deferred(request, doc, by, telechat_date): - (to, cc) = gather_address_lists('ballot_deferred', doc=doc) + (to, cc) = gather_address_lists('ballot_deferred',doc=doc) frm = "DraftTracker Mail System " - send_mail( - request, - to, - frm, - "IESG Deferred Ballot notification: %s" % doc.file_tag(), - "doc/mail/ballot_deferred_email.txt", - dict(doc=doc, by=by, action='deferred', telechat_date=telechat_date), - cc=cc) - + send_mail(request, to, frm, + "IESG Deferred Ballot notification: %s" % doc.file_tag(), + "doc/mail/ballot_deferred_email.txt", + dict(doc=doc, + by=by, + action='deferred', + telechat_date=telechat_date), + cc=cc) def email_ballot_undeferred(request, doc, by, telechat_date): - (to, cc) = gather_address_lists('ballot_deferred', doc=doc) + (to, cc) = gather_address_lists('ballot_deferred',doc=doc) frm = "DraftTracker Mail System " - send_mail( - request, - to, - frm, - "IESG Undeferred Ballot notification: %s" % doc.file_tag(), - "doc/mail/ballot_deferred_email.txt", - dict(doc=doc, by=by, action='undeferred', telechat_date=telechat_date), - cc=cc) - + send_mail(request, to, frm, + "IESG Undeferred Ballot notification: %s" % doc.file_tag(), + "doc/mail/ballot_deferred_email.txt", + dict(doc=doc, + by=by, + action='undeferred', + telechat_date=telechat_date), + cc=cc) def generate_issue_ballot_mail(request, doc, ballot): - + e = doc.latest_event(LastCallDocEvent, type="sent_last_call") last_call_expires = e.expires if e else None - return render_to_string( - "doc/mail/issue_ballot_mail.txt", - dict( - doc=doc, - doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - last_call_expires=last_call_expires, - needed_ballot_positions=needed_ballot_positions( - doc, doc.active_ballot().active_ad_positions().values()), )) - + return render_to_string("doc/mail/issue_ballot_mail.txt", + dict(doc=doc, + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + last_call_expires=last_call_expires, + needed_ballot_positions= + needed_ballot_positions(doc, + doc.active_ballot().active_ad_positions().values() + ), + ) + ) def email_iana(request, doc, to, msg, cc=None): # fix up message and send it with extra info on doc in headers @@ -491,16 +379,12 @@ def email_iana(request, doc, to, msg, cc=None): extra["Reply-To"] = "noreply@ietf.org" extra["X-IETF-Draft-string"] = doc.name extra["X-IETF-Draft-revision"] = doc.rev - - send_mail_text( - request, - to, - parsed_msg["From"], - parsed_msg["Subject"], - parsed_msg.get_payload().decode(str(parsed_msg.get_charset())), - extra=extra, - cc=cc) - + + send_mail_text(request, to, + parsed_msg["From"], parsed_msg["Subject"], + parsed_msg.get_payload().decode(str(parsed_msg.get_charset())), + extra=extra, + cc=cc) def extra_automation_headers(doc): extra = {} @@ -509,200 +393,143 @@ def extra_automation_headers(doc): return extra - def email_last_call_expired(doc): - if not doc.type_id in ['draft', 'statchg']: + if not doc.type_id in ['draft','statchg']: return - text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state( - "draft-iesg" if doc.type_id == 'draft' else "statchg").name - addrs = gather_address_lists('last_call_expired', doc=doc) - - send_mail( - None, - addrs.to, - "DraftTracker Mail System ", - "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=addrs.cc) - + text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state("draft-iesg" if doc.type_id == 'draft' else "statchg").name + addrs = gather_address_lists('last_call_expired',doc=doc) + + send_mail(None, + addrs.to, + "DraftTracker Mail System ", + "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 = addrs.cc) def email_intended_status_changed(request, doc, text): - (to, cc) = gather_address_lists('doc_intended_status_changed', doc=doc) + (to,cc) = gather_address_lists('doc_intended_status_changed',doc=doc) 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) - + 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) def email_comment(request, doc, comment): - (to, cc) = gather_address_lists('doc_added_comment', doc=doc) + (to, cc) = gather_address_lists('doc_added_comment',doc=doc) - 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) + 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_adopted(request, doc, prev_state, new_state, by, comment=""): - (to, cc) = gather_address_lists('doc_adopted_by_group', doc=doc) + (to, cc) = gather_address_lists('doc_adopted_by_group',doc=doc) state_type = (prev_state or new_state).type - send_mail( - request, - to, - settings.DEFAULT_FROM_EMAIL, - u'The %s %s has placed %s in state "%s"' % - (doc.group.acronym.upper(), doc.group.type_id.upper(), doc.name, - new_state or "None"), - '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) + send_mail(request, to, settings.DEFAULT_FROM_EMAIL, + u'The %s %s has placed %s in state "%s"' % + (doc.group.acronym.upper(),doc.group.type_id.upper(), doc.name, new_state or "None"), + '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) +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, - 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) + 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, + 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_tags_changed(request, doc, added_tags, removed_tags, by, comment=""): -def email_stream_tags_changed(request, - doc, - added_tags, - removed_tags, - by, - comment=""): - - (to, cc) = gather_address_lists('doc_stream_state_edited', doc=doc) - - 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, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - added=added_tags, - removed=removed_tags, - by=by, - comment=comment), - cc=cc) + (to, cc) = gather_address_lists('doc_stream_state_edited',doc=doc) + 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, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + added=added_tags, + removed=removed_tags, + by=by, + comment=comment), + cc=cc) def send_review_possibly_replaces_request(request, doc, submitter_info): - addrs = gather_address_lists('doc_replacement_suggested', doc=doc) + 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") - ]) + possibly_replaces = Document.objects.filter(name__in=[alias.name for alias in doc.related_that_doc("possibly-replaces")]) for other_doc in possibly_replaces: - (other_to, other_cc) = gather_address_lists( - 'doc_replacement_suggested', doc=other_doc) + (other_to, other_cc) = gather_address_lists('doc_replacement_suggested',doc=other_doc) to.update(other_to) cc.update(other_cc) - 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, - submitter_info=submitter_info, - possibly_replaces=doc.related_that_doc("possibly-replaces"), - review_url=settings.IDTRACKER_BASE_URL + urlreverse( - 'ietf.doc.views_draft.review_possibly_replaces', - kwargs={"name": doc.name})), - cc=list(cc), ) - + 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, + submitter_info=submitter_info, + possibly_replaces=doc.related_that_doc("possibly-replaces"), + review_url=settings.IDTRACKER_BASE_URL + urlreverse('ietf.doc.views_draft.review_possibly_replaces', kwargs={ "name": doc.name })), + cc=list(cc),) 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) + 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 %s Review: %s (%s)' % - (charter.group.type.name, charter.group.name, charter.group.acronym), - 'doc/mail/charter_internal_review.txt', - dict( - charter=charter, - charter_text=charter_text, - review_type="new" - if charter.group.state_id == "proposed" else "recharter", - charter_url=settings.IDTRACKER_BASE_URL + - charter.get_absolute_url(), - chairs=charter.group.role_set.filter(name="chair"), - secr=charter.group.role_set.filter(name="secr"), - ads=charter.group.role_set.filter(name='ad'), - parent_ads=charter.group.parent.role_set.filter(name='ad'), - techadv=charter.group.role_set.filter(name="techadv"), - milestones=charter.group.groupmilestone_set.filter( - state="charter"), ), - cc=addrs.cc, - extra={'Reply-To': "iesg@ietf.org"}, ) + filename, + os.path.join(settings.CHARTER_PATH,filename), + split=False, + markup=False, + ) + send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL, + 'Internal %s Review: %s (%s)'%(charter.group.type.name,charter.group.name,charter.group.acronym), + 'doc/mail/charter_internal_review.txt', + dict(charter=charter, + charter_text=charter_text, + review_type = "new" if charter.group.state_id == "proposed" else "recharter", + charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(), + chairs=charter.group.role_set.filter(name="chair"), + secr=charter.group.role_set.filter(name="secr"), + ads=charter.group.role_set.filter(name='ad'), + parent_ads=charter.group.parent.role_set.filter(name='ad'), + techadv=charter.group.role_set.filter(name="techadv"), + milestones=charter.group.groupmilestone_set.filter(state="charter"), + ), + cc=addrs.cc, + extra={'Reply-To':"iesg@ietf.org"}, + ) diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 2218be4d1..30046b267 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -12,23 +12,19 @@ from django.contrib.auth.decorators import login_required from django.template.defaultfilters import pluralize from django.contrib import messages -import debug # pyflakes:ignore +import debug # pyflakes:ignore -from ietf.doc.models import ( - Document, DocAlias, RelatedDocument, State, StateType, DocEvent, - ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS) -from ietf.doc.mails import ( - email_pulled_from_rfc_queue, email_resurrect_requested, +from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State, + StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS) +from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested, email_resurrection_completed, email_state_changed, email_stream_changed, - email_edit_shepherd, email_stream_state_changed, email_stream_tags_changed, - extra_automation_headers, generate_publication_request, email_adopted, - email_intended_status_changed, email_iesg_processing_document, - email_ad_approved_doc) -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, - set_replaces_for_document, default_consensus) + email_stream_state_changed, email_stream_tags_changed, extra_automation_headers, + generate_publication_request, email_adopted, email_intended_status_changed, + email_iesg_processing_document, email_ad_approved_doc ) +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, + set_replaces_for_document, default_consensus ) from ietf.doc.lastcall import request_last_call from ietf.doc.fields import SearchableDocAliasesField from ietf.group.models import Group, Role @@ -43,52 +39,42 @@ 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) - substate = forms.ModelChoiceField( - DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS), required=False) - comment = forms.CharField( - widget=forms.Textarea, required=False, strip=False) + state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True) + substate = forms.ModelChoiceField(DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS), required=False) + comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) def clean(self): retclean = self.cleaned_data state = self.cleaned_data.get('state', '(None)') - tag = self.cleaned_data.get('substate', '') - comment = self.cleaned_data['comment'].strip() # pyflakes:ignore + tag = self.cleaned_data.get('substate','') + comment = self.cleaned_data['comment'].strip() # pyflakes:ignore doc = get_object_or_404(Document, docalias__name=self.docname) prev = doc.get_state("draft-iesg") - + # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if state == prev and tag == prev_tag: - self._errors['comment'] = ErrorList([ - u'State not changed. Comments entered will be lost with no state change. Please go back and use the Add Comment feature on the history tab to add comments without changing state.' - ]) + self._errors['comment'] = ErrorList([u'State not changed. Comments entered will be lost with no state change. Please go back and use the Add Comment feature on the history tab to add comments without changing state.']) return retclean - -@role_required('Area Director', 'Secretariat') +@role_required('Area Director','Secretariat') def change_state(request, name): """Change IESG state of Internet Draft, notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) - if (not doc.latest_event( - type="started_iesg_process")) or doc.get_state_slug() == "expired": + if (not doc.latest_event(type="started_iesg_process")) or doc.get_state_slug() == "expired": raise Http404 login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) - form.docname = name + form.docname=name if form.is_valid(): new_state = form.cleaned_data['state'] @@ -97,6 +83,7 @@ def change_state(request, name): tag = form.cleaned_data['substate'] comment = form.cleaned_data['comment'].strip() + # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) @@ -109,13 +96,8 @@ def change_state(request, name): events = [] - e = add_state_change_event( - doc, - login, - prev_state, - new_state, - prev_tags=prev_tags, - new_tags=new_tags) + e = add_state_change_event(doc, login, prev_state, new_state, + prev_tags=prev_tags, new_tags=new_tags) events.append(e) @@ -133,69 +115,55 @@ def change_state(request, name): msg = u"\n".join(e.desc for e in events) - email_state_changed(request, doc, msg, 'doc_state_edited') + email_state_changed(request, doc, msg,'doc_state_edited') + + if new_state.slug == "approved" and new_tags == [] and has_role(request.user, "Area Director"): + email_ad_approved_doc(request, doc, comment) - if new_state.slug == "approved" and new_tags == [] and has_role( - request.user, "Area Director"): - email_ad_approved_doc(request, doc, comment) - - if prev_state and prev_state.slug in ( - "ann", - "rfcqueue") and new_state.slug not in ("rfcqueue", - "pub"): - email_pulled_from_rfc_queue(request, doc, comment, - prev_state, new_state) + if prev_state and prev_state.slug in ("ann", "rfcqueue") and new_state.slug not in ("rfcqueue", "pub"): + email_pulled_from_rfc_queue(request, doc, comment, prev_state, new_state) if new_state.slug in ("iesg-eva", "lc"): if not doc.get_state_slug("draft-iana-review"): - doc.set_state( - State.objects.get( - used=True, - type="draft-iana-review", - slug="need-rev")) + doc.set_state(State.objects.get(used=True, type="draft-iana-review", slug="need-rev")) if new_state.slug == "lc-req": request_last_call(request, doc) - return render(request, - 'doc/draft/last_call_requested.html', - dict(doc=doc, url=doc.get_absolute_url())) - + return render(request, 'doc/draft/last_call_requested.html', + dict(doc=doc, + url=doc.get_absolute_url())) + return HttpResponseRedirect(doc.get_absolute_url()) else: state = doc.get_state("draft-iesg") t = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) - form = ChangeStateForm(initial=dict( - state=state.pk if state else None, substate=t[0].pk - if t else None)) - form.docname = name + form = ChangeStateForm(initial=dict(state=state.pk if state else None, + substate=t[0].pk if t else None)) + form.docname=name state = doc.get_state("draft-iesg") next_states = state.next_states.all() if state else None prev_state = None - hists = doc.history_set.exclude( - states=doc.get_state("draft-iesg")).order_by('-time')[:1] + hists = doc.history_set.exclude(states=doc.get_state("draft-iesg")).order_by('-time')[:1] if hists: prev_state = hists[0].get_state("draft-iesg") to_iesg_eval = None if not doc.latest_event(type="sent_ballot_announcement"): if next_states and next_states.filter(slug="iesg-eva"): - to_iesg_eval = State.objects.get( - used=True, type="draft-iesg", slug="iesg-eva") + to_iesg_eval = State.objects.get(used=True, type="draft-iesg", slug="iesg-eva") next_states = next_states.exclude(slug="iesg-eva") return render(request, 'doc/draft/change_state.html', - dict( - form=form, - doc=doc, - state=state, - prev_state=prev_state, - next_states=next_states, - to_iesg_eval=to_iesg_eval)) - + dict(form=form, + doc=doc, + state=state, + prev_state=prev_state, + next_states=next_states, + to_iesg_eval=to_iesg_eval)) class ChangeIanaStateForm(forms.Form): state = forms.ModelChoiceField(State.objects.all(), required=False) @@ -203,12 +171,9 @@ class ChangeIanaStateForm(forms.Form): def __init__(self, state_type, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) - choices = State.objects.filter( - used=True, type=state_type).order_by("order").values_list( - "pk", "name") + choices = State.objects.filter(used=True, type=state_type).order_by("order").values_list("pk", "name") self.fields['state'].choices = [("", "-------")] + list(choices) - @role_required('Secretariat', 'IANA') def change_iana_state(request, name, state_type): """Change IANA review state of Internet Draft. Normally, this is done via @@ -227,47 +192,39 @@ def change_iana_state(request, name, state_type): if new_state != prev_state: doc.set_state(new_state) - events = [ - add_state_change_event(doc, request.user.person, - prev_state, new_state) - ] + events = [add_state_change_event(doc, request.user.person, prev_state, new_state)] doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) else: - form = ChangeIanaStateForm( - state_type, - initial=dict(state=prev_state.pk if prev_state else None)) + form = ChangeIanaStateForm(state_type, initial=dict(state=prev_state.pk if prev_state else None)) return render(request, 'doc/draft/change_iana_state.html', - dict(form=form, doc=doc)) + dict(form=form, + doc=doc)) + class ChangeStreamForm(forms.Form): - stream = forms.ModelChoiceField( - StreamName.objects.exclude(slug="legacy"), required=False) - comment = forms.CharField( - widget=forms.Textarea, required=False, strip=False) - + stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False) + comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) @login_required def change_stream(request, name): """Change the stream of a Document of type 'draft', notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) - if not doc.type_id == 'draft': + if not doc.type_id=='draft': raise Http404 if not (has_role(request.user, ("Area Director", "Secretariat")) or - (request.user.is_authenticated and Role.objects.filter( - name="chair", - group__acronym__in=StreamName.objects.values_list( - "slug", flat=True), - person__user=request.user))): - return HttpResponseForbidden( - "You do not have permission to view this page") + (request.user.is_authenticated and + Role.objects.filter(name="chair", + group__acronym__in=StreamName.objects.values_list("slug", flat=True), + person__user=request.user))): + return HttpResponseForbidden("You do not have permission to view this page") login = request.user.person @@ -290,17 +247,14 @@ def change_stream(request, name): events = [] - e = DocEvent( - doc=doc, rev=doc.rev, by=login, type='changed_document') - e.desc = u"Stream changed to %s from %s" % ( - new_stream, old_stream or "None") + e = DocEvent(doc=doc, rev=doc.rev, by=login, type='changed_document') + e.desc = u"Stream changed to %s from %s"% (new_stream, old_stream or "None") e.save() events.append(e) if comment: - c = DocEvent( - doc=doc, rev=doc.rev, by=login, type="added_comment") + c = DocEvent(doc=doc, rev=doc.rev, by=login, type="added_comment") c.desc = comment c.save() events.append(c) @@ -318,15 +272,13 @@ def change_stream(request, name): form = ChangeStreamForm(initial=dict(stream=stream)) return render(request, 'doc/draft/change_stream.html', - dict( - form=form, - doc=doc, )) - + dict(form=form, + doc=doc, + )) class ReplacesForm(forms.Form): replaces = SearchableDocAliasesField(required=False) - comment = forms.CharField( - widget=forms.Textarea, required=False, strip=False) + comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) def __init__(self, *args, **kwargs): self.doc = kwargs.pop('doc') @@ -337,24 +289,19 @@ class ReplacesForm(forms.Form): for d in self.cleaned_data['replaces']: if d.document == self.doc: raise forms.ValidationError("A draft can't replace itself") - if d.document.type_id == "draft" and d.document.get_state_slug( - ) == "rfc": + if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc": raise forms.ValidationError("A draft can't replace an RFC") return self.cleaned_data['replaces'] - def replaces(request, name): - """Change 'replaces' set of a Document of type 'draft' , notifying parties + """Change 'replaces' set of a Document of type 'draft' , notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, - ("Secretariat", "Area Director", "WG Chair", "RG Chair", - "WG Secretary", "RG Secretary")) or - is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director", "WG Chair", "RG Chair", "WG Secretary", "RG Secretary")) + or is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden("You do not have the necessary permissions to view this page") if request.method == 'POST': form = ReplacesForm(request.POST, doc=doc) @@ -365,14 +312,9 @@ def replaces(request, name): by = request.user.person if new_replaces != old_replaces: - events = set_replaces_for_document( - request, - doc, - new_replaces, - by=by, - email_subject="%s replacement status updated by %s" % - (doc.name, by), - comment=comment) + events = set_replaces_for_document(request, doc, new_replaces, by=by, + email_subject="%s replacement status updated by %s" % (doc.name, by), + comment=comment) doc.save_with_history(events) @@ -380,42 +322,30 @@ def replaces(request, name): else: form = ReplacesForm(doc=doc) return render(request, 'doc/draft/change_replaces.html', - dict( - form=form, - doc=doc, )) - + dict(form=form, + doc=doc, + )) class SuggestedReplacesForm(forms.Form): - replaces = forms.ModelMultipleChoiceField( - queryset=DocAlias.objects.all(), - label="Suggestions", - required=False, - widget=forms.CheckboxSelectMultiple, - help_text="Select only the documents that are replaced by this document" - ) - comment = forms.CharField( - label="Optional comment", - widget=forms.Textarea, - required=False, - strip=False) + replaces = forms.ModelMultipleChoiceField(queryset=DocAlias.objects.all(), + label="Suggestions", required=False, widget=forms.CheckboxSelectMultiple, + help_text="Select only the documents that are replaced by this document") + comment = forms.CharField(label="Optional comment", widget=forms.Textarea, required=False, strip=False) def __init__(self, suggested, *args, **kwargs): super(SuggestedReplacesForm, self).__init__(*args, **kwargs) pks = [d.pk for d in suggested] self.fields["replaces"].initial = pks - self.fields["replaces"].queryset = self.fields[ - "replaces"].queryset.filter(pk__in=pks) + self.fields["replaces"].queryset = self.fields["replaces"].queryset.filter(pk__in=pks) self.fields["replaces"].choices = [(d.pk, d.name) for d in suggested] - def review_possibly_replaces(request, name): doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, ("Secretariat", "Area Director")) or - is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) + or is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden("You do not have the necessary permissions to view this page") suggested = list(doc.related_that_doc("possibly-replaces")) if not suggested: @@ -434,38 +364,17 @@ def review_possibly_replaces(request, name): events = [] # all suggestions reviewed, so get rid of them - events.append( - DocEvent.objects.create( - doc=doc, - rev=doc.rev, - by=by, - type="reviewed_suggested_replaces", - desc="Reviewed suggested replacement relationships: %s" % - ", ".join(d.name for d in suggested))) - RelatedDocument.objects.filter( - source=doc, - target__in=suggested, - relationship__slug='possibly-replaces').delete() + events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, type="reviewed_suggested_replaces", + desc="Reviewed suggested replacement relationships: %s" % ", ".join(d.name for d in suggested))) + RelatedDocument.objects.filter(source=doc, target__in=suggested,relationship__slug='possibly-replaces').delete() if new_replaces != old_replaces: - events.extend( - set_replaces_for_document( - request, - doc, - new_replaces, - by=by, - email_subject="%s replacement status updated by %s" % ( - doc.name, by), - comment=comment)) + events.extend(set_replaces_for_document(request, doc, new_replaces, by=by, + email_subject="%s replacement status updated by %s" % (doc.name, by), + comment=comment)) if comment: - events.append( - DocEvent.objects.create( - doc=doc, - rev=doc.rev, - by=by, - type="added_comment", - desc=comment)) + events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, type="added_comment", desc=comment)) doc.save_with_history(events) @@ -474,32 +383,25 @@ def review_possibly_replaces(request, name): form = SuggestedReplacesForm(suggested) return render(request, 'doc/draft/review_possibly_replaces.html', - dict( - form=form, - doc=doc, )) + dict(form=form, + doc=doc, + )) class ChangeIntentionForm(forms.Form): - intended_std_level = forms.ModelChoiceField( - IntendedStdLevelName.objects.filter(used=True), - empty_label="(None)", - required=True, - label="Intended RFC status") - comment = forms.CharField( - widget=forms.Textarea, required=False, strip=False) - + intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status") + comment = forms.CharField(widget=forms.Textarea, required=False, strip=False) def change_intention(request, name): - """Change the intended publication status of a Document of type 'draft' , notifying parties + """Change the intended publication status of a Document of type 'draft' , notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id != 'draft': raise Http404 - if not (has_role(request.user, ("Secretariat", "Area Director")) or - is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) + or is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden("You do not have the necessary permissions to view this page") login = request.user.person @@ -514,33 +416,24 @@ def change_intention(request, name): doc.intended_std_level = new_level events = [] - e = DocEvent( - doc=doc, rev=doc.rev, by=login, type='changed_document') - e.desc = u"Intended Status changed to %s from %s" % ( - new_level, old_level) + e = DocEvent(doc=doc, rev=doc.rev, by=login, type='changed_document') + e.desc = u"Intended Status changed to %s from %s"% (new_level,old_level) e.save() events.append(e) if comment: - c = DocEvent( - doc=doc, rev=doc.rev, by=login, type="added_comment") + c = DocEvent(doc=doc, rev=doc.rev, by=login, type="added_comment") c.desc = comment c.save() events.append(c) - de = doc.latest_event( - ConsensusDocEvent, type="changed_consensus") + de = doc.latest_event(ConsensusDocEvent, type="changed_consensus") prev_consensus = de and de.consensus - if not prev_consensus and doc.intended_std_level_id in ( - "std", "ds", "ps", "bcp"): - ce = ConsensusDocEvent( - doc=doc, - rev=doc.rev, - by=login, - type="changed_consensus") + if not prev_consensus and doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): + ce = ConsensusDocEvent(doc=doc, rev=doc.rev, by=login, type="changed_consensus") ce.consensus = True - ce.desc = "Changed consensus to %s from %s" % ( - nice_consensus(True), nice_consensus(prev_consensus)) + ce.desc = "Changed consensus to %s from %s" % (nice_consensus(True), + nice_consensus(prev_consensus)) ce.save() events.append(ce) @@ -554,51 +447,21 @@ def change_intention(request, name): else: intended_std_level = doc.intended_std_level - form = ChangeIntentionForm(initial=dict( - intended_std_level=intended_std_level)) + form = ChangeIntentionForm(initial=dict(intended_std_level=intended_std_level)) return render(request, 'doc/draft/change_intended_status.html', - dict( - form=form, - doc=doc, )) - + dict(form=form, + doc=doc, + )) class EditInfoForm(forms.Form): - intended_std_level = forms.ModelChoiceField( - IntendedStdLevelName.objects.filter(used=True), - empty_label="(None)", - required=True, - label="Intended RFC status") - area = forms.ModelChoiceField( - Group.objects.filter(type="area", state="active"), - empty_label="(None - individual submission)", - required=False, - label="Assigned to area") - ad = forms.ModelChoiceField( - Person.objects.filter( - role__name="ad", - role__group__state="active", - role__group__type='area').order_by('name'), - label="Responsible AD", - empty_label="(None)", - required=True) - create_in_state = forms.ModelChoiceField( - State.objects.filter( - used=True, type="draft-iesg", slug__in=("pub-req", "watching")), - empty_label=None, - required=False) - notify = forms.CharField( - max_length=255, - label="Notice emails", - help_text="Separate email addresses with commas.", - required=False) - note = forms.CharField( - widget=forms.Textarea, label="IESG note", required=False, strip=False) - telechat_date = forms.TypedChoiceField( - coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), - empty_value=None, - required=False, - widget=forms.Select(attrs={'onchange': 'make_bold()'})) + intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status") + area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area") + ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True) + create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False) + notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False) + note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False) + telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'})) returning_item = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): @@ -608,58 +471,48 @@ class EditInfoForm(forms.Form): ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices if ad_pk and ad_pk not in [pk for pk, name in choices]: - self.fields['ad'].choices = list(choices) + [ - ("", "-------"), - (ad_pk, Person.objects.get(pk=ad_pk).plain_name()) - ] - + self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] + # telechat choices - dates = [ - d.date for d in TelechatDate.objects.active().order_by('date') - ] + dates = [d.date for d in TelechatDate.objects.active().order_by('date')] init = kwargs['initial']['telechat_date'] if init and init not in dates: dates.insert(0, init) - self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [ - (d, d.strftime("%Y-%m-%d")) for d in dates - ] + self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] # returning item is rendered non-standard - self.standard_fields = [ - x for x in self.visible_fields() - if x.name not in ('returning_item', ) - ] + self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)] def clean_note(self): return self.cleaned_data['note'].replace('\r', '').strip() - -def to_iesg(request, name): - """ Submit an IETF stream document to the IESG for publication """ +def to_iesg(request,name): + """ Submit an IETF stream document to the IESG for publication """ doc = get_object_or_404(Document, docalias__name=name, stream='ietf') - if doc.get_state_slug('draft') == "expired" or doc.get_state_slug( - 'draft-iesg') == 'pub-req': + if doc.get_state_slug('draft') == "expired" or doc.get_state_slug('draft-iesg') == 'pub-req' : raise Http404 if not is_authorized_in_doc_stream(request.user, doc): raise Http404 - - target_state = { - 'iesg': State.objects.get(type='draft-iesg', slug='pub-req'), - 'wg': State.objects.get(type='draft-stream-ietf', slug='sub-pub'), + + target_state={ + 'iesg' : State.objects.get(type='draft-iesg',slug='pub-req'), + 'wg' : State.objects.get(type='draft-stream-ietf',slug='sub-pub'), } - target_map = {'draft-iesg': 'iesg', 'draft-stream-ietf': 'wg'} + target_map={ + 'draft-iesg' : 'iesg', + 'draft-stream-ietf' : 'wg' + } - warn = {} + warn={} if not doc.intended_std_level: warn['intended_std_level'] = True if not doc.shepherd: warn['shepherd'] = True - shepherd_writeup = doc.latest_event( - WriteupDocEvent, type="changed_protocol_writeup") + shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup") if not shepherd_writeup: warn['shepherd_writeup'] = True tags = doc.tags.filter(slug__in=get_tags_for_stream_id(doc.stream_id)) @@ -668,11 +521,11 @@ def to_iesg(request, name): notify = doc.notify if not notify: notify = get_initial_notify(doc) - ad = doc.ad or getattr(doc.group.ad_role(), 'person', None) + ad = doc.ad or getattr(doc.group.ad_role(),'person',None) if request.method == 'POST': - if request.POST.get("confirm", ""): + if request.POST.get("confirm", ""): by = request.user.person @@ -686,35 +539,27 @@ def to_iesg(request, name): e.by = by e.doc = doc e.rev = doc.rev - e.desc = "IESG process started in state %s" % target_state[ - 'iesg'].name + e.desc = "IESG process started in state %s" % target_state['iesg'].name e.save() events.append(e) - for state_type in ['draft-iesg', 'draft-stream-ietf']: - prev_state = doc.get_state(state_type) + for state_type in ['draft-iesg','draft-stream-ietf']: + prev_state=doc.get_state(state_type) new_state = target_state[target_map[state_type]] - if not prev_state == new_state: + if not prev_state==new_state: doc.set_state(new_state) - events.append( - add_state_change_event( - doc=doc, - by=by, - prev_state=prev_state, - new_state=new_state)) + events.append(add_state_change_event(doc=doc,by=by,prev_state=prev_state,new_state=new_state)) - if not doc.ad == ad: + if not doc.ad == ad : doc.ad = ad changes.append("Responsible AD changed to %s" % doc.ad) - if not doc.notify == notify: + if not doc.notify == notify : doc.notify = notify - changes.append("State Change Notice email list changed to %s" % - doc.notify) + changes.append("State Change Notice email list changed to %s" % doc.notify) -# Get the last available writeup - previous_writeup = doc.latest_event( - WriteupDocEvent, type="changed_protocol_writeup") + # Get the last available writeup + previous_writeup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") if previous_writeup != None: changes.append(previous_writeup.text) @@ -727,37 +572,30 @@ def to_iesg(request, name): doc.save_with_history(events) - addrs = gather_address_lists('pubreq_iesg', doc=doc) + addrs= gather_address_lists('pubreq_iesg',doc=doc) extra = {} extra['Cc'] = addrs.as_strings().cc - send_mail( - request=request, - to=addrs.to, - frm=by.formatted_email(), - subject="Publication has been requested for %s-%s" % (doc.name, - doc.rev), - template="doc/submit_to_iesg_email.txt", - context=dict( - doc=doc, - by=by, - url="%s%s" % (settings.IDTRACKER_BASE_URL, - doc.get_absolute_url()), ), - extra=extra) + send_mail(request=request, + to = addrs.to, + frm = by.formatted_email(), + subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev), + template = "doc/submit_to_iesg_email.txt", + context = dict(doc=doc,by=by,url="%s%s"%(settings.IDTRACKER_BASE_URL,doc.get_absolute_url()),), + extra = extra) return HttpResponseRedirect(doc.get_absolute_url()) return render(request, 'doc/submit_to_iesg.html', - dict( - doc=doc, - warn=warn, - target_state=target_state, - ad=ad, - shepherd_writeup=shepherd_writeup, - tags=tags, - notify=notify, )) + dict(doc=doc, + warn=warn, + target_state=target_state, + ad=ad, + shepherd_writeup=shepherd_writeup, + tags=tags, + notify=notify, + )) - -@role_required('Area Director', 'Secretariat') +@role_required('Area Director','Secretariat') def edit_info(request, name): """Edit various Internet Draft attributes, notifying parties as necessary and logging changes as document events.""" @@ -766,9 +604,7 @@ def edit_info(request, name): raise Http404 new_document = False - if not doc.get_state( - "draft-iesg" - ): # FIXME: should probably receive "new document" as argument to view instead of this + if not doc.get_state("draft-iesg"): # FIXME: should probably receive "new document" as argument to view instead of this new_document = True doc.notify = get_initial_notify(doc) @@ -777,9 +613,9 @@ def edit_info(request, name): initial_returning_item = bool(e and e.returning_item) if request.method == 'POST': - form = EditInfoForm( - request.POST, - initial=dict(ad=doc.ad_id, telechat_date=initial_telechat_date)) + form = EditInfoForm(request.POST, + initial=dict(ad=doc.ad_id, + telechat_date=initial_telechat_date)) if form.is_valid(): by = request.user.person @@ -790,10 +626,9 @@ def edit_info(request, name): doc.set_state(r['create_in_state']) # Is setting the WG state here too much of a hidden side-effect? - if r['create_in_state'].slug == 'pub-req': - if doc.stream and doc.stream.slug == 'ietf' and doc.group and doc.group.type_id == 'wg': - submitted_state = State.objects.get( - type='draft-stream-ietf', slug='sub-pub') + if r['create_in_state'].slug=='pub-req': + if doc.stream and doc.stream.slug=='ietf' and doc.group and doc.group.type_id == 'wg': + submitted_state = State.objects.get(type='draft-stream-ietf',slug='sub-pub') doc.set_state(submitted_state) e = DocEvent() e.type = "changed_document" @@ -804,9 +639,7 @@ def edit_info(request, name): e.save() events.append(e) - replaces = Document.objects.filter( - docalias__relateddocument__source=doc, - docalias__relateddocument__relationship="replaces") + replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces") if replaces: # this should perhaps be somewhere else, e.g. the # place where the replace relationship is established? @@ -815,8 +648,7 @@ def edit_info(request, name): e.by = Person.objects.get(name="(System)") e.doc = doc e.rev = doc.rev - e.desc = "Earlier history may be found in the Comment Log for %s" % ( - replaces[0], replaces[0].get_absolute_url()) + e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url()) e.save() events.append(e) @@ -825,8 +657,7 @@ def edit_info(request, name): e.by = by e.doc = doc e.rev = doc.rev - e.desc = "IESG process started in state %s" % doc.get_state( - "draft-iesg").name + e.desc = "IESG process started in state %s" % doc.get_state("draft-iesg").name e.save() events.append(e) @@ -836,9 +667,9 @@ def edit_info(request, name): entry = "%(attr)s changed to %(new)s from %(old)s" if new_document: entry = "%(attr)s changed to %(new)s" - + return entry % dict(attr=attr, new=new, old=old) - + def diff(attr, name): v = getattr(doc, attr) if r[attr] != v: @@ -859,7 +690,7 @@ def edit_info(request, name): changes.append("Note changed to '%s'" % r['note']) else: changes.append("Note added '%s'" % r['note']) - + doc.note = r['note'] if doc.group.type_id in ("individ", "area"): @@ -868,24 +699,17 @@ def edit_info(request, name): if r["area"] != doc.group: if r["area"].type_id == "area": - changes.append( - u"Assigned to %s" % r["area"].name) + changes.append(u"Assigned to %s" % r["area"].name) else: changes.append(u"No longer assigned to any area") doc.group = r["area"] for c in changes: - events.append( - DocEvent.objects.create( - doc=doc, - rev=doc.rev, - by=by, - desc=c, - type="changed_document")) + events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, desc=c, type="changed_document")) # Todo - chase this - e = update_telechat(request, doc, by, r['telechat_date'], - r['returning_item']) + e = update_telechat(request, doc, by, + r['telechat_date'], r['returning_item']) if e: events.append(e) @@ -894,40 +718,33 @@ def edit_info(request, name): if changes: email_iesg_processing_document(request, doc, changes) - + return HttpResponseRedirect(doc.get_absolute_url()) else: - init = dict( - intended_std_level=doc.intended_std_level_id, - area=doc.group_id, - ad=doc.ad_id, - notify=doc.notify, - note=doc.note, - telechat_date=initial_telechat_date, - returning_item=initial_returning_item, ) + init = dict(intended_std_level=doc.intended_std_level_id, + area=doc.group_id, + ad=doc.ad_id, + notify=doc.notify, + note=doc.note, + telechat_date=initial_telechat_date, + returning_item=initial_returning_item, + ) form = EditInfoForm(initial=init) # optionally filter out some fields if not new_document: - form.standard_fields = [ - x for x in form.standard_fields if x.name != "create_in_state" - ] + form.standard_fields = [x for x in form.standard_fields if x.name != "create_in_state"] if doc.group.type_id not in ("individ", "area"): - form.standard_fields = [ - x for x in form.standard_fields if x.name != "area" - ] + form.standard_fields = [x for x in form.standard_fields if x.name != "area"] return render(request, 'doc/draft/edit_info.html', - dict( - doc=doc, - form=form, - user=request.user, - ballot_issued=doc.latest_event( - type="sent_ballot_announcement"))) + dict(doc=doc, + form=form, + user=request.user, + ballot_issued=doc.latest_event(type="sent_ballot_announcement"))) - -@role_required('Area Director', 'Secretariat') +@role_required('Area Director','Secretariat') def request_resurrect(request, name): """Request resurrect of expired Internet Draft.""" doc = get_object_or_404(Document, docalias__name=name) @@ -938,17 +755,17 @@ def request_resurrect(request, name): by = request.user.person email_resurrect_requested(request, doc, by) - + e = DocEvent(doc=doc, rev=doc.rev, by=by) e.type = "requested_resurrect" e.desc = "Resurrection was requested" e.save() - + return HttpResponseRedirect(doc.get_absolute_url()) - + return render(request, 'doc/draft/request_resurrect.html', - dict(doc=doc, back_url=doc.get_absolute_url())) - + dict(doc=doc, + back_url=doc.get_absolute_url())) @role_required('Secretariat') def resurrect(request, name): @@ -958,15 +775,13 @@ def resurrect(request, name): raise Http404 resurrect_requested_by = None - e = doc.latest_event(type__in=('requested_resurrect', - "completed_resurrect")) + e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect")) if e and e.type == 'requested_resurrect': resurrect_requested_by = e.by if request.method == 'POST': if resurrect_requested_by: - email_resurrection_completed( - request, doc, requester=resurrect_requested_by) + email_resurrection_completed(request, doc, requester=resurrect_requested_by) events = [] e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person) @@ -975,31 +790,25 @@ def resurrect(request, name): e.save() events.append(e) - doc.set_state( - State.objects.get(used=True, type="draft", slug="active")) - doc.expires = datetime.datetime.now() + datetime.timedelta( - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) + doc.set_state(State.objects.get(used=True, type="draft", slug="active")) + doc.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) - + return render(request, 'doc/draft/resurrect.html', - dict( - doc=doc, - resurrect_requested_by=resurrect_requested_by, - back_url=doc.get_absolute_url())) - + dict(doc=doc, + resurrect_requested_by=resurrect_requested_by, + back_url=doc.get_absolute_url())) class IESGNoteForm(forms.Form): - note = forms.CharField( - widget=forms.Textarea, label="IESG note", required=False, strip=False) + note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False) def clean_note(self): # not munging the database content to use html line breaks -- # that has caused a lot of pain in the past. return self.cleaned_data['note'].replace('\r', '').strip() - @role_required("Area Director", "Secretariat") def edit_iesg_note(request, name): doc = get_object_or_404(Document, type="draft", name=name) @@ -1022,8 +831,7 @@ def edit_iesg_note(request, name): else: log_message = "Note added '%s'" % new_note - c = DocEvent( - type="added_comment", doc=doc, rev=doc.rev, by=login) + c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=login) c.desc = log_message c.save() @@ -1035,22 +843,13 @@ def edit_iesg_note(request, name): form = IESGNoteForm(initial=initial) return render(request, 'doc/draft/edit_iesg_note.html', - dict( - doc=doc, - form=form, )) - + dict(doc=doc, + form=form, + )) class ShepherdWriteupUploadForm(forms.Form): - content = forms.CharField( - widget=forms.Textarea, - label="Shepherd writeup", - help_text="Edit the shepherd writeup.", - required=False, - strip=False) - txt = forms.FileField( - label=".txt format", - help_text="Or upload a .txt file.", - required=False) + content = forms.CharField(widget=forms.Textarea, label="Shepherd writeup", help_text="Edit the shepherd writeup.", required=False, strip=False) + txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file.", required=False) def clean_content(self): return self.cleaned_data["content"].replace("\r", "") @@ -1058,21 +857,18 @@ class ShepherdWriteupUploadForm(forms.Form): def clean_txt(self): return get_cleaned_text_file_content(self.cleaned_data["txt"]) - @login_required def edit_shepherd_writeup(request, name): """Change this document's shepherd writeup""" doc = get_object_or_404(Document, type="draft", name=name) can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - can_edit_shepherd_writeup = ( - can_edit_stream_info or - (doc.shepherd and user_is_person(request.user, doc.shepherd.person)) or - has_role(request.user, ["Area Director"])) + can_edit_shepherd_writeup = ( can_edit_stream_info + or (doc.shepherd and user_is_person(request.user, doc.shepherd.person)) + or has_role(request.user, ["Area Director"])) if not can_edit_shepherd_writeup: - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + return HttpResponseForbidden("You do not have the necessary permissions to view this page") login = request.user.person @@ -1080,39 +876,30 @@ def edit_shepherd_writeup(request, name): if "submit_response" in request.POST: form = ShepherdWriteupUploadForm(request.POST, request.FILES) if form.is_valid(): - + from_file = form.cleaned_data['txt'] if from_file: - writeup = from_file + writeup = from_file else: - writeup = form.cleaned_data['content'] - e = WriteupDocEvent( - doc=doc, - rev=doc.rev, - by=login, - type="changed_protocol_writeup") + writeup = form.cleaned_data['content'] + e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login, type="changed_protocol_writeup") - # Add the shepherd writeup to description if the document is in submitted for publication state + # Add the shepherd writeup to description if the document is in submitted for publication state stream_state = doc.get_state("draft-stream-%s" % doc.stream_id) - iesg_state = doc.get_state("draft-iesg") - if (iesg_state or - (stream_state and stream_state.slug == 'sub-pub')): + iesg_state = doc.get_state("draft-iesg") + if (iesg_state or (stream_state and stream_state.slug=='sub-pub')): e.desc = writeup else: e.desc = "Changed document writeup" e.text = writeup e.save() - - return redirect( - 'ietf.doc.views_doc.document_main', name=doc.name) + + return redirect('ietf.doc.views_doc.document_main', name=doc.name) elif "reset_text" in request.POST: - init = { - "content": - render_to_string("doc/shepherd_writeup.txt", dict(doc=doc)) - } + init = { "content": render_to_string("doc/shepherd_writeup.txt",dict(doc=doc))} form = ShepherdWriteupUploadForm(initial=init) # Protect against handcrufted malicious posts @@ -1123,39 +910,33 @@ def edit_shepherd_writeup(request, name): form = None if not form: - init = {"content": ""} + init = { "content": ""} - previous_writeup = doc.latest_event( - WriteupDocEvent, type="changed_protocol_writeup") + previous_writeup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") if previous_writeup: init["content"] = previous_writeup.text else: - init["content"] = render_to_string( - "doc/shepherd_writeup.txt", - dict(doc=doc), ) + init["content"] = render_to_string("doc/shepherd_writeup.txt", + dict(doc=doc), + ) form = ShepherdWriteupUploadForm(initial=init) - return render(request, 'doc/draft/change_shepherd_writeup.html', { - 'form': form, - 'doc': doc, - }) - + return render(request, 'doc/draft/change_shepherd_writeup.html', + {'form': form, + 'doc' : doc, + }) class ShepherdForm(forms.Form): shepherd = SearchableEmailField(required=False, only_users=True) - def edit_shepherd(request, name): """Change the shepherd for a Document""" # TODO - this shouldn't be type="draft" specific - doc = get_object_or_404(Document, type="draft", name=name) - old_shepherd_email = doc.shepherd.address can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) if not can_edit_stream_info: - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + return HttpResponseForbidden("You do not have the necessary permissions to view this page") if request.method == 'POST': form = ShepherdForm(request.POST) @@ -1166,59 +947,41 @@ def edit_shepherd(request, name): doc.shepherd = form.cleaned_data['shepherd'] - c = DocEvent( - type="added_comment", - doc=doc, - rev=doc.rev, - by=request.user.person) - c.desc = "Document shepherd changed to " + ( - doc.shepherd.person.name if doc.shepherd else "(None)") + c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) + c.desc = "Document shepherd changed to "+ (doc.shepherd.person.name if doc.shepherd else "(None)") c.save() events.append(c) - - if doc.shepherd and ( - doc.shepherd.formatted_email() not in doc.notify): + + if doc.shepherd and (doc.shepherd.formatted_email() not in doc.notify): addrs = doc.notify if addrs: addrs += ', ' addrs += doc.shepherd.formatted_email() - events.append( - make_notify_changed_event( - request, doc, request.user.person, addrs, c.time)) + events.append(make_notify_changed_event(request, doc, request.user.person, addrs, c.time)) doc.notify = addrs - - email_edit_shepherd(request, doc, old_shepherd_email) - + doc.save_with_history(events) else: - messages.info( - request, - "The selected shepherd was already assigned - no changes have been made." - ) + messages.info(request,"The selected shepherd was already assigned - no changes have been made.") return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - form = ShepherdForm(initial={"shepherd": doc.shepherd_id}) + form = ShepherdForm(initial={ "shepherd": doc.shepherd_id }) return render(request, 'doc/change_shepherd.html', { 'form': form, 'doc': doc, }) - class ChangeShepherdEmailForm(forms.Form): - shepherd = forms.ModelChoiceField( - queryset=Email.objects.all(), label="Shepherd email", empty_label=None) + shepherd = forms.ModelChoiceField(queryset=Email.objects.all(), label="Shepherd email", empty_label=None) def __init__(self, *args, **kwargs): super(ChangeShepherdEmailForm, self).__init__(*args, **kwargs) - self.fields[ - "shepherd"].queryset = self.fields["shepherd"].queryset.filter( - person__email=self.initial["shepherd"]).distinct() - - + self.fields["shepherd"].queryset = self.fields["shepherd"].queryset.filter(person__email=self.initial["shepherd"]).distinct() + def change_shepherd_email(request, name): """Change the shepherd email address for a Document""" doc = get_object_or_404(Document, name=name) @@ -1227,13 +990,11 @@ def change_shepherd_email(request, name): raise Http404 can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - is_shepherd = user_is_person(request.user, doc.shepherd and - doc.shepherd.person) + is_shepherd = user_is_person(request.user, doc.shepherd and doc.shepherd.person) if not can_edit_stream_info and not is_shepherd: - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + return HttpResponseForbidden("You do not have the necessary permissions to view this page") - initial = {"shepherd": doc.shepherd_id} + initial = { "shepherd": doc.shepherd_id } if request.method == 'POST': form = ChangeShepherdEmailForm(request.POST, initial=initial) if form.is_valid(): @@ -1241,21 +1002,14 @@ def change_shepherd_email(request, name): doc.shepherd = form.cleaned_data['shepherd'] events = [] - c = DocEvent( - type="added_comment", - doc=doc, - rev=doc.rev, - by=request.user.person) + c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) c.desc = "Document shepherd email changed" c.save() events.append(c) doc.save_with_history(events) else: - messages.info( - request, - "The selected shepherd address was already assigned - no changes have been made." - ) + messages.info(request,"The selected shepherd address was already assigned - no changes have been made.") return redirect('ietf.doc.views_doc.document_main', name=doc.name) @@ -1267,16 +1021,9 @@ def change_shepherd_email(request, name): 'doc': doc, }) - class AdForm(forms.Form): - ad = forms.ModelChoiceField( - Person.objects.filter( - role__name="ad", - role__group__state="active", - role__group__type="area").order_by('name'), - label="Shepherding AD", - empty_label="(None)", - required=True) + ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area").order_by('name'), + label="Shepherding AD", empty_label="(None)", required=True) def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) @@ -1285,11 +1032,7 @@ class AdForm(forms.Form): ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices if ad_pk and ad_pk not in [pk for pk, name in choices]: - self.fields['ad'].choices = list(choices) + [ - ("", "-------"), - (ad_pk, Person.objects.get(pk=ad_pk).plain_name()) - ] - + self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] @role_required("Area Director", "Secretariat") def edit_ad(request, name): @@ -1302,49 +1045,36 @@ def edit_ad(request, name): if form.is_valid(): doc.ad = form.cleaned_data['ad'] - c = DocEvent( - type="added_comment", - doc=doc, - rev=doc.rev, - by=request.user.person) - c.desc = "Shepherding AD changed to " + doc.ad.name + c = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person) + c.desc = "Shepherding AD changed to "+doc.ad.name c.save() doc.save_with_history([c]) - + return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - init = {"ad": doc.ad_id} + init = { "ad" : doc.ad_id } form = AdForm(initial=init) - return render( - request, - 'doc/draft/change_ad.html', - { - 'form': form, - 'doc': doc, - }, ) - + return render(request, 'doc/draft/change_ad.html', + {'form': form, + 'doc': doc, + }, + ) class ConsensusForm(forms.Form): - consensus = forms.ChoiceField( - choices=(("Unknown", "Unknown"), ("Yes", "Yes"), ("No", "No")), - required=True, - label= - "When published as an RFC, should the consensus boilerplate be included?" - ) - + consensus = forms.ChoiceField(choices=(("Unknown", "Unknown"), ("Yes", "Yes"), ("No", "No")), + required=True, label="When published as an RFC, should the consensus boilerplate be included?") def edit_consensus(request, name): """When this draft is published as an RFC, should it include the consensus boilerplate or not.""" doc = get_object_or_404(Document, type="draft", name=name) - if not (has_role(request.user, ("Secretariat", "Area Director")) or - is_authorized_in_doc_stream(request.user, doc)): - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + if not (has_role(request.user, ("Secretariat", "Area Director")) + or is_authorized_in_doc_stream(request.user, doc)): + return HttpResponseForbidden("You do not have the necessary permissions to view this page") e = doc.latest_event(ConsensusDocEvent, type="changed_consensus") prev_consensus = e.consensus if e else default_consensus(doc) @@ -1353,69 +1083,48 @@ def edit_consensus(request, name): form = ConsensusForm(request.POST) if form.is_valid(): if form.cleaned_data["consensus"] != prev_consensus: - e = ConsensusDocEvent( - doc=doc, - rev=doc.rev, - type="changed_consensus", - by=request.user.person) - e.consensus = { - "Unknown": None, - "Yes": True, - "No": False - }[form.cleaned_data["consensus"]] - if not e.consensus and doc.intended_std_level_id in ( - "std", "ds", "ps", "bcp"): - return HttpResponseForbidden( - "BCPs and Standards Track documents must include the consensus boilerplate" - ) + e = ConsensusDocEvent(doc=doc, rev=doc.rev, type="changed_consensus", by=request.user.person) + e.consensus = {"Unknown":None,"Yes":True,"No":False}[form.cleaned_data["consensus"]] + if not e.consensus and doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): + return HttpResponseForbidden("BCPs and Standards Track documents must include the consensus boilerplate") - e.desc = "Changed consensus to %s from %s" % ( - nice_consensus(e.consensus), - nice_consensus(prev_consensus)) + e.desc = "Changed consensus to %s from %s" % (nice_consensus(e.consensus), + nice_consensus(prev_consensus)) e.save() return redirect('ietf.doc.views_doc.document_main', name=doc.name) else: - form = ConsensusForm(initial=dict( - consensus=nice_consensus(prev_consensus))) - - return render( - request, - 'doc/draft/change_consensus.html', - { - 'form': form, - 'doc': doc, - }, ) + form = ConsensusForm(initial=dict(consensus=nice_consensus(prev_consensus))) + return render(request, 'doc/draft/change_consensus.html', + {'form': form, + 'doc': doc, + }, + ) class PublicationForm(forms.Form): subject = forms.CharField(max_length=200, required=True) body = forms.CharField(widget=forms.Textarea, required=True, strip=False) - def request_publication(request, name): """Request publication by RFC Editor for a document which hasn't been through the IESG ballot process.""" - doc = get_object_or_404( - Document, type="draft", name=name, stream__in=("iab", "ise", "irtf")) + doc = get_object_or_404(Document, type="draft", name=name, stream__in=("iab", "ise", "irtf")) if not is_authorized_in_doc_stream(request.user, doc): - return HttpResponseForbidden( - "You do not have the necessary permissions to view this page") + return HttpResponseForbidden("You do not have the necessary permissions to view this page") - consensus_event = doc.latest_event( - ConsensusDocEvent, type="changed_consensus") + consensus_event = doc.latest_event(ConsensusDocEvent, type="changed_consensus") m = Message() m.frm = request.user.person.formatted_email() - (m.to, m.cc) = gather_address_lists('pubreq_rfced', doc=doc) + (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") + next_state = State.objects.get(used=True, type="draft-stream-%s" % doc.stream.slug, slug="rfc-edit") if request.method == 'POST' and not request.POST.get("reset"): form = PublicationForm(request.POST) @@ -1425,13 +1134,12 @@ def request_publication(request, name): if not request.POST.get("skiprfceditorpost"): # start by notifying the RFC Editor import ietf.sync.rfceditor - response, error = ietf.sync.rfceditor.post_approved_draft( - settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) + response, error = ietf.sync.rfceditor.post_approved_draft(settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) if error: - return render( - request, - 'doc/draft/rfceditor_post_approved_draft_failed.html', - dict(name=doc.name, response=response, error=error)) + return render(request, 'doc/draft/rfceditor_post_approved_draft_failed.html', + dict(name=doc.name, + response=response, + error=error)) m.subject = form.cleaned_data["subject"] m.body = form.cleaned_data["body"] @@ -1444,14 +1152,10 @@ def request_publication(request, name): send_mail_message(request, m) # IANA copy - (m.to, m.cc) = gather_address_lists('pubreq_rfced_iana', doc=doc) + (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", - rev=doc.rev, - by=request.user.person) + e = DocEvent(doc=doc, type="requested_publication", rev=doc.rev, by=request.user.person) e.desc = "Sent request for publication to the RFC Editor" e.save() events.append(e) @@ -1460,8 +1164,7 @@ def request_publication(request, name): prev_state = doc.get_state(next_state.type_id) if next_state != prev_state: doc.set_state(next_state) - e = add_state_change_event(doc, request.user.person, - prev_state, next_state) + e = add_state_change_event(doc, request.user.person, prev_state, next_state) if e: events.append(e) doc.save_with_history(events) @@ -1476,50 +1179,29 @@ def request_publication(request, name): from ietf.doc.templatetags.mail_filters import std_level_prompt - subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, - std_level_prompt(doc), - doc.name, doc.rev) + subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, std_level_prompt(doc), doc.name, doc.rev) body = generate_publication_request(request, doc) - form = PublicationForm(initial=dict(subject=subject, body=body)) - - return render( - request, - 'doc/draft/request_publication.html', - dict( - form=form, - doc=doc, - message=m, - next_state=next_state, - consensus_filled_in=(True if (doc.stream_id and - doc.stream_id == 'ietf') else - (consensus_event != None and - consensus_event.consensus != None)), ), ) + form = PublicationForm(initial=dict(subject=subject, + body=body)) + return render(request, 'doc/draft/request_publication.html', + dict(form=form, + doc=doc, + message=m, + next_state=next_state, + consensus_filled_in=( + True if (doc.stream_id and doc.stream_id=='ietf') + else (consensus_event != None and consensus_event.consensus != None)), + ), + ) class AdoptDraftForm(forms.Form): - group = forms.ModelChoiceField( - queryset=Group.objects.filter(type__in=["wg", "rg"], - state="active").order_by( - "-type", "acronym"), - required=True, - empty_label=None) - newstate = forms.ModelChoiceField( - queryset=State.objects.filter( - type__in=['draft-stream-ietf', 'draft-stream-irtf'], - used=True).exclude( - slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING), - required=True, - label="State") - comment = forms.CharField( - widget=forms.Textarea, - required=False, - label="Comment", - help_text="Optional comment explaining the reasons for the adoption.", - strip=False) - weeks = forms.IntegerField( - required=False, label="Expected weeks in adoption state") + group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=["wg", "rg"], state="active").order_by("-type", "acronym"), required=True, empty_label=None) + newstate = forms.ModelChoiceField(queryset=State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'], used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING), required=True, label="State") + comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for the adoption.", strip=False) + weeks = forms.IntegerField(required=False, label="Expected weeks in adoption state") def __init__(self, *args, **kwargs): user = kwargs.pop("user") @@ -1527,70 +1209,39 @@ class AdoptDraftForm(forms.Form): super(AdoptDraftForm, self).__init__(*args, **kwargs) if has_role(user, "Secretariat"): - state_choices = State.objects.filter( - type__in=['draft-stream-ietf', 'draft-stream-irtf'], - used=True).exclude( - slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + state_choices = State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'], used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) elif has_role(user, "IRTF Chair"): #The IRTF chair can adopt a draft into any RG - group_ids = list( - Group.objects.filter(type="rg", state="active").values_list( - 'id', flat=True)) - group_ids.extend( - list( - Group.objects.filter( - type="wg", - state="active", - role__person__user=user, - role__name__in=("chair", "delegate", "secr")) - .values_list('id', flat=True))) - self.fields["group"].queryset = self.fields[ - "group"].queryset.filter(id__in=group_ids).distinct() - state_choices = State.objects.filter( - type='draft-stream-irtf', used=True).exclude( - slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + group_ids = list(Group.objects.filter(type="rg", state="active").values_list('id', flat=True)) + group_ids.extend(list(Group.objects.filter(type="wg", state="active", role__person__user=user, role__name__in=("chair", "delegate", "secr")).values_list('id', flat=True))) + self.fields["group"].queryset = self.fields["group"].queryset.filter(id__in=group_ids).distinct() + state_choices = State.objects.filter(type='draft-stream-irtf', used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) else: - self.fields["group"].queryset = self.fields[ - "group"].queryset.filter( - role__person__user=user, - role__name__in=("chair", "delegate", "secr")).distinct() - state_choices = State.objects.filter( - type='draft-stream-ietf', used=True).exclude( - slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) + self.fields["group"].queryset = self.fields["group"].queryset.filter(role__person__user=user, role__name__in=("chair", "delegate", "secr")).distinct() + state_choices = State.objects.filter(type='draft-stream-ietf', used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING) - self.fields['group'].choices = [(g.pk, '%s - %s' % (g.acronym, g.name)) - for g in self.fields["group"].queryset] - self.fields['newstate'].choices = [('', '-- Pick a state --')] - self.fields['newstate'].choices.extend( - [(x.pk, x.name + " (IETF)") for x in state_choices - if x.type_id == 'draft-stream-ietf']) - self.fields['newstate'].choices.extend( - [(x.pk, x.name + " (IRTF)") for x in state_choices - if x.type_id == 'draft-stream-irtf']) + self.fields['group'].choices = [(g.pk, '%s - %s' % (g.acronym, g.name)) for g in self.fields["group"].queryset] + self.fields['newstate'].choices = [('','-- Pick a state --')] + self.fields['newstate'].choices.extend([(x.pk,x.name + " (IETF)") for x in state_choices if x.type_id == 'draft-stream-ietf']) + self.fields['newstate'].choices.extend([(x.pk,x.name + " (IRTF)") for x in state_choices if x.type_id == 'draft-stream-irtf']) def clean_newstate(self): group = self.cleaned_data['group'] newstate = self.cleaned_data['newstate'] - - if (newstate.type_id == 'draft-stream-ietf') and ( - group.type_id == 'rg'): - raise forms.ValidationError( - 'Cannot assign IETF WG state to IRTF group') - elif (newstate.type_id == 'draft-stream-irtf') and ( - group.type_id == 'wg'): - raise forms.ValidationError( - 'Cannot assign IRTF RG state to IETF group') + + if (newstate.type_id == 'draft-stream-ietf') and (group.type_id == 'rg'): + raise forms.ValidationError('Cannot assign IETF WG state to IRTF group') + elif (newstate.type_id == 'draft-stream-irtf') and (group.type_id == 'wg'): + raise forms.ValidationError('Cannot assign IRTF RG state to IETF group') else: return newstate - - + @login_required def adopt_draft(request, name): doc = get_object_or_404(Document, type="draft", name=name) if not can_adopt_draft(request.user, doc): - return HttpResponseForbidden( - "You don't have permission to access this page") + return HttpResponseForbidden("You don't have permission to access this page") if request.method == 'POST': form = AdoptDraftForm(request.POST, user=request.user) @@ -1602,16 +1253,15 @@ def adopt_draft(request, name): group = form.cleaned_data["group"] if group.type.slug == "rg": - new_stream = StreamName.objects.get(slug="irtf") + new_stream = StreamName.objects.get(slug="irtf") else: - new_stream = StreamName.objects.get(slug="ietf") + new_stream = StreamName.objects.get(slug="ietf") new_state = form.cleaned_data["newstate"] # stream if doc.stream != new_stream: - e = DocEvent( - type="changed_stream", doc=doc, rev=doc.rev, by=by) + e = DocEvent(type="changed_stream", doc=doc, rev=doc.rev, by=by) e.desc = u"Changed stream to %s" % new_stream.name if doc.stream: e.desc += u" from %s" % doc.stream.name @@ -1625,18 +1275,15 @@ def adopt_draft(request, name): # group if group != doc.group: e = DocEvent(type="changed_group", doc=doc, rev=doc.rev, by=by) - e.desc = u"Changed group to %s (%s)" % ( - group.name, group.acronym.upper()) + e.desc = u"Changed group to %s (%s)" % (group.name, group.acronym.upper()) if doc.group.type_id != "individ": - e.desc += " from %s (%s)" % (doc.group.name, - doc.group.acronym.upper()) + e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym.upper()) e.save() events.append(e) doc.group = group - new_notify = get_initial_notify(doc, extra=doc.notify) - events.append( - make_notify_changed_event(request, doc, by, new_notify)) + new_notify = get_initial_notify(doc,extra=doc.notify) + events.append(make_notify_changed_event(request, doc, by, new_notify)) doc.notify = new_notify comment = form.cleaned_data["comment"].strip() @@ -1650,8 +1297,7 @@ def adopt_draft(request, name): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta( - weeks=form.cleaned_data["weeks"]) + due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) @@ -1670,25 +1316,16 @@ def adopt_draft(request, name): else: form = AdoptDraftForm(user=request.user) - return render(request, 'doc/draft/adopt_draft.html', { - 'doc': doc, - 'form': form, - }) - + return render(request, 'doc/draft/adopt_draft.html', + {'doc': doc, + 'form': form, + }) class ChangeStreamStateForm(forms.Form): - new_state = forms.ModelChoiceField( - queryset=State.objects.filter(used=True), label='State') - weeks = forms.IntegerField(label='Expected weeks in state', required=False) - comment = forms.CharField( - widget=forms.Textarea, - required=False, - help_text="Optional comment for the document history.", - strip=False) - tags = forms.ModelMultipleChoiceField( - queryset=DocTagName.objects.filter(used=True), - widget=forms.CheckboxSelectMultiple, - required=False) + new_state = forms.ModelChoiceField(queryset=State.objects.filter(used=True), label='State' ) + weeks = forms.IntegerField(label='Expected weeks in state',required=False) + comment = forms.CharField(widget=forms.Textarea, required=False, help_text="Optional comment for the document history.", strip=False) + tags = forms.ModelMultipleChoiceField(queryset=DocTagName.objects.filter(used=True), widget=forms.CheckboxSelectMultiple, required=False) def __init__(self, *args, **kwargs): doc = kwargs.pop("doc") @@ -1700,8 +1337,7 @@ class ChangeStreamStateForm(forms.Form): f = self.fields["new_state"] f.queryset = f.queryset.filter(type=state_type) if doc.group: - unused_states = doc.group.unused_states.values_list( - "pk", flat=True) + unused_states = doc.group.unused_states.values_list("pk", flat=True) f.queryset = f.queryset.exclude(pk__in=unused_states) f.label = state_type.label if self.stream.slug == 'ietf': @@ -1711,20 +1347,17 @@ class ChangeStreamStateForm(forms.Form): f.help_text = u"You may not set the 'Submitted to IESG for Publication' using this form - Use the document's main page to request publication." f = self.fields['tags'] - f.queryset = f.queryset.filter( - slug__in=get_tags_for_stream_id(doc.stream_id)) + f.queryset = f.queryset.filter(slug__in=get_tags_for_stream_id(doc.stream_id)) if doc.group: unused_tags = doc.group.unused_tags.values_list("pk", flat=True) f.queryset = f.queryset.exclude(pk__in=unused_tags) def clean_new_state(self): new_state = self.cleaned_data.get('new_state') - if new_state.slug == 'sub-pub' and not self.can_set_sub_pub: - raise forms.ValidationError( - 'You may not set the %s state using this form. Use the "Submit to IESG for publication" button on the document\'s main page instead. If that button does not appear, the document may already have IESG state. Ask your Area Director or the Secretariat for help.' - % new_state.name) + if new_state.slug=='sub-pub' and not self.can_set_sub_pub: + raise forms.ValidationError('You may not set the %s state using this form. Use the "Submit to IESG for publication" button on the document\'s main page instead. If that button does not appear, the document may already have IESG state. Ask your Area Director or the Secretariat for help.'%new_state.name) return new_state - + def next_states_for_stream_state(doc, state_type, current_state): # find next states @@ -1733,14 +1366,12 @@ def next_states_for_stream_state(doc, state_type, current_state): next_states = current_state.next_states.all() if doc.stream_id == "ietf" and doc.group: - transitions = doc.group.groupstatetransitions_set.filter( - state=current_state) + transitions = doc.group.groupstatetransitions_set.filter(state=current_state) if transitions: next_states = transitions[0].next_states.all() else: # return the initial state - states = State.objects.filter( - used=True, type=state_type).order_by('order') + states = State.objects.filter(used=True, type=state_type).order_by('order') if states: next_states = states[:1] @@ -1750,7 +1381,6 @@ def next_states_for_stream_state(doc, state_type, current_state): return next_states - @login_required def change_stream_state(request, name, state_type): doc = get_object_or_404(Document, type="draft", name=name) @@ -1760,24 +1390,15 @@ def change_stream_state(request, name, state_type): state_type = get_object_or_404(StateType, slug=state_type) if not is_authorized_in_doc_stream(request.user, doc): - return HttpResponseForbidden( - "You don't have permission to access this page") + return HttpResponseForbidden("You don't have permission to access this page") prev_state = doc.get_state(state_type.slug) next_states = next_states_for_stream_state(doc, state_type, prev_state) - can_set_sub_pub = has_role( - request.user, - ('Secretariat', 'Area Director')) or (prev_state and - prev_state.slug == 'sub-pub') + can_set_sub_pub = has_role(request.user,('Secretariat','Area Director')) or (prev_state and prev_state.slug=='sub-pub') if request.method == 'POST': - form = ChangeStreamStateForm( - request.POST, - doc=doc, - state_type=state_type, - can_set_sub_pub=can_set_sub_pub, - stream=doc.stream) + form = ChangeStreamStateForm(request.POST, doc=doc, state_type=state_type,can_set_sub_pub=can_set_sub_pub,stream=doc.stream) if form.is_valid(): by = request.user.person events = [] @@ -1793,13 +1414,11 @@ def change_stream_state(request, name, state_type): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta( - weeks=form.cleaned_data["weeks"]) + due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) - email_stream_state_changed(request, doc, prev_state, new_state, - by, comment) + email_stream_state_changed(request, doc, prev_state, new_state, by, comment) # tags existing_tags = set(doc.tags.all()) @@ -1808,25 +1427,19 @@ def change_stream_state(request, name, state_type): if existing_tags != new_tags: doc.tags = new_tags - e = DocEvent( - type="changed_document", doc=doc, rev=doc.rev, by=by) + e = DocEvent(type="changed_document", doc=doc, rev=doc.rev, by=by) added_tags = new_tags - existing_tags removed_tags = existing_tags - new_tags l = [] if added_tags: - l.append(u"Tag%s %s set." % - (pluralize(added_tags), - ", ".join(t.name for t in added_tags))) + l.append(u"Tag%s %s set." % (pluralize(added_tags), ", ".join(t.name for t in added_tags))) if removed_tags: - l.append(u"Tag%s %s cleared." % - (pluralize(removed_tags), - ", ".join(t.name for t in removed_tags))) + l.append(u"Tag%s %s cleared." % (pluralize(removed_tags), ", ".join(t.name for t in removed_tags))) e.desc = " ".join(l) e.save() events.append(e) - email_stream_tags_changed(request, doc, added_tags, - removed_tags, by, comment) + email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment) # comment if comment: @@ -1839,26 +1452,18 @@ def change_stream_state(request, name, state_type): doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) else: - form.add_error( - None, - "No change in state or tags found, and no comment provided -- nothing to do." - ) + form.add_error(None, "No change in state or tags found, and no comment provided -- nothing to do.") else: - form = ChangeStreamStateForm( - initial=dict( - new_state=prev_state.pk if prev_state else None, - tags=doc.tags.all()), - doc=doc, - state_type=state_type, - can_set_sub_pub=can_set_sub_pub, - stream=doc.stream) + form = ChangeStreamStateForm(initial=dict(new_state=prev_state.pk if prev_state else None, tags= doc.tags.all()), + doc=doc, state_type=state_type, can_set_sub_pub = can_set_sub_pub,stream = doc.stream) milestones = doc.groupmilestone_set.all() - return render(request, "doc/draft/change_stream_state.html", { - "doc": doc, - "form": form, - "milestones": milestones, - "state_type": state_type, - "next_states": next_states, - }) + + return render(request, "doc/draft/change_stream_state.html", + {"doc": doc, + "form": form, + "milestones": milestones, + "state_type": state_type, + "next_states": next_states, + }) diff --git a/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py b/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py index b55d22944..72be0899b 100644 --- a/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py +++ b/ietf/mailtrigger/migrations/0002_auto_20150809_1314.py @@ -3,16 +3,19 @@ from __future__ import unicode_literals from django.db import migrations - def make_recipients(apps): - Recipient = apps.get_model('mailtrigger', 'Recipient') + Recipient=apps.get_model('mailtrigger','Recipient') rc = Recipient.objects.create - rc(slug='iesg', desc='The IESG', template='The IESG ') + rc(slug='iesg', + desc='The IESG', + template='The IESG ') - rc(slug='iab', desc='The IAB', template='The IAB ') + rc(slug='iab', + desc='The IAB', + template='The IAB ') rc(slug='ietf_announce', desc='The IETF Announce list', @@ -32,67 +35,59 @@ def make_recipients(apps): rc(slug='doc_authors', desc="The document's authors", - template= - '{% if doc.type_id == "draft" %}<{{doc.name}}@ietf.org>{% endif %}') + 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)", + 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)", + 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", + 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", + 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", + 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 %}') + 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 %}') + 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) + template=None ) rc(slug='doc_stream_manager', desc="The manager of the document's stream", - template=None) + template=None ) rc(slug='stream_managers', desc="The managers of any related streams", - template=None) + template=None ) rc(slug='conflict_review_stream_manager', - desc= - "The stream manager of a document being reviewed for IETF stream conflicts", - template=None) + 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) + 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", @@ -106,7 +101,9 @@ def make_recipients(apps): desc="IANA's draft evaluation address", template='IANA ') - rc(slug='iana', desc="IANA", template='') + rc(slug='iana', + desc="IANA", + template='') rc(slug='group_mail_list', desc="The group's mailing list", @@ -118,9 +115,7 @@ def make_recipients(apps): rc(slug='group_chairs', desc="The group's chairs", - template= - "{% if group and group.acronym %}<{{group.acronym}}-chairs@ietf.org>{% endif %}" - ) + 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", @@ -155,8 +150,7 @@ def make_recipients(apps): template=None) rc(slug='doc_non_ietf_stream_manager', - desc= - "The document's stream manager if the document is not in the IETF stream", + 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', @@ -168,8 +162,7 @@ def make_recipients(apps): template=None) rc(slug='group_changed_personnel', - desc= - "Any personnel who were added or deleted when a group's personnel changes", + desc="Any personnel who were added or deleted when a group's personnel changes", template='{{ changed_personnel | join:", " }}') rc(slug='session_requests', @@ -181,11 +174,8 @@ def make_recipients(apps): 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 %}' - ) + 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", @@ -193,19 +183,16 @@ def make_recipients(apps): rc(slug='ipr_submitter', desc="The submitter of an IPR disclosure", - template= - '{% if ipr.submitter_email %}{{ ipr.submitter_email }}{% endif %}') + 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.", + 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.", + desc="The holders of all IPR disclosures updated by disclosure and disclosures updated by those and so on.", template=None) rc(slug='ipr_announce', @@ -217,25 +204,21 @@ def make_recipients(apps): template=None) rc(slug='liaison_to_contacts', - desc= - "The addresses captured in the To field of the liaison statement form", + 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", + 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", + 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", + 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:", "}}') @@ -264,13 +247,12 @@ def make_recipients(apps): desc="The IETF New Work list", template='') - def make_mailtriggers(apps): - Recipient = apps.get_model('mailtrigger', 'Recipient') - MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') + Recipient=apps.get_model('mailtrigger','Recipient') + MailTrigger=apps.get_model('mailtrigger','MailTrigger') - def mt_factory(slug, desc, to_slugs, cc_slugs=[]): + def mt_factory(slug,desc,to_slugs,cc_slugs=[]): # Try to protect ourselves from typos all_slugs = to_slugs[:] @@ -279,769 +261,578 @@ def make_mailtriggers(apps): try: Recipient.objects.get(slug=recipient_slug) except Recipient.DoesNotExist: - print "****Some rule tried to use", recipient_slug + 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', - ]) - + 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=apps.get_model('mailtrigger','Recipient') + MailTrigger=apps.get_model('mailtrigger','MailTrigger') Recipient.objects.all().delete() MailTrigger.objects.all().delete() @@ -1053,4 +844,6 @@ class Migration(migrations.Migration): ('mailtrigger', '0001_initial'), ] - operations = [migrations.RunPython(forward, reverse)] + operations = [ + migrations.RunPython(forward, reverse) + ] diff --git a/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py b/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py deleted file mode 100644 index 77b8f0cd1..000000000 --- a/ietf/mailtrigger/migrations/0011_edit_shepherd_email.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations - - -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) - m = MailTrigger.objects.create(slug=slug, desc=desc) - m.to = Recipient.objects.filter(slug__in=to_slugs) - - mt_factory( - slug='email_edit_shepherd', - desc="Recipients when a sheperd has changed", - to_slugs=[ - 'doc_group_chairs', - 'doc_notify', - 'doc_group_delegates', - 'doc_shepherd', - ], ) - - -def forward(apps, schema_editor): - # make_recipients(apps) - make_mailtriggers(apps) - - -def reverse(apps, schema_editor): - MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') - - MailTrigger.objects.filter(slug__in=['email_edit_shepherd']).delete() - # recipients are not deleted because they are all created - # from a previous migration - - -class Migration(migrations.Migration): - - dependencies = [ - ('mailtrigger', '0010_auto_20161207_1104'), - ] - - operations = [ - migrations.RunPython(forward, reverse), - ] diff --git a/ietf/templates/doc/mail/email_edit_shepherd.txt b/ietf/templates/doc/mail/email_edit_shepherd.txt deleted file mode 100644 index 2042d9b4f..000000000 --- a/ietf/templates/doc/mail/email_edit_shepherd.txt +++ /dev/null @@ -1,3 +0,0 @@ -{% autoescape off %}{{ text }} -Datatracker URL: {{ url }} -{% endautoescape %} From 6359192cbb49702f4382ebacbd767ee80da4c46a Mon Sep 17 00:00:00 2001 From: Seth Birkholz Date: Tue, 25 Jul 2017 20:20:00 +0000 Subject: [PATCH 4/4] Commit ready for merge. This commit implements fixes suggested by Henrik to the MeetingRegistration class - added tests - reverted 'obj' to 'object' - removed check for valid email - identifing person by Email.person rather than Person.user__username - used a simpler form of unidecode logic. TBD: the previous code was stolen from elsewhere in the code which should also be simplified. - Person.name now uses firstname + lastname rather than email. - Legacy-Id: 13969 --- ietf/stats/tests.py | 20 ++++++++++++++++++++ ietf/stats/utils.py | 29 ++++++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/ietf/stats/tests.py b/ietf/stats/tests.py index ad30b4ef3..29f1c8c35 100644 --- a/ietf/stats/tests.py +++ b/ietf/stats/tests.py @@ -1,3 +1,4 @@ + import datetime from mock import patch @@ -5,6 +6,7 @@ from pyquery import PyQuery from requests import Response from django.urls import reverse as urlreverse +from django.contrib.auth.models import User from ietf.utils.test_data import make_test_data, make_review_data from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent @@ -199,3 +201,21 @@ class StatisticsTests(TestCase): get_meeting_registration_data(meeting) query = MeetingRegistration.objects.filter(first_name='John',last_name='Smith',country_code='US') self.assertTrue(query.count(), 1) + self.assertTrue(isinstance(query[0].person,Person)) + + @patch('requests.get') + def test_get_meeting_registration_data_user_exists(self, mock_get): + response = Response() + response.status_code = 200 + response._content = '[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US","Email":"john.doe@example.us"}]' + user = User.objects.create(username="john.doe@example.us") + user.save() + + mock_get.return_value = response + meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="96") + get_meeting_registration_data(meeting) + query = MeetingRegistration.objects.filter(first_name='John',last_name='Smith',country_code='US') + self.assertTrue(query.count(), 1) + self.assertTrue(isinstance(query[0].person, Person)) + + diff --git a/ietf/stats/utils.py b/ietf/stats/utils.py index 4e4b19029..d2cbeed16 100644 --- a/ietf/stats/utils.py +++ b/ietf/stats/utils.py @@ -6,7 +6,8 @@ from django.conf import settings from ietf.stats.models import AffiliationAlias, AffiliationIgnoredEnding, CountryAlias, MeetingRegistration from ietf.name.models import CountryName -from ietf.person.models import Person +from ietf.person.models import Person, Email +from ietf.utils import log from django.contrib.auth.models import User from unidecode import unidecode @@ -236,7 +237,7 @@ def get_meeting_registration_data(meeting): # Create a DataTracker MeetingRegistration object for registration in decoded: person = None - obj, created = MeetingRegistration.objects.get_or_create( + object, created = MeetingRegistration.objects.get_or_create( meeting_id=meeting.pk, first_name=registration['FirstName'], last_name=registration['LastName'], @@ -247,24 +248,21 @@ def get_meeting_registration_data(meeting): # Add a Person object to MeetingRegistration object # if valid email is available - if not obj.person and "Email" in registration and '@' in registration["Email"]:\ + if not object.person: # If the person already exists do not try to create a new one - persons = Person.objects.filter(user__username=registration["Email"]) - if len(persons) > 0: - person = persons[0] + emails = Email.objects.filter(address=registration["Email"]) + # there can only be on Email object with a unique email address (primary key) + if len(emails) == 1: + person = emails[0].person # Create a new Person object else: # ascii_name - convert from unicode if necessary regname = "%s %s" % (registration["FirstName"], registration["LastName"]) # if there are any unicode characters decode the string to ascii - if not all(ord(c) < 128 for c in regname): - ascii_name = unidecode(regname).strip() - # it is already ascii, no need to convert - else: - ascii_name = regname + ascii_name = unidecode(regname).strip() # Create a new user object if it does not exist already - # if the user already exists do not try ot create a new one + # if the user already exists do not try to create a new one users = User.objects.filter(username=registration["Email"]) if len(users) > 0: user = users[0] @@ -276,10 +274,11 @@ def get_meeting_registration_data(meeting): username=registration["Email"], email=registration["Email"] ) + user.save() # Create the new Person object. person = Person.objects.create( - name=registration["Email"], + name=regname, ascii=ascii_name, affiliation=registration["Company"], user=user @@ -287,8 +286,8 @@ def get_meeting_registration_data(meeting): person.save() # update the person object to an actual value - obj.person = person - obj.save() + object.person = person + object.save() if created: num_created += 1