From 5db9e0d6a80ab76309e72b03764977170c76fcf5 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 19 Aug 2015 19:53:09 +0000 Subject: [PATCH] checkpoint - Legacy-Id: 10029 --- ietf/ipr/mail.py | 2 + ietf/ipr/models.py | 12 ++ ietf/ipr/tests.py | 2 +- ietf/ipr/views.py | 27 ++- ietf/liaisons/mails.py | 22 +-- .../migrations/0002_auto_20150809_1314.py | 104 +++++++++- ietf/mailtoken/models.py | 45 +++++ ietf/name/fixtures/names.json | 186 ++++++++++++++++++ ietf/secr/utils/mail.py | 34 ---- 9 files changed, 367 insertions(+), 67 deletions(-) delete mode 100644 ietf/secr/utils/mail.py diff --git a/ietf/ipr/mail.py b/ietf/ipr/mail.py index 98aa16b3e..abb1338a1 100644 --- a/ietf/ipr/mail.py +++ b/ietf/ipr/mail.py @@ -129,6 +129,8 @@ def get_update_submitter_emails(ipr): else: email_to_iprs[email] = [related.target] + # TODO: This has not been converted to use mailtoken. It is complicated. + # When converting it, it will need something like ipr_submitter_ietfer_or_holder perhaps for email in email_to_iprs: context = dict( to_email=email, diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py index 79d91c58a..355bb6dec 100644 --- a/ietf/ipr/models.py +++ b/ietf/ipr/models.py @@ -92,6 +92,18 @@ class IprDisclosureBase(models.Model): else: return None + def recursively_updates(self,disc_set=None): + """Returns the set of disclosures updated directly or transitively by this disclosure""" + if disc_set == None: + disc_set = set() + new_candidates = set([y.target.get_child() for y in self.updates]) + unseen = new_candidates - disc_set + disc_set.update(unseen) + for disc in unseen: + disc_set.update(disc.recursively_updates(disc_set)) + return disc_set + + class HolderIprDisclosure(IprDisclosureBase): ietfer_name = models.CharField(max_length=255, blank=True) # "Whose Personal Belief Triggered..." ietfer_contact_email = models.EmailField(blank=True) diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py index 907b920d1..fa3f3d94b 100644 --- a/ietf/ipr/tests.py +++ b/ietf/ipr/tests.py @@ -489,7 +489,7 @@ I would like to revoke this declaration. self.assertEqual(r.status_code,302) self.assertEqual(len(outbox),len_before+2) self.assertTrue('george@acme.com' in outbox[len_before]['To']) - self.assertTrue('aread@ietf.org' in outbox[len_before+1]['To']) + self.assertTrue('draft-ietf-mars-test@ietf.org' in outbox[len_before+1]['To']) self.assertTrue('mars-wg@ietf.org' in outbox[len_before+1]['Cc']) def test_process_response_email(self): diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index a7b11c2ef..22f406e97 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -16,8 +16,7 @@ from django.template.loader import render_to_string from ietf.doc.models import DocAlias from ietf.group.models import Role, Group from ietf.ietfauth.utils import role_required, has_role -from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails, - get_update_cc_addrs) +from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails) from ietf.ipr.fields import select2_id_ipr_title_json from ietf.ipr.forms import (HolderIprDisclosureForm, GenericDisclosureForm, ThirdPartyIprDisclosureForm, DraftForm, SearchForm, MessageModelForm, @@ -80,13 +79,13 @@ def get_document_emails(ipr): else: cc_list = get_wg_email_list(doc.group) - author_emails = ','.join([a.address for a in authors]) + to_list = gather_addresses('ipr_posted_on_doc',doc=doc) + cc_list = gather_addresses('ipr_posted_on_doc_cc',doc=doc) author_names = ', '.join([a.person.name for a in authors]) - cc_list += ", ipr-announce@ietf.org" context = dict( doc_info=doc_info, - to_email=author_emails, + to_email=to_list, to_name=author_names, cc_email=cc_list, ipr=ipr) @@ -99,16 +98,14 @@ def get_posted_emails(ipr): """Return a list of messages suitable to initialize a NotifyFormset for the notify view when a new disclosure is posted""" messages = [] - # NOTE 1000+ legacy iprs have no submitter_email - # add submitter message - if True: - context = dict( - to_email=ipr.submitter_email, - to_name=ipr.submitter_name, - cc_email=get_update_cc_addrs(ipr), - ipr=ipr) - text = render_to_string('ipr/posted_submitter_email.txt',context) - messages.append(text) + + context = dict( + to_email=gather_addresses('ipr_posting_confirmation',ipr=ipr), + to_name=ipr.submitter_name, + cc_email=gather_addresses('ipr_posting_confirmation_cc',ipr=ipr), + ipr=ipr) + text = render_to_string('ipr/posted_submitter_email.txt',context) + messages.append(text) # add email to related document authors / parties if ipr.iprdocrel_set.all(): diff --git a/ietf/liaisons/mails.py b/ietf/liaisons/mails.py index f2d9b9a89..49923fb6c 100644 --- a/ietf/liaisons/mails.py +++ b/ietf/liaisons/mails.py @@ -7,16 +7,13 @@ 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 def send_liaison_by_email(request, liaison): subject = u'New Liaison Statement, "%s"' % (liaison.title) from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = liaison.to_contact.split(',') - cc = liaison.cc.split(',') - if liaison.technical_contact: - cc += liaison.technical_contact.split(',') - if liaison.response_contact: - cc += liaison.response_contact.split(',') + to_email = gather_address_list('liaison_statement_posted',liaison=liaison) + cc = gather_address_list('liaison_statement_posted_cc',liaison=liaison) bcc = ['statements@ietf.org'] body = render_to_string('liaisons/liaison_mail.txt', dict( liaison=liaison, @@ -42,13 +39,14 @@ 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) 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, ['statements@ietf.org'], from_email, subject, body) + send_mail_text(request, to, from_email, subject, body) def send_sdo_reminder(sdo): roles = Role.objects.filter(name="liaiman", group=sdo) @@ -58,7 +56,7 @@ def send_sdo_reminder(sdo): manager_role = roles[0] subject = 'Request for update of list of authorized individuals' - to_email = manager_role.email.address + to_email = gather_address_list('liaison_manager_update_request',group=sdo) name = manager_role.person.plain_name() authorized_list = role_persons_with_fixed_email(sdo, "auth") @@ -95,12 +93,8 @@ def possibly_send_deadline_reminder(liaison): days_msg = 'expires %s' % PREVIOUS_DAYS[days_to_go] from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = liaison.to_contact.split(',') - cc = liaison.cc.split(',') - if liaison.technical_contact: - cc += liaison.technical_contact.split(',') - if liaison.response_contact: - cc += liaison.response_contact.split(',') + to_email = gather_address_list('liaison_deadline_soon',liaison=liaison) + cc = gather_address_list('liaison_deadline_soon_cc',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 76ce9d54b..8db9424bc 100644 --- a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py +++ b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py @@ -161,6 +161,10 @@ def make_recipients(apps): desc="The session request ticketing system", template='session-request@ietf.org') + rc(slug='logged_in_person', + desc="The person currently logged into the datatracker who initiated a given action", + template='{% if person and person.email_address %}{{ person.email_address }}{% endif %}') + rc(slug='ipr_requests', desc="The ipr disclosure handling system", template='ietf-ipr@ietf.org') @@ -169,9 +173,47 @@ def make_recipients(apps): desc="The submitter of an IPR disclosure", template='{% if ipr.submitter_email %}{{ ipr.submitter_email }}{% endif %}') - rc(slug='logged_in_person', - desc="The person currently logged into the datatracker who initiated a given action", - template='{% if person and person.email_address %}{{ person.email_address }}{% endif %}') + rc(slug='ipr_updatedipr_contacts', + desc="The submitter (or ietf participant if the submitter is not available) " + "of all IPR disclosures updated directly by this disclosure, without recursing " + "to what the updated disclosures might have updated.", + template=None) + + rc(slug='ipr_updatedipr_holders', + desc="The holders of all IPR disclosures updated by disclosure and disclosures updated by those and so on.", + template=None) + + rc(slug='ipr_announce', + desc="The IETF IPR announce list", + template='ipr-announce@ietf.org') + + rc(slug='doc_ipr_group_or_ad', + desc="Leadership for a document that has a new IPR disclosure", + template=None) + + rc(slug='liaison_to_contact', + desc="The addresses captured in the To field of the liaison statement form", + template='{{liaison.to_contact}}') + + rc(slug='liaison_cc', + desc="The addresses captured in the Cc field of the liaison statement form", + template='{{liaison.cc}}') + + rc(slug='liaison_technical_contact', + desc="The addresses captured in the technical contact field of the liaison statement form", + template='{{liaison.technical_contact}}') + + rc(slug='liaison_response_contact', + desc="The addresses captured in the response contact field of the liaison statement form", + template='{{liaison.response_contact}}') + + rc(slug='liaison_statements_list', + desc="The IETF liaison statement ticketing system", + template='statements@ietf.org') + + rc(slug='liaison_manager', + desc="The assigned liaison manager for an external group ", + template=None) def make_mailtokens(apps): @@ -645,6 +687,62 @@ def make_mailtokens(apps): desc="Copied when the secretary follows up on an IPR disclosure submission", recipient_slugs=[]) + mt_factory(slug='ipr_posting_confirmation', + desc="Recipients for a message confirming that a disclosure has been posted", + recipient_slugs=['ipr_submitter', + ]) + + mt_factory(slug='ipr_posting_confirmation_cc', + desc="Copied on a message confirming that a disclosure has been posted", + recipient_slugs=['ipr_updatedipr_contacts', + 'ipr_updatedipr_holders', + ]) + + mt_factory(slug='ipr_posted_on_doc', + desc="Recipients when an IPR disclosure calls out a given document", + recipient_slugs=['doc_authors', + ]) + + mt_factory(slug='ipr_posted_on_doc_cc', + desc="Copied when an IPR disclosure calls out a given document", + recipient_slugs=['doc_ipr_group_or_ad', + 'ipr_announce', + ]) + + mt_factory(slug='liaison_statement_posted', + desc="Recipient for a message when a new liaison statement is posted", + recipient_slugs=['liaison_to_contact', + ]) + + mt_factory(slug='liaison_statement_posted_cc', + desc="Copied on a message when a new liaison statement is posted", + recipient_slugs=['liaison_cc', + 'liaison_technical_contact', + 'liaison_response_contact', + ]) + + mt_factory(slug='liaison_approval_requested', + desc="Recipients for a message that a pending liaison statement needs approval", + recipient_slugs=['liaison_statements_list', + ]) + + mt_factory(slug='liaison_deadline_soon', + desc="Recipients for a message about a liaison statement deadline that is approaching.", + recipient_slugs=['liaison_to_contact', + ]) + + mt_factory(slug='liaison_deadline_soon_cc', + desc="Copied on a message about a liaison statement deadline that is approaching.", + recipient_slugs=['liaison_cc', + 'liaison_technical_contact', + 'liaison_response_contact', + ]) + + mt_factory(slug='liaison_manager_update_request', + desc="Recipients for a message requesting an updated list of authorized individuals", + recipient_slugs=['liaison_manager', + ]) + def forward(apps, schema_editor): make_recipients(apps) diff --git a/ietf/mailtoken/models.py b/ietf/mailtoken/models.py index 1579f6de0..e89c8ad7c 100644 --- a/ietf/mailtoken/models.py +++ b/ietf/mailtoken/models.py @@ -3,6 +3,8 @@ from django.db import models from django.template import Template, Context +from ietf.group.models import Role + class MailToken(models.Model): slug = models.CharField(max_length=32, primary_key=True) desc = models.TextField(blank=True) @@ -206,3 +208,46 @@ class Recipient(models.Model): if pos and pos.pos_id == "discuss": addrs.append(ad.role_email("ad").address) return addrs + + def gather_ipr_updatedipr_contacts(self, **kwargs): + addrs=[] + if 'ipr' in kwargs: + ipr = kwargs['ipr'] + for rel in ipr.updates: + if rel.target.submitter_email: + addrs.append(rel.target.submitter_email) + elif hasattr(rel.target,'ietfer_email') and rel.target.ietfer_email: + addrs.append(rel.target.ietfer_email) + return addrs + + def gather_ipr_updatedipr_holders(self, **kwargs): + addrs=[] + if 'ipr' in kwargs: + ipr = kwargs['ipr'] + for disc in ipr.recursively_updates(): + if hasattr(ipr,'holder_contact_email') and ipr.holder_contact_email: + addrs.append(ipr.holder_contact_email) + return addrs + + def gather_doc_ipr_group_or_ad(self, **kwargs): + """A document's group email list if the document is a group document, + otherwise, the document's AD if the document is active, otherwise + the IETF chair""" + addrs=[] + if 'doc' in kwargs: + doc=kwargs['doc'] + if doc.group and doc.group.acronym == 'none': + if doc.ad and doc.get_state_slug('draft')=='active': + addrs.extend(Recipient.objects.get(slug='doc_ad').gather(**kwargs)) + else: + addrs.extend(Role.objects.filter(group__acronym='gen',name='ad').values_list('email__address',flat=True)) + else: + addrs.extend(Recipient.objects.get(slug='doc_group_mail_list').gather(**kwargs)) + return addrs + + def gather_liaison_manager(self, **kwargs): + addrs=[] + if 'group' in kwargs: + group=kwargs['group'] + addrs.extend(group.role_set.filter(name='liaiman').values_list('email__address',flat=True)) + return addrs diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index c43c08eb3..e65455762 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -4423,6 +4423,14 @@ "model": "mailtoken.recipient", "pk": "doc_group_responsible_directors" }, +{ + "fields": { + "template": null, + "desc": "Leadership for a document that has a new IPR disclosure" + }, + "model": "mailtoken.recipient", + "pk": "doc_ipr_group_or_ad" +}, { "fields": { "template": null, @@ -4559,6 +4567,14 @@ "model": "mailtoken.recipient", "pk": "internet_draft_requests" }, +{ + "fields": { + "template": "ipr-announce@ietf.org", + "desc": "The IETF IPR announce list" + }, + "model": "mailtoken.recipient", + "pk": "ipr_announce" +}, { "fields": { "template": "ietf-ipr@ietf.org", @@ -4575,6 +4591,70 @@ "model": "mailtoken.recipient", "pk": "ipr_submitter" }, +{ + "fields": { + "template": null, + "desc": "The submitter (or ietf participant if the submitter is not available) of all IPR disclosures updated directly by this disclosure, without recursing to what the updated disclosures might have updated." + }, + "model": "mailtoken.recipient", + "pk": "ipr_updatedipr_contacts" +}, +{ + "fields": { + "template": null, + "desc": "The holders of all IPR disclosures updated by disclosure and disclosures updated by those and so on." + }, + "model": "mailtoken.recipient", + "pk": "ipr_updatedipr_holders" +}, +{ + "fields": { + "template": "{{liaison.cc}}", + "desc": "The addresses captured in the Cc field of the liaison statement form" + }, + "model": "mailtoken.recipient", + "pk": "liaison_cc" +}, +{ + "fields": { + "template": null, + "desc": "The assigned liaison manager for an external group " + }, + "model": "mailtoken.recipient", + "pk": "liaison_manager" +}, +{ + "fields": { + "template": "{{liaison.response_contact}}", + "desc": "The addresses captured in the response contact field of the liaison statement form" + }, + "model": "mailtoken.recipient", + "pk": "liaison_response_contact" +}, +{ + "fields": { + "template": "statements@ietf.org", + "desc": "The IETF liaison statement ticketing system" + }, + "model": "mailtoken.recipient", + "pk": "liaison_statements_list" +}, +{ + "fields": { + "template": "{{liaison.technical_contact}}", + "desc": "The addresses captured in the technical contact field of the liaison statement form" + }, + "model": "mailtoken.recipient", + "pk": "liaison_technical_contact" +}, +{ + "fields": { + "template": "{{liaison.to_contact}}", + "desc": "The addresses captured in the To field of the liaison statement form" + }, + "model": "mailtoken.recipient", + "pk": "liaison_to_contact" +}, { "fields": { "template": "{% if person and person.email_address %}{{ person.email_address }}{% endif %}", @@ -5107,6 +5187,48 @@ "model": "mailtoken.mailtoken", "pk": "ipr_disclosure_submitted" }, +{ + "fields": { + "recipients": [ + "doc_authors" + ], + "desc": "Recipients when an IPR disclosure calls out a given document" + }, + "model": "mailtoken.mailtoken", + "pk": "ipr_posted_on_doc" +}, +{ + "fields": { + "recipients": [ + "doc_ipr_group_or_ad", + "ipr_announce" + ], + "desc": "Copied when an IPR disclosure calls out a given document" + }, + "model": "mailtoken.mailtoken", + "pk": "ipr_posted_on_doc_cc" +}, +{ + "fields": { + "recipients": [ + "ipr_submitter" + ], + "desc": "Recipients for a message confirming that a disclosure has been posted" + }, + "model": "mailtoken.mailtoken", + "pk": "ipr_posting_confirmation" +}, +{ + "fields": { + "recipients": [ + "ipr_updatedipr_contacts", + "ipr_updatedipr_holders" + ], + "desc": "Copied on a message confirming that a disclosure has been posted" + }, + "model": "mailtoken.mailtoken", + "pk": "ipr_posting_confirmation_cc" +}, { "fields": { "recipients": [ @@ -5189,6 +5311,70 @@ "model": "mailtoken.mailtoken", "pk": "last_call_requested_cc" }, +{ + "fields": { + "recipients": [ + "liaison_statements_list" + ], + "desc": "Recipients for a message that a pending liaison statement needs approval" + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_approval_requested" +}, +{ + "fields": { + "recipients": [ + "liaison_to_contact" + ], + "desc": "Recipients for a message about a liaison statement deadline that is approaching." + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_deadline_soon" +}, +{ + "fields": { + "recipients": [ + "liaison_cc", + "liaison_response_contact", + "liaison_technical_contact" + ], + "desc": "Copied on a message about a liaison statement deadline that is approaching." + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_deadline_soon_cc" +}, +{ + "fields": { + "recipients": [ + "liaison_manager" + ], + "desc": "Recipients for a message requesting an updated list of authorized individuals" + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_manager_update_request" +}, +{ + "fields": { + "recipients": [ + "liaison_to_contact" + ], + "desc": "Recipient for a message when a new liaison statement is posted" + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_statement_posted" +}, +{ + "fields": { + "recipients": [ + "liaison_cc", + "liaison_response_contact", + "liaison_technical_contact" + ], + "desc": "Copied on a message when a new liaison statement is posted" + }, + "model": "mailtoken.mailtoken", + "pk": "liaison_statement_posted_cc" +}, { "fields": { "recipients": [ diff --git a/ietf/secr/utils/mail.py b/ietf/secr/utils/mail.py deleted file mode 100644 index 01133dd27..000000000 --- a/ietf/secr/utils/mail.py +++ /dev/null @@ -1,34 +0,0 @@ -def get_ad_email_list(group): - ''' - This function takes a group and returns the Area Director email as a list. - NOTE: we still have custom logic here for IRTF groups, where the "Area Director" - is the chair of the parent group, 'irtf'. - ''' - emails = [] - if group.type.slug == 'wg': - emails.append('%s-ads@tools.ietf.org' % group.acronym) - elif group.type.slug == 'rg' and group.parent: - emails.append(group.parent.role_set.filter(name='chair')[0].email.address) - return emails - -def get_cc_list(group, person=None): - ''' - This function takes a Group and Person. It returns a list of emails for the ads and chairs of - the group and the person's email if it isn't already in the list. - - Per Pete Resnick, at IETF 80 meeting, session request notifications - should go to chairs,ads lists not individuals. - ''' - emails = [] - emails.extend(get_ad_email_list(group)) - emails.extend(get_chair_email_list(group)) - if person and person.email_address() not in emails: - emails.append(person.email_address()) - return emails - -def get_chair_email_list(group): - ''' - This function takes a group and returns chair email(s) as a list. - ''' - return [ r.email.address for r in group.role_set.filter(name='chair') ] -