diff --git a/ietf/bin/send-milestone-reminders b/ietf/bin/send-milestone-reminders deleted file mode 100755 index 2df17f4c4..000000000 --- a/ietf/bin/send-milestone-reminders +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -# This script will send various milestone reminders. It's supposed to -# be run daily, and will then send reminders weekly/monthly as -# appropriate. - -import datetime, os -import syslog - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings") - -syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0) - -import django -django.setup() - -from ietf.group.mails import * - -today = datetime.date.today() - -MONDAY = 1 -FIRST_DAY_OF_MONTH = 1 - -if today.isoweekday() == MONDAY: - # send milestone review reminders - ideally we'd keep track of - # exactly when we sent one last time for a group, but it's a bit - # complicated because people can change the milestones in the mean - # time, so dodge all of this by simply sending once a week only - for g in groups_with_milestones_needing_review(): - mail_sent = email_milestone_review_reminder(g, grace_period=7) - if mail_sent: - syslog.syslog("Sent milestone review reminder for %s %s" % (g.acronym, g.type.name)) - - -early_warning_days = 30 - -# send any milestones due reminders -for g in groups_needing_milestones_due_reminder(early_warning_days): - email_milestones_due(g, early_warning_days) - syslog.syslog("Sent milestones due reminder for %s %s" % (g.acronym, g.type.name)) - -if today.day == FIRST_DAY_OF_MONTH: - # send milestone overdue reminders - once a month - for g in groups_needing_milestones_overdue_reminder(grace_period=30): - email_milestones_overdue(g) - syslog.syslog("Sent milestones overdue reminder for %s %s" % (g.acronym, g.type.name)) diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py index b2a62ff91..4e215abc6 100644 --- a/ietf/doc/expire.py +++ b/ietf/doc/expire.py @@ -7,10 +7,10 @@ from pathlib import Path from ietf.utils.mail import send_mail from ietf.doc.models import Document, DocEvent, State, save_document_in_history, IESG_SUBSTATE_TAGS -from ietf.person.models import Person, Email +from ietf.person.models import Person from ietf.meeting.models import Meeting from ietf.doc.utils import add_state_change_event - +from ietf.mailtrigger.utils import gather_address_lists def expirable_draft(draft): @@ -70,10 +70,7 @@ def send_expire_warning_for_draft(doc): expiration = doc.expires.date() - to = [e.formatted_email() for e in doc.authors.all() if not e.address.startswith("unknown-email")] - cc = None - if doc.group.type_id in ("wg", "rg"): - cc = [e.formatted_email() for e in Email.objects.filter(role__group=doc.group, role__name="chair") if not e.address.startswith("unknown-email")] + (to,cc) = gather_address_lists('doc_expires_soon',doc=doc) s = doc.get_state("draft-iesg") state = s.name if s else "I-D Exists" @@ -91,21 +88,22 @@ def send_expire_warning_for_draft(doc): cc=cc) def send_expire_notice_for_draft(doc): - if not doc.ad or doc.get_state_slug("draft-iesg") == "dead": + if doc.get_state_slug("draft-iesg") == "dead": return s = doc.get_state("draft-iesg") state = s.name if s else "I-D Exists" request = None - to = doc.ad.role_email("ad").formatted_email() + (to,cc) = gather_address_lists('doc_expired',doc=doc) send_mail(request, to, "I-D Expiring System ", u"I-D was expired %s" % doc.file_tag(), "doc/draft/id_expired_email.txt", dict(doc=doc, state=state, - )) + ), + cc=cc) def move_draft_files_to_archive(doc, rev): def move_file(f): diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 6184458d8..ab7d336c6 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -1,5 +1,6 @@ # generation of mails +import os import textwrap, datetime from django.template.loader import render_to_string @@ -9,14 +10,14 @@ from django.core.urlresolvers import reverse as urlreverse from ietf.utils.mail import send_mail, send_mail_text from ietf.ipr.utils import iprs_from_docs, related_docs -from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent, DocTagName -from ietf.doc.utils import needed_ballot_positions -from ietf.person.models import Person -from ietf.group.models import Group, Role +from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent +from ietf.doc.utils import needed_ballot_positions, get_document_content +from ietf.group.models import Role from ietf.doc.models import Document +from ietf.mailtrigger.utils import gather_address_lists -def email_state_changed(request, doc, text): - to = [x.strip() for x in doc.notify.replace(';', ',').split(',')] +def email_state_changed(request, doc, text, mailtrigger_id=None): + (to,cc) = gather_address_lists(mailtrigger_id or 'doc_state_edited',doc=doc) if not to: return @@ -25,17 +26,17 @@ def email_state_changed(request, doc, text): "ID Tracker State Update Notice: %s" % doc.file_tag(), "doc/mail/state_changed_email.txt", dict(text=text, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_stream_changed(request, doc, old_stream, new_stream, text=""): """Email the change text to the notify group and to the stream chairs""" - to = [x.strip() for x in doc.notify.replace(';', ',').split(',')] - - # These use comprehension to deal with conditions when there might be more than one chair listed for a stream + streams = [] if old_stream: - to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=old_stream.slug, name='chair')]) + streams.append(old_stream.slug) if new_stream: - to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=new_stream.slug, name='chair')]) + streams.append(new_stream.slug) + (to,cc) = gather_address_lists('doc_stream_changed',doc=doc,streams=streams) if not to: return @@ -48,12 +49,14 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""): "ID Tracker Stream Change Notice: %s" % doc.file_tag(), "doc/mail/stream_changed_email.txt", dict(text=text, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state): extra=extra_automation_headers(doc) - extra['Cc'] = 'iesg-secretary@ietf.org' - send_mail(request, ["IANA ", "RFC Editor "], None, + addrs = gather_address_lists('doc_pulled_from_rfc_queue',doc=doc) + extra['Cc'] = addrs.as_strings().cc + send_mail(request, addrs.to , None, "%s changed state from %s to %s" % (doc.name, prev_state.name, next_state.name), "doc/mail/pulled_from_rfc_queue_email.txt", dict(doc=doc, @@ -63,31 +66,18 @@ def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state): url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), extra=extra) - -def email_authors(request, doc, subject, text): - to = [x.strip() for x in doc.author_list().split(',')] - if not to: - return - - send_mail_text(request, to, None, subject, text) +def email_iesg_processing_document(request, doc, changes): + addrs = gather_address_lists('doc_iesg_processing_started',doc=doc) + send_mail(request, addrs.to, None, + 'IESG processing details changed for %s' % doc.name, + 'doc/mail/email_iesg_processing.txt', + dict(doc=doc, + changes=changes), + cc=addrs.cc) def html_to_text(html): return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace("
", "\n")) -def email_ad(request, doc, ad, changed_by, text, subject=None): - if not ad or not changed_by or ad == changed_by: - return - - to = ad.role_email("ad").formatted_email() - send_mail(request, to, - "DraftTracker Mail System ", - "%s updated by %s" % (doc.file_tag(), changed_by.plain_name()), - "doc/mail/change_notice.txt", - dict(text=html_to_text(text), - doc=doc, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) - - def generate_ballot_writeup(request, doc): e = doc.latest_event(type="iana_review") iana = e.desc if e else "" @@ -104,14 +94,11 @@ def generate_ballot_writeup(request, doc): def generate_last_call_announcement(request, doc): expiration_date = datetime.date.today() + datetime.timedelta(days=14) - cc = [] if doc.group.type_id in ("individ", "area"): group = "an individual submitter" expiration_date += datetime.timedelta(days=14) else: group = "the %s WG (%s)" % (doc.group.name, doc.group.acronym) - if doc.group.list_email: - cc.append(doc.group.list_email) doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) @@ -122,11 +109,14 @@ def generate_last_call_announcement(request, doc): else: ipr_links = None + + addrs = gather_address_lists('last_call_issued',doc=doc).as_strings() mail = render_to_string("doc/mail/last_call_announcement.txt", dict(doc=doc, doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url() + "ballot/", expiration_date=expiration_date.strftime("%Y-%m-%d"), #.strftime("%B %-d, %Y"), - cc=", ".join("<%s>" % e for e in cc), + to=addrs.to, + cc=addrs.cc, group=group, docs=[ doc ], urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], @@ -170,16 +160,10 @@ def generate_approval_mail_approved(request, doc): else: action_type = "Document" - cc = [] - cc.extend(settings.DOC_APPROVAL_EMAIL_CC) - # the second check catches some area working groups (like # Transport Area Working Group) if doc.group.type_id not in ("area", "individ", "ag") and not doc.group.name.endswith("Working Group"): doc.group.name_with_wg = doc.group.name + " Working Group" - if doc.group.list_email: - cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email)) - cc.append("%s chair <%s-chairs@ietf.org>" % (doc.group.acronym, doc.group.acronym)) else: doc.group.name_with_wg = doc.group.name @@ -202,11 +186,13 @@ def generate_approval_mail_approved(request, doc): doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft" + addrs = gather_address_lists('ballot_approved_ietf_stream',doc=doc).as_strings() return render_to_string("doc/mail/approval_mail.txt", dict(doc=doc, docs=[doc], doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - cc=",\n ".join(cc), + to = addrs.to, + cc = addrs.cc, doc_type=doc_type, made_by=made_by, contacts=contacts, @@ -215,30 +201,19 @@ def generate_approval_mail_approved(request, doc): ) def generate_approval_mail_rfc_editor(request, doc): + # This is essentially dead code - it is only exercised if the IESG ballots on some other stream's document, + # which does not happen now that we have conflict reviews. disapproved = doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft" - - to = [] - if doc.group.type_id != "individ": - for r in doc.group.role_set.filter(name="chair").select_related(): - to.append(r.formatted_email()) - - if doc.stream_id in ("ise", "irtf"): - # include ISE/IRTF chairs - g = Group.objects.get(acronym=doc.stream_id) - for r in g.role_set.filter(name="chair").select_related(): - to.append(r.formatted_email()) - - if doc.stream_id == "irtf": - # include IRSG - to.append('"Internet Research Steering Group" ') + addrs = gather_address_lists('ballot_approved_conflrev', doc=doc).as_strings() return render_to_string("doc/mail/approval_mail_rfc_editor.txt", dict(doc=doc, doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), doc_type=doc_type, disapproved=disapproved, - to=", ".join(to), + to = addrs.to, + cc = addrs.cc, ) ) @@ -268,17 +243,19 @@ def generate_publication_request(request, doc): ) def send_last_call_request(request, doc): - to = "iesg-secretary@ietf.org" + (to, cc) = gather_address_lists('last_call_requested',doc=doc) frm = '"DraftTracker Mail System" ' send_mail(request, to, frm, "Last Call: %s" % doc.file_tag(), "doc/mail/last_call_request.txt", dict(docs=[doc], - doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + ), + cc=cc) def email_resurrect_requested(request, doc, by): - to = "I-D Administrator " + (to, cc) = gather_address_lists('resurrection_requested',doc=doc) if by.role_set.filter(name="secr", group__acronym="secretariat"): e = by.role_email("secr", group="secretariat") @@ -291,25 +268,22 @@ def email_resurrect_requested(request, doc, by): "doc/mail/resurrect_request_email.txt", dict(doc=doc, by=frm, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_resurrection_completed(request, doc, requester): - if requester.role_set.filter(name="secr", group__acronym="secretariat"): - e = requester.role_email("secr", group="secretariat") - else: - e = requester.role_email("ad") - - to = e.formatted_email() + (to, cc) = gather_address_lists('resurrection_completed',doc=doc) frm = "I-D Administrator " send_mail(request, to, frm, "I-D Resurrection Completed - %s" % doc.file_tag(), "doc/mail/resurrect_completed_email.txt", dict(doc=doc, by=frm, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_ballot_deferred(request, doc, by, telechat_date): - to = "iesg@ietf.org" + (to, cc) = gather_address_lists('ballot_deferred',doc=doc) frm = "DraftTracker Mail System " send_mail(request, to, frm, "IESG Deferred Ballot notification: %s" % doc.file_tag(), @@ -317,10 +291,11 @@ def email_ballot_deferred(request, doc, by, telechat_date): dict(doc=doc, by=by, action='deferred', - telechat_date=telechat_date)) + telechat_date=telechat_date), + cc=cc) def email_ballot_undeferred(request, doc, by, telechat_date): - to = "iesg@ietf.org" + (to, cc) = gather_address_lists('ballot_deferred',doc=doc) frm = "DraftTracker Mail System " send_mail(request, to, frm, "IESG Undeferred Ballot notification: %s" % doc.file_tag(), @@ -328,69 +303,18 @@ def email_ballot_undeferred(request, doc, by, telechat_date): dict(doc=doc, by=by, action='undeferred', - telechat_date=telechat_date)) + telechat_date=telechat_date), + cc=cc) def generate_issue_ballot_mail(request, doc, ballot): - active_ads = Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area").distinct() - positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).order_by("-time", '-id').select_related('ad') - - # format positions and setup discusses and comments - ad_feedback = [] - seen = set() - active_ad_positions = [] - inactive_ad_positions = [] - for p in positions: - if p.ad in seen: - continue - - seen.add(p.ad) - - def formatted(val): - if val: - return "[ X ]" - else: - return "[ ]" - - fmt = u"%-21s%-10s%-11s%-9s%-10s" % ( - p.ad.plain_name()[:21], - formatted(p.pos_id == "yes"), - formatted(p.pos_id == "noobj"), - formatted(p.pos_id == "discuss"), - "[ R ]" if p.pos_id == "recuse" else formatted(p.pos_id == "abstain"), - ) - - if p.ad in active_ads: - active_ad_positions.append(fmt) - if not p.pos_id == "discuss": - p.discuss = "" - if p.comment or p.discuss: - ad_feedback.append(p) - else: - inactive_ad_positions.append(fmt) - - active_ad_positions.sort() - inactive_ad_positions.sort() - ad_feedback.sort(key=lambda p: p.ad.plain_name()) - e = doc.latest_event(LastCallDocEvent, type="sent_last_call") last_call_expires = e.expires if e else None - e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") - approval_text = e.text if e else "" - - e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") - ballot_writeup = e.text if e else "" - return render_to_string("doc/mail/issue_ballot_mail.txt", dict(doc=doc, doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), - active_ad_positions=active_ad_positions, - inactive_ad_positions=inactive_ad_positions, - ad_feedback=ad_feedback, last_call_expires=last_call_expires, - approval_text=approval_text, - ballot_writeup=ballot_writeup, needed_ballot_positions= needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values() @@ -398,7 +322,7 @@ def generate_issue_ballot_mail(request, doc, ballot): ) ) -def email_iana(request, doc, to, msg): +def email_iana(request, doc, to, msg, cc=None): # fix up message and send it with extra info on doc in headers import email parsed_msg = email.message_from_string(msg.encode("utf-8")) @@ -411,7 +335,8 @@ def email_iana(request, doc, to, msg): send_mail_text(request, "IANA <%s>" % to, parsed_msg["From"], parsed_msg["Subject"], parsed_msg.get_payload(), - extra=extra) + extra=extra, + cc=cc) def extra_automation_headers(doc): extra = {} @@ -422,45 +347,70 @@ def extra_automation_headers(doc): def email_last_call_expired(doc): text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state("draft-iesg").name + addrs = gather_address_lists('last_call_expired',doc=doc) - to = [x.strip() for x in doc.notify.replace(';', ',').split(',')] - to.insert(0, "iesg@ietf.org") - send_mail(None, - to, + addrs.to, "DraftTracker Mail System ", "Last Call Expired: %s" % doc.file_tag(), "doc/mail/change_notice.txt", dict(text=text, doc=doc, url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), - cc="iesg-secretary@ietf.org") + cc = addrs.cc) -def stream_state_email_recipients(doc, extra_recipients=[]): - persons = set() - res = [] - for r in Role.objects.filter(group=doc.group, name__in=("chair", "delegate")).select_related("person", "email"): - res.append(r.formatted_email()) - persons.add(r.person) +def email_intended_status_changed(request, doc, text): + (to,cc) = gather_address_lists('doc_intended_status_changed',doc=doc) - for email in doc.authors.all(): - if email.person not in persons: - res.append(email.formatted_email()) - persons.add(email.person) + if not to: + return + + text = strip_tags(text) + send_mail(request, to, None, + "Intended Status for %s changed to %s" % (doc.file_tag(),doc.intended_std_level), + "doc/mail/intended_status_changed_email.txt", + dict(text=text, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) - for e in extra_recipients: - if e.person not in persons: - res.append(e.formatted_email()) - persons.add(e.person) +def email_comment(request, doc, comment): + (to, cc) = gather_address_lists('doc_added_comment',doc=doc) - return res + send_mail(request, to, None, "Comment added to %s history"%doc.name, + "doc/mail/comment_added_email.txt", + dict( + comment=comment, + doc=doc, + by=request.user.person, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + ), + cc = cc) -def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""): - recipients = stream_state_email_recipients(doc) + +def email_adopted(request, doc, prev_state, new_state, by, comment=""): + (to, cc) = gather_address_lists('doc_adopted_by_group',doc=doc) state_type = (prev_state or new_state).type - send_mail(request, recipients, settings.DEFAULT_FROM_EMAIL, + send_mail(request, to, settings.DEFAULT_FROM_EMAIL, + u"The %s %s has adopted %s" % + (doc.group.acronym.upper(),doc.group.type_id.upper(), doc.name), + 'doc/mail/doc_adopted_email.txt', + dict(doc=doc, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + state_type=state_type, + prev_state=prev_state, + new_state=new_state, + by=by, + comment=comment), + cc=cc) + +def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""): + (to, cc)= gather_address_lists('doc_stream_state_edited',doc=doc) + + state_type = (prev_state or new_state).type + + send_mail(request, to, settings.DEFAULT_FROM_EMAIL, u"%s changed for %s" % (state_type.label, doc.name), 'doc/mail/stream_state_changed_email.txt', dict(doc=doc, @@ -469,17 +419,14 @@ def email_stream_state_changed(request, doc, prev_state, new_state, by, comment= prev_state=prev_state, new_state=new_state, by=by, - comment=comment)) + comment=comment), + cc=cc) def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment=""): - extra_recipients = [] - if DocTagName.objects.get(slug="sheph-u") in added_tags and doc.shepherd: - extra_recipients.append(doc.shepherd) + (to, cc) = gather_address_lists('doc_stream_state_edited',doc=doc) - recipients = stream_state_email_recipients(doc, extra_recipients) - - send_mail(request, recipients, settings.DEFAULT_FROM_EMAIL, + send_mail(request, to, settings.DEFAULT_FROM_EMAIL, u"Tags changed for %s" % doc.name, 'doc/mail/stream_tags_changed_email.txt', dict(doc=doc, @@ -487,32 +434,46 @@ def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, commen added=added_tags, removed=removed_tags, by=by, - comment=comment)) + comment=comment), + cc=cc) def send_review_possibly_replaces_request(request, doc): - to_email = [] - - if doc.stream_id == "ietf": - to_email.extend(r.formatted_email() for r in Role.objects.filter(group=doc.group, name="chair").select_related("email", "person")) - elif doc.stream_id == "iab": - to_email.append("IAB Stream ") - elif doc.stream_id == "ise": - to_email.append("Independent Submission Editor ") - elif doc.stream_id == "irtf": - to_email.append("IRSG ") + addrs = gather_address_lists('doc_replacement_suggested',doc=doc) + to = set(addrs.to) + cc = set(addrs.cc) possibly_replaces = Document.objects.filter(name__in=[alias.name for alias in doc.related_that_doc("possibly-replaces")]) - other_chairs = Role.objects.filter(group__in=[other.group for other in possibly_replaces], name="chair").select_related("email", "person") - to_email.extend(r.formatted_email() for r in other_chairs) + for other_doc in possibly_replaces: + (other_to, other_cc) = gather_address_lists('doc_replacement_suggested',doc=other_doc) + to.update(other_to) + cc.update(other_cc) - if not to_email: - to_email.append("internet-drafts@ietf.org") + send_mail(request, list(to), settings.DEFAULT_FROM_EMAIL, + 'Review of suggested possible replacements for %s-%s needed' % (doc.name, doc.rev), + 'doc/mail/review_possibly_replaces_request.txt', + dict(doc= doc, + possibly_replaces=doc.related_that_doc("possibly-replaces"), + review_url=settings.IDTRACKER_BASE_URL + urlreverse("doc_review_possibly_replaces", kwargs={ "name": doc.name })), + cc=list(cc),) - if to_email: - send_mail(request, list(set(to_email)), settings.DEFAULT_FROM_EMAIL, - 'Review of suggested possible replacements for %s-%s needed' % (doc.name, doc.rev), - 'doc/mail/review_possibly_replaces_request.txt', { - 'doc': doc, - 'possibly_replaces': doc.related_that_doc("possibly-replaces"), - 'review_url': settings.IDTRACKER_BASE_URL + urlreverse("doc_review_possibly_replaces", kwargs={ "name": doc.name }), - }) +def email_charter_internal_review(request, charter): + addrs = gather_address_lists('charter_internal_review',doc=charter,group=charter.group) + filename = '%s-%s.txt' % (charter.canonical_name(),charter.rev) + charter_text = get_document_content( + filename, + os.path.join(settings.CHARTER_PATH,filename), + split=False, + markup=False, + ) + send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL, + 'Internal WG Review: %s (%s)'%(charter.group.name,charter.group.acronym), + 'doc/mail/charter_internal_review.txt', + dict(charter=charter, + chairs=charter.group.role_set.filter(name='chair').values_list('person__name',flat=True), + ads=charter.group.role_set.filter(name='ad').values_list('person__name',flat=True), + charter_text=charter_text, + milestones=charter.group.groupmilestone_set.filter(state="charter"), + ), + cc=addrs.cc, + extra={'Reply-To':"iesg@ietf.org"}, + ) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 53b023bf9..06dd3f83c 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -718,8 +718,9 @@ class AddCommentTestCase(TestCase): self.assertEqual("This is a test.", draft.latest_event().desc) self.assertEqual("added_comment", draft.latest_event().type) self.assertEqual(len(outbox), mailbox_before + 1) - self.assertTrue("updated" in outbox[-1]['Subject']) + self.assertTrue("Comment added" in outbox[-1]['Subject']) self.assertTrue(draft.name in outbox[-1]['Subject']) + self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) # Make sure we can also do it as IANA self.client.login(username="iana", password="iana+password") @@ -788,14 +789,20 @@ expand-draft-ietf-ames-test.all@virtual.ietf.org ames-author@example.ames, ames os.unlink(self.doc_alias_file.name) def testAliases(self): - url = urlreverse('ietf.doc.views_doc.email_aliases', kwargs=dict(name="draft-ietf-mars-test")) + url = urlreverse('doc_specific_email_aliases', kwargs=dict(name="draft-ietf-mars-test")) r = self.client.get(url) - self.assertTrue(all([x in r.content for x in ['mars-test@','mars-test.authors@','mars-test.chairs@']])) - self.assertFalse(any([x in r.content for x in ['ames-test@','ames-test.authors@','ames-test.chairs@']])) + self.assertEqual(r.status_code, 302) url = urlreverse('ietf.doc.views_doc.email_aliases', kwargs=dict()) login_testing_unauthorized(self, "plain", url) r = self.client.get(url) + self.assertEqual(r.status_code, 200) self.assertTrue(all([x in r.content for x in ['mars-test@','mars-test.authors@','mars-test.chairs@']])) self.assertTrue(all([x in r.content for x in ['ames-test@','ames-test.authors@','ames-test.chairs@']])) + def testExpansions(self): + url = urlreverse('ietf.doc.views_doc.document_email', kwargs=dict(name="draft-ietf-mars-test")) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue('draft-ietf-mars-test.all@ietf.org' in r.content) + self.assertTrue('ballot_saved' in r.content) diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index 5ba9fa867..1589df22c 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -12,7 +12,7 @@ from ietf.name.models import BallotPositionName from ietf.iesg.models import TelechatDate from ietf.person.models import Person from ietf.utils.test_utils import TestCase -from ietf.utils.mail import outbox +from ietf.utils.mail import outbox, empty_outbox from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized @@ -147,12 +147,12 @@ class EditPositionTests(TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertTrue(len(q('form input[name="cc"]')) > 0) + self.assertTrue(len(q('form input[name="extra_cc"]')) > 0) # send mailbox_before = len(outbox) - r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1",cc_group_list="1")) + r = self.client.post(url, dict(extra_cc="test298347@example.com", cc_choices=['doc_notify','doc_group_chairs'])) self.assertEqual(r.status_code, 302) self.assertEqual(len(outbox), mailbox_before + 1) @@ -162,16 +162,22 @@ class EditPositionTests(TestCase): self.assertTrue(draft.name in m['Subject']) self.assertTrue("clearer title" in str(m)) self.assertTrue("Test!" in str(m)) + self.assertTrue("iesg@" in m['To']) + # cc_choice doc_group_chairs + self.assertTrue("mars-chairs@" in m['Cc']) + # cc_choice doc_notify self.assertTrue("somebody@example.com" in m['Cc']) - self.assertTrue("test@example.com" in m['Cc']) - self.assertTrue(draft.group.list_email) - self.assertTrue(draft.group.list_email in m['Cc']) + # cc_choice doc_group_email_list was not selected + self.assertFalse(draft.group.list_email in m['Cc']) + # extra-cc + self.assertTrue("test298347@example.com" in m['Cc']) r = self.client.post(url, dict(cc="")) self.assertEqual(r.status_code, 302) self.assertEqual(len(outbox), mailbox_before + 2) m = outbox[-1] - self.assertEqual(m['Cc'],None) + self.assertTrue("iesg@" in m['To']) + self.assertFalse(m['Cc'] and draft.group.list_email in m['Cc']) class BallotWriteupsTests(TestCase): @@ -232,9 +238,11 @@ class BallotWriteupsTests(TestCase): send_last_call_request="1")) draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-iesg"), "lc-req") - self.assertEqual(len(outbox), mailbox_before + 3) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Last Call" in outbox[-1]['Subject']) self.assertTrue(draft.name in outbox[-1]['Subject']) + self.assertTrue('iesg-secretary@' in outbox[-1]['To']) + self.assertTrue('aread@' in outbox[-1]['Cc']) def test_edit_ballot_writeup(self): draft = make_test_data() @@ -270,36 +278,8 @@ class BallotWriteupsTests(TestCase): url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "ad", url) - ballot = draft.latest_event(BallotDocEvent, type="created_ballot") - def create_pos(num, vote, comment="", discuss=""): - ad = Person.objects.get(name="Ad No%s" % num) - e = BallotPositionDocEvent() - e.doc = draft - e.ballot = ballot - e.by = ad - e.ad = ad - e.pos = BallotPositionName.objects.get(slug=vote) - e.type = "changed_ballot_position" - e.comment = comment - if e.comment: - e.comment_time = datetime.datetime.now() - e.discuss = discuss - if e.discuss: - e.discuss_time = datetime.datetime.now() - e.save() - - # active - create_pos(1, "yes", discuss="discuss1 " * 20) - create_pos(2, "noobj", comment="comment2 " * 20) - create_pos(3, "discuss", discuss="discuss3 " * 20, comment="comment3 " * 20) - create_pos(4, "abstain") - create_pos(5, "recuse") - - # inactive - create_pos(9, "yes") - - mailbox_before = len(outbox) + empty_outbox() r = self.client.post(url, dict( ballot_writeup="This is a test.", @@ -308,15 +288,12 @@ class BallotWriteupsTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertTrue(draft.latest_event(type="sent_ballot_announcement")) - self.assertEqual(len(outbox), mailbox_before + 2) - issue_email = outbox[-2] - self.assertTrue("Evaluation:" in issue_email['Subject']) - self.assertTrue("comment1" not in str(issue_email)) - self.assertTrue("comment2" in str(issue_email)) - self.assertTrue("comment3" in str(issue_email)) - self.assertTrue("discuss3" in str(issue_email)) - self.assertTrue("This is a test" in str(issue_email)) - self.assertTrue("The IESG has approved" in str(issue_email)) + self.assertEqual(len(outbox), 2) + self.assertTrue('Evaluation:' in outbox[-2]['Subject']) + self.assertTrue('iesg@' in outbox[-2]['To']) + self.assertTrue('Evaluation:' in outbox[-1]['Subject']) + self.assertTrue('drafts-eval@' in outbox[-1]['To']) + self.assertTrue('X-IETF-Draft-string' in outbox[-1]) def test_edit_approval_text(self): draft = make_test_data() @@ -387,14 +364,20 @@ class ApproveBallotTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-iesg"), "ann") - self.assertEqual(len(outbox), mailbox_before + 4) + self.assertEqual(len(outbox), mailbox_before + 2) self.assertTrue("Protocol Action" in outbox[-2]['Subject']) + self.assertTrue("ietf-announce" in outbox[-2]['To']) + self.assertTrue("rfc-editor" in outbox[-2]['Cc']) # the IANA copy self.assertTrue("Protocol Action" in outbox[-1]['Subject']) self.assertTrue(not outbox[-1]['CC']) + self.assertTrue('drafts-approval@icann.org' in outbox[-1]['To']) self.assertTrue("Protocol Action" in draft.message_set.order_by("-time")[0].subject) def test_disapprove_ballot(self): + # This tests a codepath that is not used in production + # and that has already had some drift from usefulness (it results in a + # older-style conflict review response). draft = make_test_data() draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw")) @@ -409,10 +392,9 @@ class ApproveBallotTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-iesg"), "dead") - self.assertEqual(len(outbox), mailbox_before + 3) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("NOT be published" in str(outbox[-1])) - class MakeLastCallTests(TestCase): def test_make_last_call(self): draft = make_test_data() @@ -441,11 +423,17 @@ class MakeLastCallTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-iesg"), "lc") self.assertEqual(draft.latest_event(LastCallDocEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) - self.assertEqual(len(outbox), mailbox_before + 4) - self.assertTrue("Last Call" in outbox[-4]['Subject']) - # the IANA copy - self.assertTrue("Last Call" in outbox[-3]['Subject']) + self.assertEqual(len(outbox), mailbox_before + 2) + + self.assertTrue("Last Call" in outbox[-2]['Subject']) + self.assertTrue("ietf-announce@" in outbox[-2]['To']) + for prefix in ['draft-ietf-mars-test','mars-chairs','aread']: + self.assertTrue(prefix+"@" in outbox[-2]['Cc']) + + self.assertTrue("Last Call" in outbox[-1]['Subject']) + self.assertTrue("drafts-lastcall@icann.org" in outbox[-1]['To']) + self.assertTrue("Last Call" in draft.message_set.order_by("-time")[0].subject) class DeferUndeferTestCase(TestCase): @@ -491,11 +479,16 @@ class DeferUndeferTestCase(TestCase): if doc.type_id in defer_states: self.assertEqual(doc.get_state(defer_states[doc.type_id][0]).slug,defer_states[doc.type_id][1]) self.assertTrue(doc.active_defer_event()) - self.assertEqual(len(outbox), mailbox_before + 3) - self.assertTrue("State Update" in outbox[-3]['Subject']) - self.assertTrue("Telechat update" in outbox[-2]['Subject']) + + self.assertEqual(len(outbox), mailbox_before + 2) + + self.assertTrue('Telechat update' in outbox[-2]['Subject']) + self.assertTrue('iesg-secretary@' in outbox[-2]['To']) + self.assertTrue('iesg@' in outbox[-2]['To']) + self.assertTrue("Deferred" in outbox[-1]['Subject']) self.assertTrue(doc.file_tag() in outbox[-1]['Subject']) + self.assertTrue('iesg@' in outbox[-1]['To']) # Ensure it's not possible to defer again r = self.client.get(url) @@ -546,11 +539,13 @@ class DeferUndeferTestCase(TestCase): if doc.type_id in undefer_states: self.assertEqual(doc.get_state(undefer_states[doc.type_id][0]).slug,undefer_states[doc.type_id][1]) self.assertFalse(doc.active_defer_event()) - self.assertEqual(len(outbox), mailbox_before + 3) - self.assertTrue("Telechat update" in outbox[-3]['Subject']) - self.assertTrue("State Update" in outbox[-2]['Subject']) + self.assertEqual(len(outbox), mailbox_before + 2) + self.assertTrue("Telechat update" in outbox[-2]['Subject']) + self.assertTrue('iesg-secretary@' in outbox[-2]['To']) + self.assertTrue('iesg@' in outbox[-2]['To']) self.assertTrue("Undeferred" in outbox[-1]['Subject']) self.assertTrue(doc.file_tag() in outbox[-1]['Subject']) + self.assertTrue('iesg@' in outbox[-1]['To']) # Ensure it's not possible to undefer again r = self.client.get(url) diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py index ac99b7552..2ea147f36 100644 --- a/ietf/doc/tests_charter.py +++ b/ietf/doc/tests_charter.py @@ -9,12 +9,12 @@ from django.core.urlresolvers import reverse as urlreverse from ietf.doc.models import ( Document, State, BallotDocEvent, BallotType, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent ) -from ietf.doc.utils_charter import next_revision, default_review_text, default_action_text +from ietf.doc.utils_charter import next_revision, default_review_text, default_action_text from ietf.group.models import Group, GroupMilestone from ietf.iesg.models import TelechatDate from ietf.person.models import Person from ietf.utils.test_utils import TestCase -from ietf.utils.mail import outbox +from ietf.utils.mail import outbox, empty_outbox from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized @@ -78,7 +78,8 @@ class EditCharterTests(TestCase): for slug in ("intrev", "extrev", "iesgrev"): s = State.objects.get(used=True, type="charter", slug=slug) events_before = charter.docevent_set.count() - mailbox_before = len(outbox) + + empty_outbox() r = self.client.post(url, dict(charter_state=str(s.pk), message="test message")) self.assertEqual(r.status_code, 302) @@ -96,8 +97,17 @@ class EditCharterTests(TestCase): if slug in ("intrev", "iesgrev"): self.assertTrue(find_event("created_ballot")) - self.assertEqual(len(outbox), mailbox_before + 1) - self.assertTrue("state changed" in outbox[-1]['Subject'].lower()) + self.assertEqual(len(outbox), 3 if slug=="intrev" else 2 ) + + if slug=="intrev": + self.assertTrue("Internal WG Review" in outbox[-3]['Subject']) + self.assertTrue(all([x in outbox[-3]['To'] for x in ['iab@','iesg@']])) + + self.assertTrue("state changed" in outbox[-2]['Subject'].lower()) + self.assertTrue("iesg-secretary@" in outbox[-2]['To']) + + self.assertTrue("State Update Notice" in outbox[-1]['Subject']) + self.assertTrue("ames-chairs@" in outbox[-1]['To']) def test_edit_telechat_date(self): make_test_data() @@ -196,8 +206,7 @@ class EditCharterTests(TestCase): self.assertEqual(charter.notify,newlist) q = PyQuery(r.content) formlist = q('form input[name=notify]')[0].value - self.assertTrue('marschairman@ietf.org' in formlist) - self.assertFalse('someone@example.com' in formlist) + self.assertEqual(formlist, None) def test_edit_ad(self): make_test_data() @@ -264,43 +273,101 @@ class EditCharterTests(TestCase): self.assertEqual(f.read(), "Windows line\nMac line\nUnix line\n" + utf_8_snippet) - def test_edit_announcement_text(self): + def test_edit_review_announcement_text(self): draft = make_test_data() charter = draft.group.charter - for ann in ("action", "review"): - url = urlreverse('ietf.doc.views_charter.announcement_text', kwargs=dict(name=charter.name, ann=ann)) - self.client.logout() - login_testing_unauthorized(self, "secretary", url) + url = urlreverse('ietf.doc.views_charter.review_announcement_text', kwargs=dict(name=charter.name)) + self.client.logout() + login_testing_unauthorized(self, "secretary", url) - # normal get - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - q = PyQuery(r.content) - self.assertEqual(len(q('textarea[name=announcement_text]')), 1) - # as Secretariat, we can send - if ann == "review": - mailbox_before = len(outbox) - by = Person.objects.get(user__username="secretary") - r = self.client.post(url, dict( - announcement_text=default_review_text(draft.group, charter, by).text, - send_text="1")) - self.assertEqual(len(outbox), mailbox_before + 1) + # normal get + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('textarea[name=announcement_text]')), 1) + self.assertEqual(len(q('textarea[name=new_work_text]')), 1) - # save - r = self.client.post(url, dict( - announcement_text="This is a simple test.", - save_text="1")) - self.assertEqual(r.status_code, 302) - self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann).text) + by = Person.objects.get(user__username="secretary") - # test regenerate - r = self.client.post(url, dict( - announcement_text="This is a simple test.", - regenerate_text="1")) - self.assertEqual(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann).text) + (e1, e2) = default_review_text(draft.group, charter, by) + announcement_text = e1.text + new_work_text = e2.text + + empty_outbox() + r = self.client.post(url, dict( + announcement_text=announcement_text, + new_work_text=new_work_text, + send_both="1")) + self.assertEqual(len(outbox), 2) + self.assertTrue(all(['WG Review' in m['Subject'] for m in outbox])) + self.assertTrue('ietf-announce@' in outbox[0]['To']) + self.assertTrue('mars-wg@' in outbox[0]['Cc']) + self.assertTrue('new-work@' in outbox[1]['To']) + + empty_outbox() + r = self.client.post(url, dict( + announcement_text=announcement_text, + new_work_text=new_work_text, + send_annc_only="1")) + self.assertEqual(len(outbox), 1) + self.assertTrue('ietf-announce@' in outbox[0]['To']) + + empty_outbox() + r = self.client.post(url, dict( + announcement_text=announcement_text, + new_work_text=new_work_text, + send_nw_only="1")) + self.assertEqual(len(outbox), 1) + self.assertTrue('new-work@' in outbox[0]['To']) + + # save + r = self.client.post(url, dict( + announcement_text="This is a simple test.", + new_work_text="New work gets something different.", + save_text="1")) + self.assertEqual(r.status_code, 302) + self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text) + self.assertTrue("New work gets something different." in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text) + + # test regenerate + r = self.client.post(url, dict( + announcement_text="This is a simple test.", + new_work_text="Too simple perhaps?", + regenerate_text="1")) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text) + self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text) + + def test_edit_action_announcement_text(self): + draft = make_test_data() + charter = draft.group.charter + + url = urlreverse('ietf.doc.views_charter.action_announcement_text', kwargs=dict(name=charter.name)) + self.client.logout() + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('textarea[name=announcement_text]')), 1) + + # save + r = self.client.post(url, dict( + announcement_text="This is a simple test.", + save_text="1")) + self.assertEqual(r.status_code, 302) + self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text) + + # test regenerate + r = self.client.post(url, dict( + announcement_text="This is a simple test.", + regenerate_text="1")) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text) def test_edit_ballot_writeupnotes(self): draft = make_test_data() @@ -334,11 +401,12 @@ class EditCharterTests(TestCase): self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) # send - mailbox_before = len(outbox) + empty_outbox() r = self.client.post(url, dict( ballot_writeup="This is a simple test.", send_ballot="1")) - self.assertEqual(len(outbox), mailbox_before + 1) + self.assertEqual(len(outbox), 1) + self.assertTrue('Evaluation' in outbox[0]['Subject']) def test_approve(self): make_test_data() @@ -394,7 +462,7 @@ class EditCharterTests(TestCase): self.assertEqual(len(q('pre')), 1) # approve - mailbox_before = len(outbox) + empty_outbox() r = self.client.post(url, dict()) self.assertEqual(r.status_code, 302) @@ -406,9 +474,12 @@ class EditCharterTests(TestCase): self.assertEqual(charter.rev, "01") self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev)))) - self.assertEqual(len(outbox), mailbox_before + 2) - self.assertTrue("WG Action" in outbox[-1]['Subject']) - self.assertTrue("approved" in outbox[-2]['Subject'].lower()) + self.assertEqual(len(outbox), 2) + self.assertTrue("approved" in outbox[0]['Subject'].lower()) + self.assertTrue("iesg-secretary" in outbox[0]['To']) + self.assertTrue("WG Action" in outbox[1]['Subject']) + self.assertTrue("ietf-announce" in outbox[1]['To']) + self.assertTrue("ames-wg@ietf.org" in outbox[1]['Cc']) self.assertEqual(group.groupmilestone_set.filter(state="charter").count(), 0) self.assertEqual(group.groupmilestone_set.filter(state="active").count(), 2) diff --git a/ietf/doc/tests_conflict_review.py b/ietf/doc/tests_conflict_review.py index 6de5ac13f..34edd19d8 100644 --- a/ietf/doc/tests_conflict_review.py +++ b/ietf/doc/tests_conflict_review.py @@ -15,7 +15,7 @@ from ietf.group.models import Person from ietf.iesg.models import TelechatDate from ietf.name.models import StreamName from ietf.utils.test_utils import TestCase -from ietf.utils.mail import outbox +from ietf.utils.mail import outbox, empty_outbox from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized @@ -70,7 +70,6 @@ class ConflictReviewTests(TestCase): self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review requested")) self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review initiated")) self.assertTrue('Conflict Review requested' in outbox[-1]['Subject']) - self.assertTrue(settings.IANA_EVAL_EMAIL in outbox[-1]['To']) # verify you can't start a review when a review is already in progress r = self.client.post(url,dict(ad="Aread Irector",create_in_state="Needs Shepherd",notify='ipu@ietf.org')) @@ -116,10 +115,14 @@ class ConflictReviewTests(TestCase): self.assertEquals(review_doc.notify,u'ipu@ietf.org') doc = Document.objects.get(name='draft-imaginary-independent-submission') self.assertTrue(doc in [x.target.document for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')]) + self.assertEqual(len(outbox), messages_before + 2) + self.assertTrue('Conflict Review requested' in outbox[-1]['Subject']) - self.assertTrue(any('iesg-secretary@ietf.org' in x['To'] for x in outbox[-2:])) - self.assertTrue(any(settings.IANA_EVAL_EMAIL in x['To'] for x in outbox[-2:])) + self.assertTrue('drafts-eval@icann.org' in outbox[-1]['To']) + + self.assertTrue('Conflict Review requested' in outbox[-2]['Subject']) + self.assertTrue('iesg-secretary@' in outbox[-2]['To']) def test_change_state(self): @@ -189,9 +192,7 @@ class ConflictReviewTests(TestCase): # Regenerate does not save! self.assertEqual(doc.notify,newlist) q = PyQuery(r.content) - self.assertTrue('draft-imaginary-irtf-submission@ietf.org' in q('form input[name=notify]')[0].value) - self.assertTrue('irtf-chair@ietf.org' in q('form input[name=notify]')[0].value) - self.assertTrue('foo@bar.baz.com' not in q('form input[name=notify]')[0].value) + self.assertEqual(None,q('form input[name=notify]')[0].value) def test_edit_ad(self): doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission') @@ -281,7 +282,7 @@ class ConflictReviewTests(TestCase): self.assertTrue( 'NOT be published' in ''.join(wrap(r.content,2**16))) # submit - messages_before = len(outbox) + empty_outbox() r = self.client.post(url,dict(announcement_text=default_approval_text(doc))) self.assertEqual(r.status_code, 302) @@ -289,12 +290,15 @@ class ConflictReviewTests(TestCase): self.assertEqual(doc.get_state_slug(),approve_type+'-sent') self.assertFalse(doc.ballot_open("conflrev")) - self.assertEqual(len(outbox), messages_before + 1) - self.assertTrue('Results of IETF-conflict review' in outbox[-1]['Subject']) + self.assertEqual(len(outbox), 1) + self.assertTrue('Results of IETF-conflict review' in outbox[0]['Subject']) + self.assertTrue('irtf-chair' in outbox[0]['To']) + self.assertTrue('ietf-announce@' in outbox[0]['Cc']) + self.assertTrue('iana@' in outbox[0]['Cc']) if approve_type == 'appr-noprob': - self.assertTrue( 'IESG has no problem' in ''.join(wrap(unicode(outbox[-1]),2**16))) + self.assertTrue( 'IESG has no problem' in ''.join(wrap(unicode(outbox[0]),2**16))) else: - self.assertTrue( 'NOT be published' in ''.join(wrap(unicode(outbox[-1]),2**16))) + self.assertTrue( 'NOT be published' in ''.join(wrap(unicode(outbox[0]),2**16))) def test_approve_reqnopub(self): diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 44a9b0b61..ff342a605 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -20,7 +20,7 @@ from ietf.meeting.models import Meeting, MeetingTypeName from ietf.iesg.models import TelechatDate from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_data import make_test_data -from ietf.utils.mail import outbox +from ietf.utils.mail import outbox, empty_outbox from ietf.utils.test_utils import TestCase @@ -72,10 +72,11 @@ class ChangeStateTests(TestCase): self.assertEqual(draft.docevent_set.count(), events_before + 2) self.assertTrue("Test comment" in draft.docevent_set.all()[0].desc) self.assertTrue("IESG state changed" in draft.docevent_set.all()[1].desc) - self.assertEqual(len(outbox), mailbox_before + 2) - self.assertTrue("State Update Notice" in outbox[-2]['Subject']) - self.assertTrue(draft.name in outbox[-1]['Subject']) - + self.assertEqual(len(outbox), mailbox_before + 1) + self.assertTrue("State Update Notice" in outbox[-1]['Subject']) + self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) + self.assertTrue('mars-chairs@' in outbox[-1]['To']) + self.assertTrue('aread@' in outbox[-1]['To']) # check that we got a previous state now r = self.client.get(url) @@ -101,11 +102,19 @@ class ChangeStateTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-iesg"), "review-e") - self.assertEqual(len(outbox), mailbox_before + 2 + 1) + + self.assertEqual(len(outbox), mailbox_before + 2) + self.assertTrue(draft.name in outbox[-1]['Subject']) self.assertTrue("changed state" in outbox[-1]['Subject']) self.assertTrue("is no longer" in str(outbox[-1])) self.assertTrue("Test comment" in str(outbox[-1])) + self.assertTrue("rfc-editor@" in outbox[-1]['To']) + self.assertTrue("iana@" in outbox[-1]['To']) + + self.assertTrue("ID Tracker State Update Notice:" in outbox[-2]['Subject']) + self.assertTrue("aread@" in outbox[-2]['To']) + def test_change_iana_state(self): draft = make_test_data() @@ -145,8 +154,8 @@ class ChangeStateTests(TestCase): self.client.login(username="secretary", password="secretary+password") url = urlreverse('doc_change_state', kwargs=dict(name=draft.name)) - mailbox_before = len(outbox) - + empty_outbox() + self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text")) r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk)) self.assertTrue("Your request to issue" in r.content) @@ -171,8 +180,14 @@ class ChangeStateTests(TestCase): self.assertTrue("Technical Summary" in e.text) # mail notice - self.assertTrue(len(outbox) > mailbox_before) - self.assertTrue("Last Call:" in outbox[-1]['Subject']) + self.assertEqual(len(outbox), 2) + + self.assertTrue("ID Tracker State Update" in outbox[0]['Subject']) + self.assertTrue("aread@" in outbox[0]['To']) + + self.assertTrue("Last Call:" in outbox[1]['Subject']) + self.assertTrue('iesg-secretary@' in outbox[1]['To']) + self.assertTrue('aread@' in outbox[1]['Cc']) # comment self.assertTrue("Last call was requested" in draft.latest_event().desc) @@ -252,6 +267,8 @@ class EditInfoTests(TestCase): self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date) self.assertEqual(len(outbox),mailbox_before+1) self.assertTrue("Telechat update" in outbox[-1]['Subject']) + self.assertTrue('iesg@' in outbox[-1]['To']) + self.assertTrue('iesg-secretary@' in outbox[-1]['To']) # change telechat mailbox_before=len(outbox) @@ -330,7 +347,7 @@ class EditInfoTests(TestCase): self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('form select[name=intended_std_level]')), 1) - self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) + self.assertEqual(None,q('form input[name=notify]')[0].value) # add events_before = draft.docevent_set.count() @@ -356,7 +373,9 @@ class EditInfoTests(TestCase): self.assertEqual(draft.docevent_set.count(), events_before + 3) events = list(draft.docevent_set.order_by('time', 'id')) self.assertEqual(events[-3].type, "started_iesg_process") - self.assertEqual(len(outbox), mailbox_before) + self.assertEqual(len(outbox), mailbox_before+1) + self.assertTrue('IESG processing' in outbox[-1]['Subject']) + self.assertTrue('draft-ietf-mars-test2@' in outbox[-1]['To']) # Redo, starting in publication requested to make sure WG state is also set draft.unset_state('draft-iesg') @@ -431,6 +450,7 @@ class ResurrectTests(TestCase): self.assertTrue("Resurrection" in e.desc) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Resurrection" in outbox[-1]['Subject']) + self.assertTrue('internet-drafts@' in outbox[-1]['To']) def test_resurrect(self): draft = make_test_data() @@ -463,6 +483,9 @@ class ResurrectTests(TestCase): self.assertEqual(draft.get_state_slug(), "active") self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) self.assertEqual(len(outbox), mailbox_before + 1) + self.assertTrue('Resurrection Completed' in outbox[-1]['Subject']) + self.assertTrue('iesg-secretary' in outbox[-1]['To']) + self.assertTrue('aread' in outbox[-1]['To']) class ExpireIDsTests(TestCase): @@ -524,8 +547,9 @@ class ExpireIDsTests(TestCase): send_expire_warning_for_draft(draft) self.assertEqual(len(outbox), mailbox_before + 1) - self.assertTrue("aread@ietf.org" in str(outbox[-1])) # author - self.assertTrue("marschairman@ietf.org" in str(outbox[-1])) + self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) # Gets the authors + self.assertTrue('mars-chairs@ietf.org' in outbox[-1]['Cc']) + self.assertTrue('aread@' in outbox[-1]['Cc']) def test_expire_drafts(self): from ietf.doc.expire import get_expired_drafts, send_expire_notice_for_draft, expire_draft @@ -556,6 +580,9 @@ class ExpireIDsTests(TestCase): self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("expired" in outbox[-1]["Subject"]) + self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) # gets authors + self.assertTrue('mars-chairs@ietf.org' in outbox[-1]['Cc']) + self.assertTrue('aread@' in outbox[-1]['Cc']) # test expiry txt = "%s-%s.txt" % (draft.name, draft.rev) @@ -680,7 +707,9 @@ class ExpireLastCallTests(TestCase): self.assertEqual(draft.docevent_set.count(), events_before + 1) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Last Call Expired" in outbox[-1]["Subject"]) - + self.assertTrue('iesg-secretary@' in outbox[-1]['Cc']) + self.assertTrue('aread@' in outbox[-1]['To']) + self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To']) class IndividualInfoFormsTests(TestCase): def test_doc_change_stream(self): @@ -694,22 +723,25 @@ class IndividualInfoFormsTests(TestCase): self.assertEqual(len(q('[type=submit]:contains("Save")')), 1) # shift to ISE stream - messages_before = len(outbox) + empty_outbox() r = self.client.post(url,dict(stream="ise",comment="7gRMTjBM")) self.assertEqual(r.status_code,302) self.doc = Document.objects.get(name=self.docname) self.assertEqual(self.doc.stream_id,'ise') - self.assertEqual(len(outbox),messages_before+1) - self.assertTrue('Stream Change Notice' in outbox[-1]['Subject']) - self.assertTrue('7gRMTjBM' in str(outbox[-1])) + self.assertEqual(len(outbox), 1) + self.assertTrue('Stream Change Notice' in outbox[0]['Subject']) + self.assertTrue('rfc-ise@' in outbox[0]['To']) + self.assertTrue('iesg@' in outbox[0]['To']) + self.assertTrue('7gRMTjBM' in str(outbox[0])) self.assertTrue('7gRMTjBM' in self.doc.latest_event(DocEvent,type='added_comment').desc) - # Would be nice to test that the stream managers were in the To header... # shift to an unknown stream (it must be possible to throw a document out of any stream) + empty_outbox() r = self.client.post(url,dict(stream="")) self.assertEqual(r.status_code,302) self.doc = Document.objects.get(name=self.docname) self.assertEqual(self.doc.stream,None) + self.assertTrue('rfc-ise@' in outbox[0]['To']) def test_doc_change_notify(self): url = urlreverse('doc_change_notify', kwargs=dict(name=self.docname)) @@ -734,7 +766,7 @@ class IndividualInfoFormsTests(TestCase): # Regenerate does not save! self.assertEqual(self.doc.notify,'TJ2APh2P@ietf.org') q = PyQuery(r.content) - self.assertTrue('TJ2Aph2P' not in q('form input[name=notify]')[0].value) + self.assertEqual(None,q('form input[name=notify]')[0].value) def test_doc_change_intended_status(self): url = urlreverse('doc_change_intended_status', kwargs=dict(name=self.docname)) @@ -759,7 +791,10 @@ class IndividualInfoFormsTests(TestCase): self.doc = Document.objects.get(name=self.docname) self.assertEqual(self.doc.intended_std_level_id,'bcp') self.assertEqual(len(outbox),messages_before+1) + self.assertTrue('Intended Status ' in outbox[-1]['Subject']) + self.assertTrue('mars-chairs@' in outbox[-1]['To']) self.assertTrue('ZpyQFGmA' in str(outbox[-1])) + self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc) def test_doc_change_telechat_date(self): @@ -773,12 +808,17 @@ class IndividualInfoFormsTests(TestCase): self.assertEqual(len(q('[type=submit]:contains("Save")')), 1) # set a date + empty_outbox() self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat")) telechat_date = TelechatDate.objects.active().order_by('date')[0].date r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) self.assertEqual(r.status_code,302) self.doc = Document.objects.get(name=self.docname) self.assertEqual(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date) + self.assertEqual(len(outbox), 1) + self.assertTrue('Telechat update notice' in outbox[0]['Subject']) + self.assertTrue('iesg@' in outbox[0]['To']) + self.assertTrue('iesg-secretary@' in outbox[0]['To']) # Take the doc back off any telechat r = self.client.post(url,dict(telechat_date="")) @@ -964,7 +1004,7 @@ class IndividualInfoFormsTests(TestCase): class SubmitToIesgTests(TestCase): - def verify_permissions(self): + def test_verify_permissions(self): def verify_fail(username): if username: @@ -987,7 +1027,7 @@ class SubmitToIesgTests(TestCase): for username in ['marschairman','secretary','ad']: verify_can_see(username) - def cancel_submission(self): + def test_cancel_submission(self): url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname)) self.client.login(username="marschairman", password="marschairman+password") @@ -997,7 +1037,7 @@ class SubmitToIesgTests(TestCase): doc = Document.objects.get(pk=self.doc.pk) self.assertTrue(doc.get_state('draft-iesg')==None) - def confirm_submission(self): + def test_confirm_submission(self): url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname)) self.client.login(username="marschairman", password="marschairman+password") @@ -1014,6 +1054,8 @@ class SubmitToIesgTests(TestCase): self.assertTrue(doc.docevent_set.count() != docevent_count_pre) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Publication has been requested" in outbox[-1]['Subject']) + self.assertTrue("aread@" in outbox[-1]['To']) + self.assertTrue("iesg-secretary@" in outbox[-1]['Cc']) def setUp(self): make_test_data() @@ -1052,12 +1094,16 @@ class RequestPublicationTests(TestCase): draft = Document.objects.get(name=draft.name) self.assertEqual(draft.get_state_slug("draft-stream-iab"), "rfc-edit") + self.assertEqual(len(outbox), mailbox_before + 2) + self.assertTrue("Document Action" in outbox[-2]['Subject']) - self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject) - # the IANA copy + self.assertTrue("rfc-editor@" in outbox[-2]['To']) + self.assertTrue("Document Action" in outbox[-1]['Subject']) - self.assertTrue(not outbox[-1]['CC']) + self.assertTrue("drafts-approval@icann.org" in outbox[-1]['To']) + + self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject) class AdoptDraftTests(TestCase): def test_adopt_document(self): @@ -1092,13 +1138,12 @@ class AdoptDraftTests(TestCase): self.assertEqual(draft.group.acronym, "mars") self.assertEqual(draft.stream_id, "ietf") self.assertEqual(draft.docevent_set.count() - events_before, 5) - self.assertTrue('draft-ietf-mars-test@ietf.org' in draft.notify) - self.assertTrue('draft-ietf-mars-test.ad@ietf.org' in draft.notify) - self.assertTrue('draft-ietf-mars-test.shepherd@ietf.org' in draft.notify) + self.assertEqual(draft.notify,"aliens@example.mars") self.assertEqual(len(outbox), mailbox_before + 1) - self.assertTrue("state changed" in outbox[-1]["Subject"].lower()) - self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1])) - self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1])) + self.assertTrue("has adopted" in outbox[-1]["Subject"].lower()) + self.assertTrue("mars-chairs@ietf.org" in outbox[-1]['To']) + self.assertTrue("draft-ietf-mars-test@" in outbox[-1]['To']) + self.assertTrue("mars-wg@" in outbox[-1]['To']) self.assertFalse(mars.list_email in draft.notify) @@ -1139,7 +1184,7 @@ class ChangeStreamStateTests(TestCase): self.assertEqual(draft.docevent_set.count() - events_before, 2) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("tags changed" in outbox[-1]["Subject"].lower()) - self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1])) + self.assertTrue("mars-chairs@ietf.org" in unicode(outbox[-1])) self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1])) self.assertTrue("plain@example.com" in unicode(outbox[-1])) @@ -1163,7 +1208,7 @@ class ChangeStreamStateTests(TestCase): old_state = draft.get_state("draft-stream-%s" % draft.stream_id ) new_state = State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="parked") self.assertNotEqual(old_state, new_state) - mailbox_before = len(outbox) + empty_outbox() events_before = draft.docevent_set.count() r = self.client.post(url, @@ -1181,10 +1226,10 @@ class ChangeStreamStateTests(TestCase): self.assertEqual(len(reminder), 1) due = datetime.datetime.now() + datetime.timedelta(weeks=10) self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1)) - self.assertEqual(len(outbox), mailbox_before + 1) - self.assertTrue("state changed" in outbox[-1]["Subject"].lower()) - self.assertTrue("marschairman@ietf.org" in unicode(outbox[-1])) - self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[-1])) + self.assertEqual(len(outbox), 1) + self.assertTrue("state changed" in outbox[0]["Subject"].lower()) + self.assertTrue("mars-chairs@ietf.org" in unicode(outbox[0])) + self.assertTrue("marsdelegate@ietf.org" in unicode(outbox[0])) class ChangeReplacesTests(TestCase): def setUp(self): @@ -1255,6 +1300,7 @@ class ChangeReplacesTests(TestCase): self.assertEqual(len(q('[type=submit]:contains("Save")')), 1) # Post that says replacea replaces base a + empty_outbox() RelatedDocument.objects.create(source=self.replacea, target=self.basea.docalias_set.first(), relationship=DocRelationshipName.objects.get(slug="possibly-replaces")) self.assertEqual(self.basea.get_state().slug,'active') @@ -1263,7 +1309,12 @@ class ChangeReplacesTests(TestCase): self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1) self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea)) + self.assertEqual(len(outbox), 1) + self.assertTrue('replacement status updated' in outbox[-1]['Subject']) + self.assertTrue('base-a@' in outbox[-1]['To']) + self.assertTrue('replace-a@' in outbox[-1]['To']) + empty_outbox() # Post that says replaceboth replaces both base a and base b url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name)) self.assertEqual(self.baseb.get_state().slug,'expired') @@ -1271,18 +1322,31 @@ class ChangeReplacesTests(TestCase): self.assertEqual(r.status_code, 302) self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl') + self.assertEqual(len(outbox), 1) + self.assertTrue('base-a@' in outbox[-1]['To']) + self.assertTrue('base-b@' in outbox[-1]['To']) + self.assertTrue('replace-both@' in outbox[-1]['To']) # Post that undoes replaceboth + empty_outbox() r = self.client.post(url, dict(replaces="")) self.assertEqual(r.status_code, 302) self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired') + self.assertEqual(len(outbox), 1) + self.assertTrue('base-a@' in outbox[-1]['To']) + self.assertTrue('base-b@' in outbox[-1]['To']) + self.assertTrue('replace-both@' in outbox[-1]['To']) # Post that undoes replacea + empty_outbox() url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name)) r = self.client.post(url, dict(replaces="")) self.assertEqual(r.status_code, 302) self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active') + self.assertTrue('base-a@' in outbox[-1]['To']) + self.assertTrue('replace-a@' in outbox[-1]['To']) + def test_review_possibly_replaces(self): replaced = self.basea.docalias_set.first() diff --git a/ietf/doc/tests_status_change.py b/ietf/doc/tests_status_change.py index b22cd9f31..f9a564549 100644 --- a/ietf/doc/tests_status_change.py +++ b/ietf/doc/tests_status_change.py @@ -111,7 +111,6 @@ class StatusChangeTests(TestCase): doc = Document.objects.get(name='status-change-imaginary-mid-review') self.assertEquals(doc.get_state('statchg').slug,'lc-req') self.assertEquals(len(outbox), messages_before + 1) - self.assertTrue('iesg-secretary' in outbox[-1]['To']) self.assertTrue('Last Call:' in outbox[-1]['Subject']) # successful change to IESG Evaluation @@ -157,9 +156,7 @@ class StatusChangeTests(TestCase): self.assertEqual(doc.notify,newlist) q = PyQuery(r.content) formlist = q('form input[name=notify]')[0].value - self.assertTrue('draft-ietf-random-thing@ietf.org' in formlist) - self.assertTrue('draft-ietf-random-otherthing@ietf.org' in formlist) - self.assertFalse('foo@bar.baz.com' in formlist) + self.assertEqual(None,formlist) def test_edit_title(self): doc = Document.objects.get(name='status-change-imaginary-mid-review') @@ -285,7 +282,6 @@ class StatusChangeTests(TestCase): self.assertEqual(r.status_code,200) self.assertTrue( 'Last call requested' in ''.join(wrap(r.content,2**16))) self.assertEqual(len(outbox), messages_before + 1) - self.assertTrue('iesg-secretary' in outbox[-1]['To']) self.assertTrue('Last Call:' in outbox[-1]['Subject']) self.assertTrue('Last Call Request has been submitted' in ''.join(wrap(unicode(outbox[-1]),2**16))) @@ -326,8 +322,10 @@ class StatusChangeTests(TestCase): self.assertEqual(len(outbox), messages_before + 2) self.assertTrue('Action:' in outbox[-1]['Subject']) - self.assertTrue('(rfc9999) to Internet Standard' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16))) + self.assertTrue('ietf-announce' in outbox[-1]['To']) + self.assertTrue('rfc-editor' in outbox[-1]['Cc']) self.assertTrue('(rfc9998) to Historic' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16))) + self.assertTrue('(rfc9999) to Internet Standard' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16))) self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('The following approval message was sent')) diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index d9d8489ec..0711b11cb 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -55,6 +55,7 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/(?:(?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), url(r'^(?P[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"), url(r'^(?P[A-Za-z0-9._+-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"), + url(r'^(?P[A-Za-z0-9._+-]+)/email/$', views_doc.document_email, name="doc_email"), url(r'^(?P[A-Za-z0-9._+-]+)/shepherdwriteup/$', views_doc.document_shepherd_writeup, name="doc_shepherd_writeup"), url(r'^(?P[A-Za-z0-9._+-]+)/references/$', views_doc.document_references, name="doc_references"), url(r'^(?P[A-Za-z0-9._+-]+)/referencedby/$', views_doc.document_referenced_by, name="doc_referenced_by"), @@ -65,7 +66,7 @@ urlpatterns = patterns('', (r'^(?P[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json), (r'^(?P[A-Za-z0-9._+-]+)/ballotpopup/(?P[0-9]+)/$', views_doc.ballot_popup), - url(r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', views_doc.email_aliases), + url(r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_draft.change_state, name='doc_change_state'), # IESG state url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Piana-action|iana-review)/$', views_draft.change_iana_state, name='doc_change_iana_state'), diff --git a/ietf/doc/urls_charter.py b/ietf/doc/urls_charter.py index 9384e5cbc..5bc52770f 100644 --- a/ietf/doc/urls_charter.py +++ b/ietf/doc/urls_charter.py @@ -9,7 +9,8 @@ urlpatterns = patterns('', url(r'^telechat/$', "ietf.doc.views_doc.telechat_date", name='charter_telechat_date'), url(r'^notify/$', "ietf.doc.views_doc.edit_notify", name='charter_edit_notify'), url(r'^ad/$', "ietf.doc.views_charter.edit_ad", name='charter_edit_ad'), - url(r'^(?Paction|review)/$', "ietf.doc.views_charter.announcement_text", name="charter_edit_announcement"), + url(r'^action/$', "ietf.doc.views_charter.action_announcement_text"), + url(r'^review/$', "ietf.doc.views_charter.review_announcement_text"), url(r'^ballotwriteupnotes/$', "ietf.doc.views_charter.ballot_writeupnotes"), url(r'^approve/$', "ietf.doc.views_charter.approve", name='charter_approve'), url(r'^submit/(?:(?P