Automatic numbering of agenda items
This commit is contained in:
parent
943ede2e81
commit
2c5b3a8e4f
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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})
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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(),
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user