Fixed countdown and projector update issues
* agenda_item.get_absolute_url('projector') returns the activate-url of the related slide. * agenda_item.is_activate() returns True, if the related item is active * Fixed set_active_slide to accept kwargs * Reset countdown when saving a new duration time * Update countdown overlay when begin_speak and end_speak is called * Fixed blinking countdown Fixes: #1078, #1076, #1075
This commit is contained in:
parent
3be0507f1b
commit
f8048da76c
@ -128,16 +128,21 @@ class Item(SlideMixin, MPTTModel):
|
||||
The link can be detail, update or delete.
|
||||
"""
|
||||
if link == 'detail' or link == 'view':
|
||||
return reverse('item_view', args=[str(self.id)])
|
||||
if link == 'update' or link == 'edit':
|
||||
return reverse('item_edit', args=[str(self.id)])
|
||||
if link == 'delete':
|
||||
return reverse('item_delete', args=[str(self.id)])
|
||||
if link == 'projector_list_of_speakers':
|
||||
return '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector')
|
||||
if link == 'projector_summary':
|
||||
return '%s&type=summary' % super(Item, self).get_absolute_url('projector')
|
||||
return super(Item, self).get_absolute_url(link)
|
||||
url = reverse('item_view', args=[str(self.id)])
|
||||
elif link == 'update' or link == 'edit':
|
||||
url = reverse('item_edit', args=[str(self.id)])
|
||||
elif link == 'delete':
|
||||
url = reverse('item_delete', args=[str(self.id)])
|
||||
elif link == 'projector_list_of_speakers':
|
||||
url = '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector')
|
||||
elif link == 'projector_summary':
|
||||
url = '%s&type=summary' % super(Item, self).get_absolute_url('projector')
|
||||
elif (link in ('projector', 'projector_preview') and
|
||||
self.content_object and isinstance(self.content_object, SlideMixin)):
|
||||
url = self.content_object.get_absolute_url(link)
|
||||
else:
|
||||
url = super(Item, self).get_absolute_url(link)
|
||||
return url
|
||||
|
||||
def get_title(self):
|
||||
"""
|
||||
@ -268,6 +273,17 @@ class Item(SlideMixin, MPTTModel):
|
||||
# The list of speakers is empty.
|
||||
return None
|
||||
|
||||
def is_active_slide(self):
|
||||
"""
|
||||
Returns True if the slide is True. If the slide is a related item,
|
||||
Returns True if the related object is active.
|
||||
"""
|
||||
if self.content_object and isinstance(self.content_object, SlideMixin):
|
||||
value = self.content_object.is_active_slide()
|
||||
else:
|
||||
value = super(Item, self).is_active_slide()
|
||||
return value
|
||||
|
||||
|
||||
class SpeakerManager(models.Manager):
|
||||
def add(self, person, item):
|
||||
@ -373,6 +389,10 @@ class Speaker(models.Model):
|
||||
if config['agenda_couple_countdown_and_speakers']:
|
||||
reset_countdown()
|
||||
start_countdown()
|
||||
if self.item.is_active_slide():
|
||||
# TODO: only update the overlay if the overlay is active and
|
||||
# slide type is None.
|
||||
update_projector_overlay('projector_countdown')
|
||||
|
||||
def end_speach(self):
|
||||
"""
|
||||
@ -383,3 +403,7 @@ class Speaker(models.Model):
|
||||
# stop countdown
|
||||
if config['agenda_couple_countdown_and_speakers']:
|
||||
stop_countdown()
|
||||
if self.item.is_active_slide():
|
||||
# TODO: only update the overlay if the overlay is active and
|
||||
# slide type is None.
|
||||
update_projector_overlay('projector_countdown')
|
||||
|
@ -172,14 +172,13 @@ def register_slide_model(SlideModel, template):
|
||||
register_slide(SlideModel.slide_callback_name, model_slide)
|
||||
|
||||
|
||||
def set_active_slide(callback, kwargs=None):
|
||||
def set_active_slide(callback, **kwargs):
|
||||
"""
|
||||
Set the active Slide.
|
||||
|
||||
callback: The name of the slide callback.
|
||||
kwargs: Keyword arguments for the slide callback.
|
||||
"""
|
||||
kwargs = kwargs or {}
|
||||
kwargs.update(callback=callback)
|
||||
config['projector_active_slide'] = kwargs
|
||||
update_projector()
|
||||
|
@ -5,6 +5,7 @@ from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
from django.core.context_processors import csrf
|
||||
from django.dispatch import receiver, Signal
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from openslides.config.api import config, ConfigPage, ConfigVariable
|
||||
from openslides.config.signals import config_signal
|
||||
@ -99,18 +100,14 @@ def countdown(sender, **kwargs):
|
||||
"""
|
||||
Returns JavaScript for the projector
|
||||
"""
|
||||
start = int(config['countdown_start_stamp'])
|
||||
duration = int(config['countdown_time'])
|
||||
pause = int(config['countdown_pause_stamp'])
|
||||
state = config['countdown_state']
|
||||
|
||||
return {
|
||||
'load_file': static('javascript/countdown.js'),
|
||||
'call': 'update_countdown();',
|
||||
'projector_countdown_start': start,
|
||||
'projector_countdown_duration': duration,
|
||||
'projector_countdown_pause': pause,
|
||||
'projector_countdown_state': state}
|
||||
value = SortedDict()
|
||||
value['load_file'] = static('javascript/countdown.js')
|
||||
value['projector_countdown_start'] = int(config['countdown_start_stamp'])
|
||||
value['projector_countdown_duration'] = int(config['countdown_time'])
|
||||
value['projector_countdown_pause'] = int(config['countdown_pause_stamp'])
|
||||
value['projector_countdown_state'] = config['countdown_state']
|
||||
value['call'] = 'update_countdown();'
|
||||
return value
|
||||
|
||||
def get_projector_html():
|
||||
"""
|
||||
|
@ -85,7 +85,7 @@ class ActivateView(RedirectView):
|
||||
ProjectorSocketHandler.send_updates(
|
||||
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
||||
else:
|
||||
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
||||
set_active_slide(kwargs['callback'], **dict(request.GET.items()))
|
||||
config['projector_scroll'] = config.get_default('projector_scroll')
|
||||
config['projector_scale'] = config.get_default('projector_scale')
|
||||
call_on_projector({'scroll': config['projector_scroll'],
|
||||
@ -186,10 +186,10 @@ class CountdownControllView(RedirectView):
|
||||
try:
|
||||
config['countdown_time'] = \
|
||||
int(self.request.GET['countdown_time'])
|
||||
except ValueError:
|
||||
pass
|
||||
except AttributeError:
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
reset_countdown()
|
||||
update_projector_overlay('projector_countdown')
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
|
@ -1,7 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
|
||||
from openslides.projector.models import SlideMixin
|
||||
|
||||
class RelatedItem(models.Model):
|
||||
|
||||
class RelatedItem(SlideMixin, models.Model):
|
||||
slide_callback_name = 'test_related_item'
|
||||
name = models.CharField(max_length='255')
|
||||
|
||||
class Meta:
|
||||
@ -13,8 +18,12 @@ class RelatedItem(models.Model):
|
||||
def get_agenda_title_supplement(self):
|
||||
return 'test item'
|
||||
|
||||
def get_absolute_url(self, *args, **kwargs):
|
||||
return '/absolute-url-here/'
|
||||
def get_absolute_url(self, link=None):
|
||||
if link is None:
|
||||
value = '/absolute-url-here/'
|
||||
else:
|
||||
value = super(RelatedItem, self).get_absolute_url(link)
|
||||
return value
|
||||
|
||||
|
||||
class BadRelatedItem(models.Model):
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test.client import Client
|
||||
from mock import patch, MagicMock
|
||||
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
from openslides.config.api import config
|
||||
@ -69,24 +70,33 @@ class ListOfSpeakerModelTests(TestCase):
|
||||
self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time)
|
||||
self.assertIsNotNone(speaker2_item1.begin_time)
|
||||
|
||||
def test_speach_coupled_with_countdown(self):
|
||||
@patch('openslides.agenda.models.update_projector_overlay')
|
||||
def test_speach_coupled_with_countdown(self, mock_update_projector_overlay):
|
||||
config['agenda_couple_countdown_and_speakers'] = True
|
||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||
speaker1_item1.begin_speach()
|
||||
self.assertTrue(config['countdown_state'] == 'active')
|
||||
speaker1_item1.end_speach()
|
||||
self.assertTrue(config['countdown_state'] == 'paused')
|
||||
self.item1.is_active_slide = MagicMock(return_value=True)
|
||||
|
||||
def test_begin_speach_not_coupled_with_countdown(self):
|
||||
config['agenda_couple_countdown_and_speakers'] = False
|
||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||
speaker1_item1.begin_speach()
|
||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
||||
self.assertEqual(config['countdown_state'], 'active')
|
||||
mock_update_projector_overlay.assert_called_with('projector_countdown')
|
||||
|
||||
mock_update_projector_overlay.reset_mock()
|
||||
speaker1_item1.end_speach()
|
||||
self.assertEqual(config['countdown_state'], 'paused')
|
||||
mock_update_projector_overlay.assert_called_with('projector_countdown')
|
||||
|
||||
@patch('openslides.agenda.models.update_projector_overlay')
|
||||
def test_begin_speach_not_coupled_with_countdown(self, mock_update_projector_overlay):
|
||||
config['agenda_couple_countdown_and_speakers'] = False
|
||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||
|
||||
speaker1_item1.begin_speach()
|
||||
self.assertEqual(config['countdown_state'], 'inactive')
|
||||
|
||||
config['countdown_state'] = 'active'
|
||||
speaker1_item1.end_speach()
|
||||
self.assertTrue(config['countdown_state'] == 'active')
|
||||
self.assertEqual(config['countdown_state'], 'active')
|
||||
self.assertFalse(mock_update_projector_overlay.called)
|
||||
|
||||
|
||||
class SpeakerViewTestCase(TestCase):
|
||||
@ -233,7 +243,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||
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.')
|
||||
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
set_active_slide('agenda', pk=1)
|
||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||
self.assertRedirects(response, '/agenda/1/')
|
||||
|
||||
@ -242,7 +252,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||
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.')
|
||||
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
set_active_slide('agenda', pk=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)
|
||||
@ -258,7 +268,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||
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.')
|
||||
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
set_active_slide('agenda', pk=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.')
|
||||
@ -274,7 +284,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||
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.')
|
||||
|
||||
set_active_slide('agenda', {'pk': 1})
|
||||
set_active_slide('agenda', pk=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.')
|
||||
|
@ -8,6 +8,7 @@ from mock import patch
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.agenda.slides import agenda_slide
|
||||
from openslides.participant.models import User
|
||||
from openslides.projector.api import set_active_slide
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
from .models import BadRelatedItem, RelatedItem
|
||||
@ -77,7 +78,26 @@ class ItemTest(TestCase):
|
||||
def test_deleted_related_item(self):
|
||||
self.related.delete()
|
||||
self.assertFalse(RelatedItem.objects.all().exists())
|
||||
self.assertEqual(Item.objects.get(pk=self.item5.pk).title, '< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
||||
self.assertEqual(Item.objects.get(pk=self.item5.pk).title,
|
||||
'< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
||||
|
||||
def test_related_item_get_absolute_url(self):
|
||||
"""
|
||||
Tests that the get_absolute_url method with the link 'projector'
|
||||
and 'projector_preview' returns the absolute_url for the related
|
||||
item.
|
||||
"""
|
||||
self.assertEqual(self.item5.get_absolute_url('projector'),
|
||||
'/projector/activate/test_related_item/?pk=1')
|
||||
self.assertEqual(self.item5.get_absolute_url('projector_preview'),
|
||||
'/projector/preview/test_related_item/?pk=1')
|
||||
|
||||
def test_activate_related_item(self):
|
||||
"""
|
||||
The agenda item has to be active, if its related item is.
|
||||
"""
|
||||
set_active_slide('test_related_item', pk=1)
|
||||
self.assertTrue(self.item5.is_active_slide)
|
||||
|
||||
def test_bad_related_item(self):
|
||||
bad = BadRelatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
||||
|
@ -170,7 +170,7 @@ class ApiFunctions(TestCase):
|
||||
def test_set_active_slide(self, mock_update_projector, mock_update_projector_overlay):
|
||||
mock_config = {}
|
||||
with patch('openslides.projector.api.config', mock_config):
|
||||
projector_api.set_active_slide('callback_name', {'some': 'kwargs'})
|
||||
projector_api.set_active_slide('callback_name', some='kwargs')
|
||||
self.assertEqual(mock_config,
|
||||
{'projector_active_slide': {'callback': 'callback_name',
|
||||
'some': 'kwargs'}})
|
||||
|
21
tests/projector/test_signals.py
Normal file
21
tests/projector/test_signals.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openslides.projector.signals import countdown
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class CountdownTest(TestCase):
|
||||
def test_order_of_get_projector_js(self):
|
||||
"""
|
||||
Tests that the order of the js values is in the right order. Especially
|
||||
the value 'call' has to come at the end.
|
||||
"""
|
||||
overlay = countdown('fake sender')
|
||||
test_value = overlay.get_javascript()
|
||||
|
||||
self.assertIsInstance(test_value, dict)
|
||||
self.assertEqual(
|
||||
test_value.keys(),
|
||||
['load_file', 'projector_countdown_start',
|
||||
'projector_countdown_duration', 'projector_countdown_pause',
|
||||
'projector_countdown_state', 'call'])
|
@ -3,6 +3,7 @@
|
||||
from django.test.client import Client, RequestFactory
|
||||
from mock import call, MagicMock, patch
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.projector.models import ProjectorSlide
|
||||
from openslides.projector import views
|
||||
from openslides.utils.test import TestCase
|
||||
@ -199,3 +200,19 @@ class CustomSlidesTest(TestCase):
|
||||
response = self.admin_client.post(url, {'yes': 'true'})
|
||||
self.assertRedirects(response, '/projector/dashboard/')
|
||||
self.assertFalse(ProjectorSlide.objects.exists())
|
||||
|
||||
|
||||
class CountdownControllView(TestCase):
|
||||
def setUp(self):
|
||||
self.admin_client = Client()
|
||||
self.admin_client.login(username='admin', password='admin')
|
||||
|
||||
@patch('openslides.projector.views.reset_countdown')
|
||||
def test_set_default(self, mock_reset_countdown):
|
||||
"""
|
||||
Test, that the url /countdown/set-default/ sets the time for the countdown
|
||||
and reset the countdown.
|
||||
"""
|
||||
self.admin_client.get('/projector/countdown/set-default/', {'countdown_time': 42})
|
||||
self.assertEqual(config['countdown_time'], 42)
|
||||
mock_reset_countdown.assert_called_with()
|
||||
|
Loading…
Reference in New Issue
Block a user