Merged [5902],[5904] from markus.stenberg@iki.fi:
Added support and scripts for generation of wg- and draft-aliases. Fixes issue #713. - Legacy-Id: 5937 Note: SVN reference [5902] has been migrated to Git commite5b551f4ce
Note: SVN reference [5904] has been migrated to Git commite68e51cc55
This commit is contained in:
parent
9f998e5bbc
commit
8675711c27
ietf
147
ietf/bin/generate-draft-aliases
Executable file
147
ietf/bin/generate-draft-aliases
Executable 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
91
ietf/bin/generate-wg-aliases
Executable 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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
69
ietf/utils/aliases.py
Normal 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
|
||||
|
Loading…
Reference in a new issue