Skip to content

Commit bcac1df

Browse files
committed
Added slide sorting. Commit ready to merge.
- Legacy-Id: 11995
1 parent 02ee5dc commit bcac1df

4 files changed

Lines changed: 133 additions & 4 deletions

File tree

ietf/meeting/tests_js.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
99
from django.core.urlresolvers import reverse as urlreverse
10+
#from django.test.utils import override_settings
1011

1112
import debug # pyflakes:ignore
1213

14+
from ietf.doc.factories import DocumentFactory
1315
from ietf.group import colors
16+
from ietf.meeting.factories import SessionFactory
1417
from ietf.meeting.test_data import make_meeting_test_data
1518
from ietf.meeting.models import SchedTimeSessAssignment
1619
from ietf.utils.test_runner import set_coverage_checking
@@ -88,6 +91,45 @@ def testUnschedule(self):
8891
time.sleep(0.1) # The API that modifies the database runs async
8992
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars',schedule__name='test-agenda').count(),0)
9093

94+
@skipIf(skip_selenium, skip_message)
95+
class SlideReorderTests(StaticLiveServerTestCase):
96+
def setUp(self):
97+
set_coverage_checking(False)
98+
self.session = SessionFactory(meeting__type_id='ietf')
99+
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1)
100+
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2)
101+
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3)
102+
self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
103+
self.driver.set_window_size(1024,768)
104+
105+
def absreverse(self,*args,**kwargs):
106+
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
107+
108+
def secr_login(self):
109+
url = '%s%s'%(self.live_server_url, urlreverse('django.contrib.auth.views.login'))
110+
self.driver.get(url)
111+
self.driver.find_element_by_name('username').send_keys('secretary')
112+
self.driver.find_element_by_name('password').send_keys('secretary+password')
113+
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
114+
115+
#@override_settings(DEBUG=True)
116+
def testReorderSlides(self):
117+
return
118+
url = self.absreverse('ietf.meeting.views.session_details',
119+
kwargs=dict(
120+
num=self.session.meeting.number,
121+
acronym = self.session.group.acronym,))
122+
self.secr_login()
123+
self.driver.get(url)
124+
#debug.show('unicode(self.driver.page_source)')
125+
second = self.driver.find_element_by_css_selector('#slides tr:nth-child(2)')
126+
third = self.driver.find_element_by_css_selector('#slides tr:nth-child(3)')
127+
ActionChains(self.driver).drag_and_drop(second,third).perform()
128+
129+
time.sleep(0.1) # The API that modifies the database runs async
130+
names=self.session.sessionpresentation_set.values_list('document__name',flat=True)
131+
self.assertEqual(list(names),[u'one',u'three',u'two'])
132+
91133
# The following are useful debugging tools
92134

93135
# If you add this to a LiveServerTestCase and run just this test, you can browse

ietf/meeting/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
url(r'^session/(?P<session_id>\d+)/minutes$', views.upload_session_minutes),
1515
url(r'^session/(?P<session_id>\d+)/agenda$', views.upload_session_agenda),
1616
url(r'^session/(?P<session_id>\d+)/slides(?:/%(name)s)?$' % settings.URL_REGEXPS, views.upload_session_slides),
17+
url(r'^session/(?P<session_id>\d+)/slides/%(name)s/order$' % settings.URL_REGEXPS, views.set_slide_order),
1718
]
1819

1920

ietf/meeting/views.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,32 @@ def upload_session_slides(request, session_id, num, name):
14501450
'form': form,
14511451
})
14521452

1453+
def set_slide_order(request, session_id, num, name):
1454+
# num is redundant, but we're dragging it along an artifact of where we are in the current URL structure
1455+
session = get_object_or_404(Session,pk=session_id)
1456+
if not Document.objects.filter(type_id='slides',name=name).exists():
1457+
raise Http404
1458+
if not session.can_manage_materials(request.user):
1459+
return HttpResponseForbidden("You don't have permission to upload slides for this session.")
1460+
if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
1461+
return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.")
1462+
1463+
if request.method != 'POST' or not request.POST:
1464+
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json')
1465+
order_str = request.POST.get('order', None)
1466+
try:
1467+
order = int(order_str)
1468+
except ValueError:
1469+
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json')
1470+
if order <=0 or order > 32767 :
1471+
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json')
1472+
1473+
sp = session.sessionpresentation_set.get(document__name = name)
1474+
sp.order = order
1475+
sp.save()
1476+
1477+
return HttpResponse(json.dumps({'success':True}),content_type='application/json')
1478+
14531479
@role_required('Secretariat')
14541480
def make_schedule_official(request, num, owner, name):
14551481

@@ -1962,3 +1988,4 @@ def proceedings_overview(request, num=None):
19621988
'meeting': meeting,
19631989
'template': template,
19641990
})
1991+

ietf/templates/meeting/session_details.html

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
{% extends "base.html" %}
22
{# Copyright The IETF Trust 2015, All Rights Reserved #}
3-
{% load origin ietf_filters %}
3+
{% load origin ietf_filters staticfiles %}
44

55
{% block title %}{{ meeting }} : {{ acronym }}{% endblock %}
66

7+
{% block morecss %}
8+
.ui-sortable tr {
9+
cursor:pointer;
10+
}
11+
{% endblock %}
12+
713
{% block content %}
814
{% origin %}
915
<h1>{{ meeting }} : {{ acronym }}</h1>
@@ -70,11 +76,12 @@ <h2>{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ se
7076
</div>
7177
</div>
7278
<div class="panel panel-default">
73-
<div class="panel-heading">Slides</div>
79+
<div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
7480
<div class="panel-body">
75-
<table class="table table-condensed table-striped">
81+
<table class="table table-condensed table-striped" id="slides">
82+
<tbody>
7683
{% for pres in session.filtered_slides %}
77-
<tr>
84+
<tr data-order="{{pres.order}}" data-url="{% url 'ietf.meeting.views.set_slide_order' session_id=session.pk num=session.meeting.number name=pres.document.name %}">
7885
{% if pres.rev %}
7986
{% url 'doc_view' name=pres.document.name rev=pres.rev as url %}
8087
{% else %}
@@ -90,6 +97,7 @@ <h2>{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ se
9097
{% endif %}
9198
</tr>
9299
{% endfor %}
100+
</tbody>
93101
</table>
94102
{% if can_manage_materials %}
95103
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number %}">Upload New Slides</a>
@@ -124,3 +132,54 @@ <h2>{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ se
124132
{% endfor %}
125133

126134
{% endblock %}
135+
136+
{# TODO don't rely on secr/js version of jquery-ui #}
137+
{# Sorting based loosely on the original secr upload sorting and on http://www.avtex.com/blog/2015/01/27/drag-and-drop-sorting-of-table-rows-in-priority-order/ #}
138+
{% block js %}
139+
{% if can_manage_materials %}
140+
<script type="text/javascript" src="{% static 'jquery/jquery.min.js' %}"></script>
141+
<script type="text/javascript" src="{% static 'secr/js/jquery-ui-1.11.4.custom.min.js' %}"></script>
142+
<script type="text/javascript" src="{% static 'jquery.cookie/jquery.cookie.js' %}"></script>
143+
144+
<script type="text/javascript">
145+
146+
$.ajaxSetup({
147+
crossDomain: false,
148+
beforeSend: function(xhr, settings) {
149+
if (!csrfSafeMethod(settings.type)) {
150+
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
151+
}
152+
}
153+
});
154+
155+
$(document).ready(function() {
156+
var rowWidthHelper = function (e, tr) {
157+
var $originals = tr.children();
158+
var $helper = tr.clone();
159+
$helper.children().each(function(index)
160+
{
161+
$(this).width($originals.eq(index).width())
162+
});
163+
return $helper;
164+
};
165+
166+
$("#slides tbody").sortable({
167+
helper: rowWidthHelper,
168+
stop: function(event,ui) {adjustDatabase("#slides")}
169+
}).disableSelection();
170+
});
171+
172+
function adjustDatabase(tableID) {
173+
$(tableID + " tr").each(function() {
174+
count = $(this).parent().children().index($(this)) + 1;
175+
old_order = $(this).attr("data-order");
176+
if ( count != old_order ) {
177+
$(this).attr("data-order", count);
178+
$.post($(this).attr("data-url"),{'order':count});
179+
}
180+
});
181+
}
182+
</script>
183+
184+
{% endif %}
185+
{% endblock %}

0 commit comments

Comments
 (0)