Tear out old meeting schedule editor and related code
- Legacy-Id: 19551
This commit is contained in:
parent
7b35c09c40
commit
d7f20342b6
|
@ -8,16 +8,12 @@ import jsonfield
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.deletion import CASCADE, PROTECT
|
from django.db.models.deletion import CASCADE, PROTECT
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
#from simple_history.models import HistoricalRecords
|
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.group.colors import fg_group_colors, bg_group_colors
|
from ietf.group.colors import fg_group_colors, bg_group_colors
|
||||||
|
@ -168,30 +164,6 @@ class Group(GroupInfo):
|
||||||
def bg_color(self):
|
def bg_color(self):
|
||||||
return bg_group_colors[self.upcase_acronym]
|
return bg_group_colors[self.upcase_acronym]
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/group/%s.json" % (self.acronym,)
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
group1= dict()
|
|
||||||
group1['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
group1['acronym'] = self.acronym
|
|
||||||
group1['name'] = self.name
|
|
||||||
group1['state'] = self.state.slug
|
|
||||||
group1['type'] = self.type.slug
|
|
||||||
if self.parent is not None:
|
|
||||||
group1['parent_href'] = urljoin(host_scheme, self.parent.json_url())
|
|
||||||
# uncomment when people URL handle is created
|
|
||||||
try:
|
|
||||||
if self.ad_role() is not None:
|
|
||||||
group1['ad_href'] = urljoin(host_scheme, self.ad_role().person.json_url())
|
|
||||||
except Person.DoesNotExist:
|
|
||||||
pass
|
|
||||||
group1['list_email'] = self.list_email
|
|
||||||
group1['list_subscribe'] = self.list_subscribe
|
|
||||||
group1['list_archive'] = self.list_archive
|
|
||||||
group1['comments'] = self.comments
|
|
||||||
return group1
|
|
||||||
|
|
||||||
def has_tools_page(self):
|
def has_tools_page(self):
|
||||||
return self.type_id in ['wg', ] and self.state_id in ['active', 'dormant', 'replaced', 'conclude']
|
return self.type_id in ['wg', ] and self.state_id in ['active', 'dormant', 'replaced', 'conclude']
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,6 @@ info_detail_urls = [
|
||||||
group_urls = [
|
group_urls = [
|
||||||
url(r'^$', views.active_groups),
|
url(r'^$', views.active_groups),
|
||||||
url(r'^groupmenu.json', views.group_menu_data, None, 'ietf.group.views.group_menu_data'),
|
url(r'^groupmenu.json', views.group_menu_data, None, 'ietf.group.views.group_menu_data'),
|
||||||
url(r'^%(acronym)s.json$' % settings.URL_REGEXPS, views.group_json),
|
|
||||||
url(r'^chartering/$', views.chartering_groups),
|
url(r'^chartering/$', views.chartering_groups),
|
||||||
url(r'^chartering/create/(?P<group_type>(wg|rg))/$', views.edit, {'action': "charter"}),
|
url(r'^chartering/create/(?P<group_type>(wg|rg))/$', views.edit, {'action': "charter"}),
|
||||||
url(r'^concluded/$', views.concluded_groups),
|
url(r'^concluded/$', views.concluded_groups),
|
||||||
|
|
|
@ -38,7 +38,6 @@ import copy
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import io
|
import io
|
||||||
import json
|
|
||||||
import markdown
|
import markdown
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
@ -1299,13 +1298,6 @@ def stream_edit(request, acronym):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def group_json(request, acronym):
|
|
||||||
group = get_object_or_404(Group, acronym=acronym)
|
|
||||||
|
|
||||||
return HttpResponse(json.dumps(group.json_dict(request.build_absolute_uri('/')),
|
|
||||||
sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
@cache_control(public=True, max_age=30*60)
|
@cache_control(public=True, max_age=30*60)
|
||||||
@cache_page(30 * 60)
|
@cache_page(30 * 60)
|
||||||
def group_menu_data(request):
|
def group_menu_data(request):
|
||||||
|
|
|
@ -1,629 +0,0 @@
|
||||||
# Copyright The IETF Trust 2013-2019, All Rights Reserved
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.http import QueryDict
|
|
||||||
from django.http import Http404
|
|
||||||
from django.views.decorators.http import require_POST
|
|
||||||
|
|
||||||
from ietf.ietfauth.utils import role_required, has_role
|
|
||||||
from ietf.meeting.helpers import get_meeting, get_schedule, schedule_permissions, get_person_by_email, get_schedule_by_name
|
|
||||||
from ietf.meeting.models import TimeSlot, Session, Schedule, Room, Constraint, SchedTimeSessAssignment, ResourceAssociation
|
|
||||||
from ietf.meeting.views import edit_timeslots, edit_meeting_schedule
|
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
|
||||||
|
|
||||||
def is_truthy_enough(value):
|
|
||||||
return not (value == "0" or value == 0 or value=="false")
|
|
||||||
|
|
||||||
# look up a schedule by number, owner and schedule name, returning an API error if it can not be found
|
|
||||||
def get_meeting_schedule(num, owner, name):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
person = get_person_by_email(owner)
|
|
||||||
schedule = get_schedule_by_name(meeting, person, name)
|
|
||||||
|
|
||||||
if schedule is None or person is None or meeting is None:
|
|
||||||
meeting_pk = 0
|
|
||||||
person_pk = 0
|
|
||||||
schedule_pk =0
|
|
||||||
# to make diagnostics more meaningful, log what we found
|
|
||||||
if meeting:
|
|
||||||
meeting_pk = meeting.pk
|
|
||||||
if person:
|
|
||||||
person_pk = person.pk
|
|
||||||
if schedule:
|
|
||||||
schedule_pk=schedule.pk
|
|
||||||
return HttpResponse(json.dumps({'error' : 'invalid meeting=%s/person=%s/schedule=%s' % (num,owner,name),
|
|
||||||
'meeting': meeting_pk,
|
|
||||||
'person': person_pk,
|
|
||||||
'schedule': schedule_pk}),
|
|
||||||
content_type="application/json",
|
|
||||||
status=404);
|
|
||||||
return meeting, person, schedule
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# should asking if an schedule is read-only require any kind of permission?
|
|
||||||
def schedule_permission_api(request, num, owner, name):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
person = get_person_by_email(owner)
|
|
||||||
schedule = get_schedule_by_name(meeting, person, name)
|
|
||||||
|
|
||||||
save_perm = False
|
|
||||||
secretariat = False
|
|
||||||
cansee = False
|
|
||||||
canedit = False
|
|
||||||
owner_href = ""
|
|
||||||
|
|
||||||
if schedule is not None:
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
owner_href = request.build_absolute_uri(schedule.owner.json_url())
|
|
||||||
|
|
||||||
if has_role(request.user, "Area Director") or secretariat:
|
|
||||||
save_perm = True
|
|
||||||
|
|
||||||
return HttpResponse(json.dumps({'secretariat': secretariat,
|
|
||||||
'save_perm': save_perm,
|
|
||||||
'read_only': canedit==False,
|
|
||||||
'owner_href': owner_href}),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## ROOM API
|
|
||||||
#############################################################################
|
|
||||||
from django.forms.models import modelform_factory
|
|
||||||
AddRoomForm = modelform_factory(Room, exclude=('meeting','time'))
|
|
||||||
|
|
||||||
# no authorization required
|
|
||||||
def timeslot_roomlist(request, mtg):
|
|
||||||
rooms = mtg.room_set.all()
|
|
||||||
json_array=[]
|
|
||||||
for room in rooms:
|
|
||||||
json_array.append(room.json_dict(request.build_absolute_uri('/')))
|
|
||||||
return HttpResponse(json.dumps(json_array),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_addroom(request, meeting):
|
|
||||||
newroomform = AddRoomForm(request.POST)
|
|
||||||
if not newroomform.is_valid():
|
|
||||||
return HttpResponse(status=404)
|
|
||||||
|
|
||||||
newroom = newroomform.save(commit=False)
|
|
||||||
newroom.meeting = meeting
|
|
||||||
newroom.save()
|
|
||||||
newroom.create_timeslots()
|
|
||||||
|
|
||||||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
|
||||||
return redirect(timeslot_roomurl, meeting.number, newroom.pk)
|
|
||||||
else:
|
|
||||||
return redirect(edit_timeslots, meeting.number)
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_delroom(request, meeting, roomid):
|
|
||||||
room = get_object_or_404(meeting.room_set, pk=roomid)
|
|
||||||
|
|
||||||
room.delete_timeslots()
|
|
||||||
room.delete()
|
|
||||||
return HttpResponse('{"error":"none"}', status = 200)
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_updroom(request, meeting, roomid):
|
|
||||||
room = get_object_or_404(meeting.room_set, pk=roomid)
|
|
||||||
|
|
||||||
if "name" in request.POST:
|
|
||||||
room.name = request.POST["name"]
|
|
||||||
|
|
||||||
if "capacity" in request.POST:
|
|
||||||
room.capacity = request.POST["capacity"]
|
|
||||||
|
|
||||||
if "resources" in request.POST:
|
|
||||||
new_resource_ids = request.POST["resources"]
|
|
||||||
new_resources = [ ResourceAssociation.objects.get(pk=a)
|
|
||||||
for a in new_resource_ids]
|
|
||||||
room.resources = new_resources
|
|
||||||
|
|
||||||
room.save()
|
|
||||||
return HttpResponse('{"error":"none"}', status = 200)
|
|
||||||
|
|
||||||
def timeslot_roomsurl(request, num=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return timeslot_roomlist(request, meeting)
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return timeslot_addroom(request, meeting)
|
|
||||||
|
|
||||||
# unacceptable reply
|
|
||||||
return HttpResponse(status=406)
|
|
||||||
|
|
||||||
def timeslot_roomurl(request, num=None, roomid=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
room = get_object_or_404(meeting.room_set, pk=roomid)
|
|
||||||
return HttpResponse(json.dumps(room.json_dict(request.build_absolute_uri('/'))),
|
|
||||||
content_type="application/json")
|
|
||||||
elif request.method == 'PUT':
|
|
||||||
return timeslot_updroom(request, meeting, roomid)
|
|
||||||
elif request.method == 'DELETE':
|
|
||||||
return timeslot_delroom(request, meeting, roomid)
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## DAY/SLOT API
|
|
||||||
## -- this creates groups of timeslots, and associated schedtimesessassignments.
|
|
||||||
#############################################################################
|
|
||||||
AddSlotForm = modelform_factory(TimeSlot, exclude=('meeting','name','location','sessions', 'modified'))
|
|
||||||
|
|
||||||
# no authorization required to list.
|
|
||||||
def timeslot_slotlist(request, mtg):
|
|
||||||
slots = mtg.timeslot_set.all()
|
|
||||||
# Restrict graphical editing to slots of type 'regular' for now
|
|
||||||
slots = slots.filter(type__slug='regular')
|
|
||||||
json_array=[]
|
|
||||||
for slot in slots:
|
|
||||||
json_array.append(slot.json_dict(request.build_absolute_uri('/')))
|
|
||||||
return HttpResponse(json.dumps(json_array, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_addslot(request, meeting):
|
|
||||||
addslotform = AddSlotForm(request.POST)
|
|
||||||
#debug.log("newslot: %u" % ( addslotform.is_valid() ))
|
|
||||||
if not addslotform.is_valid():
|
|
||||||
return HttpResponse(status=404)
|
|
||||||
|
|
||||||
newslot = addslotform.save(commit=False)
|
|
||||||
newslot.meeting = meeting
|
|
||||||
newslot.save()
|
|
||||||
|
|
||||||
# XXX FIXME: timeslot_dayurl is undefined. Placeholder:
|
|
||||||
# timeslot_dayurl = None
|
|
||||||
# XXX FIXME: newroom is undefined. Placeholder:
|
|
||||||
# newroom = None
|
|
||||||
values = newslot.json_dict(request.build_absolute_uri('/'))
|
|
||||||
response = HttpResponse(json.dumps(values),
|
|
||||||
content_type="application/json",
|
|
||||||
status=201)
|
|
||||||
response['Location'] = values['href']
|
|
||||||
return response
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_updslot(request, meeting, slotid):
|
|
||||||
slot = get_object_or_404(meeting.timeslot_set, pk=slotid)
|
|
||||||
|
|
||||||
# at present, updates to the purpose only is supported.
|
|
||||||
# updates to time or duration would need likely need to be
|
|
||||||
# propogated to the entire vertical part of the grid, and nothing
|
|
||||||
# needs to do that yet.
|
|
||||||
if request.method == 'POST':
|
|
||||||
put_vars = request.POST
|
|
||||||
slot.type_id = put_vars["purpose"]
|
|
||||||
else:
|
|
||||||
put_vars = QueryDict(request.body)
|
|
||||||
slot.type_id = put_vars.get("purpose")
|
|
||||||
|
|
||||||
slot.save()
|
|
||||||
|
|
||||||
# WORKAROUND: Right now, if there are sessions scheduled in this timeslot
|
|
||||||
# when it is marked unavailable (or any other value besides 'regular') they
|
|
||||||
# become unreachable from the editing screen. The session is listed in the
|
|
||||||
# "unscheduled" block incorrectly, and drag-dropping it onto the a new
|
|
||||||
# timeslot produces erroneous results. To avoid this, we will silently
|
|
||||||
# unschedule any sessions in the timeslot that has just been made
|
|
||||||
# unavailable.
|
|
||||||
|
|
||||||
if slot.type_id != 'regular':
|
|
||||||
slot.sessionassignments.all().delete()
|
|
||||||
|
|
||||||
# ENDWORKAROUND
|
|
||||||
|
|
||||||
# need to return the new object.
|
|
||||||
dict1 = slot.json_dict(request.build_absolute_uri('/'))
|
|
||||||
dict1['message'] = 'valid'
|
|
||||||
return HttpResponse(json.dumps(dict1),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def timeslot_delslot(request, meeting, slotid):
|
|
||||||
slot = get_object_or_404(meeting.timeslot_set, pk=slotid)
|
|
||||||
|
|
||||||
# this will delete self as well.
|
|
||||||
slot.delete_concurrent_timeslots()
|
|
||||||
return HttpResponse('{"error":"none"}', status = 200)
|
|
||||||
|
|
||||||
def timeslot_slotsurl(request, num=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return timeslot_slotlist(request, meeting)
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return timeslot_addslot(request, meeting)
|
|
||||||
|
|
||||||
# unacceptable reply
|
|
||||||
return HttpResponse(status=406)
|
|
||||||
|
|
||||||
def timeslot_sloturl(request, num=None, slotid=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
slot = get_object_or_404(meeting.timeslot_set, pk=slotid)
|
|
||||||
return HttpResponse(json.dumps(slot.json_dict(request.build_absolute_uri('/'))),
|
|
||||||
content_type="application/json")
|
|
||||||
elif request.method == 'POST' or request.method == 'PUT':
|
|
||||||
return timeslot_updslot(request, meeting, slotid)
|
|
||||||
elif request.method == 'DELETE':
|
|
||||||
return timeslot_delslot(request, meeting, slotid)
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Schedule List API
|
|
||||||
#############################################################################
|
|
||||||
ScheduleEntryForm = modelform_factory(Schedule, exclude=('meeting','owner'))
|
|
||||||
EditScheduleEntryForm = modelform_factory(Schedule, exclude=('meeting','owner', 'name'))
|
|
||||||
|
|
||||||
@role_required('Area Director','Secretariat')
|
|
||||||
def schedule_list(request, mtg):
|
|
||||||
schedules = mtg.schedule_set.all()
|
|
||||||
json_array=[]
|
|
||||||
for schedule in schedules:
|
|
||||||
json_array.append(schedule.json_dict(request.build_absolute_uri('/')))
|
|
||||||
return HttpResponse(json.dumps(json_array),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# duplicates save-as functionality below.
|
|
||||||
@role_required('Area Director','Secretariat')
|
|
||||||
def schedule_add(request, meeting):
|
|
||||||
newscheduleform = ScheduleEntryForm(request.POST)
|
|
||||||
if not newscheduleform.is_valid():
|
|
||||||
return HttpResponse(status=404)
|
|
||||||
|
|
||||||
newschedule = newscheduleform.save(commit=False)
|
|
||||||
newschedule.meeting = meeting
|
|
||||||
newschedule.owner = request.user.person
|
|
||||||
newschedule.save()
|
|
||||||
|
|
||||||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
|
||||||
return redirect(schedule_infourl, meeting.number, newschedule.owner_email(), newschedule.name)
|
|
||||||
else:
|
|
||||||
return redirect(edit_meeting_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
|
||||||
|
|
||||||
@require_POST
|
|
||||||
def schedule_update(request, meeting, schedule):
|
|
||||||
# forms are completely useless for update actions that want to
|
|
||||||
# accept a subset of values. (huh? we could use required=False)
|
|
||||||
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
if not user.is_authenticated:
|
|
||||||
return HttpResponse({'error':'no permission'}, status=403)
|
|
||||||
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
#read_only = not canedit ## not used
|
|
||||||
|
|
||||||
# TODO: Secretariat should always get canedit
|
|
||||||
if not (canedit or secretariat):
|
|
||||||
return HttpResponse({'error':'no permission'}, status=403)
|
|
||||||
|
|
||||||
if "public" in request.POST:
|
|
||||||
schedule.public = is_truthy_enough(request.POST["public"])
|
|
||||||
|
|
||||||
if "visible" in request.POST:
|
|
||||||
schedule.visible = is_truthy_enough(request.POST["visible"])
|
|
||||||
|
|
||||||
if "name" in request.POST:
|
|
||||||
schedule.name = request.POST["name"]
|
|
||||||
|
|
||||||
schedule.save()
|
|
||||||
|
|
||||||
# enforce that a non-public schedule can not be the public one.
|
|
||||||
if meeting.schedule == schedule and not schedule.public:
|
|
||||||
meeting.schedule = None
|
|
||||||
meeting.save()
|
|
||||||
|
|
||||||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
|
||||||
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
|
|
||||||
content_type="application/json")
|
|
||||||
else:
|
|
||||||
return redirect(edit_meeting_schedule, meeting.number, schedule.owner_email(), schedule.name)
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def schedule_del(request, meeting, schedule):
|
|
||||||
schedule.delete_assignments()
|
|
||||||
#debug.log("deleting meeting: %s schedule: %s" % (meeting, meeting.schedule))
|
|
||||||
if meeting.schedule == schedule:
|
|
||||||
meeting.schedule = None
|
|
||||||
meeting.save()
|
|
||||||
schedule.delete()
|
|
||||||
return HttpResponse('{"error":"none"}', status = 200)
|
|
||||||
|
|
||||||
def schedule_infosurl(request, num=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return schedule_list(request, meeting)
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return schedule_add(request, meeting)
|
|
||||||
|
|
||||||
# unacceptable action
|
|
||||||
return HttpResponse(status=406)
|
|
||||||
|
|
||||||
def schedule_infourl(request, num=None, owner=None, name=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
person = get_person_by_email(owner)
|
|
||||||
schedule = get_schedule_by_name(meeting, person, name)
|
|
||||||
if schedule is None:
|
|
||||||
raise Http404("No meeting information for meeting %s schedule %s available" % (num,name))
|
|
||||||
|
|
||||||
#debug.log("results in schedule: %u / %s" % (schedule.id, request.method))
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
|
|
||||||
content_type="application/json")
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return schedule_update(request, meeting, schedule)
|
|
||||||
elif request.method == 'DELETE':
|
|
||||||
return schedule_del(request, meeting, schedule)
|
|
||||||
else:
|
|
||||||
return HttpResponse(status=406)
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Meeting API (very limited)
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
def meeting_get(request, meeting):
|
|
||||||
return HttpResponse(json.dumps(meeting.json_dict(request.build_absolute_uri('/')),
|
|
||||||
sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
|
||||||
def meeting_update(request, meeting):
|
|
||||||
# at present, only the official schedule can be updated from this interface.
|
|
||||||
|
|
||||||
#debug.log("1 meeting.schedule: %s / %s / %s" % (meeting.schedule, update_dict, request.body))
|
|
||||||
if "schedule" in request.POST:
|
|
||||||
value = request.POST["schedule"]
|
|
||||||
#debug.log("4 meeting.schedule: %s" % (value))
|
|
||||||
if not value or value == "None": # value == "None" is just weird, better with empty string
|
|
||||||
meeting.set_official_schedule(None)
|
|
||||||
else:
|
|
||||||
schedule = get_schedule(meeting, value)
|
|
||||||
if not schedule.public:
|
|
||||||
return HttpResponse(status = 406)
|
|
||||||
#debug.log("3 meeting.schedule: %s" % (schedule))
|
|
||||||
meeting.set_official_schedule(schedule)
|
|
||||||
|
|
||||||
#debug.log("2 meeting.schedule: %s" % (meeting.schedule))
|
|
||||||
meeting.save()
|
|
||||||
return meeting_get(request, meeting)
|
|
||||||
|
|
||||||
def meeting_json(request, num):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return meeting_get(request, meeting)
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return meeting_update(request, meeting)
|
|
||||||
else:
|
|
||||||
return HttpResponse(status=406)
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Session details API functions
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
def session_json(request, num, sessionid):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = meeting.session_set.get(pk=int(sessionid))
|
|
||||||
except Session.DoesNotExist:
|
|
||||||
# return json.dumps({'error':"no such session %s" % sessionid})
|
|
||||||
return HttpResponse(json.dumps({'error':"no such session %s" % sessionid}),
|
|
||||||
status = 404,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
sess1 = session.json_dict(request.build_absolute_uri('/'))
|
|
||||||
return HttpResponse(json.dumps(sess1, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# get group of all sessions.
|
|
||||||
def sessions_json(request, num):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
sessions = meeting.session_set.that_can_meet().with_requested_time().with_requested_by()
|
|
||||||
|
|
||||||
sess1_dict = [ x.json_dict(request.build_absolute_uri('/')) for x in sessions ]
|
|
||||||
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Scheduledsesion
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
# this creates an entirely *NEW* schedtimesessassignment
|
|
||||||
def assignments_post(request, meeting, schedule):
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
if not canedit:
|
|
||||||
return HttpResponse(json.dumps({'error':'no permission to modify this schedule'}),
|
|
||||||
status = 403,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# get JSON out of raw body. XXX should check Content-Type!
|
|
||||||
newvalues = json.loads(request.body)
|
|
||||||
if not ("session_id" in newvalues) or not ("timeslot_id" in newvalues):
|
|
||||||
return HttpResponse(json.dumps({'error':'missing values, timeslot_id and session_id required'}),
|
|
||||||
status = 406,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
try:
|
|
||||||
Session.objects.get(pk=newvalues["session_id"])
|
|
||||||
except Session.DoesNotExist:
|
|
||||||
return HttpResponse(json.dumps({'error':'session has been deleted'}),
|
|
||||||
status = 406,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
ss1 = SchedTimeSessAssignment(schedule = schedule,
|
|
||||||
session_id = newvalues["session_id"],
|
|
||||||
timeslot_id = newvalues["timeslot_id"])
|
|
||||||
if("extendedfrom_id" in newvalues):
|
|
||||||
val = int(newvalues["extendedfrom_id"])
|
|
||||||
try:
|
|
||||||
ss2 = schedule.assignments.get(pk = val)
|
|
||||||
ss1.extendedfrom = ss2
|
|
||||||
except SchedTimeSessAssignment.DoesNotExist:
|
|
||||||
return HttpResponse(json.dumps({'error':'invalid extendedfrom value: %u' % val}),
|
|
||||||
status = 406,
|
|
||||||
content_type="application/json")
|
|
||||||
ss1.save()
|
|
||||||
ss1_dict = ss1.json_dict(request.build_absolute_uri('/'))
|
|
||||||
response = HttpResponse(json.dumps(ss1_dict),
|
|
||||||
status = 201,
|
|
||||||
content_type="application/json")
|
|
||||||
# 201 code needs a Location: header.
|
|
||||||
response['Location'] = ss1_dict["href"],
|
|
||||||
return response
|
|
||||||
|
|
||||||
def assignments_get(request, num, schedule):
|
|
||||||
assignments = schedule.assignments.all()
|
|
||||||
|
|
||||||
absolute_url = request.build_absolute_uri('/')
|
|
||||||
sess1_dict = [ x.json_dict(absolute_url) for x in assignments ]
|
|
||||||
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# this returns the list of scheduled sessions for the given named schedule
|
|
||||||
def assignments_json(request, num, owner, name):
|
|
||||||
info = get_meeting_schedule(num, owner, name)
|
|
||||||
# The return values from get_meeting_schedule() are silly, in that it
|
|
||||||
# is a tuple for non-error return, but a HTTPResponse when error, but
|
|
||||||
# work around that for the moment
|
|
||||||
if isinstance(info, HttpResponse):
|
|
||||||
return info
|
|
||||||
meeting, person, schedule = info
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return assignments_get(request, meeting, schedule)
|
|
||||||
elif request.method == 'POST':
|
|
||||||
return assignments_post(request, meeting, schedule)
|
|
||||||
else:
|
|
||||||
return HttpResponse(json.dumps({'error':'inappropriate action: %s' % (request.method)}),
|
|
||||||
status = 406,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# accepts both POST and PUT in order to implement Postel Doctrine.
|
|
||||||
def assignment_update(request, meeting, schedule, ss):
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
if not canedit:
|
|
||||||
return HttpResponse(json.dumps({'error':'no permission to update this schedule'}),
|
|
||||||
status = 403,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
put_vars = request.POST
|
|
||||||
ss.pinned = is_truthy_enough(put_vars["pinned"])
|
|
||||||
else:
|
|
||||||
put_vars = QueryDict(request.body)
|
|
||||||
ss.pinned = is_truthy_enough(put_vars.get("pinned"))
|
|
||||||
|
|
||||||
ss.save()
|
|
||||||
return HttpResponse(json.dumps({'message':'valid'}),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
def assignment_delete(request, meeting, schedule, ss):
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
if not canedit:
|
|
||||||
return HttpResponse(json.dumps({'error':'no permission to update this schedule'}),
|
|
||||||
status = 403,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# in case there is, somehow, more than one item with the same pk.. XXX
|
|
||||||
assignments = schedule.assignments.filter(pk = ss.pk)
|
|
||||||
if len(assignments) == 0:
|
|
||||||
return HttpResponse(json.dumps({'error':'no such object'}),
|
|
||||||
status = 404,
|
|
||||||
content_type="application/json")
|
|
||||||
count=0
|
|
||||||
for ss in assignments:
|
|
||||||
ss.delete()
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
return HttpResponse(json.dumps({'result':"%u objects deleted"%(count)}),
|
|
||||||
status = 200,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
def assignment_get(request, meeting, schedule, ss):
|
|
||||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
|
||||||
|
|
||||||
if not cansee:
|
|
||||||
return HttpResponse(json.dumps({'error':'no permission to see this schedule'}),
|
|
||||||
status = 403,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
sess1_dict = ss.json_dict(request.build_absolute_uri('/'))
|
|
||||||
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
# this return a specific session, updates a session or deletes a SPECIFIC scheduled session
|
|
||||||
def assignment_json(request, num, owner, name, assignment_id):
|
|
||||||
meeting, person, schedule = get_meeting_schedule(num, owner, name)
|
|
||||||
|
|
||||||
assignments = schedule.assignments.filter(pk = assignment_id)
|
|
||||||
if len(assignments) == 0:
|
|
||||||
return HttpResponse(json.dumps({'error' : 'invalid assignment'}),
|
|
||||||
content_type="application/json",
|
|
||||||
status=404);
|
|
||||||
ss = assignments[0]
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return assignment_get(request, meeting, schedule, ss)
|
|
||||||
elif request.method == 'PUT' or request.method=='POST':
|
|
||||||
return assignment_update(request, meeting, schedule, ss)
|
|
||||||
elif request.method == 'DELETE':
|
|
||||||
return assignment_delete(request, meeting, schedule, ss)
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Constraints API
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
# Would like to cache for 1 day, but there are invalidation issues.
|
|
||||||
#@cache_page(86400)
|
|
||||||
def constraint_json(request, num, constraintid):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
try:
|
|
||||||
constraint = meeting.constraint_set.get(pk=int(constraintid))
|
|
||||||
except Constraint.DoesNotExist:
|
|
||||||
return HttpResponse(json.dumps({'error':"no such constraint %s" % constraintid}),
|
|
||||||
status = 404,
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
json1 = constraint.json_dict(request.build_absolute_uri('/'))
|
|
||||||
return HttpResponse(json.dumps(json1, sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# Cache for 2 hour2
|
|
||||||
#@cache_page(7200)
|
|
||||||
# caching is a problem if there Host: header changes.
|
|
||||||
#
|
|
||||||
def session_constraints(request, num, sessionid):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
|
|
||||||
#print "Getting meeting=%s session contraints for %s" % (num, sessionid)
|
|
||||||
try:
|
|
||||||
session = meeting.session_set.get(pk=int(sessionid))
|
|
||||||
except Session.DoesNotExist:
|
|
||||||
return json.dumps({"error":"no such session"})
|
|
||||||
|
|
||||||
constraint_list = session.constraints_dict(request.build_absolute_uri('/'))
|
|
||||||
|
|
||||||
json_str = json.dumps(constraint_list,
|
|
||||||
sort_keys=True, indent=2),
|
|
||||||
#print " gives: %s" % (json_str)
|
|
||||||
|
|
||||||
return HttpResponse(json_str, content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,11 @@ import os
|
||||||
import re
|
import re
|
||||||
from tempfile import mkstemp
|
from tempfile import mkstemp
|
||||||
|
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import Http404
|
||||||
from django.db.models import F, Max, Q, Prefetch
|
from django.db.models import F, Prefetch
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.cache import cache
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.cache import get_cache_key
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
@ -32,95 +30,12 @@ from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssign
|
||||||
from ietf.meeting.utils import session_requested_by, add_event_info_to_session_qs
|
from ietf.meeting.utils import session_requested_by, add_event_info_to_session_qs
|
||||||
from ietf.name.models import ImportantDateName, SessionPurposeName
|
from ietf.name.models import ImportantDateName, SessionPurposeName
|
||||||
from ietf.utils import log
|
from ietf.utils import log
|
||||||
from ietf.utils.history import find_history_active_at, find_history_replacements_active_at
|
from ietf.utils.history import find_history_replacements_active_at
|
||||||
from ietf.utils.mail import send_mail
|
from ietf.utils.mail import send_mail
|
||||||
from ietf.utils.pipe import pipe
|
from ietf.utils.pipe import pipe
|
||||||
from ietf.utils.text import xslugify
|
from ietf.utils.text import xslugify
|
||||||
|
|
||||||
|
|
||||||
def find_ads_for_meeting(meeting):
|
|
||||||
ads = []
|
|
||||||
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
|
|
||||||
|
|
||||||
num = 0
|
|
||||||
# get list of ADs which are/were active at the time of the meeting.
|
|
||||||
# (previous [x for x in y] syntax changed to aid debugging)
|
|
||||||
for g in Group.objects.filter(type="area").order_by("acronym"):
|
|
||||||
history = find_history_active_at(g, meeting_time)
|
|
||||||
num = num +1
|
|
||||||
if history and history != g:
|
|
||||||
#print " history[%u]: %s" % (num, history)
|
|
||||||
if history.state_id == "active":
|
|
||||||
for x in history.rolehistory_set.filter(name="ad",group__type='area').select_related('group', 'person', 'email'):
|
|
||||||
#print "xh[%u]: %s" % (num, x)
|
|
||||||
ads.append(x)
|
|
||||||
else:
|
|
||||||
#print " group[%u]: %s" % (num, g)
|
|
||||||
if g.state_id == "active":
|
|
||||||
for x in g.role_set.filter(name="ad",group__type='area').select_related('group', 'person', 'email'):
|
|
||||||
#print "xg[%u]: %s (#%u)" % (num, x, x.pk)
|
|
||||||
ads.append(x)
|
|
||||||
return ads
|
|
||||||
|
|
||||||
|
|
||||||
# get list of all areas, + IRTF + IETF (plenaries).
|
|
||||||
def get_pseudo_areas():
|
|
||||||
return Group.objects.filter(Q(state="active", name="IRTF")|
|
|
||||||
Q(state="active", name="IETF")|
|
|
||||||
Q(state="active", type="area")).order_by('acronym')
|
|
||||||
|
|
||||||
# get list of all areas, + IRTF.
|
|
||||||
def get_areas():
|
|
||||||
return Group.objects.filter(Q(state="active",
|
|
||||||
name="IRTF")|
|
|
||||||
Q(state="active", type="area")).order_by('acronym')
|
|
||||||
|
|
||||||
# get list of areas that are referenced.
|
|
||||||
def get_area_list_from_sessions(assignments, num):
|
|
||||||
return assignments.filter(timeslot__type = 'regular',
|
|
||||||
session__group__parent__isnull = False).order_by(
|
|
||||||
'session__group__parent__acronym').distinct().values_list(
|
|
||||||
'session__group__parent__acronym',flat=True)
|
|
||||||
|
|
||||||
def build_all_agenda_slices(meeting):
|
|
||||||
time_slices = []
|
|
||||||
date_slices = {}
|
|
||||||
|
|
||||||
for ts in meeting.timeslot_set.filter(type__in=['regular',]).order_by('time','name'):
|
|
||||||
ymd = ts.time.date()
|
|
||||||
|
|
||||||
if ymd not in date_slices and ts.location != None:
|
|
||||||
date_slices[ymd] = []
|
|
||||||
time_slices.append(ymd)
|
|
||||||
|
|
||||||
if ymd in date_slices:
|
|
||||||
if [ts.time, ts.time+ts.duration] not in date_slices[ymd]: # only keep unique entries
|
|
||||||
date_slices[ymd].append([ts.time, ts.time+ts.duration])
|
|
||||||
|
|
||||||
time_slices.sort()
|
|
||||||
return time_slices,date_slices
|
|
||||||
|
|
||||||
def get_all_assignments_from_schedule(schedule):
|
|
||||||
ss = schedule.assignments.filter(timeslot__location__isnull = False)
|
|
||||||
ss = ss.filter(session__type__slug='regular')
|
|
||||||
ss = ss.order_by('timeslot__time','timeslot__name')
|
|
||||||
|
|
||||||
return ss
|
|
||||||
|
|
||||||
def get_modified_from_assignments(assignments):
|
|
||||||
return assignments.aggregate(Max('timeslot__modified'))['timeslot__modified__max']
|
|
||||||
|
|
||||||
def get_wg_name_list(assignments):
|
|
||||||
return assignments.filter(timeslot__type = 'regular',
|
|
||||||
session__group__isnull = False,
|
|
||||||
session__group__parent__isnull = False).order_by(
|
|
||||||
'session__group__acronym').distinct().values_list(
|
|
||||||
'session__group__acronym',flat=True)
|
|
||||||
|
|
||||||
def get_wg_list(assignments):
|
|
||||||
wg_name_list = get_wg_name_list(assignments)
|
|
||||||
return Group.objects.filter(acronym__in = set(wg_name_list)).order_by('parent__acronym','acronym')
|
|
||||||
|
|
||||||
def get_meeting(num=None,type_in=['ietf',],days=28):
|
def get_meeting(num=None,type_in=['ietf',],days=28):
|
||||||
meetings = Meeting.objects
|
meetings = Meeting.objects
|
||||||
if type_in:
|
if type_in:
|
||||||
|
@ -799,15 +714,6 @@ def schedule_permissions(meeting, schedule, user):
|
||||||
|
|
||||||
return cansee, canedit, secretariat
|
return cansee, canedit, secretariat
|
||||||
|
|
||||||
def session_constraint_expire(request,session):
|
|
||||||
from .ajax import session_constraints
|
|
||||||
path = reverse(session_constraints, args=[session.meeting.number, session.pk])
|
|
||||||
temp_request = HttpRequest()
|
|
||||||
temp_request.path = path
|
|
||||||
temp_request.META['HTTP_HOST'] = request.META['HTTP_HOST']
|
|
||||||
key = get_cache_key(temp_request)
|
|
||||||
if key is not None and key in cache:
|
|
||||||
cache.delete(key)
|
|
||||||
|
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
# Interim Meeting Helpers
|
# Interim Meeting Helpers
|
||||||
|
|
|
@ -14,7 +14,6 @@ import string
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
|
@ -23,9 +22,6 @@ from django.db import models
|
||||||
from django.db.models import Max, Subquery, OuterRef, TextField, Value, Q
|
from django.db.models import Max, Subquery, OuterRef, TextField, Value, Q
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
# mostly used by json_dict()
|
|
||||||
#from django.template.defaultfilters import slugify, date as date_format, time as time_format
|
|
||||||
from django.template.defaultfilters import date as date_format
|
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -310,35 +306,9 @@ class Meeting(models.Model):
|
||||||
slugs = ('conflict', 'conflic2', 'conflic3')
|
slugs = ('conflict', 'conflic2', 'conflic3')
|
||||||
return ConstraintName.objects.filter(slug__in=slugs)
|
return ConstraintName.objects.filter(slug__in=slugs)
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/meeting/%s/json" % (self.number, )
|
|
||||||
|
|
||||||
def base_url(self):
|
def base_url(self):
|
||||||
return "/meeting/%s" % (self.number, )
|
return "/meeting/%s" % (self.number, )
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
# unfortunately, using the datetime aware json encoder seems impossible,
|
|
||||||
# so the dates are formatted as strings here.
|
|
||||||
agenda_url = ""
|
|
||||||
if self.schedule:
|
|
||||||
agenda_url = urljoin(host_scheme, self.schedule.base_url())
|
|
||||||
return {
|
|
||||||
'href': urljoin(host_scheme, self.json_url()),
|
|
||||||
'name': self.number,
|
|
||||||
'submission_start_date': fmt_date(self.get_submission_start_date()),
|
|
||||||
'submission_cut_off_date': fmt_date(self.get_submission_cut_off_date()),
|
|
||||||
'submission_correction_date': fmt_date(self.get_submission_correction_date()),
|
|
||||||
'date': fmt_date(self.date),
|
|
||||||
'agenda_href': agenda_url,
|
|
||||||
'city': self.city,
|
|
||||||
'country': self.country,
|
|
||||||
'time_zone': self.time_zone,
|
|
||||||
'venue_name': self.venue_name,
|
|
||||||
'venue_addr': self.venue_addr,
|
|
||||||
'break_area': self.break_area,
|
|
||||||
'reg_area': self.reg_area
|
|
||||||
}
|
|
||||||
|
|
||||||
def build_timeslices(self):
|
def build_timeslices(self):
|
||||||
"""Get unique day/time/timeslot data for meeting
|
"""Get unique day/time/timeslot data for meeting
|
||||||
|
|
||||||
|
@ -438,13 +408,6 @@ class ResourceAssociation(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.desc
|
return self.desc
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
res1 = dict()
|
|
||||||
res1['name'] = self.name.slug
|
|
||||||
res1['icon'] = "/images/%s" % (self.icon)
|
|
||||||
res1['desc'] = self.desc
|
|
||||||
res1['resource_id'] = self.pk
|
|
||||||
return res1
|
|
||||||
|
|
||||||
class Room(models.Model):
|
class Room(models.Model):
|
||||||
meeting = ForeignKey(Meeting)
|
meeting = ForeignKey(Meeting)
|
||||||
|
@ -487,15 +450,6 @@ class Room(models.Model):
|
||||||
def dom_id(self):
|
def dom_id(self):
|
||||||
return "room%u" % (self.pk)
|
return "room%u" % (self.pk)
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/meeting/%s/room/%s.json" % (self.meeting.number, self.id)
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
return {
|
|
||||||
'href': urljoin(host_scheme, self.json_url()),
|
|
||||||
'name': self.name,
|
|
||||||
'capacity': self.capacity,
|
|
||||||
}
|
|
||||||
# floorplan support
|
# floorplan support
|
||||||
def floorplan_url(self):
|
def floorplan_url(self):
|
||||||
mtg_num = self.meeting.get_number()
|
mtg_num = self.meeting.get_number()
|
||||||
|
@ -700,29 +654,8 @@ class TimeSlot(models.Model):
|
||||||
dom_id = self.location.dom_id()
|
dom_id = self.location.dom_id()
|
||||||
return "%s_%s_%s" % (dom_id, self.time.strftime('%Y-%m-%d'), self.time.strftime('%H%M'))
|
return "%s_%s_%s" % (dom_id, self.time.strftime('%Y-%m-%d'), self.time.strftime('%H%M'))
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
ts = dict()
|
|
||||||
ts['timeslot_id'] = self.id
|
|
||||||
ts['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
ts['room'] = self.get_location()
|
|
||||||
ts['roomtype'] = self.type.slug
|
|
||||||
if self.location is not None:
|
|
||||||
ts['capacity'] = self.location.capacity
|
|
||||||
ts["time"] = date_format(self.time, 'Hi')
|
|
||||||
ts["date"] = fmt_date(self.time)
|
|
||||||
ts["domid"] = self.js_identifier
|
|
||||||
following = self.slot_to_the_right
|
|
||||||
if following is not None:
|
|
||||||
ts["following_timeslot_id"] = following.id
|
|
||||||
return ts
|
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/meeting/%s/timeslot/%s.json" % (self.meeting.number, self.id)
|
|
||||||
|
|
||||||
"""
|
|
||||||
This routine deletes all timeslots which are in the same time as this slot.
|
|
||||||
"""
|
|
||||||
def delete_concurrent_timeslots(self):
|
def delete_concurrent_timeslots(self):
|
||||||
|
"""Delete all timeslots which are in the same time as this slot"""
|
||||||
# can not include duration in filter, because there is no support
|
# can not include duration in filter, because there is no support
|
||||||
# for having it a WHERE clause.
|
# for having it a WHERE clause.
|
||||||
# below will delete self as well.
|
# below will delete self as well.
|
||||||
|
@ -826,25 +759,6 @@ class Schedule(models.Model):
|
||||||
def delete_assignments(self):
|
def delete_assignments(self):
|
||||||
self.assignments.all().delete()
|
self.assignments.all().delete()
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "%s.json" % self.base_url()
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
sch = dict()
|
|
||||||
sch['schedule_id'] = self.id
|
|
||||||
sch['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
if self.visible:
|
|
||||||
sch['visible'] = "visible"
|
|
||||||
else:
|
|
||||||
sch['visible'] = "hidden"
|
|
||||||
if self.public:
|
|
||||||
sch['public'] = "public"
|
|
||||||
else:
|
|
||||||
sch['public'] = "private"
|
|
||||||
sch['owner'] = urljoin(host_scheme, self.owner.json_url())
|
|
||||||
# should include href to list of assignments, but they have no direct API yet.
|
|
||||||
return sch
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def qs_assignments_with_sessions(self):
|
def qs_assignments_with_sessions(self):
|
||||||
return self.assignments.filter(session__isnull=False)
|
return self.assignments.filter(session__isnull=False)
|
||||||
|
@ -908,40 +822,6 @@ class SchedTimeSessAssignment(models.Model):
|
||||||
"""Get the TimeSlotTypeName that applies to this assignment"""
|
"""Get the TimeSlotTypeName that applies to this assignment"""
|
||||||
return self.timeslot.type
|
return self.timeslot.type
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
if not hasattr(self, '_cached_json_url'):
|
|
||||||
self._cached_json_url = "/meeting/%s/agenda/%s/%s/session/%u.json" % (
|
|
||||||
self.schedule.meeting.number,
|
|
||||||
self.schedule.owner_email(),
|
|
||||||
self.schedule.name, self.id )
|
|
||||||
return self._cached_json_url
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
if not hasattr(self, '_cached_json_dict'):
|
|
||||||
ss = dict()
|
|
||||||
ss['assignment_id'] = self.id
|
|
||||||
ss['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
ss['timeslot_id'] = self.timeslot.id
|
|
||||||
|
|
||||||
efset = self.session.timeslotassignments.filter(schedule=self.schedule).order_by("timeslot__time")
|
|
||||||
if efset.count() > 1:
|
|
||||||
# now we know that there is some work to do finding the extendedfrom_id.
|
|
||||||
# loop through the list of items
|
|
||||||
previous = None
|
|
||||||
for efss in efset:
|
|
||||||
if efss.pk == self.pk:
|
|
||||||
extendedfrom = previous
|
|
||||||
break
|
|
||||||
previous = efss
|
|
||||||
if extendedfrom is not None:
|
|
||||||
ss['extendedfrom_id'] = extendedfrom.id
|
|
||||||
|
|
||||||
if self.session:
|
|
||||||
ss['session_id'] = self.session.id
|
|
||||||
ss["pinned"] = self.pinned
|
|
||||||
self._cached_json_dict = ss
|
|
||||||
return self._cached_json_dict
|
|
||||||
|
|
||||||
def slug(self):
|
def slug(self):
|
||||||
"""Return sensible id string for session, e.g. suitable for use as HTML anchor."""
|
"""Return sensible id string for session, e.g. suitable for use as HTML anchor."""
|
||||||
components = []
|
components = []
|
||||||
|
@ -1033,30 +913,6 @@ class Constraint(models.Model):
|
||||||
elif not self.target and self.person:
|
elif not self.target and self.person:
|
||||||
return "%s " % (self.person)
|
return "%s " % (self.person)
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/meeting/%s/constraint/%s.json" % (self.meeting.number, self.id)
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
ct1 = dict()
|
|
||||||
ct1['constraint_id'] = self.id
|
|
||||||
ct1['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
ct1['name'] = self.name.slug
|
|
||||||
if self.person is not None:
|
|
||||||
ct1['person_href'] = urljoin(host_scheme, self.person.json_url())
|
|
||||||
if self.source is not None:
|
|
||||||
ct1['source_href'] = urljoin(host_scheme, self.source.json_url())
|
|
||||||
if self.target is not None:
|
|
||||||
ct1['target_href'] = urljoin(host_scheme, self.target.json_url())
|
|
||||||
ct1['meeting_href'] = urljoin(host_scheme, self.meeting.json_url())
|
|
||||||
if self.time_relation:
|
|
||||||
ct1['time_relation'] = self.time_relation
|
|
||||||
ct1['time_relation_display'] = self.get_time_relation_display()
|
|
||||||
if self.timeranges.count():
|
|
||||||
ct1['timeranges_cant_meet'] = [t.slug for t in self.timeranges.all()]
|
|
||||||
timeranges_str = ", ".join([t.desc for t in self.timeranges.all()])
|
|
||||||
ct1['timeranges_display'] = "Can't meet %s" % timeranges_str
|
|
||||||
return ct1
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPresentation(models.Model):
|
class SessionPresentation(models.Model):
|
||||||
session = ForeignKey('Session')
|
session = ForeignKey('Session')
|
||||||
|
@ -1367,92 +1223,10 @@ class Session(models.Model):
|
||||||
def official_timeslotassignment(self):
|
def official_timeslotassignment(self):
|
||||||
return self.timeslotassignments.filter(schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting.schedule else None]).first()
|
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 = []
|
|
||||||
for constraint in self.constraints():
|
|
||||||
ct1 = constraint.json_dict(host_scheme)
|
|
||||||
constraint_list.append(ct1)
|
|
||||||
|
|
||||||
for constraint in self.reverse_constraints():
|
|
||||||
ct1 = constraint.json_dict(host_scheme)
|
|
||||||
constraint_list.append(ct1)
|
|
||||||
return constraint_list
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def people_constraints(self):
|
def people_constraints(self):
|
||||||
return self.group.constraint_source_set.filter(meeting=self.meeting, name='bethere')
|
return self.group.constraint_source_set.filter(meeting=self.meeting, name='bethere')
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/meeting/%s/session/%s.json" % (self.meeting.number, self.id)
|
|
||||||
|
|
||||||
def json_dict(self, host_scheme):
|
|
||||||
sess1 = dict()
|
|
||||||
sess1['href'] = urljoin(host_scheme, self.json_url())
|
|
||||||
if self.group is not None:
|
|
||||||
sess1['group'] = self.group.json_dict(host_scheme)
|
|
||||||
sess1['group_href'] = urljoin(host_scheme, self.group.json_url())
|
|
||||||
if self.group.parent is not None:
|
|
||||||
sess1['area'] = self.group.parent.acronym.upper()
|
|
||||||
sess1['description'] = self.group.name
|
|
||||||
sess1['group_id'] = str(self.group.pk)
|
|
||||||
reslist = []
|
|
||||||
for r in self.resources.all():
|
|
||||||
reslist.append(r.json_dict(host_scheme))
|
|
||||||
sess1['resources'] = reslist
|
|
||||||
sess1['session_id'] = str(self.pk)
|
|
||||||
sess1['name'] = self.name
|
|
||||||
sess1['title'] = self.short_name
|
|
||||||
sess1['short_name'] = self.short_name
|
|
||||||
sess1['bof'] = str(self.group.is_bof())
|
|
||||||
sess1['agenda_note'] = self.agenda_note
|
|
||||||
sess1['attendees'] = str(self.attendees)
|
|
||||||
sess1['joint_with_groups'] = self.joint_with_groups_acronyms()
|
|
||||||
|
|
||||||
# fish out scheduling information - eventually, we should pick
|
|
||||||
# this out in the caller instead
|
|
||||||
latest_event = None
|
|
||||||
first_event = None
|
|
||||||
|
|
||||||
if self.pk is not None:
|
|
||||||
if not hasattr(self, 'current_status') or not hasattr(self, 'requested_time'):
|
|
||||||
events = list(SchedulingEvent.objects.filter(session=self.pk).order_by('time', 'id'))
|
|
||||||
if events:
|
|
||||||
first_event = events[0]
|
|
||||||
latest_event = events[-1]
|
|
||||||
|
|
||||||
status_id = None
|
|
||||||
if hasattr(self, 'current_status'):
|
|
||||||
status_id = self.current_status
|
|
||||||
elif latest_event:
|
|
||||||
status_id = latest_event.status_id
|
|
||||||
|
|
||||||
sess1['status'] = SessionStatusName.objects.get(slug=status_id).name if status_id else None
|
|
||||||
if self.comments is not None:
|
|
||||||
sess1['comments'] = self.comments
|
|
||||||
|
|
||||||
requested_time = None
|
|
||||||
if hasattr(self, 'requested_time'):
|
|
||||||
requested_time = self.requested_time
|
|
||||||
elif first_event:
|
|
||||||
requested_time = first_event.time
|
|
||||||
sess1['requested_time'] = requested_time.strftime("%Y-%m-%d") if requested_time else None
|
|
||||||
|
|
||||||
|
|
||||||
requested_by = None
|
|
||||||
if hasattr(self, 'requested_by'):
|
|
||||||
requested_by = self.requested_by
|
|
||||||
elif first_event:
|
|
||||||
requested_by = first_event.by_id
|
|
||||||
|
|
||||||
if requested_by is not None:
|
|
||||||
requested_by_person = Person.objects.filter(pk=requested_by).first()
|
|
||||||
if requested_by_person:
|
|
||||||
sess1['requested_by'] = str(requested_by_person)
|
|
||||||
|
|
||||||
sess1['requested_duration']= "%.1f" % (float(self.requested_duration.seconds) / 3600)
|
|
||||||
sess1['special_request'] = str(self.special_request_token)
|
|
||||||
return sess1
|
|
||||||
|
|
||||||
def agenda_text(self):
|
def agenda_text(self):
|
||||||
doc = self.agenda()
|
doc = self.agenda()
|
||||||
if doc:
|
if doc:
|
||||||
|
|
|
@ -1,501 +0,0 @@
|
||||||
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from urllib.parse import urlsplit
|
|
||||||
|
|
||||||
from django.urls import reverse as urlreverse
|
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
|
||||||
|
|
||||||
from ietf.name.models import TimerangeName
|
|
||||||
from ietf.group.models import Group
|
|
||||||
from ietf.meeting.models import Schedule, TimeSlot, Session, SchedTimeSessAssignment, Meeting, Constraint
|
|
||||||
from ietf.meeting.test_data import make_meeting_test_data
|
|
||||||
from ietf.person.models import Person
|
|
||||||
from ietf.utils.test_utils import TestCase
|
|
||||||
from ietf.utils.mail import outbox
|
|
||||||
|
|
||||||
|
|
||||||
class ApiTests(TestCase):
|
|
||||||
def test_update_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
schedule = Schedule.objects.get(meeting__number=72,name="test-schedule")
|
|
||||||
mars_session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
|
||||||
ames_session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
|
|
||||||
|
|
||||||
mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-schedule')
|
|
||||||
mars_slot = mars_scheduled.timeslot
|
|
||||||
|
|
||||||
ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule')
|
|
||||||
ames_slot = ames_scheduled.timeslot
|
|
||||||
|
|
||||||
def do_unschedule(assignment):
|
|
||||||
url = urlreverse("ietf.meeting.ajax.assignment_json",
|
|
||||||
kwargs=dict(num=assignment.session.meeting.number,
|
|
||||||
owner=assignment.schedule.owner_email(),
|
|
||||||
name=assignment.schedule.name,
|
|
||||||
assignment_id=assignment.pk,))
|
|
||||||
return self.client.delete(url)
|
|
||||||
|
|
||||||
def do_schedule(schedule,session,timeslot):
|
|
||||||
url = urlreverse("ietf.meeting.ajax.assignments_json",
|
|
||||||
kwargs=dict(num=session.meeting.number,
|
|
||||||
owner=schedule.owner_email(),
|
|
||||||
name=schedule.name,))
|
|
||||||
post_data = '{ "session_id": "%s", "timeslot_id": "%s" }'%(session.pk,timeslot.pk)
|
|
||||||
return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded')
|
|
||||||
|
|
||||||
def do_extend(schedule, assignment):
|
|
||||||
session = assignment.session
|
|
||||||
url = urlreverse("ietf.meeting.ajax.assignments_json",
|
|
||||||
kwargs=dict(num=session.meeting.number,
|
|
||||||
owner=schedule.owner_email(),
|
|
||||||
name=schedule.name,))
|
|
||||||
post_data = '{ "session_id": "%s", "timeslot_id": "%s", "extendedfrom_id": "%s" }'%(session.pk,assignment.timeslot.slot_to_the_right.pk,assignment.pk)
|
|
||||||
|
|
||||||
|
|
||||||
return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded')
|
|
||||||
|
|
||||||
# not logged in
|
|
||||||
# faulty delete
|
|
||||||
r = do_unschedule(mars_scheduled)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
self.assertEqual(SchedTimeSessAssignment.objects.get(pk=mars_scheduled.pk).session, mars_session)
|
|
||||||
# faulty post
|
|
||||||
r = do_schedule(schedule,ames_session,mars_slot)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# logged in as non-owner
|
|
||||||
# faulty delete
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = do_unschedule(mars_scheduled)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
self.assertTrue("error" in r.json())
|
|
||||||
# faulty post
|
|
||||||
r = do_schedule(schedule,ames_session,mars_slot)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# Put ames in the same timeslot as mars
|
|
||||||
self.client.login(username="plain", password='plain+password')
|
|
||||||
r = do_unschedule(ames_scheduled)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
self.assertNotIn("error", r.json())
|
|
||||||
|
|
||||||
r = do_schedule(schedule,ames_session,mars_slot)
|
|
||||||
self.assertEqual(r.status_code, 201)
|
|
||||||
|
|
||||||
# Move the two timeslots close enough together for extension to work
|
|
||||||
ames_slot_qs=TimeSlot.objects.filter(id=ames_slot.id)
|
|
||||||
ames_slot_qs.update(time=mars_slot.time+mars_slot.duration+datetime.timedelta(minutes=10))
|
|
||||||
|
|
||||||
# Extend the mars session
|
|
||||||
r = do_extend(schedule,mars_scheduled)
|
|
||||||
self.assertEqual(r.status_code, 201)
|
|
||||||
self.assertTrue("error" not in r.json())
|
|
||||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),2)
|
|
||||||
|
|
||||||
# Unschedule mars
|
|
||||||
r = do_unschedule(mars_scheduled)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
self.assertNotIn("error", r.json())
|
|
||||||
# Make sure it got both the original and extended session
|
|
||||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),0)
|
|
||||||
|
|
||||||
self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule').timeslot, mars_slot)
|
|
||||||
|
|
||||||
|
|
||||||
def test_constraints_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").select_related("group").first()
|
|
||||||
c_ames = Constraint.objects.create(meeting=meeting, source=session.group,
|
|
||||||
target=Group.objects.get(acronym="ames"),
|
|
||||||
name_id="conflict")
|
|
||||||
|
|
||||||
c_person = Constraint.objects.create(meeting=meeting, source=session.group,
|
|
||||||
person=Person.objects.get(user__username="ad"),
|
|
||||||
name_id="bethere")
|
|
||||||
|
|
||||||
c_adjacent = Constraint.objects.create(meeting=meeting, source=session.group,
|
|
||||||
target=Group.objects.get(acronym="irg"),
|
|
||||||
name_id="wg_adjacent")
|
|
||||||
|
|
||||||
c_time_relation = Constraint.objects.create(meeting=meeting, source=session.group,
|
|
||||||
time_relation='subsequent-days',
|
|
||||||
name_id="time_relation")
|
|
||||||
|
|
||||||
c_timerange = Constraint.objects.create(meeting=meeting, source=session.group,
|
|
||||||
name_id="timerange")
|
|
||||||
c_timerange.timeranges.set(TimerangeName.objects.filter(slug__startswith='monday'))
|
|
||||||
|
|
||||||
r = self.client.get(urlreverse("ietf.meeting.ajax.session_constraints", kwargs=dict(num=meeting.number, sessionid=session.pk)))
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
constraints = r.json()
|
|
||||||
expected_keys = set([c_ames.pk, c_person.pk, c_adjacent.pk, c_time_relation.pk, c_timerange.pk])
|
|
||||||
self.assertEqual(expected_keys, set(c["constraint_id"] for c in constraints))
|
|
||||||
|
|
||||||
def test_meeting_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
r = self.client.get(urlreverse("ietf.meeting.ajax.meeting_json", kwargs=dict(num=meeting.number)))
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["name"], meeting.number)
|
|
||||||
|
|
||||||
def test_get_room_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
room = meeting.room_set.first()
|
|
||||||
|
|
||||||
r = self.client.get(urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk)))
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["name"], room.name)
|
|
||||||
|
|
||||||
def test_create_new_room(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
timeslots_before = meeting.timeslot_set.filter(type='regular').count()
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_roomsurl", kwargs=dict(num=meeting.number))
|
|
||||||
|
|
||||||
post_data = { "name": "new room", "capacity": "50" , "resources": [], "session_types":['regular']}
|
|
||||||
|
|
||||||
# unauthorized post
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
self.assertTrue(not meeting.room_set.filter(name="new room"))
|
|
||||||
|
|
||||||
# create room
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
self.assertTrue(meeting.room_set.filter(name="new room"))
|
|
||||||
|
|
||||||
timeslots_after = meeting.timeslot_set.filter(type='regular').count()
|
|
||||||
# It's not clear that what that ajax function is doing is the right thing to do,
|
|
||||||
# but it currently makes a new timeslot for any existing timeslot.
|
|
||||||
# The condition tested below relies on the timeslots before this test all having different start and end times
|
|
||||||
self.assertEqual( timeslots_after, 2 * timeslots_before)
|
|
||||||
|
|
||||||
def test_delete_room(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
room = meeting.room_set.first()
|
|
||||||
timeslots_before = list(room.timeslot_set.values_list("pk", flat=True))
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk))
|
|
||||||
|
|
||||||
# unauthorized delete
|
|
||||||
r = self.client.delete(url)
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
self.assertTrue(meeting.room_set.filter(pk=room.pk))
|
|
||||||
|
|
||||||
# delete
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.delete(url)
|
|
||||||
self.assertTrue(not meeting.room_set.filter(pk=room.pk))
|
|
||||||
self.assertTrue(not TimeSlot.objects.filter(pk__in=timeslots_before))
|
|
||||||
|
|
||||||
# This really belongs in group tests
|
|
||||||
def test_group_json(self):
|
|
||||||
make_meeting_test_data()
|
|
||||||
group = Group.objects.get(acronym="mars")
|
|
||||||
|
|
||||||
url = urlreverse("ietf.group.views.group_json", kwargs=dict(acronym=group.acronym))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["name"], group.name)
|
|
||||||
|
|
||||||
# This really belongs in person tests
|
|
||||||
def test_person_json(self):
|
|
||||||
make_meeting_test_data()
|
|
||||||
person = Person.objects.get(user__username="ad")
|
|
||||||
|
|
||||||
url = urlreverse("ietf.person.ajax.person_json", kwargs=dict(personid=person.pk))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["name"], person.name)
|
|
||||||
|
|
||||||
def test_sessions_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.sessions_json",kwargs=dict(num=meeting.number))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(set([x['short_name'] for x in info]),set([s.session.short_name for s in meeting.schedule.assignments.filter(session__type_id='regular')]))
|
|
||||||
|
|
||||||
schedule = meeting.schedule
|
|
||||||
url = urlreverse("ietf.meeting.ajax.assignments_json",
|
|
||||||
kwargs=dict(num=meeting.number,owner=schedule.owner_email(),name=schedule.name))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(len(info),schedule.assignments.count())
|
|
||||||
|
|
||||||
|
|
||||||
def test_slot_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
slot = meeting.timeslot_set.all()[0]
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
|
|
||||||
kwargs=dict(num=meeting.number, slotid=slot.pk))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["timeslot_id"], slot.pk)
|
|
||||||
|
|
||||||
def test_create_new_slot(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
slot_time = datetime.date.today()
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_slotsurl",
|
|
||||||
kwargs=dict(num=meeting.number))
|
|
||||||
post_data = {
|
|
||||||
'type' : 'plenary',
|
|
||||||
'time' : slot_time.strftime("%Y-%m-%d"),
|
|
||||||
'duration': '08:00:00',
|
|
||||||
}
|
|
||||||
|
|
||||||
# unauthorized post
|
|
||||||
prior_slotcount = meeting.timeslot_set.count()
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
self.assertEqual(meeting.timeslot_set.count(),prior_slotcount)
|
|
||||||
|
|
||||||
# create slot
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 201)
|
|
||||||
self.assertTrue(meeting.timeslot_set.filter(time=slot_time))
|
|
||||||
self.assertEqual(meeting.timeslot_set.count(),prior_slotcount+1)
|
|
||||||
|
|
||||||
def test_delete_slot(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
slot = meeting.timeslot_set.all()[0]
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
|
|
||||||
kwargs=dict(num=meeting.number, slotid=slot.pk))
|
|
||||||
|
|
||||||
# unauthorized delete
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = self.client.delete(url)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# delete
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
self.client.delete(url)
|
|
||||||
self.assertTrue(not meeting.timeslot_set.filter(pk=slot.pk))
|
|
||||||
|
|
||||||
def test_schedule_json(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
|
||||||
kwargs=dict(num=meeting.number,
|
|
||||||
owner=meeting.schedule.owner_email(),
|
|
||||||
name=meeting.schedule.name))
|
|
||||||
|
|
||||||
r = self.client.get(url)
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info["schedule_id"], meeting.schedule.pk)
|
|
||||||
|
|
||||||
def test_create_new_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.schedule_infosurl",
|
|
||||||
kwargs=dict(num=meeting.number))
|
|
||||||
post_data = {
|
|
||||||
'name': 'new-schedule',
|
|
||||||
}
|
|
||||||
|
|
||||||
# unauthorized post
|
|
||||||
self.client.login(username="plain", password="plain+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
self.assertTrue(not meeting.schedule_set.filter(name='new-schedule'))
|
|
||||||
|
|
||||||
# create new schedule
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
self.assertTrue(meeting.schedule_set.filter(name='new-schedule'))
|
|
||||||
|
|
||||||
def test_update_meeting_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
self.assertTrue(meeting.schedule.visible)
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
|
||||||
kwargs=dict(num=meeting.number,
|
|
||||||
owner=meeting.schedule.owner_email(),
|
|
||||||
name=meeting.schedule.name))
|
|
||||||
|
|
||||||
post_data = {
|
|
||||||
'visible': 'false',
|
|
||||||
'name': 'new-test-name',
|
|
||||||
}
|
|
||||||
|
|
||||||
# unauthorized posts
|
|
||||||
self.client.logout()
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# change schedule
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
changed_schedule = Schedule.objects.get(pk=meeting.schedule.pk)
|
|
||||||
self.assertTrue(not changed_schedule.visible)
|
|
||||||
self.assertEqual(changed_schedule.name, "new-test-name")
|
|
||||||
|
|
||||||
def test_delete_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
|
||||||
kwargs=dict(num=meeting.number,
|
|
||||||
owner=meeting.schedule.owner_email(),
|
|
||||||
name=meeting.schedule.name))
|
|
||||||
# unauthorized delete
|
|
||||||
self.client.login(username="plain", password="plain+password")
|
|
||||||
r = self.client.delete(url)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# delete
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.delete(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
self.assertTrue(not Schedule.objects.filter(pk=meeting.schedule.pk))
|
|
||||||
|
|
||||||
def test_set_meeting_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
schedule = meeting.schedule
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.meeting_json",
|
|
||||||
kwargs=dict(num=meeting.number))
|
|
||||||
post_data = {
|
|
||||||
"schedule": "",
|
|
||||||
}
|
|
||||||
# unauthorized post
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
# clear
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)
|
|
||||||
|
|
||||||
# set schedule - first fail with non-public
|
|
||||||
post_data = {
|
|
||||||
"schedule": schedule.name,
|
|
||||||
}
|
|
||||||
schedule.public = False
|
|
||||||
schedule.save()
|
|
||||||
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertTrue(r.status_code != 200)
|
|
||||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)
|
|
||||||
|
|
||||||
# then go through with public
|
|
||||||
schedule.public = True
|
|
||||||
schedule.save()
|
|
||||||
|
|
||||||
# Setting a meeting as official no longer sends mail immediately
|
|
||||||
prior_length= len(outbox)
|
|
||||||
r = self.client.post(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
self.assertEqual(Meeting.objects.get(pk=meeting.pk).schedule, schedule)
|
|
||||||
self.assertEqual(len(outbox),prior_length)
|
|
||||||
|
|
||||||
def test_read_only(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
# Secretariat
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info['secretariat'], True)
|
|
||||||
self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.schedule.owner_id)
|
|
||||||
self.assertEqual(info['read_only'], True)
|
|
||||||
self.assertEqual(info['save_perm'], True)
|
|
||||||
|
|
||||||
# owner
|
|
||||||
self.client.login(username=meeting.schedule.owner.user.username,
|
|
||||||
password=meeting.schedule.owner.user.username+"+password")
|
|
||||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
info = r.json()
|
|
||||||
self.assertEqual(info['secretariat'], False)
|
|
||||||
self.assertEqual(info['read_only'], False)
|
|
||||||
self.assertEqual(info['save_perm'], False)
|
|
||||||
|
|
||||||
def test_update_timeslot_pinned(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
scheduled = SchedTimeSessAssignment.objects.filter(
|
|
||||||
session__meeting=meeting, session__group__acronym="mars").first()
|
|
||||||
|
|
||||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)
|
|
||||||
|
|
||||||
post_data = {
|
|
||||||
"pinned": True
|
|
||||||
}
|
|
||||||
|
|
||||||
# unauthorized post gets failure (no redirect)
|
|
||||||
r = self.client.put(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 403,
|
|
||||||
"post to %s should have failed, no permission, got: %u/%s" %
|
|
||||||
(url, r.status_code, r.content))
|
|
||||||
self.assertTrue(not SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned)
|
|
||||||
|
|
||||||
# set pinned
|
|
||||||
meeting.schedule.owner = Person.objects.get(user__username="secretary")
|
|
||||||
meeting.schedule.save()
|
|
||||||
|
|
||||||
# need to rebuild URL, since the schedule owner has changed.
|
|
||||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)
|
|
||||||
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.put(url, post_data)
|
|
||||||
self.assertEqual(r.status_code, 200,
|
|
||||||
"post to %s should have worked, but got: %u/%s" %
|
|
||||||
(url, r.status_code, r.content))
|
|
||||||
self.assertTrue(SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned)
|
|
||||||
|
|
||||||
class TimeSlotEditingApiTests(TestCase):
|
|
||||||
|
|
||||||
def test_manipulate_timeslot(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
slot = meeting.timeslot_set.filter(type_id='regular')[0]
|
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
|
|
||||||
kwargs=dict(num=meeting.number, slotid=slot.pk))
|
|
||||||
|
|
||||||
modify_post_data = {
|
|
||||||
"purpose" : "plenary"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fail as non-secretariat
|
|
||||||
self.client.login(username="plain", password="plain+password")
|
|
||||||
r = self.client.post(url, modify_post_data)
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
slot.refresh_from_db()
|
|
||||||
self.assertEqual(slot.type_id, 'regular')
|
|
||||||
|
|
||||||
# Successful change of purpose
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.post(url, modify_post_data)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
slot.refresh_from_db()
|
|
||||||
self.assertEqual(slot.type_id, 'plenary')
|
|
|
@ -854,7 +854,7 @@ class ScheduleEditTests(IetfSeleniumTestCase):
|
||||||
ss = list(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule')) # pyflakes:ignore
|
ss = list(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule')) # pyflakes:ignore
|
||||||
|
|
||||||
self.login()
|
self.login()
|
||||||
url = self.absreverse('ietf.meeting.views.edit_schedule',kwargs=dict(num='72',name='test-schedule',owner='plain@example.com'))
|
url = self.absreverse('ietf.meeting.views.edit_meeting_schedule',kwargs=dict(num='72',name='test-schedule',owner='plain@example.com'))
|
||||||
self.driver.get(url)
|
self.driver.get(url)
|
||||||
|
|
||||||
# driver.get() will wait for scripts to finish, but not ajax
|
# driver.get() will wait for scripts to finish, but not ajax
|
||||||
|
@ -2770,5 +2770,5 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
|
||||||
# make_meeting_test_data()
|
# make_meeting_test_data()
|
||||||
#
|
#
|
||||||
# def testOpenSchedule(self):
|
# def testOpenSchedule(self):
|
||||||
# url = urlreverse('ietf.meeting.views.edit_schedule', kwargs=dict(num='72',name='test-schedule'))
|
# url = urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs=dict(num='72',name='test-schedule'))
|
||||||
# r = self.client.get(url)
|
# r = self.client.get(url)
|
||||||
|
|
|
@ -3053,6 +3053,7 @@ class ReorderSlidesTests(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class EditTests(TestCase):
|
class EditTests(TestCase):
|
||||||
|
"""Test schedule edit operations"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# make sure we have the colors of the area
|
# make sure we have the colors of the area
|
||||||
from ietf.group.colors import fg_group_colors, bg_group_colors
|
from ietf.group.colors import fg_group_colors, bg_group_colors
|
||||||
|
@ -3060,17 +3061,6 @@ class EditTests(TestCase):
|
||||||
fg_group_colors[area_upper] = "#333"
|
fg_group_colors[area_upper] = "#333"
|
||||||
bg_group_colors[area_upper] = "#aaa"
|
bg_group_colors[area_upper] = "#aaa"
|
||||||
|
|
||||||
def test_edit_schedule(self):
|
|
||||||
meeting = make_meeting_test_data()
|
|
||||||
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
|
||||||
r = self.client.get(urlreverse("ietf.meeting.views.edit_schedule", kwargs={'num': meeting.number}))
|
|
||||||
self.assertRedirects(
|
|
||||||
r,
|
|
||||||
urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs={'num': meeting.number}),
|
|
||||||
status_code=301,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_official_record_schedule_is_read_only(self):
|
def test_official_record_schedule_is_read_only(self):
|
||||||
def _set_date_offset_and_retrieve_page(meeting, days_offset, client):
|
def _set_date_offset_and_retrieve_page(meeting, days_offset, client):
|
||||||
meeting.date = datetime.date.today() + datetime.timedelta(days=days_offset)
|
meeting.date = datetime.date.today() + datetime.timedelta(days=days_offset)
|
||||||
|
@ -3598,8 +3588,8 @@ class EditTests(TestCase):
|
||||||
self.assertEqual(tostring(s2_constraints[1][0]), conf_label) # [0][0] is the innermost <span>
|
self.assertEqual(tostring(s2_constraints[1][0]), conf_label) # [0][0] is the innermost <span>
|
||||||
|
|
||||||
def test_new_meeting_schedule(self):
|
def test_new_meeting_schedule(self):
|
||||||
|
"""Can create a meeting schedule from scratch"""
|
||||||
meeting = make_meeting_test_data()
|
meeting = make_meeting_test_data()
|
||||||
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
self.client.login(username="secretary", password="secretary+password")
|
||||||
|
|
||||||
# new from scratch
|
# new from scratch
|
||||||
|
@ -3623,7 +3613,11 @@ class EditTests(TestCase):
|
||||||
self.assertEqual(new_schedule.origin, None)
|
self.assertEqual(new_schedule.origin, None)
|
||||||
self.assertEqual(new_schedule.base_id, meeting.schedule.base_id)
|
self.assertEqual(new_schedule.base_id, meeting.schedule.base_id)
|
||||||
|
|
||||||
# copy
|
def test_copy_meeting_schedule(self):
|
||||||
|
"""Can create a copy of an existing meeting schedule"""
|
||||||
|
meeting = make_meeting_test_data()
|
||||||
|
self.client.login(username="secretary", password="secretary+password")
|
||||||
|
|
||||||
url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name))
|
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)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
@ -3647,34 +3641,21 @@ class EditTests(TestCase):
|
||||||
for a in SchedTimeSessAssignment.objects.filter(schedule=new_schedule):
|
for a in SchedTimeSessAssignment.objects.filter(schedule=new_schedule):
|
||||||
self.assertIn((a.session_id, a.timeslot_id), old_assignments)
|
self.assertIn((a.session_id, a.timeslot_id), old_assignments)
|
||||||
|
|
||||||
def test_save_agenda_as_and_read_permissions(self):
|
def test_schedule_read_permissions(self):
|
||||||
meeting = make_meeting_test_data()
|
meeting = make_meeting_test_data()
|
||||||
|
schedule = meeting.schedule
|
||||||
|
|
||||||
# try to get non-existing agenda
|
# try to get non-existing agenda
|
||||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number,
|
||||||
owner=meeting.schedule.owner_email(),
|
owner=schedule.owner_email(),
|
||||||
name="foo"))
|
name="foo"))
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
# save as new name (requires valid existing agenda)
|
url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number,
|
||||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
owner=schedule.owner_email(),
|
||||||
owner=meeting.schedule.owner_email(),
|
name=schedule.name))
|
||||||
name=meeting.schedule.name))
|
self.client.login(username='ad', password='ad+password')
|
||||||
self.client.login(username="ad", password="ad+password")
|
|
||||||
r = self.client.post(url, {
|
|
||||||
'savename': "foo",
|
|
||||||
'saveas': "saveas",
|
|
||||||
})
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
# Verify that we actually got redirected to a new place.
|
|
||||||
self.assertNotEqual(urlparse(r.url).path, url)
|
|
||||||
|
|
||||||
# get
|
|
||||||
schedule = meeting.get_schedule_by_name("foo")
|
|
||||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
|
||||||
owner=schedule.owner_email(),
|
|
||||||
name="foo"))
|
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
@ -3701,39 +3682,73 @@ class EditTests(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
def test_save_agenda_broken_names(self):
|
def test_new_meeting_schedule_rejects_invalid_names(self):
|
||||||
meeting = make_meeting_test_data()
|
meeting = make_meeting_test_data()
|
||||||
|
|
||||||
# save as new name (requires valid existing agenda)
|
orig_schedule_count = meeting.schedule_set.count()
|
||||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
|
||||||
owner=meeting.schedule.owner_email(),
|
|
||||||
name=meeting.schedule.name))
|
|
||||||
self.client.login(username="ad", password="ad+password")
|
self.client.login(username="ad", password="ad+password")
|
||||||
|
url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number))
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
'savename': "/no/this/should/not/work/it/is/too/long",
|
'name': "/no/this/should/not/work/it/is/too/long",
|
||||||
'saveas': "saveas",
|
'public': "on",
|
||||||
})
|
'notes': "Name too long",
|
||||||
self.assertEqual(r.status_code, 302)
|
'base': meeting.schedule.base_id,
|
||||||
self.assertEqual(urlparse(r.url).path, url)
|
})
|
||||||
# TODO: Verify that an error message was in fact returned.
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFormError(r, 'form', 'name', 'Enter a valid value.')
|
||||||
|
self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created')
|
||||||
|
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
'savename': "/invalid/chars/",
|
'name': "/invalid/chars/",
|
||||||
'saveas': "saveas",
|
'public': "on",
|
||||||
})
|
'notes': "Name too long",
|
||||||
# TODO: Verify that an error message was in fact returned.
|
'base': meeting.schedule.base_id,
|
||||||
self.assertEqual(r.status_code, 302)
|
})
|
||||||
self.assertEqual(urlparse(r.url).path, url)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFormError(r, 'form', 'name', 'Enter a valid value.')
|
||||||
|
self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created')
|
||||||
|
|
||||||
# Non-ASCII alphanumeric characters
|
# Non-ASCII alphanumeric characters
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
'savename': "f\u00E9ling",
|
'name': "f\u00E9ling",
|
||||||
'saveas': "saveas",
|
'public': "on",
|
||||||
})
|
'notes': "Name too long",
|
||||||
# TODO: Verify that an error message was in fact returned.
|
'base': meeting.schedule.base_id,
|
||||||
self.assertEqual(r.status_code, 302)
|
})
|
||||||
self.assertEqual(urlparse(r.url).path, url)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFormError(r, 'form', 'name', 'Enter a valid value.')
|
||||||
|
self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created')
|
||||||
|
|
||||||
|
def test_edit_session(self):
|
||||||
|
session = SessionFactory(group__type_id='team') # type determines allowed session purposes
|
||||||
|
self.client.login(username='secretary', password='secretary+password')
|
||||||
|
url = urlreverse('ietf.meeting.views.edit_session', kwargs={'session_id': session.pk})
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertContains(r, 'Edit session', status_code=200)
|
||||||
|
r = self.client.post(url, {
|
||||||
|
'name': 'this is a name',
|
||||||
|
'short': 'tian',
|
||||||
|
'purpose': 'coding',
|
||||||
|
'type': 'other',
|
||||||
|
'requested_duration': '3600',
|
||||||
|
'on_agenda': True,
|
||||||
|
'remote_instructions': 'Do this do that',
|
||||||
|
'attendees': '103',
|
||||||
|
'comments': 'So much to say',
|
||||||
|
})
|
||||||
|
self.assertNoFormPostErrors(r)
|
||||||
|
self.assertRedirects(r, urlreverse('ietf.meeting.views.edit_meeting_schedule',
|
||||||
|
kwargs={'num': session.meeting.number}))
|
||||||
|
session = Session.objects.get(pk=session.pk) # refresh objects from DB
|
||||||
|
self.assertEqual(session.name, 'this is a name')
|
||||||
|
self.assertEqual(session.short, 'tian')
|
||||||
|
self.assertEqual(session.purpose_id, 'coding')
|
||||||
|
self.assertEqual(session.type_id, 'other')
|
||||||
|
self.assertEqual(session.requested_duration, datetime.timedelta(hours=1))
|
||||||
|
self.assertEqual(session.on_agenda, True)
|
||||||
|
self.assertEqual(session.remote_instructions, 'Do this do that')
|
||||||
|
self.assertEqual(session.attendees, 103)
|
||||||
|
self.assertEqual(session.comments, 'So much to say')
|
||||||
|
|
||||||
def test_edit_timeslots(self):
|
def test_edit_timeslots(self):
|
||||||
meeting = make_meeting_test_data()
|
meeting = make_meeting_test_data()
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.conf.urls import include
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from ietf.meeting import views, ajax, views_proceedings
|
from ietf.meeting import views, views_proceedings
|
||||||
from ietf.utils.urls import url
|
from ietf.utils.urls import url
|
||||||
|
|
||||||
safe_for_all_meeting_types = [
|
safe_for_all_meeting_types = [
|
||||||
|
@ -26,10 +26,7 @@ safe_for_all_meeting_types = [
|
||||||
|
|
||||||
|
|
||||||
type_ietf_only_patterns = [
|
type_ietf_only_patterns = [
|
||||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit$' % settings.URL_REGEXPS,
|
url(r'^agenda/%(owner)s/%(schedule_name)s/edit/?$' % settings.URL_REGEXPS, views.edit_meeting_schedule),
|
||||||
RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True),
|
|
||||||
name='ietf.meeting.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/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/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/delete$' % settings.URL_REGEXPS, views.delete_schedule),
|
||||||
|
@ -40,10 +37,6 @@ type_ietf_only_patterns = [
|
||||||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-room/?$' % settings.URL_REGEXPS, views.agenda_by_room),
|
url(r'^agenda/%(owner)s/%(schedule_name)s/by-room/?$' % settings.URL_REGEXPS, views.agenda_by_room),
|
||||||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/?$' % settings.URL_REGEXPS, views.agenda_by_type),
|
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/?$' % settings.URL_REGEXPS, views.agenda_by_type),
|
||||||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/(?P<type>[a-z]+)$' % settings.URL_REGEXPS, views.agenda_by_type),
|
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/(?P<type>[a-z]+)$' % settings.URL_REGEXPS, views.agenda_by_type),
|
||||||
url(r'^agenda/%(owner)s/%(schedule_name)s/permissions$' % settings.URL_REGEXPS, ajax.schedule_permission_api),
|
|
||||||
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/new/$' % settings.URL_REGEXPS, views.new_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-room$', views.agenda_by_room),
|
||||||
url(r'^agenda/by-type$', views.agenda_by_type),
|
url(r'^agenda/by-type$', views.agenda_by_type),
|
||||||
|
@ -58,20 +51,8 @@ type_ietf_only_patterns = [
|
||||||
url(r'^timeslot/new$', views.create_timeslot),
|
url(r'^timeslot/new$', views.create_timeslot),
|
||||||
url(r'^timeslot/(?P<slot_id>\d+)/edit$', views.edit_timeslot),
|
url(r'^timeslot/(?P<slot_id>\d+)/edit$', views.edit_timeslot),
|
||||||
url(r'^timeslot/(?P<slot_id>\d+)/edittype$', views.edit_timeslot_type),
|
url(r'^timeslot/(?P<slot_id>\d+)/edittype$', views.edit_timeslot_type),
|
||||||
url(r'^rooms$', ajax.timeslot_roomsurl),
|
|
||||||
url(r'^room/(?P<roomid>\d+).json$', ajax.timeslot_roomurl),
|
|
||||||
url(r'^timeslots$', ajax.timeslot_slotsurl),
|
|
||||||
url(r'^timeslots.json$', ajax.timeslot_slotsurl),
|
|
||||||
url(r'^timeslot/(?P<slotid>\d+).json$', ajax.timeslot_sloturl),
|
|
||||||
url(r'^agendas$', ajax.schedule_infosurl),
|
|
||||||
url(r'^agendas.json$', ajax.schedule_infosurl),
|
|
||||||
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.pdf$', views.session_draft_pdf),
|
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.pdf$', views.session_draft_pdf),
|
||||||
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.tgz$', views.session_draft_tarfile),
|
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.tgz$', views.session_draft_tarfile),
|
||||||
url(r'^sessions\.json$', ajax.sessions_json),
|
|
||||||
url(r'^session/(?P<sessionid>\d+).json', ajax.session_json),
|
|
||||||
url(r'^session/(?P<sessionid>\d+)/constraints.json', ajax.session_constraints),
|
|
||||||
url(r'^constraint/(?P<constraintid>\d+).json', ajax.constraint_json),
|
|
||||||
url(r'^json$', ajax.meeting_json),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# This is a limited subset of the list above -- many of the views above won't work for interim meetings
|
# This is a limited subset of the list above -- many of the views above won't work for interim meetings
|
||||||
|
@ -88,7 +69,7 @@ type_ietf_only_patterns_id_optional = [
|
||||||
url(r'^agenda(?P<ext>.csv)$', views.agenda),
|
url(r'^agenda(?P<ext>.csv)$', views.agenda),
|
||||||
url(r'^agenda/edit$',
|
url(r'^agenda/edit$',
|
||||||
RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True),
|
RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True),
|
||||||
name='ietf.meeting.views.edit_schedule'),
|
name='ietf.meeting.views.edit_meeting_schedule'),
|
||||||
url(r'^agenda/edit/$', views.edit_meeting_schedule),
|
url(r'^agenda/edit/$', views.edit_meeting_schedule),
|
||||||
url(r'^requests$', views.meeting_requests),
|
url(r'^requests$', views.meeting_requests),
|
||||||
url(r'^agenda/agenda\.ics$', views.agenda_ical),
|
url(r'^agenda/agenda\.ics$', views.agenda_ical),
|
||||||
|
|
|
@ -59,11 +59,7 @@ from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPr
|
||||||
from ietf.meeting.models import SessionStatusName, SchedulingEvent, SchedTimeSessAssignment, Room, TimeSlotTypeName
|
from ietf.meeting.models import SessionStatusName, SchedulingEvent, SchedTimeSessAssignment, Room, TimeSlotTypeName
|
||||||
from ietf.meeting.forms import ( CustomDurationField, SwapDaysForm, SwapTimeslotsForm,
|
from ietf.meeting.forms import ( CustomDurationField, SwapDaysForm, SwapTimeslotsForm,
|
||||||
TimeSlotCreateForm, TimeSlotEditForm, SessionEditForm )
|
TimeSlotCreateForm, TimeSlotEditForm, SessionEditForm )
|
||||||
from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name
|
from ietf.meeting.helpers import 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
|
|
||||||
from ietf.meeting.helpers import get_modified_from_assignments
|
|
||||||
from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting
|
|
||||||
from ietf.meeting.helpers import get_meeting, get_ietf_meeting, get_current_ietf_meeting_num
|
from ietf.meeting.helpers import get_meeting, get_ietf_meeting, get_current_ietf_meeting_num
|
||||||
from ietf.meeting.helpers import get_schedule, schedule_permissions
|
from ietf.meeting.helpers import get_schedule, schedule_permissions
|
||||||
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
|
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
|
||||||
|
@ -280,86 +276,6 @@ def materials_editable_groups(request, num=None):
|
||||||
return render(request, "meeting/materials_editable_groups.html", {
|
return render(request, "meeting/materials_editable_groups.html", {
|
||||||
'meeting_num': meeting.number})
|
'meeting_num': meeting.number})
|
||||||
|
|
||||||
def ascii_alphanumeric(string):
|
|
||||||
return re.match(r'^[a-zA-Z0-9]*$', string)
|
|
||||||
|
|
||||||
class SaveAsForm(forms.Form):
|
|
||||||
savename = forms.CharField(max_length=16)
|
|
||||||
|
|
||||||
@role_required('Area Director','Secretariat')
|
|
||||||
def schedule_create(request, num=None, owner=None, name=None):
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
person = get_person_by_email(owner)
|
|
||||||
schedule = get_schedule_by_name(meeting, person, name)
|
|
||||||
|
|
||||||
if schedule is None:
|
|
||||||
# here we have to return some ajax to display an error.
|
|
||||||
messages.error("Error: No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name)) # pylint: disable=no-value-for-parameter
|
|
||||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
|
||||||
|
|
||||||
# authorization was enforced by the @group_require decorator above.
|
|
||||||
|
|
||||||
saveasform = SaveAsForm(request.POST)
|
|
||||||
if not saveasform.is_valid():
|
|
||||||
messages.info(request, "This name is not valid. Please choose another one.")
|
|
||||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
|
||||||
|
|
||||||
savedname = saveasform.cleaned_data['savename']
|
|
||||||
|
|
||||||
if not ascii_alphanumeric(savedname):
|
|
||||||
messages.info(request, "This name contains illegal characters. Please choose another one.")
|
|
||||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
|
||||||
|
|
||||||
# create the new schedule, and copy the assignments
|
|
||||||
try:
|
|
||||||
sched = meeting.schedule_set.get(name=savedname, owner=request.user.person)
|
|
||||||
if sched:
|
|
||||||
return redirect(edit_schedule, num=meeting.number, owner=sched.owner_email(), name=sched.name)
|
|
||||||
else:
|
|
||||||
messages.info(request, "Schedule creation failed. Please try again.")
|
|
||||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
|
||||||
|
|
||||||
except Schedule.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# must be done
|
|
||||||
newschedule = Schedule(name=savedname,
|
|
||||||
owner=request.user.person,
|
|
||||||
meeting=meeting,
|
|
||||||
base=schedule.base,
|
|
||||||
origin=schedule,
|
|
||||||
visible=False,
|
|
||||||
public=False)
|
|
||||||
|
|
||||||
newschedule.save()
|
|
||||||
if newschedule is None:
|
|
||||||
return HttpResponse(status=500)
|
|
||||||
|
|
||||||
# keep a mapping so that extendedfrom references can be chased.
|
|
||||||
mapping = {};
|
|
||||||
for ss in schedule.assignments.all():
|
|
||||||
# hack to copy the object, creating a new one
|
|
||||||
# just reset the key, and save it again.
|
|
||||||
oldid = ss.pk
|
|
||||||
ss.pk = None
|
|
||||||
ss.schedule=newschedule
|
|
||||||
ss.save()
|
|
||||||
mapping[oldid] = ss.pk
|
|
||||||
#print "Copying %u to %u" % (oldid, ss.pk)
|
|
||||||
|
|
||||||
# now fix up any extendedfrom references to new set.
|
|
||||||
for ss in newschedule.assignments.all():
|
|
||||||
if ss.extendedfrom is not None:
|
|
||||||
oldid = ss.extendedfrom.id
|
|
||||||
newid = mapping[oldid]
|
|
||||||
#print "Fixing %u to %u" % (oldid, newid)
|
|
||||||
ss.extendedfrom = newschedule.assignments.get(pk = newid)
|
|
||||||
ss.save()
|
|
||||||
|
|
||||||
|
|
||||||
# now redirect to this new schedule.
|
|
||||||
return redirect(edit_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
|
||||||
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
@role_required('Secretariat')
|
||||||
def edit_timeslots(request, num=None):
|
def edit_timeslots(request, num=None):
|
||||||
|
@ -1397,80 +1313,6 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#@role_required('Area Director','Secretariat')
|
|
||||||
# disable the above security for now, check it below.
|
|
||||||
@ensure_csrf_cookie
|
|
||||||
def edit_schedule(request, num=None, owner=None, name=None):
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
return schedule_create(request, num, owner, name)
|
|
||||||
|
|
||||||
user = request.user
|
|
||||||
meeting = get_meeting(num)
|
|
||||||
person = get_person_by_email(owner)
|
|
||||||
if name is None:
|
|
||||||
schedule = meeting.schedule
|
|
||||||
else:
|
|
||||||
schedule = get_schedule_by_name(meeting, person, name)
|
|
||||||
if schedule is None:
|
|
||||||
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
|
|
||||||
|
|
||||||
meeting_base_url = request.build_absolute_uri(meeting.base_url())
|
|
||||||
site_base_url = request.build_absolute_uri('/')[:-1] # skip the trailing slash
|
|
||||||
|
|
||||||
rooms = meeting.room_set.filter(session_types__slug='regular').distinct().order_by("capacity")
|
|
||||||
saveas = SaveAsForm()
|
|
||||||
saveasurl=reverse(edit_schedule,
|
|
||||||
args=[meeting.number, schedule.owner_email(), schedule.name])
|
|
||||||
|
|
||||||
can_see, can_edit,secretariat = schedule_permissions(meeting, schedule, user)
|
|
||||||
|
|
||||||
if not can_see:
|
|
||||||
return render(request, "meeting/private_schedule.html",
|
|
||||||
{"schedule":schedule,
|
|
||||||
"meeting": meeting,
|
|
||||||
"meeting_base_url":meeting_base_url,
|
|
||||||
"hide_menu": True
|
|
||||||
}, status=403, content_type="text/html")
|
|
||||||
|
|
||||||
assignments = get_all_assignments_from_schedule(schedule)
|
|
||||||
|
|
||||||
# get_modified_from needs the query set, not the list
|
|
||||||
modified = get_modified_from_assignments(assignments)
|
|
||||||
|
|
||||||
area_list = get_areas()
|
|
||||||
wg_name_list = get_wg_name_list(assignments)
|
|
||||||
wg_list = get_wg_list(wg_name_list)
|
|
||||||
ads = find_ads_for_meeting(meeting)
|
|
||||||
for ad in ads:
|
|
||||||
# set the default to avoid needing extra arguments in templates
|
|
||||||
# django 1.3+
|
|
||||||
ad.default_hostscheme = site_base_url
|
|
||||||
|
|
||||||
time_slices,date_slices = build_all_agenda_slices(meeting)
|
|
||||||
|
|
||||||
return render(request, "meeting/landscape_edit.html",
|
|
||||||
{"schedule":schedule,
|
|
||||||
"saveas": saveas,
|
|
||||||
"saveasurl": saveasurl,
|
|
||||||
"meeting_base_url": meeting_base_url,
|
|
||||||
"site_base_url": site_base_url,
|
|
||||||
"rooms":rooms,
|
|
||||||
"time_slices":time_slices,
|
|
||||||
"date_slices":date_slices,
|
|
||||||
"modified": modified,
|
|
||||||
"meeting":meeting,
|
|
||||||
"area_list": area_list,
|
|
||||||
"area_directors" : ads,
|
|
||||||
"wg_list": wg_list ,
|
|
||||||
"assignments": assignments,
|
|
||||||
"show_inline": set(["txt","htm","html"]),
|
|
||||||
"hide_menu": True,
|
|
||||||
"can_edit_properties": can_edit or secretariat,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class SchedulePropertiesForm(forms.ModelForm):
|
class SchedulePropertiesForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Schedule
|
model = Schedule
|
||||||
|
@ -1504,7 +1346,7 @@ def edit_schedule_properties(request, num, owner, name):
|
||||||
form.save()
|
form.save()
|
||||||
if request.GET.get('next'):
|
if request.GET.get('next'):
|
||||||
return HttpResponseRedirect(request.GET.get('next'))
|
return HttpResponseRedirect(request.GET.get('next'))
|
||||||
return redirect('ietf.meeting.views.edit_schedule', num=num, owner=owner, name=name)
|
return redirect('ietf.meeting.views.edit_meeting_schedule', num=num, owner=owner, name=name)
|
||||||
else:
|
else:
|
||||||
form = SchedulePropertiesForm(meeting, instance=schedule)
|
form = SchedulePropertiesForm(meeting, instance=schedule)
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,6 @@ from django.http import HttpResponse
|
||||||
from ietf.ietfauth.utils import role_required
|
from ietf.ietfauth.utils import role_required
|
||||||
from ietf.person.models import Person
|
from ietf.person.models import Person
|
||||||
|
|
||||||
def person_json(request, personid):
|
|
||||||
person = get_object_or_404(Person, pk=personid)
|
|
||||||
|
|
||||||
return HttpResponse(json.dumps(person.json_dict(request.build_absolute_uri("/")),
|
|
||||||
sort_keys=True, indent=2),
|
|
||||||
content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
@role_required('Secretariat')
|
@role_required('Secretariat')
|
||||||
def person_email_json(request, personid):
|
def person_email_json(request, personid):
|
||||||
person = get_object_or_404(Person, pk=personid)
|
person = get_object_or_404(Person, pk=personid)
|
||||||
|
|
|
@ -228,18 +228,6 @@ class Person(models.Model):
|
||||||
def defurl(self):
|
def defurl(self):
|
||||||
return urljoin(self.default_hostscheme,self.json_url())
|
return urljoin(self.default_hostscheme,self.json_url())
|
||||||
|
|
||||||
def json_url(self):
|
|
||||||
return "/person/%s.json" % (self.id, )
|
|
||||||
|
|
||||||
# return info about the person
|
|
||||||
def json_dict(self, hostscheme):
|
|
||||||
ct1 = dict()
|
|
||||||
ct1['person_id'] = self.id
|
|
||||||
ct1['href'] = urljoin(hostscheme, self.json_url())
|
|
||||||
ct1['name'] = self.name
|
|
||||||
ct1['ascii'] = self.ascii
|
|
||||||
return ct1
|
|
||||||
|
|
||||||
def available_api_endpoints(self):
|
def available_api_endpoints(self):
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
return list(set([ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES if r==None or has_role(self.user, r) ]))
|
return list(set([ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES if r==None or has_role(self.user, r) ]))
|
||||||
|
|
|
@ -4,7 +4,6 @@ from ietf.utils.urls import url
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^merge/$', views.merge),
|
url(r'^merge/$', views.merge),
|
||||||
url(r'^search/(?P<model_name>(person|email))/$', views.ajax_select2_search),
|
url(r'^search/(?P<model_name>(person|email))/$', views.ajax_select2_search),
|
||||||
url(r'^(?P<personid>[a-z0-9]+).json$', ajax.person_json),
|
|
||||||
url(r'^(?P<personid>[a-z0-9]+)/email.json$', ajax.person_email_json),
|
url(r'^(?P<personid>[a-z0-9]+)/email.json$', ajax.person_email_json),
|
||||||
url(r'^(?P<email_or_name>[^/]+)$', views.profile),
|
url(r'^(?P<email_or_name>[^/]+)$', views.profile),
|
||||||
url(r'^(?P<email_or_name>[^/]+)/photo/?$', views.photo),
|
url(r'^(?P<email_or_name>[^/]+)/photo/?$', views.photo),
|
||||||
|
|
|
@ -539,10 +539,6 @@ def edit(request, acronym, num=None):
|
||||||
# send notification
|
# send notification
|
||||||
send_notification(group,meeting,login,form.cleaned_data,'update')
|
send_notification(group,meeting,login,form.cleaned_data,'update')
|
||||||
|
|
||||||
# nuke any cache that might be lingering around.
|
|
||||||
from ietf.meeting.helpers import session_constraint_expire
|
|
||||||
session_constraint_expire(request,session)
|
|
||||||
|
|
||||||
messages.success(request, 'Session Request updated')
|
messages.success(request, 'Session Request updated')
|
||||||
return redirect('ietf.secr.sreq.views.view', acronym=acronym)
|
return redirect('ietf.secr.sreq.views.view', acronym=acronym)
|
||||||
|
|
||||||
|
|
|
@ -1,184 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* FILE: agenda_edit.js
|
|
||||||
* Copyright (c) 2013, The IETF Trust. See ../../../LICENSE.
|
|
||||||
*
|
|
||||||
* www.credil.org: Project Orlando 2013
|
|
||||||
* Author: Justin Hornosty ( justin@credil.org )
|
|
||||||
* Michael Richardson <mcr@sandelman.ca>
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* This is the main file for the agenda editing page.
|
|
||||||
* It contains the document read function that starts everything
|
|
||||||
* off, and uses functions and objects from agenda_*.js
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////-GLOBALS----////////////////////////////////////////
|
|
||||||
|
|
||||||
// these need to be setup in landscape_edit's setup_slots() inline function:
|
|
||||||
//var meeting_number = 0; // is the meeting name.
|
|
||||||
//var schedule_id = 0; // what is the schedule we are editing.
|
|
||||||
//var schedule_name; // what is the schedule we are editing.
|
|
||||||
//var schedule_owner_href = ''; // who owns this schedule
|
|
||||||
//var assignments_post_href;
|
|
||||||
//var meeting_base_url;
|
|
||||||
//var site_base_url;
|
|
||||||
//var total_rooms = 0; // the number of rooms
|
|
||||||
//var total_days = 0; // the number of days
|
|
||||||
|
|
||||||
var is_secretariat = false;
|
|
||||||
|
|
||||||
var agenda_globals;
|
|
||||||
|
|
||||||
var area_directors = {}; // list of promises of area directors, index by href.
|
|
||||||
|
|
||||||
var read_only = true; // it is true until we learn otherwise.
|
|
||||||
var days = [];
|
|
||||||
var legend_status = {}; // agenda area colors.
|
|
||||||
var load_conflicts = true;
|
|
||||||
var duplicate_sessions = {};
|
|
||||||
/********* colors ************************************/
|
|
||||||
|
|
||||||
var dragging_color = "blue"; // color when draging events.
|
|
||||||
var none_color = ''; // when we reset the color. I believe doing '' will force it back to the stylesheet value.
|
|
||||||
var color_droppable_empty_slot = 'rgb(0, 102, 153)';
|
|
||||||
|
|
||||||
// these are used for debugging only.
|
|
||||||
var last_json_txt = ""; // last txt from a json call.
|
|
||||||
var last_json_reply = []; // last parsed content
|
|
||||||
|
|
||||||
var hidden_rooms = [];
|
|
||||||
var hidden_days = [];
|
|
||||||
|
|
||||||
/****************************************************/
|
|
||||||
|
|
||||||
/////////////-END-GLOBALS-///////////////////////////////////////
|
|
||||||
|
|
||||||
/* refactor this out into the html */
|
|
||||||
$(document).ready(function() {
|
|
||||||
initStuff();
|
|
||||||
|
|
||||||
$("#close_ietf_menubar").click();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/* initStuff()
|
|
||||||
This is ran at page load and sets up the entire page.
|
|
||||||
*/
|
|
||||||
function initStuff(){
|
|
||||||
agenda_globals = new AgendaGlobals();
|
|
||||||
//agenda_globals.__debug_session_move = true;
|
|
||||||
|
|
||||||
log("initstuff() running...");
|
|
||||||
var directorpromises = [];
|
|
||||||
|
|
||||||
/* define a slot for unscheduled items */
|
|
||||||
var unassigned = new ScheduledSlot();
|
|
||||||
unassigned.make_unassigned();
|
|
||||||
|
|
||||||
setup_slots(directorpromises);
|
|
||||||
mark_area_directors(directorpromises);
|
|
||||||
log("setup_slots() ran");
|
|
||||||
droppable();
|
|
||||||
log("droppable() ran");
|
|
||||||
|
|
||||||
$.when.apply($,directorpromises).done(function() {
|
|
||||||
/* can not load events until area director info,
|
|
||||||
timeslots, sessions, and assignments
|
|
||||||
have been loaded
|
|
||||||
*/
|
|
||||||
log("loading/linking objects");
|
|
||||||
load_events();
|
|
||||||
log("load_events() ran");
|
|
||||||
find_meeting_no_room();
|
|
||||||
calculate_name_select_box();
|
|
||||||
calculate_room_select_box();
|
|
||||||
listeners();
|
|
||||||
droppable();
|
|
||||||
duplicate_sessions = find_double_timeslots();
|
|
||||||
empty_info_table();
|
|
||||||
count_sessions();
|
|
||||||
|
|
||||||
if(load_conflicts) {
|
|
||||||
recalculate(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
static_listeners();
|
|
||||||
log("listeners() ran");
|
|
||||||
|
|
||||||
start_spin();
|
|
||||||
|
|
||||||
read_only = true;
|
|
||||||
log("do read only check");
|
|
||||||
read_only_check();
|
|
||||||
stop_spin();
|
|
||||||
|
|
||||||
meeting_objs_length = Object.keys(agenda_globals.meeting_objs).length;
|
|
||||||
|
|
||||||
/* Comment this out for fast loading */
|
|
||||||
//load_conflicts = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var __READ_ONLY;
|
|
||||||
function read_only_result(msg) {
|
|
||||||
__READ_ONLY = msg;
|
|
||||||
is_secretariat = msg.secretariat;
|
|
||||||
|
|
||||||
read_only = msg.read_only;
|
|
||||||
console.log("read only", read_only);
|
|
||||||
|
|
||||||
if(!read_only) {
|
|
||||||
$("#read_only").css("display", "none");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.save_perm) {
|
|
||||||
$(".agenda_save_box").css("display", "block");
|
|
||||||
if(read_only) {
|
|
||||||
$(".agenda_save_box").css("position", "fixed");
|
|
||||||
$(".agenda_save_box").css("top", "20px");
|
|
||||||
$(".agenda_save_box").css("right", "10px");
|
|
||||||
$(".agenda_save_box").css("bottom", "auto");
|
|
||||||
$(".agenda_save_box").css("border", "3px solid blue");
|
|
||||||
$(".agenda_save_box").css("z-index", "2000");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$(".agenda_save_box").html("please login to save");
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule_owner_href = msg.owner_href;
|
|
||||||
// XX go fetch the owner and display it.
|
|
||||||
console.log("owner href:", schedule_owner_href);
|
|
||||||
|
|
||||||
$("#pageloaded").show();
|
|
||||||
|
|
||||||
listeners();
|
|
||||||
droppable();
|
|
||||||
}
|
|
||||||
|
|
||||||
function read_only_check() {
|
|
||||||
var read_only_url = meeting_base_url + "/agenda/" + schedule_owner_email + "/" + schedule_name + "/permissions";
|
|
||||||
console.log("Loading readonly status from: ", read_only_url);
|
|
||||||
var read_only_load = $.ajax(read_only_url);
|
|
||||||
|
|
||||||
read_only_load.success(function(newobj, status, jqXHR) {
|
|
||||||
last_json_reply = newobj;
|
|
||||||
read_only_result(newobj);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function print_all_ss(objs){
|
|
||||||
console.log(objs)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Local Variables:
|
|
||||||
* c-basic-offset:4
|
|
||||||
* End:
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,626 +0,0 @@
|
||||||
/*
|
|
||||||
* agenda_helpers.js
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013, The IETF Trust. See ../../../LICENSE.
|
|
||||||
*
|
|
||||||
* www.credil.org: Project Orlando 2013
|
|
||||||
* Author: Justin Hornosty ( justin@credil.org )
|
|
||||||
* Michael Richardson <mcr@sandelman.ca>
|
|
||||||
*
|
|
||||||
* Should contain miscellaneous commonly used functions.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* do_work:
|
|
||||||
when we are waiting for something to either resolve to true, or another similar job
|
|
||||||
this function should achieve this.
|
|
||||||
|
|
||||||
result will be a function that when returns true will stop the work and then the callback
|
|
||||||
will be triggered.
|
|
||||||
|
|
||||||
ex:
|
|
||||||
global_x = 0
|
|
||||||
do_work(function(){ global_x++; return global_x > 100 }, function(){ console.log("resolved") })
|
|
||||||
*/
|
|
||||||
function do_work(result,callback){
|
|
||||||
setTimeout(function(){
|
|
||||||
if(!result()){
|
|
||||||
setTimeout(arguments.callee,1);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function log(text){
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function print_all(){
|
|
||||||
console.log("all");
|
|
||||||
console.log(agenda_globals.meeting_objs.length);
|
|
||||||
for(var i=0; i<agenda_globals.meeting_objs.length; i++){
|
|
||||||
agenda_globals.meeting_objs[i].print_out();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function find_title(title){
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key){
|
|
||||||
if (agenda_globals.meeting_objs[key].title == title) {
|
|
||||||
console.log(agenda_globals.meeting_objs[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function find_session_id(session_id){
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key){
|
|
||||||
if (agenda_globals.meeting_objs[key].session_id == session_id) {
|
|
||||||
console.log(agenda_globals.meeting_objs[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function find_same_area(area){
|
|
||||||
var areas = []
|
|
||||||
area = area.toUpperCase();
|
|
||||||
$.each(agenda_globals.meeting_objs, function(index,obj){
|
|
||||||
if(obj.area == area){
|
|
||||||
areas.push({id:index,slot_status_key:obj.slot_status_key})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return areas
|
|
||||||
}
|
|
||||||
|
|
||||||
function style_empty_slots(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var __debug_load_events = false;
|
|
||||||
/* this pushes every event into the agendas */
|
|
||||||
function load_events(){
|
|
||||||
var slot_id;
|
|
||||||
|
|
||||||
console.log("load events...");
|
|
||||||
|
|
||||||
/* first delete all html items that might have gotten saved if
|
|
||||||
* user save-as and went offline.
|
|
||||||
*/
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("processing double slot status relations");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear out all the timeslots */
|
|
||||||
$.each(agenda_globals.timeslot_bydomid, function(key) {
|
|
||||||
insert_cell(key, "", true);
|
|
||||||
|
|
||||||
var timeslot = agenda_globals.timeslot_bydomid[key];
|
|
||||||
slot_id = ("#"+key);
|
|
||||||
|
|
||||||
$(slot_id).addClass("agenda_slot_" + timeslot.roomtype);
|
|
||||||
|
|
||||||
if(timeslot.roomtype == "unavail") {
|
|
||||||
$(slot_id).removeClass("ui-droppable");
|
|
||||||
$(slot_id).removeClass("free_slot");
|
|
||||||
$(slot_id).addClass("agenda_slot_unavailable");
|
|
||||||
} else {
|
|
||||||
$(slot_id).removeClass("agenda_slot_unavailable");
|
|
||||||
$(slot_id).addClass("ui-droppable");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$.each(agenda_globals.slot_status, function(key) {
|
|
||||||
ssid_arr = agenda_globals.slot_status[key];
|
|
||||||
|
|
||||||
for(var q = 0; q<ssid_arr.length; q++){
|
|
||||||
ssid = ssid_arr[q];
|
|
||||||
|
|
||||||
ssid.connect_to_timeslot_session();
|
|
||||||
|
|
||||||
// also see if the slots have any declared relationship, and take it forward as
|
|
||||||
// well as backwards.
|
|
||||||
if(ssid.extendedfrom_id != false) {
|
|
||||||
other = agenda_globals.slot_objs[ssid.extendedfrom_id];
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("slot:",ssid.assignment_id, "extended from: ",key,ssid.extendedfrom_id); // ," is: ", other);
|
|
||||||
}
|
|
||||||
if(other != undefined) {
|
|
||||||
ssid.extendedfrom = other;
|
|
||||||
other.extendedto = ssid;
|
|
||||||
} else {
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("extended from: ",ssid.extendedfrom_id," not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// go through the slots again, and if one slot has been extended, then
|
|
||||||
// extend any other "sister" slots as well.
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("marking extended slots for slots with multiple sessions");
|
|
||||||
}
|
|
||||||
$.each(agenda_globals.slot_status, function(key) {
|
|
||||||
ssid_arr = agenda_globals.slot_status[key];
|
|
||||||
|
|
||||||
var extendedto = undefined;
|
|
||||||
for(var q = 0; q<ssid_arr.length; q++){
|
|
||||||
ssid = ssid_arr[q];
|
|
||||||
if(extendedto == undefined &&
|
|
||||||
ssid.extendedto != undefined) {
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("ssid",ssid.session_id,"extended 1");
|
|
||||||
}
|
|
||||||
extendedto = ssid.extendedto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(var q = 0; q<ssid_arr.length; q++){
|
|
||||||
ssid = ssid_arr[q];
|
|
||||||
ssid.extendedto = extendedto;
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("ssid",ssid.session_id,"extended 2");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("finding responsible ad");
|
|
||||||
}
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key) {
|
|
||||||
session = agenda_globals.meeting_objs[key];
|
|
||||||
session.find_responsible_ad();
|
|
||||||
});
|
|
||||||
|
|
||||||
$.each(agenda_globals.slot_status, function(key) {
|
|
||||||
ssid_arr = agenda_globals.slot_status[key]
|
|
||||||
if(key == "sortable-list"){
|
|
||||||
console.log("sortable list");
|
|
||||||
}else {
|
|
||||||
for(var q = 0; q<ssid_arr.length; q++){
|
|
||||||
ssid = ssid_arr[q];
|
|
||||||
slot_id = ("#"+ssid.domid());
|
|
||||||
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log("populating slot: ",slot_id,key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ssid.timeslot.roomtype != "unavail") {
|
|
||||||
session = agenda_globals.meeting_objs[ssid.session_id];
|
|
||||||
if (session != null) {
|
|
||||||
if(ssid.extendedto != undefined) {
|
|
||||||
session.double_wide = true;
|
|
||||||
session.slot2 = ssid.extendedto;
|
|
||||||
}
|
|
||||||
if(ssid.extendedfrom == undefined) {
|
|
||||||
session.slot_status_key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(slot_id).removeClass('free_slot');
|
|
||||||
|
|
||||||
if(ssid.extendedfrom == undefined) {
|
|
||||||
if(__debug_load_events) {
|
|
||||||
console.log(" with session", session.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
session.populate_event(key);
|
|
||||||
}
|
|
||||||
session.placed(ssid.timeslot, false, ssid);
|
|
||||||
} else {
|
|
||||||
$(slot_id).addClass('free_slot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key) {
|
|
||||||
session = agenda_globals.meeting_objs[key];
|
|
||||||
|
|
||||||
// note in the group, what the set of column classes is.
|
|
||||||
// this is an array, as the group might have multiple
|
|
||||||
// sessions!
|
|
||||||
group = session.group;
|
|
||||||
if(group == undefined) {
|
|
||||||
console.log("session: ", session.title, "has no group_href:", session.group_href);
|
|
||||||
} else {
|
|
||||||
group.add_column_classes(session.column_class_list);
|
|
||||||
group.add_session(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_free(inp){
|
|
||||||
var empty = false;
|
|
||||||
slot = agenda_globals.timeslot_bydomid[inp.id];
|
|
||||||
if(slot == null){
|
|
||||||
//console.log("\t from check_free, slot is null?", inp,inp.id, agenda_globals.slot_status[inp.id]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (slot.empty == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clears any background highlight colors of scheduled sessions */
|
|
||||||
function clear_highlight(inp_arr){ // @args: array from slot_status{}
|
|
||||||
if(inp_arr == null){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for(var i =0; i<inp_arr.length; i++){
|
|
||||||
$("#session_"+inp_arr[i].session_id).removeClass('free_slot');
|
|
||||||
$("#session_"+inp_arr[i].session_id).css('background-color','');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* based on any meeting object, it finds any other objects inside the same timeslot. */
|
|
||||||
function find_friends(inp){
|
|
||||||
var ts = $(inp).parent().attr('id');
|
|
||||||
var ss_arr = agenda_globals.slot_status[ts];
|
|
||||||
if (ss_arr != null){
|
|
||||||
return ss_arr;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
//console.log("find_friends("+inp+") did not find anything");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function json_to_id(j){
|
|
||||||
return (j.room()+"_"+j.date()+"_"+j.time());
|
|
||||||
}
|
|
||||||
|
|
||||||
function id_to_json(id){
|
|
||||||
if(id != null){
|
|
||||||
var split = id.split('_');
|
|
||||||
return {"room":split[0],"date":split[1],"time":split[2]}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* returns a the html for a row in a table
|
|
||||||
as: <tr><td>title</td><td>data</td></tr>
|
|
||||||
*/
|
|
||||||
function gen_tr_td(title,data){
|
|
||||||
return "<tr><td>"+title+"</td><td>"+data+"</td></tr>";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mainly for the case where we didn't get any data back from the server */
|
|
||||||
function empty_info_table(){
|
|
||||||
$("#info_grp").html(name_select_html);
|
|
||||||
$("#info_name").html("");
|
|
||||||
$("#info_area").html("");
|
|
||||||
$("#info_duration").html("");
|
|
||||||
|
|
||||||
$(".agenda_selected_buttons").attr('disabled',true);
|
|
||||||
$(".agenda_double_slot").addClass("button_disabled");
|
|
||||||
$(".agenda_double_slot").removeClass("button_enabled");
|
|
||||||
|
|
||||||
if(!read_only) {
|
|
||||||
$("#info_location").html(generate_select_box()+"<button id='info_location_set'>Set</button>");
|
|
||||||
$("#info_location_select").val("");
|
|
||||||
$("#info_location_select").val($("#info_location_select_option_"+current_timeslot_id).val());
|
|
||||||
}
|
|
||||||
$("#info_responsible").html("");
|
|
||||||
$("#info_requestedby").html("");
|
|
||||||
$("#agenda_requested_features").html("");
|
|
||||||
|
|
||||||
/* need to reset listeners, because we just changed the HTML */
|
|
||||||
listeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var temp_1;
|
|
||||||
/* creates the 'info' table that is located on the right side.
|
|
||||||
takes in a json.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function compare_timeslot(a,b) {
|
|
||||||
//console.log("day: a,b", a.day, b.day);
|
|
||||||
|
|
||||||
// sometimes (a.day==b.say)==false and (a.day===b.day)==false,
|
|
||||||
// for days that appear identical, but built from different strings,
|
|
||||||
// yet (a.day-b.day)==0.
|
|
||||||
if((a.day - b.day) == 0) {
|
|
||||||
//console.log("time: a,b", a.starttime, b.starttime);
|
|
||||||
if(a.starttime == b.starttime) {
|
|
||||||
//console.log("room: a,b", a.room, b.room, a.room < b.room);
|
|
||||||
if(a.room > b.room) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if(a.starttime > b.starttime) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(a.day > b.day) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var room_select_html = "";
|
|
||||||
function calculate_room_select_box() {
|
|
||||||
var html = "<select id='info_location_select'>";
|
|
||||||
var mobj_array = [];
|
|
||||||
|
|
||||||
$.each(agenda_globals.timeslot_byid, function(key, value){
|
|
||||||
mobj_array.push(value)
|
|
||||||
});
|
|
||||||
|
|
||||||
var sorted = mobj_array.sort(compare_timeslot);
|
|
||||||
var lastone_id = undefined;
|
|
||||||
|
|
||||||
$.each(sorted, function(index, value) {
|
|
||||||
// this check removes duplicates from the list, if there are any.
|
|
||||||
if(value.roomtype == "break" || value.roomtype=="reg") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(value.timeslot_id == lastone_id) {
|
|
||||||
return; // from subfunction.
|
|
||||||
}
|
|
||||||
//console.log("room_select_html", index, value, value.short_string);
|
|
||||||
html=html+"<option value='"+value.timeslot_id;
|
|
||||||
html=html+"' id='info_location_select_option_";
|
|
||||||
html=html+value.timeslot_id+"'>";
|
|
||||||
html=html+value.short_string;
|
|
||||||
if(value.roomtype != "regular") {
|
|
||||||
html = html+ "(" + value.roomtype + ")";
|
|
||||||
}
|
|
||||||
html=html+"</option>";
|
|
||||||
lastone_id = value.timeslot_id;
|
|
||||||
});
|
|
||||||
html = html+"</select>";
|
|
||||||
room_select_html = html;
|
|
||||||
return room_select_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name_select_html = undefined;
|
|
||||||
var temp_sorted = null;
|
|
||||||
function calculate_name_select_box(){
|
|
||||||
var html = "<select id='info_name_select'>";
|
|
||||||
var mobj_array = [];
|
|
||||||
var mobj_array2;
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key, value){ mobj_array.push(value) });
|
|
||||||
mobj_array2 = mobj_array.sort(function(a,b) { return a.title.localeCompare(b.title); });
|
|
||||||
|
|
||||||
var mlen = mobj_array.length;
|
|
||||||
console.log("calculate name_select box with",mlen,"objects");
|
|
||||||
for(var i = 0; i < mlen; i++){
|
|
||||||
//console.log("select box mobj["+i+"]="+mobj_array[i]);
|
|
||||||
// html=html+"<option value='"+mobj_array[i].slot_status_key;
|
|
||||||
html=html+"<option value='"+mobj_array[i].session_id;
|
|
||||||
html=html+"' id='info_name_select_option_";
|
|
||||||
ts_id = "err";
|
|
||||||
//console.log(mobj_array[i].session_id);
|
|
||||||
try{
|
|
||||||
ts_id = mobj_array[i].session_id;
|
|
||||||
}catch(err){
|
|
||||||
console.log(err); // bucket list items.
|
|
||||||
|
|
||||||
}
|
|
||||||
html=html+ts_id+"'>";
|
|
||||||
|
|
||||||
|
|
||||||
try{
|
|
||||||
html=html+mobj_array[i].title; // + " (" + mobj_array[i].description + ")";
|
|
||||||
} catch(err) {
|
|
||||||
html=html+"ERRROR!!!";
|
|
||||||
}
|
|
||||||
html=html+"</option>";
|
|
||||||
}
|
|
||||||
|
|
||||||
html = html+"</select>";
|
|
||||||
name_select_html = html;
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function generate_select_box(){
|
|
||||||
if(!room_select_html) {
|
|
||||||
calculate_name_select_box();
|
|
||||||
}
|
|
||||||
return room_select_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function insert_cell(js_room_id, text, replace){
|
|
||||||
slot_id = ("#"+js_room_id);
|
|
||||||
try{
|
|
||||||
var found;
|
|
||||||
if(replace) {
|
|
||||||
found = $(slot_id).html(text);
|
|
||||||
} else {
|
|
||||||
found = $(slot_id).append($(text));
|
|
||||||
|
|
||||||
}
|
|
||||||
$(slot_id).css('background','');
|
|
||||||
$(slot_id).removeClass('free_slot');
|
|
||||||
if(found.length == 0){
|
|
||||||
// do something here, if length was zero... then?
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch(err){
|
|
||||||
log("error");
|
|
||||||
log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function find_meeting_no_room(){
|
|
||||||
$.each(agenda_globals.meeting_objs, function(key){
|
|
||||||
if(agenda_globals.meeting_objs[key].slot_status_key == null) {
|
|
||||||
session = agenda_globals.meeting_objs[key]
|
|
||||||
session.slot_status_key = null;
|
|
||||||
session.populate_event(bucketlist_id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* in some cases we have sessions that span over two timeslots.
|
|
||||||
so we end up with two slot_status pointing to the same meeting_obj.
|
|
||||||
this this occures when someone requests a session that is extra long
|
|
||||||
which will then fill up the next timeslot.
|
|
||||||
|
|
||||||
this functions finds those cases.
|
|
||||||
|
|
||||||
returns a json{ 'ts': arr[time_slot_ids] }
|
|
||||||
|
|
||||||
*/
|
|
||||||
function find_double_timeslots(){
|
|
||||||
var duplicate = {};
|
|
||||||
|
|
||||||
$.each(agenda_globals.slot_status, function(key){
|
|
||||||
for(var i =0; i<agenda_globals.slot_status[key].length; i++){
|
|
||||||
// goes threw all the slots
|
|
||||||
var ss_id = agenda_globals.slot_status[key][i].session_id;
|
|
||||||
if(duplicate[ss_id]){
|
|
||||||
duplicate[ss_id]['count']++;
|
|
||||||
duplicate[ss_id]['ts'].push(key);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
duplicate[ss_id] = {'count': 1, 'ts':[key]};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var dup = {};
|
|
||||||
// console.log(duplicate);
|
|
||||||
$.each(duplicate, function(key){
|
|
||||||
if(duplicate[key]['count'] > 1){
|
|
||||||
dup[key] = duplicate[key]['ts'];
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return dup;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var child = null;
|
|
||||||
/* removes a duplicate timeslot. completely. it's gone. */
|
|
||||||
function remove_duplicate(timeslot_id, ss_id){
|
|
||||||
children = $("#"+timeslot_id).children();
|
|
||||||
child = children;
|
|
||||||
for(var i = 0; i< children.length; i++){ // loop to
|
|
||||||
if($(children[i]).attr('session_id') == ss_id) { // make sure we only remove duplicate.
|
|
||||||
try{
|
|
||||||
$(children[i]).remove();
|
|
||||||
}catch(exception){
|
|
||||||
console.log("exception from remove_duplicate",exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function auto_remove(){
|
|
||||||
dup = find_double_timeslots();
|
|
||||||
$.each(dup, function(key){
|
|
||||||
remove_duplicate(dup[key][1], key);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* for the spinnner */
|
|
||||||
|
|
||||||
/* spinner code from:
|
|
||||||
http://fgnass.github.com/spin.js/
|
|
||||||
|
|
||||||
ex: $("#spinner").spin() < start the spin
|
|
||||||
$("#spinner").spin(false) < stop the spin
|
|
||||||
|
|
||||||
http://gist.github.com/itsflorida < jquery functionality.
|
|
||||||
|
|
||||||
lines: 30, // The number of lines to draw
|
|
||||||
length: 7, // The length of each line
|
|
||||||
width: 1, // The line thickness
|
|
||||||
radius: 20, // The radius of the inner circle
|
|
||||||
corners: 1, // Corner roundness (0..1)
|
|
||||||
rotate: 0, // The rotation offset
|
|
||||||
color: '#000', // #rgb or #rrggbb
|
|
||||||
speed: 1, // Rounds per second
|
|
||||||
trail: 60, // Afterglow percentage
|
|
||||||
shadow: false, // Whether to render a shadow
|
|
||||||
hwaccel: true, // Whether to use hardware acceleration
|
|
||||||
className: 'spinner', // The CSS class to assign to the spinner
|
|
||||||
zIndex: 2e9, // The zindex (defaults to 2000000000)
|
|
||||||
top: 'auto', // Top position relative to parent in px
|
|
||||||
left: 'auto' // Left position relative to parent in px
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
$.fn.spin = function(opts, color) {
|
|
||||||
if (Spinner) {
|
|
||||||
return this.each(function() {
|
|
||||||
var $this = $(this),
|
|
||||||
data = $this.data();
|
|
||||||
|
|
||||||
if (data.spinner) {
|
|
||||||
data.spinner.stop();
|
|
||||||
delete data.spinner;
|
|
||||||
}
|
|
||||||
if (opts !== false) {
|
|
||||||
if (typeof opts === "string") {
|
|
||||||
if (opts in presets) {
|
|
||||||
opts = presets[opts];
|
|
||||||
} else {
|
|
||||||
opts = {};
|
|
||||||
}
|
|
||||||
if (color) {
|
|
||||||
opts.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw "Spinner class not available.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
||||||
|
|
||||||
|
|
||||||
function start_spin(opts){
|
|
||||||
//spinner
|
|
||||||
// $("#schedule_name").hide();
|
|
||||||
$("#spinner").show();
|
|
||||||
$("#spinner").spin({lines:16, radius:8, length:16, width:4});
|
|
||||||
$("#pageloaded").hide();
|
|
||||||
}
|
|
||||||
function stop_spin(){
|
|
||||||
//spinner
|
|
||||||
$("#schedule_name").show();
|
|
||||||
$("#spinner").hide();
|
|
||||||
$("#spinner").spin(false);
|
|
||||||
$("#pageloaded").show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Local Variables:
|
|
||||||
* c-basic-offset:4
|
|
||||||
* End:
|
|
||||||
*/
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -49,7 +49,7 @@
|
||||||
·
|
·
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<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.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">Copy agenda</a>
|
||||||
·
|
·
|
||||||
|
|
||||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
|
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
|
||||||
|
|
|
@ -1,388 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
|
||||||
{% load origin %}
|
|
||||||
{% load static %}
|
|
||||||
{% load ietf_filters %}
|
|
||||||
{% load humanize %}
|
|
||||||
|
|
||||||
{% 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 }} }
|
|
||||||
.director-mark-{{ area.upcase_acronym}} {
|
|
||||||
border: 2px solid {{ area.fg_color}};
|
|
||||||
color:{{ area.fg_color }};
|
|
||||||
background-color: {{ area.bg_color }}
|
|
||||||
}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock morecss %}
|
|
||||||
|
|
||||||
{% block title %}IETF {{ meeting.number }} Meeting Agenda{% endblock %}
|
|
||||||
{% load agenda_custom_tags %}
|
|
||||||
{% block pagehead %}
|
|
||||||
<link rel='stylesheet' type='text/css' href="{% static 'ietf/css/agenda/jquery-ui-themes/jquery-ui-1.8.11.custom.css' %}" />
|
|
||||||
<link rel='stylesheet' type='text/css' href="{% static 'ietf/css/agenda/agenda.css' %}" />
|
|
||||||
{% endblock pagehead %}
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
<script type="text/javascript" src="{% static 'ietf/js/agenda/jquery-1.8.2.min.js' %}"></script>
|
|
||||||
<script src="{% static 'js-cookie/src/js.cookie.js' %}"></script>
|
|
||||||
<script>
|
|
||||||
jQuery.ajaxSetup({
|
|
||||||
crossDomain: false, // obviates need for sameOrigin test
|
|
||||||
beforeSend: function(xhr, settings) {
|
|
||||||
if (!csrfSafeMethod(settings.type)) {
|
|
||||||
xhr.setRequestHeader("X-CSRFToken", Cookies.get('csrftoken'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery-ui.custom.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery.ui.widget.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery.ui.droppable.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery.ui.sortable.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery.ui.accordion.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/minified/jquery.ui.draggable.min.js' %}"></script>
|
|
||||||
|
|
||||||
<script type='text/javascript' src="{% static 'spin.js/spin.min.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/agenda_edit.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/agenda_helpers.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/agenda_objects.js' %}"></script>
|
|
||||||
<script type='text/javascript' src="{% static 'ietf/js/agenda/agenda_listeners.js' %}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<script type='text/javascript'>
|
|
||||||
|
|
||||||
|
|
||||||
var meeting_number = "{{ meeting.number }}";
|
|
||||||
var schedule_id = {{ schedule.id }};
|
|
||||||
var schedule_owner_href = "{{ schedule.owner_email }}";
|
|
||||||
var schedule_name = "{{ schedule.name }}";
|
|
||||||
var meeting_base_url = "{{ meeting_base_url }}";
|
|
||||||
var schedule_owner_email = "{{ schedule.owner_email }}";
|
|
||||||
var site_base_url = "{{ site_base_url }}";
|
|
||||||
var assignments_post_href = "{% url "ietf.meeting.ajax.assignments_json" meeting.number schedule.owner_email schedule.name %}";
|
|
||||||
var total_days = {{time_slices|length}};
|
|
||||||
var total_rooms = {{rooms|length}};
|
|
||||||
|
|
||||||
function setup_slots(promiselist){
|
|
||||||
{% for day in time_slices %}
|
|
||||||
days.push("{{day}}");
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for ad in area_directors %}
|
|
||||||
area_directors["{{ad.group.acronym}}"] = [];
|
|
||||||
{% endfor %}
|
|
||||||
{% for ad in area_directors %}
|
|
||||||
area_directors["{{ad.group.acronym}}"].push(find_person_by_href("{{ad.person.defurl}}"));
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
var ts_promise = load_timeslots("{% url "ietf.meeting.ajax.timeslot_slotsurl" meeting.number %}");
|
|
||||||
var sess_promise = load_sessions("{% url "ietf.meeting.ajax.sessions_json" meeting.number %}");
|
|
||||||
promiselist.push(ts_promise);
|
|
||||||
promiselist.push(sess_promise);
|
|
||||||
|
|
||||||
var ss_promise = load_assignments(ts_promise, sess_promise, assignments_post_href);
|
|
||||||
promiselist.push(ss_promise);
|
|
||||||
|
|
||||||
console.log("setup_slots run");
|
|
||||||
|
|
||||||
{% for area in area_list %}
|
|
||||||
legend_status["{{area.upcase_acronym}}"] = true;
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style type='text/css'>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{% endblock js %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% origin %}
|
|
||||||
<div id="read_only">
|
|
||||||
<p>You do not have write permission to agenda: {{schedule.name}}</p>
|
|
||||||
{% if schedule.is_official_record %}
|
|
||||||
<p>This is the official schedule for a meeting in the past.</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>Please save this agenda to your account first.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content"> {% comment %} For preserving styling across the facelift {% endcomment %}
|
|
||||||
|
|
||||||
<div class="wrapper custom_text_stuff">
|
|
||||||
|
|
||||||
|
|
||||||
<div id="unassigned-items">
|
|
||||||
<div id="all_agendas" class="events_bar_buttons">
|
|
||||||
<a href="{% url "ietf.meeting.views.list_schedules" meeting.number %}">
|
|
||||||
<button class="styled_button">all agendas</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div id="hidden_room" class="hide_buttons events_bar_buttons">
|
|
||||||
<div class="very_small left">hidden rooms:<span id="hidden_rooms" >0/{{rooms|length}}</span></div>
|
|
||||||
<div><button class="small_button" id="show_hidden_rooms">Show</button></div>
|
|
||||||
</div>
|
|
||||||
<div id="hidden_day" class="hide_buttons events_bar_buttons">
|
|
||||||
<div class="very_small left">hidden days:<span id="hidden_days" >0/{{time_slices|length}}</span></div>
|
|
||||||
<div><button class="small_button" id="show_hidden_days">Show</button></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="unassigned_order" class="events_bar_buttons">
|
|
||||||
<select id="unassigned_sort_button" class="dialog">
|
|
||||||
<option id="unassigned_alpha" value="alphaname" selected>Alphabetical</option>
|
|
||||||
<option id="unassigned_area" value="area">By Area</option>
|
|
||||||
<option id="unassigned_duration" value="duration">By Duration</option>
|
|
||||||
<option id="unassigned_special" value="special">Special Request</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="agenda_slot_title" >
|
|
||||||
<div style="ui-icon ui-icon-arrow-1-w" id="close_ietf_menubar">
|
|
||||||
<
|
|
||||||
</div>
|
|
||||||
<b>Unassigned Events:</b>
|
|
||||||
<span id="schedule_name">name: {{schedule.name}}</span>
|
|
||||||
</div>
|
|
||||||
<div id="sortable-list" class="ui-droppable bucket-list room_title">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="agenda_div">
|
|
||||||
|
|
||||||
<div id="dialog-confirm" title="" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
Are you sure you want to put two sessions into the same slot?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="can-extend-dialog" title="" class="ui-dialog dialog" style="display:none">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="can-not-extend-dialog" title="" class="ui-dialog dialog" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
You can not extend this session. The slot is not available.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="dialog-confirm" title="" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
Are you sure you want to put two sessions into the same slot?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- some boxes for dialogues -->
|
|
||||||
<div id="dialog-confirm-two" title="" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
<span class="dialog-confirm-text">Are you sure you want to put two sessions into the same slot?</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="dialog-confirm-toosmall" title="" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
<span class="dialog-confirm-text">The room you are moving to has a lower
|
|
||||||
room capacity then the requested capacity,<br>
|
|
||||||
Are you sure you want to continue?
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="dialog-confirm-twotoosmall" title="" style="display:none">
|
|
||||||
<p>
|
|
||||||
<span class="ui-icon ui-icon-alert" style="background: white; float: left; margin: 0 7px 20px 0;"></span>
|
|
||||||
<span class="dialog-confirm-text">
|
|
||||||
The slot you are moving to already has a session in it, <br>
|
|
||||||
the room is also smaller than the requested amount.<br>
|
|
||||||
Are you sure you want to continue?
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{%comment%}<table id="meetings" class="ietf-navbar" style="width:100%">{%endcomment%}
|
|
||||||
<table id="meetings" class="ietf-navbar" >
|
|
||||||
<tr>
|
|
||||||
<th class="schedule_title"><div id="pageloaded" style="display:none">loaded</div><div id="spinner"><!-- spinney goes here --></div></th>
|
|
||||||
{% comment %}<th></th>{% endcomment %}
|
|
||||||
{% for day in time_slices %}
|
|
||||||
<th colspan="{{date_slices|colWidth:day}}" id="{{day|date:'Y-m-d'}}-btn" class=" day_{{day}} agenda_slot_title agenda_slot_unavailable">
|
|
||||||
<div id="close_{{day|date:'Y-m-d'}}" class="close top_left very_small close_day">x</div>
|
|
||||||
{{day|date:'D'}} ({{day}})
|
|
||||||
|
|
||||||
</th>
|
|
||||||
<th class="day_{{day}} spacer {{day|date:'Y-m-d'}}-spacer" id="">
|
|
||||||
<div class="ui-widget-content ui-resizable" id="resize-{{day|date:'Y-m-d'}}-spacer">
|
|
||||||
<div class="spacer_grip ui-resizable-handle ui-resizable-e"></div>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th class="th_column"><button id="show_all_button" class="styled_button">show all</button></th>
|
|
||||||
{% comment %}<th><!-- resources --></th>{% endcomment %}
|
|
||||||
{% for day in time_slices %}
|
|
||||||
{% for slot in date_slices|lookup:day %}
|
|
||||||
<th class="day_{{day}}-{{slot.0|date:'Hi'}} day_{{day}} room_title ">{{slot.0|date:'Hi'}}-{{slot.1|date:'Hi'}} </th>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
<th class="day_{{day}} spacer {{day|date:'Y-m-d'}}-spacer"></th>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% for r in rooms %}
|
|
||||||
<tr id="{{r.name|to_acceptable_id}}" class="{% cycle 'agenda_row_alt' '' %} agenda_slot">
|
|
||||||
<th class="vert_time">
|
|
||||||
<div class="close very_small close_room top_left small_button" id="close_{{r.name|to_acceptable_id}}">X</div>
|
|
||||||
<div class="right room_name">{{r.name}} <span class="capacity">({{r.capacity}})</span></div>
|
|
||||||
</th>
|
|
||||||
{% comment 'The secretariat is not using these features' %}
|
|
||||||
<th class="room_features">
|
|
||||||
<div class="resource_list">
|
|
||||||
{% for resource in r.resources.all %}
|
|
||||||
<span class="resource_image">
|
|
||||||
<img src="{% static 'ietf/images/{{ resource.icon }}' %}" height=24 alt="{{resource.desc}}" title="{{resource.desc}}"/>
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
{% endcomment %}
|
|
||||||
{% for day in time_slices %}
|
|
||||||
{% for slot in date_slices|lookup:day %}
|
|
||||||
<td id="{{r.dom_id}}_{{day}}_{{slot.0|date:'Hi'}}" class="day_{{day}} agenda-column-{{day}}-{{slot.0|date:'Hi'}} agenda_slot agenda_slot_unavailable" capacity="{{r.capacity}}" ></td>
|
|
||||||
{% endfor %}
|
|
||||||
<td class="day_{{day}} spacer {{day|date:'Y-m-d'}}-spacer"></td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="session-info" class="ui-droppable bucket-list room_title">
|
|
||||||
<div class="agenda_slot_title"><b>Session Information:</b></div>
|
|
||||||
|
|
||||||
<div class="ss_info_box">
|
|
||||||
<div class="ss_info ss_info_left">
|
|
||||||
<table>
|
|
||||||
<tr><td class="ss_info_name_short">Group:</td><td><span id="info_grp"></span>
|
|
||||||
<!-- <button id="agenda_sreq_button" class="right">Edit Request</button> --></tr>
|
|
||||||
<tr><td class="ss_info_name_short">Name:</td> <td id="info_name"></td></tr>
|
|
||||||
<tr><td class="ss_info_name_short">Area:</td> <td><span id="info_area"></span>
|
|
||||||
<button id="show_all_area" class="right">Show All</button></td></tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan=2>
|
|
||||||
<div class="agenda_nice_button" id="agenda_find_free">
|
|
||||||
<button class="agenda_selected_buttons small_button" id="find_free">Find Free</button>
|
|
||||||
</div>
|
|
||||||
<div class="agenda_nice_button button_disabled" id="agenda_double_slot">
|
|
||||||
<button class="agenda_selected_buttons small_button" disabled id="double_slot">Extend</button>
|
|
||||||
</div>
|
|
||||||
<div id="agenda_pin_slot" class="agenda_nice_button button_disabled">
|
|
||||||
<button class="agenda_selected_buttons small_button" disabled id="pin_slot">Pin</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ss_info ss_info_right">
|
|
||||||
<table>
|
|
||||||
<tr><td class="ss_info_name_long">Duration/Capacity:</td>
|
|
||||||
<td class="info_split"><span id="info_duration"></span>
|
|
||||||
<span style="right" id="grp_type"></span></td>
|
|
||||||
<td class="info_split" id="info_capacity"></td></tr>
|
|
||||||
<tr><td class="ss_info_name_long">Location:</td><td colspan=2 id="info_location"></td></tr>
|
|
||||||
<tr><td class="ss_info_name_long">Responsible AD:</td><td colspan=2 id="info_responsible"></td></tr>
|
|
||||||
<tr><td class="ss_info_name_long">Requested By:</td><td colspan=2 id="info_requestedby"></td></tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan=3>
|
|
||||||
<div class="agenda_nice_button button_disabled" id="agenda_prev_session">
|
|
||||||
<button class="agenda_selected_buttons small_button" disabled id="prev_session">Prev</button>
|
|
||||||
</div>
|
|
||||||
<div class="agenda_nice_button button_disabled" id="agenda_show">
|
|
||||||
<button class="agenda_selected_buttons small_button" disabled id="show_session">Show</button>
|
|
||||||
</div>
|
|
||||||
<div class="agenda_nice_button button_disabled" id="agenda_next_session">
|
|
||||||
<button class="agenda_selected_buttons small_button" disabled id="next_session">Next</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="request_features" id="agenda_requested_features">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="conflict_table">
|
|
||||||
<div id="special_requests">Special Requests</div>
|
|
||||||
<table>
|
|
||||||
<tbody id="conflict_table_body">
|
|
||||||
<tr class="conflict_list_row">
|
|
||||||
<td class="conflict_list_title">
|
|
||||||
Group conflicts
|
|
||||||
</td>
|
|
||||||
<td id="conflict_group_list">
|
|
||||||
<ul>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="conflict_list_row">
|
|
||||||
<td class="conflict_list_title">
|
|
||||||
<b>be present</b>
|
|
||||||
</td>
|
|
||||||
<td id="conflict_people_list">
|
|
||||||
<ul>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="color_legend">
|
|
||||||
{% for area in area_list %}
|
|
||||||
<span class="{{area.upcase_acronym}}-scheme"><input class='color_checkboxes' type="checkbox" id="{{area.upcase_acronym}}" value="{{area.upcase_acronym}}-value" checked>{{area.upcase_acronym}}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="agenda_save_box">
|
|
||||||
|
|
||||||
<div id="agenda_title"><b>Agenda name: </b><span>{{schedule.name}}</span></div>
|
|
||||||
{% if can_edit_properties %}
|
|
||||||
<div><b>Properties</b> <a href="{% url "ietf.meeting.views.edit_schedule_properties" schedule.meeting.number schedule.owner_email schedule.name %}">Edit</a></div>
|
|
||||||
{% endif %}
|
|
||||||
<div id="agenda_saveas">
|
|
||||||
<form action="{{saveasurl}}" method="post">{% csrf_token %}
|
|
||||||
{{ saveas.as_p }}
|
|
||||||
<input id="saveasbutton" type="submit" name="saveas" value="saveas">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div> {% comment %} End of .content div {% endcomment %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -38,12 +38,12 @@
|
||||||
{% for schedule in schedules %}
|
{% for schedule in schedules %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url "ietf.meeting.views.edit_schedule" meeting.number schedule.owner_email schedule.name %}" title="Show regular sessions in agenda">{{ schedule.name }}</a>
|
<a href="{% url "ietf.meeting.views.edit_meeting_schedule" meeting.number schedule.owner_email schedule.name %}" title="Show regular sessions in agenda">{{ schedule.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ schedule.owner }}</td>
|
<td>{{ schedule.owner }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if schedule.origin %}
|
{% if schedule.origin %}
|
||||||
<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.edit_meeting_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>
|
<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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
Loading…
Reference in a new issue