diff --git a/openslides/agenda/templates/agenda/speaker_widget.html b/openslides/agenda/templates/agenda/speaker_widget.html
index 0e5567ec7..11a54fa25 100644
--- a/openslides/agenda/templates/agenda/speaker_widget.html
+++ b/openslides/agenda/templates/agenda/speaker_widget.html
@@ -9,4 +9,5 @@
{% if perms.agenda.can_manage_agenda %}
{% trans 'Next speaker' %}
+ {% trans 'End speach' %}
{% endif %}
diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py
index 705351a68..a671a6685 100644
--- a/openslides/agenda/urls.py
+++ b/openslides/agenda/urls.py
@@ -114,5 +114,10 @@ urlpatterns = patterns(
url(r'^list_of_speakers/next/$',
CurrentListOfSpeakersView.as_view(next_speaker=True),
name='agenda_next_on_current_list_of_speakers',
+ ),
+
+ url(r'^list_of_speakers/end_speach/$',
+ CurrentListOfSpeakersView.as_view(end_speach=True),
+ name='agenda_end_speach_on_current_list_of_speakers',
)
)
diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py
index 8e7cb41fb..8642a8bec 100644
--- a/openslides/agenda/views.py
+++ b/openslides/agenda/views.py
@@ -436,11 +436,12 @@ class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
class CurrentListOfSpeakersView(RedirectView):
"""
- Redirect to the current list of speakers and set the request.user on it or
- begins speach of the next speaker.
+ Redirect to the current list of speakers and set the request.user on it,
+ begins speach of the next speaker or ends the speach of the current speaker.
"""
set_speaker = False
next_speaker = False
+ end_speach = False
def get_item(self):
"""
@@ -463,11 +464,14 @@ class CurrentListOfSpeakersView(RedirectView):
in other case, it returns the URL to the dashboard.
- This method also adds the request.user to the list of speakers, if he
+ This method also adds the request.user to the list of speakers if he
has the right permissions and the list is not closed.
- This method also begins the speach of the next speaker, if the flag
+ This method also begins the speach of the next speaker if the flag
next_speaker is given.
+
+ This method also ends the speach of the current speaker if the flag
+ end_speach is given.
"""
item = self.get_item()
request = self.request
@@ -506,6 +510,16 @@ class CurrentListOfSpeakersView(RedirectView):
if not self.set_speaker:
reverse_to_dashboard = True
+ if self.end_speach:
+ try:
+ current_speaker = item.speaker_set.filter(end_time=None).exclude(begin_time=None).get()
+ except Speaker.DoesNotExist:
+ messages.error(request, _('There is no one speaking at the moment.'))
+ else:
+ current_speaker.end_speach()
+ messages.success(request, _('%s is now finished.') % current_speaker)
+ reverse_to_dashboard = True
+
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_agenda'):
return reverse('dashboard')
else:
diff --git a/tests/agenda/test_list_of_speakers.py b/tests/agenda/test_list_of_speakers.py
index 8a6d82ca4..b8e93c7df 100644
--- a/tests/agenda/test_list_of_speakers.py
+++ b/tests/agenda/test_list_of_speakers.py
@@ -13,6 +13,7 @@ from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.test import TestCase
from openslides.participant.models import User
from openslides.agenda.models import Item, Speaker
+from openslides.config.api import config
class ListOfSpeakerModelTests(TestCase):
@@ -200,3 +201,70 @@ class SpeakerListOpenView(SpeakerViewTestCase):
response = self.check_url('/agenda/1/speaker/reopen/', self.admin_client, 302)
item = Item.objects.get(pk=self.item1.pk)
self.assertFalse(item.speaker_list_closed)
+
+
+class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
+ def test_global_redirect_url(self):
+ self.assertFalse(config['presentation'])
+ response = self.speaker1_client.get('/agenda/list_of_speakers/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
+
+ response = self.admin_client.get('/projector/activate/item-1/')
+ self.assertEqual(config['presentation'], 'item-1')
+ response = self.speaker1_client.get('/agenda/list_of_speakers/')
+ self.assertRedirects(response, '/agenda/1/')
+
+ def test_global_add_url(self):
+ self.assertFalse(config['presentation'])
+ response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
+
+ response = self.admin_client.get('/projector/activate/item-1/')
+ self.assertEqual(config['presentation'], 'item-1')
+ response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
+ self.assertRedirects(response, '/agenda/1/')
+ self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
+
+ def test_global_next_speaker_url(self):
+ self.assertFalse(config['presentation'])
+ response = self.admin_client.get('/agenda/list_of_speakers/next/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
+
+ response = self.admin_client.get('/projector/activate/item-1/')
+ self.assertEqual(config['presentation'], 'item-1')
+ response = self.admin_client.get('/agenda/list_of_speakers/next/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'The list of speakers is empty.')
+
+ response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
+ self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
+ response = self.admin_client.get('/agenda/list_of_speakers/next/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is not None)
+
+ def test_global_end_speach_url(self):
+ self.assertFalse(config['presentation'])
+ response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
+
+ response = self.admin_client.get('/projector/activate/item-1/')
+ self.assertEqual(config['presentation'], 'item-1')
+ response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no one speaking at the moment.')
+
+ response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
+ self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
+ response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertMessage(response, 'There is no one speaking at the moment.')
+
+ response = self.admin_client.get('/agenda/list_of_speakers/next/')
+ self.assertTrue(Speaker.objects.get(item__pk='1').end_time is None)
+ response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
+ self.assertRedirects(response, '/projector/dashboard/')
+ self.assertTrue(Speaker.objects.get(item__pk='1').end_time is not None)