From f8cdad711cf48aa8e5441340e4632009e2f0a090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Thu, 10 Apr 2014 14:23:08 +0200 Subject: [PATCH 1/2] Simplify overlay api methods. --- openslides/projector/api.py | 17 ++++--------- openslides/projector/templates/projector.html | 4 ++- .../templates/projector/all_overlays.html | 3 --- openslides/projector/views.py | 4 +-- tests/projector/test_api.py | 25 +++++-------------- tests/projector/test_views.py | 6 ++--- 6 files changed, 19 insertions(+), 40 deletions(-) delete mode 100644 openslides/projector/templates/projector/all_overlays.html 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..c17956aaf 100644 --- a/openslides/projector/templates/projector.html +++ b/openslides/projector/templates/projector.html @@ -59,7 +59,9 @@
- {{ overlays }} + {% for overlay in overlays.values %} + {{ overlay.get_projector_html|safe }} + {% endfor %}
diff --git a/openslides/projector/templates/projector/all_overlays.html b/openslides/projector/templates/projector/all_overlays.html deleted file mode 100644 index 59494c20c..000000000 --- a/openslides/projector/templates/projector/all_overlays.html +++ /dev/null @@ -1,3 +0,0 @@ -{% for overlay in overlays %} - {{ overlay.html|safe }} -{% endfor %} diff --git a/openslides/projector/views.py b/openslides/projector/views.py index 913165c0f..2bb6b9e4d 100644 --- a/openslides/projector/views.py +++ b/openslides/projector/views.py @@ -6,7 +6,7 @@ from openslides.utils.tornado_webserver import ProjectorSocketHandler from openslides.utils.views import RedirectView, TemplateView from .api import (call_on_projector, get_active_slide, - get_overlays, get_projector_content, get_projector_overlays, + get_overlays, get_projector_content, get_projector_overlays_js, reset_countdown, set_active_slide, start_countdown, stop_countdown, update_projector_overlay) @@ -24,7 +24,7 @@ class ProjectorView(TemplateView): if callback is None: kwargs.update({ 'content': get_projector_content(), - 'overlays': get_projector_overlays(), + 'overlays': get_overlays(only_active=True), 'overlay_js': get_projector_overlays_js(as_json=True), 'reload': True, 'calls': config['projector_js_cache']}) diff --git a/tests/projector/test_api.py b/tests/projector/test_api.py index a83581fa0..ea0b00a7a 100644 --- a/tests/projector/test_api.py +++ b/tests/projector/test_api.py @@ -91,30 +91,17 @@ class ApiFunctions(TestCase): mock_overlay = MagicMock() mock_overlay.name = 'mock_overlay' mock_projector_overlays.send.return_value = ((None, mock_overlay), ) - value = projector_api.get_overlays() self.assertEqual(value, {'mock_overlay': mock_overlay}) - @patch('openslides.projector.api.render_to_string') - @patch('openslides.projector.api.get_overlays') - def test_get_projector_overlays(self, mock_get_overlays, mock_render_to_string): + @patch('openslides.projector.api.projector_overlays') + def test_get_overlays_inactive(self, mock_projector_overlays): mock_overlay = MagicMock() - mock_overlay.get_projector_html.return_value = 'some html' - mock_get_overlays.return_value = {'overlay_name': mock_overlay} - - # Test with inactive overlay + mock_overlay.name = 'mock_overlay_2' mock_overlay.is_active.return_value = False - projector_api.get_projector_overlays() - mock_render_to_string.assert_called_with( - 'projector/all_overlays.html', - {'overlays': []}) - - # Test with active overlay - mock_overlay.is_active.return_value = True - projector_api.get_projector_overlays() - mock_render_to_string.assert_Called_with( - 'projector/all_overlays.html', - {'overlays': [{'name': 'overlay_name', 'html': 'some html'}]}) + mock_projector_overlays.send.return_value = ((None, mock_overlay), ) + value = projector_api.get_overlays(only_active=True) + self.assertNotEqual(value, {'mock_overlay_2': mock_overlay}) @patch('openslides.projector.api.get_overlays') def test_get_projector_overlays_js(self, mock_get_overlays): diff --git a/tests/projector/test_views.py b/tests/projector/test_views.py index e64261236..6b24a961d 100644 --- a/tests/projector/test_views.py +++ b/tests/projector/test_views.py @@ -13,9 +13,9 @@ class ProjectorViewTest(TestCase): rf = RequestFactory() @patch('openslides.projector.views.get_projector_overlays_js') - @patch('openslides.projector.views.get_projector_overlays') + @patch('openslides.projector.views.get_overlays') @patch('openslides.projector.views.get_projector_content') - def test_get(self, mock_get_projector_content, mock_get_projector_overlays, + def test_get(self, mock_get_projector_content, mock_get_overlays, mock_get_projector_overlays_js): view = views.ProjectorView() view.request = self.rf.get('/') @@ -34,7 +34,7 @@ class ProjectorViewTest(TestCase): with patch('openslides.projector.views.config', mock_config): context = view.get_context_data() mock_get_projector_content.assert_called_with() - mock_get_projector_overlays.assert_called_with() + mock_get_overlays.assert_called_with(only_active=True) mock_get_projector_overlays_js.assert_called_with(as_json=True) self.assertTrue(context['reload']) self.assertEqual(context['calls'], 'js_cache') From ac1ab81d7ad81e7e7147bce37fd7c2376e3261c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Sun, 6 Apr 2014 15:46:49 +0200 Subject: [PATCH 2/2] New projector view with the current list of speakers. --- CHANGELOG | 1 + docs/de/Agenda.rst | 6 + openslides/agenda/signals.py | 4 +- ...enda_current_list_of_speakers_projector.js | 14 +++ .../current_list_of_speakers_projector.html | 4 + .../agenda/templates/agenda/overview.html | 4 + openslides/agenda/urls.py | 4 + openslides/agenda/views.py | 116 +++++++++++++++++- openslides/projector/templates/projector.html | 3 +- tests/agenda/test_list_of_speakers.py | 20 +++ 10 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js create mode 100644 openslides/agenda/templates/agenda/current_list_of_speakers_projector.html 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')