diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py index 1551abab5..2288472e6 100644 --- a/ietf/doc/expire.py +++ b/ietf/doc/expire.py @@ -10,8 +10,7 @@ from ietf.doc.models import Document, DocEvent, State, save_document_in_history, from ietf.person.models import Person from ietf.meeting.models import Meeting from ietf.doc.utils import add_state_change_event -from ietf.mailtoken.utils import gather_address_list - +from ietf.mailtoken.utils import gather_address_lists def expirable_draft(draft): @@ -71,8 +70,7 @@ def send_expire_warning_for_draft(doc): expiration = doc.expires.date() - to = gather_address_list('doc_expires_soon',doc=doc) - cc = gather_address_list('doc_expires_soon_cc',doc=doc) + (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" @@ -97,8 +95,7 @@ def send_expire_notice_for_draft(doc): state = s.name if s else "I-D Exists" request = None - to = gather_address_list('doc_expired',doc=doc) - cc = gather_address_list('doc_expired_cc',doc=doc) + (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(), diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 97393ef9f..2f0fce9f8 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -14,10 +14,10 @@ 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 +from ietf.mailtoken.utils import gather_address_lists def email_state_changed(request, doc, text, mailtoken_id=None): - to = gather_address_list(mailtoken_id or 'doc_state_edited',doc=doc) + (to,cc) = gather_address_lists(mailtoken_id or 'doc_state_edited',doc=doc) if not to: return @@ -26,7 +26,8 @@ def email_state_changed(request, doc, text, mailtoken_id=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())) + 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""" @@ -35,7 +36,7 @@ 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 = gather_address_list('doc_stream_changed',doc=doc,streams=streams) + (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'] = gather_addresses('doc_pulled_from_rfc_queue_cc',doc=doc) - send_mail(request, gather_address_list('doc_pulled_from_rfc_queue',doc=doc), 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, @@ -112,12 +115,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"), - to=gather_addresses('last_call_issued',doc=doc), - cc=gather_addresses('last_call_issued_cc',doc=doc), + to=addrs.to, + cc=addrs.cc, group=group, docs=[ doc ], urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ], @@ -187,12 +192,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(), - to = gather_addresses('ballot_approved_ietf_stream',doc=doc), - cc = gather_addresses('ballot_approved_ietf_stream_cc',doc=doc), + to = addrs.to, + cc = addrs.cc, doc_type=doc_type, made_by=made_by, contacts=contacts, @@ -205,14 +211,15 @@ def generate_approval_mail_rfc_editor(request, doc): # 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" + 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 = gather_addresses('ballot_approved_conflrev', doc=doc), - cc = gather_addresses('ballot_approved_conflrev_cc', doc=doc), + to = addrs.to, + cc = addrs.cc, ) ) @@ -242,8 +249,7 @@ def generate_publication_request(request, doc): ) def send_last_call_request(request, doc): - to = gather_addresses('last_call_requested',doc=doc) - cc = gather_addresses('last_call_requested_cc',doc=doc) + (to, cc) = gather_address_lists('last_call_requested',doc=doc) frm = '"DraftTracker Mail System" ' send_mail(request, to, frm, @@ -255,7 +261,7 @@ def send_last_call_request(request, doc): cc=cc) def email_resurrect_requested(request, doc, by): - to = gather_address_list('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") @@ -268,20 +274,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): - to = gather_address_list('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())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def email_ballot_deferred(request, doc, by, telechat_date): - to = gather_addresses('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(), @@ -289,10 +297,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 = gather_addresses('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(), @@ -300,7 +309,8 @@ 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() @@ -370,7 +380,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")) @@ -383,7 +393,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 = {} @@ -394,24 +405,24 @@ 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) send_mail(None, - gather_addresses('last_call_expired',doc=doc), + 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 = gather_addresses('last_call_expired_cc',doc=doc) - ) + cc = addrs.cc) def email_stream_state_changed(request, doc, prev_state, new_state, by, comment=""): - recipients = gather_address_list('doc_stream_state_edited',doc=doc) + (to, cc)= gather_address_lists('doc_stream_state_edited',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"%s changed for %s" % (state_type.label, doc.name), 'doc/mail/stream_state_changed_email.txt', dict(doc=doc, @@ -420,13 +431,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=""): - recipients = gather_address_list('doc_stream_state_edited',doc=doc) + (to, cc) = gather_address_lists('doc_stream_state_edited',doc=doc) - 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, @@ -434,32 +446,24 @@ 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") - - 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 }), - }) + 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),) diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index 999dd1f5a..ecaa72c99 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -173,7 +173,7 @@ class EditPositionTests(TestCase): self.assertEqual(len(outbox), mailbox_before + 2) m = outbox[-1] self.assertTrue("iesg@" in m['To']) - self.assertFalse(draft.group.list_email in m['Cc']) + self.assertFalse(m['Cc'] and draft.group.list_email in m['Cc']) class BallotWriteupsTests(TestCase): diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 37cee0b11..91900f0b2 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -18,12 +18,12 @@ from ietf.group.models import Role from ietf.ietfauth.utils import has_role from ietf.utils import draft, markup_txt from ietf.utils.mail import send_mail -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists #TODO FIXME - it would be better if this lived in ietf/doc/mails.py, but there's # an import order issue to work out. def email_update_telechat(request, doc, text): - to = gather_address_list('doc_telechat_details_changed',doc=doc) + (to, cc) = gather_address_lists('doc_telechat_details_changed',doc=doc) if not to: return @@ -33,7 +33,8 @@ def email_update_telechat(request, doc, text): "Telechat update notice: %s" % doc.file_tag(), "doc/mail/update_telechat.txt", dict(text=text, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=cc) def get_state_types(doc): res = [] @@ -457,14 +458,18 @@ def rebuild_reference_relations(doc,filename=None): return ret def set_replaces_for_document(request, doc, new_replaces, by, email_subject, email_comment=""): - to = gather_address_list('doc_replacement_changed',doc=doc) + addrs = gather_address_lists('doc_replacement_changed',doc=doc) + to = set(addrs.to) + cc = set(addrs.cc) relationship = DocRelationshipName.objects.get(slug='replaces') old_replaces = doc.related_that_doc("replaces") for d in old_replaces: if d not in new_replaces: - to.extend(gather_address_list('doc_replacement_changed',doc=d.document)) + other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document) + to.update(other_addrs.to) + cc.update(other_addrs.cc) RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete() if not RelatedDocument.objects.filter(target=d, relationship=relationship): s = 'active' if d.document.expires > datetime.datetime.now() else 'expired' @@ -472,7 +477,9 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema for d in new_replaces: if d not in old_replaces: - to.extend(gather_address_list('doc_replacement_changed',doc=d.document)) + other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document) + to.update(other_addrs.to) + cc.update(other_addrs.cc) RelatedDocument.objects.create(source=doc, target=d, relationship=relationship) d.document.set_state(State.objects.get(type='draft', slug='repl')) @@ -490,17 +497,16 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema if email_comment: email_desc += "\n" + email_comment - to = list(set([addr for addr in to if addr])) - from ietf.doc.mails import html_to_text - send_mail(request, to, + send_mail(request, list(to), "DraftTracker Mail System ", email_subject, "doc/mail/change_notice.txt", dict(text=html_to_text(email_desc), doc=doc, - url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc=list(cc)) def check_common_doc_name_rules(name): """Check common rules for document names for use in forms, throws diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py index b92cde3e4..9c3c7d2f8 100644 --- a/ietf/doc/utils_charter.py +++ b/ietf/doc/utils_charter.py @@ -6,7 +6,7 @@ from django.conf import settings from ietf.doc.models import NewRevisionDocEvent, WriteupDocEvent, BallotPositionDocEvent from ietf.person.models import Person from ietf.utils.history import find_history_active_at -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.utils import gather_address_lists def charter_name_for_group(group): if group.type_id == "rg": @@ -99,6 +99,7 @@ def default_action_text(group, charter, by): else: action = "Rechartered" + addrs = gather_address_lists('ballot_approved_charter',doc=charter,group=group).as_strings(compact=False) e = WriteupDocEvent(doc=charter, by=by) e.by = by e.type = "changed_action_announcement" @@ -112,14 +113,15 @@ def default_action_text(group, charter, by): techadv=group.role_set.filter(name="techadv"), milestones=group.groupmilestone_set.filter(state="charter"), action_type=action, - to=gather_addresses('ballot_approved_charter',doc=charter,group=group), - cc=gather_addresses('ballot_approved_charter_cc',doc=charter,group=group), + to=addrs.to, + cc=addrs.cc, )) e.save() return e def default_review_text(group, charter, by): + addrs=gather_address_lists('charter_external_review',group=group).as_strings(compact=False) e = WriteupDocEvent(doc=charter, by=by) e.by = by e.type = "changed_review_announcement" @@ -134,8 +136,8 @@ def default_review_text(group, charter, by): milestones=group.groupmilestone_set.filter(state="charter"), review_date=(datetime.date.today() + datetime.timedelta(weeks=1)).isoformat(), review_type="new" if group.state_id == "proposed" else "recharter", - to=gather_addresses('charter_external_review',group=group), - cc=gather_addresses('charter_external_review_cc',group=group) + to=addrs.to, + cc=addrs.cc, ) ) e.save() diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 55a10d7e4..3a2fa36d7 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -27,7 +27,7 @@ from ietf.message.utils import infer_message from ietf.name.models import BallotPositionName from ietf.person.models import Person from ietf.utils.mail import send_mail_text, send_mail_preformatted -from ietf.mailtoken.utils import gather_addresses, gather_address_list +from ietf.mailtoken.utils import gather_address_lists BALLOT_CHOICES = (("yes", "Yes"), ("noobj", "No Objection"), @@ -283,19 +283,20 @@ def send_ballot_comment(request, name, ballot_id): blocking_name=blocking_name, settings=settings)) frm = ad.role_email("ad").formatted_email() - to = gather_addresses('ballot_saved',doc=doc) + + addrs = gather_address_lists('ballot_saved',doc=doc) if request.method == 'POST': - cc = gather_address_list('ballot_saved_cc',doc=doc) - explicit_cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()] - if explicit_cc: - cc.extend(explicit_cc) + # The send_ballot_comments form provides an unusual case where the form separates out + # the cc addresses to be edited before sending into a separate field + # TODO: We should consider undoing this, and going back at most to an "extra_cc" model + cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()] if request.POST.get("cc_state_change") and doc.notify: cc.extend(doc.notify.split(',')) if request.POST.get("cc_group_list") and doc.group.list_email: cc.append(doc.group.list_email) - send_mail_text(request, to, frm, subject, body, cc=u", ".join(cc)) + send_mail_text(request, addrs.to, frm, subject, body, cc=u", ".join(cc)) return HttpResponseRedirect(return_to_url) @@ -304,7 +305,8 @@ def send_ballot_comment(request, name, ballot_id): subject=subject, body=body, frm=frm, - to=to, + to=addrs.as_strings().to, + cc=addrs.as_strings().cc, ad=ad, can_send=d or c, back_url=back_url, @@ -701,8 +703,9 @@ def approve_ballot(request, name): send_mail_preformatted(request, announcement) if action == "to_announcement_list": + addrs = gather_address_lists('ballot_approved_ietf_stream_iana').as_strings(compact=False) send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), - override={ "To": gather_addresses('ballot_approved_ietf_stream_iana'), "CC": None, "Bcc": None, "Reply-To": None}) + override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) msg.by = login @@ -743,9 +746,9 @@ def make_last_call(request, name): if form.is_valid(): send_mail_preformatted(request, announcement) if doc.type.slug == 'draft': + addrs = gather_address_lists('last_call_issued_iana',doc=doc).as_strings(compact=False) send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), - override={ "To": gather_addresses('last_call_issued_iana',doc=doc), - "CC": None, "Bcc": None, "Reply-To": None}) + override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) msg.by = login diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py index dbe5e533a..a4fcb0dd9 100644 --- a/ietf/doc/views_conflict_review.py +++ b/ietf/doc/views_conflict_review.py @@ -20,7 +20,7 @@ from ietf.ietfauth.utils import has_role, role_required, is_authorized_in_doc_st from ietf.person.models import Person from ietf.utils.mail import send_mail_preformatted from ietf.utils.textupload import get_cleaned_text_file_content -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.utils import gather_address_lists class ChangeStateForm(forms.Form): review_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev"), label="Conflict review state", empty_label=None, required=True) @@ -87,10 +87,11 @@ def change_state(request, name, option=None): context_instance=RequestContext(request)) def send_conflict_review_started_email(request, review): + addrs = gather_address_lists('conflrev_requested',doc=review).as_strings(compact=False) msg = render_to_string("doc/conflict_review/review_started.txt", dict(frm = settings.DEFAULT_FROM_EMAIL, - to = gather_addresses('conflrev_requested',doc=review), - cc = gather_addresses('conflrev_requested_cc',doc=review), + to = addrs.to, + cc = addrs.cc, by = request.user.person, review = review, reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target.document, @@ -99,10 +100,13 @@ def send_conflict_review_started_email(request, review): ) if not has_role(request.user,"Secretariat"): send_mail_preformatted(request,msg) + + addrs = gather_address_lists('conflrev_requested_iana',doc=review).as_strings(compact=False) email_iana(request, review.relateddocument_set.get(relationship__slug='conflrev').target.document, - gather_addresses('conflrev_requested_iana',doc=review), - msg) + addrs.to, + msg, + cc=addrs.cc) def send_conflict_eval_email(request,review): msg = render_to_string("doc/eval_email.txt", @@ -254,6 +258,7 @@ def default_approval_text(review): receiver = 'IRTF' else: receiver = 'recipient' + addrs = gather_address_lists('ballot_approved_conflrev',doc=review).as_strings(compact=False) text = render_to_string("doc/conflict_review/approval_text.txt", dict(review=review, review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(), @@ -261,8 +266,8 @@ def default_approval_text(review): conflictdoc_url = settings.IDTRACKER_BASE_URL+conflictdoc.get_absolute_url(), receiver=receiver, approved_review = current_text, - to = gather_addresses('ballot_approved_conflrev',doc=review), - cc = gather_addresses('ballot_approved_conflrev_cc',doc=review), + to = addrs.to, + cc = addrs.cc, ) ) diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index f654dd0be..629b0f48b 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -37,7 +37,7 @@ from ietf.person.models import Person, Email from ietf.secr.lib.template import jsonapi from ietf.utils.mail import send_mail, send_mail_message from ietf.utils.textupload import get_cleaned_text_file_content -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.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) @@ -582,10 +582,11 @@ def to_iesg(request,name): doc.save() + addrs= gather_address_lists('pubreq_iesg',doc=doc) extra = {} - extra['Cc'] = gather_addresses('pubreq_iesg_cc',doc=doc) + extra['Cc'] = addrs.as_strings().cc send_mail(request=request, - to = gather_addresses('pubreq_iesg',doc=doc), + to = addrs.to, frm = login.formatted_email(), subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev), template = "doc/submit_to_iesg_email.txt", @@ -1134,7 +1135,7 @@ def request_publication(request, name): m = Message() m.frm = request.user.person.formatted_email() - m.to = gather_addresses('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") @@ -1164,7 +1165,7 @@ def request_publication(request, name): send_mail_message(request, m) # IANA copy - m.to = gather_addresses('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", by=request.user.person) diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py index a40cd21ff..e177d9bd8 100644 --- a/ietf/doc/views_status_change.py +++ b/ietf/doc/views_status_change.py @@ -22,7 +22,7 @@ from ietf.name.models import DocRelationshipName, StdLevelName from ietf.person.models import Person from ietf.utils.mail import send_mail_preformatted from ietf.utils.textupload import get_cleaned_text_file_content -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.utils import gather_address_lists class ChangeStateForm(forms.Form): new_state = forms.ModelChoiceField(State.objects.filter(type="statchg", used=True), label="Status Change Evaluation State", empty_label=None, required=True) @@ -290,7 +290,8 @@ def default_approval_text(status_change,relateddoc): else: action = "Document Action" - + + addrs = gather_address_lists('ballot_approved_status_change',doc=status_change).as_strings(compact=False) text = render_to_string("doc/status_change/approval_text.txt", dict(status_change=status_change, status_change_url = settings.IDTRACKER_BASE_URL+status_change.get_absolute_url(), @@ -299,8 +300,8 @@ def default_approval_text(status_change,relateddoc): approved_text = current_text, action=action, newstatus=newstatus(relateddoc), - to=gather_addresses('ballot_approved_status_change',doc=status_change), - cc=gather_addresses('ballot_approved_status_change_cc',doc=status_change), + to=addrs.to, + cc=addrs.cc, ) ) diff --git a/ietf/group/mails.py b/ietf/group/mails.py index 312e6f2b8..66324dad9 100644 --- a/ietf/group/mails.py +++ b/ietf/group/mails.py @@ -12,10 +12,10 @@ from django.core.urlresolvers import reverse as urlreverse from ietf.utils.mail import send_mail, send_mail_text from ietf.group.models import Group from ietf.group.utils import milestone_reviewer_for_group_type -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists def email_admin_re_charter(request, group, subject, text, mailtoken): - to = gather_address_list(mailtoken,group=group) + (to,cc) = gather_address_lists(mailtoken,group=group) full_subject = u"Regarding %s %s: %s" % (group.type.name, group.acronym, subject) text = strip_tags(text) @@ -25,17 +25,18 @@ def email_admin_re_charter(request, group, subject, text, mailtoken): group=group, group_url=settings.IDTRACKER_BASE_URL + group.about_url(), charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)) if group.charter else "[no charter]", - ) - ) + ), + cc=cc, + ) def email_personnel_change(request, group, text, changed_personnel): - to = gather_address_list('group_personnel_change',group=group,changed_personnel=changed_personnel) + (to, cc) = gather_address_lists('group_personnel_change',group=group,changed_personnel=changed_personnel) full_subject = u"Personnel change for %s working group" % (group.acronym) - send_mail_text(request, to, None, full_subject,text) + send_mail_text(request, to, None, full_subject, text, cc=cc) def email_milestones_changed(request, group, changes): - def wrap_up_email(to, text): + def wrap_up_email(addrs, text): subject = u"Milestones changed for %s %s" % (group.acronym, group.type.name) if re.search("Added .* for review, due",text): @@ -45,26 +46,25 @@ def email_milestones_changed(request, group, changes): text += "\n\n" text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + group.about_url()) - send_mail_text(request, to, None, subject, text) + send_mail_text(request, addrs.to, None, subject, text, cc=addrs.cc) # first send to those who should see any edits (such as management and chairs) - to = gather_address_list('group_milestones_edited',group=group) - if to: - wrap_up_email(to, u"\n\n".join(c + "." for c in changes)) + addrs = gather_address_lists('group_milestones_edited',group=group) + if addrs.to or addrs.cc: + wrap_up_email(addrs, u"\n\n".join(c + "." for c in changes)) # then send only the approved milestones to those who shouldn't be # bothered with milestones pending approval review_re = re.compile("Added .* for review, due") - to = gather_address_list('group_approved_milestones_edited',group=group) + addrs = gather_address_lists('group_approved_milestones_edited',group=group) msg = u"\n\n".join(c + "." for c in changes if not review_re.match(c)) - if to and msg: - wrap_up_email(to, msg) + if (addrs.to or addrs.cc) and msg: + wrap_up_email(addrs, msg) def email_milestone_review_reminder(group, grace_period=7): """Email reminders about milestones needing review to management.""" - to = gather_address_list('milestone_review_reminder',group=group) - cc = gather_address_list('milestone_review_reminder_cc',group=group) + (to, cc) = gather_address_lists('milestone_review_reminder',group=group) if not to: return False @@ -102,7 +102,7 @@ def groups_with_milestones_needing_review(): return Group.objects.filter(groupmilestone__state="review").distinct() def email_milestones_due(group, early_warning_days): - to = gather_address_list('milestones_due_soon',group=group) + (to, cc) = gather_address_lists('milestones_due_soon',group=group) today = datetime.date.today() early_warning = today + datetime.timedelta(days=early_warning_days) @@ -120,7 +120,9 @@ def email_milestones_due(group, early_warning_days): today=today, early_warning_days=early_warning_days, url=settings.IDTRACKER_BASE_URL + group.about_url(), - )) + ), + cc=cc, + ) def groups_needing_milestones_due_reminder(early_warning_days): """Return groups having milestones that are either @@ -129,7 +131,7 @@ def groups_needing_milestones_due_reminder(early_warning_days): return Group.objects.filter(state="active", groupmilestone__due__in=[today, today + datetime.timedelta(days=early_warning_days)], groupmilestone__resolved="", groupmilestone__state="active").distinct() def email_milestones_overdue(group): - to = gather_address_list('milestones_overdue',group=group) + (to, cc) = gather_address_lists('milestones_overdue',group=group) today = datetime.date.today() @@ -145,7 +147,9 @@ def email_milestones_overdue(group): dict(group=group, milestones=milestones, url=settings.IDTRACKER_BASE_URL + group.about_url(), - )) + ), + cc=cc, + ) def groups_needing_milestones_overdue_reminder(grace_period=30): cut_off = datetime.date.today() - datetime.timedelta(days=grace_period) diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py index f448ccd5d..f7b139c73 100644 --- a/ietf/ipr/tests.py +++ b/ietf/ipr/tests.py @@ -15,7 +15,7 @@ from ietf.message.models import Message from ietf.utils.test_utils import TestCase, login_testing_unauthorized from ietf.utils.test_data import make_test_data from ietf.utils.mail import outbox, empty_outbox -from ietf.mailtoken.utils import gather_addresses +from ietf.mailtoken.utils import gather_address_lists class IprTests(TestCase): @@ -537,11 +537,13 @@ I would like to revoke this declaration. self.assertTrue('joe@test.com' in outbox[0]['To']) # test process response uninteresting message + addrs = gather_address_lists('ipr_disclosure_submitted').as_strings() message_string = """To: {} +Cc: {} From: joe@test.com Date: {} Subject: test -""".format(gather_addresses('ipr_disclosure_submitted'),datetime.datetime.now().ctime()) +""".format(addrs.to, addrs.cc, datetime.datetime.now().ctime()) result = process_response_email(message_string) self.assertIsNone(result) diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index 22f406e97..29b5a8a18 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -34,7 +34,7 @@ from ietf.person.models import Person from ietf.secr.utils.document import get_rfc_num, is_draft from ietf.utils.draft_search import normalize_draftname from ietf.utils.mail import send_mail, send_mail_message -from ietf.mailtoken.utils import gather_address_list, gather_addresses +from ietf.mailtoken.utils import gather_address_lists # ---------------------------------------------------------------- # Globals @@ -79,8 +79,7 @@ def get_document_emails(ipr): else: cc_list = get_wg_email_list(doc.group) - to_list = gather_addresses('ipr_posted_on_doc',doc=doc) - cc_list = gather_addresses('ipr_posted_on_doc_cc',doc=doc) + (to_list,cc_list) = gather_address_lists('ipr_posted_on_doc',doc=doc) author_names = ', '.join([a.person.name for a in authors]) context = dict( @@ -99,10 +98,11 @@ def get_posted_emails(ipr): the notify view when a new disclosure is posted""" messages = [] + addrs = gather_address_lists('ipr_posting_confirmation',ipr=ipr).as_strings(compact=False) context = dict( - to_email=gather_addresses('ipr_posting_confirmation',ipr=ipr), + to_email=addrs.to, to_name=ipr.submitter_name, - cc_email=gather_addresses('ipr_posting_confirmation_cc',ipr=ipr), + cc_email=addrs.cc, ipr=ipr) text = render_to_string('ipr/posted_submitter_email.txt',context) messages.append(text) @@ -375,9 +375,10 @@ def email(request, id): else: reply_to = get_reply_to() + addrs = gather_address_lists('ipr_disclosure_followup',ipr=ipr).as_strings(compact=False) initial = { - 'to': gather_addresses('ipr_disclosure_followup',ipr=ipr), - 'cc': gather_addresses('ipr_disclosure_followup_cc',ipr=ipr), + 'to': addrs.to, + 'cc': addrs.cc, 'frm': settings.IPR_EMAIL_FROM, 'subject': 'Regarding {}'.format(ipr.title), 'reply_to': reply_to, @@ -473,11 +474,12 @@ def new(request, type, updates=None): desc="Disclosure Submitted") # send email notification - to = gather_address_list('ipr_disclosure_submitted') + (to, cc) = gather_address_lists('ipr_disclosure_submitted') send_mail(request, to, ('IPR Submitter App', 'ietf-ipr@ietf.org'), 'New IPR Submission Notification', "ipr/new_update_email.txt", - {"ipr": disclosure,}) + {"ipr": disclosure,}, + cc=cc) return render(request, "ipr/submitted.html") diff --git a/ietf/liaisons/mails.py b/ietf/liaisons/mails.py index 49923fb6c..ab330f773 100644 --- a/ietf/liaisons/mails.py +++ b/ietf/liaisons/mails.py @@ -7,13 +7,12 @@ from django.core.urlresolvers import reverse as urlreverse from ietf.utils.mail import send_mail_text from ietf.liaisons.utils import role_persons_with_fixed_email from ietf.group.models import Role -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists def send_liaison_by_email(request, liaison): subject = u'New Liaison Statement, "%s"' % (liaison.title) from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = gather_address_list('liaison_statement_posted',liaison=liaison) - cc = gather_address_list('liaison_statement_posted_cc',liaison=liaison) + (to_email, cc) = gather_address_lists('liaison_statement_posted',liaison=liaison) bcc = ['statements@ietf.org'] body = render_to_string('liaisons/liaison_mail.txt', dict( liaison=liaison, @@ -39,14 +38,13 @@ def notify_pending_by_email(request, liaison): # to_email.append('%s <%s>' % person.email()) subject = u'New Liaison Statement, "%s" needs your approval' % (liaison.title) from_email = settings.LIAISON_UNIVERSAL_FROM - to = gather_address_list('liaison_approval_requested',liaison=liaison) + (to, cc) = gather_address_lists('liaison_approval_requested',liaison=liaison) body = render_to_string('liaisons/pending_liaison_mail.txt', dict( liaison=liaison, url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)), referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None, )) - # send_mail_text(request, to_email, from_email, subject, body) - send_mail_text(request, to, from_email, subject, body) + send_mail_text(request, to, from_email, subject, body, cc=cc) def send_sdo_reminder(sdo): roles = Role.objects.filter(name="liaiman", group=sdo) @@ -56,7 +54,7 @@ def send_sdo_reminder(sdo): manager_role = roles[0] subject = 'Request for update of list of authorized individuals' - to_email = gather_address_list('liaison_manager_update_request',group=sdo) + (to_email,cc) = gather_address_lists('liaison_manager_update_request',group=sdo) name = manager_role.person.plain_name() authorized_list = role_persons_with_fixed_email(sdo, "auth") @@ -66,7 +64,7 @@ def send_sdo_reminder(sdo): individuals=authorized_list, )) - send_mail_text(None, to_email, settings.LIAISON_UNIVERSAL_FROM, subject, body) + send_mail_text(None, to_email, settings.LIAISON_UNIVERSAL_FROM, subject, body, cc=cc) return body @@ -93,8 +91,7 @@ def possibly_send_deadline_reminder(liaison): days_msg = 'expires %s' % PREVIOUS_DAYS[days_to_go] from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = gather_address_list('liaison_deadline_soon',liaison=liaison) - cc = gather_address_list('liaison_deadline_soon_cc',liaison=liaison) + (to_email, cc) = gather_address_lists('liaison_deadline_soon',liaison=liaison) bcc = 'statements@ietf.org' body = render_to_string('liaisons/liaison_deadline_mail.txt', dict(liaison=liaison, diff --git a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py index 984c8b1a9..d1afb3a1c 100644 --- a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py +++ b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py @@ -79,7 +79,7 @@ def make_recipients(apps): rc(slug='conflict_review_stream_manager', desc="The stream manager of a document being reviewed for IETF stream conflicts", - template = None ) + template=None ) rc(slug='conflict_review_steering_group', desc="The steering group (e.g. IRSG) of a document being reviewed for IETF stream conflicts", @@ -165,6 +165,10 @@ def make_recipients(apps): desc="The session request ticketing system", template='') + rc(slug='session_requester', + desc="The person that requested a meeting slot for a given group", + template=None) + rc(slug='logged_in_person', desc="The person currently logged into the datatracker who initiated a given action", template='{% if person and person.email_address %}<{{ person.email_address }}>{% endif %}') @@ -241,6 +245,17 @@ def make_mailtokens(apps): MailToken=apps.get_model('mailtoken','MailToken') def mt_factory(slug,desc,to_slugs,cc_slugs=[]): + + # Try to protect ourselves from typos + all_slugs = to_slugs[:] + all_slugs.extend(cc_slugs) + for recipient_slug in all_slugs: + try: + Recipient.objects.get(slug=recipient_slug) + except Recipient.DoesNotExist: + print "****Some rule tried to use",recipient_slug + raise + m = MailToken.objects.create(slug=slug, desc=desc) m.to = Recipient.objects.filter(slug__in=to_slugs) m.cc = Recipient.objects.filter(slug__in=cc_slugs) @@ -325,7 +340,7 @@ def make_mailtokens(apps): cc_slugs=['iesg', 'rfc_editor', 'doc_notify', - 'doc_affectddoc_authors', + 'doc_affecteddoc_authors', 'doc_affecteddoc_group_chairs', 'doc_affecteddoc_notify', ], @@ -347,7 +362,7 @@ def make_mailtokens(apps): 'doc_shepherd', 'doc_authors', 'doc_notify', - 'doc_group_list_email', + 'doc_group_mail_list', 'doc_group_chairs', 'doc_affecteddoc_authors', 'doc_affecteddoc_group_chairs', @@ -577,7 +592,7 @@ def make_mailtokens(apps): desc="Recipients for notification of a new version of an existing document", to_slugs=['doc_notify', 'doc_ad', - 'non_ietf_stream_manager', + 'doc_non_ietf_stream_manager', 'rfc_editor_if_doc_in_queue', 'doc_discussing_ads', ]) @@ -744,6 +759,16 @@ def make_mailtokens(apps): "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', + ]) + + def forward(apps, schema_editor): make_recipients(apps) diff --git a/ietf/mailtoken/models.py b/ietf/mailtoken/models.py index ee393a830..83bb732a7 100644 --- a/ietf/mailtoken/models.py +++ b/ietf/mailtoken/models.py @@ -252,3 +252,10 @@ class Recipient(models.Model): group=kwargs['group'] addrs.extend(group.role_set.filter(name='liaiman').values_list('email__address',flat=True)) return addrs + + def gather_session_requester(self, **kwargs): + addrs=[] + if 'session' in kwargs: + session = kwargs['session'] + addrs.append(session.requested_by.role_email('chair').address) + return addrs diff --git a/ietf/mailtoken/utils.py b/ietf/mailtoken/utils.py index d469e730f..a3cdca85b 100644 --- a/ietf/mailtoken/utils.py +++ b/ietf/mailtoken/utils.py @@ -1,22 +1,32 @@ +from collections import namedtuple from ietf.mailtoken.models import MailToken, Recipient -def gather_address_list(slug,**kwargs): - - addrs = [] +class AddrLists(namedtuple('AddrLists',['to','cc'])): - if slug.endswith('_cc'): - mailtoken = MailToken.objects.get(slug=slug[:-3]) - for recipient in mailtoken.cc.all(): - addrs.extend(recipient.gather(**kwargs)) - else: - mailtoken = MailToken.objects.get(slug=slug) - for recipient in mailtoken.to.all(): - addrs.extend(recipient.gather(**kwargs)) + __slots__ = () - return list(set([addr for addr in addrs if addr])) + def as_strings(self,compact=True): -def gather_addresses(slug,**kwargs): - return ",\n ".join(gather_address_list(slug,**kwargs)) + separator = ", " if compact else ",\n " + to_string = separator.join(self.to) + cc_string = separator.join(self.cc) + + return namedtuple('AddrListsAsStrings',['to','cc'])(to=to_string,cc=cc_string) + +def gather_address_lists(slug, **kwargs): + mailtoken = MailToken.objects.get(slug=slug) + + to = set() + for recipient in mailtoken.to.all(): + to.update(recipient.gather(**kwargs)) + to.discard('') + + cc = set() + for recipient in mailtoken.cc.all(): + cc.update(recipient.gather(**kwargs)) + cc.discard('') + + return AddrLists(to=list(to),cc=list(cc)) def get_base_ipr_request_address(): return Recipient.objects.get(slug='ipr_requests').gather()[0] diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index 1a7bb6446..805841d6d 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -4719,6 +4719,14 @@ "model": "mailtoken.recipient", "pk": "rfc_editor_if_doc_in_queue" }, +{ + "fields": { + "template": null, + "desc": "The person that requested a meeting slot for a given group" + }, + "model": "mailtoken.recipient", + "pk": "session_requester" +}, { "fields": { "template": "", @@ -4845,6 +4853,7 @@ { "fields": { "cc": [ + "doc_affecteddoc_authors", "doc_affecteddoc_group_chairs", "doc_affecteddoc_notify", "doc_notify", @@ -5034,6 +5043,20 @@ "model": "mailtoken.mailtoken", "pk": "doc_replacement_changed" }, +{ + "fields": { + "cc": [], + "to": [ + "doc_group_chairs", + "doc_group_responsible_directors", + "doc_non_ietf_stream_manager", + "iesg_secretary" + ], + "desc": "Recipients for suggestions that this doc replaces or is replace by some other document" + }, + "model": "mailtoken.mailtoken", + "pk": "doc_replacement_suggested" +}, { "fields": { "cc": [], @@ -5220,6 +5243,7 @@ "doc_affecteddoc_notify", "doc_authors", "doc_group_chairs", + "doc_group_mail_list", "doc_notify", "doc_shepherd" ], @@ -5564,7 +5588,8 @@ "group_responsible_directors" ], "to": [ - "group_chairs" + "group_chairs", + "session_requester" ], "desc": "Recipients for details when a session has been scheduled" }, @@ -5650,6 +5675,7 @@ "to": [ "doc_ad", "doc_discussing_ads", + "doc_non_ietf_stream_manager", "doc_notify", "rfc_editor_if_doc_in_queue" ], diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index 7be1e6322..0c571733f 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -20,7 +20,7 @@ from ietf.person.models import Email from ietf.person.fields import SearchableEmailField from ietf.utils.fields import MultiEmailField from ietf.utils.mail import send_mail -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None) @@ -408,12 +408,12 @@ class NominateForm(BaseNomcomForm, forms.ModelForm): if author: subject = 'Nomination receipt' from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomination_receipt_requested',nominator=author.address) + (to_email, cc) = gather_address_lists('nomination_receipt_requested',nominator=author.address) context = {'nominee': nominee.email.person.name, 'comments': comments, 'position': position.name} path = nomcom_template_path + NOMINATION_RECEIPT_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) + send_mail(None, to_email, from_email, subject, path, context, cc=cc) return nomination @@ -526,12 +526,12 @@ class FeedbackForm(BaseNomcomForm, forms.ModelForm): if author: subject = "NomCom comment confirmation" from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomcom_comment_receipt_requested',commenter=author.address) + (to_email, cc) = gather_address_lists('nomcom_comment_receipt_requested',commenter=author.address) context = {'nominee': self.nominee.email.person.name, 'comments': comments, 'position': self.position.name} path = nomcom_template_path + FEEDBACK_RECEIPT_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) + send_mail(None, to_email, from_email, subject, path, context, cc=cc) class Meta: model = Feedback diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index b5bf08d0c..83ed384e7 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -18,7 +18,7 @@ from django.utils.encoding import smart_str from ietf.dbtemplate.models import DBTemplate from ietf.person.models import Email, Person -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists from ietf.utils.pipe import pipe from ietf.utils import unaccent from ietf.utils.mail import send_mail_text, send_mail @@ -208,7 +208,7 @@ def send_accept_reminder_to_nominee(nominee_position): nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym mail_path = nomcom_template_path + NOMINEE_ACCEPT_REMINDER_TEMPLATE nominee = nominee_position.nominee - to_email = gather_address_list('nomination_accept_reminder',nominee=nominee.email.address) + (to_email, cc) = gather_address_lists('nomination_accept_reminder',nominee=nominee.email.address) hash = get_hash_nominee_position(today, nominee_position.id) accept_url = reverse('nomcom_process_nomination_status', @@ -234,7 +234,7 @@ def send_accept_reminder_to_nominee(nominee_position): body = render_to_string(mail_path, context) path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE) body += '\n\n%s' % render_to_string(path, context) - send_mail_text(None, to_email, from_email, subject, body) + send_mail_text(None, to_email, from_email, subject, body, cc=cc) def send_questionnaire_reminder_to_nominee(nominee_position): subject = 'Reminder: please complete the Nomcom questionnaires for your nomination.' @@ -245,7 +245,7 @@ def send_questionnaire_reminder_to_nominee(nominee_position): nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym mail_path = nomcom_template_path + NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE nominee = nominee_position.nominee - to_email = gather_address_list('nomcom_questionnaire_reminder',nominee=nominee.email.address) + (to_email,cc) = gather_address_lists('nomcom_questionnaire_reminder',nominee=nominee.email.address) context = {'nominee': nominee, 'position': position, @@ -254,7 +254,7 @@ def send_questionnaire_reminder_to_nominee(nominee_position): body = render_to_string(mail_path, context) path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE) body += '\n\n%s' % render_to_string(path, context) - send_mail_text(None, to_email, from_email, subject, body) + send_mail_text(None, to_email, from_email, subject, body, cc=cc) def send_reminder_to_nominees(nominees,type): addrs = [] @@ -295,18 +295,18 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut # send email to secretariat and nomcomchair to warn about the new person subject = 'New person is created' from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomination_created_person',nomcom=nomcom) + (to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom) context = {'email': email.address, 'fullname': email.person.name, 'person_id': email.person.id} path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) + send_mail(None, to_email, from_email, subject, path, context, cc=cc) if nominee_position_created: # send email to nominee subject = 'IETF Nomination Information' from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomination_new_nominee',nominee=email.address) + (to_email, cc) = gather_address_lists('nomination_new_nominee',nominee=email.address) domain = Site.objects.get_current().domain today = datetime.date.today().strftime('%Y%m%d') hash = get_hash_nominee_position(today, nominee_position.id) @@ -332,13 +332,13 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut 'decline_url': decline_url} path = nomcom_template_path + NOMINEE_EMAIL_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) + send_mail(None, to_email, from_email, subject, path, context, cc=cc) # send email to nominee with questionnaire if nomcom.send_questionnaire: subject = '%s Questionnaire' % position from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomcom_questionnaire',nominee=email.address) + (to_email, cc) = gather_address_lists('nomcom_questionnaire',nominee=email.address) context = {'nominee': email.person.name, 'position': position.name} path = '%s%d/%s' % (nomcom_template_path, @@ -347,12 +347,12 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE) body += '\n\n%s' % render_to_string(path, context) - send_mail_text(None, to_email, from_email, subject, body) + send_mail_text(None, to_email, from_email, subject, body, cc=cc) # send emails to nomcom chair subject = 'Nomination Information' from_email = settings.NOMCOM_FROM_EMAIL - to_email = gather_address_list('nomination_received',nomcom=nomcom) + (to_email, cc) = gather_address_lists('nomination_received',nomcom=nomcom) context = {'nominee': email.person.name, 'nominee_email': email.address, 'position': position.name} @@ -365,7 +365,7 @@ def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, aut 'nominator_email': ''}) path = nomcom_template_path + NOMINATION_EMAIL_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) + send_mail(None, to_email, from_email, subject, path, context, cc=cc) return nominee diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index 827298278..4a7ef84d3 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -26,7 +26,7 @@ from ietf.secr.proceedings.views import build_choices, handle_upload_file, make_ from ietf.secr.sreq.forms import GroupSelectForm from ietf.secr.sreq.views import get_initial_session from ietf.secr.utils.meeting import get_session, get_timeslot -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists # prep for agenda changes @@ -188,8 +188,7 @@ def send_notifications(meeting, groups, person): now = datetime.datetime.now() for group in groups: sessions = group.session_set.filter(meeting=meeting) - to_email = gather_address_list('session_scheduled',group=group,person=sessions[0].requested_by) - cc_list = gather_address_list('session_scheduled_cc',group=group,person=sessions[0].requested_by) + addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0]) from_email = ('"IETF Secretariat"','agenda@ietf.org') if len(sessions) == 1: subject = '%s - Requested session has been scheduled for IETF %s' % (group.acronym, meeting.number) @@ -222,12 +221,12 @@ def send_notifications(meeting, groups, person): context['login'] = sessions[0].requested_by send_mail(None, - to_email, + addrs.to, from_email, subject, template, context, - cc=cc_list) + cc=addrs.cc) # create sent_notification event GroupEvent.objects.create(group=group,time=now,type='sent_notification', diff --git a/ietf/secr/sreq/views.py b/ietf/secr/sreq/views.py index 2f3ad1e1a..f960c30f0 100644 --- a/ietf/secr/sreq/views.py +++ b/ietf/secr/sreq/views.py @@ -16,7 +16,7 @@ from ietf.secr.utils.decorators import check_permissions from ietf.secr.utils.group import groups_by_session from ietf.utils.mail import send_mail from ietf.person.models import Person -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists # ------------------------------------------------- # Globals @@ -113,8 +113,7 @@ def send_notification(group,meeting,login,session,action): session argument is a dictionary of fields from the session request form action argument is a string [new|update]. ''' - to_email = gather_address_list('session_requested',group=group,person=login) - cc_list = gather_address_list('session_requested_cc',group=group,person=login) + (to_email, cc_list) = gather_address_lists('session_requested',group=group,person=login) from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org') subject = '%s - New Meeting Session Request for IETF %s' % (group.acronym, meeting.number) template = 'sreq/session_request_notification.txt' @@ -137,8 +136,7 @@ def send_notification(group,meeting,login,session,action): # change headers TO=ADs, CC=session-request, submitter and cochairs if session.get('length_session3',None): context['session']['num_session'] = 3 - to_email = gather_address_list('session_requested_long',group=group,person=login) - cc_list = gather_address_list('session_requested_long_cc',group=group,person=login) + (to_email, cc_list) = gather_address_lists('session_requested_long',group=group,person=login) subject = '%s - Request for meeting session approval for IETF %s' % (group.acronym, meeting.number) template = 'sreq/session_approval_notification.txt' #status_text = 'the %s Directors for approval' % group.parent @@ -209,8 +207,7 @@ def cancel(request, acronym): session.scheduledsession_set.all().delete() # send notifitcation - to_email = gather_address_list('session_request_cancelled',group=group,person=login) - cc_list = gather_address_list('session_request_cancelled_cc',group=group,person=login) + (to_email, cc_list) = gather_address_lists('session_request_cancelled',group=group,person=login) from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org') subject = '%s - Cancelling a meeting request for IETF %s' % (group.acronym, meeting.number) send_mail(request, to_email, from_email, subject, 'sreq/session_cancel_notification.txt', @@ -626,8 +623,7 @@ def no_session(request, acronym): session_save(session) # send notification - to_email = gather_address_list('session_request_not_meeting',group=group,person=login) - cc_list = gather_address_list('session_request_not_meeting_cc',group=group,person=login) + (to_email, cc_list) = gather_address_lists('session_request_not_meeting',group=group,person=login) from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org') subject = '%s - Not having a session at IETF %s' % (group.acronym, meeting.number) send_mail(request, to_email, from_email, subject, 'sreq/not_meeting_notification.txt', diff --git a/ietf/submit/mail.py b/ietf/submit/mail.py index d6148df1b..2939b33f8 100644 --- a/ietf/submit/mail.py +++ b/ietf/submit/mail.py @@ -8,56 +8,66 @@ from ietf.doc.models import Document from ietf.person.models import Person from ietf.message.models import Message from ietf.utils.accesstoken import generate_access_token -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists def send_submission_confirmation(request, submission): subject = 'Confirm submission of I-D %s' % submission.name from_email = settings.IDSUBMIT_FROM_EMAIL - to_email = gather_address_list('sub_confirmation_requested',submission=submission) + (to_email, cc) = gather_address_lists('sub_confirmation_requested',submission=submission) confirm_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_confirm_submission', kwargs=dict(submission_id=submission.pk, auth_token=generate_access_token(submission.auth_key))) status_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token())) - send_mail(request, to_email, from_email, subject, 'submit/confirm_submission.txt', { - 'submission': submission, - 'confirm_url': confirm_url, - 'status_url': status_url, - }) + send_mail(request, to_email, from_email, subject, 'submit/confirm_submission.txt', + { + 'submission': submission, + 'confirm_url': confirm_url, + 'status_url': status_url, + }, + cc=cc) - return to_email + all_addrs = to_email + all_addrs.extend(cc) + return all_addrs def send_full_url(request, submission): subject = 'Full URL for managing submission of draft %s' % submission.name from_email = settings.IDSUBMIT_FROM_EMAIL - to_email = gather_address_list('sub_management_url_requested',submission=submission) + (to_email, cc) = gather_address_lists('sub_management_url_requested',submission=submission) url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token())) - send_mail(request, to_email, from_email, subject, 'submit/full_url.txt', { - 'submission': submission, - 'url': url, - }) + send_mail(request, to_email, from_email, subject, 'submit/full_url.txt', + { + 'submission': submission, + 'url': url, + }, + cc=cc) - return to_email + all_addrs = to_email + all_addrs.extend(cc) + return all_addrs def send_approval_request_to_group(request, submission): subject = 'New draft waiting for approval: %s' % submission.name from_email = settings.IDSUBMIT_FROM_EMAIL - to_email = gather_address_list('sub_chair_approval_requested',submission=submission) + (to_email,cc) = gather_address_lists('sub_chair_approval_requested',submission=submission) if not to_email: return to_email - send_mail(request, to_email, from_email, subject, 'submit/approval_request.txt', { - 'submission': submission, - 'domain': Site.objects.get_current().domain, - }) - - return to_email + send_mail(request, to_email, from_email, subject, 'submit/approval_request.txt', + { + 'submission': submission, + 'domain': Site.objects.get_current().domain, + }, + cc=cc) + all_addrs = to_email + all_addrs.extend(cc) + return all_addrs def send_manual_post_request(request, submission, errors): subject = u'Manual Post Requested for %s' % submission.name from_email = settings.IDSUBMIT_FROM_EMAIL - to_email = gather_address_list('sub_manual_post_requested',submission=submission) - cc = gather_address_list('sub_manual_post_requested_cc',submission=submission) + (to_email,cc) = gather_address_lists('sub_manual_post_requested',submission=submission) send_mail(request, to_email, from_email, subject, 'submit/manual_post_request.txt', { 'submission': submission, 'url': settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status', kwargs=dict(submission_id=submission.pk)), @@ -75,8 +85,7 @@ def announce_to_lists(request, submission): pass m.subject = 'I-D Action: %s-%s.txt' % (submission.name, submission.rev) m.frm = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL - m.to = gather_address_list('sub_announced',submission=submission) - m.cc = gather_address_list('sub_announced_cc',submission=submission) + (m.to, m.cc) = gather_address_lists('sub_announced',submission=submission) m.body = render_to_string('submit/announce_to_lists.txt', dict(submission=submission, settings=settings)) @@ -87,17 +96,18 @@ def announce_to_lists(request, submission): def announce_new_version(request, submission, draft, state_change_msg): - to_email = gather_address_list('sub_new_version',doc=draft,submission=submission) + (to_email,cc) = gather_address_lists('sub_new_version',doc=draft,submission=submission) if to_email: subject = 'New Version Notification - %s-%s.txt' % (submission.name, submission.rev) from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt', {'submission': submission, - 'msg': state_change_msg}) + 'msg': state_change_msg}, + cc=cc) def announce_to_authors(request, submission): - to_email = gather_address_list('sub_announced_to_authors',submission=submission) + (to_email, cc) = gather_address_lists('sub_announced_to_authors',submission=submission) from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL subject = 'New Version Notification for %s-%s.txt' % (submission.name, submission.rev) if submission.group: @@ -108,4 +118,5 @@ def announce_to_authors(request, submission): group = 'Individual Submission' send_mail(request, to_email, from_email, subject, 'submit/announce_to_authors.txt', {'submission': submission, - 'group': group}) + 'group': group}, + cc=cc) diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 1e12a62dc..5cff51526 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -221,8 +221,8 @@ class SubmitTests(TestCase): self.assertTrue("review" in outbox[-1]["Subject"].lower()) self.assertTrue(name in unicode(outbox[-1])) self.assertTrue(sug_replaced_alias.name in unicode(outbox[-1])) - self.assertTrue("ameschairman" in outbox[-1]["To"].lower()) - self.assertTrue("marschairman" in outbox[-1]["To"].lower()) + self.assertTrue("ames-chairs@" in outbox[-1]["To"].lower()) + self.assertTrue("mars-chairs@" in outbox[-1]["To"].lower()) def test_submit_new_wg_txt(self): self.submit_new_wg(["txt"]) diff --git a/ietf/submit/views.py b/ietf/submit/views.py index caf0a55e3..6c5325bdb 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -23,7 +23,7 @@ from ietf.submit.utils import check_idnits, found_idnits, validate_submission, c from ietf.submit.utils import post_submission, cancel_submission, rename_submission_files from ietf.utils.accesstoken import generate_random_key, generate_access_token from ietf.utils.draft import Draft -from ietf.mailtoken.utils import gather_address_list +from ietf.mailtoken.utils import gather_address_lists def upload_submission(request): @@ -181,7 +181,9 @@ def submission_status(request, submission_id, access_token=None): can_force_post = is_secretariat and submission.state.next_states.filter(slug="posted") show_send_full_url = not key_matched and not is_secretariat and submission.state_id not in ("cancel", "posted") - confirmation_list = gather_address_list('sub_confirmation_requested',submission=submission) + addrs = gather_address_lists('sub_confirmation_requested',submission=submission) + confirmation_list = addrs.to + confirmation_list.extend(addrs.cc) requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())