Add new page for editing timeslots and misc session, accessible from
the possible agendas list for a meeting. The new page incorporates the editing functionality otherwise accessible from /secr/meetings/<meeting>/<schedule>/miscsessions/ /secr/meetings/<meeting>/<schedule>/regularsessions/ /meeting/<meeting>/timeslots/edit in a time vs. room grid that gives a better overview of what's going on each day and allows adding and editing time slots and sessions. Regular sessions must be scheduled in the other schedule editor, but it's possible to add regular timeslots and edit agenda notes on scheduled regular sessions. - Legacy-Id: 18379
This commit is contained in:
parent
c78ffbcd18
commit
996fc716eb
|
@ -1171,6 +1171,161 @@ class EditTests(TestCase):
|
|||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[1])), [])
|
||||
self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[2])), [])
|
||||
|
||||
def test_edit_meeting_timeslots_and_misc_sessions(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
# check we have the grid and everything set up as a baseline -
|
||||
# the Javascript tests check that the Javascript can work with
|
||||
# it
|
||||
url = urlreverse("ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions", kwargs=dict(num=meeting.number, owner=meeting.schedule.base.owner_email(), name=meeting.schedule.base.name))
|
||||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
breakfast_room = Room.objects.get(meeting=meeting, name="Breakfast Room")
|
||||
break_room = Room.objects.get(meeting=meeting, name="Break Area")
|
||||
reg_room = Room.objects.get(meeting=meeting, name="Registration Area")
|
||||
|
||||
for i in range(meeting.days):
|
||||
self.assertTrue(q("[data-day=\"{}\"]".format((meeting.date + datetime.timedelta(days=i)).isoformat())))
|
||||
|
||||
self.assertTrue(q(".room-label:contains(\"{}\")".format(breakfast_room.name)))
|
||||
self.assertTrue(q(".room-label:contains(\"{}\")".format(break_room.name)))
|
||||
self.assertTrue(q(".room-label:contains(\"{}\")".format(reg_room.name)))
|
||||
|
||||
break_slot = TimeSlot.objects.get(location=break_room, type='break')
|
||||
|
||||
room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.time.date().isoformat(), break_slot.location_id))
|
||||
self.assertTrue(room_row)
|
||||
self.assertTrue(room_row.find("#timeslot{}".format(break_slot.pk)))
|
||||
|
||||
self.assertTrue(q(".timeslot-form"))
|
||||
|
||||
# add timeslot
|
||||
ietf_group = Group.objects.get(acronym='ietf')
|
||||
|
||||
r = self.client.post(url, {
|
||||
'day': meeting.date,
|
||||
'time': '08:30',
|
||||
'duration': '1:30',
|
||||
'location': break_room.pk,
|
||||
'show_location': 'on',
|
||||
'type': 'other',
|
||||
'group': ietf_group.pk,
|
||||
'name': "IETF Testing",
|
||||
'short': "ietf-testing",
|
||||
'scroll': 1234,
|
||||
'action': 'add-timeslot',
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
self.assertIn("#scroll=1234", r['Location'])
|
||||
|
||||
test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing")
|
||||
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(8, 30)))
|
||||
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1, minutes=30))
|
||||
self.assertEqual(test_timeslot.location_id, break_room.pk)
|
||||
self.assertEqual(test_timeslot.show_location, True)
|
||||
self.assertEqual(test_timeslot.type_id, 'other')
|
||||
|
||||
test_session = Session.objects.get(meeting=meeting, timeslotassignments__timeslot=test_timeslot)
|
||||
self.assertEqual(test_session.short, 'ietf-testing')
|
||||
self.assertEqual(test_session.group, ietf_group)
|
||||
|
||||
self.assertTrue(SchedulingEvent.objects.filter(session=test_session, status='sched'))
|
||||
|
||||
# edit timeslot
|
||||
r = self.client.get(url, {
|
||||
'timeslot': test_timeslot.pk,
|
||||
'action': 'edit-timeslot',
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
edit_form_html = json.loads(r.content)['form']
|
||||
q = PyQuery(edit_form_html)
|
||||
self.assertEqual(q("[name=name]").val(), test_timeslot.name)
|
||||
self.assertEqual(q("[name=location]").val(), str(test_timeslot.location_id))
|
||||
self.assertEqual(q("[name=timeslot]").val(), str(test_timeslot.pk))
|
||||
self.assertEqual(q("[name=type]").val(), str(test_timeslot.type_id))
|
||||
self.assertEqual(q("[name=group]").val(), str(ietf_group.pk))
|
||||
|
||||
iab_group = Group.objects.get(acronym='iab')
|
||||
|
||||
r = self.client.post(url, {
|
||||
'timeslot': test_timeslot.pk,
|
||||
'day': meeting.date,
|
||||
'time': '09:30',
|
||||
'duration': '1:00',
|
||||
'location': breakfast_room.pk,
|
||||
'type': 'other',
|
||||
'group': iab_group.pk,
|
||||
'name': "IETF Testing 2",
|
||||
'short': "ietf-testing2",
|
||||
'action': 'edit-timeslot',
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
test_timeslot.refresh_from_db()
|
||||
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(9, 30)))
|
||||
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1))
|
||||
self.assertEqual(test_timeslot.location_id, breakfast_room.pk)
|
||||
self.assertEqual(test_timeslot.show_location, False)
|
||||
self.assertEqual(test_timeslot.type_id, 'other')
|
||||
|
||||
test_session.refresh_from_db()
|
||||
self.assertEqual(test_session.short, 'ietf-testing2')
|
||||
self.assertEqual(test_session.group, iab_group)
|
||||
|
||||
# cancel timeslot
|
||||
r = self.client.post(url, {
|
||||
'timeslot': test_timeslot.pk,
|
||||
'action': 'cancel-timeslot',
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
|
||||
event = SchedulingEvent.objects.filter(
|
||||
session__timeslotassignments__timeslot=test_timeslot
|
||||
).order_by('-id').first()
|
||||
self.assertEqual(event.status_id, 'canceled')
|
||||
|
||||
# delete timeslot
|
||||
test_presentation = Document.objects.create(name='slides-test', type_id='slides')
|
||||
SessionPresentation.objects.create(
|
||||
document=test_presentation,
|
||||
rev='1',
|
||||
session=test_session
|
||||
)
|
||||
|
||||
r = self.client.post(url, {
|
||||
'timeslot': test_timeslot.pk,
|
||||
'action': 'delete-timeslot',
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
|
||||
self.assertEqual(list(TimeSlot.objects.filter(pk=test_timeslot.pk)), [])
|
||||
self.assertEqual(list(Session.objects.filter(pk=test_session.pk)), [])
|
||||
self.assertEqual(test_presentation.get_state_slug(), 'deleted')
|
||||
|
||||
# set agenda note
|
||||
assignment = SchedTimeSessAssignment.objects.filter(session__group__acronym='mars', schedule=meeting.schedule).first()
|
||||
|
||||
url = urlreverse("ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name))
|
||||
|
||||
r = self.client.post(url, {
|
||||
'timeslot': assignment.timeslot_id,
|
||||
'day': assignment.timeslot.time.date().isoformat(),
|
||||
'time': assignment.timeslot.time.time().isoformat(),
|
||||
'duration': assignment.timeslot.duration,
|
||||
'location': assignment.timeslot.location_id,
|
||||
'type': assignment.timeslot.type_id,
|
||||
'name': assignment.timeslot.name,
|
||||
'agenda_note': "New Test Note",
|
||||
'action': 'edit-timeslot',
|
||||
})
|
||||
self.assertNoFormPostErrors(r)
|
||||
|
||||
assignment.session.refresh_from_db()
|
||||
self.assertEqual(assignment.session.agenda_note, "New Test Note")
|
||||
|
||||
|
||||
def test_new_meeting_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ safe_for_all_meeting_types = [
|
|||
type_ietf_only_patterns = [
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit$' % settings.URL_REGEXPS, views.edit_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit/$' % settings.URL_REGEXPS, views.edit_meeting_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/timeslots/$' % settings.URL_REGEXPS, views.edit_meeting_timeslots_and_misc_sessions),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/details$' % settings.URL_REGEXPS, views.edit_schedule_properties),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/delete$' % settings.URL_REGEXPS, views.delete_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/make_official$' % settings.URL_REGEXPS, views.make_schedule_official),
|
||||
|
|
|
@ -54,7 +54,8 @@ from ietf.person.models import Person
|
|||
from ietf.ietfauth.utils import role_required, has_role, user_is_person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPresentation, TimeSlot, SlideSubmission
|
||||
from ietf.meeting.models import SessionStatusName, SchedulingEvent, SchedTimeSessAssignment
|
||||
from ietf.meeting.models import SessionStatusName, SchedulingEvent, SchedTimeSessAssignment, Room, TimeSlotTypeName
|
||||
from ietf.meeting.forms import CustomDurationField
|
||||
from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.helpers import build_all_agenda_slices, get_wg_name_list
|
||||
from ietf.meeting.helpers import get_all_assignments_from_schedule
|
||||
|
@ -470,13 +471,12 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
if request.method == 'POST':
|
||||
return HttpResponseForbidden("Can't view this schedule")
|
||||
|
||||
# FIXME: check this
|
||||
return render(request, "meeting/private_schedule.html",
|
||||
{"schedule":schedule,
|
||||
"meeting": meeting,
|
||||
"meeting_base_url": request.build_absolute_uri(meeting.base_url()),
|
||||
"hide_menu": True
|
||||
}, status=403, content_type="text/html")
|
||||
return render(request, "meeting/private_schedule.html", {
|
||||
"schedule":schedule,
|
||||
"meeting": meeting,
|
||||
"meeting_base_url": request.build_absolute_uri(meeting.base_url()),
|
||||
"hide_menu": True
|
||||
}, status=403, content_type="text/html")
|
||||
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base],
|
||||
|
@ -495,7 +495,6 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting=meeting,
|
||||
# Restrict graphical scheduling to regular meeting requests (Sessions) for now
|
||||
type='regular',
|
||||
).order_by('pk'),
|
||||
requested_time=True,
|
||||
|
@ -746,6 +745,328 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
})
|
||||
|
||||
|
||||
class RoomNameModelChoiceField(forms.ModelChoiceField):
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
class TimeSlotForm(forms.Form):
|
||||
day = forms.TypedChoiceField(coerce=lambda t: datetime.datetime.strptime(t, "%Y-%m-%d").date())
|
||||
time = forms.TimeField()
|
||||
duration = CustomDurationField() # this is just to make 1:30 turn into 1.5 hours instead of 1.5 minutes
|
||||
location = RoomNameModelChoiceField(queryset=Room.objects.all(), required=False, empty_label="(No location)")
|
||||
show_location = forms.BooleanField(initial=True, required=False)
|
||||
type = forms.ModelChoiceField(queryset=TimeSlotTypeName.objects.filter(used=True), empty_label=None, required=False)
|
||||
name = forms.CharField(help_text='Name that appears on the agenda', required=False)
|
||||
short = forms.CharField(max_length=32,label='Short name', help_text='Abbreviated session name used for material file names', required=False)
|
||||
group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=['ietf', 'team'], state='active'),
|
||||
help_text='''Select a group to associate with this session.<br>For example: Tutorials = Education, Code Sprint = Tools Team''',
|
||||
required=False)
|
||||
agenda_note = forms.CharField(required=False)
|
||||
|
||||
def __init__(self, meeting, schedule, *args, timeslot=None, **kwargs):
|
||||
super().__init__(*args,**kwargs)
|
||||
|
||||
self.fields["time"].widget.attrs["placeholder"] = "HH:MM"
|
||||
self.fields["duration"].widget.attrs["placeholder"] = "HH:MM"
|
||||
self.fields["duration"].initial = ""
|
||||
|
||||
self.fields["day"].choices = [
|
||||
((meeting.date + datetime.timedelta(days=i)).isoformat(), (meeting.date + datetime.timedelta(days=i)).strftime("%a %b %d"))
|
||||
for i in range(meeting.days)
|
||||
]
|
||||
|
||||
self.fields['location'].queryset = self.fields['location'].queryset.filter(meeting=meeting)
|
||||
|
||||
self.fields['group'].widget.attrs['data-ietf'] = Group.objects.get(acronym='ietf').pk
|
||||
|
||||
self.active_assignment = None
|
||||
|
||||
if timeslot:
|
||||
self.initial = {
|
||||
'day': timeslot.time.date(),
|
||||
'time': timeslot.time.time(),
|
||||
'duration': timeslot.duration,
|
||||
'location': timeslot.location_id,
|
||||
'show_location': timeslot.show_location,
|
||||
'type': timeslot.type_id,
|
||||
'name': timeslot.name,
|
||||
}
|
||||
|
||||
assignments = sorted(SchedTimeSessAssignment.objects.filter(
|
||||
timeslot=timeslot,
|
||||
schedule__in=[schedule, schedule.base if schedule else None]
|
||||
).select_related('session', 'session__group'), key=lambda a: 0 if a.schedule_id == schedule.pk else 1)
|
||||
|
||||
if assignments:
|
||||
self.active_assignment = assignments[0]
|
||||
|
||||
self.initial['short'] = self.active_assignment.session.short
|
||||
self.initial['group'] = self.active_assignment.session.group_id
|
||||
|
||||
if not self.active_assignment or timeslot.type_id != 'regular':
|
||||
del self.fields['agenda_note'] # at the moment, the UI only shows this field for regular sessions
|
||||
|
||||
self.timeslot = timeslot
|
||||
|
||||
def clean(self):
|
||||
group = self.cleaned_data.get('group')
|
||||
ts_type = self.cleaned_data.get('type')
|
||||
short = self.cleaned_data.get('short')
|
||||
|
||||
if ts_type:
|
||||
if ts_type.slug in ['break', 'reg', 'reserved', 'unavail', 'regular']:
|
||||
if ts_type.slug != 'regular':
|
||||
self.cleaned_data['group'] = self.fields['group'].queryset.get(acronym='secretariat')
|
||||
else:
|
||||
if not group:
|
||||
self.add_error('group', 'When scheduling this type of time slot, a group must be associated')
|
||||
if not short:
|
||||
self.add_error('short', 'When scheduling this type of time slot, a short name is required')
|
||||
|
||||
if self.timeslot and self.timeslot.type_id == 'regular' and self.active_assignment and ts_type.pk != self.timeslot.type_id:
|
||||
self.add_error('type', "Can't change type on time slots for regular sessions when a session has been assigned")
|
||||
|
||||
if self.active_assignment and self.active_assignment.session.group != self.cleaned_data.get('group') and self.active_assignment.session.materials.exists() and self.timeslot.type_id != 'regular':
|
||||
self.add_error('group', "Can't change group after materials have been uploaded")
|
||||
|
||||
|
||||
@role_required('Area Director', 'Secretariat')
|
||||
def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name=None):
|
||||
meeting = get_meeting(num)
|
||||
if name is None:
|
||||
schedule = meeting.schedule
|
||||
else:
|
||||
schedule = get_schedule_by_name(meeting, get_person_by_email(owner), name)
|
||||
|
||||
if schedule is None:
|
||||
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
|
||||
|
||||
rooms = list(Room.objects.filter(meeting=meeting).prefetch_related('session_types').order_by('-capacity', 'name'))
|
||||
rooms.append(Room(name="(No location)"))
|
||||
|
||||
timeslot_qs = TimeSlot.objects.filter(meeting=meeting).prefetch_related('type').order_by('time')
|
||||
|
||||
can_edit = has_role(request.user, 'Secretariat')
|
||||
|
||||
if request.method == 'GET' and request.GET.get('action') == "edit-timeslot":
|
||||
timeslot_pk = request.GET.get('timeslot')
|
||||
if not timeslot_pk or not timeslot_pk.isdecimal():
|
||||
raise Http404
|
||||
timeslot = get_object_or_404(timeslot_qs, pk=timeslot_pk)
|
||||
|
||||
assigned_session = add_event_info_to_session_qs(Session.objects.filter(
|
||||
timeslotassignments__schedule__in=[schedule, schedule.base],
|
||||
timeslotassignments__timeslot=timeslot,
|
||||
)).first()
|
||||
|
||||
timeslot.can_cancel = not assigned_session or assigned_session.current_status not in ['canceled', 'canceled', 'resched']
|
||||
|
||||
return JsonResponse({
|
||||
'form': render_to_string("meeting/edit_timeslot_form.html", {
|
||||
'timeslot_form_action': 'edit',
|
||||
'timeslot_form': TimeSlotForm(meeting, schedule, timeslot=timeslot),
|
||||
'timeslot': timeslot,
|
||||
'schedule': schedule,
|
||||
'meeting': meeting,
|
||||
'can_edit': can_edit,
|
||||
}, request=request)
|
||||
})
|
||||
|
||||
scroll = request.POST.get('scroll')
|
||||
|
||||
def redirect_with_scroll():
|
||||
url = request.get_full_path()
|
||||
if scroll and scroll.isdecimal():
|
||||
url += "#scroll={}".format(scroll)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
add_timeslot_form = None
|
||||
if request.method == 'POST' and request.POST.get('action') == 'add-timeslot' and can_edit:
|
||||
add_timeslot_form = TimeSlotForm(meeting, schedule, request.POST)
|
||||
if add_timeslot_form.is_valid():
|
||||
c = add_timeslot_form.cleaned_data
|
||||
|
||||
timeslot, created = TimeSlot.objects.get_or_create(
|
||||
meeting=meeting,
|
||||
type=c['type'],
|
||||
name=c['name'],
|
||||
time=datetime.datetime.combine(c['day'], c['time']),
|
||||
duration=c['duration'],
|
||||
location=c['location'],
|
||||
show_location=c['show_location'],
|
||||
)
|
||||
|
||||
if timeslot.type_id != 'regular':
|
||||
if not created:
|
||||
Session.objects.filter(timeslotassignments__timeslot=timeslot).delete()
|
||||
|
||||
session = Session.objects.create(
|
||||
meeting=meeting,
|
||||
name=c['name'],
|
||||
short=c['short'],
|
||||
group=c['group'],
|
||||
type=c['type'],
|
||||
agenda_note=c.get('agenda_note') or "",
|
||||
)
|
||||
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='sched'),
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
SchedTimeSessAssignment.objects.create(
|
||||
timeslot=timeslot,
|
||||
session=session,
|
||||
schedule=schedule
|
||||
)
|
||||
|
||||
return redirect_with_scroll()
|
||||
|
||||
edit_timeslot_form = None
|
||||
if request.method == 'POST' and request.POST.get('action') == 'edit-timeslot' and can_edit:
|
||||
timeslot_pk = request.POST.get('timeslot')
|
||||
if not timeslot_pk or not timeslot_pk.isdecimal():
|
||||
raise Http404
|
||||
|
||||
timeslot = get_object_or_404(TimeSlot, pk=timeslot_pk)
|
||||
|
||||
edit_timeslot_form = TimeSlotForm(meeting, schedule, request.POST, timeslot=timeslot)
|
||||
if edit_timeslot_form.is_valid() and edit_timeslot_form.active_assignment.schedule_id == schedule.pk:
|
||||
|
||||
c = edit_timeslot_form.cleaned_data
|
||||
|
||||
timeslot.type = c['type']
|
||||
timeslot.name = c['name']
|
||||
timeslot.time = datetime.datetime.combine(c['day'], c['time'])
|
||||
timeslot.duration = c['duration']
|
||||
timeslot.location = c['location']
|
||||
timeslot.show_location = c['show_location']
|
||||
timeslot.save()
|
||||
|
||||
session = Session.objects.filter(
|
||||
timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None],
|
||||
timeslotassignments__timeslot=timeslot,
|
||||
).select_related('group').first()
|
||||
|
||||
if session:
|
||||
if timeslot.type_id != 'regular':
|
||||
session.name = c['name']
|
||||
session.short = c['short']
|
||||
session.group = c['group']
|
||||
session.type = c['type']
|
||||
session.agenda_note = c.get('agenda_note') or ""
|
||||
session.save()
|
||||
|
||||
return redirect_with_scroll()
|
||||
|
||||
if request.method == 'POST' and request.POST.get('action') == 'cancel-timeslot' and can_edit:
|
||||
timeslot_pk = request.POST.get('timeslot')
|
||||
if not timeslot_pk or not timeslot_pk.isdecimal():
|
||||
raise Http404
|
||||
|
||||
timeslot = get_object_or_404(TimeSlot, pk=timeslot_pk)
|
||||
if timeslot.type_id != 'break':
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(timeslotassignments__schedule=schedule, timeslotassignments__timeslot=timeslot),
|
||||
).exclude(current_status__in=['canceled', 'resched'])
|
||||
for session in sessions:
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='canceled'),
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
return redirect_with_scroll()
|
||||
|
||||
if request.method == 'POST' and request.POST.get('action') == 'delete-timeslot' and can_edit:
|
||||
timeslot_pk = request.POST.get('timeslot')
|
||||
if not timeslot_pk or not timeslot_pk.isdecimal():
|
||||
raise Http404
|
||||
|
||||
timeslot = get_object_or_404(TimeSlot, pk=timeslot_pk)
|
||||
|
||||
if timeslot.type_id != 'regular':
|
||||
for session in Session.objects.filter(timeslotassignments__schedule=schedule, timeslotassignments__timeslot=timeslot):
|
||||
for doc in session.materials.all():
|
||||
doc.set_state(State.objects.get(type=doc.type_id, slug='deleted'))
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person, type='deleted')
|
||||
e.desc = "Deleted meeting session"
|
||||
e.save()
|
||||
|
||||
session.delete()
|
||||
|
||||
timeslot.delete()
|
||||
|
||||
return redirect_with_scroll()
|
||||
|
||||
sessions_by_pk = {
|
||||
s.pk: s for s in
|
||||
add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting=meeting,
|
||||
).order_by('pk'),
|
||||
requested_time=True,
|
||||
requested_by=True,
|
||||
).filter(
|
||||
current_status__in=['appr', 'schedw', 'scheda', 'sched', 'canceled', 'canceledpa', 'resched']
|
||||
).prefetch_related(
|
||||
'group', 'group', 'group__type',
|
||||
)
|
||||
}
|
||||
|
||||
assignments_by_timeslot = defaultdict(list)
|
||||
for a in SchedTimeSessAssignment.objects.filter(schedule__in=[schedule, schedule.base]):
|
||||
assignments_by_timeslot[a.timeslot_id].append(a)
|
||||
|
||||
days = [meeting.date + datetime.timedelta(days=i) for i in range(meeting.days)]
|
||||
|
||||
timeslots_by_day_and_room = defaultdict(list)
|
||||
for t in timeslot_qs:
|
||||
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
|
||||
|
||||
min_time = min([t.time.time() for t in timeslot_qs] + [datetime.time(8)])
|
||||
max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)])
|
||||
min_max_delta = datetime.datetime.combine(meeting.date, max_time) - datetime.datetime.combine(meeting.date, min_time)
|
||||
|
||||
day_grid = []
|
||||
for d in days:
|
||||
room_timeslots = []
|
||||
for r in rooms:
|
||||
ts = []
|
||||
for t in timeslots_by_day_and_room.get((d, r.pk), []):
|
||||
if t.type_id == 'regular' and not any(t.slug == 'regular' for t in r.session_types.all()):
|
||||
continue
|
||||
t.assigned_sessions = []
|
||||
for a in assignments_by_timeslot.get(t.pk, []):
|
||||
s = sessions_by_pk.get(a.session_id)
|
||||
if s:
|
||||
t.assigned_sessions.append(s)
|
||||
|
||||
t.left_offset = 100.0 * (t.time - datetime.datetime.combine(t.time.date(), min_time)) / min_max_delta
|
||||
t.layout_width = min(100.0 * t.duration / min_max_delta, 100 - t.left_offset)
|
||||
ts.append(t)
|
||||
|
||||
room_timeslots.append((r, ts))
|
||||
|
||||
day_grid.append({
|
||||
'day': d,
|
||||
'room_timeslots': room_timeslots
|
||||
})
|
||||
|
||||
return render(request, "meeting/edit_meeting_timeslots_and_misc_sessions.html", {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
'can_edit': can_edit,
|
||||
'day_grid': day_grid,
|
||||
'empty_timeslot_form': TimeSlotForm(meeting, schedule),
|
||||
'add_timeslot_form': add_timeslot_form,
|
||||
'edit_timeslot_form': edit_timeslot_form,
|
||||
'scroll': scroll,
|
||||
'hide_menu': True,
|
||||
})
|
||||
|
||||
|
||||
##############################################################################
|
||||
#@role_required('Area Director','Secretariat')
|
||||
# disable the above security for now, check it below.
|
||||
|
|
|
@ -1005,11 +1005,6 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
|
|||
|
||||
/* === List Meeting Schedules ====================================== */
|
||||
|
||||
.table a.edit-schedule-properties {
|
||||
display: inline-block;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
.from-base-schedule {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
@ -1317,3 +1312,130 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
|
|||
cursor: default;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
/* === Edit Meeting Timeslots and Misc Sessions =================== */
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .day {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .day-label {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .room-row {
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .room-label {
|
||||
width: 12em;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeline {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeline.hover {
|
||||
background: radial-gradient(#999 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeline.selected.hover,
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeline.selected {
|
||||
background: radial-gradient(#999 2px, transparent 2px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeslot {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
opacity: 0.8;
|
||||
height: 19px;
|
||||
top: 0px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
padding-left: 0.2em;
|
||||
border-left: 1px solid #999;
|
||||
border-right: 1px solid #999;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeslot:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeslot.selected {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .timeslot .session.cancelled {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
border-top: 0.2em solid #ccc;
|
||||
padding-top: 0.2em;
|
||||
margin-bottom: 2em;
|
||||
background-color: #fff;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel form {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel form button {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form .form-group {
|
||||
margin-right: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form label {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form .form-control {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=time],
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=duration] {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=name] {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=short] {
|
||||
width: 10em;
|
||||
}
|
||||
|
|
156
ietf/static/ietf/js/edit-meeting-timeslots-and-misc-sessions.js
Normal file
156
ietf/static/ietf/js/edit-meeting-timeslots-and-misc-sessions.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
jQuery(document).ready(function () {
|
||||
function reportServerError(xhr, textStatus, error) {
|
||||
let errorText = error || textStatus;
|
||||
if (xhr && xhr.responseText)
|
||||
errorText += "\n\n" + xhr.responseText;
|
||||
alert("Error: " + errorText);
|
||||
}
|
||||
|
||||
let content = jQuery(".edit-meeting-timeslots-and-misc-sessions");
|
||||
|
||||
if (content.data('scroll'))
|
||||
jQuery(document).scrollTop(+content.data('scroll'));
|
||||
else {
|
||||
let scrollFragment = "#scroll=";
|
||||
if (window.location.hash.slice(0, scrollFragment.length) == scrollFragment && !isNaN(+window.location.hash.slice(scrollFragment.length))) {
|
||||
jQuery(document).scrollTop(+window.location.hash.slice(scrollFragment.length));
|
||||
history.replaceState(null, document.title, window.location.pathname + window.location.search);
|
||||
}
|
||||
}
|
||||
|
||||
function reportServerError(xhr, textStatus, error) {
|
||||
let errorText = error || textStatus;
|
||||
if (xhr && xhr.responseText)
|
||||
errorText += "\n\n" + xhr.responseText;
|
||||
alert("Error: " + errorText);
|
||||
}
|
||||
|
||||
let timeslots = content.find(".timeslot");
|
||||
|
||||
timeslots.each(function () {
|
||||
jQuery(this).tooltip({title: jQuery(this).text()});
|
||||
});
|
||||
|
||||
content.find(".day-grid").on("click", cancelCurrentActivity);
|
||||
|
||||
let schedulingPanel = content.find(".scheduling-panel");
|
||||
|
||||
function cancelCurrentActivity() {
|
||||
content.find(".selected").removeClass("selected");
|
||||
|
||||
schedulingPanel.hide();
|
||||
schedulingPanel.find(".panel-content").children().remove();
|
||||
// if we came from a failed POST, that's no longer relevant so overwrite history
|
||||
history.replaceState(null, document.title, window.location.pathname + window.location.search);
|
||||
}
|
||||
|
||||
if (!content.hasClass("read-only")) {
|
||||
// we handle the hover effect in Javascript because we don't want
|
||||
// it to show in case the timeslot itself is hovered
|
||||
content.find(".room-label,.timeline").on("mouseover", function () {
|
||||
jQuery(this).closest(".day").find(".timeline.hover").removeClass("hover");
|
||||
jQuery(this).closest(".room-row").find(".timeline").addClass("hover");
|
||||
}).on("mouseleave", function (){
|
||||
jQuery(this).closest(".day").find(".timeline.hover").removeClass("hover");
|
||||
});
|
||||
|
||||
content.find(".timeline .timeslot").on("mouseover", function (e) {
|
||||
e.stopPropagation();
|
||||
jQuery(this).closest(".day").find(".timeline.hover").removeClass("hover");
|
||||
}).on("mouseleave", function (e) {
|
||||
jQuery(this).closest(".day").find(".timeline.hover").removeClass("hover");
|
||||
});
|
||||
|
||||
content.find(".room-row").on("click", function (e) {
|
||||
e.stopPropagation();
|
||||
cancelCurrentActivity();
|
||||
|
||||
jQuery(this).find(".timeline").addClass("selected");
|
||||
|
||||
schedulingPanel.find(".panel-content").append(content.find(".add-timeslot-template").html());
|
||||
schedulingPanel.find("[name=day]").val(this.dataset.day);
|
||||
schedulingPanel.find("[name=location]").val(this.dataset.room);
|
||||
schedulingPanel.find("[name=type]").trigger("change");
|
||||
schedulingPanel.show();
|
||||
schedulingPanel.find("[name=time]").focus();
|
||||
});
|
||||
}
|
||||
|
||||
content.find(".timeline .timeslot").on("click", function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
let element = jQuery(this);
|
||||
|
||||
element.addClass("selected");
|
||||
|
||||
jQuery.ajax({
|
||||
url: window.location.href,
|
||||
method: "get",
|
||||
timeout: 5 * 1000,
|
||||
data: {
|
||||
action: "edit-timeslot",
|
||||
timeslot: this.id.slice("timeslot".length)
|
||||
}
|
||||
}).fail(reportServerError).done(function (response) {
|
||||
if (!response.form) {
|
||||
reportServerError(null, null, response);
|
||||
return;
|
||||
}
|
||||
|
||||
cancelCurrentActivity();
|
||||
element.addClass("selected");
|
||||
|
||||
schedulingPanel.find(".panel-content").append(response.form);
|
||||
schedulingPanel.find(".timeslot-form [name=type]").trigger("change");
|
||||
schedulingPanel.find(".timeslot-form").show();
|
||||
schedulingPanel.show();
|
||||
});
|
||||
});
|
||||
|
||||
content.on("change click", ".timeslot-form [name=type]", function () {
|
||||
let form = jQuery(this).closest("form");
|
||||
|
||||
let hide = {};
|
||||
|
||||
form.find("[name=group],[name=short],[name=\"agenda_note\"]").prop('disabled', false).closest(".form-group").show();
|
||||
|
||||
if (this.value == "break") {
|
||||
form.find("[name=short]").closest(".form-group").hide();
|
||||
}
|
||||
else if (this.value == "plenary") {
|
||||
let group = form.find("[name=group]");
|
||||
group.val(group.data('ietf'));
|
||||
}
|
||||
else if (this.value == "regular") {
|
||||
form.find("[name=short]").closest(".form-group").hide();
|
||||
}
|
||||
|
||||
if (this.value != "regular")
|
||||
form.find("[name=\"agenda_note\"]").closest(".form-group").hide();
|
||||
|
||||
if (['break', 'reg', 'reserved', 'unavail', 'regular'].indexOf(this.value) != -1) {
|
||||
let group = form.find("[name=group]");
|
||||
group.prop('disabled', true);
|
||||
group.closest(".form-group").hide();
|
||||
}
|
||||
});
|
||||
|
||||
content.on("submit", ".timeslot-form", function () {
|
||||
let form = jQuery(this).closest("form");
|
||||
form.find("[name=scroll]").remove();
|
||||
form.append("<input type=hidden name=scroll value=" + jQuery(document).scrollTop() + ">");
|
||||
});
|
||||
|
||||
content.on("click", "button[type=submit][name=action][value=\"delete-timeslot\"],button[type=submit][name=action][value=\"cancel-timeslot\"]", function (e) {
|
||||
let msg = this.value == "delete-timeslot" ? "Delete this time slot?" : "Cancel the session in this time slot?";
|
||||
if (!confirm(msg)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
schedulingPanel.find(".close").on("click", function () {
|
||||
cancelCurrentActivity();
|
||||
});
|
||||
|
||||
schedulingPanel.find('.timeslot-form [name=type]').trigger("change");
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load staticfiles %}
|
||||
{% load ietf_filters %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript" src="{% static 'ietf/js/edit-meeting-timeslots-and-misc-sessions.js' %}"></script>
|
||||
{% endblock js %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<div class="edit-meeting-timeslots-and-misc-sessions {% if not can_edit %}read-only{% endif %}" {% if scroll %}data-scroll="{{ scroll }}"{% endif %}>
|
||||
|
||||
<p class="pull-right">
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Meeting time slots and misc. sessions for agenda: {{ schedule.name }} {% if not can_edit %}<em>(you do not have permission to edit time slots)</em>{% endif %}
|
||||
</p>
|
||||
|
||||
<div class="day-grid">
|
||||
{% for day in day_grid %}
|
||||
<div class="day">
|
||||
<div class="day-label">
|
||||
<strong>{{ day.day|date:"l" }}</strong>
|
||||
{{ day.day|date:"N j, Y" }}
|
||||
</div>
|
||||
|
||||
{% for room, timeslots in day.room_timeslots %}
|
||||
<div class="room-row" data-room="{{ room.pk }}" data-day="{{ day.day.isoformat }}">
|
||||
<div class="room-label" title="{{ room.name }}">
|
||||
<strong>{{ room.name }}</strong>
|
||||
{% if room.capacity %}{{ room.capacity }}{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
{% for t in timeslots %}
|
||||
<div id="timeslot{{ t.pk }}" class="timeslot" style="left: {{ t.left_offset|floatformat }}%; width: {{ t.layout_width|floatformat }}%;">
|
||||
{% for s in t.assigned_sessions %}
|
||||
<span class="session {% if s.current_status == 'canceled' or s.current_status == 'resched' %}cancelled{% endif %}">
|
||||
{% if s.name %}
|
||||
{{ s.name }}
|
||||
{% if s.group %}
|
||||
({{ s.group.acronym }})
|
||||
{% endif %}
|
||||
{% elif s.group %}
|
||||
{{ s.group.acronym }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% empty %}
|
||||
{% if t.type_id == 'regular' %}
|
||||
(session)
|
||||
{% elif t.name %}
|
||||
{{ t.name }}
|
||||
{% else %}
|
||||
{{ t.type.name }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="time-label">{{ t.time|date:"G:i" }}-{{ t.end_time|date:"G:i" }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="add-timeslot-template" style="display:none">
|
||||
{% include "meeting/edit_timeslot_form.html" with timeslot_form_action='add' timeslot_form=empty_timeslot_form %}
|
||||
</div>
|
||||
|
||||
<div class="scheduling-panel" style="{% if not edit_timeslot_form and not add_timeslot_form %}display:none{% endif %}">
|
||||
<i class="close fa fa-times pull-right"></i>
|
||||
|
||||
<div class="panel-content">
|
||||
{% if edit_timeslot_form %}
|
||||
{% include "meeting/edit_timeslot_form.html" with timeslot_form_action='edit' timeslot_form=edit_timeslot_form %}
|
||||
{% elif add_timeslot_form %}
|
||||
{% include "meeting/edit_timeslot_form.html" with timeslot_form_action='add' timeslot_form=add_timeslot_form %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
41
ietf/templates/meeting/edit_timeslot_form.html
Normal file
41
ietf/templates/meeting/edit_timeslot_form.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load bootstrap3 %}
|
||||
{% if not timeslot_form.active_assignment or timeslot_form.active_assignment.schedule_id == schedule.pk %}
|
||||
<form class="timeslot-form" method="post">{% csrf_token %}
|
||||
<div class="flowing-form">
|
||||
{% bootstrap_field timeslot_form.day %}
|
||||
{% bootstrap_field timeslot_form.time %}
|
||||
{% bootstrap_field timeslot_form.duration %}
|
||||
|
||||
{% bootstrap_field timeslot_form.location %}
|
||||
{% bootstrap_field timeslot_form.show_location %}
|
||||
|
||||
{% bootstrap_field timeslot_form.type %}
|
||||
{% bootstrap_field timeslot_form.group %}
|
||||
{% bootstrap_field timeslot_form.name %}
|
||||
{% bootstrap_field timeslot_form.short %}
|
||||
{% if 'agenda_note' in timeslot_form.fields %}
|
||||
{% bootstrap_field timeslot_form.agenda_note %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<button type="submit" class="btn btn-primary" name="action" value="{{ timeslot_form_action }}-timeslot">
|
||||
{% if timeslot_form_action == 'add' %}Add time slot{% else %}Save{% endif %} slot
|
||||
</button>
|
||||
|
||||
{% if timeslot %}
|
||||
<input type="hidden" name="timeslot" value="{{ timeslot.pk }}">
|
||||
|
||||
{% if timeslot.type_id != 'break' and timeslot.can_cancel %}
|
||||
<button type="submit" class="btn btn-danger" name="action" value="cancel-timeslot" title="Cancel session">Cancel session</button>
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" class="btn btn-danger" name="action" value="delete-timeslot" title="Delete time slot">Delete</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
{% elif schedule.base %}
|
||||
<p class="text-center">You cannot edit this session here - it is set up in the <a href="{% url 'ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions' meeting.number schedule.base.owner_email schedule.base.name %}">base schedule</a></p>
|
||||
{% endif %}
|
|
@ -1,21 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}IETF {{ meeting.number }} Meeting Agenda: {{ schedule.owner }} / {{ schedule.name }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
{% for area in area_list %}
|
||||
.{{ area.upcase_acronym}}-scheme, .meeting_event th.{{ area.upcase_acronym}}-scheme, #{{ area.upcase_acronym }}-groups, #selector-{{ area.upcase_acronym }} { color:{{ area.fg_color }}; background-color: {{ area.bg_color }} }
|
||||
{% endfor %}
|
||||
{% endblock morecss %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<div id="read_only">
|
||||
<p>You do not have access this agenda. It belongs to {{ schedule.owner }}.</p>
|
||||
|
||||
<p><a href="{% url "ietf.meeting.views.list_schedules" meeting.number %}">List your meetings</a>.</p>
|
||||
<p><a href="{% url "ietf.meeting.views.list_schedules" meeting.number %}">Other agendas for this meeting</a>.</p>
|
||||
|
||||
<div class="wrapper custom_text_stuff"></div>
|
||||
</div>
|
||||
|
|
|
@ -8,12 +8,6 @@
|
|||
|
||||
<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>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
|
||||
<div>
|
||||
{% for schedules, own, label in schedule_groups %}
|
||||
<div class="panel panel-default">
|
||||
|
@ -35,16 +29,12 @@
|
|||
<th class="col-md-3">Notes</th>
|
||||
<th class="col-md-1">Visible</th>
|
||||
<th class="col-md-1">Public</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% for schedule in schedules %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.owner_email schedule.name %}">{{ schedule.name }}</a>
|
||||
{% if schedule.can_edit_properties %}
|
||||
<a class="edit-schedule-properties" href="{% url "ietf.meeting.views.edit_schedule_properties" meeting.number schedule.owner_email schedule.name %}?next={{ request.get_full_path|urlencode }}">
|
||||
<i title="Edit agenda properties" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.owner_email schedule.name %}" title="Show regular sessions in agenda">{{ schedule.name }}</a>
|
||||
</td>
|
||||
<td>{{ schedule.owner }}</td>
|
||||
<td>
|
||||
|
@ -55,7 +45,7 @@
|
|||
</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>
|
||||
<a href="{% url "ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions" meeting.number schedule.base.owner_email schedule.base.name %}">{{ schedule.base.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ schedule.notes|linebreaksbr }}</td>
|
||||
|
@ -73,6 +63,17 @@
|
|||
<div class="label label-danger">private</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if schedule.can_edit_properties %}
|
||||
<a class="edit-schedule-properties" href="{% url "ietf.meeting.views.edit_schedule_properties" meeting.number schedule.owner_email schedule.name %}?next={{ request.get_full_path|urlencode }}">
|
||||
<i title="Edit agenda properties" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url "ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions" meeting.number schedule.owner_email schedule.name %}">
|
||||
<i title="Show time slots and misc. sessions for agenda" class="fa fa-calendar"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
Loading…
Reference in a new issue