Skip to content

Commit c78ffbc

Browse files
committed
Introduce support for setting a base schedule on a schedule. All
assignments on the base schedule are shown in the pages for the schedule, read-only. This allows managing things like breaks and misc sessions separately from the regular WG sessions. Base schedules are not allowed to be the base of other base schedules (the hierarchy can only be one level deep) to simplify the mental model and the code. Add link for creating new schedules instead of relying on copying Empty-Schedule and change the meeting creation code to no longer create the special Empty-Schedule. Instead a "base" schedule is created and a first schedule with the name and permissions of the user creating the meeting, using "base" as base. Speed up a couple of the Secretariat/AD agenda views by adding prefetches. - Legacy-Id: 18355
1 parent 923cb35 commit c78ffbc

30 files changed

Lines changed: 399 additions & 212 deletions

ietf/bin/create-break-sessions

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ for meeting in Meeting.objects.filter(type="ietf").order_by("date"):
3333
for schedule in meeting.schedule_set.all():
3434
print " Checking for missing Break and Reg sessions in %s" % schedule
3535
for timeslot in meeting.timeslot_set.all():
36-
if timeslot.type_id == 'break':
37-
assignment, created = ScheduleTimeslotSSessionAssignment.objects.get_or_create(timeslot=timeslot, session=brk, schedule=schedule)
36+
if timeslot.type_id == 'break' and not (schedule.base and SchedTimeSessAssignment.objects.filter(timeslot=timeslot, session=brk, schedule=schedule.base).exists()):
37+
assignment, created = SchedTimeSessAssignment.objects.get_or_create(timeslot=timeslot, session=brk, schedule=schedule)
3838
if created:
3939
print " Added %s break assignment" % timeslot
40-
if timeslot.type_id == 'reg':
41-
assignment, created = ScheduleTimeslotSSessionAssignment.objects.get_or_create(timeslot=timeslot, session=reg, schedule=schedule)
40+
if timeslot.type_id == 'reg' and not (schedule.base and SchedTimeSessAssignment.objects.filter(timeslot=timeslot, session=reg, schedule=schedule.base).exists()):
41+
assignment, created = SchedTimeSessAssignment.objects.get_or_create(timeslot=timeslot, session=reg, schedule=schedule)
4242
if created:
4343
print " Added %s registration assignment" % timeslot

ietf/meeting/admin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ class SchedulingEventAdmin(admin.ModelAdmin):
126126

127127
class ScheduleAdmin(admin.ModelAdmin):
128128
list_display = ["name", "meeting", "owner", "visible", "public", "badness"]
129-
list_filter = ["meeting", ]
130-
raw_id_fields = ["meeting", "owner", ]
129+
list_filter = ["meeting"]
130+
raw_id_fields = ["meeting", "owner", "origin", "base"]
131131
search_fields = ["meeting__number", "name", "owner__name"]
132132
ordering = ["-meeting", "name"]
133133

ietf/meeting/helpers.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,6 @@ def get_schedule(meeting, name=None):
143143
schedule = get_object_or_404(meeting.schedule_set, name=name)
144144
return schedule
145145

146-
def get_schedule_by_id(meeting, schedid):
147-
if schedid is None:
148-
schedule = meeting.schedule
149-
else:
150-
schedule = get_object_or_404(meeting.schedule_set, id=int(schedid))
151-
return schedule
152-
153146
# seems this belongs in ietf/person/utils.py?
154147
def get_person_by_email(email):
155148
# email == None may actually match people who haven't set an email!
@@ -428,6 +421,11 @@ def get_announcement_initial(meeting, is_change=False):
428421
type = group.type.slug.upper()
429422
if group.type.slug == 'wg' and group.state.slug == 'bof':
430423
type = 'BOF'
424+
425+
assignments = SchedTimeSessAssignment.objects.filter(
426+
schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]
427+
).order_by('timeslot__time')
428+
431429
initial['subject'] = '{name} ({acronym}) {type} {desc} Meeting: {date}{change}'.format(
432430
name=group.name,
433431
acronym=group.acronym,

ietf/meeting/management/commands/create_dummy_meeting.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ def handle(self, *args, **options):
8585
date=datetime.date(2019, 11, 16),
8686
days=7,
8787
)
88-
schedule = Schedule.objects.create(meeting=m, name='Empty-Schedule', owner_id=1,
89-
visible=True, public=True)
88+
base_schedule = Schedule.objects.create(meeting=m, name='base', owner_id=1,
89+
visible=True, public=True)
90+
91+
schedule = Schedule.objects.create(meeting=m, name='first1', owner_id=1,
92+
visible=True, public=True, base=base_schedule)
9093
m.schedule = schedule
9194
m.save()
9295

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 2.0.13 on 2020-08-07 09:30
2+
3+
from django.db import migrations
4+
import django.db.models.deletion
5+
import ietf.utils.models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('meeting', '0031_add_session_origin'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='schedule',
17+
name='base',
18+
field=ietf.utils.models.ForeignKey(blank=True, help_text='Sessions scheduled in the base show up in this schedule.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='derivedschedule_set', to='meeting.Schedule'),
19+
),
20+
migrations.AlterField(
21+
model_name='schedule',
22+
name='origin',
23+
field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='meeting.Schedule'),
24+
),
25+
]

ietf/meeting/models.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ def updated(self):
291291
min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
292292
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
293293
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
294-
assignments_updated = (self.schedule.assignments.aggregate(Max('modified'))["modified__max"] or min_time) if self.schedule else min_time
294+
assignments_updated = min_time
295+
if self.schedule:
296+
assignments_updated = SchedTimeSessAssignment.objects.filter(schedule__in=[self.schedule, self.schedule.base if self.schedule else None]).aggregate(Max('modified'))["modified__max"] or min_time
295297
ts = max(timeslots_updated, sessions_updated, assignments_updated)
296298
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
297299
ts = tz.localize(ts)
@@ -450,7 +452,7 @@ class TimeSlot(models.Model):
450452
@property
451453
def session(self):
452454
if not hasattr(self, "_session_cache"):
453-
self._session_cache = self.sessions.filter(timeslotassignments__schedule=self.meeting.schedule).first()
455+
self._session_cache = self.sessions.filter(timeslotassignments__schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting else None]).first()
454456
return self._session_cache
455457

456458
@property
@@ -626,7 +628,10 @@ class Schedule(models.Model):
626628
public = models.BooleanField(default=True, help_text="Allow others to see this agenda.")
627629
badness = models.IntegerField(null=True, blank=True)
628630
notes = models.TextField(blank=True)
629-
origin = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL)
631+
origin = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL, related_name="+")
632+
base = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL,
633+
help_text="Sessions scheduled in the base schedule show up in this schedule too.", related_name="derivedschedule_set",
634+
limit_choices_to={'base': None}) # prevent the inheritance from being more than one layer deep (no recursion)
630635

631636
def __str__(self):
632637
return u"%s:%s(%s)" % (self.meeting, self.name, self.owner)
@@ -1047,7 +1052,7 @@ def __str__(self):
10471052
ss0name = "(%s)" % SessionStatusName.objects.get(slug=status_id).name
10481053
else:
10491054
ss0name = "(unscheduled)"
1050-
ss = self.timeslotassignments.filter(schedule=self.meeting.schedule).order_by('timeslot__time')
1055+
ss = self.timeslotassignments.filter(schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting.schedule else None]).order_by('timeslot__time')
10511056
if ss:
10521057
ss0name = ','.join(x.timeslot.time.strftime("%a-%H%M") for x in ss)
10531058
return "%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
@@ -1080,11 +1085,8 @@ def constraints(self):
10801085
def reverse_constraints(self):
10811086
return Constraint.objects.filter(target=self.group, meeting=self.meeting).order_by('name__name')
10821087

1083-
def timeslotassignment_for_schedule(self, schedule):
1084-
return self.timeslotassignments.filter(schedule=schedule).first()
1085-
10861088
def official_timeslotassignment(self):
1087-
return self.timeslotassignment_for_schedule(self.meeting.schedule)
1089+
return self.timeslotassignments.filter(schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting.schedule else None]).first()
10881090

10891091
def constraints_dict(self, host_scheme):
10901092
constraint_list = []

ietf/meeting/test_data.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ def make_meeting_test_data(meeting=None):
7676

7777
if not meeting:
7878
meeting = Meeting.objects.get(number="72", type="ietf")
79-
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-schedule", visible=True, public=True)
80-
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-schedule", visible=True, public=True)
79+
base_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="base", visible=True, public=True)
80+
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-schedule", visible=True, public=True, base=base_schedule)
81+
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-schedule", visible=True, public=True, base=base_schedule)
8182

8283
# test room
8384
pname = RoomResourceName.objects.create(name='projector',slug='proj')
@@ -146,15 +147,15 @@ def make_meeting_test_data(meeting=None):
146147
requested_duration=datetime.timedelta(minutes=480),
147148
type_id="reg")
148149
SchedulingEvent.objects.create(session=reg_session, status_id='schedw', by=system_person)
149-
SchedTimeSessAssignment.objects.create(timeslot=reg_slot, session=reg_session, schedule=schedule)
150+
SchedTimeSessAssignment.objects.create(timeslot=reg_slot, session=reg_session, schedule=base_schedule)
150151

151152
# Break
152153
break_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
153154
name="Morning Break", attendees=250,
154155
requested_duration=datetime.timedelta(minutes=30),
155156
type_id="break")
156157
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
157-
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=schedule)
158+
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=base_schedule)
158159

159160
meeting.schedule = schedule
160161
meeting.save()

ietf/meeting/tests_js.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ def test_edit_meeting_schedule(self):
133133
url = self.absreverse('ietf.meeting.views.edit_meeting_schedule', kwargs=dict(num=meeting.number, name=schedule.name, owner=schedule.owner_email()))
134134
self.driver.get(url)
135135

136+
WebDriverWait(self.driver, 2).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.edit-meeting-schedule')))
137+
136138
self.assertEqual(len(self.driver.find_elements_by_css_selector('.session')), 3)
137139

138140
# select - show session info

0 commit comments

Comments
 (0)