Simplified the view that lets the secretariat see and change timeslot types. Fixes #2313. Commit ready for merge.

- Legacy-Id: 14634
This commit is contained in:
Robert Sparks 2018-02-09 17:05:11 +00:00
parent 0306f0ad57
commit 012d51b63c
5 changed files with 105 additions and 185 deletions

View file

@ -34,7 +34,7 @@ from ietf.utils.text import xslugify
from ietf.person.factories import PersonFactory
from ietf.group.factories import GroupFactory, GroupEventFactory
from ietf.meeting.factories import ( SessionFactory, SessionPresentationFactory, ScheduleFactory,
MeetingFactory, FloorPlanFactory )
MeetingFactory, FloorPlanFactory, TimeSlotFactory )
from ietf.doc.factories import DocumentFactory
@ -536,6 +536,17 @@ class EditTests(TestCase):
self.assertEqual(r.status_code, 200)
self.assertTrue(meeting.room_set.all().first().name in unicontent(r))
def test_edit_timeslot_type(self):
timeslot = TimeSlotFactory()
url = urlreverse('ietf.meeting.views.edit_timeslot_type', kwargs=dict(num=timeslot.meeting.number,slot_id=timeslot.id))
login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
r = self.client.post(url,{'type':'other',})
self.assertEqual(r.status_code, 302)
timeslot = TimeSlot.objects.get(id=timeslot.id)
self.assertEqual(timeslot.type.slug,'other')
def test_slot_to_the_right(self):
meeting = make_meeting_test_data()
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()

View file

@ -41,6 +41,7 @@ type_ietf_only_patterns = [
url(r'^agendas/list$', views.list_agendas),
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_agendas', permanent=True)),
url(r'^timeslots/edit$', views.edit_timeslots),
url(r'^timeslot/(?P<slot_id>\d+)/edittype$', views.edit_timeslot_type),
url(r'^rooms$', ajax.timeslot_roomsurl),
url(r'^room/(?P<roomid>\d+).json$', ajax.timeslot_roomurl),
url(r'^timeslots$', ajax.timeslot_slotsurl),

View file

@ -6,7 +6,7 @@ import re
import tarfile
import urllib
from tempfile import mkstemp
from collections import OrderedDict, Counter
from collections import OrderedDict, Counter, deque
import csv
import json
import pytz
@ -39,7 +39,7 @@ from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent
from ietf.group.models import Group
from ietf.group.utils import can_manage_materials
from ietf.ietfauth.utils import role_required, has_role
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPresentation
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPresentation, TimeSlot
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
@ -266,41 +266,29 @@ def agenda_create(request, num=None, owner=None, name=None):
@role_required('Secretariat')
@ensure_csrf_cookie
def edit_timeslots(request, num=None):
meeting = get_meeting(num)
timeslots = meeting.timeslot_set.exclude(location=None).select_related("location", "type")
time_slices,date_slices,slots = meeting.build_timeslices()
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.order_by("capacity")
# this import locate here to break cyclic loop.
from ietf.meeting.ajax import timeslot_roomsurl, AddRoomForm, timeslot_slotsurl, AddSlotForm
roomsurl = reverse(timeslot_roomsurl, args=[meeting.number])
adddayurl = reverse(timeslot_slotsurl, args=[meeting.number])
ts_list = deque()
rooms = meeting.room_set.order_by("capacity","name","id")
for room in rooms:
for day in time_slices:
for slice in date_slices[day]:
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])).first())
return render(request, "meeting/timeslot_edit.html",
{"timeslots": timeslots,
"meeting_base_url": meeting_base_url,
"site_base_url": site_base_url,
"rooms":rooms,
"addroom": AddRoomForm(),
"roomsurl": roomsurl,
"addday": AddSlotForm(),
"adddayurl":adddayurl,
{"rooms":rooms,
"time_slices":time_slices,
"slot_slices": slots,
"date_slices":date_slices,
"meeting":meeting,
"hide_menu": True,
"ts_list":ts_list,
})
##############################################################################
#@role_required('Area Director','Secretariat')
# disable the above security for now, check it below.
@ -2204,3 +2192,26 @@ def important_dates(request, num=None):
context={'meetings':meetings}
return render(request, 'meeting/important-dates.html', context)
TimeSlotTypeForm = modelform_factory(TimeSlot, fields=('type',))
@role_required('Secretariat')
def edit_timeslot_type(request, num, slot_id):
timeslot = get_object_or_404(TimeSlot,id=slot_id)
meeting = get_object_or_404(Meeting,number=num)
if timeslot.meeting!=meeting:
raise Http404()
if request.method=='POST':
form = TimeSlotTypeForm(instance=timeslot,data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots',kwargs={'num':num}))
else:
form = TimeSlotTypeForm(instance=timeslot)
sessions = timeslot.sessions.filter(timeslotassignments__schedule=meeting.agenda)
return render(request, 'meeting/edit_timeslot_type.html', {'timeslot':timeslot,'form':form,'sessions':sessions})

View file

@ -0,0 +1,27 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2018, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}
{% block title %}Edit timeslot type for {{timeslot}}{% endblock %}
{% block content %}
{% origin %}
<h1>Edit timeslot type for {{timeslot}}</h1>
{% if sessions %}
<div class="alert alert-warning">
This timeslot currently has the following sessions assigned to it:
{% for s in sessions %}
<div>{{s}}</div>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Save</button>
<a class="btn btn-default" href="{% url 'ietf.meeting.views.edit_timeslots' num=timeslot.meeting.number %}">Cancel</a>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -1,187 +1,57 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load staticfiles %}
{% load ietf_filters %}
{% load humanize %}
{% load agenda_custom_tags %}
{% block title %}IETF {{ meeting.number }} Meeting Agenda: Timeslot/Room Availability{% 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/base2.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 'jquery.cookie/jquery.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", $.cookie('csrftoken'));
}
}
});
</script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery-ui.custom.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.widget.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.droppable.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.sortable.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.accordion.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.draggable.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-1.9.0.custom/jquery.ui.datepicker.js' %}"></script>
<!-- source (MIT License) http://momentjs.com/ https://github.com/moment/moment/ -->
<script type='text/javascript' src="{% static 'ietf/js/agenda/moment.min.js' %}"></script>
<!-- source (MIT License) : https://github.com/trentrichardson/jQuery-Timepicker-Addon -->
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-timepicker/jquery-ui-timepicker-addon.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/jquery-ui-timepicker/jquery-ui-sliderAccess.js' %}"></script>
<link rel='stylesheet' type='text/css' href="{% static 'ietf/css/agenda/jquery-ui-timepicker-addon.css' %}" />
<script type='text/javascript' src="{% static 'spin.js/spin.min.js' %}"></script>
<script type='text/javascript' src="{% static 'ietf/js/agenda/timeslot_edit.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_helpers.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 meeting_base_url = "{{ meeting_base_url }}";
var site_base_url = "{{ site_base_url }}";
var meeting_slots_href = "{% url "ietf.meeting.ajax.timeslot_slotsurl" meeting.number %}";
total_days = {{time_slices|length}};
total_rooms = {{rooms|length}};
first_day = new Date("{% with timeslots|first as day %} {{ day.time }} {% endwith %}"); /* needed for the datepicker */
function setup_slots(promiselist){
var ts_promise = load_timeslots(meeting_slots_href);
promiselist.push(ts_promise);
{% for day in time_slices %}
days.push("{{day}}");
{% endfor %}
console.log("setup_slots run");
}
</script>
{% endblock js %}
{% block morecss %}
.tstable { width: 100%;}
.tstable th { white-space: nowrap;}
.tstable td { white-space: nowrap;}
.capacity { font-size:80%; font-weight: normal;}
.tstable .tstype_unavail {background-color:#666;}
{% endblock %}
{% block content %}
{% origin %}
<div class="wrapper custom_text_stuff">
<div style="ui-icon ui-icon-arrow-1-w" id="close_ietf_menubar">
&lt;
</div>
<div class="agenda_div">
<table id="meetings" class="ietf-navbar" style="width:100%">
<th class="schedule_title"><div id="pageloaded" style="display:none"><span id="schedule_name">name: {{meeting.number}}</span></div>
<div id="spinner"><!-- spinney goes here --></div>
</th>
<th><!-- resources --></th>
{% for day in time_slices %}
<th colspan="{{date_slices|colWidth:day}}" id="{{day|date:'Y-m-d'}}-btn" class=" day_{{day}} agenda_slot_title">
<div style="display: none;" class="delete delete_day bottom_left" id="delete_{{day|date:'Y-m-d'}}">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>
<th>
<div class="addbutton" id="add_room">+ROOM</div>
<div class="addbutton" id="add_day">+DAY</div>
</th>
<th><!-- resources --></th>
<table class="tstable table table-striped table-compact table-bordered">
<thead>
<tr>
<th></th>
{% for day in time_slices %}
<th colspan="{{date_slices|colWidth:day}}">
{{day|date:'D'}}&nbsp;({{day}})
</th>
{% endfor %}
</tr>
<tr>
<th></th>
{% for day in time_slices %}
{% for slot in slot_slices|lookup:day %}
<th class="day_{{day}} room_title">
<div
href="{{slot.json_url}}"
timeslot_id="{{slot.pk}}"
class="delete delete_slot bottom_left"
id="delete_{{day|date:'Y-m-d'}}_{{slot.time|date:'Hi'}}">X</div>
{{slot.time|date:'Hi'}}-{{slot.end_time|date:'Hi'}}
<th>
{{slot.time|date:'Hi'}}-{{slot.end_time|date:'Hi'}}
</th>
{% endfor %}
<th class="day_{{day}} spacer {{day|date:'Y-m-d'}}-spacer">
</div></th>
{% endfor %}
</tr>
</thead>
{% for r in rooms %}
<tr id="{{r.name|to_acceptable_id}}" class="agenda_slot_row">
<th class="vert_time">
<div class="delete delete_room bottom_left"
id="delete_{{r.name|to_acceptable_id}}"
href="{{r.json_url}}"
roomid="{{r.pk}}">X</div>
<div class="right room_name"><a class="edit_room editlink"
href="/meeting/{{ meeting.number }}/room/{{r.pk}}.html" >{{r.name}} <span class="capacity">({{r.capacity}})</span></a></div>
<!-- <span class="hide_room light_blue_border">X</span><span class="left">{{r.name}}</span></th> -->
<th class="room_features">
<div class="resource_list">
{% for resource in r.resources.all %}
<span class="resource_image">
{% if resource.id == "project" %} <img src="{% static 'ietf/images/projector.png' %}" height=24 alt="{{resource.desc}}" title="{{resource.desc}}"/>{% endif %}
{% if resource.id == "proj2" %} <img src="{% static 'ietf/images/projector2.png' %}" height=24 alt="{{resource.desc}}" title="{{resource.desc}}"/>{% endif %}
{% if resource.id == "meetecho" %} <img src="{% static 'ietf/images/meetecho-mini.png' %}" height=24 alt="{{resource.desc}}" title="{{resource.desc}}"/>{% endif %}
{% if resource.id == "boardroom" %} <img src="{% static 'ietf/images/projector.png' %}" height=24 alt="{{resource.desc}}" title="{{resource.desc}}"/>{% endif %}
</span>
{% endfor %}
</div>
</th>
{% for room in rooms %}
<tr>
<th>{{room.name}}<span class='capacity'>{% if room.capacity %} ({{room.capacity}}){% endif %}</th>
{% for day in time_slices %}
{% for slot in date_slices|lookup:day %}
<td slot_time="{{day}} {{slot.0|date:'H:i:s'}}" slot_duration="{{slot.2}}" slot_room="{{r.pk}}" id="{{r.dom_id}}_{{day}}_{{slot.0|date:'Hi'}}" class="day_{{day}} agenda-column-{{day}}-{{slot.0|date:'Hi'}} agenda_slot {% cycle 'agenda_slot_alt' '' %} agenda_slot_unavailable" ></td>
{% for slice in date_slices|lookup:day %}
{% with ts=ts_list.popleft %}
<td{% if ts %} class="tstype_{{ts.type.slug}}"{% endif %}>{% if ts %}<a href="{% url 'ietf.meeting.views.edit_timeslot_type' num=meeting.number slot_id=ts.id %}">{{ts.type.slug}}</a>{% endif %}</td>
{% endwith %}
{% endfor %}
<td class="day_{{day}} spacer {{day|date:'Y-m-d'}}-spacer"></td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
<div class="dialog" id="add_room_dialog">
<form action="{{roomsurl}}" method="post">{% csrf_token %}
<table>
{{ addroom.as_table }}
<tr><td><input type="submit" name="addroom" value="addroom"></td></tr>
</table>
</form>
</div>
<div class="dialog" id="add_day_dialog">
<table>
<form action="{{adddayurl}}" method="post">{% csrf_token %}
{{ addday }}
<tr><th><label>Duration</label></th><td><input type="text" id="duration_time"></td></tr>
<tr><th></th><td><div id="timespan"></div></td></tr>
<tr><td><input type="submit" name="addday" value="addday"></td></tr>
</form>
</table>
</div>
<div class="dialog" id="room_delete_dialog">
Are you sure you want to delete this room?
</div>
<div class="dialog" id="slot_delete_dialog">
Are you sure you want to delete this entire timeslot?
</div>
{% endblock %}