Updated numbering feature.
Fixed organizational item structuring. Prevented organizational items from having agenda items as descendents. Some coding style changes. Added CHANGELOG and README entries.
This commit is contained in:
parent
2c5b3a8e4f
commit
5254cc83a6
@ -11,6 +11,8 @@ Version 1.6 (unreleased)
|
||||
Agenda:
|
||||
- New projector view with the current list of speakers.
|
||||
- Added CSV import.
|
||||
- Added automatic numbering of agenda items.
|
||||
- Fixed organizational item structuring.
|
||||
Assignment:
|
||||
- Coupled assignment candidates with list of speakers.
|
||||
Dashboard:
|
||||
|
@ -272,6 +272,8 @@ OpenSlides uses the following projects or parts of them:
|
||||
* `ReportLab <http://www.reportlab.com/software/opensource/rl-toolkit/>`_,
|
||||
License: BSD
|
||||
|
||||
* `roman <https://pypi.python.org/pypi/roman>`_, License: Python 2.1.1
|
||||
|
||||
* `sockjs-client <https://github.com/sockjs/sockjs-client>`_,
|
||||
License: MIT
|
||||
|
||||
|
@ -31,7 +31,7 @@ class ItemForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
exclude = ('closed', 'weight', 'content_type', 'object_id', 'item_number')
|
||||
fields = ('item_number', 'title', 'text', 'comment', 'type', 'duration', 'parent', 'speaker_list_closed')
|
||||
|
||||
|
||||
class RelatedItemForm(ItemForm):
|
||||
@ -40,7 +40,7 @@ class RelatedItemForm(ItemForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = Item
|
||||
exclude = ('closed', 'type', 'weight', 'content_type', 'object_id', 'title', 'text', 'item_number')
|
||||
fields = ('comment', 'duration', 'parent', 'speaker_list_closed')
|
||||
|
||||
|
||||
class ItemOrderForm(CssClassMixin, forms.Form):
|
||||
|
@ -5,6 +5,7 @@ from datetime import datetime
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -37,16 +38,16 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
(AGENDA_ITEM, ugettext_lazy('Agenda item')),
|
||||
(ORGANIZATIONAL_ITEM, ugettext_lazy('Organizational item')))
|
||||
|
||||
item_number = models.CharField(blank=True, max_length=255, verbose_name=ugettext_lazy("Number"))
|
||||
"""
|
||||
Number of agenda item.
|
||||
"""
|
||||
|
||||
title = models.CharField(null=True, max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||
"""
|
||||
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"))
|
||||
"""
|
||||
The optional text of the agenda item.
|
||||
@ -122,6 +123,16 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
if self.parent and self.parent.is_active_slide():
|
||||
update_projector()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Ensures that the children of orga items are only orga items.
|
||||
"""
|
||||
if self.type == self.AGENDA_ITEM and self.parent is not None and self.parent.type == self.ORGANIZATIONAL_ITEM:
|
||||
raise ValidationError(_('Agenda items can not be descendents of an organizational item.'))
|
||||
if self.type == self.ORGANIZATIONAL_ITEM and self.get_descendants().filter(type=self.AGENDA_ITEM).exists():
|
||||
raise ValidationError(_('Organizational items can not have agenda items as descendents.'))
|
||||
return super(Item, self).clean()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.get_title()
|
||||
|
||||
@ -155,7 +166,7 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
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 '%s %s' % (item_no, self.title) if item_no else self.title
|
||||
return self.title
|
||||
try:
|
||||
return self.content_object.get_agenda_title()
|
||||
@ -296,32 +307,35 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
@property
|
||||
def item_no(self):
|
||||
if config['agenda_agenda_fixed']:
|
||||
item_no = self.item_number
|
||||
item_number = self.item_number
|
||||
else:
|
||||
item_no = self.calc_item_no()
|
||||
if item_no:
|
||||
return '%s %s' % (config['agenda_number_prefix'], item_no)
|
||||
item_number = self.calc_item_no()
|
||||
if item_number:
|
||||
item_no = '%s %s' % (config['agenda_number_prefix'], item_number)
|
||||
else:
|
||||
item_no = None
|
||||
return item_no
|
||||
|
||||
def calc_item_no(self):
|
||||
"""
|
||||
Returns the number of this agenda item
|
||||
Returns the number of this agenda item.
|
||||
"""
|
||||
if self.type == self.AGENDA_ITEM:
|
||||
if self.is_root_node():
|
||||
if config['agenda_numeral_system'] == 'a':
|
||||
if config['agenda_numeral_system'] == 'arabic':
|
||||
return str(self._calc_sibling_no())
|
||||
else:
|
||||
else: # config['agenda_numeral_system'] == 'roman'
|
||||
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
|
||||
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:
|
||||
while prev_sibling is not None:
|
||||
if prev_sibling.type == self.AGENDA_ITEM:
|
||||
sibling_no += 1
|
||||
prev_sibling = prev_sibling.get_previous_sibling()
|
||||
|
@ -77,19 +77,18 @@ def setup_agenda_config(sender, **kwargs):
|
||||
|
||||
agenda_numeral_system = ConfigVariable(
|
||||
name='agenda_numeral_system',
|
||||
default_value='a',
|
||||
default_value='arabic',
|
||||
form_field=forms.ChoiceField(
|
||||
label=ugettext_lazy('Numeral System for Top items'),
|
||||
label=ugettext_lazy('Numeral system for agenda items'),
|
||||
widget=forms.Select(),
|
||||
choices=(
|
||||
('a', ugettext_lazy('Arabic')),
|
||||
('r', ugettext_lazy('Roman'))),
|
||||
('arabic', ugettext_lazy('Arabic')),
|
||||
('roman', ugettext_lazy('Roman'))),
|
||||
required=False))
|
||||
|
||||
agenda_agenda_fixed = ConfigVariable(
|
||||
name='agenda_agenda_fixed',
|
||||
default_value=False,
|
||||
form_field=None)
|
||||
default_value=False)
|
||||
|
||||
extra_stylefiles = ['css/jquery-ui-timepicker.css']
|
||||
extra_javascript = ['js/jquery/jquery-ui-timepicker-addon.min.js',
|
||||
|
@ -109,14 +109,11 @@ class Overview(TemplateView):
|
||||
agenda_is_active = 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({
|
||||
'items': items,
|
||||
'agenda_is_active': agenda_is_active,
|
||||
'agenda_enable_auto_numbering': agenda_enable_auto_numbering,
|
||||
'agenda_numbering_fixed': agenda_numbering_fixed,
|
||||
'agenda_enable_auto_numbering': config['agenda_enable_auto_numbering'],
|
||||
'agenda_numbering_fixed': config['agenda_agenda_fixed'],
|
||||
'duration': duration,
|
||||
'start': start,
|
||||
'end': end,
|
||||
@ -140,8 +137,14 @@ class Overview(TemplateView):
|
||||
parent = Item.objects.get(id=form.cleaned_data['parent'])
|
||||
except Item.DoesNotExist:
|
||||
parent = None
|
||||
item.weight = form.cleaned_data['weight']
|
||||
else:
|
||||
if item.type == item.AGENDA_ITEM and parent.type == item.ORGANIZATIONAL_ITEM:
|
||||
transaction.rollback()
|
||||
messages.error(
|
||||
request, _('Agenda items can not be descendents of an organizational item.'))
|
||||
break
|
||||
item.parent = parent
|
||||
item.weight = form.cleaned_data['weight']
|
||||
Model.save(item)
|
||||
else:
|
||||
transaction.rollback()
|
||||
@ -349,13 +352,9 @@ class FixAgendaView(QuestionView):
|
||||
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:
|
||||
for item in Item.objects.all():
|
||||
item.item_number = item.calc_item_no()
|
||||
item.save()
|
||||
|
||||
@ -370,13 +369,9 @@ class ResetAgendaView(QuestionView):
|
||||
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:
|
||||
for item in Item.objects.all():
|
||||
item.item_number = ''
|
||||
item.save()
|
||||
|
||||
|
@ -229,6 +229,20 @@ class ViewTest(TestCase):
|
||||
self.assertIsNone(Item.objects.get(pk=1).parent)
|
||||
self.assertEqual(Item.objects.get(pk=2).parent_id, 1)
|
||||
|
||||
def test_change_item_order_with_orga_item(self):
|
||||
self.item1.type = 2
|
||||
self.item1.save()
|
||||
data = {
|
||||
'i1-self': 1,
|
||||
'i1-weight': 50,
|
||||
'i1-parent': 0,
|
||||
'i2-self': 2,
|
||||
'i2-weight': 50,
|
||||
'i2-parent': 1}
|
||||
response = self.adminClient.post('/agenda/', data)
|
||||
self.assertNotEqual(Item.objects.get(pk=2).parent_id, 1)
|
||||
self.assertContains(response, 'Agenda items can not be descendents of an organizational item.')
|
||||
|
||||
def test_delete(self):
|
||||
response = self.adminClient.get('/agenda/%s/del/' % self.item1.pk)
|
||||
self.assertRedirects(response, '/agenda/')
|
||||
@ -276,6 +290,32 @@ class ViewTest(TestCase):
|
||||
response = client.get('/agenda/2/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_orga_item_with_orga_parent_one(self):
|
||||
item1 = Item.objects.create(title='item1_Taeboog1de1sahSeiM8y', type=2)
|
||||
response = self.adminClient.post(
|
||||
'/agenda/new/',
|
||||
{'title': 'item2_faelohD2uK7ohNgeepi2',
|
||||
'type': '1',
|
||||
'parent': item1.pk})
|
||||
self.assertFormError(
|
||||
response,
|
||||
'form',
|
||||
None,
|
||||
'Agenda items can not be descendents of an organizational item.')
|
||||
|
||||
def test_orga_item_with_orga_parent_two(self):
|
||||
item1 = Item.objects.create(title='item1_aeNg4Heibee8ULooneep')
|
||||
Item.objects.create(title='item2_fooshaeroo7Ohvoow0hoo', parent=item1)
|
||||
response = self.adminClient.post(
|
||||
'/agenda/%s/edit/' % item1.pk,
|
||||
{'title': 'item1_aeNg4Heibee8ULooneep_changed',
|
||||
'type': '2'})
|
||||
self.assertFormError(
|
||||
response,
|
||||
'form',
|
||||
None,
|
||||
'Organizational items can not have agenda items as descendents.')
|
||||
|
||||
def test_csv_import(self):
|
||||
item_number = Item.objects.all().count()
|
||||
new_csv_file = SimpleUploadedFile(
|
||||
|
Loading…
Reference in New Issue
Block a user