Add second time field for list of speakers. Now you can click begin and end speach.

This commit is contained in:
Norman Jäckel 2013-04-25 16:18:16 +02:00
parent 0df5c5a2e6
commit 0b85164b84
9 changed files with 163 additions and 76 deletions

View File

@ -8,7 +8,7 @@
It includes time-management and list of speakers.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""

View File

@ -6,7 +6,7 @@
Forms for the agenda app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -69,7 +69,7 @@ class AppendSpeakerForm(CssClassMixin, forms.Form):
Checks, that the user is not already on the list.
"""
speaker = self.cleaned_data['speaker']
if Speaker.objects.filter(person=speaker, item=self.item, time=None).exists():
if Speaker.objects.filter(person=speaker, item=self.item, begin_time=None).exists():
raise forms.ValidationError(ugettext_lazy(
'%s is already on the list of speakers.'
% unicode(speaker)))

View File

@ -6,7 +6,7 @@
Models for the agenda app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -157,13 +157,21 @@ class Item(MPTTModel, SlideMixin):
'template': 'projector/AgendaSummary.html',
}
elif config['presentation_argument'] == 'show_list_of_speakers':
speakers = Speaker.objects.filter(time=None, item=self.pk).order_by('weight')
old_speakers = Speaker.objects.filter(item=self.pk).exclude(time=None).order_by('time')
speaker_query = Speaker.objects.filter(item=self)
coming_speakers = speaker_query.filter(begin_time=None).order_by('weight')
old_speakers = speaker_query.exclude(begin_time=None).exclude(end_time=None).order_by('end_time')
try:
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
actual_speaker = None
slice_items = max(0, old_speakers.count()-2)
data = {'title': self.get_title(),
'template': 'projector/agenda_list_of_speaker.html',
'speakers': speakers,
'old_speakers': old_speakers[slice_items:]}
'coming_speakers': coming_speakers,
'old_speakers': old_speakers[slice_items:],
'actual_speaker': actual_speaker}
elif self.related_sid:
data = self.get_related_slide().slide()
else:
@ -246,7 +254,7 @@ class Item(MPTTModel, SlideMixin):
class SpeakerManager(models.Manager):
def add(self, person, item):
if self.filter(person=person, item=item, time=None).exists():
if self.filter(person=person, item=item, begin_time=None).exists():
raise OpenSlidesError(_('%(person)s is already on the list of speakers of item %(id)s.') % {'person': person, 'id': item.id})
weight = (self.filter(item=item).aggregate(
models.Max('weight'))['weight__max'] or 0)
@ -270,9 +278,14 @@ class Speaker(models.Model):
ForeinKey to the AgendaItem to which the person want to speak.
"""
time = models.DateTimeField(null=True)
begin_time = models.DateTimeField(null=True)
"""
Saves the time, when the speaker has spoken. None, if he has not spoken yet.
Saves the time, when the speaker begins to speak. None, if he has not spoken yet.
"""
end_time = models.DateTimeField(null=True)
"""
Saves the time, when the speaker ends his speach. None, if he is not finished yet.
"""
weight = models.IntegerField(null=True)
@ -295,12 +308,26 @@ class Speaker(models.Model):
return reverse('agenda_speaker_delete',
args=[self.item.pk, self.pk])
def speak(self):
def begin_speach(self):
"""
Let the person speak.
Set the weight to None and the time to now.
Set the weight to None and the time to now. If anyone is still
speaking, end his speach.
"""
try:
actual_speaker = Speaker.objects.filter(item=self.item, end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
pass
else:
actual_speaker.end_speach()
self.weight = None
self.time = datetime.now()
self.begin_time = datetime.now()
self.save()
def end_speach(self):
"""
The speach is finished. Set the time to now.
"""
self.end_time = datetime.now()
self.save()

View File

@ -80,7 +80,7 @@ def agenda_list_of_speakers(sender, **kwargs):
# Only show list of speakers on Agenda-Items
return None
clear_projector_cache()
speakers = Speaker.objects.filter(time=None, item=slide)[:5]
speakers = Speaker.objects.filter(begin_time=None, item=slide)[:5]
context = {'speakers': speakers}
return render_to_string('agenda/overlay_speaker_projector.html', context)

View File

@ -77,42 +77,40 @@
{% if old_speakers %}
<div class="well">
<b>{% trans "Last speakers" %}:</b>
{% if old_speakers|length > 1 %}
<div class="btn-group" data-toggle="buttons-checkbox">
<button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#all_speakers">
{% trans "Show all speakers" %}
</button>
</div>
{% endif %}
<br>
<div class="btn-group" data-toggle="buttons-checkbox">
<button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#all_speakers">
{% trans "Show all speakers" %}
</button>
</div>
<br />
<div id="all_speakers" class="collapse out">
{% for speaker in old_speakers %}
{% if not forloop.last %}
<small>{{forloop.counter}}.
[{{ speaker.time }}h]
<a href="{% model_url speaker %}">{{ speaker }}</a>
{% if perms.agenda.can_manage_agenda %}
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}
</small><br>
{% endif %}
<small>{{forloop.counter}}.
[{{ speaker.begin_time }} {{ speaker.end_time }}]
<a href="{% model_url speaker %}">{{ speaker }}</a>
{% if perms.agenda.can_manage_agenda %}
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}
</small><br />
{% endfor %}
</div>
{% if old_speakers %}
{% with last=old_speakers|last %}
<small>{{ old_speakers|length }}.
[{{ last.time }}h]
<a href="{% model_url last %}">{{ last }}</a>
{% if perms.agenda.can_manage_agenda %}
<a href="{% model_url last 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}
</small>
{% endwith %}
{% endif %}
</div>
{% endif %}
{% if actual_speaker %}
<div class="well">
<b>{% trans 'Actual speaker' %}:</b><br />
[{{ actual_speaker.begin_time }}]
<a href="{% model_url actual_speaker %}">{{ actual_speaker }}</a>
{% if perms.agenda.can_manage_agenda %}
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans 'End speach' %}</a>
<a href="{% model_url actual_speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}
</div>
{% endif %}
@ -128,11 +126,10 @@
</form>
{% endif %}
<div class="well">
<b>{% trans "Next speakers:" %}</b>
<b>{% trans "Next speakers" %}:</b>
<ul {% if perms.agenda.can_manage_agenda %}id="list_of_speakers"{% endif %}>
{% for speaker in speakers %}
{% for speaker in coming_speakers %}
<li id="speaker_{{ speaker.pk }}">
<span {% if perms.agenda.can_manage_agenda %}class="ui-icon ui-icon-arrowthick-2-n-s"{% endif %}></span>
<span>{{ forloop.counter }}.</span>
@ -150,7 +147,7 @@
</ul>
<p>
{% if is_speaker %}
{% if is_on_the_list_of_speakers %}
<a href="{% url 'agenda_speaker_delete' object.id %}" class="btn">{% trans "Remove me from the list" %}</a>
{% elif not object.speaker_list_closed and perms.can_be_speaker %}
<a href="{% url 'agenda_speaker_append' object.id %}" class="btn">{% trans "Put me on the list" %}</a>

View File

@ -1,8 +1,8 @@
{% extends "base-projector.html" %}
{% extends 'base-projector.html' %}
{% load i18n %}
{% block title %}{{ block.super }} - {{ item }}{% endblock %}
{% block title %}{{ block.super }} {{ item }}{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
@ -10,17 +10,18 @@
{% endblock %}
{% block scrollcontent %}
{% if old_speakers|length > 0 %}
{% if old_speakers|length > 0 or actual_speaker %}
<ul class="list_of_speakers last_speakers">
{% for speaker in old_speakers %}
<li>{{ speaker }}</li>
{% endfor %}
<li>{{ actual_speaker }} {% trans 'Actual Speaker' %}</li>
</ul>
{% endif %}
{% if speakers %}
{% if coming_speakers %}
<ol class="list_of_speakers">
{% for speaker in speakers %}
{% for speaker in coming_speakers %}
<li>{{ speaker }}</li>
{% endfor %}
</ol>

View File

@ -6,13 +6,13 @@
URL list for the agenda app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf.urls import url, patterns
from openslides.agenda.views import (
Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView,
Overview, AgendaItemView, SetClosed, ItemUpdate, SpeakerSpeakView, SpeakerEndSpeachView,
ItemCreate, ItemDelete, AgendaPDF, SpeakerAppendView, SpeakerDeleteView,
SpeakerListCloseView, SpeakerChangeOrderView, CurrentListOfSpeakersView)
@ -91,6 +91,11 @@ urlpatterns = patterns(
name='agenda_speaker_speak',
),
url(r'^(?P<pk>\d+)/speaker/end_speach/$',
SpeakerEndSpeachView.as_view(),
name='agenda_speaker_end_speach',
),
url(r'^(?P<pk>\d+)/speaker/change_order/$',
SpeakerChangeOrderView.as_view(),
name='agenda_speaker_change_order',

View File

@ -6,7 +6,7 @@
Views for the agenda app.
:copyright: 2011, 2012 by the OpenSlides team, see AUTHORS.
:copyright: 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
# TODO: Rename all views and template names
@ -127,15 +127,19 @@ class AgendaItemView(SingleObjectMixin, FormView):
def get_context_data(self, **kwargs):
self.object = self.get_object()
speakers = Speaker.objects.filter(time=None, item=self.object.pk).order_by('weight')
old_speakers = list(Speaker.objects.filter(item=self.object.pk)
.exclude(time=None).order_by('time'))
speaker_query = Speaker.objects.filter(item=self.object)
coming_speakers = speaker_query.filter(begin_time=None).order_by('weight')
old_speakers = speaker_query.exclude(begin_time=None).exclude(end_time=None).order_by('end_time')
try:
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
actual_speaker = None
kwargs.update({
'object': self.object,
'speakers': speakers,
'coming_speakers': coming_speakers,
'old_speakers': old_speakers,
'is_speaker': Speaker.objects.filter(
time=None, person=self.request.user, item=self.object).exists(),
'actual_speaker': actual_speaker,
'is_on_the_list_of_speakers': speaker_query.filter(begin_time=None, person=self.request.user).exists(),
'show_list': config['presentation_argument'] == 'show_list_of_speakers',
})
return super(AgendaItemView, self).get_context_data(**kwargs)
@ -334,15 +338,41 @@ class SpeakerSpeakView(SingleObjectMixin, RedirectView):
try:
speaker = Speaker.objects.filter(
person=kwargs['person_id'],
item=self.object.pk).exclude(
weight=None).get()
except Speaker.DoesNotExist:
item=self.object,
begin_time=None).get()
except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
messages.error(
self.request,
_('%(person)s is not on the list of %(item)s.')
% {'person': kwargs['person_id'], 'item': self.object})
else:
speaker.speak()
speaker.begin_speach()
def get_url_name_args(self):
return [self.object.pk]
class SpeakerEndSpeachView(SingleObjectMixin, RedirectView):
"""
The speach of the actual speaker is finished.
"""
permission_required = 'agenda.can_manage_agenda'
url_name = 'item_view'
model = Item
def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
try:
speaker = Speaker.objects.filter(
item=self.object,
end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
messages.error(
self.request,
_('There is no one speaking at the moment according to %(item)s.')
% {'item': self.object})
else:
speaker.end_speach()
def get_url_name_args(self):
return [self.object.pk]

View File

@ -41,7 +41,8 @@ class ListOfSpeakerModelTests(TestCase):
# Check time and weight
for object in (speaker1_item1, speaker2_item1, speaker1_item2):
self.assertIsNone(object.time)
self.assertIsNone(object.begin_time)
self.assertIsNone(object.end_time)
self.assertEqual(speaker1_item1.weight, 1)
self.assertEqual(speaker1_item2.weight, 1)
self.assertEqual(speaker2_item1.weight, 2)
@ -52,13 +53,25 @@ class ListOfSpeakerModelTests(TestCase):
self.item1.save()
self.assertTrue(Item.objects.get(pk=self.item1.pk).speaker_list_closed)
def test_speak(self):
def test_speak_and_finish(self):
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
self.assertIsNone(speaker1_item1.time)
speaker1_item1.speak()
self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).time)
self.assertIsNone(speaker1_item1.begin_time)
self.assertIsNone(speaker1_item1.end_time)
speaker1_item1.begin_speach()
self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).begin_time)
self.assertIsNone(Speaker.objects.get(pk=speaker1_item1.pk).weight)
speaker1_item1.end_speach()
self.assertIsNotNone(Speaker.objects.get(pk=speaker1_item1.pk).end_time)
def test_finish_when_other_speaker_begins(self):
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
speaker2_item1 = Speaker.objects.add(self.speaker2, self.item1)
speaker1_item1.begin_speach()
self.assertIsNone(speaker1_item1.end_time)
self.assertIsNone(speaker2_item1.begin_time)
speaker2_item1.begin_speach()
self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time)
self.assertIsNotNone(speaker2_item1.begin_time)
class SpeakerViewTestCase(TestCase):
@ -160,7 +173,21 @@ class TestSpeakerSpeakView(SpeakerViewTestCase):
speaker = Speaker.objects.add(self.speaker1, self.item1)
response = self.check_url(url, self.admin_client, 302)
speaker = Speaker.objects.get(pk=speaker.pk)
self.assertIsNotNone(speaker.time)
self.assertIsNotNone(speaker.begin_time)
self.assertIsNone(speaker.weight)
class TestSpeakerEndSpeachView(SpeakerViewTestCase):
def test_get(self):
url = '/agenda/1/speaker/end_speach/'
response = self.check_url(url, self.admin_client, 302)
self.assertMessage(response, 'There is no one speaking at the moment according to item1.')
speaker = Speaker.objects.add(self.speaker1, self.item1)
speaker.begin_speach()
response = self.check_url(url, self.admin_client, 302)
speaker = Speaker.objects.get(pk=speaker.pk)
self.assertIsNotNone(speaker.begin_time)
self.assertIsNotNone(speaker.end_time)
self.assertIsNone(speaker.weight)