|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import os, sys, re |
| 4 | + |
| 5 | +from copy import copy |
| 6 | + |
| 7 | +import datetime |
| 8 | + |
| 9 | +# boilerplate |
| 10 | +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../web/")) |
| 11 | +sys.path = [ basedir ] + sys.path |
| 12 | +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings") |
| 13 | + |
| 14 | +import django |
| 15 | + |
| 16 | +django.setup() |
| 17 | + |
| 18 | +from django.db.models import F |
| 19 | +from django.template import Template,Context |
| 20 | +from ietf.doc.models import Document, DocEvent |
| 21 | +from ietf.person.models import Person |
| 22 | +from ietf.utils.mail import send_mail_text |
| 23 | + |
| 24 | +def message_body_template(): |
| 25 | + return Template("""{% filter wordwrap:72 %}The Notify field for the document{{ count|pluralize }} listed at the end of this message {% if count > 1 %}were{% else %}was{% endif %} changed by removing the chair, shepherd, author, and similar addresses (in direct or alias form) to the point they could be identified. |
| 26 | +
|
| 27 | +The Datatracker now includes those addresses explicitly in each message it sends as appropriate. You can review where the datatracker sends messages for a given action in general using <https://datatracker.ietf.org/mailtoken/token>. You can review the expansions for a specific document by the new Email expansions tab on the document's page. Note that the addresses included for any given action are much more comprehensive than they were before this release. |
| 28 | +
|
| 29 | +Please review each new Notify field, and help remove any remaining addresses that will normally be copied per the configuration shown at https://datatracker.ietf.org/mailtoken/token. The field should now only contain exceptional addresses - parties you wish to be notified that aren't part of the new normal recipient set. |
| 30 | +
|
| 31 | +You can see exactly how the Notify field was changed for a given document by looking in the document's history.{% endfilter %} |
| 32 | +
|
| 33 | +{% if non_empty%}The document{{non_empty|length|pluralize }} with non-empty new Notify fields are:{% for doc in non_empty %} |
| 34 | + https://datatracker.ietf.org{{doc.get_absolute_url}}{% endfor %}{% endif %} |
| 35 | +
|
| 36 | +{% if empty%}The document{{non_empty|length|pluralize }} with empty new Notify fields are:{% for doc in empty %} |
| 37 | + https://datatracker.ietf.org{{doc.get_absolute_url}}{% endfor %}{% endif %} |
| 38 | +
|
| 39 | +""") |
| 40 | + |
| 41 | +def other_addrs(addr): |
| 42 | + person = Person.objects.filter(email__address__iexact=addr).first() |
| 43 | + if not person: |
| 44 | + return None |
| 45 | + return [x.lower() for x in person.email_set.values_list('address',flat=True)] |
| 46 | + |
| 47 | +def prep(item): |
| 48 | + retval = item.lower() |
| 49 | + if '<' in retval: |
| 50 | + if not '>' in retval: |
| 51 | + raise "Bad item: "+item |
| 52 | + start=retval.index('<')+1 |
| 53 | + stop=retval.index('>') |
| 54 | + retval = retval[start:stop] |
| 55 | + return retval |
| 56 | + |
| 57 | +def is_management(item, doc): |
| 58 | + |
| 59 | + item = prep(item) |
| 60 | + |
| 61 | + if any([ |
| 62 | + item == '%s.chairs@ietf.org'%doc.name, |
| 63 | + item == '%s.ad@ietf.org'%doc.name, |
| 64 | + item == '%s.shepherd@ietf.org'%doc.name, |
| 65 | + item == '%s.chairs@tools.ietf.org'%doc.name, |
| 66 | + item == '%s.ad@tools.ietf.org'%doc.name, |
| 67 | + item == '%s.shepherd@tools.ietf.org'%doc.name, |
| 68 | + doc.ad and item == doc.ad.email_address().lower(), |
| 69 | + doc.shepherd and item == doc.shepherd.address.lower(), |
| 70 | + ]): |
| 71 | + return True |
| 72 | + |
| 73 | + if doc.group: |
| 74 | + if any([ |
| 75 | + item == '%s-chairs@ietf.org'%doc.group.acronym, |
| 76 | + item == '%s-ads@ietf.org'%doc.group.acronym, |
| 77 | + item == '%s-chairs@tools.ietf.org'%doc.group.acronym, |
| 78 | + item == '%s-ads@tools.ietf.org'%doc.group.acronym, |
| 79 | + ]): |
| 80 | + return True |
| 81 | + for addr in doc.group.role_set.filter(name__in=['chair','ad','delegate']).values_list('email__address',flat=True): |
| 82 | + other = other_addrs(addr) |
| 83 | + if item == addr.lower() or item in other: |
| 84 | + return True |
| 85 | + if doc.group.parent: |
| 86 | + if item == '%s-ads@ietf.org'%doc.group.parent.acronym or item == '%s-ads@tools.ietf.org'%doc.group.parent.acronym: |
| 87 | + return True |
| 88 | + |
| 89 | + return False |
| 90 | + |
| 91 | +def is_author(item, doc): |
| 92 | + item = prep(item) |
| 93 | + |
| 94 | + if item == '%s@ietf.org' % doc.name or item == '%s@tools.ietf.org' % doc.name: |
| 95 | + return True |
| 96 | + |
| 97 | + for addr in doc.authors.values_list('address',flat=True): |
| 98 | + other = other_addrs(addr) |
| 99 | + if item == addr.lower() or item in other: |
| 100 | + return True |
| 101 | + |
| 102 | + return False |
| 103 | + |
| 104 | +msg_template = message_body_template() |
| 105 | +by = Person.objects.get(name="(System)") |
| 106 | +active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area")) |
| 107 | + |
| 108 | +affected = set() |
| 109 | +empty = dict() |
| 110 | +non_empty = dict() |
| 111 | +changed = 0 |
| 112 | +emptied = 0 |
| 113 | + |
| 114 | +qs = Document.objects.exclude(notify__isnull=True).exclude(notify='') |
| 115 | +for doc in qs: |
| 116 | + doc.notify = doc.notify.replace(';', ',') |
| 117 | + items = set([ i.strip() for i in doc.notify.split(',') if i.strip() and '@' in i]) |
| 118 | + original_items = copy(items) |
| 119 | + for item in original_items: |
| 120 | + if any([ |
| 121 | + doc.group and doc.group.list_email and item.lower() == doc.group.list_email.lower(), |
| 122 | + is_management(item,doc), |
| 123 | + is_author(item,doc), |
| 124 | + ]): |
| 125 | + items.discard(item) |
| 126 | + if original_items != items: |
| 127 | + changed += 1 |
| 128 | + if len(list(items))==0: |
| 129 | + emptied += 1 |
| 130 | + |
| 131 | + to = [] |
| 132 | + if doc.ad and doc.ad in active_ads: |
| 133 | + to.append(doc.ad.email_address()) |
| 134 | + if doc.group and doc.group.state_id=='active': |
| 135 | + to.extend(doc.group.role_set.filter(name__in=['chair','ad']).values_list('email__address',flat=True)) |
| 136 | + if not to: |
| 137 | + to = ['iesg@ietf.org'] |
| 138 | + |
| 139 | + to = ", ".join(sorted(to)) |
| 140 | + affected.add(to) |
| 141 | + empty.setdefault(to,[]) |
| 142 | + non_empty.setdefault(to,[]) |
| 143 | + if len(list(items))==0: |
| 144 | + empty[to].append(doc) |
| 145 | + else: |
| 146 | + non_empty[to].append(doc) |
| 147 | + original_notify = doc.notify |
| 148 | + new_notify = ', '.join(list(items)) |
| 149 | + doc.notify = new_notify |
| 150 | + doc.time = datetime.datetime.now() |
| 151 | + doc.save() |
| 152 | + e = DocEvent(type="added_comment",doc=doc,time=doc.time,by=by) |
| 153 | + e.desc = "Notify list changed from %s to %s"% (original_notify, new_notify if new_notify else '(None)') |
| 154 | + e.save() |
| 155 | + |
| 156 | +for a in list(affected): |
| 157 | + |
| 158 | + txt = msg_template.render(Context({'count':len(empty[a])+len(non_empty[a]),'empty':empty[a],'non_empty':non_empty[a]})) |
| 159 | + send_mail_text(None, to=a, frm=None, subject='Document Notify fields changed to match new Datatracker addressing defaults',txt =txt) |
| 160 | + |
| 161 | +print "Changed",changed,"documents.",emptied,"of those had their notify field emptied" |
| 162 | +print "Sent email to ",len(affected),"different sets of addresses" |
| 163 | + |
0 commit comments