Eliminate the officehours timeslot type, update/renumber migrations, mark offagenda/reserved TimeSlotTypeNames as not used, add a 'none' SessionPurposeName and disallow null, update agenda filter keywords/filter helpers, fix broken tests and general debugging
- Legacy-Id: 19550
This commit is contained in:
parent
3dfce7b850
commit
7b35c09c40
|
@ -40,7 +40,7 @@ from ietf.doc.utils import create_ballot_if_not_open, uppercase_std_abbreviated_
|
|||
from ietf.group.models import Group
|
||||
from ietf.group.factories import GroupFactory, RoleFactory
|
||||
from ietf.ipr.factories import HolderIprDisclosureFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, SchedulingEvent
|
||||
from ietf.meeting.factories import ( MeetingFactory, SessionFactory, SessionPresentationFactory,
|
||||
ProceedingsMaterialFactory )
|
||||
|
||||
|
@ -1473,12 +1473,12 @@ class DocTestCase(TestCase):
|
|||
)
|
||||
doc.set_state(State.objects.get(type="slides", slug="active"))
|
||||
|
||||
session = Session.objects.create(
|
||||
session = SessionFactory(
|
||||
name = "session-72-mars-1",
|
||||
meeting = Meeting.objects.get(number='72'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
modified = datetime.datetime.now(),
|
||||
type_id = 'regular',
|
||||
add_to_schedule=False,
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
|
|
|
@ -17,8 +17,8 @@ from django.urls import reverse as urlreverse
|
|||
from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.factories import MeetingFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, SchedulingEvent
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
|
@ -152,12 +152,11 @@ class GroupMaterialTests(TestCase):
|
|||
def test_revise(self):
|
||||
doc = self.create_slides()
|
||||
|
||||
session = Session.objects.create(
|
||||
session = SessionFactory(
|
||||
name = "session-42-mars-1",
|
||||
meeting = Meeting.objects.get(number='42'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
modified = datetime.datetime.now(),
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
|
|
|
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
dependencies = [
|
||||
('group', '0050_populate_groupfeatures_agenda_filter_type'),
|
||||
('name', '0035_sessionpurposename'),
|
||||
('name', '0034_sessionpurposename'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -7,23 +7,23 @@ from django.db import migrations
|
|||
|
||||
default_purposes = dict(
|
||||
adhoc=['presentation'],
|
||||
adm=['closed_meeting', 'office_hours'],
|
||||
adm=['closed_meeting', 'officehours'],
|
||||
ag=['regular'],
|
||||
area=['regular'],
|
||||
dir=['open_meeting', 'presentation', 'regular', 'social', 'tutorial'],
|
||||
iab=['closed_meeting', 'regular'],
|
||||
iabasg=['closed_meeting', 'open_meeting'],
|
||||
iana=['office_hours'],
|
||||
iabasg=['closed_meeting', 'officehours', 'open_meeting'],
|
||||
iana=['officehours'],
|
||||
iesg=['closed_meeting', 'open_meeting'],
|
||||
ietf=['admin', 'plenary', 'presentation', 'social'],
|
||||
irtf=[],
|
||||
ise=['office_hours'],
|
||||
isoc=['office_hours', 'open_meeting', 'presentation'],
|
||||
nomcom=['closed_meeting', 'office_hours'],
|
||||
ise=['officehours'],
|
||||
isoc=['officehours', 'open_meeting', 'presentation'],
|
||||
nomcom=['closed_meeting', 'officehours'],
|
||||
program=['regular', 'tutorial'],
|
||||
rag=['regular'],
|
||||
review=['open_meeting', 'social'],
|
||||
rfcedtyp=['office_hours'],
|
||||
rfcedtyp=['officehours'],
|
||||
rg=['regular'],
|
||||
team=['coding', 'presentation', 'social', 'tutorial'],
|
||||
wg=['regular'],
|
||||
|
@ -55,7 +55,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
dependencies = [
|
||||
('group', '0051_groupfeatures_session_purposes'),
|
||||
('name', '0036_populate_sessionpurposename'),
|
||||
('name', '0035_populate_sessionpurposename'),
|
||||
|
||||
]
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.views.decorators.http import require_POST
|
|||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.meeting.helpers import get_meeting, get_schedule, schedule_permissions, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.models import TimeSlot, Session, Schedule, Room, Constraint, SchedTimeSessAssignment, ResourceAssociation
|
||||
from ietf.meeting.views import edit_timeslots, edit_schedule
|
||||
from ietf.meeting.views import edit_timeslots, edit_meeting_schedule
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -286,7 +286,7 @@ def schedule_add(request, meeting):
|
|||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
||||
return redirect(schedule_infourl, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
else:
|
||||
return redirect(edit_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
return redirect(edit_meeting_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
|
||||
@require_POST
|
||||
def schedule_update(request, meeting, schedule):
|
||||
|
@ -325,7 +325,7 @@ def schedule_update(request, meeting, schedule):
|
|||
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
|
||||
content_type="application/json")
|
||||
else:
|
||||
return redirect(edit_schedule, meeting.number, schedule.owner_email(), schedule.name)
|
||||
return redirect(edit_meeting_schedule, meeting.number, schedule.owner_email(), schedule.name)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def schedule_del(request, meeting, schedule):
|
||||
|
|
|
@ -12,7 +12,8 @@ from django.db.models import Q
|
|||
from ietf.meeting.models import (Meeting, Session, SchedulingEvent, Schedule,
|
||||
TimeSlot, SessionPresentation, FloorPlan, Room, SlideSubmission, Constraint,
|
||||
MeetingHost, ProceedingsMaterial)
|
||||
from ietf.name.models import ConstraintName, SessionStatusName, ProceedingsMaterialTypeName, TimerangeName
|
||||
from ietf.name.models import (ConstraintName, SessionStatusName, ProceedingsMaterialTypeName,
|
||||
TimerangeName, SessionPurposeName)
|
||||
from ietf.doc.factories import ProceedingsMaterialDocFactory
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
@ -104,9 +105,11 @@ class SessionFactory(factory.django.DjangoModelFactory):
|
|||
model = Session
|
||||
|
||||
meeting = factory.SubFactory(MeetingFactory)
|
||||
type_id='regular'
|
||||
purpose_id = 'regular'
|
||||
type_id = 'regular'
|
||||
group = factory.SubFactory(GroupFactory)
|
||||
requested_duration = datetime.timedelta(hours=1)
|
||||
on_agenda = factory.lazy_attribute(lambda obj: SessionPurposeName.objects.get(pk=obj.purpose_id).on_agenda)
|
||||
|
||||
@factory.post_generation
|
||||
def status_id(obj, create, extracted, **kwargs):
|
||||
|
@ -128,7 +131,7 @@ class SessionFactory(factory.django.DjangoModelFactory):
|
|||
status=SessionStatusName.objects.get(slug=extracted),
|
||||
by=PersonFactory(),
|
||||
)
|
||||
|
||||
|
||||
@factory.post_generation
|
||||
def add_to_schedule(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
|
||||
'''
|
||||
|
|
|
@ -5,7 +5,7 @@ from django import forms
|
|||
|
||||
from ietf.name.models import SessionPurposeName, TimeSlotTypeName
|
||||
|
||||
import debug
|
||||
import debug # pyflakes: ignore
|
||||
|
||||
class SessionPurposeAndTypeWidget(forms.MultiWidget):
|
||||
css_class = 'session_purpose_widget' # class to apply to all widgets
|
||||
|
|
|
@ -310,7 +310,7 @@ class AgendaKeywordTool:
|
|||
|
||||
@property
|
||||
def filterable_purposes(self):
|
||||
return SessionPurposeName.objects.exclude(slug='regular').order_by('name')
|
||||
return SessionPurposeName.objects.exclude(slug='none').order_by('name')
|
||||
|
||||
|
||||
class AgendaFilterOrganizer(AgendaKeywordTool):
|
||||
|
@ -336,7 +336,7 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
|
|||
# group acronyms in this list will never be used as filter buttons
|
||||
exclude_acronyms = ('iesg', 'ietf', 'secretariat')
|
||||
# extra keywords to include in the no-heading column if they apply to any sessions
|
||||
extra_labels = ('BoF', 'Plenary')
|
||||
extra_labels = ('BoF',)
|
||||
# group types whose acronyms should be word-capitalized
|
||||
capitalized_group_types = ('team',)
|
||||
# group types whose acronyms should be all-caps
|
||||
|
@ -352,6 +352,8 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
|
|||
# filled in when _organize_filters() is called
|
||||
self.filter_categories = None
|
||||
self.special_filters = None
|
||||
if self._use_legacy_keywords():
|
||||
self.extra_labels += ('Plenary',) # need this when not using session purpose
|
||||
|
||||
def get_non_area_keywords(self):
|
||||
"""Get list of any 'non-area' (aka 'special') keywords
|
||||
|
@ -465,11 +467,14 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
|
|||
|
||||
# Call legacy version for older meetings
|
||||
if self._use_legacy_keywords():
|
||||
return self._legacy_non_group_filters()
|
||||
return self._legacy_non_group_filters(sessions)
|
||||
|
||||
# Not using legacy version
|
||||
filter_cols = []
|
||||
for purpose in self.filterable_purposes:
|
||||
if purpose.slug == 'regular':
|
||||
continue
|
||||
|
||||
# Map label to its keyword, discarding duplicate labels.
|
||||
# This does what we want as long as sessions with the same
|
||||
# name and purpose belong to the same group.
|
||||
|
@ -497,19 +502,16 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
|
|||
|
||||
return filter_cols
|
||||
|
||||
def _legacy_non_group_filters(self):
|
||||
def _legacy_non_group_filters(self, sessions):
|
||||
"""Get list of non-group filters for older meetings
|
||||
|
||||
Returns a list of filter columns
|
||||
"""
|
||||
if self.assignments is None:
|
||||
return [] # can only use timeslot type when we have assignments
|
||||
|
||||
office_hours_items = set()
|
||||
suffix = ' office hours'
|
||||
for a in self.assignments:
|
||||
if a.session.name.lower().endswith(suffix):
|
||||
office_hours_items.add((a.session.name[:-len(suffix)].strip(), a.session.group))
|
||||
for s in sessions:
|
||||
if s.name.lower().endswith(suffix):
|
||||
office_hours_items.add((s.name[:-len(suffix)].strip(), s.group))
|
||||
|
||||
headings = []
|
||||
# currently we only do office hours
|
||||
|
@ -640,8 +642,7 @@ class AgendaKeywordTagger(AgendaKeywordTool):
|
|||
Keywords are all lower case.
|
||||
"""
|
||||
for a in self.assignments:
|
||||
a.filter_keywords = {a.slot_type().slug.lower()}
|
||||
a.filter_keywords.update(self._filter_keywords_for_assignment(a))
|
||||
a.filter_keywords = self._filter_keywords_for_assignment(a)
|
||||
a.filter_keywords = sorted(list(a.filter_keywords))
|
||||
|
||||
def _tag_sessions_with_filter_keywords(self):
|
||||
|
@ -652,14 +653,17 @@ class AgendaKeywordTagger(AgendaKeywordTool):
|
|||
@staticmethod
|
||||
def _legacy_extra_session_keywords(session):
|
||||
"""Get extra keywords for a session at a legacy meeting"""
|
||||
extra = []
|
||||
if session.type_id == 'plenary':
|
||||
extra.append('plenary')
|
||||
office_hours_match = re.match(r'^ *\w+(?: +\w+)* +office hours *$', session.name, re.IGNORECASE)
|
||||
if office_hours_match is not None:
|
||||
suffix = 'officehours'
|
||||
return [
|
||||
extra.extend([
|
||||
'officehours',
|
||||
session.name.lower().replace(' ', '')[:-len(suffix)] + '-officehours',
|
||||
]
|
||||
return []
|
||||
])
|
||||
return extra
|
||||
|
||||
def _filter_keywords_for_session(self, session):
|
||||
keywords = set()
|
||||
|
|
|
@ -15,7 +15,9 @@ def forward(apps, schema_editor):
|
|||
),
|
||||
timeslot__type__private=True,
|
||||
)
|
||||
Session.objects.filter(timeslotassignments__in=private_assignments).update(on_agenda=False)
|
||||
for pa in private_assignments:
|
||||
pa.session.on_agenda = False
|
||||
pa.session.save()
|
||||
# Also update any sessions to match their purpose's default setting (this intentionally
|
||||
# overrides the timeslot settings above, but that is unlikely to matter because the
|
||||
# purposes will roll out at the same time as the on_agenda field)
|
||||
|
|
|
@ -1162,7 +1162,7 @@ class Session(models.Model):
|
|||
meeting = ForeignKey(Meeting)
|
||||
name = models.CharField(blank=True, max_length=255, help_text="Name of session, in case the session has a purpose rather than just being a group meeting.")
|
||||
short = models.CharField(blank=True, max_length=32, help_text="Short version of 'name' above, for use in filenames.")
|
||||
purpose = ForeignKey(SessionPurposeName, null=True, help_text='Purpose of the session')
|
||||
purpose = ForeignKey(SessionPurposeName, null=False, help_text='Purpose of the session')
|
||||
type = ForeignKey(TimeSlotTypeName)
|
||||
group = ForeignKey(Group) # The group type historically determined the session type. BOFs also need to be added as a group. Note that not all meeting requests have a natural group to associate with.
|
||||
joint_with_groups = models.ManyToManyField(Group, related_name='sessions_joint_in',blank=True)
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
from django import template
|
||||
from django.urls import reverse
|
||||
|
||||
from ietf.utils.text import xslugify
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.factories import DocumentFactory
|
||||
from ietf.group.factories import GroupFactory, RoleFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import (Meeting, Room, TimeSlot, Session, Schedule, SchedTimeSessAssignment,
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.factories import SessionFactory
|
||||
from ietf.meeting.models import (Meeting, Room, TimeSlot, Schedule, SchedTimeSessAssignment,
|
||||
ResourceAssociation, SessionPresentation, UrlResource, SchedulingEvent)
|
||||
from ietf.meeting.helpers import create_interim_meeting
|
||||
from ietf.name.models import RoomResourceName
|
||||
|
@ -24,11 +25,11 @@ def make_interim_meeting(group,date,status='sched'):
|
|||
system_person = Person.objects.get(name="(System)")
|
||||
time = datetime.datetime.combine(date, datetime.time(9))
|
||||
meeting = create_interim_meeting(group=group,date=date)
|
||||
session = Session.objects.create(meeting=meeting, group=group,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
remote_instructions='http://webex.com',
|
||||
type_id='regular')
|
||||
session = SessionFactory(meeting=meeting, group=group,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
remote_instructions='http://webex.com',
|
||||
add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=session, status_id=status, by=system_person)
|
||||
slot = TimeSlot.objects.create(
|
||||
meeting=meeting,
|
||||
|
@ -121,52 +122,52 @@ def make_meeting_test_data(meeting=None, create_interims=False):
|
|||
time=datetime.datetime.combine(session_date, datetime.time(11,0)))
|
||||
# mars WG
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars_session = Session.objects.create(meeting=meeting, group=mars,
|
||||
attendees=10, requested_duration=datetime.timedelta(minutes=50),
|
||||
type_id='regular')
|
||||
mars_session = SessionFactory(meeting=meeting, group=mars,
|
||||
attendees=10, requested_duration=datetime.timedelta(minutes=50),
|
||||
add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=mars_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=mars_session, schedule=schedule)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=mars_session, schedule=unofficial_schedule)
|
||||
|
||||
# ames WG
|
||||
ames_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ames"),
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id='regular')
|
||||
ames_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="ames"),
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=ames_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=ames_session, schedule=schedule)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=ames_session, schedule=unofficial_schedule)
|
||||
|
||||
# IESG breakfast
|
||||
iesg_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="iesg"),
|
||||
name="IESG Breakfast", attendees=25,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id="lead")
|
||||
iesg_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="iesg"),
|
||||
name="IESG Breakfast", attendees=25,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id="lead", purpose_id='closed_meeting', add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=iesg_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=breakfast_slot, session=iesg_session, schedule=schedule)
|
||||
# No breakfast on unofficial schedule
|
||||
|
||||
# Registration
|
||||
reg_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Registration", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=480),
|
||||
type_id="reg")
|
||||
reg_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Registration", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=480),
|
||||
type_id="reg", purpose_id='admin', add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=reg_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=reg_slot, session=reg_session, schedule=base_schedule)
|
||||
|
||||
# Break
|
||||
break_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Morning Break", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=30),
|
||||
type_id="break")
|
||||
break_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Morning Break", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=30),
|
||||
type_id="break", purpose_id='social', add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=base_schedule)
|
||||
|
||||
# IETF Plenary
|
||||
plenary_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ietf"),
|
||||
name="IETF Plenary", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id="plenary")
|
||||
plenary_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="ietf"),
|
||||
name="IETF Plenary", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id="plenary", purpose_id='plenary', add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=plenary_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=plenary_slot, session=plenary_session, schedule=schedule)
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
# Copyright The IETF Trust 2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
import copy
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import override_settings
|
||||
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.factories import SessionFactory, MeetingFactory, TimeSlotFactory
|
||||
from ietf.meeting.helpers import AgendaFilterOrganizer, AgendaKeywordTagger
|
||||
from ietf.meeting.models import SchedTimeSessAssignment
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
||||
|
@ -21,7 +22,6 @@ class AgendaKeywordTaggerTests(TestCase):
|
|||
The historic param can be None, group, or parent, to specify whether to test
|
||||
with no historic_group, a historic_group but no historic_parent, or both.
|
||||
"""
|
||||
session_types = ['regular', 'plenary']
|
||||
# decide whether meeting should use legacy keywords (for office hours)
|
||||
legacy_keywords = meeting_num <= 111
|
||||
|
||||
|
@ -44,71 +44,92 @@ class AgendaKeywordTaggerTests(TestCase):
|
|||
expected_group = group
|
||||
expected_area = group.parent
|
||||
|
||||
# create the ordinary sessions
|
||||
for session_type in session_types:
|
||||
sess = SessionFactory(group=group, meeting=meeting, type_id=session_type, add_to_schedule=False)
|
||||
sess.timeslotassignments.create(
|
||||
timeslot=TimeSlotFactory(meeting=meeting, type_id=session_type),
|
||||
# create sessions, etc
|
||||
session_data = [
|
||||
{
|
||||
'description': 'regular wg session',
|
||||
'session': SessionFactory(
|
||||
group=group, meeting=meeting, add_to_schedule=False,
|
||||
purpose_id='none' if legacy_keywords else 'regular',
|
||||
type_id='regular',
|
||||
),
|
||||
'expected_keywords': {
|
||||
expected_group.acronym,
|
||||
expected_area.acronym,
|
||||
# if legacy_keywords, next line repeats a previous entry to avoid adding anything to the set
|
||||
expected_group.acronym if legacy_keywords else 'regular',
|
||||
f'{expected_group.acronym}-sessa',
|
||||
},
|
||||
},
|
||||
{
|
||||
'description': 'plenary session',
|
||||
'session': SessionFactory(
|
||||
group=group, meeting=meeting, add_to_schedule=False,
|
||||
name=f'{group.acronym} plenary',
|
||||
purpose_id='none' if legacy_keywords else 'plenary',
|
||||
type_id='plenary',
|
||||
),
|
||||
'expected_keywords': {
|
||||
expected_group.acronym,
|
||||
expected_area.acronym,
|
||||
f'{expected_group.acronym}-sessb',
|
||||
'plenary',
|
||||
f'{group.acronym}-plenary',
|
||||
},
|
||||
},
|
||||
{
|
||||
'description': 'office hours session',
|
||||
'session': SessionFactory(
|
||||
group=group, meeting=meeting, add_to_schedule=False,
|
||||
name=f'{group.acronym} office hours',
|
||||
purpose_id='none' if legacy_keywords else 'officehours',
|
||||
type_id='other',
|
||||
),
|
||||
'expected_keywords': {
|
||||
expected_group.acronym,
|
||||
expected_area.acronym,
|
||||
f'{expected_group.acronym}-sessc',
|
||||
'officehours',
|
||||
f'{group.acronym}-officehours' if legacy_keywords else 'officehours',
|
||||
# officehours in prev line is a repeated value - since this is a set, it will be ignored
|
||||
f'{group.acronym}-office-hours',
|
||||
},
|
||||
}
|
||||
]
|
||||
for sd in session_data:
|
||||
sd['session'].timeslotassignments.create(
|
||||
timeslot=TimeSlotFactory(meeting=meeting, type=sd['session'].type),
|
||||
schedule=meeting.schedule,
|
||||
)
|
||||
|
||||
# Create an office hours session in the group's area (i.e., parent). Handle this separately
|
||||
# from other session creation to test legacy office hours naming.
|
||||
office_hours = SessionFactory(
|
||||
name='some office hours',
|
||||
group=Group.objects.get(acronym='iesg') if legacy_keywords else expected_area,
|
||||
meeting=meeting,
|
||||
type_id='other' if legacy_keywords else 'officehours',
|
||||
add_to_schedule=False,
|
||||
)
|
||||
office_hours.timeslotassignments.create(
|
||||
timeslot=TimeSlotFactory(meeting=meeting, type_id=office_hours.type_id),
|
||||
schedule=meeting.schedule,
|
||||
)
|
||||
|
||||
assignments = meeting.schedule.assignments.all()
|
||||
orig_num_assignments = len(assignments)
|
||||
|
||||
# Set up historic groups if needed. We've already set the office hours group properly
|
||||
# so skip that session. The expected_group will already have its historic_parent set
|
||||
# if historic == 'parent'
|
||||
# Set up historic groups if needed.
|
||||
if historic:
|
||||
for a in assignments:
|
||||
if a.session != office_hours:
|
||||
a.session.historic_group = expected_group
|
||||
a.session.historic_group = expected_group
|
||||
|
||||
# Execute the method under test
|
||||
AgendaKeywordTagger(assignments=assignments).apply()
|
||||
|
||||
# Assert expected results
|
||||
self.assertEqual(len(assignments), orig_num_assignments, 'Should not change number of assignments')
|
||||
|
||||
for assignment in assignments:
|
||||
expected_filter_keywords = {assignment.slot_type().slug, assignment.session.type.slug}
|
||||
|
||||
if assignment.session == office_hours:
|
||||
expected_filter_keywords.update([
|
||||
office_hours.group.acronym,
|
||||
'officehours',
|
||||
'some-officehours' if legacy_keywords else '{}-officehours'.format(expected_area.acronym),
|
||||
])
|
||||
else:
|
||||
expected_filter_keywords.update([
|
||||
expected_group.acronym,
|
||||
expected_area.acronym
|
||||
])
|
||||
if bof:
|
||||
expected_filter_keywords.add('bof')
|
||||
token = assignment.session.docname_token_only_for_multiple()
|
||||
if token is not None:
|
||||
expected_filter_keywords.update([expected_group.acronym + "-" + token])
|
||||
# check the assignment count - paranoid, but the method mutates its input so let's be careful
|
||||
self.assertEqual(len(assignments), len(session_data), 'Should not change number of assignments')
|
||||
|
||||
assignment_by_session_pk = {a.session.pk: a for a in assignments}
|
||||
for sd in session_data:
|
||||
assignment = assignment_by_session_pk[sd['session'].pk]
|
||||
expected_filter_keywords = sd['expected_keywords']
|
||||
if bof:
|
||||
expected_filter_keywords.add('bof')
|
||||
self.assertCountEqual(
|
||||
assignment.filter_keywords,
|
||||
expected_filter_keywords,
|
||||
'Assignment has incorrect filter keywords'
|
||||
f'Assignment for "{sd["description"]}" has incorrect filter keywords'
|
||||
)
|
||||
|
||||
@override_settings(MEETING_LEGACY_OFFICE_HOURS_END=111)
|
||||
def test_tag_assignments_with_filter_keywords(self):
|
||||
# use distinct meeting numbers > 111 for non-legacy keyword tests
|
||||
self.do_test_tag_assignments_with_filter_keywords(112)
|
||||
|
@ -119,6 +140,7 @@ class AgendaKeywordTaggerTests(TestCase):
|
|||
self.do_test_tag_assignments_with_filter_keywords(117, bof=True, historic='parent')
|
||||
|
||||
|
||||
@override_settings(MEETING_LEGACY_OFFICE_HOURS_END=111)
|
||||
def test_tag_assignments_with_filter_keywords_legacy(self):
|
||||
# use distinct meeting numbers <= 111 for legacy keyword tests
|
||||
self.do_test_tag_assignments_with_filter_keywords(101)
|
||||
|
@ -131,8 +153,19 @@ class AgendaKeywordTaggerTests(TestCase):
|
|||
|
||||
class AgendaFilterOrganizerTests(TestCase):
|
||||
def test_get_filter_categories(self):
|
||||
self.do_get_filter_categories_test(False)
|
||||
|
||||
def test_get_legacy_filter_categories(self):
|
||||
self.do_get_filter_categories_test(True)
|
||||
|
||||
def do_get_filter_categories_test(self, legacy):
|
||||
# set up
|
||||
meeting = make_meeting_test_data()
|
||||
if legacy:
|
||||
meeting.session_set.all().update(purpose_id='none') # legacy meetings did not have purposes
|
||||
else:
|
||||
meeting.number = str(settings.MEETING_LEGACY_OFFICE_HOURS_END + 1)
|
||||
meeting.save()
|
||||
|
||||
# create extra groups for testing
|
||||
iab = Group.objects.get(acronym='iab')
|
||||
|
@ -147,55 +180,97 @@ class AgendaFilterOrganizerTests(TestCase):
|
|||
# office hours session
|
||||
SessionFactory(
|
||||
group=Group.objects.get(acronym='farfut'),
|
||||
purpose_id='officehours' if not legacy else 'none',
|
||||
type_id='other',
|
||||
name='FARFUT office hours',
|
||||
meeting=meeting
|
||||
)
|
||||
|
||||
expected = [
|
||||
[
|
||||
# area category
|
||||
{'label': 'FARFUT', 'keyword': 'farfut', 'is_bof': False,
|
||||
'children': [
|
||||
{'label': 'ames', 'keyword': 'ames', 'is_bof': False},
|
||||
{'label': 'mars', 'keyword': 'mars', 'is_bof': False},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-area category
|
||||
{'label': 'IAB', 'keyword': 'iab', 'is_bof': False,
|
||||
'children': [
|
||||
{'label': iab_child.acronym, 'keyword': iab_child.acronym, 'is_bof': False},
|
||||
]},
|
||||
{'label': 'IRTF', 'keyword': 'irtf', 'is_bof': False,
|
||||
'children': [
|
||||
{'label': irtf_child.acronym, 'keyword': irtf_child.acronym, 'is_bof': True},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-group category
|
||||
{'label': 'Office Hours', 'keyword': 'officehours', 'is_bof': False,
|
||||
'children': [
|
||||
{'label': 'FARFUT', 'keyword': 'farfut-officehours', 'is_bof': False}
|
||||
]},
|
||||
{'label': None, 'keyword': None,'is_bof': False,
|
||||
'children': [
|
||||
{'label': 'BoF', 'keyword': 'bof', 'is_bof': False},
|
||||
{'label': 'Plenary', 'keyword': 'plenary', 'is_bof': False},
|
||||
]},
|
||||
],
|
||||
]
|
||||
|
||||
# when using sessions instead of assignments, won't get timeslot-type based filters
|
||||
expected_with_sessions = copy.deepcopy(expected)
|
||||
expected_with_sessions[-1].pop(0) # pops 'office hours' column
|
||||
|
||||
if legacy:
|
||||
expected = [
|
||||
[
|
||||
# area category
|
||||
{'label': 'FARFUT', 'keyword': 'farfut', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'ames', 'keyword': 'ames', 'is_bof': False, 'toggled_by': ['farfut']},
|
||||
{'label': 'mars', 'keyword': 'mars', 'is_bof': False, 'toggled_by': ['farfut']},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-area category
|
||||
{'label': 'IAB', 'keyword': 'iab', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': iab_child.acronym, 'keyword': iab_child.acronym, 'is_bof': False, 'toggled_by': ['iab']},
|
||||
]},
|
||||
{'label': 'IRTF', 'keyword': 'irtf', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': irtf_child.acronym, 'keyword': irtf_child.acronym, 'is_bof': True, 'toggled_by': ['bof', 'irtf']},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-group category
|
||||
{'label': 'Office Hours', 'keyword': 'officehours', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'FARFUT', 'keyword': 'farfut-officehours', 'is_bof': False, 'toggled_by': ['officehours', 'farfut']}
|
||||
]},
|
||||
{'label': None, 'keyword': None,'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'BoF', 'keyword': 'bof', 'is_bof': False, 'toggled_by': []},
|
||||
{'label': 'Plenary', 'keyword': 'plenary', 'is_bof': False, 'toggled_by': []},
|
||||
]},
|
||||
],
|
||||
]
|
||||
else:
|
||||
expected = [
|
||||
[
|
||||
# area category
|
||||
{'label': 'FARFUT', 'keyword': 'farfut', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'ames', 'keyword': 'ames', 'is_bof': False, 'toggled_by': ['farfut']},
|
||||
{'label': 'mars', 'keyword': 'mars', 'is_bof': False, 'toggled_by': ['farfut']},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-area category
|
||||
{'label': 'IAB', 'keyword': 'iab', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': iab_child.acronym, 'keyword': iab_child.acronym, 'is_bof': False, 'toggled_by': ['iab']},
|
||||
]},
|
||||
{'label': 'IRTF', 'keyword': 'irtf', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': irtf_child.acronym, 'keyword': irtf_child.acronym, 'is_bof': True, 'toggled_by': ['bof', 'irtf']},
|
||||
]},
|
||||
],
|
||||
[
|
||||
# non-group category
|
||||
{'label': 'Administrative', 'keyword': 'admin', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'Registration', 'keyword': 'registration', 'is_bof': False, 'toggled_by': ['admin', 'secretariat']},
|
||||
]},
|
||||
{'label': 'Closed meeting', 'keyword': 'closed_meeting', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'IESG Breakfast', 'keyword': 'iesg-breakfast', 'is_bof': False, 'toggled_by': ['closed_meeting', 'iesg']},
|
||||
]},
|
||||
{'label': 'Office hours', 'keyword': 'officehours', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'FARFUT office hours', 'keyword': 'farfut-office-hours', 'is_bof': False, 'toggled_by': ['officehours', 'farfut']}
|
||||
]},
|
||||
{'label': 'Plenary', 'keyword': 'plenary', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'IETF Plenary', 'keyword': 'ietf-plenary', 'is_bof': False, 'toggled_by': ['plenary', 'ietf']},
|
||||
]},
|
||||
{'label': 'Social', 'keyword': 'social', 'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'Morning Break', 'keyword': 'morning-break', 'is_bof': False, 'toggled_by': ['social', 'secretariat']},
|
||||
]},
|
||||
{'label': None, 'keyword': None,'is_bof': False, 'toggled_by': [],
|
||||
'children': [
|
||||
{'label': 'BoF', 'keyword': 'bof', 'is_bof': False, 'toggled_by': []},
|
||||
]},
|
||||
],
|
||||
]
|
||||
# put all the above together for single-column tests
|
||||
expected_single_category = [
|
||||
sorted(sum(expected, []), key=lambda col: col['label'] or 'zzzzz')
|
||||
]
|
||||
expected_single_category_with_sessions = [
|
||||
sorted(sum(expected_with_sessions, []), key=lambda col: col['label'] or 'zzzzz')
|
||||
]
|
||||
expected_single_category = [sum(expected, [])]
|
||||
|
||||
###
|
||||
# test using sessions
|
||||
|
@ -204,15 +279,17 @@ class AgendaFilterOrganizerTests(TestCase):
|
|||
|
||||
# default
|
||||
filter_organizer = AgendaFilterOrganizer(sessions=sessions)
|
||||
self.assertEqual(filter_organizer.get_filter_categories(), expected_with_sessions)
|
||||
self.assertEqual(filter_organizer.get_filter_categories(), expected)
|
||||
|
||||
# single-column
|
||||
filter_organizer = AgendaFilterOrganizer(sessions=sessions, single_category=True)
|
||||
self.assertEqual(filter_organizer.get_filter_categories(), expected_single_category_with_sessions)
|
||||
self.assertEqual(filter_organizer.get_filter_categories(), expected_single_category)
|
||||
|
||||
###
|
||||
# test again using assignments
|
||||
assignments = meeting.schedule.assignments.all()
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=(meeting.schedule, meeting.schedule.base)
|
||||
)
|
||||
AgendaKeywordTagger(assignments=assignments).apply()
|
||||
|
||||
# default
|
||||
|
|
|
@ -89,7 +89,13 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
s2.save()
|
||||
SchedTimeSessAssignment.objects.filter(session=s1).delete()
|
||||
|
||||
s2b = Session.objects.create(meeting=meeting, group=s2.group, attendees=10, requested_duration=datetime.timedelta(minutes=60), type_id='regular')
|
||||
s2b = SessionFactory(
|
||||
meeting=meeting,
|
||||
group=s2.group,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
add_to_schedule=False,
|
||||
)
|
||||
|
||||
SchedulingEvent.objects.create(
|
||||
session=s2b,
|
||||
|
@ -110,34 +116,34 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
|
||||
WebDriverWait(self.driver, 2).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.edit-meeting-schedule')))
|
||||
|
||||
self.assertEqual(len(self.driver.find_elements_by_css_selector('.session')), 3)
|
||||
self.assertEqual(len(self.driver.find_elements(By.CSS_SELECTOR, '.session.purpose-regular')), 3)
|
||||
|
||||
# select - show session info
|
||||
s2_element = self.driver.find_element_by_css_selector('#session{}'.format(s2.pk))
|
||||
s2b_element = self.driver.find_element_by_css_selector('#session{}'.format(s2b.pk))
|
||||
s2_element = self.driver.find_element(By.CSS_SELECTOR, '#session{}'.format(s2.pk))
|
||||
s2b_element = self.driver.find_element(By.CSS_SELECTOR, '#session{}'.format(s2b.pk))
|
||||
self.assertNotIn('other-session-selected', s2b_element.get_attribute('class'))
|
||||
s2_element.click()
|
||||
|
||||
# other session for group should be flagged for highlighting
|
||||
s2b_element = self.driver.find_element_by_css_selector('#session{}'.format(s2b.pk))
|
||||
s2b_element = self.driver.find_element(By.CSS_SELECTOR, '#session{}'.format(s2b.pk))
|
||||
self.assertIn('other-session-selected', s2b_element.get_attribute('class'))
|
||||
|
||||
# other session for group should appear in the info panel
|
||||
session_info_container = self.driver.find_element_by_css_selector('.session-info-container')
|
||||
self.assertIn(s2.group.acronym, session_info_container.find_element_by_css_selector(".title").text)
|
||||
self.assertEqual(session_info_container.find_element_by_css_selector(".other-session .time").text, "not yet scheduled")
|
||||
session_info_container = self.driver.find_element(By.CSS_SELECTOR, '.session-info-container')
|
||||
self.assertIn(s2.group.acronym, session_info_container.find_element(By.CSS_SELECTOR, ".title").text)
|
||||
self.assertEqual(session_info_container.find_element(By.CSS_SELECTOR, ".other-session .time").text, "not yet scheduled")
|
||||
|
||||
# deselect
|
||||
self.driver.find_element_by_css_selector('.scheduling-panel').click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, '.scheduling-panel').click()
|
||||
|
||||
self.assertEqual(session_info_container.find_elements_by_css_selector(".title"), [])
|
||||
self.assertEqual(session_info_container.find_elements(By.CSS_SELECTOR, ".title"), [])
|
||||
self.assertNotIn('other-session-selected', s2b_element.get_attribute('class'))
|
||||
|
||||
# unschedule
|
||||
|
||||
# we would like to do
|
||||
#
|
||||
# unassigned_sessions_element = self.driver.find_element_by_css_selector('.unassigned-sessions')
|
||||
# unassigned_sessions_element = self.driver.find_element(By.CSS_SELECTOR, '.unassigned-sessions')
|
||||
# ActionChains(self.driver).drag_and_drop(s2_element, unassigned_sessions_element).perform()
|
||||
#
|
||||
# but unfortunately, Selenium does not simulate drag and drop events, see
|
||||
|
@ -158,20 +164,20 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
|
||||
# sorting unassigned
|
||||
sorted_pks = [s.pk for s in sorted([s1, s2, s2b], key=lambda s: (s.group.acronym, s.requested_duration, s.pk))]
|
||||
self.driver.find_element_by_css_selector('[name=sort_unassigned] option[value=name]').click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector('.unassigned-sessions .drop-target #session{} + #session{} + #session{}'.format(*sorted_pks)))
|
||||
self.driver.find_element(By.CSS_SELECTOR, '[name=sort_unassigned] option[value=name]').click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, '.unassigned-sessions .drop-target #session{} + #session{} + #session{}'.format(*sorted_pks)))
|
||||
|
||||
sorted_pks = [s.pk for s in sorted([s1, s2, s2b], key=lambda s: (s.group.parent.acronym, s.group.acronym, s.requested_duration, s.pk))]
|
||||
self.driver.find_element_by_css_selector('[name=sort_unassigned] option[value=parent]').click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector('.unassigned-sessions .drop-target #session{} + #session{}'.format(*sorted_pks)))
|
||||
self.driver.find_element(By.CSS_SELECTOR, '[name=sort_unassigned] option[value=parent]').click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, '.unassigned-sessions .drop-target #session{} + #session{}'.format(*sorted_pks)))
|
||||
|
||||
sorted_pks = [s.pk for s in sorted([s1, s2, s2b], key=lambda s: (s.requested_duration, s.group.parent.acronym, s.group.acronym, s.pk))]
|
||||
self.driver.find_element_by_css_selector('[name=sort_unassigned] option[value=duration]').click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector('.unassigned-sessions .drop-target #session{} + #session{}'.format(*sorted_pks)))
|
||||
self.driver.find_element(By.CSS_SELECTOR, '[name=sort_unassigned] option[value=duration]').click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, '.unassigned-sessions .drop-target #session{} ~ #session{}'.format(*sorted_pks)))
|
||||
|
||||
sorted_pks = [s.pk for s in sorted([s1, s2, s2b], key=lambda s: (int(bool(s.comments)), s.group.parent.acronym, s.group.acronym, s.requested_duration, s.pk))]
|
||||
self.driver.find_element_by_css_selector('[name=sort_unassigned] option[value=comments]').click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector('.unassigned-sessions .drop-target #session{} + #session{}'.format(*sorted_pks)))
|
||||
self.driver.find_element(By.CSS_SELECTOR, '[name=sort_unassigned] option[value=comments]').click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, '.unassigned-sessions .drop-target #session{} + #session{}'.format(*sorted_pks)))
|
||||
|
||||
# schedule
|
||||
self.driver.execute_script("jQuery('#session{}').simulateDragDrop({{dropTarget: '#timeslot{} .drop-target'}});".format(s2.pk, slot1.pk))
|
||||
|
@ -182,30 +188,30 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.assertEqual(assignment.timeslot, slot1)
|
||||
|
||||
# timeslot constraint hints when selected
|
||||
s1_element = self.driver.find_element_by_css_selector('#session{}'.format(s1.pk))
|
||||
s1_element = self.driver.find_element(By.CSS_SELECTOR, '#session{}'.format(s1.pk))
|
||||
s1_element.click()
|
||||
|
||||
# violated due to constraints - both the timeslot and its timeslot label
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{}.would-violate-hint'.format(slot1.pk)))
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#timeslot{}.would-violate-hint'.format(slot1.pk)))
|
||||
# Find the timeslot label for slot1 - it's the first timeslot in the first room group
|
||||
slot1_roomgroup_elt = self.driver.find_element_by_css_selector(
|
||||
slot1_roomgroup_elt = self.driver.find_element(By.CSS_SELECTOR,
|
||||
'.day-flow .day:first-child .room-group:nth-child(2)' # count from 2 - first-child is the day label
|
||||
)
|
||||
self.assertTrue(
|
||||
slot1_roomgroup_elt.find_elements_by_css_selector(
|
||||
slot1_roomgroup_elt.find_elements(By.CSS_SELECTOR,
|
||||
'.time-header > .time-label.would-violate-hint:first-child'
|
||||
),
|
||||
'Timeslot header label should show a would-violate hint for a constraint violation'
|
||||
)
|
||||
|
||||
# violated due to missing capacity
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{}.would-violate-hint'.format(slot3.pk)))
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#timeslot{}.would-violate-hint'.format(slot3.pk)))
|
||||
# Find the timeslot label for slot3 - it's the second timeslot in the second room group
|
||||
slot3_roomgroup_elt = self.driver.find_element_by_css_selector(
|
||||
slot3_roomgroup_elt = self.driver.find_element(By.CSS_SELECTOR,
|
||||
'.day-flow .day:first-child .room-group:nth-child(3)' # count from 2 - first-child is the day label
|
||||
)
|
||||
self.assertFalse(
|
||||
slot3_roomgroup_elt.find_elements_by_css_selector(
|
||||
slot3_roomgroup_elt.find_elements(By.CSS_SELECTOR,
|
||||
'.time-header > .time-label.would-violate-hint:nth-child(2)'
|
||||
),
|
||||
'Timeslot header label should not show a would-violate hint for room capacity violation'
|
||||
|
@ -220,15 +226,15 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.assertEqual(assignment.timeslot, slot2)
|
||||
|
||||
# too many attendees warning
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#session{}.too-many-attendees'.format(s2.pk)))
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#session{}.too-many-attendees'.format(s2.pk)))
|
||||
|
||||
# overfull timeslot
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{}.overfull'.format(slot2.pk)))
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#timeslot{}.overfull'.format(slot2.pk)))
|
||||
|
||||
# constraint hints
|
||||
s1_element.click()
|
||||
self.assertIn('would-violate-hint', s2_element.get_attribute('class'))
|
||||
constraint_element = s2_element.find_element_by_css_selector(".constraints span[data-sessions=\"{}\"].would-violate-hint".format(s1.pk))
|
||||
constraint_element = s2_element.find_element(By.CSS_SELECTOR, ".constraints span[data-sessions=\"{}\"].would-violate-hint".format(s1.pk))
|
||||
self.assertTrue(constraint_element.is_displayed())
|
||||
|
||||
# current constraint violations
|
||||
|
@ -236,12 +242,12 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
|
||||
WebDriverWait(self.driver, 2).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '#timeslot{} #session{}'.format(slot1.pk, s1.pk))))
|
||||
|
||||
constraint_element = s2_element.find_element_by_css_selector(".constraints span[data-sessions=\"{}\"].violated-hint".format(s1.pk))
|
||||
constraint_element = s2_element.find_element(By.CSS_SELECTOR, ".constraints span[data-sessions=\"{}\"].violated-hint".format(s1.pk))
|
||||
self.assertTrue(constraint_element.is_displayed())
|
||||
|
||||
# hide sessions in area
|
||||
self.assertTrue(s1_element.is_displayed())
|
||||
self.driver.find_element_by_css_selector(".session-parent-toggles [value=\"{}\"]".format(s1.group.parent.acronym)).click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, ".session-parent-toggles [value=\"{}\"]".format(s1.group.parent.acronym)).click()
|
||||
self.assertTrue(s1_element.is_displayed()) # should still be displayed
|
||||
self.assertIn('hidden-parent', s1_element.get_attribute('class'),
|
||||
'Session should be hidden when parent disabled')
|
||||
|
@ -249,7 +255,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.assertNotIn('selected', s1_element.get_attribute('class'),
|
||||
'Session should not be selectable when parent disabled')
|
||||
|
||||
self.driver.find_element_by_css_selector(".session-parent-toggles [value=\"{}\"]".format(s1.group.parent.acronym)).click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, ".session-parent-toggles [value=\"{}\"]".format(s1.group.parent.acronym)).click()
|
||||
self.assertTrue(s1_element.is_displayed())
|
||||
self.assertNotIn('hidden-parent', s1_element.get_attribute('class'),
|
||||
'Session should not be hidden when parent enabled')
|
||||
|
@ -258,32 +264,32 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
'Session should be selectable when parent enabled')
|
||||
|
||||
# hide timeslots
|
||||
self.driver.find_element_by_css_selector(".timeslot-group-toggles button").click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
|
||||
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.time.strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
|
||||
self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal [data-dismiss=\"modal\"]").click()
|
||||
self.assertTrue(not self.driver.find_element_by_css_selector("#timeslot-group-toggles-modal").is_displayed())
|
||||
self.driver.find_element(By.CSS_SELECTOR, "#timeslot-toggle-modal-open").click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#timeslot-group-toggles-modal").is_displayed())
|
||||
self.driver.find_element(By.CSS_SELECTOR, "#timeslot-group-toggles-modal [value=\"{}\"]".format("ts-group-{}-{}".format(slot2.time.strftime("%Y%m%d-%H%M"), int(slot2.duration.total_seconds() / 60)))).click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, "#timeslot-group-toggles-modal [data-dismiss=\"modal\"]").click()
|
||||
self.assertTrue(not self.driver.find_element(By.CSS_SELECTOR, "#timeslot-group-toggles-modal").is_displayed())
|
||||
|
||||
# swap days
|
||||
self.driver.find_element_by_css_selector(".day .swap-days[data-dayid=\"{}\"]".format(slot4.time.date().isoformat())).click()
|
||||
self.assertTrue(self.driver.find_element_by_css_selector("#swap-days-modal").is_displayed())
|
||||
self.driver.find_element_by_css_selector("#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.time.date().isoformat())).click()
|
||||
self.driver.find_element_by_css_selector("#swap-days-modal button[type=\"submit\"]").click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, ".day .swap-days[data-dayid=\"{}\"]".format(slot4.time.date().isoformat())).click()
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#swap-days-modal").is_displayed())
|
||||
self.driver.find_element(By.CSS_SELECTOR, "#swap-days-modal input[name=\"target_day\"][value=\"{}\"]".format(slot1.time.date().isoformat())).click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, "#swap-days-modal button[type=\"submit\"]").click()
|
||||
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{} #session{}'.format(slot4.pk, s1.pk)),
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#timeslot{} #session{}'.format(slot4.pk, s1.pk)),
|
||||
'Session s1 should have moved to second meeting day')
|
||||
|
||||
# swap timeslot column - put session in a differently-timed timeslot
|
||||
self.driver.find_element_by_css_selector(
|
||||
self.driver.find_element(By.CSS_SELECTOR,
|
||||
'.day .swap-timeslot-col[data-timeslot-pk="{}"]'.format(slot1b.pk)
|
||||
).click() # open modal on the second timeslot for room1
|
||||
self.assertTrue(self.driver.find_element_by_css_selector("#swap-timeslot-col-modal").is_displayed())
|
||||
self.driver.find_element_by_css_selector(
|
||||
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#swap-timeslot-col-modal").is_displayed())
|
||||
self.driver.find_element(By.CSS_SELECTOR,
|
||||
'#swap-timeslot-col-modal input[name="target_timeslot"][value="{}"]'.format(slot4.pk)
|
||||
).click() # select room1 timeslot that has a session in it
|
||||
self.driver.find_element_by_css_selector('#swap-timeslot-col-modal button[type="submit"]').click()
|
||||
self.driver.find_element(By.CSS_SELECTOR, '#swap-timeslot-col-modal button[type="submit"]').click()
|
||||
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{} #session{}'.format(slot1b.pk, s1.pk)),
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, '#timeslot{} #session{}'.format(slot1b.pk, s1.pk)),
|
||||
'Session s1 should have moved to second timeslot on first meeting day')
|
||||
|
||||
def test_past_flags(self):
|
||||
|
@ -351,19 +357,19 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.login(username=meeting.schedule.owner.user.username)
|
||||
self.driver.get(url)
|
||||
|
||||
past_flags = self.driver.find_elements_by_css_selector(
|
||||
past_flags = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join('#timeslot{} .past-flag'.format(ts.pk) for ts in past_timeslots)
|
||||
)
|
||||
self.assertGreaterEqual(len(past_flags), len(past_timeslots) + len(past_sessions),
|
||||
'Expected at least one flag for each past timeslot and session')
|
||||
|
||||
now_flags = self.driver.find_elements_by_css_selector(
|
||||
now_flags = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join('#timeslot{} .past-flag'.format(ts.pk) for ts in now_timeslots)
|
||||
)
|
||||
self.assertGreaterEqual(len(now_flags), len(now_timeslots) + len(now_sessions),
|
||||
'Expected at least one flag for each "now" timeslot and session')
|
||||
|
||||
future_flags = self.driver.find_elements_by_css_selector(
|
||||
future_flags = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join('#timeslot{} .past-flag'.format(ts.pk) for ts in future_timeslots)
|
||||
)
|
||||
self.assertGreaterEqual(len(future_flags), len(future_timeslots) + len(future_sessions),
|
||||
|
@ -417,21 +423,21 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.login(username=meeting.schedule.owner.user.username)
|
||||
self.driver.get(url)
|
||||
|
||||
past_swap_days_buttons = self.driver.find_elements_by_css_selector(
|
||||
past_swap_days_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-days[data-start="{}"]'.format(ts.time.date().isoformat()) for ts in past_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(past_swap_days_buttons), len(past_timeslots), 'Missing past swap days buttons')
|
||||
|
||||
future_swap_days_buttons = self.driver.find_elements_by_css_selector(
|
||||
future_swap_days_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-days[data-start="{}"]'.format(ts.time.date().isoformat()) for ts in future_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(future_swap_days_buttons), len(future_timeslots), 'Missing future swap days buttons')
|
||||
|
||||
now_swap_days_buttons = self.driver.find_elements_by_css_selector(
|
||||
now_swap_days_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-days[data-start="{}"]'.format(ts.time.date().isoformat()) for ts in now_timeslots
|
||||
)
|
||||
|
@ -475,7 +481,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.fail('Modal never appeared')
|
||||
self.assertFalse(
|
||||
any(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_day"][value="{}"]'.format(ts.time.date().isoformat()) for ts in past_timeslots)
|
||||
)),
|
||||
'Past day is enabled in swap-days modal for official schedule',
|
||||
|
@ -484,14 +490,14 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
enabled_timeslots = (ts for ts in future_timeslots if ts != future_timeslots[clicked_index])
|
||||
self.assertTrue(
|
||||
all(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_day"][value="{}"]'.format(ts.time.date().isoformat()) for ts in enabled_timeslots)
|
||||
)),
|
||||
'Future day is not enabled in swap-days modal for official schedule',
|
||||
)
|
||||
self.assertFalse(
|
||||
any(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_day"][value="{}"]'.format(ts.time.date().isoformat()) for ts in now_timeslots)
|
||||
)),
|
||||
'"Now" day is enabled in swap-days modal for official schedule',
|
||||
|
@ -533,21 +539,21 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.login(username=meeting.schedule.owner.user.username)
|
||||
self.driver.get(url)
|
||||
|
||||
past_swap_ts_buttons = self.driver.find_elements_by_css_selector(
|
||||
past_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in past_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(past_swap_ts_buttons), len(past_timeslots), 'Missing past swap timeslot col buttons')
|
||||
|
||||
future_swap_ts_buttons = self.driver.find_elements_by_css_selector(
|
||||
future_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in future_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(future_swap_ts_buttons), len(future_timeslots), 'Missing future swap timeslot col buttons')
|
||||
|
||||
now_swap_ts_buttons = self.driver.find_elements_by_css_selector(
|
||||
now_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in now_timeslots
|
||||
)
|
||||
|
@ -590,7 +596,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.fail('Modal never appeared')
|
||||
self.assertFalse(
|
||||
any(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_timeslot"][value="{}"]'.format(ts.pk) for ts in past_timeslots)
|
||||
)),
|
||||
'Past timeslot is enabled in swap-timeslot-col modal for official schedule',
|
||||
|
@ -599,14 +605,14 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
enabled_timeslots = (ts for ts in future_timeslots if ts != future_timeslots[clicked_index])
|
||||
self.assertTrue(
|
||||
all(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_timeslot"][value="{}"]'.format(ts.pk) for ts in enabled_timeslots)
|
||||
)),
|
||||
'Future timeslot is not enabled in swap-timeslot-col modal for official schedule',
|
||||
)
|
||||
self.assertFalse(
|
||||
any(radio.is_enabled()
|
||||
for radio in modal.find_elements_by_css_selector(','.join(
|
||||
for radio in modal.find_elements(By.CSS_SELECTOR, ','.join(
|
||||
'input[name="target_timeslot"][value="{}"]'.format(ts.pk) for ts in now_timeslots)
|
||||
)),
|
||||
'"Now" timeslot is enabled in swap-timeslot-col modal for official schedule',
|
||||
|
@ -625,7 +631,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
def sort_by_position(driver, sessions):
|
||||
"""Helper to sort sessions by the position of their session element in the unscheduled box"""
|
||||
def _sort_key(sess):
|
||||
elt = driver.find_element_by_id('session{}'.format(sess.pk))
|
||||
elt = driver.find_element(By.ID, 'session{}'.format(sess.pk))
|
||||
return (elt.location['y'], elt.location['x'])
|
||||
return sorted(sessions, key=_sort_key)
|
||||
|
||||
|
@ -687,10 +693,10 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
self.login('secretary')
|
||||
self.driver.get(url)
|
||||
|
||||
select = self.driver.find_element_by_name('sort_unassigned')
|
||||
select = self.driver.find_element(By.NAME, 'sort_unassigned')
|
||||
options = {
|
||||
opt.get_attribute('value'): opt
|
||||
for opt in select.find_elements_by_tag_name('option')
|
||||
for opt in select.find_elements(By.TAG_NAME, 'option')
|
||||
}
|
||||
|
||||
# check sorting by name
|
||||
|
@ -760,18 +766,8 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
all of the events needed by the editor.
|
||||
"""
|
||||
# Set up a meeting and a schedule a plain user can edit
|
||||
meeting = make_meeting_test_data()
|
||||
schedule = Schedule.objects.filter(meeting=meeting, owner__user__username="plain").first()
|
||||
sessions = meeting.session_set.filter(type_id='regular')
|
||||
timeslots = meeting.timeslot_set.filter(type_id='regular')
|
||||
self.assertGreaterEqual(timeslots.count(), sessions.count(),
|
||||
'Need a timeslot for each session')
|
||||
for index, session in enumerate(sessions):
|
||||
SchedTimeSessAssignment.objects.create(
|
||||
schedule=schedule,
|
||||
timeslot=timeslots[index],
|
||||
session=session,
|
||||
)
|
||||
schedule = ScheduleFactory(owner__user__username="plain")
|
||||
meeting = schedule.meeting
|
||||
|
||||
# Open the editor
|
||||
self.login()
|
||||
|
@ -780,12 +776,11 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
kwargs=dict(num=meeting.number, name=schedule.name, owner=schedule.owner_email())
|
||||
)
|
||||
self.driver.get(url)
|
||||
|
||||
# Check that the drop target for unassigned sessions is actually empty
|
||||
drop_target = self.driver.find_element_by_css_selector(
|
||||
drop_target = self.driver.find_element(By.CSS_SELECTOR,
|
||||
'.unassigned-sessions .drop-target'
|
||||
)
|
||||
self.assertEqual(len(drop_target.find_elements_by_class_name('session')), 0,
|
||||
self.assertEqual(len(drop_target.find_elements(By.CLASS_NAME, 'session')), 0,
|
||||
'Unassigned sessions box is not empty, test is broken')
|
||||
|
||||
# Check that the drop target has non-zero size
|
||||
|
@ -829,7 +824,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
kwargs=dict(num=meeting.number, owner=schedule.owner.email(), name=schedule.name))
|
||||
self.login(schedule.owner.user.username)
|
||||
self.driver.get(url)
|
||||
session_elements = [self.driver.find_element_by_css_selector(f'#session{sess.pk}') for sess in sessions]
|
||||
session_elements = [self.driver.find_element(By.CSS_SELECTOR, f'#session{sess.pk}') for sess in sessions]
|
||||
session_elements[0].click()
|
||||
|
||||
# All conflicting sessions should be flagged with the would-violate-hint class.
|
||||
|
@ -864,20 +859,20 @@ class ScheduleEditTests(IetfSeleniumTestCase):
|
|||
|
||||
# driver.get() will wait for scripts to finish, but not ajax
|
||||
# requests. Wait for completion of the permissions check:
|
||||
read_only_note = self.driver.find_element_by_id('read_only')
|
||||
read_only_note = self.driver.find_element(By.ID, 'read_only')
|
||||
WebDriverWait(self.driver, 10).until(expected_conditions.invisibility_of_element(read_only_note), "Read-only schedule")
|
||||
|
||||
s1 = Session.objects.filter(group__acronym='mars', meeting=meeting).first()
|
||||
selector = "#session_{}".format(s1.pk)
|
||||
WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, selector)), "Did not find %s"%selector)
|
||||
|
||||
self.assertEqual(self.driver.find_elements_by_css_selector("#sortable-list #session_{}".format(s1.pk)), [])
|
||||
self.assertEqual(self.driver.find_elements(By.CSS_SELECTOR, "#sortable-list #session_{}".format(s1.pk)), [])
|
||||
|
||||
element = self.driver.find_element_by_id('session_{}'.format(s1.pk))
|
||||
target = self.driver.find_element_by_id('sortable-list')
|
||||
element = self.driver.find_element(By.ID, 'session_{}'.format(s1.pk))
|
||||
target = self.driver.find_element(By.ID, 'sortable-list')
|
||||
ActionChains(self.driver).drag_and_drop(element,target).perform()
|
||||
|
||||
self.assertTrue(self.driver.find_elements_by_css_selector("#sortable-list #session_{}".format(s1.pk)))
|
||||
self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, "#sortable-list #session_{}".format(s1.pk)))
|
||||
|
||||
time.sleep(0.1) # The API that modifies the database runs async
|
||||
|
||||
|
@ -905,8 +900,8 @@ class SlideReorderTests(IetfSeleniumTestCase):
|
|||
self.secr_login()
|
||||
self.driver.get(url)
|
||||
#debug.show('unicode(self.driver.page_source)')
|
||||
second = self.driver.find_element_by_css_selector('#slides tr:nth-child(2)')
|
||||
third = self.driver.find_element_by_css_selector('#slides tr:nth-child(3)')
|
||||
second = self.driver.find_element(By.CSS_SELECTOR, '#slides tr:nth-child(2)')
|
||||
third = self.driver.find_element(By.CSS_SELECTOR, '#slides tr:nth-child(3)')
|
||||
ActionChains(self.driver).drag_and_drop(second,third).perform()
|
||||
|
||||
time.sleep(0.1) # The API that modifies the database runs async
|
||||
|
@ -1003,7 +998,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + querystring)
|
||||
self.assert_agenda_item_visibility(visible_groups)
|
||||
self.assert_agenda_view_filter_matches_ics_filter(querystring)
|
||||
weekview_iframe = self.driver.find_element_by_id('weekview')
|
||||
weekview_iframe = self.driver.find_element(By.ID, 'weekview')
|
||||
if len(querystring) == 0:
|
||||
self.assertFalse(weekview_iframe.is_displayed(), 'Weekview should be hidden when filters off')
|
||||
else:
|
||||
|
@ -1184,7 +1179,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
for item in self.get_expected_items():
|
||||
row_id = self.row_id_for_item(item)
|
||||
try:
|
||||
item_row = self.driver.find_element_by_id(row_id)
|
||||
item_row = self.driver.find_element(By.ID, row_id)
|
||||
except NoSuchElementException:
|
||||
item_row = None
|
||||
self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
|
||||
|
@ -1205,7 +1200,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
label = 'Free Slot'
|
||||
|
||||
try:
|
||||
item_div = self.driver.find_element_by_xpath('//div/span[contains(text(),"%s")]/..' % label)
|
||||
item_div = self.driver.find_element(By.XPATH, '//div/span[contains(text(),"%s")]/..' % label)
|
||||
except NoSuchElementException:
|
||||
item_div = None
|
||||
|
||||
|
@ -1480,7 +1475,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
ics_url = self.absreverse('ietf.meeting.views.agenda_ical')
|
||||
|
||||
# parse out the events
|
||||
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"]:not(.info)')
|
||||
agenda_rows = self.driver.find_elements(By.CSS_SELECTOR, '[id^="row-"]:not(.info)')
|
||||
visible_rows = [r for r in agenda_rows if r.is_displayed()]
|
||||
sessions = [self.session_from_agenda_row_id(row.get_attribute("id"))
|
||||
for row in visible_rows]
|
||||
|
@ -1510,7 +1505,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.driver.get(url)
|
||||
|
||||
# modal should start hidden
|
||||
modal_div = self.driver.find_element_by_css_selector('div#modal-%s' % slug)
|
||||
modal_div = self.driver.find_element(By.CSS_SELECTOR, 'div#modal-%s' % slug)
|
||||
self.assertFalse(modal_div.is_displayed())
|
||||
|
||||
# Click the 'materials' button
|
||||
|
@ -1535,7 +1530,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
)
|
||||
self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
for slide in not_deleted_slides:
|
||||
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
anchor = self.driver.find_element(By.XPATH, '//a[text()="%s"]' % slide.title)
|
||||
self.assertIsNotNone(anchor)
|
||||
|
||||
deleted_slides = session.materials.filter(
|
||||
|
@ -1544,7 +1539,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
for slide in deleted_slides:
|
||||
with self.assertRaises(NoSuchElementException):
|
||||
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
self.driver.find_element(By.XPATH, '//a[text()="%s"]' % slide.title)
|
||||
|
||||
# Now close the modal
|
||||
close_modal_button = WebDriverWait(self.driver, 2).until(
|
||||
|
@ -1589,7 +1584,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.assertNotIn(newly_deleted_slide, not_deleted_slides)
|
||||
self.assertIn(newly_undeleted_slide, not_deleted_slides)
|
||||
for slide in not_deleted_slides:
|
||||
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
anchor = self.driver.find_element(By.XPATH, '//a[text()="%s"]' % slide.title)
|
||||
self.assertIsNotNone(anchor)
|
||||
|
||||
deleted_slides = session.materials.filter(
|
||||
|
@ -1599,7 +1594,7 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.assertNotIn(newly_undeleted_slide, deleted_slides)
|
||||
for slide in deleted_slides:
|
||||
with self.assertRaises(NoSuchElementException):
|
||||
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
self.driver.find_element(By.XPATH, '//a[text()="%s"]' % slide.title)
|
||||
|
||||
def test_agenda_time_zone_selection(self):
|
||||
self.assertNotEqual(self.meeting.time_zone, 'UTC', 'Meeting time zone must not be UTC')
|
||||
|
@ -1616,14 +1611,14 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
)
|
||||
)
|
||||
|
||||
tz_select_input = self.driver.find_element_by_id('timezone-select')
|
||||
meeting_tz_link = self.driver.find_element_by_id('meeting-timezone')
|
||||
local_tz_link = self.driver.find_element_by_id('local-timezone')
|
||||
utc_tz_link = self.driver.find_element_by_id('utc-timezone')
|
||||
tz_displays = self.driver.find_elements_by_css_selector('.current-tz')
|
||||
tz_select_input = self.driver.find_element(By.ID, 'timezone-select')
|
||||
meeting_tz_link = self.driver.find_element(By.ID, 'meeting-timezone')
|
||||
local_tz_link = self.driver.find_element(By.ID, 'local-timezone')
|
||||
utc_tz_link = self.driver.find_element(By.ID, 'utc-timezone')
|
||||
tz_displays = self.driver.find_elements(By.CSS_SELECTOR, '.current-tz')
|
||||
self.assertGreaterEqual(len(tz_displays), 1)
|
||||
# we'll check that all current-tz elements are updated, but first check that at least one is in the nav sidebar
|
||||
self.assertIsNotNone(self.driver.find_element_by_css_selector('.nav .current-tz'))
|
||||
self.assertIsNotNone(self.driver.find_element(By.CSS_SELECTOR, '.nav .current-tz'))
|
||||
|
||||
# Moment.js guesses local time zone based on the behavior of Selenium's web client. This seems
|
||||
# to inherit Django's settings.TIME_ZONE but I don't know whether that's guaranteed to be consistent.
|
||||
|
@ -1632,9 +1627,9 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
self.assertNotEqual(self.meeting.time_zone, local_tz, 'Meeting time zone must not be local time zone')
|
||||
self.assertNotEqual(local_tz, 'UTC', 'Local time zone must not be UTC')
|
||||
|
||||
meeting_tz_opt = tz_select_input.find_element_by_css_selector('option[value="%s"]' % self.meeting.time_zone)
|
||||
local_tz_opt = tz_select_input.find_element_by_css_selector('option[value="%s"]' % local_tz)
|
||||
utc_tz_opt = tz_select_input.find_element_by_css_selector('option[value="UTC"]')
|
||||
meeting_tz_opt = tz_select_input.find_element(By.CSS_SELECTOR, 'option[value="%s"]' % self.meeting.time_zone)
|
||||
local_tz_opt = tz_select_input.find_element(By.CSS_SELECTOR, 'option[value="%s"]' % local_tz)
|
||||
utc_tz_opt = tz_select_input.find_element(By.CSS_SELECTOR, 'option[value="UTC"]')
|
||||
|
||||
# Should start off in meeting time zone
|
||||
self.assertTrue(meeting_tz_opt.is_selected())
|
||||
|
@ -1753,21 +1748,21 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
|
||||
# Verify that elements are all updated when the filters change. That the correct elements
|
||||
# have the appropriate classes is a separate test.
|
||||
elements_to_check = self.driver.find_elements_by_css_selector('.agenda-link.filterable')
|
||||
elements_to_check = self.driver.find_elements(By.CSS_SELECTOR, '.agenda-link.filterable')
|
||||
self.assertGreater(len(elements_to_check), 0, 'No elements with agenda links to update were found')
|
||||
|
||||
self.assertFalse(
|
||||
any(checkbox.is_selected()
|
||||
for checkbox in self.driver.find_elements_by_css_selector(
|
||||
for checkbox in self.driver.find_elements(By.CSS_SELECTOR,
|
||||
'input.checkbox[name="selected-sessions"]')),
|
||||
'Sessions were selected before being clicked',
|
||||
)
|
||||
|
||||
mars_sessa_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="mars-sessa"]')
|
||||
mars_sessb_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="mars-sessb"]')
|
||||
farfut_button = self.driver.find_element_by_css_selector('button[data-filter-item="farfut"]')
|
||||
break_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessb"]')
|
||||
registration_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessa"]')
|
||||
mars_sessa_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="mars-sessa"]')
|
||||
mars_sessb_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="mars-sessb"]')
|
||||
farfut_button = self.driver.find_element(By.CSS_SELECTOR, 'button[data-filter-item="farfut"]')
|
||||
break_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessb"]')
|
||||
registration_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessa"]')
|
||||
|
||||
mars_sessa_checkbox.click() # select mars session
|
||||
try:
|
||||
|
@ -1884,9 +1879,9 @@ class WeekviewTests(IetfSeleniumTestCase):
|
|||
def _assert_wrapped(displayed, expected_time_string):
|
||||
self.assertEqual(len(displayed), 2)
|
||||
first = displayed[0]
|
||||
first_parent = first.find_element_by_xpath('..')
|
||||
first_parent = first.find_element(By.XPATH, '..')
|
||||
second = displayed[1]
|
||||
second_parent = second.find_element_by_xpath('..')
|
||||
second_parent = second.find_element(By.XPATH, '..')
|
||||
self.assertNotIn('continued', first.text)
|
||||
self.assertIn(expected_time_string, first_parent.text)
|
||||
self.assertIn('continued', second.text)
|
||||
|
@ -1895,7 +1890,7 @@ class WeekviewTests(IetfSeleniumTestCase):
|
|||
def _assert_not_wrapped(displayed, expected_time_string):
|
||||
self.assertEqual(len(displayed), 1)
|
||||
first = displayed[0]
|
||||
first_parent = first.find_element_by_xpath('..')
|
||||
first_parent = first.find_element(By.XPATH, '..')
|
||||
self.assertNotIn('continued', first.text)
|
||||
self.assertIn(expected_time_string, first_parent.text)
|
||||
|
||||
|
@ -2021,6 +2016,7 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
sg_interim = make_interim_meeting(somegroup, datetime.date.today() + datetime.timedelta(days=20))
|
||||
sg_sess = sg_interim.session_set.first()
|
||||
sg_slot = sg_sess.timeslotassignments.first().timeslot
|
||||
sg_sess.purpose_id = 'plenary'
|
||||
sg_sess.type_id = 'plenary'
|
||||
sg_slot.type_id = 'plenary'
|
||||
sg_sess.save()
|
||||
|
@ -2069,7 +2065,7 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
return meetings
|
||||
|
||||
def find_upcoming_meeting_entries(self):
|
||||
return self.driver.find_elements_by_css_selector(
|
||||
return self.driver.find_elements(By.CSS_SELECTOR,
|
||||
'table#upcoming-meeting-table a.ietf-meeting-link, table#upcoming-meeting-table a.interim-meeting-link'
|
||||
)
|
||||
|
||||
|
@ -2119,7 +2115,7 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
# 12 in order to check the starting month of the following year, which
|
||||
# will usually contain the day 1 year from the start date.
|
||||
for _ in range(13):
|
||||
entries = self.driver.find_elements_by_css_selector(
|
||||
entries = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
'div#calendar div.fc-content'
|
||||
)
|
||||
for entry in entries:
|
||||
|
@ -2150,9 +2146,9 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
if simplified_querystring in ['?show=', '?hide=', '?show=&hide=']:
|
||||
simplified_querystring = '' # these empty querystrings will be dropped (not an exhaustive list)
|
||||
|
||||
ics_link = self.driver.find_element_by_link_text('Download as .ics')
|
||||
ics_link = self.driver.find_element(By.LINK_TEXT, 'Download as .ics')
|
||||
self.assertIn(simplified_querystring, ics_link.get_attribute('href'))
|
||||
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')
|
||||
webcal_link = self.driver.find_element(By.LINK_TEXT, 'Subscribe with webcal')
|
||||
self.assertIn(simplified_querystring, webcal_link.get_attribute('href'))
|
||||
|
||||
def assert_upcoming_view_filter_matches_ics_filter(self, filter_string):
|
||||
|
@ -2314,8 +2310,8 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
ts = session.official_timeslotassignment().timeslot
|
||||
start = ts.utc_start_time().astimezone(zone).strftime('%Y-%m-%d %H:%M')
|
||||
end = ts.utc_end_time().astimezone(zone).strftime('%H:%M')
|
||||
meeting_link = self.driver.find_element_by_link_text(session.meeting.number)
|
||||
time_td = meeting_link.find_element_by_xpath('../../td[@class="session-time"]')
|
||||
meeting_link = self.driver.find_element(By.LINK_TEXT, session.meeting.number)
|
||||
time_td = meeting_link.find_element(By.XPATH, '../../td[@class="session-time"]')
|
||||
self.assertIn('%s - %s' % (start, end), time_td.text)
|
||||
|
||||
def _assert_ietf_tz_correct(meetings, tz):
|
||||
|
@ -2333,8 +2329,8 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
|
||||
start = start_dt.astimezone(zone).strftime('%Y-%m-%d')
|
||||
end = end_dt.astimezone(zone).strftime('%Y-%m-%d')
|
||||
meeting_link = self.driver.find_element_by_link_text("IETF " + meeting.number)
|
||||
time_td = meeting_link.find_element_by_xpath('../../td[@class="meeting-time"]')
|
||||
meeting_link = self.driver.find_element(By.LINK_TEXT, "IETF " + meeting.number)
|
||||
time_td = meeting_link.find_element(By.XPATH, '../../td[@class="meeting-time"]')
|
||||
self.assertIn('%s - %s' % (start, end), time_td.text)
|
||||
|
||||
sessions = [m.session_set.first() for m in self.displayed_interims()]
|
||||
|
@ -2343,12 +2339,12 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
self.assertGreater(len(ietf_meetings), 0)
|
||||
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.upcoming'))
|
||||
tz_select_input = self.driver.find_element_by_id('timezone-select')
|
||||
tz_select_bottom_input = self.driver.find_element_by_id('timezone-select-bottom')
|
||||
local_tz_link = self.driver.find_element_by_id('local-timezone')
|
||||
utc_tz_link = self.driver.find_element_by_id('utc-timezone')
|
||||
local_tz_bottom_link = self.driver.find_element_by_id('local-timezone-bottom')
|
||||
utc_tz_bottom_link = self.driver.find_element_by_id('utc-timezone-bottom')
|
||||
tz_select_input = self.driver.find_element(By.ID, 'timezone-select')
|
||||
tz_select_bottom_input = self.driver.find_element(By.ID, 'timezone-select-bottom')
|
||||
local_tz_link = self.driver.find_element(By.ID, 'local-timezone')
|
||||
utc_tz_link = self.driver.find_element(By.ID, 'utc-timezone')
|
||||
local_tz_bottom_link = self.driver.find_element(By.ID, 'local-timezone-bottom')
|
||||
utc_tz_bottom_link = self.driver.find_element(By.ID, 'utc-timezone-bottom')
|
||||
|
||||
# wait for the select box to be updated - look for an arbitrary time zone to be in
|
||||
# its options list to detect this
|
||||
|
@ -2358,18 +2354,18 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
(By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz)
|
||||
)
|
||||
)
|
||||
arbitrary_tz_bottom_opt = tz_select_bottom_input.find_element_by_css_selector(
|
||||
arbitrary_tz_bottom_opt = tz_select_bottom_input.find_element(By.CSS_SELECTOR,
|
||||
'option[value="%s"]' % arbitrary_tz)
|
||||
|
||||
utc_tz_opt = tz_select_input.find_element_by_css_selector('option[value="UTC"]')
|
||||
utc_tz_bottom_opt= tz_select_bottom_input.find_element_by_css_selector('option[value="UTC"]')
|
||||
utc_tz_opt = tz_select_input.find_element(By.CSS_SELECTOR, 'option[value="UTC"]')
|
||||
utc_tz_bottom_opt= tz_select_bottom_input.find_element(By.CSS_SELECTOR, 'option[value="UTC"]')
|
||||
|
||||
# Moment.js guesses local time zone based on the behavior of Selenium's web client. This seems
|
||||
# to inherit Django's settings.TIME_ZONE but I don't know whether that's guaranteed to be consistent.
|
||||
# To avoid test fragility, ask Moment what it considers local and expect that.
|
||||
local_tz = self.driver.execute_script('return moment.tz.guess();')
|
||||
local_tz_opt = tz_select_input.find_element_by_css_selector('option[value=%s]' % local_tz)
|
||||
local_tz_bottom_opt = tz_select_bottom_input.find_element_by_css_selector('option[value="%s"]' % local_tz)
|
||||
local_tz_opt = tz_select_input.find_element(By.CSS_SELECTOR, 'option[value=%s]' % local_tz)
|
||||
local_tz_bottom_opt = tz_select_bottom_input.find_element(By.CSS_SELECTOR, 'option[value="%s"]' % local_tz)
|
||||
|
||||
# Should start off in local time zone
|
||||
self.assertTrue(local_tz_opt.is_selected())
|
||||
|
@ -2466,7 +2462,7 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
slug = assignment.slug()
|
||||
|
||||
# modal should start hidden
|
||||
modal_div = self.driver.find_element_by_css_selector('div#modal-%s' % slug)
|
||||
modal_div = self.driver.find_element(By.CSS_SELECTOR, 'div#modal-%s' % slug)
|
||||
self.assertFalse(modal_div.is_displayed())
|
||||
|
||||
# Click the 'materials' button
|
||||
|
|
|
@ -322,12 +322,12 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
parent=iab,
|
||||
list_email="venus@ietf.org",
|
||||
)
|
||||
venus_session = Session.objects.create(
|
||||
venus_session = SessionFactory(
|
||||
meeting=meeting,
|
||||
group=venus,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id='regular',
|
||||
add_to_schedule=False,
|
||||
)
|
||||
system_person = Person.objects.get(name="(System)")
|
||||
SchedulingEvent.objects.create(session=venus_session, status_id='schedw', by=system_person)
|
||||
|
@ -784,7 +784,7 @@ class MeetingTests(BaseMeetingTestCase):
|
|||
)
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=plenary,secretariat,ames&hide=reg',
|
||||
querystring='?show=plenary,secretariat,ames&hide=admin',
|
||||
expected_session_summaries=[
|
||||
'Morning Break',
|
||||
'IETF Plenary',
|
||||
|
@ -3062,10 +3062,14 @@ class EditTests(TestCase):
|
|||
|
||||
def test_edit_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number)))
|
||||
self.assertContains(r, "load_assignments")
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.edit_schedule", kwargs={'num': meeting.number}))
|
||||
self.assertRedirects(
|
||||
r,
|
||||
urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs={'num': meeting.number}),
|
||||
status_code=301,
|
||||
)
|
||||
|
||||
def test_official_record_schedule_is_read_only(self):
|
||||
def _set_date_offset_and_retrieve_page(meeting, days_offset, client):
|
||||
|
@ -3156,9 +3160,9 @@ class EditTests(TestCase):
|
|||
|
||||
timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time'))
|
||||
|
||||
base_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="irg"),
|
||||
attendees=20, requested_duration=datetime.timedelta(minutes=30),
|
||||
type_id='regular')
|
||||
base_session = SessionFactory(meeting=meeting, group=Group.objects.get(acronym="irg"),
|
||||
attendees=20, requested_duration=datetime.timedelta(minutes=30),
|
||||
add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=base_session, status_id='schedw', by=Person.objects.get(user__username='secretary'))
|
||||
SchedTimeSessAssignment.objects.create(timeslot=base_timeslot, session=base_session, schedule=meeting.schedule.base)
|
||||
|
||||
|
@ -3872,9 +3876,9 @@ class EditScheduleListTests(TestCase):
|
|||
|
||||
session1 = Session.objects.filter(meeting=meeting, group__acronym='mars').first()
|
||||
session2 = Session.objects.filter(meeting=meeting, group__acronym='ames').first()
|
||||
session3 = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym='mars'),
|
||||
attendees=10, requested_duration=datetime.timedelta(minutes=70),
|
||||
type_id='regular')
|
||||
session3 = SessionFactory(meeting=meeting, group=Group.objects.get(acronym='mars'),
|
||||
attendees=10, requested_duration=datetime.timedelta(minutes=70),
|
||||
add_to_schedule=False)
|
||||
SchedulingEvent.objects.create(session=session3, status_id='schedw', by=Person.objects.first())
|
||||
|
||||
slot2 = TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('-time').first()
|
||||
|
@ -6200,11 +6204,13 @@ class AgendaFilterTests(TestCase):
|
|||
dict(
|
||||
label='child00',
|
||||
keyword='keyword00',
|
||||
toggled_by=['keyword0'],
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child01',
|
||||
keyword='keyword01',
|
||||
toggled_by=['keyword0', 'bof'],
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
|
@ -6215,11 +6221,13 @@ class AgendaFilterTests(TestCase):
|
|||
dict(
|
||||
label='child10',
|
||||
keyword='keyword10',
|
||||
toggled_by=['keyword1'],
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child11',
|
||||
keyword='keyword11',
|
||||
toggled_by=['keyword1', 'bof'],
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
|
@ -6232,11 +6240,13 @@ class AgendaFilterTests(TestCase):
|
|||
dict(
|
||||
label='child20',
|
||||
keyword='keyword20',
|
||||
toggled_by=['keyword2', 'bof'],
|
||||
is_bof=True,
|
||||
),
|
||||
dict(
|
||||
label='child21',
|
||||
keyword='keyword21',
|
||||
toggled_by=['keyword2'],
|
||||
is_bof=False,
|
||||
),
|
||||
]),
|
||||
|
@ -6249,11 +6259,13 @@ class AgendaFilterTests(TestCase):
|
|||
dict(
|
||||
label='child30',
|
||||
keyword='keyword30',
|
||||
toggled_by=[],
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child31',
|
||||
keyword='keyword31',
|
||||
toggled_by=['bof'],
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
|
@ -6283,7 +6295,6 @@ class AgendaFilterTests(TestCase):
|
|||
_assert_button_ok(header_cells.eq(0)('button.keyword0'),
|
||||
expected_label='area0',
|
||||
expected_filter_item='keyword0')
|
||||
|
||||
buttons = button_cells.eq(0)('button.pickview')
|
||||
self.assertEqual(len(buttons), 2) # two children
|
||||
_assert_button_ok(buttons('.keyword00'),
|
||||
|
|
|
@ -88,7 +88,7 @@ type_ietf_only_patterns_id_optional = [
|
|||
url(r'^agenda(?P<ext>.csv)$', views.agenda),
|
||||
url(r'^agenda/edit$',
|
||||
RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True),
|
||||
name='ietf.meetingviews.edit_schedule'),
|
||||
name='ietf.meeting.views.edit_schedule'),
|
||||
url(r'^agenda/edit/$', views.edit_meeting_schedule),
|
||||
url(r'^requests$', views.meeting_requests),
|
||||
url(r'^agenda/agenda\.ics$', views.agenda_ical),
|
||||
|
|
|
@ -88,7 +88,7 @@ from ietf.meeting.utils import diff_meeting_schedules, prefetch_schedule_diff_ob
|
|||
from ietf.meeting.utils import swap_meeting_schedule_timeslot_assignments, bulk_create_timeslots
|
||||
from ietf.meeting.utils import preprocess_meeting_important_dates
|
||||
from ietf.message.utils import infer_message
|
||||
from ietf.name.models import SlideSubmissionStatusName, ProceedingsMaterialTypeName
|
||||
from ietf.name.models import SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName
|
||||
from ietf.secr.proceedings.utils import handle_upload_file
|
||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
|
||||
create_recording)
|
||||
|
@ -621,7 +621,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
|
||||
s.scheduling_label = "???"
|
||||
s.purpose_label = None
|
||||
if (s.purpose is None or s.purpose.slug == 'regular') and s.group:
|
||||
if (s.purpose.slug in ('none', 'regular')) and s.group:
|
||||
s.scheduling_label = s.group.acronym
|
||||
s.purpose_label = 'BoF' if s.group.is_bof() else s.group.type.name
|
||||
else:
|
||||
|
@ -1058,6 +1058,7 @@ class TimeSlotForm(forms.Form):
|
|||
location = RoomNameModelChoiceField(queryset=Room.objects.all(), required=False, empty_label="(No location)")
|
||||
show_location = forms.BooleanField(initial=True, required=False)
|
||||
type = forms.ModelChoiceField(queryset=TimeSlotTypeName.objects.filter(used=True), empty_label=None, required=False)
|
||||
purpose = forms.ModelChoiceField(queryset=SessionPurposeName.objects.filter(used=True), required=False, widget=forms.HiddenInput)
|
||||
name = forms.CharField(help_text='Name that appears on the agenda', required=False)
|
||||
short = forms.CharField(max_length=32,label='Short name', help_text='Abbreviated session name used for material file names', required=False)
|
||||
group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=['ietf', 'team'], state='active'),
|
||||
|
@ -1083,6 +1084,12 @@ class TimeSlotForm(forms.Form):
|
|||
|
||||
self.active_assignment = None
|
||||
|
||||
# only allow timeslots with at least one purpose
|
||||
timeslot_types_with_purpose = set()
|
||||
for spn in SessionPurposeName.objects.filter(used=True):
|
||||
timeslot_types_with_purpose.update(spn.timeslot_types)
|
||||
self.fields['type'].queryset = self.fields['type'].queryset.filter(pk__in=timeslot_types_with_purpose)
|
||||
|
||||
if timeslot:
|
||||
self.initial = {
|
||||
'day': timeslot.time.date(),
|
||||
|
@ -1115,7 +1122,10 @@ class TimeSlotForm(forms.Form):
|
|||
ts_type = self.cleaned_data.get('type')
|
||||
short = self.cleaned_data.get('short')
|
||||
|
||||
if ts_type:
|
||||
if not ts_type:
|
||||
# assign a generic purpose if no type has been set
|
||||
self.cleaned_data['purpose'] = SessionPurposeName.objects.get(slug='open_meeting')
|
||||
else:
|
||||
if ts_type.slug in ['break', 'reg', 'reserved', 'unavail', 'regular']:
|
||||
if ts_type.slug != 'regular':
|
||||
self.cleaned_data['group'] = self.fields['group'].queryset.get(acronym='secretariat')
|
||||
|
@ -1128,6 +1138,15 @@ class TimeSlotForm(forms.Form):
|
|||
if self.timeslot and self.timeslot.type.slug == 'regular' and self.active_assignment and ts_type.slug != self.timeslot.type.slug:
|
||||
self.add_error('type', "Can't change type on time slots for regular sessions when a session has been assigned")
|
||||
|
||||
# find an allowed session purpose (guaranteed by TimeSlotForm)
|
||||
for purpose in SessionPurposeName.objects.filter(used=True):
|
||||
if ts_type.pk in purpose.timeslot_types:
|
||||
self.cleaned_data['purpose'] = purpose
|
||||
break
|
||||
if self.cleaned_data['purpose'] is None:
|
||||
self.add_error('type', f'{ts_type} has no allowed purposes')
|
||||
|
||||
|
||||
if (self.active_assignment
|
||||
and self.active_assignment.session.group != self.cleaned_data.get('group')
|
||||
and self.active_assignment.session.materials.exists()
|
||||
|
@ -1211,6 +1230,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
|||
short=c['short'],
|
||||
group=c['group'],
|
||||
type=c['type'],
|
||||
purpose=c['purpose'],
|
||||
agenda_note=c.get('agenda_note') or "",
|
||||
)
|
||||
|
||||
|
|
|
@ -2620,6 +2620,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]",
|
||||
"session_purposes": "[\n \"presentation\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2656,6 +2657,7 @@
|
|||
"parent_types": [],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"chair\"\n]",
|
||||
"session_purposes": "[\n \"closed_meeting\",\n \"officehours\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2695,6 +2697,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"regular\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2733,6 +2736,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"regular\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2771,6 +2775,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"open_meeting\",\n \"presentation\",\n \"regular\",\n \"social\",\n \"tutorial\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2809,6 +2814,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"closed_meeting\",\n \"regular\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2819,6 +2825,7 @@
|
|||
"about_page": "ietf.group.views.group_about",
|
||||
"acts_like_wg": false,
|
||||
"admin_roles": "[\n \"lead\"\n]",
|
||||
"agenda_filter_type": "none",
|
||||
"agenda_type": "ad",
|
||||
"create_wiki": false,
|
||||
"custom_group_roles": true,
|
||||
|
@ -2846,6 +2853,7 @@
|
|||
],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"closed_meeting\",\n \"officehours\",\n \"open_meeting\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2882,6 +2890,7 @@
|
|||
"parent_types": [],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"chair\"\n]",
|
||||
"session_purposes": "[\n \"officehours\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2918,6 +2927,7 @@
|
|||
"parent_types": [],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]",
|
||||
"session_purposes": "[\n \"closed_meeting\",\n \"open_meeting\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2956,6 +2966,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"admin\",\n \"plenary\",\n \"presentation\",\n \"social\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -2994,6 +3005,7 @@
|
|||
],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3032,6 +3044,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3068,6 +3081,7 @@
|
|||
"parent_types": [],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"delegate\"\n]",
|
||||
"session_purposes": "[\n \"officehours\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3106,6 +3120,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"officehours\",\n \"open_meeting\",\n \"presentation\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3144,6 +3159,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"member\",\n \"advisor\"\n]",
|
||||
"session_purposes": "[\n \"closed_meeting\",\n \"officehours\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3182,6 +3198,7 @@
|
|||
],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"regular\",\n \"tutorial\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3220,6 +3237,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"regular\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3258,6 +3276,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"open_meeting\",\n \"social\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3294,6 +3313,7 @@
|
|||
"parent_types": [],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"officehours\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3332,6 +3352,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
|
||||
"session_purposes": "[\n \"regular\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3371,6 +3392,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"liaiman\"\n]",
|
||||
"session_purposes": "[]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3409,6 +3431,7 @@
|
|||
],
|
||||
"req_subm_approval": false,
|
||||
"role_order": "[\n \"chair\",\n \"member\",\n \"matman\"\n]",
|
||||
"session_purposes": "[\n \"coding\",\n \"presentation\",\n \"social\",\n \"tutorial\"\n]",
|
||||
"show_on_agenda": false
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -3447,6 +3470,7 @@
|
|||
],
|
||||
"req_subm_approval": true,
|
||||
"role_order": "[\n \"chair\",\n \"secr\",\n \"delegate\"\n]",
|
||||
"session_purposes": "[\n \"regular\"\n]",
|
||||
"show_on_agenda": true
|
||||
},
|
||||
"model": "group.groupfeatures",
|
||||
|
@ -12738,6 +12762,138 @@
|
|||
"model": "name.roomresourcename",
|
||||
"pk": "webex"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Meeting administration",
|
||||
"name": "Administrative",
|
||||
"on_agenda": true,
|
||||
"order": 5,
|
||||
"timeslot_types": "[\n \"other\",\n \"reg\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "admin"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Closed meeting",
|
||||
"name": "Closed meeting",
|
||||
"on_agenda": false,
|
||||
"order": 10,
|
||||
"timeslot_types": "[\n \"other\",\n \"regular\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "closed_meeting"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Coding session",
|
||||
"name": "Coding",
|
||||
"on_agenda": true,
|
||||
"order": 4,
|
||||
"timeslot_types": "[\n \"other\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "coding"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Value not set (do not use for new sessions)",
|
||||
"name": "None",
|
||||
"on_agenda": true,
|
||||
"order": 0,
|
||||
"timeslot_types": "[]",
|
||||
"used": false
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "none"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Office hours session",
|
||||
"name": "Office hours",
|
||||
"on_agenda": true,
|
||||
"order": 3,
|
||||
"timeslot_types": "[\n \"other\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "officehours"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Open meeting",
|
||||
"name": "Open meeting",
|
||||
"on_agenda": true,
|
||||
"order": 9,
|
||||
"timeslot_types": "[\n \"other\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "open_meeting"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Plenary session",
|
||||
"name": "Plenary",
|
||||
"on_agenda": true,
|
||||
"order": 7,
|
||||
"timeslot_types": "[\n \"plenary\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "plenary"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Presentation session",
|
||||
"name": "Presentation",
|
||||
"on_agenda": true,
|
||||
"order": 8,
|
||||
"timeslot_types": "[\n \"other\",\n \"regular\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "presentation"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Regular group session",
|
||||
"name": "Regular",
|
||||
"on_agenda": true,
|
||||
"order": 1,
|
||||
"timeslot_types": "[\n \"regular\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "regular"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Social event or activity",
|
||||
"name": "Social",
|
||||
"on_agenda": true,
|
||||
"order": 6,
|
||||
"timeslot_types": "[\n \"break\",\n \"other\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "social"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Tutorial or training session",
|
||||
"name": "Tutorial",
|
||||
"on_agenda": true,
|
||||
"order": 2,
|
||||
"timeslot_types": "[\n \"other\"\n]",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.sessionpurposename",
|
||||
"pk": "tutorial"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -13163,7 +13319,6 @@
|
|||
"desc": "",
|
||||
"name": "Break",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13174,7 +13329,6 @@
|
|||
"desc": "Leadership Meetings",
|
||||
"name": "Leadership",
|
||||
"order": 0,
|
||||
"private": true,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13185,29 +13339,16 @@
|
|||
"desc": "Other Meetings Not Published on Agenda",
|
||||
"name": "Off Agenda",
|
||||
"order": 0,
|
||||
"private": true,
|
||||
"used": true
|
||||
"used": false
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "offagenda"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Office hours timeslot",
|
||||
"name": "Office Hours",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "officehours"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Other",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13218,7 +13359,6 @@
|
|||
"desc": "",
|
||||
"name": "Plenary",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13229,7 +13369,6 @@
|
|||
"desc": "",
|
||||
"name": "Registration",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13240,7 +13379,6 @@
|
|||
"desc": "",
|
||||
"name": "Regular",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
@ -13251,8 +13389,7 @@
|
|||
"desc": "A room has been reserved for use by another body the timeslot indicated",
|
||||
"name": "Room Reserved",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
"used": false
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "reserved"
|
||||
|
@ -13262,7 +13399,6 @@
|
|||
"desc": "A room was not booked for the timeslot indicated",
|
||||
"name": "Room Unavailable",
|
||||
"order": 0,
|
||||
"private": false,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 2.2.19 on 2021-03-29 08:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
TimeSlotTypeName = apps.get_model('name', 'TimeSlotTypeName')
|
||||
|
||||
TimeSlotTypeName.objects.get_or_create(
|
||||
slug='officehours',
|
||||
defaults=dict(
|
||||
name='Office Hours',
|
||||
desc='Office hours timeslot',
|
||||
used=True,
|
||||
)
|
||||
)
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
pass # don't remove the name when migrating
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0033_populate_agendafiltertypename'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -10,7 +10,7 @@ import jsonfield
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0034_add_officehours_timeslottypename'),
|
||||
('name', '0033_populate_agendafiltertypename'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -9,17 +9,18 @@ def forward(apps, schema_editor):
|
|||
SessionPurposeName = apps.get_model('name', 'SessionPurposeName')
|
||||
TimeSlotTypeName = apps.get_model('name', 'TimeSlotTypeName')
|
||||
|
||||
for order, (slug, name, desc, tstypes, on_agenda) in enumerate((
|
||||
('regular', 'Regular', 'Regular group session', ['regular'], True),
|
||||
('tutorial', 'Tutorial', 'Tutorial or training session', ['other'], True),
|
||||
('office_hours', 'Office hours', 'Office hours session', ['other'], True),
|
||||
('coding', 'Coding', 'Coding session', ['other'], True),
|
||||
('admin', 'Administrative', 'Meeting administration', ['other', 'reg'], True),
|
||||
('social', 'Social', 'Social event or activity', ['break', 'other'], True),
|
||||
('plenary', 'Plenary', 'Plenary session', ['plenary'], True),
|
||||
('presentation', 'Presentation', 'Presentation session', ['other', 'regular'], True),
|
||||
('open_meeting', 'Open meeting', 'Open meeting', ['other'], True),
|
||||
('closed_meeting', 'Closed meeting', 'Closed meeting', ['other', 'regular'], False),
|
||||
for order, (slug, name, desc, tstypes, on_agenda, used) in enumerate((
|
||||
('none', 'None', 'Value not set (do not use for new sessions)', [], True, False),
|
||||
('regular', 'Regular', 'Regular group session', ['regular'], True, True),
|
||||
('tutorial', 'Tutorial', 'Tutorial or training session', ['other'], True, True),
|
||||
('officehours', 'Office hours', 'Office hours session', ['other'], True, True),
|
||||
('coding', 'Coding', 'Coding session', ['other'], True, True),
|
||||
('admin', 'Administrative', 'Meeting administration', ['other', 'reg'], True, True),
|
||||
('social', 'Social', 'Social event or activity', ['break', 'other'], True, True),
|
||||
('plenary', 'Plenary', 'Plenary session', ['plenary'], True, True),
|
||||
('presentation', 'Presentation', 'Presentation session', ['other', 'regular'], True, True),
|
||||
('open_meeting', 'Open meeting', 'Open meeting', ['other'], True, True),
|
||||
('closed_meeting', 'Closed meeting', 'Closed meeting', ['other', 'regular'], False, True),
|
||||
)):
|
||||
# verify that we're not about to use an invalid type
|
||||
for ts_type in tstypes:
|
||||
|
@ -29,7 +30,7 @@ def forward(apps, schema_editor):
|
|||
slug=slug,
|
||||
name=name,
|
||||
desc=desc,
|
||||
used=True,
|
||||
used=used,
|
||||
order=order,
|
||||
timeslot_types = tstypes,
|
||||
on_agenda=on_agenda,
|
||||
|
@ -44,7 +45,7 @@ def reverse(apps, schema_editor):
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0035_sessionpurposename'),
|
||||
('name', '0034_sessionpurposename'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -22,7 +22,7 @@ def reverse(apps, schema_editor):
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0036_populate_sessionpurposename'),
|
||||
('name', '0035_populate_sessionpurposename'),
|
||||
('meeting', '0050_populate_session_on_agenda'),
|
||||
]
|
||||
|
|
@ -6,7 +6,7 @@ from django.db import migrations
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0037_depopulate_timeslottypename_private'),
|
||||
('name', '0036_depopulate_timeslottypename_private'),
|
||||
]
|
||||
|
||||
operations = [
|
24
ietf/name/migrations/0038_disuse_offagenda_and_reserved.py
Normal file
24
ietf/name/migrations/0038_disuse_offagenda_and_reserved.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.24 on 2021-10-29 06:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
TimeSlotTypeName = apps.get_model('name', 'TimeSlotTypeName')
|
||||
TimeSlotTypeName.objects.filter(slug__in=('offagenda', 'reserved')).update(used=False)
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
TimeSlotTypeName = apps.get_model('name', 'TimeSlotTypeName')
|
||||
TimeSlotTypeName.objects.filter(slug__in=('offagenda', 'reserved')).update(used=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0037_remove_timeslottypename_private'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -18,7 +18,7 @@ from ietf.name.models import ( AgendaFilterTypeName, AgendaTypeName, BallotPosit
|
|||
ReviewAssignmentStateName, ReviewRequestStateName, ReviewResultName, ReviewTypeName,
|
||||
RoleName, RoomResourceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName,
|
||||
TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName,
|
||||
SlideSubmissionStatusName, ProceedingsMaterialTypeName)
|
||||
SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName )
|
||||
|
||||
class TimeSlotTypeNameResource(ModelResource):
|
||||
class Meta:
|
||||
|
@ -701,3 +701,22 @@ class AgendaFilterTypeNameResource(ModelResource):
|
|||
"order": ALL,
|
||||
}
|
||||
api.name.register(AgendaFilterTypeNameResource())
|
||||
|
||||
|
||||
class SessionPurposeNameResource(ModelResource):
|
||||
class Meta:
|
||||
queryset = SessionPurposeName.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'sessionpurposename'
|
||||
ordering = ['slug', ]
|
||||
filtering = {
|
||||
"slug": ALL,
|
||||
"name": ALL,
|
||||
"desc": ALL,
|
||||
"used": ALL,
|
||||
"order": ALL,
|
||||
"timeslot_types": ALL,
|
||||
"on_agenda": ALL,
|
||||
}
|
||||
api.name.register(SessionPurposeNameResource())
|
||||
|
|
|
@ -336,7 +336,8 @@ class SecrMeetingTestCase(TestCase):
|
|||
'duration':'02:00',
|
||||
'name':'Testing',
|
||||
'short':'test',
|
||||
'type':'reg',
|
||||
'purpose_0': 'admin', # purpose
|
||||
'purpose_1':'reg', # type
|
||||
'group':group.pk,
|
||||
'location': room.pk,
|
||||
'remote_instructions': 'http://webex.com/foobar',
|
||||
|
@ -382,7 +383,8 @@ class SecrMeetingTestCase(TestCase):
|
|||
'time':new_time.strftime('%H:%M'),
|
||||
'duration':'01:00',
|
||||
'day':'2',
|
||||
'type':'other',
|
||||
'purpose_0': 'coding', # purpose
|
||||
'purpose_1': 'other', # type
|
||||
'remote_instructions': 'http://webex.com/foobar',
|
||||
})
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
|
|
@ -260,7 +260,7 @@ class SessionForm(forms.Form):
|
|||
num_sessions_expected = int(data.get('num_session', ''))
|
||||
except ValueError:
|
||||
self.add_error('num_session', 'Invalid value for number of sessions')
|
||||
if len(self.session_forms.errors) == 0 and num_sessions_with_data < num_sessions_expected:
|
||||
if num_sessions_with_data < num_sessions_expected:
|
||||
self.add_error('num_session', 'Must provide data for all sessions')
|
||||
|
||||
# if default (empty) option is selected, cleaned_data won't include num_session key
|
||||
|
|
|
@ -25,6 +25,8 @@ def display_duration(value):
|
|||
"""
|
||||
Maps a session requested duration from select index to
|
||||
label."""
|
||||
if value in (None, ''):
|
||||
return 'unspecified'
|
||||
value = int(value)
|
||||
map = {0: 'None',
|
||||
1800: '30 Minutes',
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import datetime
|
||||
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
@ -76,6 +77,7 @@ class SessionRequestTestCase(TestCase):
|
|||
self.assertRedirects(r,reverse('ietf.secr.sreq.views.main'))
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'deleted')
|
||||
|
||||
@override_settings(SECR_VIRTUAL_MEETINGS=tuple()) # ensure not unexpectedly testing a virtual meeting session
|
||||
def test_edit(self):
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
mars = RoleFactory(name_id='chair', person__user__username='marschairman', group__acronym='mars').group
|
||||
|
@ -90,17 +92,43 @@ class SessionRequestTestCase(TestCase):
|
|||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
attendees = 10
|
||||
comments = 'need lights'
|
||||
mars_sessions = meeting.session_set.filter(group__acronym='mars')
|
||||
post_data = {'num_session':'2',
|
||||
'length_session1':'3600',
|
||||
'length_session2':'3600',
|
||||
'attendees':'10',
|
||||
'attendees': attendees,
|
||||
'constraint_chair_conflict':iabprog.acronym,
|
||||
'comments':'need lights',
|
||||
'session_time_relation': 'subsequent-days',
|
||||
'adjacent_with_wg': group2.acronym,
|
||||
'joint_with_groups': group3.acronym + ' ' + group4.acronym,
|
||||
'joint_for_session': '2',
|
||||
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
|
||||
'session_set-TOTAL_FORMS': '2',
|
||||
'session_set-INITIAL_FORMS': '1',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
'session_set-0-id':mars_sessions[0].pk,
|
||||
'session_set-0-name': mars_sessions[0].name,
|
||||
'session_set-0-short': mars_sessions[0].short,
|
||||
'session_set-0-purpose': mars_sessions[0].purpose_id,
|
||||
'session_set-0-type': mars_sessions[0].type_id,
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': mars_sessions[0].on_agenda,
|
||||
'session_set-0-remote_instructions': mars_sessions[0].remote_instructions,
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': comments,
|
||||
'session_set-0-DELETE': '',
|
||||
# no session_set-1-id because it's a new request
|
||||
'session_set-1-name': '',
|
||||
'session_set-1-short': '',
|
||||
'session_set-1-purpose': 'regular',
|
||||
'session_set-1-type': 'regular',
|
||||
'session_set-1-requested_duration': '3600',
|
||||
'session_set-1-on_agenda': True,
|
||||
'session_set-1-remote_instructions': mars_sessions[0].remote_instructions,
|
||||
'session_set-1-attendees': attendees,
|
||||
'session_set-1-comments': comments,
|
||||
'session_set-1-DELETE': '',
|
||||
'submit': 'Continue'}
|
||||
r = self.client.post(url, post_data, HTTP_HOST='example.com')
|
||||
redirect_url = reverse('ietf.secr.sreq.views.view', kwargs={'acronym': 'mars'})
|
||||
|
@ -133,11 +161,37 @@ class SessionRequestTestCase(TestCase):
|
|||
post_data = {'num_session':'2',
|
||||
'length_session1':'3600',
|
||||
'length_session2':'3600',
|
||||
'attendees':'10',
|
||||
'attendees':attendees,
|
||||
'constraint_chair_conflict':'',
|
||||
'comments':'need lights',
|
||||
'joint_with_groups': group2.acronym,
|
||||
'joint_for_session': '1',
|
||||
'session_set-TOTAL_FORMS': '2',
|
||||
'session_set-INITIAL_FORMS': '2',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
'session_set-0-id':sessions[0].pk,
|
||||
'session_set-0-name': sessions[0].name,
|
||||
'session_set-0-short': sessions[0].short,
|
||||
'session_set-0-purpose': sessions[0].purpose_id,
|
||||
'session_set-0-type': sessions[0].type_id,
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': sessions[0].on_agenda,
|
||||
'session_set-0-remote_instructions': sessions[0].remote_instructions,
|
||||
'session_set-0-attendees': sessions[0].attendees,
|
||||
'session_set-0-comments': sessions[1].comments,
|
||||
'session_set-0-DELETE': '',
|
||||
'session_set-1-id': sessions[1].pk,
|
||||
'session_set-1-name': sessions[1].name,
|
||||
'session_set-1-short': sessions[1].short,
|
||||
'session_set-1-purpose': sessions[1].purpose_id,
|
||||
'session_set-1-type': sessions[1].type_id,
|
||||
'session_set-1-requested_duration': '3600',
|
||||
'session_set-1-on_agenda': sessions[1].on_agenda,
|
||||
'session_set-1-remote_instructions': sessions[1].remote_instructions,
|
||||
'session_set-1-attendees': sessions[1].attendees,
|
||||
'session_set-1-comments': sessions[1].comments,
|
||||
'session_set-1-DELETE': '',
|
||||
'submit': 'Continue'}
|
||||
r = self.client.post(url, post_data, HTTP_HOST='example.com')
|
||||
self.assertRedirects(r, redirect_url)
|
||||
|
@ -160,7 +214,7 @@ class SessionRequestTestCase(TestCase):
|
|||
"""Inactive conflicts should be displayed and removable"""
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today(), group_conflicts=['chair_conflict'])
|
||||
mars = RoleFactory(name_id='chair', person__user__username='marschairman', group__acronym='mars').group
|
||||
SessionFactory(meeting=meeting, group=mars, status_id='sched')
|
||||
session = SessionFactory(meeting=meeting, group=mars, status_id='sched')
|
||||
other_group = GroupFactory()
|
||||
Constraint.objects.create(
|
||||
meeting=meeting,
|
||||
|
@ -184,16 +238,31 @@ class SessionRequestTestCase(TestCase):
|
|||
# check that the target is displayed correctly in the UI
|
||||
self.assertIn(other_group.acronym, delete_checkbox.find('../input[@type="text"]').value)
|
||||
|
||||
attendees = '10'
|
||||
post_data = {
|
||||
'num_session': '1',
|
||||
'length_session1': '3600',
|
||||
'attendees': '10',
|
||||
'attendees': attendees,
|
||||
'constraint_chair_conflict':'',
|
||||
'comments':'',
|
||||
'joint_with_groups': '',
|
||||
'joint_for_session': '',
|
||||
'submit': 'Save',
|
||||
'delete_conflict': 'on',
|
||||
'session_set-TOTAL_FORMS': '1',
|
||||
'session_set-INITIAL_FORMS': '1',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
'session_set-0-id':session.pk,
|
||||
'session_set-0-name': session.name,
|
||||
'session_set-0-short': session.short,
|
||||
'session_set-0-purpose': session.purpose_id,
|
||||
'session_set-0-type': session.type_id,
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': session.on_agenda,
|
||||
'session_set-0-remote_instructions': session.remote_instructions,
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': '',
|
||||
'session_set-0-DELETE': '',
|
||||
'submit': 'Save',
|
||||
}
|
||||
r = self.client.post(url, post_data, HTTP_HOST='example.com')
|
||||
redirect_url = reverse('ietf.secr.sreq.views.view', kwargs={'acronym': 'mars'})
|
||||
|
@ -283,15 +352,31 @@ class SubmitRequestCase(TestCase):
|
|||
url = reverse('ietf.secr.sreq.views.new',kwargs={'acronym':group.acronym})
|
||||
confirm_url = reverse('ietf.secr.sreq.views.confirm',kwargs={'acronym':group.acronym})
|
||||
main_url = reverse('ietf.secr.sreq.views.main')
|
||||
attendees = '10'
|
||||
comments = 'need projector'
|
||||
post_data = {'num_session':'1',
|
||||
'length_session1':'3600',
|
||||
'attendees':'10',
|
||||
'attendees':attendees,
|
||||
'constraint_chair_conflict':'',
|
||||
'comments':'need projector',
|
||||
'comments':comments,
|
||||
'adjacent_with_wg': group2.acronym,
|
||||
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
|
||||
'joint_with_groups': group3.acronym + ' ' + group4.acronym,
|
||||
'joint_for_session': '1',
|
||||
'session_set-TOTAL_FORMS': '1',
|
||||
'session_set-INITIAL_FORMS': '0',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
# no 'session_set-0-id' to create a new session
|
||||
'session_set-0-name': '',
|
||||
'session_set-0-short': '',
|
||||
'session_set-0-purpose': 'regular',
|
||||
'session_set-0-type': 'regular',
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': True,
|
||||
'session_set-0-remote_instructions': '',
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': comments,
|
||||
'session_set-0-DELETE': '',
|
||||
'submit': 'Continue'}
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url,post_data)
|
||||
|
@ -313,7 +398,7 @@ class SubmitRequestCase(TestCase):
|
|||
self.assertRedirects(r, main_url)
|
||||
session_count_after = Session.objects.filter(meeting=meeting, group=group, type='regular').count()
|
||||
self.assertEqual(session_count_after, session_count_before + 1)
|
||||
|
||||
|
||||
# Verify database content
|
||||
session = Session.objects.get(meeting=meeting, group=group)
|
||||
self.assertEqual(session.constraints().get(name='wg_adjacent').target.acronym, group2.acronym)
|
||||
|
@ -329,17 +414,35 @@ class SubmitRequestCase(TestCase):
|
|||
area = RoleFactory(name_id='ad', person=ad, group__type_id='area').group
|
||||
group = GroupFactory(parent=area)
|
||||
url = reverse('ietf.secr.sreq.views.new',kwargs={'acronym':group.acronym})
|
||||
post_data = {'num_session':'2',
|
||||
'length_session1':'3600',
|
||||
'attendees':'10',
|
||||
'constraint_chair_conflict':'',
|
||||
'comments':'need projector'}
|
||||
attendees = '10'
|
||||
comments = 'need projector'
|
||||
post_data = {
|
||||
'num_session':'2',
|
||||
'attendees':attendees,
|
||||
'constraint_chair_conflict':'',
|
||||
'comments':comments,
|
||||
'session_set-TOTAL_FORMS': '1',
|
||||
'session_set-INITIAL_FORMS': '1',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
# no 'session_set-0-id' to create a new session
|
||||
'session_set-0-name': '',
|
||||
'session_set-0-short': '',
|
||||
'session_set-0-purpose': 'regular',
|
||||
'session_set-0-type': 'regular',
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': True,
|
||||
'session_set-0-remote_instructions': '',
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': comments,
|
||||
'session_set-0-DELETE': '',
|
||||
}
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url,post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('#session-request-form')),1)
|
||||
self.assertContains(r, 'You must enter a length for all sessions')
|
||||
self.assertContains(r, 'Must provide data for all sessions')
|
||||
|
||||
def test_submit_request_check_constraints(self):
|
||||
m1 = MeetingFactory(type_id='ietf', date=datetime.date.today() - datetime.timedelta(days=100))
|
||||
|
@ -363,7 +466,7 @@ class SubmitRequestCase(TestCase):
|
|||
target=inactive_group,
|
||||
name_id='chair_conflict',
|
||||
)
|
||||
SessionFactory(group=group, meeting=m1)
|
||||
session = SessionFactory(group=group, meeting=m1)
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
|
@ -375,11 +478,27 @@ class SubmitRequestCase(TestCase):
|
|||
self.assertIn(still_active_group.acronym, conflict1)
|
||||
self.assertNotIn(inactive_group.acronym, conflict1)
|
||||
|
||||
attendees = '10'
|
||||
comments = 'need projector'
|
||||
post_data = {'num_session':'1',
|
||||
'length_session1':'3600',
|
||||
'attendees':'10',
|
||||
'attendees':attendees,
|
||||
'constraint_chair_conflict': group.acronym,
|
||||
'comments':'need projector',
|
||||
'comments':comments,
|
||||
'session_set-TOTAL_FORMS': '1',
|
||||
'session_set-INITIAL_FORMS': '1',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
# no 'session_set-0-id' to create a new session
|
||||
'session_set-0-name': '',
|
||||
'session_set-0-short': '',
|
||||
'session_set-0-purpose': session.purpose_id,
|
||||
'session_set-0-type': session.type_id,
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': session.on_agenda,
|
||||
'session_set-0-remote_instructions': session.remote_instructions,
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': comments,
|
||||
'session_set-0-DELETE': '',
|
||||
'submit': 'Continue'}
|
||||
r = self.client.post(url,post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -405,10 +524,9 @@ class SubmitRequestCase(TestCase):
|
|||
url = reverse('ietf.secr.sreq.views.new',kwargs={'acronym':group.acronym})
|
||||
confirm_url = reverse('ietf.secr.sreq.views.confirm',kwargs={'acronym':group.acronym})
|
||||
len_before = len(outbox)
|
||||
attendees = '10'
|
||||
post_data = {'num_session':'2',
|
||||
'length_session1':'3600',
|
||||
'length_session2':'3600',
|
||||
'attendees':'10',
|
||||
'attendees':attendees,
|
||||
'bethere':str(ad.pk),
|
||||
'constraint_chair_conflict':group4.acronym,
|
||||
'comments':'',
|
||||
|
@ -418,6 +536,32 @@ class SubmitRequestCase(TestCase):
|
|||
'joint_with_groups': group3.acronym,
|
||||
'joint_for_session': '2',
|
||||
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
|
||||
'session_set-TOTAL_FORMS': '2',
|
||||
'session_set-INITIAL_FORMS': '0',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
# no 'session_set-0-id' for new session
|
||||
'session_set-0-name': '',
|
||||
'session_set-0-short': '',
|
||||
'session_set-0-purpose': 'regular',
|
||||
'session_set-0-type': 'regular',
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': True,
|
||||
'session_set-0-remote_instructions': '',
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': '',
|
||||
'session_set-0-DELETE': '',
|
||||
# no 'session_set-1-id' for new session
|
||||
'session_set-1-name': '',
|
||||
'session_set-1-short': '',
|
||||
'session_set-1-purpose': 'regular',
|
||||
'session_set-1-type': 'regular',
|
||||
'session_set-1-requested_duration': '3600',
|
||||
'session_set-1-on_agenda': True,
|
||||
'session_set-1-remote_instructions': '',
|
||||
'session_set-1-attendees': attendees,
|
||||
'session_set-1-comments': '',
|
||||
'session_set-1-DELETE': '',
|
||||
'submit': 'Continue'}
|
||||
self.client.login(username="ameschairman", password="ameschairman+password")
|
||||
# submit
|
||||
|
@ -539,23 +683,59 @@ class SessionFormTest(TestCase):
|
|||
self.group5 = GroupFactory()
|
||||
self.group6 = GroupFactory()
|
||||
|
||||
attendees = '10'
|
||||
comments = 'need lights'
|
||||
self.valid_form_data = {
|
||||
'num_session': '2',
|
||||
'third_session': 'true',
|
||||
'length_session1': '3600',
|
||||
'length_session2': '3600',
|
||||
'length_session3': '3600',
|
||||
'attendees': '10',
|
||||
'attendees': attendees,
|
||||
'constraint_chair_conflict': self.group2.acronym,
|
||||
'constraint_tech_overlap': self.group3.acronym,
|
||||
'constraint_key_participant': self.group4.acronym,
|
||||
'comments': 'need lights',
|
||||
'comments': comments,
|
||||
'session_time_relation': 'subsequent-days',
|
||||
'adjacent_with_wg': self.group5.acronym,
|
||||
'joint_with_groups': self.group6.acronym,
|
||||
'joint_for_session': '3',
|
||||
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
|
||||
'submit': 'Continue'
|
||||
'submit': 'Continue',
|
||||
'session_set-TOTAL_FORMS': '3',
|
||||
'session_set-INITIAL_FORMS': '0',
|
||||
'session_set-MIN_NUM_FORMS': '1',
|
||||
'session_set-MAX_NUM_FORMS': '3',
|
||||
# no 'session_set-0-id' for new session
|
||||
'session_set-0-name': '',
|
||||
'session_set-0-short': '',
|
||||
'session_set-0-purpose': 'regular',
|
||||
'session_set-0-type': 'regular',
|
||||
'session_set-0-requested_duration': '3600',
|
||||
'session_set-0-on_agenda': True,
|
||||
'session_set-0-remote_instructions': '',
|
||||
'session_set-0-attendees': attendees,
|
||||
'session_set-0-comments': '',
|
||||
'session_set-0-DELETE': '',
|
||||
# no 'session_set-1-id' for new session
|
||||
'session_set-1-name': '',
|
||||
'session_set-1-short': '',
|
||||
'session_set-1-purpose': 'regular',
|
||||
'session_set-1-type': 'regular',
|
||||
'session_set-1-requested_duration': '3600',
|
||||
'session_set-1-on_agenda': True,
|
||||
'session_set-1-remote_instructions': '',
|
||||
'session_set-1-attendees': attendees,
|
||||
'session_set-1-comments': '',
|
||||
'session_set-1-DELETE': '',
|
||||
# no 'session_set-2-id' for new session
|
||||
'session_set-2-name': '',
|
||||
'session_set-2-short': '',
|
||||
'session_set-2-purpose': 'regular',
|
||||
'session_set-2-type': 'regular',
|
||||
'session_set-2-requested_duration': '3600',
|
||||
'session_set-2-on_agenda': True,
|
||||
'session_set-2-remote_instructions': '',
|
||||
'session_set-2-attendees': attendees,
|
||||
'session_set-2-comments': '',
|
||||
'session_set-2-DELETE': '',
|
||||
}
|
||||
|
||||
def test_valid(self):
|
||||
|
@ -637,58 +817,65 @@ class SessionFormTest(TestCase):
|
|||
def test_invalid_joint_for_session(self):
|
||||
form = self._invalid_test_helper({
|
||||
'third_session': '',
|
||||
'session_set-TOTAL_FORMS': '2',
|
||||
'num_session': 2,
|
||||
'joint_for_session': '3',
|
||||
})
|
||||
self.assertEqual(form.errors,
|
||||
{
|
||||
'joint_for_session': ['The third session can not be the joint session, '
|
||||
'because you have not requested a third session.']
|
||||
'joint_for_session': [
|
||||
'Session 3 can not be the joint session, the session has not been requested.']
|
||||
})
|
||||
|
||||
form = self._invalid_test_helper({
|
||||
'third_session': '',
|
||||
'length_session2': '',
|
||||
'session_set-TOTAL_FORMS': '1',
|
||||
'num_session': 1,
|
||||
'joint_for_session': '2',
|
||||
'session_time_relation': '',
|
||||
})
|
||||
self.assertEqual(form.errors,
|
||||
{
|
||||
'joint_for_session': ['The second session can not be the joint session, '
|
||||
'because you have not requested a second session.']
|
||||
'joint_for_session': [
|
||||
'Session 2 can not be the joint session, the session has not been requested.']
|
||||
})
|
||||
|
||||
def test_invalid_missing_session_length(self):
|
||||
form = self._invalid_test_helper({
|
||||
'length_session2': '',
|
||||
'session_set-TOTAL_FORMS': '2',
|
||||
'session_set-1-requested_duration': '',
|
||||
'third_session': 'false',
|
||||
'joint_for_session': None,
|
||||
})
|
||||
self.assertEqual(form.errors,
|
||||
{
|
||||
'length_session2': ['You must enter a length for all sessions'],
|
||||
})
|
||||
self.assertEqual(form.session_forms.errors,
|
||||
[
|
||||
{},
|
||||
{'requested_duration': ['This field is required.']},
|
||||
])
|
||||
|
||||
form = self._invalid_test_helper({
|
||||
'length_session2': '',
|
||||
'length_session3': '',
|
||||
'session_set-1-requested_duration': '',
|
||||
'session_set-2-requested_duration': '',
|
||||
'joint_for_session': None,
|
||||
})
|
||||
self.assertEqual(form.errors,
|
||||
{
|
||||
'length_session2': ['You must enter a length for all sessions'],
|
||||
'length_session3': ['You must enter a length for all sessions'],
|
||||
})
|
||||
self.assertEqual(
|
||||
form.session_forms.errors,
|
||||
[
|
||||
{},
|
||||
{'requested_duration': ['This field is required.']},
|
||||
{'requested_duration': ['This field is required.']},
|
||||
])
|
||||
|
||||
form = self._invalid_test_helper({
|
||||
'length_session3': '',
|
||||
'session_set-2-requested_duration': '',
|
||||
'joint_for_session': None,
|
||||
})
|
||||
self.assertEqual(form.errors,
|
||||
{
|
||||
'length_session3': ['You must enter a length for all sessions'],
|
||||
})
|
||||
self.assertEqual(form.session_forms.errors,
|
||||
[
|
||||
{},
|
||||
{},
|
||||
{'requested_duration': ['This field is required.']},
|
||||
])
|
||||
|
||||
def _invalid_test_helper(self, new_form_data):
|
||||
form_data = dict(self.valid_form_data, **new_form_data)
|
||||
|
|
|
@ -336,7 +336,7 @@ def confirm(request, acronym):
|
|||
jfs = form.data.get('joint_for_session', '-1')
|
||||
if not jfs: # jfs might be ''
|
||||
jfs = '-1'
|
||||
if int(jfs) == count:
|
||||
if int(jfs) == count + 1: # count is zero-indexed
|
||||
groups_split = form.cleaned_data.get('joint_with_groups').replace(',',' ').split()
|
||||
joint = Group.objects.filter(acronym__in=groups_split)
|
||||
new_session.joint_with_groups.set(joint)
|
||||
|
@ -645,7 +645,7 @@ def new(request, acronym):
|
|||
raise Http404(f'Cannot request sessions for group "{acronym}"')
|
||||
meeting = get_meeting(days=14)
|
||||
session_conflicts = dict(inbound=inbound_session_conflicts_as_string(group, meeting))
|
||||
is_virtual = meeting.number in settings.SECR_VIRTUAL_MEETINGS,
|
||||
is_virtual = meeting.number in settings.SECR_VIRTUAL_MEETINGS
|
||||
FormClass = get_session_form_class()
|
||||
|
||||
# check if app is locked
|
||||
|
@ -723,6 +723,7 @@ def no_session(request, acronym):
|
|||
meeting=meeting,
|
||||
requested_duration=datetime.timedelta(0),
|
||||
type_id='regular',
|
||||
purpose_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
|
@ -845,7 +846,8 @@ def view(request, acronym, num = None):
|
|||
return render(request, 'sreq/view.html', {
|
||||
'is_locked': is_locked,
|
||||
'is_virtual': meeting.number in settings.SECR_VIRTUAL_MEETINGS,
|
||||
'session': session,
|
||||
'session': session, # legacy processed data
|
||||
'sessions': sessions, # actual session instances
|
||||
'activities': activities,
|
||||
'meeting': meeting,
|
||||
'group': group,
|
||||
|
|
|
@ -6,7 +6,7 @@ Session Requester: {{ login }}
|
|||
{% if session.joint_with_groups %}{{ session.joint_for_session_display }} joint with: {{ session.joint_with_groups }}{% endif %}
|
||||
|
||||
Number of Sessions: {{ session.num_session }}
|
||||
Length of Session(s): {{ session.length_session1|display_duration }}{% if session.length_session2 %}, {{ session.length_session2|display_duration }}{% endif %}{% if session.length_session3 %}, {{ session.length_session3|display_duration }}{% endif %}
|
||||
Length of Session(s): {{ session.length_session1|display_duration }}{% if session.length_session2 %}, {{ session.length_session2|display_duration }}{% endif %}{% if session.length_session3 %}, {{ session.length_session3|display_duration }}{% endif %}
|
||||
Number of Attendees: {{ session.attendees }}
|
||||
Conflicts to Avoid:
|
||||
{% for line in session.outbound_conflicts %} {{line}}
|
||||
|
|
|
@ -3,25 +3,12 @@
|
|||
<col width="200">
|
||||
<tr class="row1"><td>Working Group Name:</td><td>{{ group.name }} ({{ group.acronym }})</td></tr>
|
||||
<tr class="row2"><td>Area Name:</td><td>{{ group.parent }}</td></tr>
|
||||
<tr class="row1"><td>Number of Sessions Requested:</td><td>{% if session.length_session3 %}3{% else %}{{ session.num_session }}{% endif %}</td></tr>
|
||||
{% for sess_form in form.session_forms %}{% if sess_form.cleaned_data and not sess_form.cleaned_data.DELETE %}
|
||||
<tr class="row2"><td>Session {{ forloop.counter }}:</td><td>
|
||||
<dl>
|
||||
<dt>Length</dt><dd>{{ sess_form.cleaned_data.requested_duration.total_seconds|display_duration }}</dd>
|
||||
{% if sess_form.cleaned_data.name %}<dt>Name</dt><dd>{{ sess_form.cleaned_data.name }}</dd>{% endif %}
|
||||
{% if sess_form.cleaned_data.purpose.slug != 'regular' %}
|
||||
<dt>Purpose</dt>
|
||||
<dd>
|
||||
{{ sess_form.cleaned_data.purpose }}
|
||||
{% if sess_form.cleaned_data.purpose.timeslot_types|length > 1 %}({{ sess_form.cleaned_data.type }}){% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</td></tr>
|
||||
{% if group.features.acts_like_wg and forloop.counter == 2 and not is_virtual %}
|
||||
<tr class="row2"><td>Time between sessions:</td><td>{% if session.session_time_relation_display %}{{ session.session_time_relation_display }}{% else %}No preference{% endif %}</td></tr>
|
||||
{% endif %}
|
||||
{% endif %}{% endfor %}
|
||||
<tr class="row1"><td>Number of Sessions Requested:</td><td>{% if session.third_session %}3{% else %}{{ session.num_session }}{% endif %}</td></tr>
|
||||
{% if form %}
|
||||
{% include 'includes/sessions_request_view_formset.html' with formset=form.session_forms group=group session=session only %}
|
||||
{% else %}
|
||||
{% include 'includes/sessions_request_view_session_set.html' with session_set=sessions group=group session=session only %}
|
||||
{% endif %}
|
||||
<tr class="row1"><td>Number of Attendees:</td><td>{{ session.attendees }}</td></tr>
|
||||
<tr class="row2">
|
||||
<td>Conflicts to Avoid:</td>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
{% load ams_filters %}{# keep this in sync with sessions_request_view_session_set.html #}
|
||||
{% for sess_form in formset %}{% if sess_form.cleaned_data and not sess_form.cleaned_data.DELETE %}
|
||||
<tr class="row2">
|
||||
<td>Session {{ forloop.counter }}:</td>
|
||||
<td>
|
||||
<dl>
|
||||
<dt>Length</dt>
|
||||
<dd>{{ sess_form.cleaned_data.requested_duration.total_seconds|display_duration }}</dd>
|
||||
{% if sess_form.cleaned_data.name %}
|
||||
<dt>Name</dt>
|
||||
<dd>{{ sess_form.cleaned_data.name }}</dd>{% endif %}
|
||||
{% if sess_form.cleaned_data.purpose.slug != 'regular' %}
|
||||
<dt>Purpose</dt>
|
||||
<dd>
|
||||
{{ sess_form.cleaned_data.purpose }}
|
||||
{% if sess_form.cleaned_data.purpose.timeslot_types|length > 1 %}({{ sess_form.cleaned_data.type }}
|
||||
){% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
{% if group.features.acts_like_wg and forloop.counter == 2 and not is_virtual %}
|
||||
<tr class="row2">
|
||||
<td>Time between sessions:</td>
|
||||
<td>{% if session.session_time_relation_display %}{{ session.session_time_relation_display }}{% else %}No
|
||||
preference{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}{% endfor %}
|
|
@ -0,0 +1,30 @@
|
|||
{% load ams_filters %}{# keep this in sync with sessions_request_view_formset.html #}
|
||||
{% for sess in session_set %}
|
||||
<tr class="row2">
|
||||
<td>Session {{ forloop.counter }}:</td>
|
||||
<td>
|
||||
<dl>
|
||||
<dt>Length</dt>
|
||||
<dd>{{ sess.requested_duration.total_seconds|display_duration }}</dd>
|
||||
{% if sess.name %}
|
||||
<dt>Name</dt>
|
||||
<dd>{{ sess.name }}</dd>{% endif %}
|
||||
{% if sess.purpose.slug != 'regular' %}
|
||||
<dt>Purpose</dt>
|
||||
<dd>
|
||||
{{ sess.purpose }}
|
||||
{% if sess.purpose.timeslot_types|length > 1 %}({{ sess.type }}
|
||||
){% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
{% if group.features.acts_like_wg and forloop.counter == 2 and not is_virtual %}
|
||||
<tr class="row2">
|
||||
<td>Time between sessions:</td>
|
||||
<td>{% if session.session_time_relation_display %}{{ session.session_time_relation_display }}{% else %}No
|
||||
preference{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -963,7 +963,7 @@ FLOORPLAN_LEGACY_BASE_URL = 'https://tools.ietf.org/agenda/{meeting.number}/venu
|
|||
FLOORPLAN_LAST_LEGACY_MEETING = 95 # last meeting to use FLOORPLAN_LEGACY_BASE_URL
|
||||
|
||||
MEETING_USES_CODIMD_DATE = datetime.date(2020,7,6)
|
||||
MEETING_LEGACY_OFFICE_HOURS_END = 111 # last meeting to use legacy office hours representation
|
||||
MEETING_LEGACY_OFFICE_HOURS_END = 112 # last meeting to use legacy office hours representation
|
||||
|
||||
# Maximum dimensions to accept at all
|
||||
MEETINGHOST_LOGO_MAX_UPLOAD_WIDTH = 400
|
||||
|
|
|
@ -740,6 +740,18 @@ jQuery(document).ready(function () {
|
|||
if (timeSlotTypeInputs.length > 0) {
|
||||
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
|
||||
updateTimeSlotTypeToggling();
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-type-toggles .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', true);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-type-toggles .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', false);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
}
|
||||
|
||||
// Toggling session purposes
|
||||
|
@ -771,10 +783,10 @@ jQuery(document).ready(function () {
|
|||
}
|
||||
|
||||
// toggling visible timeslots
|
||||
let timeslotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body .individual-timeslots input");
|
||||
function updateTimeslotGroupToggling() {
|
||||
let timeSlotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body .individual-timeslots input");
|
||||
function updateTimeSlotGroupToggling() {
|
||||
let checked = [];
|
||||
timeslotGroupInputs.filter(":checked").each(function () {
|
||||
timeSlotGroupInputs.filter(":checked").each(function () {
|
||||
checked.push("." + this.value);
|
||||
});
|
||||
|
||||
|
@ -786,8 +798,21 @@ jQuery(document).ready(function () {
|
|||
});
|
||||
}
|
||||
|
||||
timeslotGroupInputs.on("click change", updateTimeslotGroupToggling);
|
||||
updateTimeslotGroupToggling();
|
||||
timeSlotGroupInputs.on("click change", updateTimeSlotGroupToggling);
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-group-buttons .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotGroupInputs.prop('checked', true);
|
||||
updateTimeSlotGroupToggling();
|
||||
});
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-group-buttons .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotGroupInputs.prop('checked', false);
|
||||
updateTimeSlotGroupToggling();
|
||||
});
|
||||
|
||||
updateTimeSlotGroupToggling();
|
||||
updatePastTimeslots();
|
||||
setInterval(updatePastTimeslots, 10 * 1000 /* ms */);
|
||||
|
||||
|
|
|
@ -195,9 +195,9 @@
|
|||
</span>
|
||||
|
||||
{% if session_purposes|length > 1 %}
|
||||
<button class="btn btn-default" data-toggle="modal" data-target="#session-toggles-modal"><input type="checkbox" checked="checked" disabled> Sessions</button>
|
||||
<button id="session-toggle-modal-open" class="btn btn-default" data-toggle="modal" data-target="#session-toggles-modal"><input type="checkbox" checked="checked" disabled> Sessions</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-default" data-toggle="modal" data-target="#timeslot-group-toggles-modal"><input type="checkbox" checked="checked" disabled> Timeslots</button>
|
||||
<button id="timeslot-toggle-modal-open" class="btn btn-default" data-toggle="modal" data-target="#timeslot-group-toggles-modal"><input type="checkbox" checked="checked" disabled> Timeslots</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -216,7 +216,12 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="timeslot-group-buttons">
|
||||
<button type="button" class="btn btn-default select-all">Select all times</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear times</button>
|
||||
</div>
|
||||
<div class="individual-timeslots">
|
||||
|
||||
{% for day, t_groups in timeslot_groups %}
|
||||
<div>
|
||||
<div><strong>{{ day|date:"M. d" }}</strong></div>
|
||||
|
@ -231,6 +236,8 @@
|
|||
{% for type in timeslot_types %}
|
||||
<label class="timeslot-type-{{ type.slug }}"><input type="checkbox" checked value="{{ type.slug }}"> {{ type }}</label>
|
||||
{% endfor %}
|
||||
<button type="button" class="btn btn-default select-all">Select all types</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear types</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -256,7 +263,7 @@
|
|||
<div class="session-purpose-toggles">
|
||||
{% for purpose in session_purposes %}
|
||||
<div>
|
||||
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{{ purpose.slug }}"> {{ purpose }}</label>
|
||||
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{% firstof purpose.slug 'none' %}"> {{ purpose }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="button" class="btn btn-default select-all">Select all</button>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div id="session{{ session.pk }}"
|
||||
class="session {% if not session.group.parent.scheduling_color %}untoggleable-by-parent{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} purpose-{% firstof session.purpose.slug 'regular' %} {% if session.readonly %}readonly{% endif %} {% if not session.on_agenda %}off-agenda{% endif %}"
|
||||
class="session {% if not session.group.parent.scheduling_color %}untoggleable-by-parent{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} purpose-{{ session.purpose.slug }} {% if session.readonly %}readonly{% endif %} {% if not session.on_agenda %}off-agenda{% endif %}"
|
||||
style="width:{{ session.layout_width }}em;"
|
||||
data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}
|
||||
data-attendees="{{ session.attendees }}"{% endif %}
|
||||
|
|
Loading…
Reference in a new issue