diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 020770c9b..4bf583485 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -112,10 +112,7 @@ class Item(SlideMixin, MPTTModel): def save(self, *args, **kwargs): super(Item, self).save(*args, **kwargs) - active_slide = get_active_slide() - active_slide_pk = active_slide.get('pk', None) - if (active_slide['callback'] == 'agenda' and - unicode(self.parent_id) == unicode(active_slide_pk)): + if self.parent and self.parent.is_active_slide(): update_projector() def __unicode__(self): @@ -359,14 +356,10 @@ class Speaker(models.Model): Checks, if the agenda item, or parts of it, is on the projector. If yes, it updates the projector. """ - active_slide = get_active_slide() - active_slide_pk = active_slide.get('pk', None) - slide_type = active_slide.get('type', None) - if (active_slide['callback'] == 'agenda' and - unicode(self.item_id) == unicode(active_slide_pk)): - if slide_type == 'list_of_speakers': + if self.item.is_active_slide(): + if get_active_slide().get('type', None) == 'list_of_speakers': update_projector() - elif slide_type is None: + else: update_projector_overlay('agenda_speaker') def begin_speach(self): diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 7def5aa6c..353a4649e 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -13,7 +13,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop from openslides.config.api import config, ConfigPage, ConfigVariable from openslides.config.signals import config_signal -from openslides.projector.api import get_active_slide +from openslides.projector.api import get_active_slide, get_active_object from openslides.projector.projector import Overlay from openslides.projector.signals import projector_overlays @@ -97,23 +97,30 @@ def agenda_list_of_speakers(sender, **kwargs): The overlay is only shown on agenda-items and not on the list-of-speakers slide. """ - active_slide = get_active_slide() - slide_type = active_slide.get('type', None) - active_slide_pk = active_slide.get('pk', None) - if (active_slide['callback'] == 'agenda' and - slide_type != 'list_of_speakers' and - active_slide_pk is not None): - item = Item.objects.get(pk=active_slide_pk) + slide = get_active_object() + if 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 + try: + item = Item.objects.filter( + content_type=ContentType.objects.get_for_model(slide), + object_id=slide.pk)[0] + except IndexError: + item = None + + if item and get_active_slide().get('type', None) != 'list_of_speakers': list_of_speakers = item.get_list_of_speakers( old_speakers_count=config['agenda_show_last_speakers'], coming_speakers_count=5) - context = { + + value = render_to_string('agenda/overlay_speaker_projector.html', { 'list_of_speakers': list_of_speakers, - 'closed': item.speaker_list_closed, - } - return render_to_string('agenda/overlay_speaker_projector.html', context) + 'closed': item.speaker_list_closed}) else: - return None + value = None + return value return Overlay(name, get_widget_html, get_projector_html) diff --git a/openslides/agenda/slides.py b/openslides/agenda/slides.py index 4ea5d4789..c47db478e 100644 --- a/openslides/agenda/slides.py +++ b/openslides/agenda/slides.py @@ -60,4 +60,4 @@ def agenda_slide(**kwargs): return slide -register_slide('agenda', agenda_slide) +register_slide('agenda', agenda_slide, Item) diff --git a/openslides/mediafile/slides.py b/openslides/mediafile/slides.py index ba8f7ff51..b5d2ae386 100644 --- a/openslides/mediafile/slides.py +++ b/openslides/mediafile/slides.py @@ -30,4 +30,4 @@ def mediafile_presentation_as_slide(**kwargs): return render_to_string('mediafile/presentation_slide.html', context) -register_slide('mediafile', mediafile_presentation_as_slide) +register_slide('mediafile', mediafile_presentation_as_slide, Mediafile) diff --git a/openslides/projector/api.py b/openslides/projector/api.py index 46a9887e7..f1b52ddbc 100644 --- a/openslides/projector/api.py +++ b/openslides/projector/api.py @@ -20,6 +20,14 @@ A dictonary where the key is the name of a slide, and the value is a callable object which returns the html code for a slide. """ +slide_model = {} +""" +A dictonary for SlideMixin models to reference from the slide_callback_name to +the Model +""" +# TODO: Find a bether way to do this. Maybe by reimplementing slides with +# metaclasses + class SlideError(OpenSlidesError): pass @@ -139,11 +147,15 @@ def get_projector_overlays_js(as_json=False): return javascript -def register_slide(name, callback): +def register_slide(name, callback, model=None): """ - Register a function as slide callback. + Registers a function as slide callback. + + The optional argument 'model' is used to register a SlideModelClass. """ slide_callback[name] = callback + if model is not None: + slide_model[name] = model def register_slide_model(SlideModel, template): @@ -169,7 +181,7 @@ def register_slide_model(SlideModel, template): return render_to_string(template, context) - register_slide(SlideModel.slide_callback_name, model_slide) + register_slide(SlideModel.slide_callback_name, model_slide, SlideModel) def set_active_slide(callback, **kwargs): @@ -192,6 +204,26 @@ def get_active_slide(): return config['projector_active_slide'] +def get_active_object(): + """ + Returns an object if the active slide is an instance of SlideMixin. + In other case, returns None + """ + active_slide_dict = get_active_slide() + callback_name = active_slide_dict.get('callback', None) + object_pk = active_slide_dict.get('pk', None) + try: + Model = slide_model[callback_name] + except KeyError: + value = None + else: + try: + value = Model.objects.get(pk=object_pk) + except Model.DoesNotExist: + value = None + return value + + def get_all_widgets(request, session=False): """ Collects the widgets from all apps and returns the Widget objects as sorted diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py index 94f436790..9c66fb652 100644 --- a/openslides/projector/projector.py +++ b/openslides/projector/projector.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from django.conf import settings from django.template import RequestContext from django.template.loader import render_to_string @@ -86,7 +87,15 @@ class Overlay(object): """ Returns the html code for the projector. """ - return self.get_html_wrapper(self.projector_html_callback()) + try: + value = self.get_html_wrapper(self.projector_html_callback()) + except Exception as exception: + if settings.DEBUG: + raise exception + else: + # Catch all errors, so an overlay can not kill the projector + value = '' + return value def get_javascript(self): """ diff --git a/tests/projector/test_api.py b/tests/projector/test_api.py index 8b0b25bb5..a83581fa0 100644 --- a/tests/projector/test_api.py +++ b/tests/projector/test_api.py @@ -139,9 +139,14 @@ class ApiFunctions(TestCase): def test_register_slide(self): mock_slide_callback = {} - with patch('openslides.projector.api.slide_callback', mock_slide_callback): - projector_api.register_slide('some name', 'some callback') - self.assertEqual(mock_slide_callback, {'some name': 'some callback'}) + mock_slide_model = {} + with patch('openslides.projector.api.slide_model', mock_slide_model): + with patch('openslides.projector.api.slide_callback', mock_slide_callback): + projector_api.register_slide('some name', 'some callback') + projector_api.register_slide('other name', 'other callback', 'Model') + self.assertEqual(mock_slide_callback, {'some name': 'some callback', + 'other name': 'other callback'}) + self.assertEqual(mock_slide_model, {'other name': 'Model'}) @patch('openslides.projector.api.render_to_string') @patch('openslides.projector.api.register_slide') @@ -156,6 +161,7 @@ class ApiFunctions(TestCase): projector_api.register_slide_model(mock_SlideModel, 'some template') used_args, __ = mock_register_slide.call_args self.assertEqual(used_args[0], 'mock_callback_name') + self.assertEqual(used_args[2], mock_SlideModel) # Test the generated slide function used_args[1](pk=1) @@ -182,3 +188,26 @@ class ApiFunctions(TestCase): with patch('openslides.projector.api.config', mock_config): value = projector_api.get_active_slide() self.assertEqual(value, 'value') + + def test_get_active_object(self): + mock_Model = MagicMock() + mock_Model.DoesNotExist = Exception + mock_slide_model = {'mock_model': mock_Model} + mock_active_slide = {'callback': 'unknown'} + mock_get_active_slide = MagicMock(return_value=mock_active_slide) + + with patch('openslides.projector.api.get_active_slide', mock_get_active_slide): + with patch('openslides.projector.api.slide_model', mock_slide_model): + # test unknwon slide_callback_name + self.assertIsNone(projector_api.get_active_object()) + + # test unknown object + mock_Model.objects.get.side_effect = Exception + mock_active_slide.update(callback='mock_model', pk=42) + self.assertIsNone(projector_api.get_active_object()) + mock_Model.objects.get.assert_called_with(pk=42) + + # test success + mock_Model.objects.get.side_effect = None + mock_Model.objects.get.return_value = 'success' + self.assertEqual(projector_api.get_active_object(), 'success') diff --git a/tests/projector/test_overlays.py b/tests/projector/test_overlays.py new file mode 100644 index 000000000..c59927c50 --- /dev/null +++ b/tests/projector/test_overlays.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from mock import MagicMock, patch + +from openslides.projector.projector import Overlay +from openslides.utils.test import TestCase + + +class OverlayTest(TestCase): + def test_error_in_html(self): + """ + Tests that the methof get_projector_html does not raise any errors. + """ + get_projector_html = MagicMock(side_effect=Exception('no good error')) + overlay = Overlay('test_overlay', lambda: 'widget_html', get_projector_html) + + # Test in productive mode + with patch('openslides.projector.projector.settings.DEBUG', False): + self.assertEqual(overlay.get_projector_html(), '') + + # Test in debug mode + with patch('openslides.projector.projector.settings.DEBUG', True): + self.assertRaisesMessage( + Exception, + 'no good error', + overlay.get_projector_html)