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:
parent
34f93932a2
commit
1016b3a514
2
ietf/externals/static/Sortable/Sortable.min.js
vendored
Normal file
2
ietf/externals/static/Sortable/Sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -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):
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue