From 56ddf4ca2ebfa3cc9724cab62e5605ccbb5a3109 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 17 Aug 2015 22:11:54 +0000
Subject: [PATCH] checkpoint. includes moderate change to how personnel changes
 are sent out  - Legacy-Id: 10020

---
 ietf/doc/views_charter.py                     |  8 +-
 ietf/group/edit.py                            | 10 +--
 ietf/group/mails.py                           | 53 ++-----------
 .../migrations/0002_auto_20150809_1314.py     | 41 +++++++++-
 ietf/name/fixtures/names.json                 | 77 ++++++++++++++++++-
 5 files changed, 129 insertions(+), 60 deletions(-)

diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py
index 8fc8fa106..2cfb56171 100644
--- a/ietf/doc/views_charter.py
+++ b/ietf/doc/views_charter.py
@@ -29,7 +29,7 @@ from ietf.person.models import Person
 from ietf.utils.history import find_history_active_at
 from ietf.utils.mail import send_mail_preformatted
 from ietf.utils.textupload import get_cleaned_text_file_content
-from ietf.group.mails import email_iesg_secretary_re_charter
+from ietf.group.mails import email_admin_re_charter
 
 class ChangeStateForm(forms.Form):
     charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
@@ -141,7 +141,7 @@ def change_state(request, name, option=None):
                 charter.save()
 
                 if message or charter_state.slug == "intrev" or charter_state.slug == "extrev":
-                    email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message)
+                    email_admin_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message,'charter_state_edit_admin_needed')
 
                 # TODO - do we need a seperate set of recipients for state changes to charters vrs other kind of documents
                 email_state_changed(request, charter, "State changed to %s." % charter_state, 'doc_state_edited')
@@ -267,7 +267,7 @@ def change_title(request, name, option=None):
                 charter.time = datetime.datetime.now()
                 charter.save()
                 if message:
-                    email_iesg_secretary_re_charter(request, group, "Charter title changed to %s" % new_title, message)
+                    email_admin_re_charter(request, group, "Charter title changed to %s" % new_title, message,'charter_state_edit_admin_needed')
                 email_state_changed(request, charter, "Title changed to %s." % new_title,'doc_state_edited')
             return redirect('doc_view', name=charter.name)
     else:
@@ -645,7 +645,7 @@ def approve(request, name):
 
         fix_charter_revision_after_approval(charter, login)
 
-        email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description)
+        email_admin_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description,'charter_state_edit_admin_needed')
 
         # move milestones over
         milestones_to_delete = list(group.groupmilestone_set.filter(state__in=("active", "review")))
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index 390a34495..10bf84be7 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -21,8 +21,7 @@ from ietf.group.utils import get_group_or_404
 from ietf.ietfauth.utils import has_role
 from ietf.person.fields import SearchableEmailsField
 from ietf.person.models import Person, Email
-from ietf.group.mails import ( email_iesg_secretary_re_charter, email_iesg_secretary_personnel_change,
-    email_interested_parties_re_changed_delegates )
+from ietf.group.mails import ( email_admin_re_charter, email_personnel_change)
 from ietf.utils.ordereddict import insert_after_in_ordered_dict
 
 MAX_GROUP_DELEGATES = 3
@@ -255,6 +254,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
             diff('list_archive', "Mailing list archive")
 
             personnel_change_text=""
+            changed_personnel = set()
             # update roles
             for attr, slug, title in [('ad','ad','Shepherding AD'), ('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]:
                 new = clean[attr]
@@ -276,10 +276,10 @@ def edit(request, group_type=None, acronym=None, action="edit"):
                     if deleted:
                         change_text=title + ' deleted: ' + ", ".join(x.formatted_email() for x in deleted)
                         personnel_change_text+=change_text+"\n"
-                    email_interested_parties_re_changed_delegates(request, group, title, added, deleted)
+                    changed_personnel.update(set(old)^set(new))
 
             if personnel_change_text!="":
-                email_iesg_secretary_personnel_change(request, group, personnel_change_text)
+                email_personnel_change(request, group, personnel_change_text, changed_personnel)
 
             # update urls
             new_urls = clean['urls']
@@ -357,7 +357,7 @@ def conclude(request, acronym, group_type=None):
         if form.is_valid():
             instructions = form.cleaned_data['instructions']
 
-            email_iesg_secretary_re_charter(request, group, "Request closing of group", instructions)
+            email_admin_re_charter(request, group, "Request closing of group", instructions, 'group_closure_requested')
 
             e = GroupEvent(group=group, by=request.user.person)
             e.type = "requested_close"
diff --git a/ietf/group/mails.py b/ietf/group/mails.py
index 0f4c1654e..28650e38d 100644
--- a/ietf/group/mails.py
+++ b/ietf/group/mails.py
@@ -14,8 +14,8 @@ from ietf.group.models import Group
 from ietf.group.utils import milestone_reviewer_for_group_type
 from ietf.mailtoken.utils import gather_address_list
 
-def email_iesg_secretary_re_charter(request, group, subject, text):
-    to = gather_address_list('charter_state_message_provided',group=group)
+def email_admin_re_charter(request, group, subject, text, mailtoken):
+    to = gather_address_list(mailtoken,group=group)
     full_subject = u"Regarding %s %s: %s" % (group.type.name, group.acronym, subject)
     text = strip_tags(text)
 
@@ -28,42 +28,11 @@ def email_iesg_secretary_re_charter(request, group, subject, text):
                    )
               )
 
-def email_iesg_secretary_personnel_change(request, group, text):
-    to = ["iesg-secretary@ietf.org"]
+def email_personnel_change(request, group, text, changed_personnel):
+    to = gather_address_list('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)
 
-def email_interested_parties_re_changed_delegates(request, group, title, added, deleted):
-
-    # Send to management and chairs
-    to = []
-    if group.ad_role():
-        to.append(group.ad_role().email.formatted_email())
-    elif group.type_id == "rg":
-        to.append("IRTF Chair <irtf-chair@irtf.org>")
-
-    for r in group.role_set.filter(name="chair"):
-        to.append(r.formatted_email())
-
-    # Send to the delegates who were added or deleted
-    for delegate in added:
-        to.append(delegate.formatted_email())
-
-    for delegate in deleted:
-        to.append(delegate.formatted_email())
-
-    personnel_change_text=""
-    if added:
-        change_text=title + ' added: ' + ", ".join(x.formatted_email() for x in added)
-        personnel_change_text+=change_text+"\n"
-    if deleted:
-        change_text=title + ' deleted: ' + ", ".join(x.formatted_email() for x in deleted)
-        personnel_change_text+=change_text+"\n"
-
-    if to:
-        full_subject = u"%s changed for %s working group" % (title, group.acronym)
-        send_mail_text(request, to, None, full_subject,personnel_change_text)
-
 
 def email_milestones_changed(request, group, changes):
     def wrap_up_email(to, text):
@@ -94,18 +63,12 @@ def email_milestones_changed(request, group, changes):
 
 def email_milestone_review_reminder(group, grace_period=7):
     """Email reminders about milestones needing review to management."""
-    to = []
-
-    if group.ad_role():
-        to.append(group.ad_role().email.formatted_email())
-    elif group.type_id == "rg":
-        to.append("IRTF Chair <irtf-chair@irtf.org>")
+    to = gather_address_list('milestone_review_reminder',group=group)
+    cc = gather_address_list('milestone_review_reminder_cc',group=group)
 
     if not to:
         return False
 
-    cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
-
     now = datetime.datetime.now()
     too_early = True
 
@@ -139,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 = [r.formatted_email() for r in group.role_set.filter(name="chair")]
+    to = gather_address_list('milestones_due_soon',group=group)
 
     today = datetime.date.today()
     early_warning = today + datetime.timedelta(days=early_warning_days)
@@ -166,7 +129,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 = [r.formatted_email() for r in group.role_set.filter(name="chair")]
+    to = gather_address_list('milestones_overdue',group=group)
 
     today = datetime.date.today()
 
diff --git a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py
index dd5490ba6..945ee6043 100644
--- a/ietf/mailtoken/migrations/0002_auto_20150809_1314.py
+++ b/ietf/mailtoken/migrations/0002_auto_20150809_1314.py
@@ -153,6 +153,10 @@ def make_recipients(apps):
        desc="Any ADs holding an active DISCUSS position on a given document",
        template=None)
 
+    rc(slug='group_changed_personnel',
+       desc="Any personnel who were added or deleted when a group's personnel changes",
+       template='{{ changed_personnel | join:", " }}')
+
 def make_mailtokens(apps):
 
     Recipient=apps.get_model('mailtoken','Recipient')
@@ -429,8 +433,12 @@ def make_mailtokens(apps):
                                 'doc_group_responsible_directors',
                                ]) 
 
-    mt_factory(slug='charter_state_message_provided',
-               desc="Recipients for extra message when provided on the charter state editing form",
+    mt_factory(slug='charter_state_edit_admin_needed',
+               desc="Recipients for message to adminstrators when a charter state edit needs followon administrative action",
+               recipient_slugs=['iesg_secretary'])
+
+    mt_factory(slug='group_closure_requested',
+               desc="Recipients for message requesting closure of a group",
                recipient_slugs=['iesg_secretary'])
 
     mt_factory(slug='doc_expires_soon',
@@ -508,7 +516,7 @@ def make_mailtokens(apps):
                                ])
 
     mt_factory(slug='sub_new_version',
-               desc="Recipient for notification of a new version of an existing document",
+               desc="Recipients for notification of a new version of an existing document",
                recipient_slugs=['doc_notify',
                                 'doc_ad',
                                 'non_ietf_stream_manager',
@@ -516,6 +524,33 @@ def make_mailtokens(apps):
                                 'doc_discussing_ads',
                                ])
 
+    mt_factory(slug='milestone_review_reminder',
+               desc="Recipients for reminder message that unapproved milestone changes need review",
+               recipient_slugs=['group_responsible_directors',
+                               ])
+
+    mt_factory(slug='milestone_review_reminder_cc',
+               desc="Copied on reminder message that unapproved milestone changes need review",
+               recipient_slugs=['group_chairs',
+                               ])
+
+    mt_factory(slug='milestones_due_soon',
+               desc='Recipients for reminder message for milestones about to become overdue',
+               recipient_slugs=['group_chairs',
+                               ])
+
+    mt_factory(slug='milestones_overdue',
+               desc='Recipients for message about milestones that are overdue',
+               recipient_slugs=['group_chairs',
+                               ])
+
+    mt_factory(slug='group_personnel_change',
+               desc="Recipients for a message noting changes in a group's personnel",
+               recipient_slugs=['iesg_secretary',
+                                'group_responsible_directors',
+                                'group_chairs',
+                                'group_changed_personnel',
+                               ])
 
 def forward(apps, schema_editor):
 
diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json
index ec116799d..88f2dc314 100644
--- a/ietf/name/fixtures/names.json
+++ b/ietf/name/fixtures/names.json
@@ -4463,6 +4463,14 @@
  "model": "mailtoken.recipient",
  "pk": "group_chairs"
 },
+{
+ "fields": {
+  "template": "{{ changed_personnel | join:\", \" }}",
+  "desc": "Any personnel who were added or deleted when a group's personnel changes"
+ },
+ "model": "mailtoken.recipient",
+ "pk": "group_changed_personnel"
+},
 {
  "fields": {
   "template": "{{ group.list_email }}",
@@ -4796,10 +4804,10 @@
   "recipients": [
    "iesg_secretary"
   ],
-  "desc": "Recipients for extra message when provided on the charter state editing form"
+  "desc": "Recipients for message to adminstrators when a charter state edit needs followon administrative action"
  },
  "model": "mailtoken.mailtoken",
- "pk": "charter_state_message_provided"
+ "pk": "charter_state_edit_admin_needed"
 },
 {
  "fields": {
@@ -5005,6 +5013,16 @@
  "model": "mailtoken.mailtoken",
  "pk": "group_approved_milestones_edited"
 },
+{
+ "fields": {
+  "recipients": [
+   "iesg_secretary"
+  ],
+  "desc": "Recipients for message requesting closure of a group"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "group_closure_requested"
+},
 {
  "fields": {
   "recipients": [
@@ -5016,6 +5034,19 @@
  "model": "mailtoken.mailtoken",
  "pk": "group_milestones_edited"
 },
+{
+ "fields": {
+  "recipients": [
+   "group_chairs",
+   "group_changed_personnel",
+   "group_responsible_directors",
+   "iesg_secretary"
+  ],
+  "desc": "Recipients for a message noting changes in a group's personnel"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "group_personnel_change"
+},
 {
  "fields": {
   "recipients": [
@@ -5098,6 +5129,46 @@
  "model": "mailtoken.mailtoken",
  "pk": "last_call_requested_cc"
 },
+{
+ "fields": {
+  "recipients": [
+   "group_chairs"
+  ],
+  "desc": "Recipients for reminder message for milestones about to become overdue"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "milestones_due_soon"
+},
+{
+ "fields": {
+  "recipients": [
+   "group_chairs"
+  ],
+  "desc": "Recipients for message about milestones that are overdue"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "milestones_overdue"
+},
+{
+ "fields": {
+  "recipients": [
+   "group_responsible_directors"
+  ],
+  "desc": "Recipients for reminder message that unapproved milestone changes need review"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "milestone_review_reminder"
+},
+{
+ "fields": {
+  "recipients": [
+   "group_chairs"
+  ],
+  "desc": "Copied on reminder message that unapproved milestone changes need review"
+ },
+ "model": "mailtoken.mailtoken",
+ "pk": "milestone_review_reminder_cc"
+},
 {
  "fields": {
   "recipients": [
@@ -5253,7 +5324,7 @@
    "doc_notify",
    "rfc_editor_if_doc_in_queue"
   ],
-  "desc": "Recipient for notification of a new version of an existing document"
+  "desc": "Recipients for notification of a new version of an existing document"
  },
  "model": "mailtoken.mailtoken",
  "pk": "sub_new_version"