Rework on list of speakers. Insert a method to get a formated list for various use in the templates.

This commit is contained in:
Norman Jäckel 2013-04-29 20:03:50 +02:00
parent 52e16d2e34
commit 719b6ff790
10 changed files with 211 additions and 202 deletions

View File

@ -6,7 +6,7 @@
The OpenSlides agenda app appends the functionality to OpenSlides to The OpenSlides agenda app appends the functionality to OpenSlides to
manage agendas. manage agendas.
It includes time-management and list of speakers. It includes time-management and lists of speakers.
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.

View File

@ -99,10 +99,44 @@ class Item(MPTTModel, SlideMixin):
True, if the list of speakers is closed. 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): 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) object = get_slide_from_sid(self.related_sid, element=True)
if object is None: if object is None:
self.title = 'Item for deleted slide: %s' % self.related_sid self.title = 'Item for deleted slide: %s' % self.related_sid
@ -114,7 +148,7 @@ class Item(MPTTModel, SlideMixin):
def get_related_type(self): 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 return self.get_related_slide().prefix
@ -129,7 +163,7 @@ class Item(MPTTModel, SlideMixin):
def get_title(self): def get_title(self):
""" """
return the title of this item. Return the title of this item.
""" """
if self.related_sid is None: if self.related_sid is None:
return self.title return self.title
@ -137,7 +171,7 @@ class Item(MPTTModel, SlideMixin):
def get_title_supplement(self): def get_title_supplement(self):
""" """
return a supplement for the title. Return a supplement for the title.
""" """
if self.related_sid is None: if self.related_sid is None:
return '' return ''
@ -148,45 +182,36 @@ class Item(MPTTModel, SlideMixin):
def slide(self): 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': if config['presentation_argument'] == 'summary':
data = { data = {'title': self.get_title(),
'title': self.get_title(), 'items': self.get_children(),
'items': self.get_children(), 'template': 'projector/AgendaSummary.html'}
'template': 'projector/AgendaSummary.html',
}
elif config['presentation_argument'] == 'show_list_of_speakers': elif config['presentation_argument'] == 'show_list_of_speakers':
list_of_speakers = self.get_list_of_speakers(
speaker_query = Speaker.objects.filter(item=self) old_speakers_count=config['agenda_show_last_speakers'])
coming_speakers = speaker_query.filter(begin_time=None).order_by('weight')
old_speakers_count = config['agenda_show_last_speakers']
if old_speakers_count > 0:
old_speakers = speaker_query.exclude(end_time=None)
old_speakers = old_speakers[max(0, old_speakers.count()) - old_speakers_count:]
else:
old_speakers = speaker_query.none()
try:
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist:
actual_speaker = None
speakers = list(old_speakers) + [actual_speaker] + list(coming_speakers)
data = {'title': self.get_title(), data = {'title': self.get_title(),
'template': 'projector/agenda_list_of_speaker.html', 'template': 'projector/agenda_list_of_speaker.html',
'speakers': speakers, 'list_of_speakers': list_of_speakers}
'actual_speaker': actual_speaker,
'old_speakers_count': -(old_speakers_count + 1)}
elif self.related_sid: elif self.related_sid:
data = self.get_related_slide().slide() data = self.get_related_slide().slide()
else: else:
data = { data = {'item': self,
'item': self, 'title': self.get_title(),
'title': self.get_title(), 'template': 'projector/AgendaText.html'}
'template': 'projector/AgendaText.html',
}
return data return data
def set_closed(self, closed=True): def set_closed(self, closed=True):
@ -224,39 +249,67 @@ class Item(MPTTModel, SlideMixin):
super(Item, self).delete() super(Item, self).delete()
Item.objects.rebuild() 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 Returns the list of speakers as a list of dictionaries. Each
slide dictionary contains a prefix, the speaker and its type. Types
are old_speaker, actual_speaker and coming_speaker.
link can be:
* view
* edit
* delete
""" """
if link == 'view': speaker_query = Speaker.objects.filter(item=self)
if self.related_sid: list_of_speakers = []
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 __unicode__(self): # Parse old speakers
return self.get_title() 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: # Parse actual speaker
permissions = ( try:
('can_see_agenda', ugettext_noop("Can see agenda")), actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get()
('can_manage_agenda', ugettext_noop("Can manage agenda")), except Speaker.DoesNotExist:
('can_see_orga_items', ugettext_noop("Can see orga items and time scheduling of agenda")), pass
) else:
list_of_speakers.append({
'prefix': '0',
'speaker': actual_speaker,
'type': 'actual_speaker',
'first_in_group': True,
'last_in_group': True})
class MPTTMeta: # Parse coming speakers
order_insertion_by = ['weight'] 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): class SpeakerManager(models.Manager):

View File

@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop, ugettext as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
from openslides.config.signals import config_signal from openslides.config.signals import config_signal
from openslides.config.api import ConfigVariable, ConfigPage, config from openslides.config.api import config, ConfigVariable, ConfigPage
from openslides.projector.signals import projector_overlays from openslides.projector.signals import projector_overlays
from openslides.projector.projector import Overlay from openslides.projector.projector import Overlay
@ -45,10 +45,10 @@ def setup_agenda_config_page(sender, **kwargs):
agenda_show_last_speakers = ConfigVariable( agenda_show_last_speakers = ConfigVariable(
name='agenda_show_last_speakers', name='agenda_show_last_speakers',
default_value=0, default_value=1,
form_field=forms.IntegerField( form_field=forms.IntegerField(
min_value=0, min_value=0,
label=_('Number of last speakers, to show on the projector'))) 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_stylefiles = ['styles/timepicker.css', 'styles/jquery-ui/jquery-ui.custom.min.css']
extra_javascript = ['javascript/jquery-ui.custom.min.js', extra_javascript = ['javascript/jquery-ui.custom.min.js',
@ -87,27 +87,10 @@ def agenda_list_of_speakers(sender, **kwargs):
# Only show list of speakers on Agenda-Items # Only show list of speakers on Agenda-Items
return None return None
clear_projector_cache() clear_projector_cache()
list_of_speakers = slide.get_list_of_speakers(
speaker_query = Speaker.objects.filter(item=slide) old_speakers_count=config['agenda_show_last_speakers'],
try: coming_speakers_count=5)
actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get() context = {'list_of_speakers': list_of_speakers}
except Speaker.DoesNotExist:
actual_speaker = None
coming_speakers = speaker_query.filter(begin_time=None)[:5]
old_speakers_count = config['agenda_show_last_speakers']
if old_speakers_count > 0:
old_speakers = speaker_query.exclude(end_time=None)
old_speakers = old_speakers[max(0, old_speakers.count()) - old_speakers_count:]
else:
old_speakers = speaker_query.none()
speakers = list(old_speakers) + [actual_speaker] + list(coming_speakers)
context = {
'actual_speaker': actual_speaker,
'speakers': speakers,
'old_speakers_count': -(old_speakers_count + 1)}
return render_to_string('agenda/overlay_speaker_projector.html', context) return render_to_string('agenda/overlay_speaker_projector.html', context)
return Overlay(name, get_widget_html, get_projector_html) return Overlay(name, get_widget_html, get_projector_html)

View File

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

View File

@ -22,15 +22,15 @@ table#agendatime td {
white-space: nowrap; white-space: nowrap;
} }
#list_of_speakers li { div#complete_list_of_speakers li {
line-height: 30px;
}
#list_of_speakers {
list-style-type: none; 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; position: absolute;
margin-left: -15px; margin-left: -15px;
margin-top: 6px; margin-top: 6px;

View File

@ -35,18 +35,18 @@
</style> </style>
<div id="overlay_list_of_speaker_box"> <div id="overlay_list_of_speaker_box">
{% if speakers %} {% if list_of_speakers %}
<h3>{% trans "List of speakers" %}:</h3> <h3>{% trans "List of speakers" %}:</h3>
<ul> <ul>
{% for speaker in speakers %} {% for speaker_dict in list_of_speakers %}
<li> <li>
{% if speaker == actual_speaker %} {% if speaker_dict.type == 'actual_speaker' %}
<span class="number"></span><strong> <span class="number"></span><strong>
{% else %} {% else %}
<span class="number">{{ forloop.counter|addition:old_speakers_count }}</span> <span class="number">{{ speaker_dict.prefix }}</span>
{% endif %} {% endif %}
<span class="name">{{ speaker }}</span> <span class="name">{{ speaker_dict.speaker }}</span>
{% if speaker == actual_speaker %} {% if speaker_dict.type == 'actual_speaker' %}
</strong> </strong>
{% endif %} {% endif %}
</li> </li>

View File

@ -70,52 +70,11 @@
<i class="icon icon-facetime-video {% if item.active and show_list %}icon-white{% endif %}"></i> <i class="icon icon-facetime-video {% if item.active and show_list %}icon-white{% endif %}"></i>
{% trans 'Show list' %} {% trans 'Show list' %}
</a> </a>
{% endif %} {% endif %}
</p> </p>
{% if old_speakers %}
<div class="well">
<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="#all_speakers">
{% trans "Show all speakers" %}
</button>
</div>
<br />
<div id="all_speakers" class="collapse out">
{% for speaker in old_speakers %}
<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>
</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 %}
{% if perms.agenda.can_manage_agenda %} {% 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> <button type="button" class="close" data-dismiss="alert">×</button>
<p>{% trans "Do you want to save the changed order of speakers?" %}</p> <p>{% trans "Do you want to save the changed order of speakers?" %}</p>
<input id="sort_order" name="sort_order" type="hidden"></hidden> <input id="sort_order" name="sort_order" type="hidden"></hidden>
@ -126,25 +85,52 @@
</form> </form>
{% endif %} {% endif %}
<div class="well"> <div id="complete_list_of_speakers" class="well">
<b>{% trans "Next speakers" %}:</b> {% for speaker_dict in list_of_speakers %}
<ul {% if perms.agenda.can_manage_agenda %}id="list_of_speakers"{% endif %}> {% if speaker_dict.first_in_group %}
{% for speaker in coming_speakers %} {% if speaker_dict.type == 'old_speaker' %}
<li id="speaker_{{ speaker.pk }}"> <b>{% trans "Last speakers" %}:</b>
<span {% if perms.agenda.can_manage_agenda %}class="ui-icon ui-icon-arrowthick-2-n-s"{% endif %}></span> <div class="btn-group" data-toggle="buttons-checkbox">
<span>{{ forloop.counter }}.</span> <button type="button" class="btn btn-mini" data-toggle="collapse" data-target="#old_speakers">
<a href="{% model_url speaker %}">{{ speaker }}</a> {% trans "Show all speakers" %}
{% if perms.agenda.can_manage_agenda %} </button>
<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> </div>
<a href="{% model_url speaker 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini"> {% elif speaker_dict.type == 'actual_speaker' %}
<i class="icon-remove"></i> <b>{% trans 'Actual speaker' %}:</b>
</a> {% 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 %} {% endif %}
</li> >
{% empty %} {% endif %}
<i>{% trans "The list of speakers is empty." %}</i> <li id="speaker_{{ speaker_dict.speaker.pk }}">
{% endfor %} {% if speaker_dict.type == 'coming_speaker' %}
</ul> <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> <p>
{% if is_on_the_list_of_speakers %} {% if is_on_the_list_of_speakers %}
@ -171,6 +157,6 @@
{% endfor %} {% endfor %}
</form> </form>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -12,29 +12,29 @@
{% block scrollcontent %} {% block scrollcontent %}
<style type="text/css"> <style type="text/css">
ul#list_of_speakers { ul#list_of_speakers {
list-style-type: None; list-style-type: None;
} }
#overlay_list_of_speaker_box li { #overlay_list_of_speaker_box li {
margin-bottom: 1px; margin-bottom: 1px;
} }
#overlay_list_of_speaker_box li span.number { #overlay_list_of_speaker_box li span.number {
display: block; display: block;
float: left; float: left;
width: 1.3em; width: 1.3em;
} }
</style> </style>
{% if speakers %} {% if list_of_speakers %}
<ul id="list_of_speakers"> <ul id="list_of_speakers">
{% for speaker in speakers %} {% for speaker_dict in list_of_speakers %}
<li> <li>
{% if speaker == actual_speaker %} {% if speaker_dict.type == 'actual_speaker' %}
<span class="number"></span><strong> <span class="number"></span><strong>
{% else %} {% else %}
<span class="number">{{ forloop.counter|addition:old_speakers_count }}</span> <span class="number">{{ speaker_dict.prefix }} </span>
{% endif %} {% endif %}
<span class="name">{{ speaker }}</span> <span class="name">{{ speaker_dict.speaker }}</span>
{% if speaker == actual_speaker %} {% if speaker_dict.type == 'actual_speaker' %}
</strong> </strong>
{% endif %} {% endif %}
</li> </li>

View File

@ -37,7 +37,7 @@ from .forms import ItemOrderForm, ItemForm, AppendSpeakerForm
class Overview(TemplateView): 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' permission_required = 'agenda.can_see_agenda'
template_name = 'agenda/overview.html' template_name = 'agenda/overview.html'
@ -127,19 +127,11 @@ class AgendaItemView(SingleObjectMixin, FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.object = self.get_object() self.object = self.get_object()
speaker_query = Speaker.objects.filter(item=self.object) list_of_speakers = self.object.get_list_of_speakers()
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({ kwargs.update({
'object': self.object, 'object': self.object,
'coming_speakers': coming_speakers, 'list_of_speakers': list_of_speakers,
'old_speakers': old_speakers, 'is_on_the_list_of_speakers': Speaker.objects.filter(item=self.object, begin_time=None, person=self.request.user).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', 'show_list': config['presentation_argument'] == 'show_list_of_speakers',
}) })
return super(AgendaItemView, self).get_context_data(**kwargs) return super(AgendaItemView, self).get_context_data(**kwargs)
@ -292,7 +284,7 @@ class SpeakerDeleteView(DeleteView):
if 'speaker' in kwargs: if 'speaker' in kwargs:
return request.user.has_perm('agenda.can_manage_agenda') return request.user.has_perm('agenda.can_manage_agenda')
else: 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 return True
def get(self, *args, **kwargs): def get(self, *args, **kwargs):

View File

@ -27,11 +27,6 @@ def get_config(key):
return config[key] return config[key]
@register.filter
def addition(value, add):
return int(value) + int(add)
@register.simple_tag @register.simple_tag
def active(request, pattern): def active(request, pattern):
if request.path.startswith(pattern): if request.path.startswith(pattern):