# generation of mails import textwrap, datetime from django.template.loader import render_to_string from django.utils.html import strip_tags from django.conf import settings from django.core.urlresolvers import reverse as urlreverse from ietf.utils.mail import send_mail, send_mail_text from ietf.ipr.utils import iprs_from_docs, related_docs from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent from ietf.doc.utils import needed_ballot_positions from ietf.person.models import Person from ietf.group.models import Role from ietf.doc.models import Document from ietf.mailtoken.utils import gather_addresses, gather_address_list def email_state_changed(request, doc, text, mailtoken_id=None): to = gather_address_list(mailtoken_id or 'doc_state_edited',doc=doc) if not to: return text = strip_tags(text) send_mail(request, to, None, "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())) 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 = [] if old_stream: streams.append(old_stream.slug) if new_stream: streams.append(new_stream.slug) to = gather_address_list('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 = 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())) def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state): extra=extra_automation_headers(doc) extra['Cc'] = gather_addresses('doc_pulled_from_rfc_queue_cc',doc=doc) send_mail(request, gather_address_list('doc_pulled_from_rfc_queue',doc=doc), 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 html_to_text(html): return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace("
", "\n")) #TODO Expunge this 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 "" e = WriteupDocEvent() e.type = "changed_ballot_writeup_text" e.by = request.user.person e.doc = doc e.desc = u"Ballot writeup was generated" e.text = unicode(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana})) 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 WG (%s)" % (doc.group.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()))) 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 ] else: ipr_links = None 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=gather_addresses('last_call_issued',doc=doc), cc=gather_addresses('last_call_issued_cc',doc=doc), group=group, docs=[ doc ], urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], ipr_links=ipr_links, ) ) e = WriteupDocEvent() e.type = "changed_last_call_text" e.by = request.user.person e.doc = doc e.desc = u"Last call announcement was generated" e.text = unicode(mail) e.save() 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'): mail = generate_approval_mail_rfc_editor(request, doc) else: mail = generate_approval_mail_approved(request, doc) e = WriteupDocEvent() e.type = "changed_ballot_approval_text" e.by = request.user.person e.doc = doc e.desc = u"Ballot approval text was generated" e.text = unicode(mail) e.save() return e def generate_approval_mail_approved(request, doc): if doc.intended_std_level_id in ("std", "ds", "ps", "bcp"): action_type = "Protocol" else: action_type = "Document" # 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" else: doc.group.name_with_wg = doc.group.name 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]+"." else: contacts = "The IESG contact person is %s." % responsible_directors[0] doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft" 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 = gather_addresses('ballot_approved_ietf_stream',doc=doc), cc = gather_addresses('ballot_approved_ietf_stream_cc',doc=doc), doc_type=doc_type, made_by=made_by, contacts=contacts, action_type=action_type, ) ) def generate_approval_mail_rfc_editor(request, doc): 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" 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 = gather_addresses('ballot_approved_conflrev', doc=doc), cc = gather_addresses('ballot_approved_conflrev_cc', doc=doc), ) ) def generate_publication_request(request, doc): group_description = "" if doc.group and doc.group.acronym != "none": group_description = doc.group.name_with_acronym() e = doc.latest_event(ConsensusDocEvent, type="changed_consensus") consensus = e.consensus if e else None if doc.stream_id == "irtf": approving_body = "IRSG" consensus_body = doc.group.acronym.upper() else: approving_body = str(doc.stream) consensus_body = approving_body 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, ) ) def send_last_call_request(request, doc): to = gather_addresses('last_call_requested',doc=doc) cc = gather_addresses('last_call_requested_cc',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) def email_resurrect_requested(request, doc, by): to = gather_address_list('resurrection_requested',doc=doc) if by.role_set.filter(name="secr", group__acronym="secretariat"): e = by.role_email("secr", group="secretariat") else: 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())) def email_resurrection_completed(request, doc, requester): to = gather_address_list('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())) def email_ballot_deferred(request, doc, by, telechat_date): to = gather_addresses('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)) def email_ballot_undeferred(request, doc, by, telechat_date): to = gather_addresses('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)) 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() ), ) ) def email_iana(request, doc, to, msg): # 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")) extra = {} extra["Reply-To"] = "noreply@ietf.org" extra["X-IETF-Draft-string"] = doc.name extra["X-IETF-Draft-revision"] = doc.rev send_mail_text(request, "IANA <%s>" % to, parsed_msg["From"], parsed_msg["Subject"], parsed_msg.get_payload(), extra=extra) def extra_automation_headers(doc): extra = {} extra["X-IETF-Draft-string"] = doc.name extra["X-IETF-Draft-revision"] = doc.rev return extra 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 send_mail(None, gather_addresses('last_call_expired',doc=doc), "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 = gather_addresses('last_call_expired_cc',doc=doc) ) def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""): recipients = gather_address_list('doc_stream_state_edited',doc=doc) state_type = (prev_state or new_state).type send_mail(request, recipients, 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)) def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment=""): recipients = gather_address_list('doc_stream_state_edited',doc=doc) send_mail(request, recipients, 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)) 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 ") 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) if not to_email: to_email.append("internet-drafts@ietf.org") 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 }), })