Skip to content

Commit 66b9c41

Browse files
Add ConstraintNames for chair, tech, and key participant conflicts. Replace temporary UI workaround with proper conflict type handling. Fixes ietf-tools#3083. Commit ready for merge.
- Legacy-Id: 19103
1 parent c8ed251 commit 66b9c41

17 files changed

Lines changed: 758 additions & 446 deletions

ietf/meeting/management/commands/generate_schedule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ def __init__(self, stdout, meeting, session_db, business_constraint_costs, verbo
583583
self.last_cost = None
584584

585585
for constraint_db in constraints_db:
586-
if constraint_db.name.slug in ['conflict', 'conflic2', 'conflic3']:
586+
if constraint_db.name.is_group_conflict:
587587
self.conflict_groups[constraint_db.target.acronym] += constraint_db.name.penalty
588588
elif constraint_db.name.slug == 'bethere':
589589
self.conflict_people.add(constraint_db.person.pk)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Generated by Django 2.2.20 on 2021-05-13 07:51
2+
3+
from django.db import migrations
4+
5+
6+
replacement_slugs = (
7+
('conflict', 'chair_conflict'),
8+
('conflic2', 'tech_overlap'),
9+
('conflic3', 'key_participant'),
10+
)
11+
12+
13+
def affected(constraint_qs):
14+
"""Filter constraints, keeping only those to be updated"""
15+
# The constraints were renamed in the UI in commit 16699 on 2019-09-03.
16+
# This was between meetings 105 and 106. Assuming this migration and
17+
# the new conflict types are in place before meeting 111, these
18+
# are the meetings for which the UI disagreed with the constraint
19+
# type actually created.
20+
affected_meetings = ['106', '107', '108', '109', '110']
21+
return constraint_qs.filter(meeting__number__in=affected_meetings)
22+
23+
24+
def forward(apps, schema_editor):
25+
Constraint = apps.get_model('meeting', 'Constraint')
26+
affected_constraints = affected(Constraint.objects.all())
27+
for old, new in replacement_slugs:
28+
affected_constraints.filter(name_id=old).update(name_id=new)
29+
30+
31+
def reverse(apps, schema_editor):
32+
Constraint = apps.get_model('meeting', 'Constraint')
33+
affected_constraints = affected(Constraint.objects.all())
34+
for old, new in replacement_slugs:
35+
affected_constraints.filter(name_id=new).update(name_id=old)
36+
37+
38+
class Migration(migrations.Migration):
39+
40+
dependencies = [
41+
('meeting', '0040_auto_20210130_1027'),
42+
('name', '0026_add_conflict_constraintnames'),
43+
]
44+
45+
operations = [
46+
migrations.RunPython(forward, reverse),
47+
]

ietf/meeting/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,26 @@ def get_number(self):
207207
else:
208208
return None
209209

210+
@property
211+
def session_constraintnames(self):
212+
"""Gets a list of the constraint names that should be used for this meeting
213+
214+
Anticipated that this will soon become a many-to-many relationship with ConstraintName
215+
(see issue #2770). Making this a @property allows use of the .all(), .filter(), etc,
216+
so that other code should not need changes when this is replaced.
217+
"""
218+
try:
219+
mtg_num = int(self.number)
220+
except ValueError:
221+
mtg_num = None # should not come up, but this method should not fail
222+
if mtg_num is None or mtg_num >= 106:
223+
# These meetings used the old 'conflic?' constraint types labeled as though
224+
# they were the new types.
225+
slugs = ('chair_conflict', 'tech_overlap', 'key_participant')
226+
else:
227+
slugs = ('conflict', 'conflic2', 'conflic3')
228+
return ConstraintName.objects.filter(slug__in=slugs)
229+
210230
def json_url(self):
211231
return "/meeting/%s/json" % (self.number, )
212232

ietf/name/fixtures/names.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5990,6 +5990,7 @@
59905990
"fields": {
59915991
"desc": "",
59925992
"editor_label": "<i class=\"fa fa-user-o\"></i>{count}",
5993+
"is_group_conflict": false,
59935994
"name": "Person must be present",
59945995
"order": 4,
59955996
"penalty": 10000,
@@ -5998,10 +5999,24 @@
59985999
"model": "name.constraintname",
59996000
"pk": "bethere"
60006001
},
6002+
{
6003+
"fields": {
6004+
"desc": "Indicates other WGs the chairs also lead or will be active participants in",
6005+
"editor_label": "<i class=\"fa fa-gavel\"></i>",
6006+
"is_group_conflict": true,
6007+
"name": "Chair conflict",
6008+
"order": 8,
6009+
"penalty": 100000,
6010+
"used": true
6011+
},
6012+
"model": "name.constraintname",
6013+
"pk": "chair_conflict"
6014+
},
60016015
{
60026016
"fields": {
60036017
"desc": "",
60046018
"editor_label": "<span class=\"encircled\">2</span>",
6019+
"is_group_conflict": true,
60056020
"name": "Conflicts with (secondary)",
60066021
"order": 2,
60076022
"penalty": 10000,
@@ -6014,6 +6029,7 @@
60146029
"fields": {
60156030
"desc": "",
60166031
"editor_label": "<span class=\"encircled\">3</span>",
6032+
"is_group_conflict": true,
60176033
"name": "Conflicts with (tertiary)",
60186034
"order": 3,
60196035
"penalty": 100000,
@@ -6026,6 +6042,7 @@
60266042
"fields": {
60276043
"desc": "",
60286044
"editor_label": "<span class=\"encircled\">1</span>",
6045+
"is_group_conflict": true,
60296046
"name": "Conflicts with",
60306047
"order": 1,
60316048
"penalty": 100000,
@@ -6034,10 +6051,37 @@
60346051
"model": "name.constraintname",
60356052
"pk": "conflict"
60366053
},
6054+
{
6055+
"fields": {
6056+
"desc": "Indicates WGs with which key participants (presenter, secretary, etc.) may overlap",
6057+
"editor_label": "<i class=\"fa fa-key\"></i>",
6058+
"is_group_conflict": true,
6059+
"name": "Key participant conflict",
6060+
"order": 10,
6061+
"penalty": 100000,
6062+
"used": true
6063+
},
6064+
"model": "name.constraintname",
6065+
"pk": "key_participant"
6066+
},
6067+
{
6068+
"fields": {
6069+
"desc": "Indicates WGs with a related technology or a closely related charter",
6070+
"editor_label": "<i class=\"fa fa-rocket\"></i>",
6071+
"is_group_conflict": true,
6072+
"name": "Technology overlap",
6073+
"order": 9,
6074+
"penalty": 10000,
6075+
"used": true
6076+
},
6077+
"model": "name.constraintname",
6078+
"pk": "tech_overlap"
6079+
},
60376080
{
60386081
"fields": {
60396082
"desc": "",
60406083
"editor_label": "&Delta;",
6084+
"is_group_conflict": false,
60416085
"name": "Preference for time between sessions",
60426086
"order": 6,
60436087
"penalty": 1000,
@@ -6050,6 +6094,7 @@
60506094
"fields": {
60516095
"desc": "",
60526096
"editor_label": "<i class=\"fa fa-calendar-o\"></i>",
6097+
"is_group_conflict": false,
60536098
"name": "Can't meet within timerange",
60546099
"order": 5,
60556100
"penalty": 1000000,
@@ -6062,6 +6107,7 @@
60626107
"fields": {
60636108
"desc": "",
60646109
"editor_label": "<i class=\"fa fa-step-forward\"></i>",
6110+
"is_group_conflict": false,
60656111
"name": "Request for adjacent scheduling with another WG",
60666112
"order": 7,
60676113
"penalty": 1000,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.20 on 2021-05-19 09:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
('name', '0023_change_stream_descriptions'),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name='constraintname',
14+
name='is_group_conflict',
15+
field=models.BooleanField(default=False,
16+
help_text='Does this constraint capture a conflict between groups?'),
17+
),
18+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 2.2.20 on 2021-05-19 09:55
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
"""Set is_group_conflict for ConstraintNames that need it to be True"""
8+
ConstraintName = apps.get_model('name', 'ConstraintName')
9+
ConstraintName.objects.filter(
10+
slug__in=['conflict', 'conflic2', 'conflic3']
11+
).update(is_group_conflict=True)
12+
13+
14+
def reverse(apps, schema_editor):
15+
pass # nothing to be done
16+
17+
18+
class Migration(migrations.Migration):
19+
dependencies = [
20+
('name', '0024_constraintname_is_group_conflict'),
21+
]
22+
23+
operations = [
24+
migrations.RunPython(forward, reverse),
25+
]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Generated by Django 2.2.20 on 2021-05-05 10:05
2+
3+
from collections import namedtuple
4+
from django.db import migrations
5+
from django.db.models import Max
6+
7+
8+
# Simple type for representing constraint name data that will be
9+
# modified.
10+
ConstraintInfo = namedtuple(
11+
'ConstraintInfo',
12+
['replaces', 'slug', 'name', 'desc', 'editor_label'],
13+
)
14+
15+
constraint_names_to_add = [
16+
ConstraintInfo(
17+
replaces='conflict',
18+
slug='chair_conflict',
19+
name='Chair conflict',
20+
desc='Indicates other WGs the chairs also lead or will be active participants in',
21+
editor_label='<i class="fa fa-gavel"></i>',
22+
),
23+
ConstraintInfo(
24+
replaces='conflic2',
25+
slug='tech_overlap',
26+
name='Technology overlap',
27+
desc='Indicates WGs with a related technology or a closely related charter',
28+
editor_label='<i class="fa fa-rocket"></i>',
29+
),
30+
ConstraintInfo(
31+
replaces='conflic3',
32+
slug='key_participant',
33+
name='Key participant conflict',
34+
desc='Indicates WGs with which key participants (presenter, secretary, etc.) may overlap',
35+
editor_label='<i class="fa fa-key"></i>',
36+
)
37+
]
38+
39+
40+
def forward(apps, schema_editor):
41+
ConstraintName = apps.get_model('name', 'ConstraintName')
42+
max_order = ConstraintName.objects.all().aggregate(Max('order'))['order__max']
43+
44+
for index, new_constraint in enumerate(constraint_names_to_add):
45+
# hack_constraint is the constraint type relabeled by the hack fix in #2754
46+
hack_constraint = ConstraintName.objects.get(slug=new_constraint.replaces)
47+
ConstraintName.objects.create(
48+
slug=new_constraint.slug,
49+
name=new_constraint.name,
50+
desc=new_constraint.desc,
51+
used=hack_constraint.used,
52+
order=max_order + index + 1,
53+
penalty=hack_constraint.penalty,
54+
editor_label=new_constraint.editor_label,
55+
is_group_conflict=True,
56+
)
57+
58+
59+
def reverse(apps, schema_editor):
60+
ConstraintName = apps.get_model('name', 'ConstraintName')
61+
for new_constraint in constraint_names_to_add:
62+
ConstraintName.objects.filter(slug=new_constraint.slug).delete()
63+
64+
class Migration(migrations.Migration):
65+
dependencies = [
66+
('name', '0025_set_constraintname_is_group_conflict'),
67+
# Reversing this migration requires that the 'day' field be removed from
68+
# the Constraint model, so we indirectly depend on the migration that
69+
# removed it.
70+
('meeting', '0027_add_constraint_options_and_joint_groups'),
71+
]
72+
73+
operations = [
74+
migrations.RunPython(forward, reverse),
75+
]

ietf/name/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class ConstraintName(NameModel):
7171
"""conflict, conflic2, conflic3, bethere, timerange, time_relation, wg_adjacent"""
7272
penalty = models.IntegerField(default=0, help_text="The penalty for violating this kind of constraint; for instance 10 (small penalty) or 10000 (large penalty)")
7373
editor_label = models.CharField(max_length=64, blank=True, help_text="Very short label for producing warnings inline in the sessions in the schedule editor.")
74+
is_group_conflict = models.BooleanField(default=False, help_text="Does this constraint capture a conflict between groups?")
7475
class TimerangeName(NameModel):
7576
"""(monday|tuesday|wednesday|thursday|friday)-(morning|afternoon-early|afternoon-late)"""
7677
class LiaisonStatementPurposeName(NameModel):

0 commit comments

Comments
 (0)