Tear out old meeting schedule editor and related code

- Legacy-Id: 19551
This commit is contained in:
Jennifer Richards 2021-11-04 17:07:22 +00:00
parent 7b35c09c40
commit d7f20342b6
22 changed files with 88 additions and 6161 deletions

View file

@ -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']

View file

@ -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),

View file

@ -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):

View file

@ -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")

View file

@ -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

View file

@ -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:

View file

@ -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')

View file

@ -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)

View file

@ -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()

View file

@ -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),

View file

@ -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)

View file

@ -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)

View file

@ -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) ]))

View file

@ -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),

View file

@ -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)

View file

@ -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:
*/

View file

@ -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

View file

@ -49,7 +49,7 @@
&middot;
{% 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>
&middot;
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>

View file

@ -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">
&lt;
</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'}}&nbsp;({{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 %}

View file

@ -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>