Skip to content

Commit 1054f90

Browse files
Snapshot of dev work to add session purpose annotation
- Legacy-Id: 19415
1 parent 3386e59 commit 1054f90

61 files changed

Lines changed: 4331 additions & 724 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ietf/doc/templatetags/ietf_filters.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,93 @@ def action_holder_badge(action_holder):
613613
else:
614614
return '' # no alert needed
615615

616+
@register.filter
617+
def is_regular_agenda_item(assignment):
618+
"""Is this agenda item a regular session item?
619+
620+
A regular item appears as a sub-entry in a timeslot within the agenda
621+
622+
>>> from collections import namedtuple # use to build mock objects
623+
>>> mock_timeslot = namedtuple('t2', ['slug'])
624+
>>> mock_assignment = namedtuple('t1', ['slot_type']) # slot_type must be a callable
625+
>>> factory = lambda t: mock_assignment(slot_type=lambda: mock_timeslot(slug=t))
626+
>>> is_regular_agenda_item(factory('regular'))
627+
True
628+
629+
>>> any(is_regular_agenda_item(factory(t)) for t in ['plenary', 'break', 'reg', 'other', 'officehours'])
630+
False
631+
"""
632+
return assignment.slot_type().slug == 'regular'
633+
634+
@register.filter
635+
def is_plenary_agenda_item(assignment):
636+
"""Is this agenda item a regular session item?
637+
638+
A regular item appears as a sub-entry in a timeslot within the agenda
639+
640+
>>> from collections import namedtuple # use to build mock objects
641+
>>> mock_timeslot = namedtuple('t2', ['slug'])
642+
>>> mock_assignment = namedtuple('t1', ['slot_type']) # slot_type must be a callable
643+
>>> factory = lambda t: mock_assignment(slot_type=lambda: mock_timeslot(slug=t))
644+
>>> is_plenary_agenda_item(factory('plenary'))
645+
True
646+
647+
>>> any(is_plenary_agenda_item(factory(t)) for t in ['regular', 'break', 'reg', 'other', 'officehours'])
648+
False
649+
"""
650+
return assignment.slot_type().slug == 'plenary'
651+
652+
@register.filter
653+
def is_special_agenda_item(assignment):
654+
"""Is this agenda item a special item?
655+
656+
Special items appear as top-level agenda entries with their own timeslot information.
657+
658+
>>> from collections import namedtuple # use to build mock objects
659+
>>> mock_timeslot = namedtuple('t2', ['slug'])
660+
>>> mock_assignment = namedtuple('t1', ['slot_type']) # slot_type must be a callable
661+
>>> factory = lambda t: mock_assignment(slot_type=lambda: mock_timeslot(slug=t))
662+
>>> all(is_special_agenda_item(factory(t)) for t in ['break', 'reg', 'other', 'officehours'])
663+
True
664+
665+
>>> any(is_special_agenda_item(factory(t)) for t in ['regular', 'plenary'])
666+
False
667+
"""
668+
return assignment.slot_type().slug in [
669+
'break',
670+
'reg',
671+
'other',
672+
'officehours',
673+
]
674+
675+
@register.filter
676+
def should_show_agenda_session_buttons(assignment):
677+
"""Should this agenda item show the session buttons (jabber link, etc)?
678+
679+
In IETF-111 and earlier, office hours sessions were designated by a name ending
680+
with ' office hours' and belonged to the IESG or some other group. This led to
681+
incorrect session buttons being displayed. Suppress session buttons for
682+
when name ends with 'office hours' in the pre-111 meetings.
683+
>>> from collections import namedtuple # use to build mock objects
684+
>>> mock_meeting = namedtuple('t3', ['number'])
685+
>>> mock_session = namedtuple('t2', ['name'])
686+
>>> mock_assignment = namedtuple('t1', ['meeting', 'session']) # meeting must be a callable
687+
>>> factory = lambda num, name: mock_assignment(session=mock_session(name), meeting=lambda: mock_meeting(num))
688+
>>> test_cases = [('105', 'acme office hours'), ('111', 'acme office hours')]
689+
>>> any(should_show_agenda_session_buttons(factory(*tc)) for tc in test_cases)
690+
False
691+
>>> test_cases = [('interim-2020-acme-112', 'acme'), ('112', 'acme'), ('150', 'acme'), ('105', 'acme'),]
692+
>>> test_cases.extend([('111', 'acme'), ('interim-2020-acme-112', 'acme office hours')])
693+
>>> test_cases.extend([('112', 'acme office hours'), ('150', 'acme office hours')])
694+
>>> all(should_show_agenda_session_buttons(factory(*tc)) for tc in test_cases)
695+
True
696+
"""
697+
num = assignment.meeting().number
698+
if num.isdigit() and int(num) <= settings.MEETING_LEGACY_OFFICE_HOURS_END:
699+
return not assignment.session.name.lower().endswith(' office hours')
700+
else:
701+
return True
702+
616703

617704
@register.simple_tag
618705
def absurl(viewname, **kwargs):

ietf/group/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class GroupFeaturesAdmin(admin.ModelAdmin):
188188
'customize_workflow',
189189
'is_schedulable',
190190
'show_on_agenda',
191+
'agenda_filter_type',
191192
'req_subm_approval',
192193
'agenda_type',
193194
'material_types',
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright The IETF Trust 2021 All Rights Reserved
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('name', '0033_populate_agendafiltertypename'),
11+
('group', '0048_has_session_materials'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='groupfeatures',
17+
name='agenda_filter_type',
18+
field=models.ForeignKey(default='none', on_delete=django.db.models.deletion.PROTECT, to='name.AgendaFilterTypeName'),
19+
),
20+
]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright The IETF Trust 2021 All Rights Reserved
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
GroupFeatures = apps.get_model('group', 'GroupFeatures')
8+
9+
# map AgendaFilterTypeName slug to group types - unlisted get 'none'
10+
filter_types = dict(
11+
# list previously hard coded in agenda view, plus 'review'
12+
normal={'wg', 'ag', 'rg', 'rag', 'iab', 'program', 'review'},
13+
heading={'area', 'ietf', 'irtf'},
14+
special={'team', 'adhoc'},
15+
)
16+
17+
for ft, group_types in filter_types.items():
18+
for gf in GroupFeatures.objects.filter(type__slug__in=group_types):
19+
gf.agenda_filter_type_id = ft
20+
gf.save()
21+
22+
23+
def reverse(apps, schema_editor):
24+
pass # nothing to do, model will be deleted anyway
25+
26+
27+
class Migration(migrations.Migration):
28+
29+
dependencies = [
30+
('group', '0049_groupfeatures_agenda_filter_type'),
31+
]
32+
33+
operations = [
34+
migrations.RunPython(forward, reverse),
35+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright The IETF Trust 2021 All Rights Reserved
2+
3+
# Generated by Django 2.2.24 on 2021-09-26 11:29
4+
5+
from django.db import migrations
6+
import ietf.group.models
7+
import ietf.name.models
8+
import jsonfield.fields
9+
10+
11+
class Migration(migrations.Migration):
12+
13+
dependencies = [
14+
('group', '0050_populate_groupfeatures_agenda_filter_type'),
15+
('name', '0035_sessionpurposename'),
16+
]
17+
18+
operations = [
19+
migrations.AddField(
20+
model_name='groupfeatures',
21+
name='session_purposes',
22+
field=jsonfield.fields.JSONField(default=[], help_text='Allowed session purposes for this group type', max_length=256, validators=[ietf.group.models.JSONForeignKeyListValidator(ietf.name.models.SessionPurposeName)]),
23+
),
24+
]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright The IETF Trust 2021 All Rights Reserved
2+
3+
# Generated by Django 2.2.24 on 2021-09-26 11:29
4+
5+
from django.db import migrations
6+
7+
8+
default_purposes = dict(
9+
dir=['presentation', 'social', 'tutorial'],
10+
ietf=['admin', 'presentation', 'social'],
11+
rg=['session'],
12+
team=['coding', 'presentation', 'social', 'tutorial'],
13+
wg=['session'],
14+
)
15+
16+
17+
def forward(apps, schema_editor):
18+
GroupFeatures = apps.get_model('group', 'GroupFeatures')
19+
SessionPurposeName = apps.get_model('name', 'SessionPurposeName')
20+
21+
# verify that we're not about to use an invalid purpose
22+
for purposes in default_purposes.values():
23+
for purpose in purposes:
24+
SessionPurposeName.objects.get(pk=purpose) # throws an exception unless exists
25+
26+
for type_, purposes in default_purposes.items():
27+
GroupFeatures.objects.filter(
28+
type=type_
29+
).update(
30+
session_purposes=purposes
31+
)
32+
33+
def reverse(apps, schema_editor):
34+
GroupFeatures = apps.get_model('group', 'GroupFeatures')
35+
GroupFeatures.objects.update(session_purposes=[]) # clear back out to default
36+
37+
38+
class Migration(migrations.Migration):
39+
40+
dependencies = [
41+
('group', '0051_groupfeatures_session_purposes'),
42+
('name', '0036_populate_sessionpurposename'),
43+
44+
]
45+
46+
operations = [
47+
migrations.RunPython(forward, reverse),
48+
]

ietf/group/models.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@
1313
from django.conf import settings
1414
from django.core.validators import RegexValidator
1515
from django.db import models
16-
from django.db.models.deletion import CASCADE
16+
from django.db.models.deletion import CASCADE, PROTECT
1717
from django.dispatch import receiver
1818

1919
#from simple_history.models import HistoricalRecords
2020

2121
import debug # pyflakes:ignore
2222

2323
from ietf.group.colors import fg_group_colors, bg_group_colors
24-
from ietf.name.models import GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName, AgendaTypeName, ExtResourceName
24+
from ietf.name.models import (GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName,
25+
AgendaTypeName, AgendaFilterTypeName, ExtResourceName, SessionPurposeName)
2526
from ietf.person.models import Email, Person
2627
from ietf.utils.mail import formataddr, send_mail_text
2728
from ietf.utils import log
2829
from ietf.utils.models import ForeignKey, OneToOneField
30+
from ietf.utils.validators import JSONForeignKeyListValidator
2931

3032

3133
class GroupInfo(models.Model):
@@ -250,6 +252,7 @@ def get_description(self):
250252
code='invalid',
251253
)
252254

255+
253256
class GroupFeatures(models.Model):
254257
type = OneToOneField(GroupTypeName, primary_key=True, null=False, related_name='features')
255258
#history = HistoricalRecords()
@@ -277,6 +280,7 @@ class GroupFeatures(models.Model):
277280
customize_workflow = models.BooleanField("Workflow", default=False)
278281
is_schedulable = models.BooleanField("Schedulable",default=False)
279282
show_on_agenda = models.BooleanField("On Agenda", default=False)
283+
agenda_filter_type = models.ForeignKey(AgendaFilterTypeName, default='none', on_delete=PROTECT)
280284
req_subm_approval = models.BooleanField("Subm. Approval", default=False)
281285
#
282286
agenda_type = models.ForeignKey(AgendaTypeName, null=True, default="ietf", on_delete=CASCADE)
@@ -290,7 +294,10 @@ class GroupFeatures(models.Model):
290294
groupman_authroles = jsonfield.JSONField(max_length=128, blank=False, default=["Secretariat",])
291295
matman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
292296
role_order = jsonfield.JSONField(max_length=128, blank=False, default=["chair","secr","member"],
293-
help_text="The order in which roles are shown, for instance on photo pages. Enter valid JSON.")
297+
help_text="The order in which roles are shown, for instance on photo pages. Enter valid JSON.")
298+
session_purposes = jsonfield.JSONField(max_length=256, blank=False, default=[],
299+
help_text="Allowed session purposes for this group type",
300+
validators=[JSONForeignKeyListValidator(SessionPurposeName)])
294301

295302

296303
class GroupHistory(GroupInfo):

0 commit comments

Comments
 (0)