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
This commit is contained in:
parent
923cb35755
commit
c78ffbcd18
|
@ -33,11 +33,11 @@ for meeting in Meeting.objects.filter(type="ietf").order_by("date"):
|
|||
for schedule in meeting.schedule_set.all():
|
||||
print " Checking for missing Break and Reg sessions in %s" % schedule
|
||||
for timeslot in meeting.timeslot_set.all():
|
||||
if timeslot.type_id == 'break':
|
||||
assignment, created = ScheduleTimeslotSSessionAssignment.objects.get_or_create(timeslot=timeslot, session=brk, schedule=schedule)
|
||||
if timeslot.type_id == 'break' and not (schedule.base and SchedTimeSessAssignment.objects.filter(timeslot=timeslot, session=brk, schedule=schedule.base).exists()):
|
||||
assignment, created = SchedTimeSessAssignment.objects.get_or_create(timeslot=timeslot, session=brk, schedule=schedule)
|
||||
if created:
|
||||
print " Added %s break assignment" % timeslot
|
||||
if timeslot.type_id == 'reg':
|
||||
assignment, created = ScheduleTimeslotSSessionAssignment.objects.get_or_create(timeslot=timeslot, session=reg, schedule=schedule)
|
||||
if timeslot.type_id == 'reg' and not (schedule.base and SchedTimeSessAssignment.objects.filter(timeslot=timeslot, session=reg, schedule=schedule.base).exists()):
|
||||
assignment, created = SchedTimeSessAssignment.objects.get_or_create(timeslot=timeslot, session=reg, schedule=schedule)
|
||||
if created:
|
||||
print " Added %s registration assignment" % timeslot
|
||||
|
|
|
@ -126,8 +126,8 @@ admin.site.register(SchedulingEvent, SchedulingEventAdmin)
|
|||
|
||||
class ScheduleAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "meeting", "owner", "visible", "public", "badness"]
|
||||
list_filter = ["meeting", ]
|
||||
raw_id_fields = ["meeting", "owner", ]
|
||||
list_filter = ["meeting"]
|
||||
raw_id_fields = ["meeting", "owner", "origin", "base"]
|
||||
search_fields = ["meeting__number", "name", "owner__name"]
|
||||
ordering = ["-meeting", "name"]
|
||||
|
||||
|
|
|
@ -143,13 +143,6 @@ def get_schedule(meeting, name=None):
|
|||
schedule = get_object_or_404(meeting.schedule_set, name=name)
|
||||
return schedule
|
||||
|
||||
def get_schedule_by_id(meeting, schedid):
|
||||
if schedid is None:
|
||||
schedule = meeting.schedule
|
||||
else:
|
||||
schedule = get_object_or_404(meeting.schedule_set, id=int(schedid))
|
||||
return schedule
|
||||
|
||||
# seems this belongs in ietf/person/utils.py?
|
||||
def get_person_by_email(email):
|
||||
# email == None may actually match people who haven't set an email!
|
||||
|
@ -428,6 +421,11 @@ def get_announcement_initial(meeting, is_change=False):
|
|||
type = group.type.slug.upper()
|
||||
if group.type.slug == 'wg' and group.state.slug == 'bof':
|
||||
type = 'BOF'
|
||||
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]
|
||||
).order_by('timeslot__time')
|
||||
|
||||
initial['subject'] = '{name} ({acronym}) {type} {desc} Meeting: {date}{change}'.format(
|
||||
name=group.name,
|
||||
acronym=group.acronym,
|
||||
|
|
|
@ -85,8 +85,11 @@ class Command(BaseCommand):
|
|||
date=datetime.date(2019, 11, 16),
|
||||
days=7,
|
||||
)
|
||||
schedule = Schedule.objects.create(meeting=m, name='Empty-Schedule', owner_id=1,
|
||||
visible=True, public=True)
|
||||
base_schedule = Schedule.objects.create(meeting=m, name='base', owner_id=1,
|
||||
visible=True, public=True)
|
||||
|
||||
schedule = Schedule.objects.create(meeting=m, name='first1', owner_id=1,
|
||||
visible=True, public=True, base=base_schedule)
|
||||
m.schedule = schedule
|
||||
m.save()
|
||||
|
||||
|
|
25
ietf/meeting/migrations/0032_add_schedule_base.py
Normal file
25
ietf/meeting/migrations/0032_add_schedule_base.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 2.0.13 on 2020-08-07 09:30
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import ietf.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0031_add_session_origin'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='base',
|
||||
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'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='origin',
|
||||
field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='meeting.Schedule'),
|
||||
),
|
||||
]
|
|
@ -291,7 +291,9 @@ class Meeting(models.Model):
|
|||
min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
|
||||
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
assignments_updated = (self.schedule.assignments.aggregate(Max('modified'))["modified__max"] or min_time) if self.schedule else min_time
|
||||
assignments_updated = min_time
|
||||
if self.schedule:
|
||||
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
|
||||
ts = max(timeslots_updated, sessions_updated, assignments_updated)
|
||||
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
|
||||
ts = tz.localize(ts)
|
||||
|
@ -450,7 +452,7 @@ class TimeSlot(models.Model):
|
|||
@property
|
||||
def session(self):
|
||||
if not hasattr(self, "_session_cache"):
|
||||
self._session_cache = self.sessions.filter(timeslotassignments__schedule=self.meeting.schedule).first()
|
||||
self._session_cache = self.sessions.filter(timeslotassignments__schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting else None]).first()
|
||||
return self._session_cache
|
||||
|
||||
@property
|
||||
|
@ -626,7 +628,10 @@ class Schedule(models.Model):
|
|||
public = models.BooleanField(default=True, help_text="Allow others to see this agenda.")
|
||||
badness = models.IntegerField(null=True, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
origin = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL)
|
||||
origin = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL, related_name="+")
|
||||
base = ForeignKey('Schedule', blank=True, null=True, on_delete=models.SET_NULL,
|
||||
help_text="Sessions scheduled in the base schedule show up in this schedule too.", related_name="derivedschedule_set",
|
||||
limit_choices_to={'base': None}) # prevent the inheritance from being more than one layer deep (no recursion)
|
||||
|
||||
def __str__(self):
|
||||
return u"%s:%s(%s)" % (self.meeting, self.name, self.owner)
|
||||
|
@ -1047,7 +1052,7 @@ class Session(models.Model):
|
|||
ss0name = "(%s)" % SessionStatusName.objects.get(slug=status_id).name
|
||||
else:
|
||||
ss0name = "(unscheduled)"
|
||||
ss = self.timeslotassignments.filter(schedule=self.meeting.schedule).order_by('timeslot__time')
|
||||
ss = self.timeslotassignments.filter(schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting.schedule else None]).order_by('timeslot__time')
|
||||
if ss:
|
||||
ss0name = ','.join(x.timeslot.time.strftime("%a-%H%M") for x in ss)
|
||||
return "%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
|
||||
|
@ -1080,11 +1085,8 @@ class Session(models.Model):
|
|||
def reverse_constraints(self):
|
||||
return Constraint.objects.filter(target=self.group, meeting=self.meeting).order_by('name__name')
|
||||
|
||||
def timeslotassignment_for_schedule(self, schedule):
|
||||
return self.timeslotassignments.filter(schedule=schedule).first()
|
||||
|
||||
def official_timeslotassignment(self):
|
||||
return self.timeslotassignment_for_schedule(self.meeting.schedule)
|
||||
return self.timeslotassignments.filter(schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting.schedule else None]).first()
|
||||
|
||||
def constraints_dict(self, host_scheme):
|
||||
constraint_list = []
|
||||
|
|
|
@ -76,8 +76,9 @@ def make_meeting_test_data(meeting=None):
|
|||
|
||||
if not meeting:
|
||||
meeting = Meeting.objects.get(number="72", type="ietf")
|
||||
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-schedule", visible=True, public=True)
|
||||
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-schedule", visible=True, public=True)
|
||||
base_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="base", visible=True, public=True)
|
||||
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-schedule", visible=True, public=True, base=base_schedule)
|
||||
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-schedule", visible=True, public=True, base=base_schedule)
|
||||
|
||||
# test room
|
||||
pname = RoomResourceName.objects.create(name='projector',slug='proj')
|
||||
|
@ -146,7 +147,7 @@ def make_meeting_test_data(meeting=None):
|
|||
requested_duration=datetime.timedelta(minutes=480),
|
||||
type_id="reg")
|
||||
SchedulingEvent.objects.create(session=reg_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=reg_slot, session=reg_session, schedule=schedule)
|
||||
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"),
|
||||
|
@ -154,7 +155,7 @@ def make_meeting_test_data(meeting=None):
|
|||
requested_duration=datetime.timedelta(minutes=30),
|
||||
type_id="break")
|
||||
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=schedule)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=base_schedule)
|
||||
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
|
|
|
@ -133,6 +133,8 @@ class EditMeetingScheduleTests(IetfLiveServerTestCase):
|
|||
url = self.absreverse('ietf.meeting.views.edit_meeting_schedule', kwargs=dict(num=meeting.number, name=schedule.name, owner=schedule.owner_email()))
|
||||
self.driver.get(url)
|
||||
|
||||
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)
|
||||
|
||||
# select - show session info
|
||||
|
|
|
@ -127,6 +127,8 @@ class MeetingTests(TestCase):
|
|||
future_meeting = Meeting.objects.create(date=datetime.date(future_year, 7, 22), number=future_num, type_id='ietf',
|
||||
city="Panama City", country="PA", time_zone='America/Panama')
|
||||
|
||||
registration_text = "Registration"
|
||||
|
||||
# utc
|
||||
time_interval = "%s-%s" % (slot.utc_start_time().strftime("%H:%M").lstrip("0"), (slot.utc_start_time() + slot.duration).strftime("%H:%M").lstrip("0"))
|
||||
|
||||
|
@ -152,6 +154,7 @@ class MeetingTests(TestCase):
|
|||
self.assertIn(session.group.parent.acronym.upper(), agenda_content)
|
||||
self.assertIn(slot.location.name, agenda_content)
|
||||
self.assertIn(time_interval, agenda_content)
|
||||
self.assertIn(registration_text, agenda_content)
|
||||
|
||||
# Make sure there's a frame for the agenda and it points to the right place
|
||||
self.assertTrue(any([session.materials.get(type='agenda').get_href() in x.attrib["data-src"] for x in q('tr div.modal-body div.frame')]))
|
||||
|
@ -191,6 +194,7 @@ class MeetingTests(TestCase):
|
|||
self.assertContains(r, session.group.name)
|
||||
self.assertContains(r, session.group.parent.acronym.upper())
|
||||
self.assertContains(r, slot.location.name)
|
||||
self.assertContains(r, registration_text)
|
||||
|
||||
self.assertContains(r, session.materials.get(type='agenda').uploaded_filename)
|
||||
self.assertContains(r, session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().uploaded_filename)
|
||||
|
@ -215,6 +219,7 @@ class MeetingTests(TestCase):
|
|||
self.assertNotContains(r, 'CANCELLED')
|
||||
self.assertContains(r, session.group.acronym)
|
||||
self.assertContains(r, slot.location.name)
|
||||
self.assertContains(r, registration_text)
|
||||
|
||||
# week view with a cancelled session
|
||||
SchedulingEvent.objects.create(
|
||||
|
@ -585,16 +590,22 @@ class MeetingTests(TestCase):
|
|||
self.client.login(username='secretary',password='secretary+password')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
new_base = Schedule.objects.create(name="newbase", owner=schedule.owner, meeting=schedule.meeting)
|
||||
response = self.client.post(url, {
|
||||
'name':schedule.name,
|
||||
'visible':True,
|
||||
'public':True,
|
||||
'notes': "New Notes",
|
||||
'base': new_base.pk,
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code,302)
|
||||
schedule = Schedule.objects.get(pk=schedule.pk)
|
||||
self.assertNoFormPostErrors(response)
|
||||
schedule.refresh_from_db()
|
||||
self.assertTrue(schedule.visible)
|
||||
self.assertTrue(schedule.public)
|
||||
self.assertEqual(schedule.notes, "New Notes")
|
||||
self.assertEqual(schedule.base_id, new_base.pk)
|
||||
|
||||
def test_agenda_by_type_ics(self):
|
||||
session=SessionFactory(meeting__type_id='ietf',type_id='lead')
|
||||
|
@ -985,7 +996,21 @@ class EditTests(TestCase):
|
|||
person=p,
|
||||
name=ConstraintName.objects.get(slug="bethere"),
|
||||
)
|
||||
|
||||
|
||||
room = Room.objects.get(meeting=meeting, session_types='regular')
|
||||
base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=50),
|
||||
time=datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30)))
|
||||
|
||||
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')
|
||||
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)
|
||||
|
||||
|
||||
# check we have the grid and everything set up as a baseline -
|
||||
# the Javascript tests check that the Javascript can work with
|
||||
# it
|
||||
|
@ -993,11 +1018,9 @@ class EditTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
room = Room.objects.get(meeting=meeting, session_types='regular')
|
||||
self.assertTrue(q(".room-name:contains(\"{}\")".format(room.name)))
|
||||
self.assertTrue(q(".room-name:contains(\"{}\")".format(room.capacity)))
|
||||
|
||||
timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular'))
|
||||
self.assertTrue(q("#timeslot{}".format(timeslots[0].pk)))
|
||||
|
||||
for s in [s1, s2]:
|
||||
|
@ -1031,12 +1054,14 @@ class EditTests(TestCase):
|
|||
if s.comments:
|
||||
self.assertIn(s.comments, e.find(".comments").text())
|
||||
|
||||
formatted_constraints = e.find(".session-info .formatted-constraints > *")
|
||||
if s == s1:
|
||||
self.assertIn(s_other.group.acronym, formatted_constraints.eq(0).html())
|
||||
self.assertIn(p.name, formatted_constraints.eq(1).html())
|
||||
elif s == s2:
|
||||
self.assertIn(p.name, formatted_constraints.eq(0).html())
|
||||
formatted_constraints1 = q("#session{} .session-info .formatted-constraints > *".format(s1.pk))
|
||||
self.assertIn(s2.group.acronym, formatted_constraints1.eq(0).html())
|
||||
self.assertIn(p.name, formatted_constraints1.eq(1).html())
|
||||
|
||||
formatted_constraints2 = q("#session{} .session-info .formatted-constraints > *".format(s2.pk))
|
||||
self.assertIn(p.name, formatted_constraints2.eq(0).html())
|
||||
|
||||
self.assertEqual(len(q("#session{}.readonly".format(base_session.pk))), 1)
|
||||
|
||||
self.assertTrue(q("em:contains(\"You can't edit this schedule\")"))
|
||||
|
||||
|
@ -1106,7 +1131,7 @@ class EditTests(TestCase):
|
|||
self.assertEqual(tombstone_event.status_id, 'resched')
|
||||
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.get(schedule=schedule, session=s_tombstone).timeslot, timeslots[1])
|
||||
self.assertTrue(PyQuery(json_content['tombstone'])("#session{}.tombstone".format(s_tombstone.pk)).html())
|
||||
self.assertTrue(PyQuery(json_content['tombstone'])("#session{}.readonly".format(s_tombstone.pk)).html())
|
||||
|
||||
# unassign
|
||||
r = self.client.post(url, {
|
||||
|
@ -1117,16 +1142,9 @@ class EditTests(TestCase):
|
|||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1)), [])
|
||||
|
||||
# try swapping days
|
||||
timeslots.append(TimeSlot.objects.create(
|
||||
meeting=meeting, type_id='regular', location=timeslots[0].location,
|
||||
duration=timeslots[0].duration - datetime.timedelta(minutes=5),
|
||||
time=timeslots[0].time + datetime.timedelta(days=1),
|
||||
))
|
||||
|
||||
SchedTimeSessAssignment.objects.create(schedule=schedule, session=s1, timeslot=timeslots[1])
|
||||
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2, timeslot=timeslots[0])), 1)
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[1])), 1)
|
||||
SchedTimeSessAssignment.objects.create(schedule=schedule, session=s1, timeslot=timeslots[0])
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[0])), 1)
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2, timeslot=timeslots[1])), 1)
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[2])), [])
|
||||
|
||||
r = self.client.post(url, {
|
||||
|
@ -1138,8 +1156,8 @@ class EditTests(TestCase):
|
|||
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[0])), [])
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[1])), [])
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1)), [])
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2, timeslot=timeslots[2])), 1)
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[2])), 1)
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2)), [])
|
||||
|
||||
# swap back
|
||||
r = self.client.post(url, {
|
||||
|
@ -1149,37 +1167,59 @@ class EditTests(TestCase):
|
|||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2, timeslot=timeslots[0])), 1)
|
||||
self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[0])), 1)
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[1])), [])
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[2])), [])
|
||||
|
||||
def test_copy_meeting_schedule(self):
|
||||
def test_new_meeting_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
url = urlreverse("ietf.meeting.views.copy_meeting_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name))
|
||||
# new from scratch
|
||||
url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# copy
|
||||
r = self.client.post(url, {
|
||||
'name': "newtest",
|
||||
'name': "scratch",
|
||||
'public': "on",
|
||||
'notes': "New test",
|
||||
'visible': "on",
|
||||
'notes': "New scratch",
|
||||
'base': meeting.schedule.base_id,
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
|
||||
new_schedule = Schedule.objects.get(meeting=meeting, owner__user__username='secretary', name='newtest')
|
||||
new_schedule = Schedule.objects.get(meeting=meeting, owner__user__username='secretary', name='scratch')
|
||||
self.assertEqual(new_schedule.public, True)
|
||||
self.assertEqual(new_schedule.visible, True)
|
||||
self.assertEqual(new_schedule.notes, "New scratch")
|
||||
self.assertEqual(new_schedule.origin, None)
|
||||
self.assertEqual(new_schedule.base_id, meeting.schedule.base_id)
|
||||
|
||||
# copy
|
||||
url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, {
|
||||
'name': "copy",
|
||||
'public': "on",
|
||||
'notes': "New copy",
|
||||
'base': meeting.schedule.base_id,
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
|
||||
new_schedule = Schedule.objects.get(meeting=meeting, owner__user__username='secretary', name='copy')
|
||||
self.assertEqual(new_schedule.public, True)
|
||||
self.assertEqual(new_schedule.visible, False)
|
||||
self.assertEqual(new_schedule.notes, "New test")
|
||||
self.assertEqual(new_schedule.notes, "New copy")
|
||||
self.assertEqual(new_schedule.origin, meeting.schedule)
|
||||
self.assertEqual(new_schedule.base_id, meeting.schedule.base_id)
|
||||
|
||||
old_assignments = {(a.session_id, a.timeslot_id) for a in SchedTimeSessAssignment.objects.filter(schedule=meeting.schedule)}
|
||||
for a in SchedTimeSessAssignment.objects.filter(schedule=new_schedule):
|
||||
self.assertIn((a.session_id, a.timeslot_id), old_assignments)
|
||||
# FIXME: test extendedfrom is copied correctly
|
||||
|
||||
def test_save_agenda_as_and_read_permissions(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
@ -1390,7 +1430,7 @@ class SessionDetailsTests(TestCase):
|
|||
class EditScheduleListTests(TestCase):
|
||||
def setUp(self):
|
||||
self.mtg = MeetingFactory(type_id='ietf')
|
||||
ScheduleFactory(meeting=self.mtg,name='Empty-Schedule')
|
||||
ScheduleFactory(meeting=self.mtg, name='secretary1')
|
||||
|
||||
def test_list_schedules(self):
|
||||
url = urlreverse('ietf.meeting.views.list_schedules',kwargs={'num':self.mtg.number})
|
||||
|
@ -1423,8 +1463,8 @@ class EditScheduleListTests(TestCase):
|
|||
)
|
||||
|
||||
# copy
|
||||
copy_url = urlreverse("ietf.meeting.views.copy_meeting_schedule", kwargs=dict(num=meeting.number, owner=from_schedule.owner_email(), name=from_schedule.name))
|
||||
r = self.client.post(copy_url, {
|
||||
new_url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number, owner=from_schedule.owner_email(), name=from_schedule.name))
|
||||
r = self.client.post(new_url, {
|
||||
'name': "newtest",
|
||||
'public': "on",
|
||||
})
|
||||
|
@ -1436,22 +1476,20 @@ class EditScheduleListTests(TestCase):
|
|||
|
||||
edit_url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number, owner=to_schedule.owner_email(), name=to_schedule.name))
|
||||
|
||||
# schedule
|
||||
# schedule session
|
||||
r = self.client.post(edit_url, {
|
||||
'action': 'assign',
|
||||
'timeslot': slot3.pk,
|
||||
'session': session3.pk,
|
||||
})
|
||||
self.assertEqual(json.loads(r.content)['success'], True)
|
||||
|
||||
# unschedule
|
||||
# unschedule session
|
||||
r = self.client.post(edit_url, {
|
||||
'action': 'unassign',
|
||||
'session': session1.pk,
|
||||
})
|
||||
self.assertEqual(json.loads(r.content)['success'], True)
|
||||
|
||||
# move
|
||||
# move session
|
||||
r = self.client.post(edit_url, {
|
||||
'action': 'assign',
|
||||
'timeslot': slot2.pk,
|
||||
|
@ -1459,7 +1497,7 @@ class EditScheduleListTests(TestCase):
|
|||
})
|
||||
self.assertEqual(json.loads(r.content)['success'], True)
|
||||
|
||||
# get differences
|
||||
# now get differences
|
||||
r = self.client.get(url, {
|
||||
'from_schedule': from_schedule.name,
|
||||
'to_schedule': to_schedule.name,
|
||||
|
@ -1584,6 +1622,7 @@ class InterimTests(TestCase):
|
|||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
meeting.time_zone = 'America/Los_Angeles'
|
||||
meeting.save()
|
||||
|
||||
url = urlreverse("ietf.meeting.views.interim_send_announcement", kwargs={'number': meeting.number})
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.get(url)
|
||||
|
|
|
@ -41,7 +41,7 @@ type_ietf_only_patterns = [
|
|||
url(r'^agenda/%(owner)s/%(schedule_name)s/session/(?P<assignment_id>\d+).json$' % settings.URL_REGEXPS, ajax.assignment_json),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/sessions.json$' % settings.URL_REGEXPS, ajax.assignments_json),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s.json$' % settings.URL_REGEXPS, ajax.schedule_infourl),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/copy/$' % settings.URL_REGEXPS, views.copy_meeting_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/new/$' % settings.URL_REGEXPS, views.new_meeting_schedule),
|
||||
url(r'^agenda/by-room$', views.agenda_by_room),
|
||||
url(r'^agenda/by-type$', views.agenda_by_type),
|
||||
url(r'^agenda/by-type/(?P<type>[a-z]+)$', views.agenda_by_type),
|
||||
|
@ -49,6 +49,7 @@ type_ietf_only_patterns = [
|
|||
url(r'^agendas/list$', views.list_schedules),
|
||||
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_schedules', permanent=True)),
|
||||
url(r'^agendas/diff/$', views.diff_schedules),
|
||||
url(r'^agenda/new/$', views.new_meeting_schedule),
|
||||
url(r'^timeslots/edit$', views.edit_timeslots),
|
||||
url(r'^timeslot/(?P<slot_id>\d+)/edittype$', views.edit_timeslot_type),
|
||||
url(r'^rooms$', ajax.timeslot_roomsurl),
|
||||
|
|
|
@ -28,7 +28,7 @@ from ietf.person.models import Person, Email
|
|||
from ietf.secr.proceedings.proc_utils import import_audio_files
|
||||
|
||||
def session_time_for_sorting(session, use_meeting_date):
|
||||
official_timeslot = TimeSlot.objects.filter(sessionassignments__session=session, sessionassignments__schedule=session.meeting.schedule).first()
|
||||
official_timeslot = TimeSlot.objects.filter(sessionassignments__session=session, sessionassignments__schedule__in=[session.meeting.schedule, session.meeting.schedule.base if session.meeting.schedule else None]).first()
|
||||
if official_timeslot:
|
||||
return official_timeslot.time
|
||||
elif use_meeting_date and session.meeting.date:
|
||||
|
|
|
@ -144,7 +144,7 @@ def materials(request, num=None):
|
|||
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(
|
||||
meeting__number=meeting.number,
|
||||
timeslotassignments__schedule=schedule
|
||||
timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).distinct().select_related('meeting__schedule', 'group__state', 'group__parent'))
|
||||
|
||||
plenaries = sessions.filter(name__icontains='plenary')
|
||||
|
@ -297,6 +297,8 @@ def schedule_create(request, num=None, owner=None, name=None):
|
|||
newschedule = Schedule(name=savedname,
|
||||
owner=request.user.person,
|
||||
meeting=meeting,
|
||||
base=schedule.base,
|
||||
origin=schedule,
|
||||
visible=False,
|
||||
public=False)
|
||||
|
||||
|
@ -354,14 +356,15 @@ def edit_timeslots(request, num=None):
|
|||
"ts_list":ts_list,
|
||||
})
|
||||
|
||||
class CopyScheduleForm(forms.ModelForm):
|
||||
class NewScheduleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Schedule
|
||||
fields = ['name', 'visible', 'public', 'notes']
|
||||
fields = ['name', 'visible', 'public', 'notes', 'base']
|
||||
|
||||
def __init__(self, schedule, new_owner, *args, **kwargs):
|
||||
super(CopyScheduleForm, self).__init__(*args, **kwargs)
|
||||
def __init__(self, meeting, schedule, new_owner, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.meeting = meeting
|
||||
self.schedule = schedule
|
||||
self.new_owner = new_owner
|
||||
|
||||
|
@ -370,7 +373,7 @@ class CopyScheduleForm(forms.ModelForm):
|
|||
name_suggestion = username
|
||||
counter = 2
|
||||
|
||||
existing_names = set(Schedule.objects.filter(meeting=schedule.meeting_id, owner=new_owner).values_list('name', flat=True))
|
||||
existing_names = set(Schedule.objects.filter(meeting=meeting, owner=new_owner).values_list('name', flat=True))
|
||||
while name_suggestion in existing_names:
|
||||
name_suggestion = username + str(counter)
|
||||
counter += 1
|
||||
|
@ -378,55 +381,68 @@ class CopyScheduleForm(forms.ModelForm):
|
|||
self.fields['name'].initial = name_suggestion
|
||||
self.fields['name'].label = "Name of new agenda"
|
||||
|
||||
self.fields['base'].queryset = self.fields['base'].queryset.filter(meeting=meeting)
|
||||
|
||||
if schedule:
|
||||
self.fields['visible'].initial = schedule.visible
|
||||
self.fields['public'].initial = schedule.public
|
||||
self.fields['base'].queryset = self.fields['base'].queryset.exclude(pk=schedule.pk)
|
||||
self.fields['base'].initial = schedule.base_id
|
||||
else:
|
||||
base = Schedule.objects.filter(meeting=meeting, name='base').first()
|
||||
if base:
|
||||
self.fields['base'].initial = base.pk
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data.get('name')
|
||||
if name and Schedule.objects.filter(meeting=self.schedule.meeting_id, owner=self.new_owner, name=name):
|
||||
if name and Schedule.objects.filter(meeting=self.meeting, owner=self.new_owner, name=name):
|
||||
raise forms.ValidationError("Schedule with this name already exists.")
|
||||
return name
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def copy_meeting_schedule(request, num, owner, name):
|
||||
def new_meeting_schedule(request, num, owner=None, name=None):
|
||||
meeting = get_meeting(num)
|
||||
schedule = get_object_or_404(meeting.schedule_set, owner__email__address=owner, name=name)
|
||||
schedule = get_schedule_by_name(meeting, get_person_by_email(owner), name)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = CopyScheduleForm(schedule, request.user.person, request.POST)
|
||||
form = NewScheduleForm(meeting, schedule, request.user.person, request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
new_schedule = form.save(commit=False)
|
||||
new_schedule.meeting = schedule.meeting
|
||||
new_schedule.meeting = meeting
|
||||
new_schedule.owner = request.user.person
|
||||
new_schedule.origin = schedule
|
||||
new_schedule.save()
|
||||
|
||||
# keep a mapping so that extendedfrom references can be chased
|
||||
old_pk_to_new_pk = {}
|
||||
extendedfroms = {}
|
||||
for assignment in schedule.assignments.all():
|
||||
extendedfrom_id = assignment.extendedfrom_id
|
||||
if schedule:
|
||||
# keep a mapping so that extendedfrom references can be chased
|
||||
old_pk_to_new_pk = {}
|
||||
extendedfroms = {}
|
||||
for assignment in schedule.assignments.all():
|
||||
extendedfrom_id = assignment.extendedfrom_id
|
||||
|
||||
# clone by resetting primary key
|
||||
old_pk = assignment.pk
|
||||
assignment.pk = None
|
||||
assignment.schedule = new_schedule
|
||||
assignment.extendedfrom = None
|
||||
assignment.save()
|
||||
# clone by resetting primary key
|
||||
old_pk = assignment.pk
|
||||
assignment.pk = None
|
||||
assignment.schedule = new_schedule
|
||||
assignment.extendedfrom = None
|
||||
assignment.save()
|
||||
|
||||
old_pk_to_new_pk[old_pk] = assignment.pk
|
||||
if extendedfrom_id is not None:
|
||||
extendedfroms[assignment.pk] = extendedfrom_id
|
||||
old_pk_to_new_pk[old_pk] = assignment.pk
|
||||
if extendedfrom_id is not None:
|
||||
extendedfroms[assignment.pk] = extendedfrom_id
|
||||
|
||||
for pk, extendedfrom_id in extendedfroms.values():
|
||||
if extendedfrom_id in old_pk_to_new_pk:
|
||||
SchedTimeSessAssignment.objects.filter(pk=pk).update(extendedfrom=old_pk_to_new_pk[extendedfrom_id])
|
||||
for pk, extendedfrom_id in extendedfroms.values():
|
||||
if extendedfrom_id in old_pk_to_new_pk:
|
||||
SchedTimeSessAssignment.objects.filter(pk=pk).update(extendedfrom=old_pk_to_new_pk[extendedfrom_id])
|
||||
|
||||
# now redirect to this new schedule
|
||||
return redirect(edit_meeting_schedule, meeting.number, new_schedule.owner_email(), new_schedule.name)
|
||||
|
||||
else:
|
||||
form = CopyScheduleForm(schedule, request.user.person)
|
||||
form = NewScheduleForm(meeting, schedule, request.user.person)
|
||||
|
||||
return render(request, "meeting/copy_meeting_schedule.html", {
|
||||
return render(request, "meeting/new_meeting_schedule.html", {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
'form': form,
|
||||
|
@ -462,7 +478,15 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
"hide_menu": True
|
||||
}, status=403, content_type="text/html")
|
||||
|
||||
assignments = get_all_assignments_from_schedule(schedule)
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base],
|
||||
timeslot__location__isnull=False,
|
||||
session__type='regular',
|
||||
).order_by('timeslot__time','timeslot__name')
|
||||
|
||||
assignments_by_session = defaultdict(list)
|
||||
for a in assignments:
|
||||
assignments_by_session[a.session_id].append(a)
|
||||
|
||||
rooms = meeting.room_set.filter(session_types__slug='regular').distinct().order_by("capacity")
|
||||
|
||||
|
@ -483,7 +507,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
'resources', 'group', 'group__parent', 'group__type', 'joint_with_groups',
|
||||
)
|
||||
|
||||
timeslots_qs = meeting.timeslot_set.filter(type='regular').prefetch_related('type', 'sessions').order_by('location', 'time', 'name')
|
||||
timeslots_qs = TimeSlot.objects.filter(meeting=meeting, type='regular').prefetch_related('type').order_by('location', 'time', 'name')
|
||||
|
||||
min_duration = min(t.duration for t in timeslots_qs)
|
||||
max_duration = max(t.duration for t in timeslots_qs)
|
||||
|
@ -549,7 +573,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
|
||||
s.other_sessions = [s_other for s_other in sessions_for_group.get(s.group_id) if s != s_other]
|
||||
|
||||
s.is_tombstone = s.current_status in tombstone_states
|
||||
s.readonly = s.current_status in tombstone_states or any(a.schedule_id != schedule.pk for a in assignments_by_session.get(s.pk, []))
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
|
@ -622,7 +646,9 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
source_day = swap_days_form.cleaned_data['source_day']
|
||||
target_day = swap_days_form.cleaned_data['target_day']
|
||||
|
||||
swap_meeting_schedule_timeslot_assignments(schedule, [ts for ts in timeslots_qs if ts.time.date() == source_day], [ts for ts in timeslots_qs if ts.time.date() == target_day], target_day - source_day)
|
||||
source_timeslots = [ts for ts in timeslots_qs if ts.time.date() == source_day]
|
||||
target_timeslots = [ts for ts in timeslots_qs if ts.time.date() == target_day]
|
||||
swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, target_timeslots, target_day - source_day)
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
|
@ -670,10 +696,6 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
ts.session_assignments = []
|
||||
timeslots_by_pk = {ts.pk: ts for ts in timeslots_qs}
|
||||
|
||||
assignments_by_session = defaultdict(list)
|
||||
for a in assignments:
|
||||
assignments_by_session[a.session_id].append(a)
|
||||
|
||||
unassigned_sessions = []
|
||||
for s in sessions:
|
||||
assigned = False
|
||||
|
@ -798,7 +820,17 @@ def edit_schedule(request, num=None, owner=None, name=None):
|
|||
})
|
||||
|
||||
|
||||
SchedulePropertiesForm = modelform_factory(Schedule, fields=['name', 'notes', 'visible', 'public'])
|
||||
class SchedulePropertiesForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Schedule
|
||||
fields = ['name', 'notes', 'visible', 'public', 'base']
|
||||
|
||||
def __init__(self, meeting, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['base'].queryset = self.fields['base'].queryset.filter(meeting=meeting)
|
||||
if self.instance.pk is not None:
|
||||
self.fields['base'].queryset = self.fields['base'].queryset.exclude(pk=self.instance.pk)
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def edit_schedule_properties(request, num, owner, name):
|
||||
|
@ -816,14 +848,14 @@ def edit_schedule_properties(request, num, owner, name):
|
|||
return HttpResponseForbidden("You may not edit this schedule")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SchedulePropertiesForm(instance=schedule, data=request.POST)
|
||||
form = SchedulePropertiesForm(meeting, instance=schedule, data=request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if request.GET.get('next'):
|
||||
return HttpResponseRedirect(request.GET.get('next'))
|
||||
return redirect('ietf.meeting.views.edit_schedule', num=num, owner=owner, name=name)
|
||||
else:
|
||||
form = SchedulePropertiesForm(instance=schedule)
|
||||
form = SchedulePropertiesForm(meeting, instance=schedule)
|
||||
|
||||
return render(request, "meeting/properties_edit.html", {
|
||||
"schedule": schedule,
|
||||
|
@ -842,7 +874,7 @@ def list_schedules(request, num):
|
|||
|
||||
schedules = Schedule.objects.filter(
|
||||
meeting=meeting
|
||||
).prefetch_related('owner', 'assignments', 'origin', 'origin__assignments').order_by('owner', '-name', '-public').distinct()
|
||||
).prefetch_related('owner', 'assignments', 'origin', 'origin__assignments', 'base').order_by('owner', '-name', '-public').distinct()
|
||||
if not has_role(request.user, 'Secretariat'):
|
||||
schedules = schedules.filter(Q(visible=True) | Q(owner=request.user.person))
|
||||
|
||||
|
@ -859,7 +891,7 @@ def list_schedules(request, num):
|
|||
if s.origin:
|
||||
s.changes_from_origin = len(diff_meeting_schedules(s.origin, s))
|
||||
|
||||
if s.pk == meeting.schedule_id:
|
||||
if s in [meeting.schedule, meeting.schedule.base if meeting.schedule else None]:
|
||||
official_schedules.append(s)
|
||||
elif user_is_person(request.user, s.owner):
|
||||
own_schedules.append(s)
|
||||
|
@ -869,13 +901,13 @@ def list_schedules(request, num):
|
|||
other_private_schedules.append(s)
|
||||
|
||||
schedule_groups = [
|
||||
("Official Agenda", official_schedules),
|
||||
("Own Draft Agendas", own_schedules),
|
||||
("Other Draft Agendas", other_public_schedules),
|
||||
("Other Private Draft Agendas", other_private_schedules),
|
||||
(official_schedules, False, "Official Agenda"),
|
||||
(own_schedules, True, "Own Draft Agendas"),
|
||||
(other_public_schedules, False, "Other Draft Agendas"),
|
||||
(other_private_schedules, False, "Other Private Draft Agendas"),
|
||||
]
|
||||
|
||||
schedule_groups = [(label, sorted(l, reverse=True, key=lambda s: natural_sort_key(s.name))) for label, l in schedule_groups if l]
|
||||
schedule_groups = [(sorted(l, reverse=True, key=lambda s: natural_sort_key(s.name)), own, *t) for l, own, *t in schedule_groups if l or own]
|
||||
|
||||
return render(request, "meeting/schedule_list.html", {
|
||||
'meeting': meeting,
|
||||
|
@ -967,7 +999,11 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
return render(request, "meeting/no-"+base+ext, {'meeting':meeting }, content_type=mimetype[ext])
|
||||
|
||||
updated = meeting.updated()
|
||||
filtered_assignments = schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
filtered_assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base]
|
||||
).exclude(
|
||||
timeslot__type__in=['lead', 'offagenda']
|
||||
)
|
||||
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
|
||||
|
||||
if ext == ".csv":
|
||||
|
@ -1095,10 +1131,15 @@ def agenda_by_room(request, num=None, name=None, owner=None):
|
|||
else:
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).prefetch_related('timeslot', 'timeslot__location', 'session', 'session__group', 'session__group__parent')
|
||||
|
||||
ss_by_day = OrderedDict()
|
||||
for day in schedule.assignments.dates('timeslot__time','day'):
|
||||
for day in assignments.dates('timeslot__time','day'):
|
||||
ss_by_day[day]=[]
|
||||
for ss in schedule.assignments.order_by('timeslot__location__functional_name','timeslot__location__name','timeslot__time'):
|
||||
for ss in assignments.order_by('timeslot__location__functional_name','timeslot__location__name','timeslot__time'):
|
||||
day = ss.timeslot.time.date()
|
||||
ss_by_day[day].append(ss)
|
||||
return render(request,"meeting/agenda_by_room.html",{"meeting":meeting,"schedule":schedule,"ss_by_day":ss_by_day})
|
||||
|
@ -1111,7 +1152,12 @@ def agenda_by_type(request, num=None, type=None, name=None, owner=None):
|
|||
else:
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
assignments = schedule.assignments.order_by('session__type__slug','timeslot__time','session__group__acronym')
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).prefetch_related(
|
||||
'timeslot', 'timeslot__location', 'session', 'session__group', 'session__group__parent'
|
||||
).order_by('session__type__slug','timeslot__time','session__group__acronym')
|
||||
|
||||
if type:
|
||||
assignments = assignments.filter(session__type__slug=type)
|
||||
return render(request,"meeting/agenda_by_type.html",{"meeting":meeting,"schedule":schedule,"assignments":assignments})
|
||||
|
@ -1120,7 +1166,11 @@ def agenda_by_type(request, num=None, type=None, name=None, owner=None):
|
|||
def agenda_by_type_ics(request,num=None,type=None):
|
||||
meeting = get_meeting(num)
|
||||
schedule = get_schedule(meeting)
|
||||
assignments = schedule.assignments.order_by('session__type__slug','timeslot__time')
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).prefetch_related(
|
||||
'timeslot', 'timeslot__location', 'session', 'session__group', 'session__group__parent'
|
||||
).order_by('session__type__slug','timeslot__time')
|
||||
if type:
|
||||
assignments = assignments.filter(session__type__slug=type)
|
||||
updated = meeting.updated()
|
||||
|
@ -1240,7 +1290,11 @@ def week_view(request, num=None, name=None, owner=None):
|
|||
if not schedule:
|
||||
raise Http404
|
||||
|
||||
filtered_assignments = schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
filtered_assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base]
|
||||
).exclude(
|
||||
timeslot__type__in=['lead','offagenda']
|
||||
)
|
||||
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
|
||||
|
||||
items = []
|
||||
|
@ -1309,7 +1363,11 @@ def room_view(request, num=None, name=None, owner=None):
|
|||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
|
||||
assignments = schedule.assignments.all()
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).prefetch_related(
|
||||
'timeslot', 'timeslot__location', 'session', 'session__group', 'session__group__parent'
|
||||
)
|
||||
unavailable = meeting.timeslot_set.filter(type__slug='unavail')
|
||||
if not (assignments.exists() or unavailable.exists()):
|
||||
return HttpResponse("No sessions/timeslots available yet")
|
||||
|
@ -1401,7 +1459,7 @@ def ical_agenda(request, num=None, name=None, acronym=None, session_id=None):
|
|||
elif len(item) > 1 and item[0] == '~':
|
||||
include_types |= set([item[1:]])
|
||||
|
||||
assignments = schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
assignments = SchedTimeSessAssignment.objects.filter(schedule__in=[schedule, schedule.base]).exclude(timeslot__type__in=['lead','offagenda'])
|
||||
assignments = preprocess_assignments_for_agenda(assignments, meeting)
|
||||
|
||||
if q:
|
||||
|
@ -1427,13 +1485,17 @@ def ical_agenda(request, num=None, name=None, acronym=None, session_id=None):
|
|||
}, content_type="text/calendar")
|
||||
|
||||
@cache_page(15 * 60)
|
||||
def json_agenda(request, num=None ):
|
||||
def json_agenda(request, num=None):
|
||||
meeting = get_meeting(num, type_in=['ietf','interim'])
|
||||
|
||||
sessions = []
|
||||
locations = set()
|
||||
parent_acronyms = set()
|
||||
assignments = meeting.schedule.assignments.exclude(session__type__in=['lead','offagenda','break','reg'])
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]
|
||||
).exclude(
|
||||
session__type__in=['lead','offagenda','break','reg']
|
||||
)
|
||||
# Update the assignments with historic information, i.e., valid at the
|
||||
# time of the meeting
|
||||
assignments = preprocess_assignments_for_agenda(assignments, meeting, extra_prefetches=[
|
||||
|
@ -1609,7 +1671,7 @@ def session_details(request, num, acronym):
|
|||
session.historic_group.historic_parent = None
|
||||
|
||||
session.type_counter = Counter()
|
||||
ss = session.timeslotassignments.filter(schedule=meeting.schedule).order_by('timeslot__time')
|
||||
ss = session.timeslotassignments.filter(schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]).order_by('timeslot__time')
|
||||
if ss:
|
||||
if meeting.type_id == 'interim' and not (meeting.city or meeting.country):
|
||||
session.times = [ x.timeslot.utc_start_time() for x in ss ]
|
||||
|
@ -2320,15 +2382,23 @@ def make_schedule_official(request, num, owner, name):
|
|||
schedule.public = True
|
||||
schedule.visible = True
|
||||
schedule.save()
|
||||
if schedule.base and not (schedule.base.public and schedule.base.visible):
|
||||
schedule.base.public = True
|
||||
schedule.base.visible = True
|
||||
schedule.base.save()
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_schedules',kwargs={'num':num}))
|
||||
|
||||
if not schedule.public:
|
||||
messages.warning(request,"This schedule will be made public as it is made official.")
|
||||
|
||||
if not schedule.visible:
|
||||
messages.warning(request,"This schedule will be made visible as it is made official.")
|
||||
if schedule.base:
|
||||
if not schedule.base.public:
|
||||
messages.warning(request,"The base schedule will be made public as it is made official.")
|
||||
if not schedule.base.visible:
|
||||
messages.warning(request,"The base schedule will be made visible as it is made official.")
|
||||
|
||||
return render(request, "meeting/make_schedule_official.html",
|
||||
{ 'schedule' : schedule,
|
||||
|
@ -2344,12 +2414,14 @@ def delete_schedule(request, num, owner, name):
|
|||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
|
||||
if schedule.name=='Empty-Schedule':
|
||||
return HttpResponseForbidden('You may not delete the default empty schedule')
|
||||
|
||||
# FIXME: we ought to put these checks in a function and only show
|
||||
# the delete button if the checks pass
|
||||
if schedule == meeting.schedule:
|
||||
return HttpResponseForbidden('You may not delete the official schedule for %s'%meeting)
|
||||
|
||||
if Schedule.objects.filter(base=schedule).exists():
|
||||
return HttpResponseForbidden('You may not delete a schedule serving as the base for other schedules')
|
||||
|
||||
if not ( has_role(request.user, 'Secretariat') or person.user == request.user ):
|
||||
return HttpResponseForbidden("You may not delete other user's schedules")
|
||||
|
||||
|
@ -2660,10 +2732,12 @@ def interim_request_details(request, number):
|
|||
return redirect(interim_pending)
|
||||
|
||||
first_session = sessions.first()
|
||||
assignments = SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
|
||||
|
||||
return render(request, "meeting/interim_request_details.html", {
|
||||
"meeting": meeting,
|
||||
"sessions": sessions,
|
||||
"assignments": assignments,
|
||||
"group": first_session.group,
|
||||
"requester": session_requested_by(first_session),
|
||||
"session_status": current_session_status(first_session),
|
||||
|
@ -2783,10 +2857,10 @@ def upcoming_ical(request):
|
|||
today = datetime.date.today()
|
||||
|
||||
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
|
||||
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).order_by('date'))
|
||||
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).prefetch_related('schedule').order_by('date'))
|
||||
|
||||
assignments = list(SchedTimeSessAssignment.objects.filter(
|
||||
schedule__meeting__schedule=F('schedule'),
|
||||
schedule__in=[m.schedule_id for m in meetings] + [m.schedule.base_id for m in meetings if m.schedule],
|
||||
session__in=[s.pk for m in meetings for s in m.sessions],
|
||||
timeslot__time__gte=today,
|
||||
).order_by(
|
||||
|
@ -2879,7 +2953,7 @@ def proceedings(request, num=None):
|
|||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting__number=meeting.number)
|
||||
).filter(
|
||||
Q(timeslotassignments__schedule=schedule) | Q(current_status='notmeet')
|
||||
Q(timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None]) | Q(current_status='notmeet')
|
||||
).select_related().order_by('-current_status')
|
||||
plenaries = sessions.filter(name__icontains='plenary').exclude(current_status='notmeet')
|
||||
ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu')
|
||||
|
@ -3101,7 +3175,7 @@ def edit_timeslot_type(request, num, slot_id):
|
|||
else:
|
||||
form = TimeSlotTypeForm(instance=timeslot)
|
||||
|
||||
sessions = timeslot.sessions.filter(timeslotassignments__schedule=meeting.schedule)
|
||||
sessions = timeslot.sessions.filter(timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
|
||||
|
||||
return render(request, 'meeting/edit_timeslot_type.html', {'timeslot':timeslot,'form':form,'sessions':sessions})
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual(len(q('#id_schedule_selector option')),3)
|
||||
self.assertEqual({option.get('value') for option in q('#id_schedule_selector option:not([value=""])')}, {'base', 'test-schedule', 'test-unofficial-schedule'})
|
||||
|
||||
def test_add_meeting(self):
|
||||
"Add Meeting"
|
||||
|
@ -92,6 +92,9 @@ class SecrMeetingTestCase(TestCase):
|
|||
new_meeting = Meeting.objects.get(number=number)
|
||||
|
||||
self.assertTrue(new_meeting.schedule)
|
||||
self.assertEqual(new_meeting.schedule.name, 'secretary1')
|
||||
self.assertTrue(new_meeting.schedule.base)
|
||||
self.assertEqual(new_meeting.schedule.base.name, 'base')
|
||||
self.assertEqual(new_meeting.attendees, None)
|
||||
|
||||
def test_edit_meeting(self):
|
||||
|
@ -197,8 +200,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
# test delete
|
||||
# first unschedule sessions so we can delete
|
||||
SchedTimeSessAssignment.objects.filter(schedule=meeting.schedule).delete()
|
||||
SchedTimeSessAssignment.objects.filter(schedule=meeting.unofficial_schedule).delete()
|
||||
SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.schedule, meeting.schedule.base, meeting.unofficial_schedule]).delete()
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
post_dict = {
|
||||
'room-TOTAL_FORMS': q('input[name="room-TOTAL_FORMS"]').val(),
|
||||
|
@ -339,27 +341,29 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
def test_meetings_misc_session_delete(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'slot_id':slot.id})
|
||||
target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
|
||||
schedule = meeting.schedule.base
|
||||
slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
|
||||
target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, target)
|
||||
self.assertFalse(meeting.schedule.assignments.filter(timeslot=slot))
|
||||
self.assertFalse(schedule.assignments.filter(timeslot=slot))
|
||||
|
||||
def test_meetings_misc_session_cancel(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'slot_id':slot.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
|
||||
schedule = meeting.schedule.base
|
||||
slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, redirect_url)
|
||||
session = slot.sessionassignments.filter(schedule=meeting.schedule).first().session
|
||||
session = slot.sessionassignments.filter(schedule=schedule).first().session
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'canceled')
|
||||
|
||||
def test_meetings_regular_session_edit(self):
|
||||
|
|
|
@ -22,7 +22,6 @@ from ietf.meeting.utils import add_event_info_to_session_qs
|
|||
from ietf.meeting.utils import only_sessions_that_can_meet
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.group.models import Group, GroupEvent
|
||||
from ietf.person.models import Person
|
||||
from ietf.secr.meetings.blue_sheets import create_blue_sheets
|
||||
from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm, MeetingSelectForm,
|
||||
MeetingRoomForm, MiscSessionForm, TimeSlotForm, RegularSessionEditForm,
|
||||
|
@ -85,21 +84,19 @@ def check_misc_sessions(meeting,schedule):
|
|||
Ensure misc session timeslots exist and have appropriate SchedTimeSessAssignment objects
|
||||
for the specified schedule.
|
||||
'''
|
||||
# FIXME: this is a legacy function: delete it once base schedules are rolled out
|
||||
|
||||
if Schedule.objects.filter(meeting=meeting, base__isnull=False).exists():
|
||||
return
|
||||
|
||||
slots = TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other','plenary','lead','offagenda'))
|
||||
plenary = slots.filter(type='plenary').first()
|
||||
if plenary:
|
||||
assignments = plenary.sessionassignments.all()
|
||||
if not assignments.filter(schedule=schedule):
|
||||
source = assignments.first().schedule
|
||||
copy_assignments(slots,source,schedule)
|
||||
|
||||
def copy_assignments(slots,source,target):
|
||||
'''
|
||||
Copy SchedTimeSessAssignment objects from source schedule to target schedule. Slots is
|
||||
a queryset of slots
|
||||
'''
|
||||
for ss in SchedTimeSessAssignment.objects.filter(schedule=source,timeslot__in=slots):
|
||||
SchedTimeSessAssignment.objects.create(schedule=target,session=ss.session,timeslot=ss.timeslot)
|
||||
for ss in SchedTimeSessAssignment.objects.filter(schedule=source,timeslot__in=slots):
|
||||
SchedTimeSessAssignment.objects.create(schedule=schedule,session=ss.session,timeslot=ss.timeslot)
|
||||
|
||||
def get_last_meeting(meeting):
|
||||
last_number = int(meeting.number) - 1
|
||||
|
@ -221,13 +218,23 @@ def add(request):
|
|||
if form.is_valid():
|
||||
meeting = form.save()
|
||||
|
||||
base_schedule = Schedule.objects.create(
|
||||
meeting=meeting,
|
||||
name='base',
|
||||
owner=request.user.person,
|
||||
visible=True,
|
||||
public=True
|
||||
)
|
||||
|
||||
schedule = Schedule.objects.create(meeting = meeting,
|
||||
name = 'Empty-Schedule',
|
||||
owner = Person.objects.get(name='(System)'),
|
||||
name = "{}1".format(request.user.username),
|
||||
owner = request.user.person,
|
||||
visible = True,
|
||||
public = True)
|
||||
public = True,
|
||||
base = base_schedule,
|
||||
)
|
||||
meeting.schedule = schedule
|
||||
|
||||
|
||||
# we want to carry session request lock status over from previous meeting
|
||||
previous_meeting = get_meeting( int(meeting.number) - 1 )
|
||||
meeting.session_request_lock_message = previous_meeting.session_request_lock_message
|
||||
|
@ -296,7 +303,7 @@ def blue_sheet_generate(request, meeting_id):
|
|||
# TODO: Why aren't 'ag' in here as well?
|
||||
groups = Group.objects.filter(
|
||||
type__in=['wg','rg'],
|
||||
session__timeslotassignments__schedule=meeting.schedule).order_by('acronym')
|
||||
session__timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]).order_by('acronym')
|
||||
create_blue_sheets(meeting, groups)
|
||||
|
||||
messages.success(request, 'Blue Sheets generated')
|
||||
|
@ -380,7 +387,7 @@ def misc_sessions(request, meeting_id, schedule_name):
|
|||
check_misc_sessions(meeting,schedule)
|
||||
|
||||
misc_session_types = ['break','reg','other','plenary','lead']
|
||||
assignments = schedule.assignments.filter(timeslot__type__in=misc_session_types)
|
||||
assignments = SchedTimeSessAssignment.objects.filter(schedule__in=[schedule, schedule.base], timeslot__type__in=misc_session_types)
|
||||
assignments = assignments.order_by('-timeslot__type__name','timeslot__time')
|
||||
|
||||
if request.method == 'POST':
|
||||
|
@ -571,7 +578,7 @@ def notifications(request, meeting_id):
|
|||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
last_notice = GroupEvent.objects.filter(type='sent_notification').first()
|
||||
groups = set()
|
||||
for ss in meeting.schedule.assignments.filter(timeslot__type='regular'):
|
||||
for ss in SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None], timeslot__type='regular'):
|
||||
last_notice = ss.session.group.latest_event(type='sent_notification')
|
||||
if last_notice and ss.modified > last_notice.time:
|
||||
groups.add(ss.session.group)
|
||||
|
@ -652,7 +659,7 @@ def regular_sessions(request, meeting_id, schedule_name):
|
|||
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
||||
|
||||
sessions = add_event_info_to_session_qs(
|
||||
only_sessions_that_can_meet(schedule.meeting.session_set)
|
||||
only_sessions_that_can_meet(meeting.session_set)
|
||||
).order_by('group__acronym')
|
||||
|
||||
if request.method == 'POST':
|
||||
|
|
|
@ -32,11 +32,10 @@ VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})
|
|||
def _get_session(number,name,date,time):
|
||||
'''Lookup session using data from video title'''
|
||||
meeting = Meeting.objects.get(number=number)
|
||||
schedule = meeting.schedule
|
||||
timeslot_time = datetime.datetime.strptime(date + time,'%Y%m%d%H%M')
|
||||
try:
|
||||
assignment = SchedTimeSessAssignment.objects.get(
|
||||
schedule = schedule,
|
||||
schedule__in = [meeting.schedule, meeting.schedule.base],
|
||||
session__group__acronym = name.lower(),
|
||||
timeslot__time = timeslot_time,
|
||||
)
|
||||
|
@ -108,7 +107,7 @@ def get_timeslot_for_filename(filename):
|
|||
meeting=meeting,
|
||||
location__name=room_mapping[match.groupdict()['room']],
|
||||
time=time,
|
||||
sessionassignments__schedule=meeting.schedule,
|
||||
sessionassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None],
|
||||
).distinct()
|
||||
uncancelled_slots = [t for t in slots if not add_event_info_to_session_qs(t.sessions.all()).filter(current_status='canceled').exists()]
|
||||
return uncancelled_slots[0]
|
||||
|
|
|
@ -232,9 +232,12 @@ def recording(request, meeting_num):
|
|||
session.
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
assignments = meeting.schedule.assignments.exclude(session__type__in=('reg','break')).order_by('session__group__acronym')
|
||||
sessions = [ x.session for x in assignments ]
|
||||
|
||||
sessions = Session.objects.filter(
|
||||
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None]
|
||||
).exclude(
|
||||
type__in=['reg','break']
|
||||
).order_by('group__acronym')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = RecordingForm(request.POST,meeting=meeting)
|
||||
if form.is_valid():
|
||||
|
|
|
@ -461,6 +461,11 @@ input.draft-file-input {
|
|||
Meeting Tool
|
||||
========================================================================== */
|
||||
|
||||
#misc-sessions .from-base-schedule {
|
||||
text-align: centeR;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#misc-session-edit-form input[type="text"] {
|
||||
width: 30em;
|
||||
}
|
||||
|
|
|
@ -33,13 +33,17 @@
|
|||
<td>{{ assignment.timeslot.location }}</td>
|
||||
<td>{{ assignment.timeslot.show_location }}</td>
|
||||
<td>{{ assignment.timeslot.type }}</td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_edit" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Edit</a></td>
|
||||
<td>
|
||||
{% if not assignment.session.type.slug == "break" %}
|
||||
<a href="{% url "ietf.secr.meetings.views.misc_session_cancel" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Cancel</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_delete" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Delete</a></td>
|
||||
{% if assignment.schedule_id == schedule.pk %}
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_edit" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Edit</a></td>
|
||||
<td>
|
||||
{% if assignment.session.type.slug != "break" %}
|
||||
<a href="{% url "ietf.secr.meetings.views.misc_session_cancel" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Cancel</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_delete" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Delete</a></td>
|
||||
{% else %}
|
||||
<td colspan="3" class="from-base-schedule">(from base schedule)</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -52,7 +52,7 @@ def get_session(timeslot, schedule=None):
|
|||
# todo, doesn't account for shared timeslot
|
||||
if not schedule:
|
||||
schedule = timeslot.meeting.schedule
|
||||
qs = timeslot.sessions.filter(timeslotassignments__schedule=schedule) #.exclude(states__slug='deleted')
|
||||
qs = timeslot.sessions.filter(timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None]) #.exclude(states__slug='deleted')
|
||||
if qs:
|
||||
return qs[0]
|
||||
else:
|
||||
|
@ -66,7 +66,7 @@ def get_timeslot(session, schedule=None):
|
|||
'''
|
||||
if not schedule:
|
||||
schedule = session.meeting.schedule
|
||||
ss = session.timeslotassignments.filter(schedule=schedule)
|
||||
ss = session.timeslotassignments.filter(schedule__in=[schedule, schedule.base if schedule else None])
|
||||
if ss:
|
||||
return ss[0].timeslot
|
||||
else:
|
||||
|
|
|
@ -1010,6 +1010,9 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
|
|||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
.from-base-schedule {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* === Edit Meeting Schedule ====================================== */
|
||||
|
||||
|
@ -1138,7 +1141,7 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
.edit-meeting-schedule .session.tombstone {
|
||||
.edit-meeting-schedule .session.readonly {
|
||||
cursor: default;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ jQuery(document).ready(function () {
|
|||
alert("Error: " + errorText);
|
||||
}
|
||||
|
||||
let sessions = content.find(".session").not(".tombstone");
|
||||
let sessions = content.find(".session").not(".readonly");
|
||||
let timeslots = content.find(".timeslot");
|
||||
let days = content.find(".day-flow .day");
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ ul.sessionlist { list-style:none; padding-left:2em; margin-bottom:10px;}
|
|||
<li class="roomlistentry"><h3>{{room.grouper|default:"Location Unavailable"}}</h3>
|
||||
<ul class="sessionlist">
|
||||
{% for ss in room.list %}
|
||||
<li class="sessionlistentry type-{{ss.timeslot.type.slug}}">{{ss.timeslot.time|date:"H:i"}}-{{ss.timeslot.end_time|date:"H:i"}} {{ss.session.short_name}}</li>
|
||||
<li class="sessionlistentry type-{{ss.timeslot.type_id}} {% if ss.schedule_id != meeting.schedule_id %}from-base-schedule{% endif %}">{{ss.timeslot.time|date:"H:i"}}-{{ss.timeslot.end_time|date:"H:i"}} {{ss.session.short_name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
@ -29,7 +29,7 @@ li.daylistentry { margin-left:2em; font-weight: 400; }
|
|||
{% block content %}
|
||||
{% include "meeting/meeting_heading.html" with updated=meeting.updated selected="by-type" title_extra="by Session Type" %}
|
||||
|
||||
{% regroup assignments by session.type.slug as type_list %}
|
||||
{% regroup assignments by session.type_id as type_list %}
|
||||
<ul class="typelist">
|
||||
{% for type in type_list %}
|
||||
<li class="typelistentry {% cycle 'even' 'odd' %}">
|
||||
|
@ -41,11 +41,11 @@ li.daylistentry { margin-left:2em; font-weight: 400; }
|
|||
<h3>{{ day.grouper }}</h3>
|
||||
<table class="sessiontable">
|
||||
{% for ss in day.list %}
|
||||
<tr>
|
||||
<tr {% if ss.schedule_id != meeting.schedule_id %}class="from-base-schedule"{% endif %}>
|
||||
<td>{{ss.timeslot.time|date:"H:i"}}-{{ss.timeslot.end_time|date:"H:i"}}</td>
|
||||
<td>{{ss.timeslot.get_hidden_location}}</td>
|
||||
<td class="type-{{ss.session.type.slug}}">{{ss.session.short_name}} </td>
|
||||
<td>{% if ss.session.type_id == 'regular' or ss.session.type_id == 'plenary' or ss.session.type_id == 'other' %} <a href="{% url 'ietf.meeting.views.session_details' num=ss.session.meeting.number acronym=ss.session.group.acronym %}">Materials</a>{% else %} {% endif %}</td>
|
||||
<td class="type-{{ss.session.type_id}}">{{ss.session.short_name}} </td>
|
||||
<td>{% if ss.session.type_id == 'regular' or ss.session.type_id == 'plenary' or ss.session.type_id == 'other' %} <a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=ss.session.group.acronym %}">Materials</a>{% else %} {% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
·
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url "ietf.meeting.views.copy_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">Copy agenda</a>
|
||||
<a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">New agenda</a>
|
||||
·
|
||||
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
|
||||
|
@ -46,7 +46,7 @@
|
|||
{% if not can_edit %}
|
||||
·
|
||||
|
||||
<strong><em>You can't edit this schedule. Take a copy first.</em></strong>
|
||||
<strong><em>You can't edit this schedule. Make a <a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">new agenda from this</a>.</em></strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div id="session{{ session.pk }}" class="session {% if not session.group.parent.scheduling_color %}untoggleable{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} {% if session.is_tombstone %}tombstone{% endif %}" style="width:{{ session.layout_width }}em;" data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}data-attendees="{{ session.attendees }}"{% endif %}>
|
||||
<div id="session{{ session.pk }}" class="session {% if not session.group.parent.scheduling_color %}untoggleable{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} {% if session.readonly %}readonly{% endif %}" style="width:{{ session.layout_width }}em;" data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}data-attendees="{{ session.attendees }}"{% endif %}>
|
||||
<div class="session-label {% if session.group and session.group.is_bof %}bof-session{% endif %}">
|
||||
{{ session.scheduling_label }}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
|
||||
|
||||
{% endif %}The {{ group.name }} ({{ group.acronym }}) {% if group.type.slug == 'wg' and group.state.slug == 'bof' %}BOF{% else %}{{group.type.name}}{% endif %} will hold
|
||||
{% if meeting.session_set.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ meeting.schedule.assignments.first.timeslot.time | date:"H:i" }} to {{ meeting.schedule.assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ meeting.schedule.assignments.first.timeslot.utc_start_time | date:"H:i" }} to {{ meeting.schedule.assignments.first.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}.
|
||||
{% if meeting.session_set.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.utc_start_time | date:"H:i" }} to {{ assignments.first.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}.
|
||||
{% else %}a multi-day {% if not meeting.city %}virtual {% endif %}interim meeting.
|
||||
|
||||
{% for assignment in meeting.schedule.assignments.all %}Session {{ forloop.counter }}:
|
||||
{% for assignment in assignments %}Session {{ forloop.counter }}:
|
||||
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.utc_start_time | date:"H:i" }} to {{ assignment.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}
|
||||
{% endfor %}{% endif %}
|
||||
{% if meeting.city %}Meeting Location:
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<dd>{{ meeting.country }}</dd>
|
||||
<dt>Timezone</dt>
|
||||
<dd>{{ meeting.time_zone }}</dd>
|
||||
{% for assignment in meeting.schedule.assignments.all %}
|
||||
{% for assignment in assignments %}
|
||||
<br>
|
||||
<dt>Date</dt>
|
||||
<dd>{{ assignment.timeslot.time|date:"Y-m-d" }}
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>{% block title %}Copy agenda {{ schedule.name }}{% endblock %}</h1>
|
||||
<h1>{% block title %}{% if schedule %}Copy agenda {{ schedule.name }} to new agenda{% else %}New agenda{% endif %}{% endblock %}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-default">Copy agenda</button>
|
||||
<button type="submit" class="btn btn-primary">Create agenda</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -8,13 +8,13 @@
|
|||
<script src="{% static 'ietf/bootstrap/js/bootstrap.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
{% autoescape off %}
|
||||
var room_names = [{% for room in rooms %}"{{room.name}}"{% if not forloop.last %},{% endif %}{% endfor %}];
|
||||
var room_functional_names = [{% for room in rooms %}"{{room.functional_name}}"{% if not forloop.last %},{% endif %}{% endfor %}];
|
||||
var room_typelabels = [{% for room in rooms %}"{% for type in room.session_types.all %}{{type.name}}{% if not forloop.last %}, {% endif %}{% endfor %}"{% if not forloop.last %},{% endif %}{% endfor %}];
|
||||
|
||||
var items = new Array();
|
||||
|
||||
{% autoescape off %}
|
||||
{% for slot in unavailable %}
|
||||
if (room_names.indexOf("{{slot.get_hidden_location}}") >= 0 )
|
||||
{
|
||||
|
@ -24,7 +24,7 @@
|
|||
{% for ss in assignments %}
|
||||
if (room_names.indexOf("{{ss.timeslot.get_hidden_location}}") >= 0 )
|
||||
{
|
||||
items.push({room_index:room_names.indexOf("{{ss.timeslot.get_hidden_location}}"),day:{{ss.day}}, delta_from_beginning:{{ss.delta_from_beginning}},time:"{{ss.timeslot.time|date:"Hi"}}-{{ss.timeslot.end_time|date:"Hi"}}", verbose_time:"{{ss.timeslot.time|date:"D M d Hi"}}-{{ss.timeslot.end_time|date:"Hi"}}",duration:{{ss.timeslot.duration.total_seconds}}, type:"{{ss.timeslot.type}}", {% if ss.session.name %}name:"{{ss.session.name|escapejs}}",{% if ss.session.group.acronym %} wg:"{{ss.session.group.acronym}}",{%endif%}{% else %}{% if ss.timeslot.type.name == "Break" %}name:"{{ss.timeslot.name|escapejs}}", area:"break", wg:"break",{% elif ss.timeslot.type.slug == "unavail" %}name:"Unavailable",{% else %}name:"{{ss.session.group.name|escapejs}}{%if ss.session.group.state.name == "BOF"%} BOF{%endif%}",wg:"{{ss.session.group.acronym}}",state:"{{ss.session.group.state}}",area:"{{ss.session.group.parent.acronym}}",{% endif %}{% endif %} dayname:"{{ ss.timeslot.time|date:"l"|upper }}, {{ ss.timeslot.time|date:"F j, Y" }}"{% if ss.session.agenda %}, agenda:"{{ss.session.agenda.get_href}}"{% endif %} });
|
||||
items.push({room_index:room_names.indexOf("{{ss.timeslot.get_hidden_location}}"),day:{{ss.day}}, delta_from_beginning:{{ss.delta_from_beginning}},time:"{{ss.timeslot.time|date:"Hi"}}-{{ss.timeslot.end_time|date:"Hi"}}", verbose_time:"{{ss.timeslot.time|date:"D M d Hi"}}-{{ss.timeslot.end_time|date:"Hi"}}",duration:{{ss.timeslot.duration.total_seconds}}, type:"{{ss.timeslot.type}}", {% if ss.session.name %}name:"{{ss.session.name|escapejs}}",{% if ss.session.group.acronym %} wg:"{{ss.session.group.acronym}}",{%endif%}{% else %}{% if ss.timeslot.type.name == "Break" %}name:"{{ss.timeslot.name|escapejs}}", area:"break", wg:"break",{% elif ss.timeslot.type_id == "unavail" %}name:"Unavailable",{% else %}name:"{{ss.session.group.name|escapejs}}{%if ss.session.group.state.name == "BOF"%} BOF{%endif%}",wg:"{{ss.session.group.acronym}}",state:"{{ss.session.group.state}}",area:"{{ss.session.group.parent.acronym}}",{% endif %}{% endif %} dayname:"{{ ss.timeslot.time|date:"l"|upper }}, {{ ss.timeslot.time|date:"F j, Y" }}"{% if ss.session.agenda %}, agenda:"{{ss.session.agenda.get_href}}"{% endif %}, from_base_schedule: {% if ss.schedule_id != meeting.schedule_id %}true{% else %}false{% endif %} });
|
||||
}
|
||||
{% endfor %}
|
||||
{% endautoescape %}
|
||||
|
@ -353,6 +353,9 @@
|
|||
e.style.padding=padding;
|
||||
e.style.fontFamily="sans-serif";
|
||||
e.style.fontSize="8pt";
|
||||
if (items[i].from_base_schedule)
|
||||
e.style.opacity = 0.5;
|
||||
|
||||
e.id=i;
|
||||
|
||||
e.onmouseover=resize_func(e,sess_top,room_left,
|
||||
|
|
|
@ -5,25 +5,34 @@
|
|||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<h1>{% block title %}Possible Meeting Agendas for IETF {{ meeting.number }}{% endblock %}</h1>
|
||||
|
||||
{% comment %}
|
||||
<div>
|
||||
<p><a href="{% url "ietf.meeting.views.edit_timeslots" meeting.number %}">Edit Timeslots</a></p>
|
||||
{% comment %}
|
||||
<div>
|
||||
<p><a href="{% url "ietf.meeting.views.edit_timeslots" meeting.number %}">Edit Timeslots</a></p>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
{% endcomment %}
|
||||
|
||||
<div>
|
||||
{% for label, schedules in schedule_groups %}
|
||||
{% for schedules, own, label in schedule_groups %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ label }}</div>
|
||||
<div class="panel-heading">
|
||||
{{ label }}
|
||||
{% if own %}
|
||||
<div class="pull-right" >
|
||||
<a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number %}"><i class="fa fa-plus"></i> New Agenda</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-striped">
|
||||
<tr>
|
||||
<th class="col-md-2">Name</th>
|
||||
<th class="col-md-2">Owner</th>
|
||||
<th class="col-md-1">Origin</th>
|
||||
<th class="col-md-4">Notes</th>
|
||||
<th class="col-md-1">Base</th>
|
||||
<th class="col-md-3">Notes</th>
|
||||
<th class="col-md-1">Visible</th>
|
||||
<th class="col-md-1">Public</th>
|
||||
</tr>
|
||||
|
@ -40,14 +49,19 @@
|
|||
<td>{{ schedule.owner }}</td>
|
||||
<td>
|
||||
{% if schedule.origin %}
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.origin.owner_email schedule.name %}">{{ schedule.origin.name }}</a>
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.origin.owner_email schedule.origin.name %}">{{ schedule.origin.name }}</a>
|
||||
<a href="{% url "ietf.meeting.views.diff_schedules" meeting.number %}?from_schedule={{ schedule.origin.name|urlencode }}&to_schedule={{ schedule.name|urlencode }}" title="{{ schedule.changes_from_origin }} change{{ schedule.changes_from_origin|pluralize }} from {{ schedule.origin.name }}">+{{ schedule.changes_from_origin }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ schedule.notes|linebreaksbr }}</td>
|
||||
<td>
|
||||
{% if schedule.visible %}
|
||||
<div class="label label-success">visible</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if schedule.base %}
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.base.owner_email schedule.base.name %}">{{ schedule.base.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ schedule.notes|linebreaksbr }}</td>
|
||||
<td>
|
||||
{% if schedule.visible %}
|
||||
<div class="label label-success">visible</div>
|
||||
{% else %}
|
||||
<div class="label label-danger">hidden</div>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue