Merge pull request #1614 from normanjaeckel/BundleCountDown

Bundle countdown with list of speakers. Fixed #1541.
This commit is contained in:
Oskar Hahn 2015-09-05 17:12:22 +02:00
commit f802fde7de
5 changed files with 129 additions and 18 deletions

View File

@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.core.config import config
from openslides.core.models import Tag
from openslides.core.projector import Countdown
from openslides.users.models import User
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import RESTModelMixin
@ -409,12 +410,9 @@ class Speaker(RESTModelMixin, models.Model):
self.weight = None
self.begin_time = datetime.now()
self.save()
# start countdown
if config['agenda_couple_countdown_and_speakers']:
# TODO: Fix me with the new countdown api
# reset_countdown()
# start_countdown()
pass
Countdown.control(action='reset')
Countdown.control(action='start')
def end_speech(self):
"""
@ -422,11 +420,8 @@ class Speaker(RESTModelMixin, models.Model):
"""
self.end_time = datetime.now()
self.save()
# stop countdown
if config['agenda_couple_countdown_and_speakers']:
# TODO: Fix me with the new countdown api
# stop_countdown()
pass
Countdown.control(action='stop')
def get_root_rest_element(self):
"""

View File

@ -0,0 +1,35 @@
from django.db import migrations
def add_default_projector_2(apps, schema_editor):
"""
Adds default projector, activates countdown.
"""
# We get the model from the versioned app registry;
# if we directly import it, it will be the wrong version.
Projector = apps.get_model('core', 'Projector')
projector = Projector.objects.get()
config = projector.config
config.append({
'name': 'core/countdown',
'stable': True,
'status': 'stop',
'countdown_time': 60
})
projector.config = config
projector.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.RunPython(
code=add_default_projector_2,
reverse_code=None,
atomic=True,
),
]

View File

@ -3,8 +3,9 @@ from django.utils.translation import ugettext as _
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
from .config import config
from .exceptions import ProjectorException
from .models import CustomSlide
from .models import CustomSlide, Projector
from .views import CustomSlideViewSet
@ -47,31 +48,84 @@ class Countdown(ProjectorElement):
{
"countdown_time": <timestamp>,
"status": "go"
"status": "running"
}
The timestamp is a POSIX timestamp (seconds) calculated from server
time, server time offset and countdown duration (countdown_time = now -
serverTimeOffset + duration).
To stop the countdown set the countdown time to the actual value of the
To stop the countdown set the countdown time to the current value of the
countdown (countdown_time = countdown_time - now + serverTimeOffset)
and set status to "stop".
To reset the countdown (it is not a reset in a functional way) just
change the countdown_time. The status value remain 'stop'.
change the countdown_time. The status value remains "stop".
To hide a running countdown add {"hidden": true}.
There might be an additional value for the "default" countdown time
which is used for the internal reset method if the countdown is coupled
with the list of speakers.
To hide a countdown add {"hidden": true}.
"""
name = 'core/countdown'
def get_context(self):
if self.config_entry.get('countdown_time') is None:
raise ProjectorException(_('No countdown time given.'))
if self.config_entry.get('status') is None:
raise ProjectorException(_('No status given.'))
self.validate_config(self.config_entry)
return {'server_time': now().timestamp()}
@classmethod
def validate_config(cls, config_data):
"""
Raises ProjectorException if the given data are invalid.
"""
if not isinstance(config_data.get('countdown_time'), (int, float)):
raise ProjectorException(_('Invalid countdown time. Use integer or float.'))
if config_data.get('status') not in ('running', 'stop'):
raise ProjectorException(_("Invalid status. Use 'running' or 'stop'."))
if config_data.get('default') is not None and not isinstance(config_data.get('default'), int):
raise ProjectorException(_('Invalid default value. Use integer.'))
@classmethod
def control(cls, action, projector_id=1, index=0):
"""
Starts, stops or resets the countdown with the given index on the
given projector.
Action must be 'start', 'stop' or 'reset'.
"""
if action not in ('start', 'stop', 'reset'):
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))
projector_instance = Projector.objects.get(pk=projector_id)
projector_config = []
found = False
for element in projector_instance.config:
if element['name'] == cls.name:
if index == 0:
try:
cls.validate_config(element)
except ProjectorException:
# Do not proceed if the specific procjector config data is invalid.
# The variable found remains False.
break
found = True
if action == 'start' and element['status'] == 'stop':
element['status'] = 'running'
element['countdown_time'] = now().timestamp() + element['countdown_time']
elif action == 'stop' and element['status'] == 'running':
element['status'] = 'stop'
element['countdown_time'] = element['countdown_time'] - now().timestamp()
elif action == 'reset':
element['status'] = 'stop'
element['countdown_time'] = element.get('default', config['projector_default_countdown'])
else:
index += -1
projector_config.append(element)
if found:
projector_instance.config = projector_config
projector_instance.save()
class Message(ProjectorElement):
"""

View File

@ -135,6 +135,13 @@ def setup_general_config(sender, **kwargs):
group=ugettext_lazy('Projector'),
translatable=True)
yield ConfigVariable(
name='projector_default_countdown',
default_value=60,
label=ugettext_lazy('Default countdown'),
weight=185,
group=ugettext_lazy('Projector'))
config_signal = Signal(providing_args=[])
"""Signal to get all config tabs from all apps."""

View File

@ -3,6 +3,8 @@ from django.core.urlresolvers import reverse
from rest_framework.test import APIClient
from openslides.agenda.models import Item, Speaker
from openslides.core.config import config
from openslides.core.models import Projector
from openslides.utils.test import TestCase
@ -178,3 +180,21 @@ class Speak(TestCase):
def test_end_speech_no_current_speaker(self):
response = self.client.delete(reverse('item-speak', args=[self.item.pk]))
self.assertEqual(response.status_code, 400)
def test_begin_speech_with_countdown(self):
config['agenda_couple_countdown_and_speakers'] = True
Speaker.objects.add(self.user, self.item)
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
self.assertEqual(Projector.objects.get().config[2]['name'], 'core/countdown')
self.client.put(
reverse('item-speak', args=[self.item.pk]),
{'speaker': speaker.pk})
self.assertEqual(Projector.objects.get().config[2]['status'], 'running')
def test_end_speech_with_countdown(self):
config['agenda_couple_countdown_and_speakers'] = True
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
speaker.begin_speech()
self.assertEqual(Projector.objects.get().config[2]['name'], 'core/countdown')
self.client.delete(reverse('item-speak', args=[self.item.pk]))
self.assertEqual(Projector.objects.get().config[2]['status'], 'stop')