Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ietf/liaisons/mails.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
def send_liaison_by_email(request, liaison):
subject = 'New Liaison Statement, "%s"' % (liaison.title)
from_email = settings.LIAISON_UNIVERSAL_FROM
(to_email, cc) = gather_address_lists('liaison_statement_posted',liaison=liaison)
if liaison.is_outgoing():
(to_email, cc) = gather_address_lists('liaison_statement_posted_outgoing',liaison=liaison)
else:
(to_email, cc) = gather_address_lists('liaison_statement_posted_incoming',liaison=liaison)
bcc = ['statements@ietf.org']
body = render_to_string('liaisons/liaison_mail.txt', dict(liaison=liaison))

Expand Down
39 changes: 22 additions & 17 deletions ietf/liaisons/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,61 +112,61 @@ def test_help_pages(self):


class UnitTests(TestCase):
def test_get_cc(self):
from ietf.liaisons.views import get_cc,EMAIL_ALIASES
def test_get_contacts_for_liaison_messages_for_group_primary(self):
from ietf.mailtrigger.utils import get_contacts_for_liaison_messages_for_group_primary,EMAIL_ALIASES

# test IETF
cc = get_cc(Group.objects.get(acronym='ietf'))
cc = get_contacts_for_liaison_messages_for_group_primary(Group.objects.get(acronym='ietf'))
self.assertTrue(EMAIL_ALIASES['IESG'] in cc)
self.assertTrue(EMAIL_ALIASES['IETFCHAIR'] in cc)
# test IAB
cc = get_cc(Group.objects.get(acronym='iab'))
cc = get_contacts_for_liaison_messages_for_group_primary(Group.objects.get(acronym='iab'))
self.assertTrue(EMAIL_ALIASES['IAB'] in cc)
self.assertTrue(EMAIL_ALIASES['IABCHAIR'] in cc)
# test an Area
area = Group.objects.filter(type='area').first()
cc = get_cc(area)
cc = get_contacts_for_liaison_messages_for_group_primary(area)
self.assertTrue(EMAIL_ALIASES['IETFCHAIR'] in cc)
self.assertTrue(contacts_from_roles([area.ad_role()]) in cc)
# test a Working Group
wg = Group.objects.filter(type='wg').first()
cc = get_cc(wg)
cc = get_contacts_for_liaison_messages_for_group_primary(wg)
self.assertTrue(contacts_from_roles([wg.parent.ad_role()]) in cc)
self.assertTrue(contacts_from_roles([wg.get_chair()]) in cc)
# test an SDO
sdo = RoleFactory(name_id='liaiman',group__type_id='sdo',).group
cc = get_cc(sdo)
cc = get_contacts_for_liaison_messages_for_group_primary(sdo)
self.assertTrue(contacts_from_roles([sdo.role_set.filter(name='liaiman').first()]) in cc)
# test a cc_contact role
cc_contact_role = RoleFactory(name_id='liaison_cc_contact', group=sdo)
cc = get_cc(sdo)
cc = get_contacts_for_liaison_messages_for_group_primary(sdo)
self.assertIn(contact_email_from_role(cc_contact_role), cc)

def test_get_contacts_for_group(self):
from ietf.liaisons.views import get_contacts_for_group, EMAIL_ALIASES
def test_get_contacts_for_liaison_messages_for_group_secondary(self):
from ietf.mailtrigger.utils import get_contacts_for_liaison_messages_for_group_secondary,EMAIL_ALIASES

# test explicit
# test explicit group contacts
sdo = GroupFactory(type_id='sdo')
contact_email = RoleFactory(name_id='liaison_contact', group=sdo).email.address
contacts = get_contacts_for_group(sdo)
contacts = get_contacts_for_liaison_messages_for_group_secondary(sdo)
self.assertIsNotNone(contact_email)
self.assertIn(contact_email, contacts)
# test area
area = Group.objects.filter(type='area').first()
contacts = get_contacts_for_group(area)
contacts = get_contacts_for_liaison_messages_for_group_secondary(area)
self.assertTrue(area.ad_role().email.address in contacts)
# test wg
wg = Group.objects.filter(type='wg').first()
contacts = get_contacts_for_group(wg)
contacts = get_contacts_for_liaison_messages_for_group_secondary(wg)
self.assertTrue(wg.get_chair().email.address in contacts)
# test ietf
contacts = get_contacts_for_group(Group.objects.get(acronym='ietf'))
contacts = get_contacts_for_liaison_messages_for_group_secondary(Group.objects.get(acronym='ietf'))
self.assertTrue(EMAIL_ALIASES['IETFCHAIR'] in contacts)
# test iab
contacts = get_contacts_for_group(Group.objects.get(acronym='iab'))
contacts = get_contacts_for_liaison_messages_for_group_secondary(Group.objects.get(acronym='iab'))
self.assertTrue(EMAIL_ALIASES['IABCHAIR'] in contacts)
# test iesg
contacts = get_contacts_for_group(Group.objects.get(acronym='iesg'))
contacts = get_contacts_for_liaison_messages_for_group_secondary(Group.objects.get(acronym='iesg'))
self.assertTrue(EMAIL_ALIASES['IESG'] in contacts)

def test_needs_approval(self):
Expand Down Expand Up @@ -786,8 +786,11 @@ def test_add_incoming_liaison(self):
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])

self.assertTrue('to_contacts@' in outbox[-1]['To'])
self.assertTrue(submitter.email_address(), outbox[-1]['To'])
self.assertTrue('cc@' in outbox[-1]['Cc'])



def test_add_outgoing_liaison(self):
RoleFactory(name_id='liaiman',group__type_id='sdo', person__user__username='ulm-liaiman')
wg = RoleFactory(name_id='chair',person__user__username='marschairman',group__acronym='mars').group
Expand Down Expand Up @@ -867,6 +870,8 @@ def test_add_outgoing_liaison(self):
self.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
self.assertTrue('aread@' in outbox[-1]['To'])
self.assertTrue(submitter.email_address(), outbox[-1]['Cc'])


def test_add_outgoing_liaison_unapproved_post_only(self):
RoleFactory(name_id='liaiman',group__type_id='sdo', person__user__username='ulm-liaiman')
Expand Down
76 changes: 6 additions & 70 deletions ietf/liaisons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@
from ietf.name.models import LiaisonStatementTagName
from ietf.utils.response import permission_denied

EMAIL_ALIASES = {
"IETFCHAIR": "The IETF Chair <chair@ietf.org>",
"IESG": "The IESG <iesg@ietf.org>",
"IAB": "The IAB <iab@iab.org>",
"IABCHAIR": "The IAB Chair <iab-chair@iab.org>",
}


# -------------------------------------------------
# Helper Functions
# -------------------------------------------------
Expand Down Expand Up @@ -94,64 +86,6 @@ def contacts_from_roles(roles):
emails = [ contact_email_from_role(r) for r in roles ]
return ','.join(emails)

def get_cc(group):
'''Returns list of emails to use as CC for group. Simplified refactor of IETFHierarchy
get_cc() and get_from_cc()
'''
emails = []

# role based CCs
if group.acronym in ('ietf','iesg'):
emails.append(EMAIL_ALIASES['IESG'])
emails.append(EMAIL_ALIASES['IETFCHAIR'])
elif group.acronym in ('iab'):
emails.append(EMAIL_ALIASES['IAB'])
emails.append(EMAIL_ALIASES['IABCHAIR'])
elif group.type_id == 'area':
emails.append(EMAIL_ALIASES['IETFCHAIR'])
ad_roles = group.role_set.filter(name='ad')
emails.extend([ contact_email_from_role(r) for r in ad_roles ])
elif group.type_id == 'wg':
ad_roles = group.parent.role_set.filter(name='ad')
emails.extend([ contact_email_from_role(r) for r in ad_roles ])
chair_roles = group.role_set.filter(name='chair')
emails.extend([ contact_email_from_role(r) for r in chair_roles ])
if group.list_email:
emails.append('{} Discussion List <{}>'.format(group.name,group.list_email))
elif group.type_id == 'sdo':
liaiman_roles = group.role_set.filter(name='liaiman')
emails.extend([ contact_email_from_role(r) for r in liaiman_roles ])

# explicit CCs
liaison_cc_roles = group.role_set.filter(name='liaison_cc_contact')
emails.extend([ contact_email_from_role(r) for r in liaison_cc_roles ])

return emails

def get_contacts_for_group(group):
'''Returns default contacts for groups as a comma separated string'''
# use explicit default contacts if defined
explicit_contacts = contacts_from_roles(group.role_set.filter(name='liaison_contact'))
if explicit_contacts:
return explicit_contacts

# otherwise construct based on group type
contacts = []
if group.type_id == 'area':
roles = group.role_set.filter(name='ad')
contacts.append(contacts_from_roles(roles))
elif group.type_id == 'wg':
roles = group.role_set.filter(name='chair')
contacts.append(contacts_from_roles(roles))
elif group.acronym == 'ietf':
contacts.append(EMAIL_ALIASES['IETFCHAIR'])
elif group.acronym == 'iab':
contacts.append(EMAIL_ALIASES['IABCHAIR'])
elif group.acronym == 'iesg':
contacts.append(EMAIL_ALIASES['IESG'])

return ','.join(contacts)

def get_details_tabs(stmt, selected):
return [
t + (t[0].lower() == selected.lower(),)
Expand Down Expand Up @@ -207,6 +141,8 @@ def post_only(group,person):
# -------------------------------------------------
@can_submit_liaison_required
def ajax_get_liaison_info(request):
from ietf.mailtrigger.utils import get_contacts_for_liaison_messages_for_group_primary,get_contacts_for_liaison_messages_for_group_secondary

'''Returns dictionary of info to update entry form given the groups
that have been selected
'''
Expand All @@ -229,14 +165,14 @@ def ajax_get_liaison_info(request):
result = {'response_contacts':[],'to_contacts': [], 'cc': [], 'needs_approval': False, 'post_only': False, 'full_list': []}

for group in from_groups:
cc.extend(get_cc(group))
cc.extend(get_contacts_for_liaison_messages_for_group_primary(group))
does_need_approval.append(needs_approval(group,person))
can_post_only.append(post_only(group,person))
response_contacts.append(get_contacts_for_group(group))
response_contacts.append(get_contacts_for_liaison_messages_for_group_secondary(group))

for group in to_groups:
cc.extend(get_cc(group))
to_contacts.append(get_contacts_for_group(group))
cc.extend(get_contacts_for_liaison_messages_for_group_primary(group))
to_contacts.append(get_contacts_for_liaison_messages_for_group_secondary(group))

# if there are from_groups and any need approval
if does_need_approval:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright The IETF Trust 2025, All Rights Reserved

from django.db import migrations


def forward(apps, schema_editor):
Mailtrigger = apps.get_model("mailtrigger", "MailTrigger")
Recipient = apps.get_model("mailtrigger", "Recipient")
recipients_to = Recipient.objects.get(pk="liaison_to_contacts")
recipients_cc = list(
Recipient.objects.filter(
slug__in=(
"liaison_cc",
"liaison_coordinators",
"liaison_response_contacts",
"liaison_technical_contacts",
)
)
)
recipient_from = Recipient.objects.get(pk="liaison_from_contact")

liaison_posted_outgoing = Mailtrigger.objects.create(
slug="liaison_statement_posted_outgoing",
desc="Recipients for a message when a new outgoing liaison statement is posted",
)
liaison_posted_outgoing.to.add(recipients_to)
liaison_posted_outgoing.cc.add(*recipients_cc)
liaison_posted_outgoing.cc.add(recipient_from)

liaison_posted_incoming = Mailtrigger.objects.create(
slug="liaison_statement_posted_incoming",
desc="Recipients for a message when a new incoming liaison statement is posted",
)
liaison_posted_incoming.to.add(recipients_to)
liaison_posted_incoming.cc.add(*recipients_cc)

Mailtrigger.objects.filter(slug=("liaison_statement_posted")).delete()


def reverse(apps, schema_editor):
Mailtrigger = apps.get_model("mailtrigger", "MailTrigger")
Recipient = apps.get_model("mailtrigger", "Recipient")

Mailtrigger.objects.filter(
slug__in=(
"liaison_statement_posted_outgoing",
"liaison_statement_posted_incoming",
)
).delete()

liaison_statement_posted = Mailtrigger.objects.create(
slug="liaison_statement_posted",
desc="Recipients for a message when a new liaison statement is posted",
)

liaison_to_contacts = Recipient.objects.get(slug="liaison_to_contacts")
recipients_ccs = Recipient.objects.filter(
slug__in=(
"liaison_cc",
"liaison_coordinators",
"liaison_response_contacts",
"liaison_technical_contacts",
)
)
liaison_statement_posted.to.add(liaison_to_contacts)
liaison_statement_posted.cc.add(*recipients_ccs)


class Migration(migrations.Migration):
dependencies = [("mailtrigger", "0007_historicalrecipient_historicalmailtrigger")]

operations = [migrations.RunPython(forward, reverse)]
71 changes: 71 additions & 0 deletions ietf/mailtrigger/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
from ietf.utils.mail import excludeaddrs


EMAIL_ALIASES = {
"IETFCHAIR": "The IETF Chair <chair@ietf.org>",
"IESG": "The IESG <iesg@ietf.org>",
"IAB": "The IAB <iab@iab.org>",
"IABCHAIR": "The IAB Chair <iab-chair@iab.org>",
}


class AddrLists(namedtuple("AddrLists", ["to", "cc"])):
__slots__ = ()

Expand Down Expand Up @@ -66,6 +74,69 @@ def get_mailtrigger(slug, create_from_slug_if_not_exists, desc_if_not_exists):
return mailtrigger


def get_contacts_for_liaison_messages_for_group_primary(group):
from ietf.liaisons.views import contact_email_from_role

'''Returns list of emails to use in liaison message for group
'''
emails = []

# role based emails
if group.acronym in ('ietf','iesg'):
emails.append(EMAIL_ALIASES['IESG'])
emails.append(EMAIL_ALIASES['IETFCHAIR'])
elif group.acronym in ('iab'):
emails.append(EMAIL_ALIASES['IAB'])
emails.append(EMAIL_ALIASES['IABCHAIR'])
elif group.type_id == 'area':
emails.append(EMAIL_ALIASES['IETFCHAIR'])
ad_roles = group.role_set.filter(name='ad')
emails.extend([ contact_email_from_role(r) for r in ad_roles ])
elif group.type_id == 'wg':
ad_roles = group.parent.role_set.filter(name='ad')
emails.extend([ contact_email_from_role(r) for r in ad_roles ])
chair_roles = group.role_set.filter(name='chair')
emails.extend([ contact_email_from_role(r) for r in chair_roles ])
if group.list_email:
emails.append('{} Discussion List <{}>'.format(group.name,group.list_email))
elif group.type_id == 'sdo':
liaiman_roles = group.role_set.filter(name='liaiman')
emails.extend([ contact_email_from_role(r) for r in liaiman_roles ])

# explicit CCs
liaison_cc_roles = group.role_set.filter(name='liaison_cc_contact')
emails.extend([ contact_email_from_role(r) for r in liaison_cc_roles ])

return emails


def get_contacts_for_liaison_messages_for_group_secondary(group):
from ietf.liaisons.views import contacts_from_roles

'''Returns default contacts for groups as a comma separated string'''
# use explicit default contacts if defined
explicit_contacts = contacts_from_roles(group.role_set.filter(name='liaison_contact'))
if explicit_contacts:
return explicit_contacts

# otherwise construct based on group type
contacts = []
if group.type_id == 'area':
roles = group.role_set.filter(name='ad')
contacts.append(contacts_from_roles(roles))
elif group.type_id == 'wg':
roles = group.role_set.filter(name='chair')
contacts.append(contacts_from_roles(roles))
elif group.acronym == 'ietf':
contacts.append(EMAIL_ALIASES['IETFCHAIR'])
elif group.acronym == 'iab':
contacts.append(EMAIL_ALIASES['IABCHAIR'])
elif group.acronym == 'iesg':
contacts.append(EMAIL_ALIASES['IESG'])

return ','.join(contacts)


def gather_relevant_expansions(**kwargs):
def starts_with(prefix):
return MailTrigger.objects.filter(slug__startswith=prefix).values_list(
Expand Down
Loading
Loading