Automatic numbering of agenda items

This commit is contained in:
Stefan Frauenknecht 2014-04-27 21:01:23 +02:00 committed by Norman Jäckel
parent 943ede2e81
commit 2c5b3a8e4f
8 changed files with 156 additions and 4 deletions

View File

@ -16,7 +16,6 @@ class ItemForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
""" """
Form to create of update an item. Form to create of update an item.
""" """
clean_html_fields = ('text', ) clean_html_fields = ('text', )
parent = TreeNodeChoiceField( parent = TreeNodeChoiceField(
@ -32,7 +31,7 @@ class ItemForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
class Meta: class Meta:
model = Item model = Item
exclude = ('closed', 'weight', 'content_type', 'object_id') exclude = ('closed', 'weight', 'content_type', 'object_id', 'item_number')
class RelatedItemForm(ItemForm): class RelatedItemForm(ItemForm):
@ -41,7 +40,7 @@ class RelatedItemForm(ItemForm):
""" """
class Meta: class Meta:
model = Item model = Item
exclude = ('closed', 'type', 'weight', 'content_type', 'object_id', 'title', 'text') exclude = ('closed', 'type', 'weight', 'content_type', 'object_id', 'title', 'text', 'item_number')
class ItemOrderForm(CssClassMixin, forms.Form): class ItemOrderForm(CssClassMixin, forms.Form):

View File

@ -19,6 +19,7 @@ from openslides.projector.models import SlideMixin
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person.models import PersonField from openslides.utils.person.models import PersonField
from openslides.utils.utils import toRoman
class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel): class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
@ -41,6 +42,11 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
Title of the agenda item. Title of the agenda item.
""" """
item_number = models.CharField(null=True, max_length=255, verbose_name=ugettext_lazy("Number"))
"""
Number of agenda item.
"""
text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text")) text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text"))
""" """
The optional text of the agenda item. The optional text of the agenda item.
@ -147,6 +153,9 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
Return the title of this item. Return the title of this item.
""" """
if not self.content_object: if not self.content_object:
if config['agenda_enable_auto_numbering']:
item_no = self.item_no
return item_no + ' ' + self.title if item_no else self.title
return self.title return self.title
try: try:
return self.content_object.get_agenda_title() return self.content_object.get_agenda_title()
@ -284,6 +293,40 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
value = False value = False
return value return value
@property
def item_no(self):
if config['agenda_agenda_fixed']:
item_no = self.item_number
else:
item_no = self.calc_item_no()
if item_no:
return '%s %s' % (config['agenda_number_prefix'], item_no)
def calc_item_no(self):
"""
Returns the number of this agenda item
"""
if self.type == self.AGENDA_ITEM:
if self.is_root_node():
if config['agenda_numeral_system'] == 'a':
return str(self._calc_sibling_no())
else:
return toRoman(self._calc_sibling_no())
else:
return '%s.%s' % (self.parent.calc_item_no(), self._calc_sibling_no())
def _calc_sibling_no(self):
"""
Counts all siblings on the same level which are AGENDA_ITEMs
"""
sibling_no = 0
prev_sibling = self.get_previous_sibling()
while not prev_sibling is None:
if prev_sibling.type == self.AGENDA_ITEM:
sibling_no += 1
prev_sibling = prev_sibling.get_previous_sibling()
return sibling_no + 1
class SpeakerManager(models.Manager): class SpeakerManager(models.Manager):
def add(self, person, item): def add(self, person, item):

View File

@ -60,6 +60,37 @@ def setup_agenda_config(sender, **kwargs):
help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'), help_text=ugettext_lazy('[Begin speach] starts the countdown, [End speach] stops the countdown.'),
required=False)) required=False))
agenda_enable_auto_numbering = ConfigVariable(
name='agenda_enable_auto_numbering',
default_value=False,
form_field=forms.BooleanField(
label=ugettext_lazy('Enable automatic numbering of agenda items'),
required=False))
agenda_number_prefix = ConfigVariable(
name='agenda_number_prefix',
default_value='',
form_field=forms.CharField(
label=ugettext_lazy('Numbering prefix for agenda items'),
max_length=20,
required=False))
agenda_numeral_system = ConfigVariable(
name='agenda_numeral_system',
default_value='a',
form_field=forms.ChoiceField(
label=ugettext_lazy('Numeral System for Top items'),
widget=forms.Select(),
choices=(
('a', ugettext_lazy('Arabic')),
('r', ugettext_lazy('Roman'))),
required=False))
agenda_agenda_fixed = ConfigVariable(
name='agenda_agenda_fixed',
default_value=False,
form_field=None)
extra_stylefiles = ['css/jquery-ui-timepicker.css'] extra_stylefiles = ['css/jquery-ui-timepicker.css']
extra_javascript = ['js/jquery/jquery-ui-timepicker-addon.min.js', extra_javascript = ['js/jquery/jquery-ui-timepicker-addon.min.js',
'js/jquery/jquery-ui-sliderAccess.min.js', 'js/jquery/jquery-ui-sliderAccess.min.js',
@ -71,7 +102,11 @@ def setup_agenda_config(sender, **kwargs):
weight=20, weight=20,
variables=(agenda_start_event_date_time, variables=(agenda_start_event_date_time,
agenda_show_last_speakers, agenda_show_last_speakers,
agenda_couple_countdown_and_speakers), agenda_couple_countdown_and_speakers,
agenda_enable_auto_numbering,
agenda_number_prefix,
agenda_numeral_system,
agenda_agenda_fixed),
extra_context={'extra_stylefiles': extra_stylefiles, extra_context={'extra_stylefiles': extra_stylefiles,
'extra_javascript': extra_javascript}) 'extra_javascript': extra_javascript})

View File

@ -70,6 +70,12 @@
{% else %} {% else %}
<a href="{% url 'config_agenda' %}" class="btn btn-mini pull-right">{% trans 'Set start time of event' %}</a> <a href="{% url 'config_agenda' %}" class="btn btn-mini pull-right">{% trans 'Set start time of event' %}</a>
{% endif %} {% endif %}
{% if perms.agenda.can_manage_agenda and agenda_enable_auto_numbering %}
<a href="{% url 'fix_agenda' %}"
class="btn btn-mini pull-left" {% if agenda_numbering_fixed %}disabled="disabled"{% endif %}>{% trans 'Fix numbering' %}</a>
<a href="{% url 'reset_agenda' %}"
class="btn btn-mini pull-left" {% if not agenda_numbering_fixed %}disabled="disabled"{% endif %}>{% trans 'Reset numbering' %}</a>
{% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -40,6 +40,14 @@ urlpatterns = patterns(
views.AgendaPDF.as_view(), views.AgendaPDF.as_view(),
name='print_agenda'), name='print_agenda'),
url(r'^fix/$',
views.FixAgendaView.as_view(),
name='fix_agenda'),
url(r'^reset/$',
views.ResetAgendaView.as_view(),
name='reset_agenda'),
# List of speakers # List of speakers
url(r'^(?P<pk>\d+)/speaker/$', url(r'^(?P<pk>\d+)/speaker/$',
views.SpeakerAppendView.as_view(), views.SpeakerAppendView.as_view(),

View File

@ -34,6 +34,7 @@ from openslides.utils.views import (
DeleteView, DeleteView,
FormView, FormView,
PDFView, PDFView,
QuestionView,
RedirectView, RedirectView,
SingleObjectMixin, SingleObjectMixin,
TemplateView, TemplateView,
@ -108,9 +109,14 @@ class Overview(TemplateView):
agenda_is_active = None agenda_is_active = None
active_type = None active_type = None
agenda_enable_auto_numbering = True if config['agenda_enable_auto_numbering'] else False
agenda_numbering_fixed = True if config['agenda_agenda_fixed'] else False
context.update({ context.update({
'items': items, 'items': items,
'agenda_is_active': agenda_is_active, 'agenda_is_active': agenda_is_active,
'agenda_enable_auto_numbering': agenda_enable_auto_numbering,
'agenda_numbering_fixed': agenda_numbering_fixed,
'duration': duration, 'duration': duration,
'start': start, 'start': start,
'end': end, 'end': end,
@ -336,6 +342,48 @@ class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
self.item = Item.objects.create(content_object=self.object) self.item = Item.objects.create(content_object=self.object)
class FixAgendaView(QuestionView):
permission_required = 'agenda.can_manage_agenda'
question_url_name = 'item_overview'
url_name = 'item_overview'
question_message = ugettext_lazy('Do you really want to fix the agenda numbering?')
url_name_args = []
def get(self, request, *args, **kwargs):
self.items = Item.objects.all()
return super(FixAgendaView, self).get(request, *args, **kwargs)
def on_clicked_yes(self):
config['agenda_agenda_fixed'] = True
for item in self.items:
item.item_number = item.calc_item_no()
item.save()
def get_final_message(self):
return ugettext_lazy('The agenda has been fixed.')
class ResetAgendaView(QuestionView):
permission_required = 'agenda.can_manage_agenda'
question_url_name = 'item_overview'
url_name = 'item_overview'
question_message = ugettext_lazy('Do you really want to reset the agenda numbering?')
url_name_args = []
def get(self, request, *args, **kwargs):
self.items = Item.objects.all()
return super(ResetAgendaView, self).get(request, *args, **kwargs)
def on_clicked_yes(self):
config['agenda_agenda_fixed'] = False
for item in self.items:
item.item_number = ''
item.save()
def get_final_message(self):
return ugettext_lazy('The agenda has been reset.')
class AgendaPDF(PDFView): class AgendaPDF(PDFView):
""" """
Create a full agenda-PDF. Create a full agenda-PDF.

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import difflib import difflib
import roman
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
@ -71,3 +72,14 @@ def int_or_none(var):
return int(var) return int(var)
except (TypeError, ValueError): except (TypeError, ValueError):
return None return None
def toRoman(number):
"""
Converts an arabic number within range from 1 to 4999 to the corresponding roman number.
Returns None on error conditions.
"""
try:
return roman.toRoman(number)
except (roman.NotIntegerError, roman.OutOfRangeError):
return None

View File

@ -7,6 +7,7 @@ django-mptt>=0.6,<0.7
jsonfield>=0.9,<0.10 jsonfield>=0.9,<0.10
natsort>=3.1,<3.2 natsort>=3.1,<3.2
reportlab>=2.7,<2.8 reportlab>=2.7,<2.8
roman>=2.0,<2.1
setuptools>=2.1,<3.5 setuptools>=2.1,<3.5
sockjs-tornado>=1.0,<1.1 sockjs-tornado>=1.0,<1.1
tornado>=3.1,<3.3 tornado>=3.1,<3.3