From 0b85164b84a62b45b9796e937edd71b6ba1256df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Thu, 25 Apr 2013 16:18:16 +0200 Subject: [PATCH 1/3] Add second time field for list of speakers. Now you can click begin and end speach. --- openslides/agenda/__init__.py | 2 +- openslides/agenda/forms.py | 4 +- openslides/agenda/models.py | 49 ++++++++++--- openslides/agenda/signals.py | 2 +- openslides/agenda/templates/agenda/view.html | 69 +++++++++---------- .../projector/agenda_list_of_speaker.html | 11 +-- openslides/agenda/urls.py | 9 ++- openslides/agenda/views.py | 52 +++++++++++--- tests/agenda/test_list_of_speakers.py | 41 +++++++++-- 9 files changed, 163 insertions(+), 76 deletions(-) diff --git a/openslides/agenda/__init__.py b/openslides/agenda/__init__.py index 004f26495..c72be1dc6 100644 --- a/openslides/agenda/__init__.py +++ b/openslides/agenda/__init__.py @@ -8,7 +8,7 @@ It includes time-management and list of speakers. - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index 38f4ae81a..783398765 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -6,7 +6,7 @@ Forms for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 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))) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index f21886910..37b12f0ee 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -6,7 +6,7 @@ Models for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 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() diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 4d9d99527..50ab1ece8 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -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) diff --git a/openslides/agenda/templates/agenda/view.html b/openslides/agenda/templates/agenda/view.html index 46ec94e29..aaac1ef1c 100644 --- a/openslides/agenda/templates/agenda/view.html +++ b/openslides/agenda/templates/agenda/view.html @@ -77,42 +77,40 @@ {% if old_speakers %}
{% trans "Last speakers" %}: - {% if old_speakers|length > 1 %} -
- -
- {% endif %} -
+
+ +
+
{% for speaker in old_speakers %} - {% if not forloop.last %} - {{forloop.counter}}. - [{{ speaker.time }}h] - {{ speaker }} - {% if perms.agenda.can_manage_agenda %} - - - - {% endif %} -
- {% endif %} + {{forloop.counter}}. + [{{ speaker.begin_time }} – {{ speaker.end_time }}] + {{ speaker }} + {% if perms.agenda.can_manage_agenda %} + + + + {% endif %} +
{% endfor %}
- {% if old_speakers %} - {% with last=old_speakers|last %} - {{ old_speakers|length }}. - [{{ last.time }}h] - {{ last }} - {% if perms.agenda.can_manage_agenda %} - - - - {% endif %} - - {% endwith %} - {% endif %} + +
+{% endif %} + +{% if actual_speaker %} +
+ {% trans 'Actual speaker' %}:
+ [{{ actual_speaker.begin_time }}] + {{ actual_speaker }} + {% if perms.agenda.can_manage_agenda %} + {% trans 'End speach' %} + + + + {% endif %}
{% endif %} @@ -128,11 +126,10 @@ {% endif %} -
- {% trans "Next speakers:" %} + {% trans "Next speakers" %}:

- {% if is_speaker %} + {% if is_on_the_list_of_speakers %} {% trans "Remove me from the list" %} {% elif not object.speaker_list_closed and perms.can_be_speaker %} {% trans "Put me on the list" %} diff --git a/openslides/agenda/templates/projector/agenda_list_of_speaker.html b/openslides/agenda/templates/projector/agenda_list_of_speaker.html index edd00ae2f..857c42c96 100644 --- a/openslides/agenda/templates/projector/agenda_list_of_speaker.html +++ b/openslides/agenda/templates/projector/agenda_list_of_speaker.html @@ -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 %}

{{ title }}

@@ -10,17 +10,18 @@ {% endblock %} {% block scrollcontent %} - {% if old_speakers|length > 0 %} + {% if old_speakers|length > 0 or actual_speaker %} {% endif %} - {% if speakers %} + {% if coming_speakers %}
    - {% for speaker in speakers %} + {% for speaker in coming_speakers %}
  1. {{ speaker }}
  2. {% endfor %}
diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py index bf6416b67..2d7ad1e9f 100644 --- a/openslides/agenda/urls.py +++ b/openslides/agenda/urls.py @@ -6,13 +6,13 @@ URL list for the agenda app. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 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\d+)/speaker/end_speach/$', + SpeakerEndSpeachView.as_view(), + name='agenda_speaker_end_speach', + ), + url(r'^(?P\d+)/speaker/change_order/$', SpeakerChangeOrderView.as_view(), name='agenda_speaker_change_order', diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 234be94b0..0f68a332b 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -6,7 +6,7 @@ Views for the agenda app. - :copyright: 2011, 2012 by the OpenSlides team, see AUTHORS. + :copyright: 2011–2013 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] diff --git a/tests/agenda/test_list_of_speakers.py b/tests/agenda/test_list_of_speakers.py index 9233d6fd0..8a6d82ca4 100644 --- a/tests/agenda/test_list_of_speakers.py +++ b/tests/agenda/test_list_of_speakers.py @@ -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) From 52e16d2e340d877d20ae4bc7dc0553da1d296c55 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 27 Apr 2013 10:16:32 +0200 Subject: [PATCH 2/3] Show a defined value of last speakers Some work on the design of the speaker-list on the projector --- openslides/agenda/models.py | 17 +++++-- openslides/agenda/signals.py | 40 ++++++++++++--- .../agenda/overlay_speaker_projector.html | 50 +++++++++++++++++-- .../projector/agenda_list_of_speaker.html | 45 +++++++++++------ .../projector/static/styles/projector.css | 13 ----- openslides/utils/templatetags/tags.py | 5 ++ 6 files changed, 127 insertions(+), 43 deletions(-) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 37b12f0ee..d2a027ecf 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -161,17 +161,24 @@ class Item(MPTTModel, SlideMixin): 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') + + 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 - slice_items = max(0, old_speakers.count()-2) + speakers = list(old_speakers) + [actual_speaker] + list(coming_speakers) data = {'title': self.get_title(), 'template': 'projector/agenda_list_of_speaker.html', - 'coming_speakers': coming_speakers, - 'old_speakers': old_speakers[slice_items:], - 'actual_speaker': actual_speaker} + 'speakers': speakers, + 'actual_speaker': actual_speaker, + 'old_speakers_count': -(old_speakers_count + 1)} elif self.related_sid: data = self.get_related_slide().slide() else: diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 50ab1ece8..d607b2a8b 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -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 ConfigVariable, ConfigPage, config 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=0, + form_field=forms.IntegerField( + min_value=0, + label=_('Number of last speakers, to show 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,27 @@ def agenda_list_of_speakers(sender, **kwargs): # Only show list of speakers on Agenda-Items return None clear_projector_cache() - speakers = Speaker.objects.filter(begin_time=None, item=slide)[:5] - context = {'speakers': speakers} + + speaker_query = Speaker.objects.filter(item=slide) + try: + actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get() + 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 Overlay(name, get_widget_html, get_projector_html) diff --git a/openslides/agenda/templates/agenda/overlay_speaker_projector.html b/openslides/agenda/templates/agenda/overlay_speaker_projector.html index e1596a4cb..362dbf4f7 100644 --- a/openslides/agenda/templates/agenda/overlay_speaker_projector.html +++ b/openslides/agenda/templates/agenda/overlay_speaker_projector.html @@ -1,13 +1,57 @@ {% load i18n %} +{% load tags %} + +
{% if speakers %}

{% trans "List of speakers" %}:

-
    +
      {% for speaker in speakers %} -
    • {{ speaker }}
    • +
    • + {% if speaker == actual_speaker %} + + {% else %} + {{ forloop.counter|addition:old_speakers_count }} + {% endif %} + {{ speaker }} + {% if speaker == actual_speaker %} + + {% endif %} +
    • {% endfor %} -
+ {% else %} {% trans 'The list of speakers is empty.' %} {% endif %} diff --git a/openslides/agenda/templates/projector/agenda_list_of_speaker.html b/openslides/agenda/templates/projector/agenda_list_of_speaker.html index 857c42c96..184a3a74d 100644 --- a/openslides/agenda/templates/projector/agenda_list_of_speaker.html +++ b/openslides/agenda/templates/projector/agenda_list_of_speaker.html @@ -1,6 +1,7 @@ {% extends 'base-projector.html' %} {% load i18n %} +{% load tags %} {% block title %}{{ block.super }} – {{ item }}{% endblock %} @@ -10,22 +11,36 @@ {% endblock %} {% block scrollcontent %} - {% if old_speakers|length > 0 or actual_speaker %} -
    - {% for speaker in old_speakers %} -
  • {{ speaker }}
  • - {% endfor %} -
  • {{ actual_speaker }} – {% trans 'Actual Speaker' %}
  • -
- {% endif %} - - {% if coming_speakers %} -
    - {% for speaker in coming_speakers %} -
  1. {{ speaker }}
  2. + + {% if speakers %} +
      + {% for speaker in speakers %} +
    • + {% if speaker == actual_speaker %} + + {% else %} + {{ forloop.counter|addition:old_speakers_count }} + {% endif %} + {{ speaker }} + {% if speaker == actual_speaker %} + + {% endif %} +
    • {% endfor %} -
+ {% else %} -

{% trans 'The list of speakers is empty.' %}

+ {% trans 'The list of speakers is empty.' %} {% endif %} {% endblock %} diff --git a/openslides/projector/static/styles/projector.css b/openslides/projector/static/styles/projector.css index 8ceb44822..fbff317f2 100644 --- a/openslides/projector/static/styles/projector.css +++ b/openslides/projector/static/styles/projector.css @@ -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 { diff --git a/openslides/utils/templatetags/tags.py b/openslides/utils/templatetags/tags.py index 7563ebfb2..93640a008 100644 --- a/openslides/utils/templatetags/tags.py +++ b/openslides/utils/templatetags/tags.py @@ -27,6 +27,11 @@ def get_config(key): return config[key] +@register.filter +def addition(value, add): + return int(value) + int(add) + + @register.simple_tag def active(request, pattern): if request.path.startswith(pattern): From 719b6ff790ff3106a93c815203bde7b450a3e3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Mon, 29 Apr 2013 20:03:50 +0200 Subject: [PATCH 3/3] Rework on list of speakers. Insert a method to get a formated list for various use in the templates. --- openslides/agenda/__init__.py | 2 +- openslides/agenda/models.py | 179 ++++++++++++------ openslides/agenda/signals.py | 31 +-- openslides/agenda/static/javascript/agenda.js | 12 +- openslides/agenda/static/styles/agenda.css | 12 +- .../agenda/overlay_speaker_projector.html | 12 +- openslides/agenda/templates/agenda/view.html | 108 +++++------ .../projector/agenda_list_of_speaker.html | 34 ++-- openslides/agenda/views.py | 18 +- openslides/utils/templatetags/tags.py | 5 - 10 files changed, 211 insertions(+), 202 deletions(-) diff --git a/openslides/agenda/__init__.py b/openslides/agenda/__init__.py index c72be1dc6..320161958 100644 --- a/openslides/agenda/__init__.py +++ b/openslides/agenda/__init__.py @@ -6,7 +6,7 @@ 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. :license: GNU GPL, see LICENSE for more details. diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index d2a027ecf..9de46ee4d 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -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,45 +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': - - speaker_query = Speaker.objects.filter(item=self) - - 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) + 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, - 'actual_speaker': actual_speaker, - 'old_speakers_count': -(old_speakers_count + 1)} + '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): @@ -224,39 +249,67 @@ 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): diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index d607b2a8b..e7f92afed 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -16,7 +16,7 @@ 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, config +from openslides.config.api import config, ConfigVariable, ConfigPage from openslides.projector.signals import projector_overlays from openslides.projector.projector import Overlay @@ -45,10 +45,10 @@ def setup_agenda_config_page(sender, **kwargs): agenda_show_last_speakers = ConfigVariable( name='agenda_show_last_speakers', - default_value=0, + default_value=1, form_field=forms.IntegerField( 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_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 return None clear_projector_cache() - - speaker_query = Speaker.objects.filter(item=slide) - try: - actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get() - 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)} + 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) diff --git a/openslides/agenda/static/javascript/agenda.js b/openslides/agenda/static/javascript/agenda.js index fd12a897d..068f5fb2e 100644 --- a/openslides/agenda/static/javascript/agenda.js +++ b/openslides/agenda/static/javascript/agenda.js @@ -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(); } }); diff --git a/openslides/agenda/static/styles/agenda.css b/openslides/agenda/static/styles/agenda.css index f5d1e311b..682330f15 100644 --- a/openslides/agenda/static/styles/agenda.css +++ b/openslides/agenda/static/styles/agenda.css @@ -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; diff --git a/openslides/agenda/templates/agenda/overlay_speaker_projector.html b/openslides/agenda/templates/agenda/overlay_speaker_projector.html index 362dbf4f7..287536aa0 100644 --- a/openslides/agenda/templates/agenda/overlay_speaker_projector.html +++ b/openslides/agenda/templates/agenda/overlay_speaker_projector.html @@ -35,18 +35,18 @@
- {% if speakers %} + {% if list_of_speakers %}

{% trans "List of speakers" %}:

    - {% for speaker in speakers %} + {% for speaker_dict in list_of_speakers %}
  • - {% if speaker == actual_speaker %} + {% if speaker_dict.type == 'actual_speaker' %} {% else %} - {{ forloop.counter|addition:old_speakers_count }} + {{ speaker_dict.prefix }} {% endif %} - {{ speaker }} - {% if speaker == actual_speaker %} + {{ speaker_dict.speaker }} + {% if speaker_dict.type == 'actual_speaker' %} {% endif %}
  • diff --git a/openslides/agenda/templates/agenda/view.html b/openslides/agenda/templates/agenda/view.html index aaac1ef1c..bad1cf15a 100644 --- a/openslides/agenda/templates/agenda/view.html +++ b/openslides/agenda/templates/agenda/view.html @@ -70,52 +70,11 @@ {% trans 'Show list' %} - {% endif %}

    -{% if old_speakers %} -
    - {% trans "Last speakers" %}: -
    - -
    -
    -
    - {% for speaker in old_speakers %} - {{forloop.counter}}. - [{{ speaker.begin_time }} – {{ speaker.end_time }}] - {{ speaker }} - {% if perms.agenda.can_manage_agenda %} - - - - {% endif %} -
    - {% endfor %} -
    - -
    -{% endif %} - -{% if actual_speaker %} -
    - {% trans 'Actual speaker' %}:
    - [{{ actual_speaker.begin_time }}] - {{ actual_speaker }} - {% if perms.agenda.can_manage_agenda %} - {% trans 'End speach' %} - - - - {% endif %} -
    -{% endif %} - {% if perms.agenda.can_manage_agenda %} - {% endif %} -
    - {% trans "Next speakers" %}: -
      - {% for speaker in coming_speakers %} -
    • - - {{ forloop.counter }}. - {{ speaker }} - {% if perms.agenda.can_manage_agenda %} - {% trans "Next speaker" %} - - - +
      + {% for speaker_dict in list_of_speakers %} + {% if speaker_dict.first_in_group %} + {% if speaker_dict.type == 'old_speaker' %} + {% trans "Last speakers" %}: +
      + +
      + {% elif speaker_dict.type == 'actual_speaker' %} + {% trans 'Actual speaker' %}: + {% else %} + {% trans "Next speakers" %}: + {% endif %} +
        - {% empty %} - {% trans "The list of speakers is empty." %} - {% endfor %} -
      + > + {% endif %} +
    • + {% if speaker_dict.type == 'coming_speaker' %} + + {{ speaker_dict.prefix }}. + {% else %} + [{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}] + {% endif %} + {{ speaker_dict.speaker }} + {% if perms.agenda.can_manage_agenda %} + {% if speaker_dict.type == 'actual_speaker' %} + {% trans 'End speach' %} + {% elif speaker_dict.type == 'coming_speaker' %} + {% trans "Next speaker" %} + {% endif %} + + + + {% endif %} +
    • + {% if speaker_dict.last_in_group %} +
    + {% endif %} + {% endfor %}

    {% if is_on_the_list_of_speakers %} @@ -171,6 +157,6 @@ {% endfor %} {% endif %} -

    + {% endblock %} diff --git a/openslides/agenda/templates/projector/agenda_list_of_speaker.html b/openslides/agenda/templates/projector/agenda_list_of_speaker.html index 184a3a74d..c966e9093 100644 --- a/openslides/agenda/templates/projector/agenda_list_of_speaker.html +++ b/openslides/agenda/templates/projector/agenda_list_of_speaker.html @@ -12,29 +12,29 @@ {% block scrollcontent %} - {% if speakers %} + {% if list_of_speakers %}
      - {% for speaker in speakers %} + {% for speaker_dict in list_of_speakers %}
    • - {% if speaker == actual_speaker %} + {% if speaker_dict.type == 'actual_speaker' %} {% else %} - {{ forloop.counter|addition:old_speakers_count }} + {{ speaker_dict.prefix }} {% endif %} - {{ speaker }} - {% if speaker == actual_speaker %} + {{ speaker_dict.speaker }} + {% if speaker_dict.type == 'actual_speaker' %} {% endif %}
    • diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 0f68a332b..0f340bbed 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -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,19 +127,11 @@ class AgendaItemView(SingleObjectMixin, FormView): def get_context_data(self, **kwargs): self.object = self.get_object() - 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 + list_of_speakers = self.object.get_list_of_speakers() kwargs.update({ 'object': self.object, - 'coming_speakers': coming_speakers, - 'old_speakers': old_speakers, - 'actual_speaker': actual_speaker, - 'is_on_the_list_of_speakers': speaker_query.filter(begin_time=None, person=self.request.user).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) @@ -292,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): diff --git a/openslides/utils/templatetags/tags.py b/openslides/utils/templatetags/tags.py index 93640a008..7563ebfb2 100644 --- a/openslides/utils/templatetags/tags.py +++ b/openslides/utils/templatetags/tags.py @@ -27,11 +27,6 @@ def get_config(key): return config[key] -@register.filter -def addition(value, add): - return int(value) + int(add) - - @register.simple_tag def active(request, pattern): if request.path.startswith(pattern):