Merged [5902],[5904] from markus.stenberg@iki.fi:

Added support and scripts for generation of wg- and draft-aliases.  Fixes issue .
 - Legacy-Id: 5937
Note: SVN reference [5902] has been migrated to Git commit e5b551f4ce

Note: SVN reference [5904] has been migrated to Git commit e68e51cc55
This commit is contained in:
Henrik Levkowetz 2013-08-01 12:44:49 +00:00
parent 9f998e5bbc
commit 8675711c27
5 changed files with 361 additions and 3 deletions

147
ietf/bin/generate-draft-aliases Executable file
View file

@ -0,0 +1,147 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-
#
# $Id: generate-draft-aliases $
#
# Author: Markus Stenberg <markus.stenberg@iki.fi>
#
"""
This code dumps Django model InternetDraft's contents as postfix email
aliases
<no suffix> (same as -authors)
.authors (list of authors)
.chairs (WG chairs)
.notify (notify emails(?))
.ad (sponsoring AD)
.all (all of the above)
TODO:
- results somewhat inconsistent with the results from the old tool;
should examine why (ask me for diff tool if interested in fixing it)
"""
DRAFT_EMAIL_SUFFIX='@tools.ietf.org'
# boilerplate (from various other ietf/bin scripts)
import os, sys
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from ietf.doc.models import Document
from ietf.group.utils import get_group_chairs_emails, get_group_ads_emails
from ietf.utils.aliases import *
import time
def get_draft_ad_emails(draft):
" Get AD email for the given draft, if any. "
# If working group document, return current WG ADs
wg = draft.group
if wg and wg.acronym != 'none' and wg.parent and wg.parent.acronym != 'none':
return get_group_ads_emails(wg)
# If not, return explicit AD set (whether up to date or not)
ad = draft.ad
#return [ad and ad.user and ad.user.email]
return [ad and ad.email_address()]
def get_draft_authors_emails(draft):
" Get list of authors for the given draft."
# This feels 'correct'; however, it creates fairly large delta
return [email.email_address() for email in draft.authors.all()]
# This gives fairly small delta compared to current state,
# however, it seems to be wrong (doesn't check for emails being
# active etc).
#return [email.address for email in draft.authors.all()]
def get_draft_notify_emails(draft):
" Get list of email addresses to notify for the given draft."
n = draft.notify
if not n:
return
l = []
draft_email = draft.name + DRAFT_EMAIL_SUFFIX
for e in n.split(','):
# If the draft name itself is listed as notify list element, we
# expand it (to make results better verifiable with the old ones)
e = e.strip()
if e == draft_email:
l.extend(get_draft_authors_emails(draft))
else:
l.append(e)
# Alternative: if we don't want to do expansion, just this would be
# perhaps better (MTA can do expansion too):
# l = n.split(',')
return l
if __name__ == '__main__':
import datetime
import time
# Year ago?
show_since = datetime.datetime.now() - datetime.timedelta(365)
# 10 years ago?
#show_since = datetime.datetime.now() - datetime.timedelta(10 * 365)
modname = 'ietf.generate_draft_aliases'
date = time.strftime("%Y-%m-%d_%H:%M:%S")
print '# Generated by python -m %s at %s' % (modname, date)
drafts = Document.objects.all()
# Drafts with active status
active_drafts = drafts.filter(states__slug='active')
# Drafts that expired within year
inactive_recent_drafts = drafts.exclude(states__slug='active').filter(expires__gte=show_since)
interesting_drafts = active_drafts | inactive_recent_drafts
for draft in interesting_drafts.distinct().iterator():
# Omit RFCs, we care only about drafts
if draft.docalias_set.filter(name__startswith='rfc'):
continue
name = draft.name
done = []
all = []
def handle_sublist(name, f, o, is_ad=False):
r = dump_sublist(name, f, o, is_ad)
if r:
done.append(name)
all.extend(r)
return r
#.authors (/and no suffix) = authors
# First, do no suffix case
# If no authors, don't generate list either
r = dump_sublist(name, get_draft_authors_emails, draft)
if not r:
continue
handle_sublist('%s%s' % (name, '.authors'), get_draft_authors_emails, draft)
wg = draft.group
if wg:
# .chairs = WG chairs
handle_sublist('%s%s' % (name, '.chairs'), get_group_chairs_emails, wg)
# .ad = sponsoring AD / WG AD (WG document)
handle_sublist('%s%s' % (name, '.ad'), get_draft_ad_emails, draft, True)
# .notify = notify email list from the Document
handle_sublist('%s%s' % (name, '.notify'), get_draft_notify_emails, draft)
# .all = everything on 'done' (recursive aliases)
#dump_sublist('%s%s' % (name, '.all'), None, done)
# .all = everything on 'all' (expanded aliases)
dump_sublist('%s%s' % (name, '.all'), None, all)

91
ietf/bin/generate-wg-aliases Executable file
View file

@ -0,0 +1,91 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-
#
# $Id: generate-wg-aliases $
#
# Author: Markus Stenberg <markus.stenberg@iki.fi>
#
"""
This code dumps Django model IETFWG's contents as two sets of postfix
mail lists: -ads, and -chairs
"""
# boilerplate (from various other ietf/bin scripts)
import os, sys
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from ietf.group.models import Group
from ietf.group.utils import get_group_ads_emails, get_group_chairs_emails, get_area_ads_emails, get_area_chairs_emails
from ietf.person.models import Email
from ietf.utils.aliases import *
# from secr/utils/group.py..
ACTIVE_STATES=['active', 'bof', 'proposed']
if __name__ == '__main__':
import datetime
import time
# Year ago?
#show_since = datetime.datetime.now() - datetime.timedelta(365)
# 2 years ago?
#show_since = datetime.datetime.now() - datetime.timedelta(2 * 365)
# 3 years ago?
#show_since = datetime.datetime.now() - datetime.timedelta(3 * 365)
# 5 years ago?
show_since = datetime.datetime.now() - datetime.timedelta(5 * 365)
modname = 'ietf.generate_wg_aliases'
date = time.strftime("%Y-%m-%d_%H:%M:%S")
print '# Generated by python -m %s at %s' % (modname, date)
wgs = Group.objects.filter(type='wg').all()
print '# WGs'
# - status = Active
active_wgs = wgs.filter(state__in=ACTIVE_STATES)
# - activity within last year? (use concluded_date)
inactive_recent_wgs = wgs.exclude(state__in=ACTIVE_STATES).filter(time__gte=show_since)
interesting_wgs = active_wgs | inactive_recent_wgs
for wg in interesting_wgs.distinct().iterator():
name = wg.acronym
dump_sublist('%s%s' % (name, '-ads'), get_group_ads_emails, wg, True)
dump_sublist('%s%s' % (name, '-chairs'), get_group_chairs_emails, wg)
print '# RGs'
# - status = Active
rgs = Group.objects.filter(type='rg').all()
active_rgs = rgs.filter(state__in=ACTIVE_STATES)
# - activity within last year? (use concluded_date)
inactive_recent_rgs = rgs.exclude(state__in=ACTIVE_STATES).filter(time__gte=show_since)
interesting_rgs = active_rgs | inactive_recent_rgs
for rg in interesting_rgs.distinct().iterator():
name = rg.acronym
#dump_sublist('%s%s' % (name, '-ads'), get_group_ads_emails, rg, True)
dump_sublist('%s%s' % (name, '-chairs'), get_group_chairs_emails, rg)
# Additionally, for areaz, we should list -ads and -chairs
# (for every chair in active groups within the area).
print '# Areas'
areas = Group.objects.filter(type='area').all()
active_areas = areas.filter(state__in=ACTIVE_STATES)
for area in active_areas:
name = area.acronym
dump_sublist('%s%s' % (name, '-ads'), get_area_ads_emails, area, True)
dump_sublist('%s%s' % (name, '-chairs'), get_area_chairs_emails, area)

View file

@ -45,10 +45,50 @@ def get_charter_text(group):
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, group.acronym) + ".desc.txt"
desc_file = open(filename)
desc = desc_file.read()
except BaseException:
except BaseException:
desc = 'Error Loading Work Group Description'
return desc
def get_area_ads_emails(area):
if area.acronym == 'none':
return []
emails = [r.email.email_address()
for r in area.role_set.filter(name__in=('ad', 'chair'))]
return filter(None, emails)
def get_group_ads_emails(wg):
" Get list of area directors' emails for a given WG "
if wg.acronym == 'none':
return []
if wg.parent and wg.parent.acronym != 'none':
# By default, we should use _current_ list of ads!
return get_area_ads_emails(wg.parent)
# As fallback, just return the single ad within the wg
return [wg.ad and wg.ad.email_address()]
def get_group_chairs_emails(wg):
" Get list of area chairs' emails for a given WG "
if wg.acronym == 'none':
return []
emails = Email.objects.filter(role__group=wg,
role__name='chair')
if not emails:
return
emails = [e.email_address() for e in emails]
emails = filter(None, emails)
return emails
def get_area_chairs_emails(area):
emails = {}
# XXX - should we filter these by validity? Or not?
wgs = Group.objects.filter(parent=area, type="wg", state="active")
for wg in wgs:
for e in get_group_chairs_emails(wg):
emails[e] = True
return emails.keys()
def save_milestone_in_history(milestone):
h = get_history_object_for(milestone)
h.milestone = milestone

View file

@ -73,14 +73,14 @@ class PersonInfo(models.Model):
class Person(PersonInfo):
user = models.OneToOneField(User, blank=True, null=True)
def person(self): # little temporary wrapper to help porting to new schema
return self
class PersonHistory(PersonInfo):
person = models.ForeignKey(Person, related_name="history_set")
user = models.ForeignKey(User, blank=True, null=True)
class Alias(models.Model):
"""This is used for alternative forms of a name. This is the
primary lookup point for names, and should always contain the
@ -115,3 +115,14 @@ class Email(models.Model):
def invalid_address(self):
# we have some legacy authors with unknown email addresses
return self.address.startswith("unknown-email") and "@" not in self.address
def email_address(self):
"""Get valid, current email address; in practise, for active,
non-invalid addresses it is just the address field. In other
cases, we default to person's email address."""
if self.invalid_address() or not self.active:
if self.person:
return self.person.email_address()
return
return self.address

69
ietf/utils/aliases.py Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-
#
# $Id: aliasutil.py $
#
# Author: Markus Stenberg <mstenber@cisco.com>
#
"""
Mailing list alias dumping utilities
"""
def rewrite_email_address(email, is_ad):
""" Prettify the email address (and if it's empty, skip it by
returning None). """
if not email:
return
email = email.strip()
if not email:
return
if email[0]=='<' and email[-1] == '>':
email = email[1:-1]
# If it doesn't look like email, skip
if '@' not in email and '?' not in email:
return
return email
def rewrite_address_list(l):
""" This utility function makes sure there is exactly one instance
of an address within the result list, and preserves order
(although it may not be relevant to start with) """
h = {}
for address in l:
#address = address.strip()
if h.has_key(address): continue
h[address] = True
yield address
def dump_sublist(alias, f, wg, is_adlist=False):
if f:
l = f(wg)
else:
l = wg
if not l:
return
# Nones in the list should be skipped
l = filter(None, l)
# Make sure emails are sane and eliminate the Nones again for
# non-sane ones
l = [rewrite_email_address(e, is_adlist) for e in l]
l = filter(None, l)
# And we'll eliminate the duplicates too but preserve order
l = list(rewrite_address_list(l))
if not l:
return
try:
print '%s: %s' % (alias, ', '.join(l))
except UnicodeEncodeError:
# If there's unicode in email address, something is badly
# wrong and we just silently punt
# XXX - is there better approach?
print '# Error encoding', alias, repr(l)
return
return l