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/templates/projector.html b/openslides/projector/templates/projector.html index c17956aaf..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 %} diff --git a/tests/agenda/test_list_of_speakers.py b/tests/agenda/test_list_of_speakers.py index a6d4b50d0..84a3bc47f 100644 --- a/tests/agenda/test_list_of_speakers.py +++ b/tests/agenda/test_list_of_speakers.py @@ -314,3 +314,23 @@ class TestOverlay(TestCase): value = agenda_list_of_speakers(sender='test').get_projector_html() self.assertEqual(value, '') + + +class TestCurrentListOfSpeakersOnProjectorView(SpeakerViewTestCase): + """ + Test the view with the current list of speakers depending on the actual + slide. + """ + def test_get_none(self): + response = self.admin_client.get('/agenda/list_of_speakers/projector/') + self.assertContains(response, 'List of speakersNot available') + + def test_get_normal(self): + self.item1.title = 'title_gupooDee8ahahnaxoo2a' + self.item1.save() + Speaker.objects.add(self.speaker1, self.item1) + config['projector_active_slide'] = {'callback': 'agenda', 'pk': self.item1.pk} + response = self.admin_client.get('/agenda/list_of_speakers/projector/') + self.assertContains(response, 'List of speakers') + self.assertContains(response, 'title_gupooDee8ahahnaxoo2a') + self.assertContains(response, 'speaker1')