Merge pull request #625 from normanjaeckel/ListOfSpeakersRework

Add second time field for list of speakers.
This commit is contained in:
Oskar Hahn 2013-05-08 08:21:08 -07:00
commit 8af50a36cc
13 changed files with 380 additions and 200 deletions

View File

@ -6,9 +6,9 @@
The OpenSlides agenda app appends the functionality to OpenSlides to
manage agendas.
It includes time-management and list of speakers.
It includes time-management and lists 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.
"""
@ -99,10 +99,44 @@ class Item(MPTTModel, SlideMixin):
True, if the list of speakers is closed.
"""
class Meta:
permissions = (
('can_see_agenda', ugettext_noop("Can see agenda")),
('can_manage_agenda', ugettext_noop("Can manage agenda")),
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")))
class MPTTMeta:
order_insertion_by = ['weight']
def __unicode__(self):
return self.get_title()
def get_absolute_url(self, link='view'):
"""
Return the URL to this item. By default it is the link to its
view or the view of a related object.
The link can be:
* view
* edit
* delete
"""
if link == 'view':
if self.related_sid:
return self.get_related_slide().get_absolute_url(link)
return reverse('item_view', args=[str(self.id)])
if link == 'edit':
if self.related_sid:
return self.get_related_slide().get_absolute_url(link)
return reverse('item_edit', args=[str(self.id)])
if link == 'delete':
return reverse('item_delete', args=[str(self.id)])
def get_related_slide(self):
"""
return the object, of which the item points.
Return the object at which the item points.
"""
# TODO: Rename it to 'get_related_object'
object = get_slide_from_sid(self.related_sid, element=True)
if object is None:
self.title = 'Item for deleted slide: %s' % self.related_sid
@ -114,7 +148,7 @@ class Item(MPTTModel, SlideMixin):
def get_related_type(self):
"""
return the type of the releated slide.
Return the type of the releated slide.
"""
return self.get_related_slide().prefix
@ -129,7 +163,7 @@ class Item(MPTTModel, SlideMixin):
def get_title(self):
"""
return the title of this item.
Return the title of this item.
"""
if self.related_sid is None:
return self.title
@ -137,7 +171,7 @@ class Item(MPTTModel, SlideMixin):
def get_title_supplement(self):
"""
return a supplement for the title.
Return a supplement for the title.
"""
if self.related_sid is None:
return ''
@ -148,30 +182,36 @@ class Item(MPTTModel, SlideMixin):
def slide(self):
"""
Return a map with all Data for the Slide
Return a map with all data for the slide.
There are four cases:
* summary slide
* list of speakers
* related slide, i. e. the slide of the related object
* normal slide of the item
The method returns only one of them according to the config value
'presentation_argument' and the attribut 'related_sid'.
"""
if config['presentation_argument'] == 'summary':
data = {
'title': self.get_title(),
'items': self.get_children(),
'template': 'projector/AgendaSummary.html',
}
data = {'title': self.get_title(),
'items': self.get_children(),
'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')
slice_items = max(0, old_speakers.count()-2)
list_of_speakers = self.get_list_of_speakers(
old_speakers_count=config['agenda_show_last_speakers'])
data = {'title': self.get_title(),
'template': 'projector/agenda_list_of_speaker.html',
'speakers': speakers,
'old_speakers': old_speakers[slice_items:]}
'list_of_speakers': list_of_speakers}
elif self.related_sid:
data = self.get_related_slide().slide()
else:
data = {
'item': self,
'title': self.get_title(),
'template': 'projector/AgendaText.html',
}
data = {'item': self,
'title': self.get_title(),
'template': 'projector/AgendaText.html'}
return data
def set_closed(self, closed=True):
@ -209,44 +249,72 @@ class Item(MPTTModel, SlideMixin):
super(Item, self).delete()
Item.objects.rebuild()
def get_absolute_url(self, link='view'):
def get_list_of_speakers(self, old_speakers_count=None, coming_speakers_count=None):
"""
Return the URL to this item. By default it is the Link to its
slide
link can be:
* view
* edit
* delete
Returns the list of speakers as a list of dictionaries. Each
dictionary contains a prefix, the speaker and its type. Types
are old_speaker, actual_speaker and coming_speaker.
"""
if link == 'view':
if self.related_sid:
return self.get_related_slide().get_absolute_url(link)
return reverse('item_view', args=[str(self.id)])
if link == 'edit':
if self.related_sid:
return self.get_related_slide().get_absolute_url(link)
return reverse('item_edit', args=[str(self.id)])
if link == 'delete':
return reverse('item_delete', args=[str(self.id)])
speaker_query = Speaker.objects.filter(item=self)
list_of_speakers = []
def __unicode__(self):
return self.get_title()
# Parse old speakers
old_speakers = speaker_query.exclude(begin_time=None).exclude(end_time=None).order_by('end_time')
if old_speakers_count is None:
old_speakers_count = old_speakers.count()
last_old_speakers_count = max(0, old_speakers.count() - old_speakers_count)
old_speakers = old_speakers[last_old_speakers_count:]
for number, speaker in enumerate(old_speakers):
prefix = old_speakers_count - number
speaker_dict = {
'prefix': '-%d' % prefix,
'speaker': speaker,
'type': 'old_speaker',
'first_in_group': False,
'last_in_group': False}
if number == 0:
speaker_dict['first_in_group'] = True
if number == old_speakers_count - 1:
speaker_dict['last_in_group'] = True
list_of_speakers.append(speaker_dict)
class Meta:
permissions = (
('can_see_agenda', ugettext_noop("Can see agenda")),
('can_manage_agenda', ugettext_noop("Can manage agenda")),
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")),
)
# Parse actual speaker
try:
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
pass
else:
list_of_speakers.append({
'prefix': '0',
'speaker': actual_speaker,
'type': 'actual_speaker',
'first_in_group': True,
'last_in_group': True})
class MPTTMeta:
order_insertion_by = ['weight']
# Parse coming speakers
coming_speakers = speaker_query.filter(begin_time=None).order_by('weight')
if coming_speakers_count is None:
coming_speakers_count = coming_speakers.count()
coming_speakers = coming_speakers[:max(0, coming_speakers_count)]
for number, speaker in enumerate(coming_speakers):
speaker_dict = {
'prefix': number + 1,
'speaker': speaker,
'type': 'coming_speaker',
'first_in_group': False,
'last_in_group': False}
if number == 0:
speaker_dict['first_in_group'] = True
if number == coming_speakers_count - 1:
speaker_dict['last_in_group'] = True
list_of_speakers.append(speaker_dict)
return list_of_speakers
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 +338,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 +368,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

@ -12,11 +12,11 @@
from django.dispatch import receiver
from django import forms
from django.utils.translation import ugettext_lazy, ugettext_noop
from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from django.template.loader import render_to_string
from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage
from openslides.config.api import config, ConfigVariable, ConfigPage
from openslides.projector.signals import projector_overlays
from openslides.projector.projector import Overlay
@ -40,8 +40,15 @@ def setup_agenda_config_page(sender, **kwargs):
form_field=forms.CharField(
widget=forms.DateTimeInput(format='%d.%m.%Y %H:%M'),
required=False,
label=ugettext_lazy('Begin of event'),
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM')))
label=_('Begin of event'),
help_text=_('Input format: DD.MM.YYYY HH:MM')))
agenda_show_last_speakers = ConfigVariable(
name='agenda_show_last_speakers',
default_value=1,
form_field=forms.IntegerField(
min_value=0,
label=_('Number of last speakers to be shown on the projector')))
extra_stylefiles = ['styles/timepicker.css', 'styles/jquery-ui/jquery-ui.custom.min.css']
extra_javascript = ['javascript/jquery-ui.custom.min.js',
@ -53,7 +60,7 @@ def setup_agenda_config_page(sender, **kwargs):
url='agenda',
required_permission='config.can_manage',
weight=20,
variables=(agenda_start_event_date_time,),
variables=(agenda_start_event_date_time, agenda_show_last_speakers),
extra_context={'extra_stylefiles': extra_stylefiles,
'extra_javascript': extra_javascript})
@ -80,8 +87,10 @@ 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]
context = {'speakers': speakers}
list_of_speakers = slide.get_list_of_speakers(
old_speakers_count=config['agenda_show_last_speakers'],
coming_speakers_count=5)
context = {'list_of_speakers': list_of_speakers}
return render_to_string('agenda/overlay_speaker_projector.html', context)
return Overlay(name, get_widget_html, get_projector_html)

View File

@ -24,8 +24,8 @@ function hideClosedSlides(hide) {
return false;
}
$('#speaker_list_changed_form').submit(function() {
$('#sort_order').val($('#list_of_speakers').sortable("toArray"));
$('#coming_speakers_changed_form').submit(function() {
$('#sort_order').val($('#coming_speakers').sortable("toArray"));
});
$(function() {
@ -76,10 +76,10 @@ $(function() {
//# $('#hide_closed_items').attr('checked', true);
//# }
if ($('#list_of_speakers').length > 0) {
$('#list_of_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
$('#speaker_list_changed_form').show();
if ($('#coming_speakers').length > 0) {
$('#coming_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
$('#coming_speakers_changed_form').show();
}});
$('#list_of_speakers').disableSelection();
$('#coming_speakers').disableSelection();
}
});

View File

@ -22,15 +22,15 @@ table#agendatime td {
white-space: nowrap;
}
#list_of_speakers li {
line-height: 30px;
}
#list_of_speakers {
div#complete_list_of_speakers li {
list-style-type: none;
}
#list_of_speakers span.ui-icon {
div#complete_list_of_speakers li {
line-height: 30px;
}
#coming_speakers span.ui-icon {
position: absolute;
margin-left: -15px;
margin-top: 6px;

View File

@ -1,13 +1,57 @@
{% load i18n %}
{% load tags %}
<style type="text/css">
#overlay_list_of_speaker_box {
position: fixed;
bottom: 0;
right: 0;
border-radius: 0.4em;
border: 0.1em solid #777777;
background-color: #cccccc;
opacity: 0.9;
padding: 0 .5em;
margin: 1em;
z-index: 2;
width: 30%;
min-width: 200px;
}
#overlay_list_of_speaker_box h3 {
margin: 5px;
}
#overlay_list_of_speaker_box ul {
list-style-type: None;
padding-left: 0;
margin: 5px;
}
#overlay_list_of_speaker_box li {
margin-bottom: 1px;
}
#overlay_list_of_speaker_box li span.number {
display: block;
float: left;
width: 1.3em;
}
</style>
<div id="overlay_list_of_speaker_box">
{% if speakers %}
{% if list_of_speakers %}
<h3>{% trans "List of speakers" %}:</h3>
<ol>
{% for speaker in speakers %}
<li>{{ speaker }}</li>
<ul>
{% for speaker_dict in list_of_speakers %}
<li>
{% if speaker_dict.type == 'actual_speaker' %}
<span class="number"></span><strong>
{% else %}
<span class="number">{{ speaker_dict.prefix }}</span>
{% endif %}
<span class="name">{{ speaker_dict.speaker }}</span>
{% if speaker_dict.type == 'actual_speaker' %}
</strong>
{% endif %}
</li>
{% endfor %}
</ol>
</ul>
{% else %}
<i>{% trans 'The list of speakers is empty.' %}</i>
{% endif %}

View File

@ -70,54 +70,11 @@
<i class="icon icon-facetime-video {% if item.active and show_list %}icon-white{% endif %}"></i>
{% trans 'Show list' %}
</a>
{% endif %}
</p>
{% 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 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 %}
{% 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 perms.agenda.can_manage_agenda %}
<form id="speaker_list_changed_form" action="{% url 'agenda_speaker_change_order' item.pk %}" method="post" style="display:none" class="alert alert-warning">{% csrf_token %}
<form id="coming_speakers_changed_form" action="{% url 'agenda_speaker_change_order' item.pk %}" method="post" style="display:none" class="alert alert-warning">{% csrf_token %}
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{% trans "Do you want to save the changed order of speakers?" %}</p>
<input id="sort_order" name="sort_order" type="hidden"></hidden>
@ -128,29 +85,55 @@
</form>
{% endif %}
<div class="well">
<b>{% trans "Next speakers:" %}</b>
<ul {% if perms.agenda.can_manage_agenda %}id="list_of_speakers"{% endif %}>
{% for speaker in 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>
<a href="{% model_url speaker %}">{{ speaker }}</a>
{% if perms.agenda.can_manage_agenda %}
<a href="{% url 'agenda_speaker_speak' item.pk speaker.person.person_id %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans "Next speaker" %}</a>
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
<div id="complete_list_of_speakers" class="well">
{% for speaker_dict in list_of_speakers %}
{% if speaker_dict.first_in_group %}
{% if speaker_dict.type == 'old_speaker' %}
<b>{% trans "Last speakers" %}:</b>
<div class="btn-group" data-toggle="buttons-checkbox">
<button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#old_speakers">
{% trans "Show all speakers" %}
</button>
</div>
{% elif speaker_dict.type == 'actual_speaker' %}
<b>{% trans 'Actual speaker' %}:</b>
{% else %}
<b>{% trans "Next speakers" %}:</b>
{% endif %}
<ul
{% if speaker_dict.type == 'old_speaker' %}
id="old_speakers" class="collapse out"
{% elif speaker_dict.type == 'coming_speaker' %}
id="coming_speakers"
{% endif %}
</li>
{% empty %}
<i>{% trans "The list of speakers is empty." %}</i>
{% endfor %}
</ul>
>
{% endif %}
<li id="speaker_{{ speaker_dict.speaker.pk }}">
{% if speaker_dict.type == 'coming_speaker' %}
<span {% if perms.agenda.can_manage_agenda %}class="ui-icon ui-icon-arrowthick-2-n-s"{% endif %}></span>
{{ speaker_dict.prefix }}.
{% else %}
[{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} {{ speaker_dict.speaker.end_time }}{% endif %}]
{% endif %}
<a href="{% model_url speaker_dict.speaker %}">{{ speaker_dict.speaker }}</a>
{% if perms.agenda.can_manage_agenda %}
{% if speaker_dict.type == 'actual_speaker' %}
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans 'End speach' %}</a>
{% elif speaker_dict.type == 'coming_speaker' %}
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.person.person_id %}" class="btn btn-mini"><i class="icon-bell"></i> {% trans "Next speaker" %}</a>
{% endif %}
<a href="{% model_url speaker_dict.speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}
</li>
{% if speaker_dict.last_in_group %}
</ul>
{% endif %}
{% endfor %}
<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>
@ -174,6 +157,6 @@
{% endfor %}
</form>
{% endif %}
</div>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends "base-projector.html" %}
{% extends 'base-projector.html' %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} - {{ item }}{% endblock %}
{% block title %}{{ block.super }} {{ item }}{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
@ -10,21 +11,36 @@
{% endblock %}
{% block scrollcontent %}
{% if old_speakers|length > 0 %}
<ul class="list_of_speakers last_speakers">
{% for speaker in old_speakers %}
<li>{{ speaker }}</li>
{% endfor %}
</ul>
{% endif %}
{% if speakers %}
<ol class="list_of_speakers">
{% for speaker in speakers %}
<li>{{ speaker }}</li>
<style type="text/css">
ul#list_of_speakers {
list-style-type: None;
}
#overlay_list_of_speaker_box li {
margin-bottom: 1px;
}
#overlay_list_of_speaker_box li span.number {
display: block;
float: left;
width: 1.3em;
}
</style>
{% if list_of_speakers %}
<ul id="list_of_speakers">
{% for speaker_dict in list_of_speakers %}
<li>
{% if speaker_dict.type == 'actual_speaker' %}
<span class="number"></span><strong>
{% else %}
<span class="number">{{ speaker_dict.prefix }} </span>
{% endif %}
<span class="name">{{ speaker_dict.speaker }}</span>
{% if speaker_dict.type == 'actual_speaker' %}
</strong>
{% endif %}
</li>
{% endfor %}
</ol>
</ul>
{% else %}
<p><i>{% trans 'The list of speakers is empty.' %}</i></p>
<i>{% trans 'The list of speakers is empty.' %}</i>
{% endif %}
{% endblock %}

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
@ -37,7 +37,7 @@ from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm
class Overview(TemplateView):
"""
Show all agenda items, and update there range via post.
Show all agenda items, and update their range via post.
"""
permission_required = 'agenda.can_see_agenda'
template_name = 'agenda/overview.html'
@ -127,15 +127,11 @@ 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'))
list_of_speakers = self.object.get_list_of_speakers()
kwargs.update({
'object': self.object,
'speakers': speakers,
'old_speakers': old_speakers,
'is_speaker': Speaker.objects.filter(
time=None, person=self.request.user, item=self.object).exists(),
'list_of_speakers': list_of_speakers,
'is_on_the_list_of_speakers': Speaker.objects.filter(item=self.object, 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)
@ -288,7 +284,7 @@ class SpeakerDeleteView(DeleteView):
if 'speaker' in kwargs:
return request.user.has_perm('agenda.can_manage_agenda')
else:
# Any person how is on the list of speakers can delete him self from the list
# Any person who is on the list of speakers can delete himself from the list.
return True
def get(self, *args, **kwargs):
@ -334,15 +330,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

@ -73,19 +73,6 @@ body{
width: 100%;
height: 100%;
}
#overlay_list_of_speaker_box {
position: fixed;
bottom: 0;
right: 0;
border-radius: 0.4em;
border: 0.1em solid #777777;
background-color: #cccccc;
opacity: 0.9;
padding: 0 1em;
margin: 1em;
z-index: 2;
width: 50%;
}
/*** CONTENT ***/
#contentwrapper {

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)