Allow slides drag-drop between sessions for groups meeting with multiple sessions. Fixes #2700. Commit ready to merge.

- Legacy-Id: 17024
This commit is contained in:
Robert Sparks 2019-11-15 20:54:14 +00:00
parent 34f93932a2
commit 1016b3a514
7 changed files with 493 additions and 57 deletions

File diff suppressed because one or more lines are too long

View file

@ -23,6 +23,7 @@ from django.urls import reverse as urlreverse
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import Client from django.test import Client
from django.db.models import F
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -34,7 +35,7 @@ from ietf.meeting.helpers import send_interim_cancellation_notice
from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates
from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import finalize from ietf.meeting.utils import finalize, condition_slide_order
from ietf.name.models import SessionStatusName, ImportantDateName from ietf.name.models import SessionStatusName, ImportantDateName
from ietf.utils.decorators import skip_coverage from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox from ietf.utils.mail import outbox, empty_outbox
@ -569,18 +570,330 @@ class MeetingTests(TestCase):
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
self.assertEqual(response.get('Content-Type'), 'text/calendar') self.assertEqual(response.get('Content-Type'), 'text/calendar')
def test_edit_slide_order(self): class ReorderSlidesTests(TestCase):
session=SessionFactory(meeting__type_id='iestf',type_id='session')
slides = DocumentFactory(type_id='slides') def test_add_slides_to_session(self):
session.sessionpresentation_set.create(document=slides,order=0) for type_id in ('ietf','interim'):
url = urlreverse('ietf.meeting.views.set_slide_order',kwargs={'session_id':session.id,'num':session.meeting.number,'name':slides.name}) chair_role = RoleFactory(name_id='chair')
response = self.client.put(url,{'order':2}) session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90), meeting__type_id=type_id)
self.assertEqual(response.status_code, 403) slides = DocumentFactory(type_id='slides')
self.client.login(username='secretary', password='secretary+password') url = urlreverse('ietf.meeting.views.ajax_add_slides_to_session', kwargs={'session_id':session.pk, 'num':session.meeting.number})
response = self.client.post(url,{'order':'2'})
self.assertEqual(response.status_code, 200) # Not a valid user
self.assertEqual(response.get('Content-Type'), 'application/json') r = self.client.post(url, {'order':1, 'name':slides.name })
self.assertEqual(session.sessionpresentation_set.first().order,2) self.assertEqual(r.status_code, 403)
self.assertIn('have permission', unicontent(r))
self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password")
# Past submission cutoff
r = self.client.post(url, {'order':0, 'name':slides.name })
self.assertEqual(r.status_code, 403)
self.assertIn('materials cutoff', unicontent(r))
session.meeting.date = datetime.date.today()
session.meeting.save()
# Invalid order
r = self.client.post(url, {})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('No data',r.json()['error'])
r = self.client.post(url, {'garbage':'garbage'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('order is not valid',r.json()['error'])
r = self.client.post(url, {'order':0, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('order is not valid',r.json()['error'])
r = self.client.post(url, {'order':2, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('order is not valid',r.json()['error'])
r = self.client.post(url, {'order':'garbage', 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('order is not valid',r.json()['error'])
# Invalid name
r = self.client.post(url, {'order':1 })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('name is not valid',r.json()['error'])
r = self.client.post(url, {'order':1, 'name':'garbage' })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('name is not valid',r.json()['error'])
# Valid post
r = self.client.post(url, {'order':1, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session.sessionpresentation_set.count(),1)
# Ingore a request to add slides that are already in a session
r = self.client.post(url, {'order':1, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session.sessionpresentation_set.count(),1)
session2 = SessionFactory(group=session.group, meeting=session.meeting)
SessionPresentationFactory.create_batch(3, document__type_id='slides', session=session2)
for num, sp in enumerate(session2.sessionpresentation_set.filter(document__type_id='slides'),start=1):
sp.order = num
sp.save()
url = urlreverse('ietf.meeting.views.ajax_add_slides_to_session', kwargs={'session_id':session2.pk, 'num':session2.meeting.number})
more_slides = DocumentFactory.create_batch(3, type_id='slides')
# Insert at beginning
r = self.client.post(url, {'order':1, 'name':more_slides[0].name})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[0]).order,1)
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,5))
# Insert at end
r = self.client.post(url, {'order':5, 'name':more_slides[1].name})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[1]).order,5)
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,6))
# Insert in middle
r = self.client.post(url, {'order':3, 'name':more_slides[2].name})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[2]).order,3)
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,7))
def test_remove_slides_from_session(self):
for type_id in ['ietf','interim']:
chair_role = RoleFactory(name_id='chair')
session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90), meeting__type_id=type_id)
slides = DocumentFactory(type_id='slides')
url = urlreverse('ietf.meeting.views.ajax_remove_slides_from_session', kwargs={'session_id':session.pk, 'num':session.meeting.number})
# Not a valid user
r = self.client.post(url, {'oldIndex':1, 'name':slides.name })
self.assertEqual(r.status_code, 403)
self.assertIn('have permission', unicontent(r))
self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password")
# Past submission cutoff
r = self.client.post(url, {'oldIndex':0, 'name':slides.name })
self.assertEqual(r.status_code, 403)
self.assertIn('materials cutoff', unicontent(r))
session.meeting.date = datetime.date.today()
session.meeting.save()
# Invalid order
r = self.client.post(url, {})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('No data',r.json()['error'])
r = self.client.post(url, {'garbage':'garbage'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
r = self.client.post(url, {'oldIndex':0, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
r = self.client.post(url, {'oldIndex':'garbage', 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
# No matching thing to delete
r = self.client.post(url, {'oldIndex':1, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
session.sessionpresentation_set.create(document=slides, rev=slides.rev, order=1)
# Bad names
r = self.client.post(url, {'oldIndex':1})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('name is not valid',r.json()['error'])
r = self.client.post(url, {'oldIndex':1, 'name':'garbage' })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('name is not valid',r.json()['error'])
slides2 = DocumentFactory(type_id='slides')
# index/name mismatch
r = self.client.post(url, {'oldIndex':1, 'name':slides2.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('SessionPresentation not found',r.json()['error'])
session.sessionpresentation_set.create(document=slides2, rev=slides2.rev, order=2)
r = self.client.post(url, {'oldIndex':1, 'name':slides2.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('Name does not match index',r.json()['error'])
# valid removal
r = self.client.post(url, {'oldIndex':1, 'name':slides.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(session.sessionpresentation_set.count(),1)
session2 = SessionFactory(group=session.group, meeting=session.meeting)
sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session2)
for num, sp in enumerate(session2.sessionpresentation_set.filter(document__type_id='slides'),start=1):
sp.order = num
sp.save()
url = urlreverse('ietf.meeting.views.ajax_remove_slides_from_session', kwargs={'session_id':session2.pk, 'num':session2.meeting.number})
# delete at first of list
r = self.client.post(url, {'oldIndex':1, 'name':sp_list[0].document.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[0].pk).exists())
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,5))
# delete in middle of list
r = self.client.post(url, {'oldIndex':4, 'name':sp_list[4].document.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[4].pk).exists())
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,4))
# delete at end of list
r = self.client.post(url, {'oldIndex':2, 'name':sp_list[2].document.name })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[2].pk).exists())
self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), range(1,3))
def test_reorder_slides_in_session(self):
chair_role = RoleFactory(name_id='chair')
session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90))
sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session)
for num, sp in enumerate(sp_list, start=1):
sp.order = num
sp.save()
url = urlreverse('ietf.meeting.views.ajax_reorder_slides_in_session', kwargs={'session_id':session.pk, 'num':session.meeting.number})
for type_id in ['ietf','interim']:
session.meeting.type_id = type_id
session.meeting.date = datetime.date.today()-datetime.timedelta(days=90)
session.meeting.save()
# Not a valid user
r = self.client.post(url, {'oldIndex':1, 'newIndex':2 })
self.assertEqual(r.status_code, 403)
self.assertIn('have permission', unicontent(r))
self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password")
# Past submission cutoff
r = self.client.post(url, {'oldIndex':1, 'newIndex':2 })
self.assertEqual(r.status_code, 403)
self.assertIn('materials cutoff', unicontent(r))
session.meeting.date = datetime.date.today()
session.meeting.save()
# Bad index values
r = self.client.post(url, {'oldIndex':0, 'newIndex':2 })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
r = self.client.post(url, {'oldIndex':2, 'newIndex':6 })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
r = self.client.post(url, {'oldIndex':2, 'newIndex':2 })
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],False)
self.assertIn('index is not valid',r.json()['error'])
# Move from beginning
r = self.client.post(url, {'oldIndex':1, 'newIndex':3})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,1,4,5])
# Move to beginning
r = self.client.post(url, {'oldIndex':3, 'newIndex':1})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,3,4,5])
# Move from end
r = self.client.post(url, {'oldIndex':5, 'newIndex':3})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,5,3,4])
# Move to end
r = self.client.post(url, {'oldIndex':3, 'newIndex':5})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,3,4,5])
# Move beginning to end
r = self.client.post(url, {'oldIndex':1, 'newIndex':5})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,4,5,1])
# Move middle to middle
r = self.client.post(url, {'oldIndex':3, 'newIndex':4})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,5,4,1])
r = self.client.post(url, {'oldIndex':3, 'newIndex':2})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['success'],True)
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,5,3,4,1])
# Reset for next iteration in the loop
session.sessionpresentation_set.update(order=F('pk'))
self.client.logout()
def test_slide_order_reconditioning(self):
chair_role = RoleFactory(name_id='chair')
session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90))
sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session)
for num, sp in enumerate(sp_list, start=1):
sp.order = 2*num
sp.save()
try:
condition_slide_order(session)
except AssertionError:
pass
self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('order',flat=True)),range(1,6))
class EditTests(TestCase): class EditTests(TestCase):
def setUp(self): def setUp(self):

View file

@ -1,4 +1,4 @@
# Copyright The IETF Trust 2007, All Rights Reserved # Copyright The IETF Trust 2007-2019, All Rights Reserved
from django.conf.urls import include from django.conf.urls import include
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -15,7 +15,9 @@ safe_for_all_meeting_types = [
url(r'^session/(?P<session_id>\d+)/agenda$', views.upload_session_agenda), url(r'^session/(?P<session_id>\d+)/agenda$', views.upload_session_agenda),
url(r'^session/(?P<session_id>\d+)/propose_slides$', views.propose_session_slides), url(r'^session/(?P<session_id>\d+)/propose_slides$', views.propose_session_slides),
url(r'^session/(?P<session_id>\d+)/slides(?:/%(name)s)?$' % settings.URL_REGEXPS, views.upload_session_slides), url(r'^session/(?P<session_id>\d+)/slides(?:/%(name)s)?$' % settings.URL_REGEXPS, views.upload_session_slides),
url(r'^session/(?P<session_id>\d+)/slides/%(name)s/order$' % settings.URL_REGEXPS, views.set_slide_order), url(r'^session/(?P<session_id>\d+)/add_to_session$', views.ajax_add_slides_to_session),
url(r'^session/(?P<session_id>\d+)/remove_from_session$', views.ajax_remove_slides_from_session),
url(r'^session/(?P<session_id>\d+)/reorder_in_session$', views.ajax_reorder_slides_in_session),
url(r'^session/(?P<session_id>\d+)/doc/%(name)s/remove$' % settings.URL_REGEXPS, views.remove_sessionpresentation), url(r'^session/(?P<session_id>\d+)/doc/%(name)s/remove$' % settings.URL_REGEXPS, views.remove_sessionpresentation),
url(r'^session/(?P<session_id>\d+)\.ics$', views.ical_agenda), url(r'^session/(?P<session_id>\d+)\.ics$', views.ical_agenda),
url(r'^sessions/(?P<acronym>[-a-z0-9]+)\.ics$', views.ical_agenda), url(r'^sessions/(?P<acronym>[-a-z0-9]+)\.ics$', views.ical_agenda),

View file

@ -19,6 +19,7 @@ from ietf.meeting.models import Session, Meeting
from ietf.group.utils import can_manage_materials from ietf.group.utils import can_manage_materials
from ietf.person.models import Email from ietf.person.models import Email
from ietf.secr.proceedings.proc_utils import import_audio_files from ietf.secr.proceedings.proc_utils import import_audio_files
from ietf.utils.log import unreachable
def group_sessions(sessions): def group_sessions(sessions):
@ -178,3 +179,15 @@ def sort_accept_tuple(accept):
tup.append((keys[0], q)) tup.append((keys[0], q))
return sorted(tup, key = lambda x: float(x[1]), reverse = True) return sorted(tup, key = lambda x: float(x[1]), reverse = True)
return tup return tup
def condition_slide_order(session):
qs = session.sessionpresentation_set.filter(document__type_id='slides').order_by('order')
order_list = qs.values_list('order',flat=True)
#assertion('list(order_list) == range(1,qs.count()+1)')
if list(order_list) != range(1,qs.count()+1):
for num, sp in enumerate(qs, start=1):
sp.order=num
sp.save()
unreachable('2019-11-15')

View file

@ -34,7 +34,7 @@ from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import URLValidator from django.core.validators import URLValidator
from django.urls import reverse,reverse_lazy from django.urls import reverse,reverse_lazy
from django.db.models import Min, Max, Q from django.db.models import Min, Max, Q, F
from django.forms.models import modelform_factory, inlineformset_factory from django.forms.models import modelform_factory, inlineformset_factory
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -67,8 +67,7 @@ from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
from ietf.meeting.helpers import send_interim_cancellation_notice from ietf.meeting.helpers import send_interim_cancellation_notice
from ietf.meeting.helpers import send_interim_approval_request from ietf.meeting.helpers import send_interim_approval_request
from ietf.meeting.helpers import send_interim_announcement_request from ietf.meeting.helpers import send_interim_announcement_request
from ietf.meeting.utils import finalize from ietf.meeting.utils import finalize, sort_accept_tuple, condition_slide_order
from ietf.meeting.utils import sort_accept_tuple
from ietf.message.utils import infer_message from ietf.message.utils import infer_message
from ietf.secr.proceedings.utils import handle_upload_file from ietf.secr.proceedings.utils import handle_upload_file
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files, from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
@ -1663,11 +1662,9 @@ def remove_sessionpresentation(request, session_id, num, name):
return render(request,'meeting/remove_sessionpresentation.html', {'sp': sp }) return render(request,'meeting/remove_sessionpresentation.html', {'sp': sp })
def set_slide_order(request, session_id, num, name): def ajax_add_slides_to_session(request, session_id, num):
# num is redundant, but we're dragging it along an artifact of where we are in the current URL structure
session = get_object_or_404(Session,pk=session_id) session = get_object_or_404(Session,pk=session_id)
if not Document.objects.filter(type_id='slides',name=name).exists():
raise Http404
if not session.can_manage_materials(request.user): if not session.can_manage_materials(request.user):
return HttpResponseForbidden("You don't have permission to upload slides for this session.") return HttpResponseForbidden("You don't have permission to upload slides for this session.")
if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"): if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
@ -1675,19 +1672,109 @@ def set_slide_order(request, session_id, num, name):
if request.method != 'POST' or not request.POST: if request.method != 'POST' or not request.POST:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json') return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json')
order_str = request.POST.get('order', None)
order_str = request.POST.get('order', None)
try: try:
order = int(order_str) order = int(order_str)
except ValueError: except (ValueError, TypeError):
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json') return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json')
if order <=0 or order > 32767 : if order < 1 or order > session.sessionpresentation_set.filter(document__type_id='slides').count() + 1 :
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json') return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json')
sp = session.sessionpresentation_set.get(document__name = name) name = request.POST.get('name', None)
sp.order = order doc = Document.objects.filter(name=name).first()
if not doc:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied name is not valid' }),content_type='application/json')
if not session.sessionpresentation_set.filter(document=doc).exists():
condition_slide_order(session)
session.sessionpresentation_set.filter(document__type_id='slides', order__gte=order).update(order=F('order')+1)
session.sessionpresentation_set.create(document=doc,rev=doc.rev,order=order)
DocEvent.objects.create(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person, desc="Added to session: %s" % session)
return HttpResponse(json.dumps({'success':True}), content_type='application/json')
def ajax_remove_slides_from_session(request, session_id, num):
session = get_object_or_404(Session,pk=session_id)
if not session.can_manage_materials(request.user):
return HttpResponseForbidden("You don't have permission to upload slides for this session.")
if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.")
if request.method != 'POST' or not request.POST:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json')
oldIndex_str = request.POST.get('oldIndex', None)
try:
oldIndex = int(oldIndex_str)
except (ValueError, TypeError):
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
if oldIndex < 1 or oldIndex > session.sessionpresentation_set.filter(document__type_id='slides').count() :
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
name = request.POST.get('name', None)
doc = Document.objects.filter(name=name).first()
if not doc:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied name is not valid' }),content_type='application/json')
condition_slide_order(session)
affected_presentations = session.sessionpresentation_set.filter(document=doc).first()
if affected_presentations:
if affected_presentations.order == oldIndex:
affected_presentations.delete()
session.sessionpresentation_set.filter(document__type_id='slides', order__gt=oldIndex).update(order=F('order')-1)
DocEvent.objects.create(type="added_comment", doc=doc, rev=doc.rev, by=request.user.person, desc="Removed from session: %s" % session)
return HttpResponse(json.dumps({'success':True}), content_type='application/json')
else:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Name does not match index' }),content_type='application/json')
else:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'SessionPresentation not found' }),content_type='application/json')
def ajax_reorder_slides_in_session(request, session_id, num):
session = get_object_or_404(Session,pk=session_id)
if not session.can_manage_materials(request.user):
return HttpResponseForbidden("You don't have permission to upload slides for this session.")
if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.")
if request.method != 'POST' or not request.POST:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json')
num_slides_in_session = session.sessionpresentation_set.filter(document__type_id='slides').count()
oldIndex_str = request.POST.get('oldIndex', None)
try:
oldIndex = int(oldIndex_str)
except (ValueError, TypeError):
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
if oldIndex < 1 or oldIndex > num_slides_in_session :
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
newIndex_str = request.POST.get('newIndex', None)
try:
newIndex = int(newIndex_str)
except (ValueError, TypeError):
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
if newIndex < 1 or newIndex > num_slides_in_session :
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
if newIndex == oldIndex:
return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied index is not valid' }),content_type='application/json')
condition_slide_order(session)
sp = session.sessionpresentation_set.get(order=oldIndex)
if oldIndex < newIndex:
session.sessionpresentation_set.filter(order__gt=oldIndex, order__lte=newIndex).update(order=F('order')-1)
else:
session.sessionpresentation_set.filter(order__gte=newIndex, order__lt=oldIndex).update(order=F('order')+1)
sp.order = newIndex
sp.save() sp.save()
return HttpResponse(json.dumps({'success':True}),content_type='application/json') return HttpResponse(json.dumps({'success':True}), content_type='application/json')
@role_required('Secretariat') @role_required('Secretariat')
def make_schedule_official(request, num, owner, name): def make_schedule_official(request, num, owner, name):

View file

@ -61,16 +61,15 @@
{% endblock %} {% endblock %}
{# TODO don't rely on secr/js version of jquery-ui #}
{# 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/ #}
{% block js %} {% block js %}
{% if can_manage_materials %} {% if can_manage_materials %}
<script type="text/javascript" src="{% static 'jquery/jquery.min.js' %}"></script> <script type="text/javascript" src="{% static 'jquery/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'secr/js/jquery-ui-1.11.4.custom.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery.cookie/jquery.cookie.js' %}"></script> <script type="text/javascript" src="{% static 'jquery.cookie/jquery.cookie.js' %}"></script>
<script type="text/javascript" src={% static 'Sortable/Sortable.min.js' %}></script>
<script type="text/javascript"> <script type="text/javascript">
$.ajaxSetup({ $.ajaxSetup({
crossDomain: false, crossDomain: false,
beforeSend: function(xhr, settings) { beforeSend: function(xhr, settings) {
@ -80,33 +79,53 @@
} }
}); });
$(document).ready(function() {
var rowWidthHelper = function (e, tr) {
var $originals = tr.children();
var $helper = tr.clone();
$helper.children().each(function(index)
{
$(this).width($originals.eq(index).width())
});
return $helper;
};
$(".slides tbody").sortable({ var sortables=[];
helper: rowWidthHelper, var options = {
stop: function(event,ui) {adjustDatabase(ui.item.parent())} group: "slides",
}).disableSelection(); animation: 150,
}); onAdd: function(event) {onAdd(event)},
onRemove: function(event) {onRemove(event)},
onEnd: function(event) {onEnd(event)}
};
function adjustDatabase(tbody) { function onAdd(event) {
tbody.find('tr').each(function() { var old_session = event.from.getAttribute("session");
count = $(this).parent().children().index($(this)) + 1; var new_session = event.to.getAttribute("session");
old_order = $(this).attr("data-order"); $.post(event.to.getAttribute("addToSession"), {
if ( count != old_order ) { 'order': event.newIndex + 1,
$(this).attr("data-order", count); 'name': event.item.getAttribute("name")
$.post($(this).attr("data-url"),{'order':count}); });
} $(event.item).find("td:eq(1)").find("a").each(function(){
$(this).attr("href", $(this).attr("href").replace(old_session,new_session) );
}); });
} }
function onRemove(event) {
var old_session = event.from.getAttribute("session");
$.post(event.from.getAttribute("removeFromSession"),{
'oldIndex': event.oldIndex + 1,
'name': event.item.getAttribute("name")
});
}
function onEnd(event) {
if (event.to == event.from) {
$.post(event.from.getAttribute("reorderInSession"),{
'oldIndex': event.oldIndex + 1,
'newIndex': event.newIndex + 1
});
}
}
$(document).ready(function() {
$(".slides tbody").each(function() {
sortables.push(Sortable.create(this, options));
});
});
</script> </script>
{% endif %} {% endif %}

View file

@ -66,9 +66,9 @@
<div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div> <div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
<div class="panel-body"> <div class="panel-body">
<table class="table table-condensed table-striped slides" id="slides_{{session.pk}}"> <table class="table table-condensed table-striped slides" id="slides_{{session.pk}}">
<tbody> <tbody session="{{session.pk}}" addToSession="{% url 'ietf.meeting.views.ajax_add_slides_to_session' session_id=session.pk num=session.meeting.number %}" removeFromSession="{% url 'ietf.meeting.views.ajax_remove_slides_from_session' session_id=session.pk num=session.meeting.number %}" reorderInSession="{% url 'ietf.meeting.views.ajax_reorder_slides_in_session' session_id=session.pk num=session.meeting.number %}">
{% for pres in session.filtered_slides %} {% for pres in session.filtered_slides %}
<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 %}"> <tr name="{{pres.document.name}}">
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %} {% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<td> <td>
<a href="{{pres.document.href}}">{{pres.document.title}} </a> <a href="{{pres.document.href}}">{{pres.document.title}} </a>