diff --git a/CHANGELOG b/CHANGELOG
index 81c4e33e6..1f5312fc0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@ Version 1.6 (unreleased)
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
Agenda:
+- New projector view with the current list of speakers.
- Added CSV import.
Assignment:
- Coupled assignment candidates with list of speakers.
diff --git a/docs/de/Agenda.rst b/docs/de/Agenda.rst
index 548f320a6..1c56f7e33 100644
--- a/docs/de/Agenda.rst
+++ b/docs/de/Agenda.rst
@@ -137,6 +137,12 @@ Redner. Die Einblendung erscheint nur auf Folien von Tagesordnungspunkten.
:scale-latex: 80
:alt: Projektor-Ansicht mit Rednerlisten-Overlay
+Schließlich haben Sie die Möglichkeit, die Rednerliste des jeweiligen
+Tagesordnungspunktes auf einem gesonderten Projektor anzeigen zu lassen.
+Klicken Sie auf der Tagesordnungsseite oben rechts auf auf den
+Glocken-Button |bell| und legen Sie die Seite im Vollbildmodus auf einen
+eigenen Projektor oder Bildschirm.
+
Die Rednerliste verwalten
-------------------------
diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py
index 0eccea885..20a1e8624 100644
--- a/openslides/agenda/signals.py
+++ b/openslides/agenda/signals.py
@@ -101,8 +101,8 @@ def agenda_list_of_speakers(sender, **kwargs):
if slide is None or isinstance(slide, Item):
item = slide
else:
- # TODO: If there are more the one items, use the first one in the
- # mptt tree that is not closed
+ # TODO: If there is more than one item, use the first one in the
+ # mptt tree that is not closed.
try:
item = Item.objects.filter(
content_type=ContentType.objects.get_for_model(slide),
diff --git a/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js b/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js
new file mode 100644
index 000000000..432c4bb6d
--- /dev/null
+++ b/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js
@@ -0,0 +1,14 @@
+/*
+ * JavaScript functions for agenda CurrentListOfSpeakersProjectorView
+ */
+
+function reloadListOfSpeakers() {
+ $.ajax({
+ url: '',
+ success: function (data) {
+ updater.updateProjector(data);
+ setTimeout('reloadListOfSpeakers()', 2000);
+ },
+ dataType: 'json'
+ });
+}
diff --git a/openslides/agenda/templates/agenda/current_list_of_speakers_projector.html b/openslides/agenda/templates/agenda/current_list_of_speakers_projector.html
new file mode 100644
index 000000000..10dd26791
--- /dev/null
+++ b/openslides/agenda/templates/agenda/current_list_of_speakers_projector.html
@@ -0,0 +1,4 @@
+{% extends 'projector.html' %}
+{% load i18n %}
+
+{% block title %}{% trans 'List of speakers' %} – {{ block.super }}{% endblock %}
diff --git a/openslides/agenda/templates/agenda/overview.html b/openslides/agenda/templates/agenda/overview.html
index 6dea8027d..225f38c31 100644
--- a/openslides/agenda/templates/agenda/overview.html
+++ b/openslides/agenda/templates/agenda/overview.html
@@ -39,6 +39,10 @@
{% trans "Import" %}
{% endif %}
PDF
+ {% if perms.core.can_see_projector %}
+
+ {% trans 'List of speakers' %}
+ {% endif %}
diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py
index 3e926950d..0e854f1c6 100644
--- a/openslides/agenda/urls.py
+++ b/openslides/agenda/urls.py
@@ -89,6 +89,10 @@ urlpatterns = patterns(
views.CurrentListOfSpeakersView.as_view(end_speach=True),
name='agenda_end_speach_on_current_list_of_speakers'),
+ url(r'^list_of_speakers/projector/$',
+ views.CurrentListOfSpeakersProjectorView.as_view(),
+ name='agenda_current_list_of_speakers_projector'),
+
url(r'^csv_import/$',
views.ItemCSVImportView.as_view(),
name='item_csv_import'))
diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py
index 0122d7e2e..6f41917e6 100644
--- a/openslides/agenda/views.py
+++ b/openslides/agenda/views.py
@@ -2,21 +2,33 @@
# TODO: Rename all views and template names
from datetime import datetime, timedelta
+from json import dumps
from django.contrib import messages
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Model
+from django.template.loader import render_to_string
+from django.utils.datastructures import SortedDict
+from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from reportlab.platypus import Paragraph
from openslides.config.api import config
-from openslides.projector.api import get_active_slide, update_projector
+from openslides.projector.api import (
+ get_active_object,
+ get_active_slide,
+ get_projector_overlays_js,
+ get_overlays,
+ update_projector)
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet
from openslides.utils.utils import html_strong
from openslides.utils.views import (
+ AjaxMixin,
CreateView,
CSVImportView,
DeleteView,
@@ -630,6 +642,108 @@ class CurrentListOfSpeakersView(RedirectView):
return reverse('item_view', args=[item.pk])
+class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
+ """
+ View with the current list of speakers depending on the active slide.
+ Usefule for the projector.
+ """
+ template_name = 'agenda/current_list_of_speakers_projector.html'
+
+ def get(self, request, *args, **kwargs):
+ """
+ Returns response object depending on request type (ajax or normal).
+ """
+ if request.is_ajax():
+ value = self.ajax_get(request, *args, **kwargs)
+ else:
+ value = super(CurrentListOfSpeakersProjectorView, self).get(request, *args, **kwargs)
+ return value
+
+ def get_item(self):
+ """
+ Returns the item of the current slide is an agenda item slide or a
+ slide of a related model else returns None.
+ """
+ slide_object = get_active_object()
+ if slide_object is None or isinstance(slide_object, Item):
+ item = slide_object
+ else:
+ # TODO: If there is more than one item, use the first one in the
+ # mptt tree that is not closed.
+ try:
+ item = Item.objects.filter(
+ content_type=ContentType.objects.get_for_model(slide_object),
+ object_id=slide_object.pk)[0]
+ except IndexError:
+ item = None
+ return item
+
+ def get_content(self):
+ """
+ Returns the content of this slide.
+ """
+ item = self.get_item()
+ if item is None:
+ content = mark_safe('
%s
%s\n' % (_('List of speakers'), _('Not available.')))
+ else:
+ content_dict = {
+ 'title': item.get_title(),
+ 'item': item,
+ 'list_of_speakers': item.get_list_of_speakers(
+ old_speakers_count=config['agenda_show_last_speakers'])}
+ content = render_to_string('agenda/item_slide_list_of_speaker.html', content_dict)
+ return content
+
+ def get_overlays_and_overlay_js(self):
+ """
+ Returns the overlays and their JavaScript for this slide as a
+ two-tuple. The overlay 'agenda_speaker' is always excluded.
+
+ The required JavaScript fot this view is inserted.
+ """
+ overlays = get_overlays(only_active=True)
+ overlays.pop('agenda_speaker', None)
+ overlay_js = get_projector_overlays_js(as_json=True)
+ # Note: The JavaScript content of overlay 'agenda_speaker' is not
+ # excluded because this overlay has no such content at the moment.
+ extra_js = SortedDict()
+ extra_js['load_file'] = static('js/agenda_current_list_of_speakers_projector.js')
+ extra_js['call'] = 'reloadListOfSpeakers();'
+ extra_js = dumps(extra_js)
+ overlay_js.append(extra_js)
+ return overlays, overlay_js
+
+ def get_context_data(self, **context):
+ """
+ Returns the context for the projector template. Contains the content
+ of this slide.
+ """
+ overlays, overlay_js = self.get_overlays_and_overlay_js()
+ return super(CurrentListOfSpeakersProjectorView, self).get_context_data(
+ content=self.get_content(),
+ overlays=overlays,
+ overlay_js=overlay_js,
+ **context)
+
+ def get_ajax_context(self, **context):
+ """
+ Returns the context including the slide content for ajax response. The
+ overlay 'agenda_speaker' is always excluded.
+ """
+ overlay_dict = {}
+ for overlay in get_overlays().values():
+ if overlay.is_active() and overlay.name != 'agenda_speaker':
+ overlay_dict[overlay.name] = {
+ 'html': overlay.get_projector_html(),
+ 'javascript': overlay.get_javascript()}
+ else:
+ overlay_dict[overlay.name] = None
+ return super(CurrentListOfSpeakersProjectorView, self).get_ajax_context(
+ content=self.get_content(),
+ overlays=overlay_dict,
+ **context)
+
+
class ItemCSVImportView(CSVImportView):
"""
Imports agenda items from an uploaded csv file.
diff --git a/openslides/projector/api.py b/openslides/projector/api.py
index 844f185a2..8f5e906c7 100644
--- a/openslides/projector/api.py
+++ b/openslides/projector/api.py
@@ -104,29 +104,22 @@ def default_slide():
return render_to_string('projector/default_slide.html')
-def get_overlays():
+def get_overlays(only_active=False):
"""
Returns all overlay objects.
+ If only_active is True, returns only active overlays.
+
The returned value is a dictonary with the name of the overlay as key, and
the overlay object as value.
"""
overlays = {}
for receiver, overlay in projector_overlays.send(sender='get_overlays'):
- overlays[overlay.name] = overlay
+ if not only_active or overlay.is_active():
+ overlays[overlay.name] = overlay
return overlays
-def get_projector_overlays():
- """
- Returns the HTML code for all active overlays.
- """
- overlays = [{'name': key, 'html': overlay.get_projector_html()}
- for key, overlay in get_overlays().items()
- if overlay.is_active()]
- return render_to_string('projector/all_overlays.html', {'overlays': overlays})
-
-
def get_projector_overlays_js(as_json=False):
"""
Returns JS-Code for the active overlays.
diff --git a/openslides/projector/templates/projector.html b/openslides/projector/templates/projector.html
index c8e5b7e2e..c0b1fce28 100644
--- a/openslides/projector/templates/projector.html
+++ b/openslides/projector/templates/projector.html
@@ -8,8 +8,7 @@
- {% trans 'Projector' %} – {{ 'event_name'|get_config }}
-
+ {% block title %}{% trans 'Projector' %} – {{ 'event_name'|get_config }}{% endblock %}
@@ -59,7 +58,9 @@
- {{ overlays }}
+ {% for overlay in overlays.values %}
+ {{ overlay.get_projector_html|safe }}
+ {% endfor %}