Skip to content

Commit df37793

Browse files
Use Roles instead of dedicated model for liaison statement group contacts. Fixes ietf-tools#3100. Commit ready for merge.
- Legacy-Id: 18828
1 parent 4dd013e commit df37793

16 files changed

Lines changed: 496 additions & 96 deletions

ietf/bin/set_admin_permissions

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ def main():
6161
'group.add_groupevent','group.change_groupevent','group.delete_groupevent',
6262
'iesg.add_telechatagendaitem','iesg.change_telechatagendaitem','iesg.delete_telechatagendaitem',
6363
'iesg.add_telechatdate','iesg.change_telechatdate','iesg.delete_telechatdate',
64-
'liaisons.add_liaisonstatementgroupcontacts','liaisons.change_liaisonstatementgroupcontacts',
65-
'liaisons.delete_liaisonstatementgroupcontacts',
6664
'mailinglists.add_list','mailinglists.change_list','mailinglists.delete_list',
6765
'mailtrigger.add_mailtrigger','mailtrigger.change_mailtrigger','mailtrigger.delete_mailtrigger',
6866
'mailtrigger.add_recipient','mailtrigger.change_recipient','mailtrigger.delete_recipient',
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 2.2.17 on 2020-12-11 08:48
2+
3+
from django.db import migrations
4+
import jsonfield.fields
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('group', '0039_remove_historicalgroupfeatures'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='group',
16+
name='used_roles',
17+
field=jsonfield.fields.JSONField(blank=True, default=[], help_text="Leave an empty list to get the group_type's default used roles", max_length=256),
18+
),
19+
migrations.AlterField(
20+
model_name='groupfeatures',
21+
name='default_used_roles',
22+
field=jsonfield.fields.JSONField(default=[], max_length=256),
23+
),
24+
migrations.AlterField(
25+
model_name='grouphistory',
26+
name='used_roles',
27+
field=jsonfield.fields.JSONField(blank=True, default=[], help_text="Leave an empty list to get the group_type's default used roles", max_length=256),
28+
),
29+
# historicalgroupfeatures has been removed
30+
# migrations.AlterField(
31+
# model_name='historicalgroupfeatures',
32+
# name='default_used_roles',
33+
# field=jsonfield.fields.JSONField(default=[], max_length=256),
34+
# ),
35+
]
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Generated by Django 2.2.17 on 2020-12-09 06:59
2+
3+
from django.db import migrations
4+
5+
from ietf.person.name import plain_name
6+
from ietf.utils.mail import parseaddr
7+
8+
9+
def find_or_create_email(email_model, person_model, formatted_email, group):
10+
"""Look up an email address or create if needed
11+
12+
Also creates a Person if the email does not have one. Created Email will have
13+
the origin field set to the origin parameter to this method.
14+
"""
15+
name, address = parseaddr(formatted_email)
16+
if not address:
17+
raise ValueError('Could not parse email "%s"' % formatted_email)
18+
email, _ = email_model.objects.get_or_create(
19+
address=address,
20+
defaults=dict(origin='liaison contact: ' + group.acronym)
21+
)
22+
23+
if not email.person:
24+
person = person_model.objects.create(name=name if name else address)
25+
email.person = person
26+
email.save()
27+
28+
# Display an alert if the formatted address sent from the Role will differ
29+
# from what was in the original contacts list
30+
if not email.person.plain and email.person.name == email.address:
31+
recreated_contact_email = email.address
32+
else:
33+
person_plain = email.person.plain if email.person.plain else plain_name(email.person.name)
34+
recreated_contact_email = "%s <%s>" % (person_plain, email.address)
35+
if recreated_contact_email != formatted_email:
36+
print('>> Note: address "%s" is now "%s" (%s)' % (
37+
formatted_email,
38+
recreated_contact_email,
39+
group.acronym,
40+
))
41+
return email
42+
43+
44+
def forward(apps, schema_editor):
45+
"""Perform forward migration
46+
47+
Creates liaison_contact and liaison_cc_contact Roles corresponding to existing
48+
LiaisonStatementGroupContact instances.
49+
"""
50+
Group = apps.get_model('group', 'Group')
51+
Role = apps.get_model('group', 'Role')
52+
Email = apps.get_model('person', 'Email')
53+
Person = apps.get_model('person', 'Person')
54+
55+
RoleName = apps.get_model('name', 'RoleName')
56+
contact_role_name = RoleName.objects.get(slug='liaison_contact')
57+
cc_contact_role_name = RoleName.objects.get(slug='liaison_cc_contact')
58+
59+
print()
60+
LiaisonStatementGroupContacts = apps.get_model('liaisons', 'LiaisonStatementGroupContacts')
61+
for lsgc in LiaisonStatementGroupContacts.objects.all():
62+
group = lsgc.group
63+
for contact_email in lsgc.contacts.split(','):
64+
if contact_email:
65+
email = find_or_create_email(Email, Person,
66+
contact_email.strip(),
67+
group)
68+
Role.objects.create(
69+
group=group,
70+
name=contact_role_name,
71+
person=email.person,
72+
email=email,
73+
)
74+
75+
for contact_email in lsgc.cc_contacts.split(','):
76+
if contact_email:
77+
email = find_or_create_email(Email, Person,
78+
contact_email.strip(),
79+
group)
80+
Role.objects.create(
81+
group=group,
82+
name=cc_contact_role_name,
83+
person=email.person,
84+
email=email,
85+
)
86+
87+
# Now validate that we got them all. As much as possible, use independent code
88+
# to avoid replicating any bugs from the original migration.
89+
for group in Group.objects.all():
90+
lsgc = LiaisonStatementGroupContacts.objects.filter(group_id=group.pk).first()
91+
92+
if not lsgc:
93+
if group.role_set.filter(name__in=[contact_role_name, cc_contact_role_name]).exists():
94+
raise ValueError('%s group has contact roles after migration but had no LiaisonStatementGroupContacts' % (
95+
group.acronym,
96+
))
97+
else:
98+
contacts = group.role_set.filter(name=contact_role_name)
99+
num_lsgc_contacts = len(lsgc.contacts.split(',')) if lsgc.contacts else 0
100+
if len(contacts) != num_lsgc_contacts:
101+
raise ValueError(
102+
'%s group has %d contact(s) but only %d address(es) in its LiaisonStatementGroupContacts (contact addresses = "%s", LSGC.contacts="%s")' % (
103+
group.acronym, len(contacts), num_lsgc_contacts,
104+
'","'.join([c.email.address for c in contacts]),
105+
lsgc.contacts,
106+
)
107+
)
108+
for contact in contacts:
109+
email = contact.email.address
110+
if email.lower() not in lsgc.contacts.lower():
111+
raise ValueError(
112+
'%s group has "%s" contact but not found in LiaisonStatementGroupContacts.contacts = "%s"' % (
113+
group.acronym, email, lsgc.contacts,
114+
)
115+
)
116+
117+
cc_contacts = group.role_set.filter(name=cc_contact_role_name)
118+
num_lsgc_cc_contacts = len(lsgc.cc_contacts.split(',')) if lsgc.cc_contacts else 0
119+
if len(cc_contacts) != num_lsgc_cc_contacts:
120+
raise ValueError(
121+
'%s group has %d CC contact(s) but %d address(es) in its LiaisonStatementGroupContacts (cc_contact addresses = "%s", LSGC.cc_contacts="%s")' % (
122+
group.acronym, len(cc_contacts), num_lsgc_cc_contacts,
123+
'","'.join([c.email.address for c in cc_contacts]),
124+
lsgc.cc_contacts,
125+
)
126+
)
127+
for cc_contact in cc_contacts:
128+
email = cc_contact.email.address
129+
if email.lower() not in lsgc.cc_contacts.lower():
130+
raise ValueError(
131+
'%s group has "%s" CC contact but not found in LiaisonStatementGroupContacts.cc_contacts = "%s"' % (
132+
group.acronym, email, lsgc.cc_contacts,
133+
)
134+
)
135+
136+
def reverse(apps, schema_editor):
137+
"""Perform reverse migration
138+
139+
Removes liaison_contact and liaison_cc_contact Roles. The forward migration creates missing
140+
Email and Person instances, but these are not removed because it's difficult to do this
141+
safely and correctly.
142+
"""
143+
Role = apps.get_model('group', 'Role')
144+
Role.objects.filter(
145+
name_id__in=['liaison_contact', 'liaison_cc_contact']
146+
).delete()
147+
148+
149+
class Migration(migrations.Migration):
150+
151+
dependencies = [
152+
('group', '0040_lengthen_used_roles_fields'),
153+
('name', '0022_add_liaison_contact_rolenames'),
154+
]
155+
156+
operations = [
157+
migrations.RunPython(forward, reverse),
158+
]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Generated by Django 2.2.17 on 2020-12-11 08:52
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
role_names_to_add = ['liaison_contact', 'liaison_cc_contact']
8+
9+
Group = apps.get_model('group', 'Group')
10+
GroupFeatures = apps.get_model('group', 'GroupFeatures')
11+
Role = apps.get_model('group', 'Role')
12+
13+
# Add new liaison contact roles to default_used_fields for wg, sdo, and area groups
14+
for group_type in ['wg', 'sdo', 'area']:
15+
gf = GroupFeatures.objects.get(type_id=group_type)
16+
for role_name in role_names_to_add:
17+
if role_name not in gf.default_used_roles:
18+
gf.default_used_roles.append(role_name)
19+
gf.save()
20+
21+
# Add new role names to any groups that both have liaison contacts
22+
# and use a custom used_roles list.
23+
for group in Group.objects.filter(type_id=group_type):
24+
used_roles_is_set = len(group.used_roles) > 0
25+
has_contacts = Role.objects.filter(name_id__in=role_names_to_add).exists()
26+
if used_roles_is_set and has_contacts:
27+
for role_name in role_names_to_add:
28+
if role_name not in group.used_roles:
29+
print('>> Adding %s to used_roles for %s' % (role_name, group.acronym))
30+
group.used_roles.append(role_name)
31+
group.save()
32+
33+
34+
def reverse(apps, schema_editor):
35+
role_names_to_remove = ['liaison_contact', 'liaison_cc_contact']
36+
37+
Group = apps.get_model('group', 'Group')
38+
GroupFeatures = apps.get_model('group', 'GroupFeatures')
39+
40+
for group in Group.objects.all():
41+
for role_name in role_names_to_remove:
42+
if role_name in group.used_roles:
43+
print('>> Removing %s from used_roles for %s' % (role_name, group.acronym))
44+
group.used_roles.remove(role_name)
45+
group.save()
46+
47+
for gf in GroupFeatures.objects.all():
48+
for role_name in role_names_to_remove:
49+
if role_name in gf.default_used_roles:
50+
print('>> Removing %s from default_used_roles for %s' % (role_name, gf.type_id))
51+
gf.default_used_roles.remove(role_name)
52+
gf.save()
53+
54+
55+
class Migration(migrations.Migration):
56+
57+
dependencies = [
58+
('group', '0041_create_liaison_contact_roles'),
59+
]
60+
61+
operations = [
62+
migrations.RunPython(forward, reverse),
63+
]

ietf/group/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class GroupInfo(models.Model):
4545
unused_states = models.ManyToManyField('doc.State', help_text="Document states that have been disabled for the group.", blank=True)
4646
unused_tags = models.ManyToManyField(DocTagName, help_text="Document tags that have been disabled for the group.", blank=True)
4747

48-
used_roles = jsonfield.JSONField(max_length=128, blank=True, default=[], help_text="Leave an empty list to get the group_type's default used roles")
48+
used_roles = jsonfield.JSONField(max_length=256, blank=True, default=[], help_text="Leave an empty list to get the group_type's default used roles")
4949

5050
uses_milestone_dates = models.BooleanField(default=True)
5151

@@ -272,7 +272,7 @@ class GroupFeatures(models.Model):
272272
about_page = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
273273
default_tab = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
274274
material_types = jsonfield.JSONField(max_length=64, blank=False, default=["slides"])
275-
default_used_roles = jsonfield.JSONField(max_length=128, blank=False, default=[])
275+
default_used_roles = jsonfield.JSONField(max_length=256, blank=False, default=[])
276276
admin_roles = jsonfield.JSONField(max_length=64, blank=False, default=["chair"]) # Trac Admin
277277
docman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
278278
groupman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair",])

0 commit comments

Comments
 (0)