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 re
|
||||
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models.deletion import CASCADE, PROTECT
|
||||
from django.dispatch import receiver
|
||||
|
||||
#from simple_history.models import HistoricalRecords
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.group.colors import fg_group_colors, bg_group_colors
|
||||
|
@ -168,30 +164,6 @@ class Group(GroupInfo):
|
|||
def bg_color(self):
|
||||
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):
|
||||
return self.type_id in ['wg', ] and self.state_id in ['active', 'dormant', 'replaced', 'conclude']
|
||||
|
||||
|
|
|
@ -54,7 +54,6 @@ info_detail_urls = [
|
|||
group_urls = [
|
||||
url(r'^$', views.active_groups),
|
||||
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/create/(?P<group_type>(wg|rg))/$', views.edit, {'action': "charter"}),
|
||||
url(r'^concluded/$', views.concluded_groups),
|
||||
|
|
|
@ -38,7 +38,6 @@ import copy
|
|||
import datetime
|
||||
import itertools
|
||||
import io
|
||||
import json
|
||||
import markdown
|
||||
import math
|
||||
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_page(30 * 60)
|
||||
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
|
||||
from tempfile import mkstemp
|
||||
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.db.models import F, Max, Q, Prefetch
|
||||
from django.http import Http404
|
||||
from django.db.models import F, Prefetch
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.cache import cache
|
||||
from django.urls import reverse
|
||||
from django.utils.cache import get_cache_key
|
||||
from django.shortcuts import get_object_or_404
|
||||
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.name.models import ImportantDateName, SessionPurposeName
|
||||
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.pipe import pipe
|
||||
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):
|
||||
meetings = Meeting.objects
|
||||
if type_in:
|
||||
|
@ -799,15 +714,6 @@ def schedule_permissions(meeting, schedule, user):
|
|||
|
||||
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
|
||||
|
|
|
@ -14,7 +14,6 @@ import string
|
|||
|
||||
from collections import namedtuple
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
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.functions import Coalesce
|
||||
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.utils.text import slugify
|
||||
from django.utils.safestring import mark_safe
|
||||
|
@ -310,35 +306,9 @@ class Meeting(models.Model):
|
|||
slugs = ('conflict', 'conflic2', 'conflic3')
|
||||
return ConstraintName.objects.filter(slug__in=slugs)
|
||||
|
||||
def json_url(self):
|
||||
return "/meeting/%s/json" % (self.number, )
|
||||
|
||||
def base_url(self):
|
||||
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):
|
||||
"""Get unique day/time/timeslot data for meeting
|
||||
|
||||
|
@ -438,13 +408,6 @@ class ResourceAssociation(models.Model):
|
|||
def __str__(self):
|
||||
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):
|
||||
meeting = ForeignKey(Meeting)
|
||||
|
@ -487,15 +450,6 @@ class Room(models.Model):
|
|||
def dom_id(self):
|
||||
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
|
||||
def floorplan_url(self):
|
||||
mtg_num = self.meeting.get_number()
|
||||
|
@ -700,29 +654,8 @@ class TimeSlot(models.Model):
|
|||
dom_id = self.location.dom_id()
|
||||
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):
|
||||
"""Delete all timeslots which are in the same time as this slot"""
|
||||
# can not include duration in filter, because there is no support
|
||||
# for having it a WHERE clause.
|
||||
# below will delete self as well.
|
||||
|
@ -826,25 +759,6 @@ class Schedule(models.Model):
|
|||
def delete_assignments(self):
|
||||
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
|
||||
def qs_assignments_with_sessions(self):
|
||||
return self.assignments.filter(session__isnull=False)
|
||||
|
@ -908,40 +822,6 @@ class SchedTimeSessAssignment(models.Model):
|
|||
"""Get the TimeSlotTypeName that applies to this assignment"""
|
||||
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):
|
||||
"""Return sensible id string for session, e.g. suitable for use as HTML anchor."""
|
||||
components = []
|
||||
|
@ -1033,30 +913,6 @@ class Constraint(models.Model):
|
|||
elif not self.target and 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):
|
||||
session = ForeignKey('Session')
|
||||
|
@ -1367,92 +1223,10 @@ class Session(models.Model):
|
|||
def official_timeslotassignment(self):
|
||||
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
|
||||
def people_constraints(self):
|
||||
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):
|
||||
doc = self.agenda()
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# driver.get() will wait for scripts to finish, but not ajax
|
||||
|
@ -2770,5 +2770,5 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
|
|||
# make_meeting_test_data()
|
||||
#
|
||||
# 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)
|
||||
|
|
|
@ -3053,6 +3053,7 @@ class ReorderSlidesTests(TestCase):
|
|||
|
||||
|
||||
class EditTests(TestCase):
|
||||
"""Test schedule edit operations"""
|
||||
def setUp(self):
|
||||
# make sure we have the colors of the area
|
||||
from ietf.group.colors import fg_group_colors, bg_group_colors
|
||||
|
@ -3060,17 +3061,6 @@ class EditTests(TestCase):
|
|||
fg_group_colors[area_upper] = "#333"
|
||||
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 _set_date_offset_and_retrieve_page(meeting, days_offset, client):
|
||||
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>
|
||||
|
||||
def test_new_meeting_schedule(self):
|
||||
"""Can create a meeting schedule from scratch"""
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
# new from scratch
|
||||
|
@ -3623,7 +3613,11 @@ class EditTests(TestCase):
|
|||
self.assertEqual(new_schedule.origin, None)
|
||||
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))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -3647,34 +3641,21 @@ class EditTests(TestCase):
|
|||
for a in SchedTimeSessAssignment.objects.filter(schedule=new_schedule):
|
||||
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()
|
||||
schedule = meeting.schedule
|
||||
|
||||
# try to get non-existing agenda
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name="foo"))
|
||||
url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=schedule.owner_email(),
|
||||
name="foo"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
# save as new name (requires valid existing agenda)
|
||||
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")
|
||||
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"))
|
||||
url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=schedule.owner_email(),
|
||||
name=schedule.name))
|
||||
self.client.login(username='ad', password='ad+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
@ -3701,39 +3682,73 @@ class EditTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
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()
|
||||
|
||||
# save as new name (requires valid existing agenda)
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
orig_schedule_count = meeting.schedule_set.count()
|
||||
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, {
|
||||
'savename': "/no/this/should/not/work/it/is/too/long",
|
||||
'saveas': "saveas",
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlparse(r.url).path, url)
|
||||
# TODO: Verify that an error message was in fact returned.
|
||||
'name': "/no/this/should/not/work/it/is/too/long",
|
||||
'public': "on",
|
||||
'notes': "Name too long",
|
||||
'base': meeting.schedule.base_id,
|
||||
})
|
||||
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, {
|
||||
'savename': "/invalid/chars/",
|
||||
'saveas': "saveas",
|
||||
})
|
||||
# TODO: Verify that an error message was in fact returned.
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlparse(r.url).path, url)
|
||||
'name': "/invalid/chars/",
|
||||
'public': "on",
|
||||
'notes': "Name too long",
|
||||
'base': meeting.schedule.base_id,
|
||||
})
|
||||
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
|
||||
r = self.client.post(url, {
|
||||
'savename': "f\u00E9ling",
|
||||
'saveas': "saveas",
|
||||
})
|
||||
# TODO: Verify that an error message was in fact returned.
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(urlparse(r.url).path, url)
|
||||
|
||||
'name': "f\u00E9ling",
|
||||
'public': "on",
|
||||
'notes': "Name too long",
|
||||
'base': meeting.schedule.base_id,
|
||||
})
|
||||
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):
|
||||
meeting = make_meeting_test_data()
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.conf.urls import include
|
|||
from django.views.generic import RedirectView
|
||||
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
|
||||
|
||||
safe_for_all_meeting_types = [
|
||||
|
@ -26,10 +26,7 @@ safe_for_all_meeting_types = [
|
|||
|
||||
|
||||
type_ietf_only_patterns = [
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit$' % settings.URL_REGEXPS,
|
||||
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/edit/?$' % settings.URL_REGEXPS, views.edit_meeting_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/timeslots/$' % settings.URL_REGEXPS, views.edit_meeting_timeslots_and_misc_sessions),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/details$' % settings.URL_REGEXPS, views.edit_schedule_properties),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/delete$' % settings.URL_REGEXPS, views.delete_schedule),
|
||||
|
@ -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-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/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/by-room$', views.agenda_by_room),
|
||||
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/(?P<slot_id>\d+)/edit$', views.edit_timeslot),
|
||||
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.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
|
||||
|
@ -88,7 +69,7 @@ type_ietf_only_patterns_id_optional = [
|
|||
url(r'^agenda(?P<ext>.csv)$', views.agenda),
|
||||
url(r'^agenda/edit$',
|
||||
RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True),
|
||||
name='ietf.meeting.views.edit_schedule'),
|
||||
name='ietf.meeting.views.edit_meeting_schedule'),
|
||||
url(r'^agenda/edit/$', views.edit_meeting_schedule),
|
||||
url(r'^requests$', views.meeting_requests),
|
||||
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.forms import ( CustomDurationField, SwapDaysForm, SwapTimeslotsForm,
|
||||
TimeSlotCreateForm, TimeSlotEditForm, SessionEditForm )
|
||||
from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.helpers import build_all_agenda_slices, get_wg_name_list
|
||||
from ietf.meeting.helpers import get_all_assignments_from_schedule
|
||||
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_person_by_email, get_schedule_by_name
|
||||
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 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", {
|
||||
'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')
|
||||
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 Meta:
|
||||
model = Schedule
|
||||
|
@ -1504,7 +1346,7 @@ def edit_schedule_properties(request, num, owner, name):
|
|||
form.save()
|
||||
if request.GET.get('next'):
|
||||
return HttpResponseRedirect(request.GET.get('next'))
|
||||
return redirect('ietf.meeting.views.edit_schedule', num=num, owner=owner, name=name)
|
||||
return redirect('ietf.meeting.views.edit_meeting_schedule', num=num, owner=owner, name=name)
|
||||
else:
|
||||
form = SchedulePropertiesForm(meeting, instance=schedule)
|
||||
|
||||
|
|
|
@ -6,14 +6,6 @@ from django.http import HttpResponse
|
|||
from ietf.ietfauth.utils import role_required
|
||||
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')
|
||||
def person_email_json(request, personid):
|
||||
person = get_object_or_404(Person, pk=personid)
|
||||
|
|
|
@ -228,18 +228,6 @@ class Person(models.Model):
|
|||
def defurl(self):
|
||||
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):
|
||||
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) ]))
|
||||
|
|
|
@ -4,7 +4,6 @@ from ietf.utils.urls import url
|
|||
urlpatterns = [
|
||||
url(r'^merge/$', views.merge),
|
||||
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<email_or_name>[^/]+)$', views.profile),
|
||||
url(r'^(?P<email_or_name>[^/]+)/photo/?$', views.photo),
|
||||
|
|
|
@ -539,10 +539,6 @@ def edit(request, acronym, num=None):
|
|||
# send notification
|
||||
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')
|
||||
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 %}
|
||||
|
||||
<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>
|
||||
|
|
|
@ -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 %}
|
||||
<tr>
|
||||
<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>{{ schedule.owner }}</td>
|
||||
<td>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
|
Loading…
Reference in a new issue