Add support for detecting and displaying AD conflicts.

- Legacy-Id: 18046
This commit is contained in:
Ole Laursen 2020-06-24 12:42:00 +00:00
parent 3b31ed9ad1
commit 44cbfca7bd
4 changed files with 48 additions and 27 deletions

View file

@ -1013,11 +1013,13 @@ class EditTests(TestCase):
constraints = e.find(".constraints > span")
s_other = s2 if s == s1 else s1
self.assertEqual(len(constraints), 2)
self.assertEqual(len(constraints), 3)
self.assertEqual(constraints.eq(0).attr("data-sessions"), str(s_other.pk))
self.assertEqual(constraints.eq(0).find(".fa-user-o").parent().text(), "1") # 1 person in the constraint
self.assertEqual(constraints.eq(1).attr("data-sessions"), str(s_other.pk))
self.assertEqual(constraints.find(".encircled").text(), "1" if s_other == s2 else "-1")
self.assertEqual(constraints.find(".fa-user-o").parent().text(), "1") # 1 person in the constraint
self.assertEqual(constraints.eq(1).find(".encircled").text(), "1" if s_other == s2 else "-1")
self.assertEqual(constraints.eq(2).attr("data-sessions"), str(s_other.pk))
self.assertEqual(constraints.eq(2).find(".encircled").text(), "AD")
# session info for the panel
self.assertIn(str(round(s.requested_duration.total_seconds() / 60.0 / 60, 1)), e.find(".session-info .title").text())

View file

@ -24,7 +24,7 @@ from ietf.group.models import Group, Role
from ietf.group.utils import can_manage_materials
from ietf.name.models import SessionStatusName, ConstraintName
from ietf.nomcom.utils import DISQUALIFYING_ROLE_QUERY_EXPRESSION
from ietf.person.models import Email
from ietf.person.models import Person, Email
from ietf.secr.proceedings.proc_utils import import_audio_files
def session_time_for_sorting(session, use_meeting_date):
@ -319,12 +319,27 @@ def reverse_editor_label(label):
else:
return reverse_sign + label
def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
constraints = Constraint.objects.filter(meeting=meeting).prefetch_related('target', 'person', 'timeranges')
# process constraint names
def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions, responsible_ad_for_group):
# process constraint names - we synthesize extra names to be able
# to treat the concepts in the same manner as the modelled ones
constraint_names = {n.pk: n for n in ConstraintName.objects.all()}
joint_with_groups_constraint_name = ConstraintName(
slug='joint_with_groups',
name="Joint session with",
editor_label="<i class=\"fa fa-clone\"></i>",
order=8,
)
constraint_names[joint_with_groups_constraint_name.slug] = joint_with_groups_constraint_name
ad_constraint_name = ConstraintName(
slug='responsible_ad',
name="Responsible AD",
editor_label="<span class=\"encircled\">AD</span>",
order=9,
)
constraint_names[ad_constraint_name.slug] = ad_constraint_name
for n in list(constraint_names.values()):
# add reversed version of the name
reverse_n = ConstraintName(
@ -338,12 +353,23 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
n.countless_formatted_editor_label = format_html(n.formatted_editor_label, count="") if "{count}" in n.formatted_editor_label else n.formatted_editor_label
# convert human-readable rules in the database to constraints on actual sessions
constraints = list(Constraint.objects.filter(meeting=meeting).prefetch_related('target', 'person', 'timeranges'))
# synthesize AD constraints - we can treat them as a special kind of 'bethere'
ad_person_lookup = {p.pk: p for p in Person.objects.filter(pk__in=set(responsible_ad_for_group.values()))}
groups_at_meeting = {s.group for s in sessions}
for group in groups_at_meeting:
ad = ad_person_lookup.get(responsible_ad_for_group.get(group.pk))
if ad is not None:
constraints.append(Constraint(meeting=meeting, source=group, person=ad, name=ad_constraint_name))
# process must not be scheduled at the same time constraints
constraints_for_sessions = defaultdict(list)
person_needed_for_groups = defaultdict(set)
person_needed_for_groups = {cn.slug: defaultdict(set) for cn in constraint_names.values()}
for c in constraints:
if c.name_id == 'bethere' and c.person_id is not None:
person_needed_for_groups[c.person_id].add(c.source_id)
if c.person_id is not None:
person_needed_for_groups[c.name_id][c.person_id].add(c.source_id)
sessions_for_group = defaultdict(list)
for s in sessions:
@ -367,7 +393,7 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
reverse_constraints.append(c)
elif c.person_id:
for g in person_needed_for_groups.get(c.person_id):
for g in person_needed_for_groups[c.name_id].get(c.person_id):
add_group_constraints(c.source_id, g, c.name_id, c.person_id)
for c in reverse_constraints:
@ -394,17 +420,7 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
for s_pk in sessions_for_group.get(group_pk, []):
formatted_constraints_for_sessions[s_pk][constraint_names[cn_pk]] = [format_constraint(c) for c in cs]
# it's easier for the rest of the code if we treat
# joint_with_groups as a constraint, even if it's not modelled as
# one
joint_with_groups_constraint_name = ConstraintName(
slug='joint_with_groups',
name="Joint session with",
editor_label="<i class=\"fa fa-clone\"></i>",
order=8,
)
joint_with_groups_constraint_name.formatted_editor_label = mark_safe(joint_with_groups_constraint_name.editor_label)
joint_with_groups_constraint_name.countless_formatted_editor_label = joint_with_groups_constraint_name.formatted_editor_label
# synthesize joint_with_groups constraints
for s in sessions:
joint_groups = s.joint_with_groups.all()
if joint_groups:

View file

@ -588,20 +588,23 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
# dig out historic AD names
ad_names = {}
ad_pks = {}
session_groups = set(s.group for s in sessions if s.group and s.group.parent and s.group.parent.type_id == 'area')
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
for group_id, history_time, name in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'name').order_by('rolehistory__group__time'):
for group_id, history_time, name, pk in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'name', 'pk').order_by('rolehistory__group__time'):
ad_names[group_id] = plain_name(name)
ad_pks[group_id] = pk
for group_id, name in Person.objects.filter(role__name='ad', role__group__in=session_groups, role__group__time__lte=meeting_time).values_list('role__group', 'name'):
for group_id, name, pk in Person.objects.filter(role__name='ad', role__group__in=session_groups, role__group__time__lte=meeting_time).values_list('role__group', 'name', 'pk'):
ad_names[group_id] = plain_name(name)
ad_pks[group_id] = pk
# requesters
requested_by_lookup = {p.pk: p for p in Person.objects.filter(pk__in=set(s.requested_by for s in sessions if s.requested_by))}
# constraints
constraints_for_sessions, formatted_constraints_for_sessions, constraint_names = preprocess_constraints_for_meeting_schedule_editor(meeting, sessions)
constraints_for_sessions, formatted_constraints_for_sessions, constraint_names = preprocess_constraints_for_meeting_schedule_editor(meeting, sessions, ad_pks)
sessions_for_group = defaultdict(list)
for s in sessions:

View file

@ -1089,7 +1089,7 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
.edit-meeting-schedule .formatted-constraints .encircled {
border: 1px solid #000;
border-radius: 1em;
min-width: 1.3em;
padding: 0 0.3em;
text-align: center;
display: inline-block;
}