From 2614a1cb2f89e3c9eaabcbefd40cdc409091ab52 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 11 Mar 2013 20:17:19 +0100 Subject: [PATCH 01/14] Categories for Motions --- openslides/motion/models.py | 18 ++++++++++++------ openslides/motion/urls.py | 18 +++++++++++++++++- openslides/motion/views.py | 24 +++++++++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index f8de1544d..2446747c6 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -65,7 +65,9 @@ class Motion(SlideMixin, models.Model): unique=True) """A string as human readable identifier for the motion.""" - # category = models.ForeignKey('Category', null=True, blank=True) + category = models.ForeignKey('Category', null=True, blank=True) + """ForeignKey to one category of motions.""" + # TODO: proposal #master = models.ForeignKey('self', null=True, blank=True) @@ -523,12 +525,16 @@ class MotionSupporter(models.Model): return unicode(self.person) -## class Category(models.Model): - ## name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) - ## prefix = models.CharField(max_length=32, verbose_name=ugettext_lazy("Category prefix")) +class Category(models.Model): + name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) + prefix = models.CharField(max_length=32, verbose_name=ugettext_lazy("Category prefix")) - ## def __unicode__(self): - ## return self.name + def __unicode__(self): + return self.name + + def get_absolute_url(self, link='update'): + if link == 'update' or link == 'edit': + return reverse('motion_category_update', args=[str(self.id)]) ## class Comment(models.Model): diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index a2b6b49e2..06bccb7b2 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -19,8 +19,9 @@ urlpatterns = patterns('openslides.motion.views', name='motion_list', ), - url(r'^create/$', + url(r'^new/$', 'motion_create', + # TODO: rename to motion_create name='motion_new', ), @@ -103,4 +104,19 @@ urlpatterns = patterns('openslides.motion.views', 'motion_detail_pdf', name='motion_detail_pdf', ), + + url(r'^category/$', + 'category_list', + name='motion_category_list', + ), + + url(r'^category/new/$', + 'category_create', + name='motion_category_create', + ), + + url(r'^category/(?P\d+)/edit/$', + 'category_update', + name='motion_category_update', + ), ) diff --git a/openslides/motion/views.py b/openslides/motion/views.py index da7d27dd4..1e1541d7c 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -32,7 +32,8 @@ from openslides.projector.projector import Widget, SLIDE from openslides.config.models import config from openslides.agenda.models import Item -from .models import Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError +from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, + MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, MotionDisableVersioningMixin, ConfigForm) from .pdf import motions_to_pdf, motion_to_pdf @@ -473,6 +474,27 @@ motion_list_pdf = MotionPDFView.as_view(print_all_motions=True) motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False) +class CategoryListView(ListView): + permission_required = 'motion.can_manage_motion' + model = Category + +category_list = CategoryListView.as_view() + + +class CategoryCreateView(CreateView): + permission_required = 'motion.can_manage_motion' + model = Category + +category_create = CategoryCreateView.as_view() + + +class CategoryUpdateView(UpdateView): + permission_required = 'motion.can_manage_motion' + model = Category + +category_update = CategoryUpdateView.as_view() + + class Config(FormView): """The View for the config tab.""" permission_required = 'config.can_manage_config' From bc7be271315b76585691fc0758d6c697bff8868c Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 11 Mar 2013 20:41:02 +0100 Subject: [PATCH 02/14] Add CategoryFormField to the MotionForm --- openslides/motion/forms.py | 8 +++++++- openslides/motion/views.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 139d4205e..1ddac7b97 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -15,7 +15,7 @@ from django.utils.translation import ugettext as _ from openslides.utils.forms import CssClassMixin from openslides.utils.person import PersonFormField, MultiplePersonFormField -from .models import Motion, Workflow +from .models import Motion, Workflow, Category class BaseMotionForm(forms.ModelForm, CssClassMixin): @@ -90,6 +90,12 @@ class MotionDisableVersioningMixin(forms.ModelForm): last_version will be used.""" +class MotionCategoryMixin(forms.ModelForm): + """Mixin to let the user choose the category for the motion.""" + + category = forms.ModelChoiceField(queryset=Category.objects.all(), required=False) + + class ConfigForm(CssClassMixin, forms.Form): """Form for the configuration tab of OpenSlides.""" motion_min_supporters = forms.IntegerField( diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 1e1541d7c..6e6c979bd 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -35,7 +35,7 @@ from openslides.agenda.models import Item from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, - MotionDisableVersioningMixin, ConfigForm) + MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin) from .pdf import motions_to_pdf, motion_to_pdf @@ -124,6 +124,7 @@ class MotionMixin(object): form_classes = [BaseMotionForm] if self.request.user.has_perm('motion.can_manage_motion'): form_classes.append(MotionSubmitterMixin) + form_classes.append(MotionCategoryMixin) if config['motion_min_supporters'] > 0: form_classes.append(MotionSupporterMixin) if self.object: From f411040c3aa05c3662372d867b79ea97f259bfe2 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 11 Mar 2013 20:43:23 +0100 Subject: [PATCH 03/14] Append missing files --- .../templates/motion/category_form.html | 33 +++++++++++++++++++ .../templates/motion/category_list.html | 16 +++++++++ 2 files changed, 49 insertions(+) create mode 100644 openslides/motion/templates/motion/category_form.html create mode 100644 openslides/motion/templates/motion/category_list.html diff --git a/openslides/motion/templates/motion/category_form.html b/openslides/motion/templates/motion/category_form.html new file mode 100644 index 000000000..5114c9a0a --- /dev/null +++ b/openslides/motion/templates/motion/category_form.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% load tags %} +{% load i18n %} + +{% block title %} + {{ block.super }} – + {% if motion %} + {% trans "Edit category" %} + {% else %} + {% trans "New category" %} + {% endif %} +{% endblock %} + +{% block content %} +

+ {% if motion %} + {% trans "Edit category" %} + {% else %} + {% trans "New category" %} + {% endif %} +

+
{% csrf_token %} + {% include "form.html" %} +

+ {% include "formbuttons_saveapply.html" %} + + {% trans 'Cancel' %} + +

+ * {% trans "required" %} +
+{% endblock %} diff --git a/openslides/motion/templates/motion/category_list.html b/openslides/motion/templates/motion/category_list.html new file mode 100644 index 000000000..175cf2854 --- /dev/null +++ b/openslides/motion/templates/motion/category_list.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% load tags %} +{% load i18n %} + +{% block title %}{{ block.super }} – {% trans "Motions" %}{% endblock %} + +{% block content %} +

{% trans "Categories" %}

+ {% for category in category_list %} +

{{ category }}

+ {% empty %} +

No Categories

+ {% endfor %} + +{% endblock %} From 514577f7c6970b953c3b3e116934ff63a0f4e245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Mon, 11 Mar 2013 21:32:09 +0100 Subject: [PATCH 04/14] Built-in Workflows are created by listening to a signal which is sent after syncdb. --- openslides/core/__init__.py | 0 openslides/core/signals.py | 16 + openslides/main.py | 1 + openslides/motion/fixtures/initial_data.json | 280 ------------------ openslides/motion/models.py | 7 +- openslides/motion/signals.py | 78 ++++- openslides/motion/workflow.py | 55 ---- .../utils/management/commands/syncdb.py | 25 ++ tests/motion/test_models.py | 8 +- 9 files changed, 128 insertions(+), 342 deletions(-) create mode 100644 openslides/core/__init__.py create mode 100644 openslides/core/signals.py delete mode 100644 openslides/motion/fixtures/initial_data.json delete mode 100644 openslides/motion/workflow.py create mode 100644 openslides/utils/management/commands/syncdb.py diff --git a/openslides/core/__init__.py b/openslides/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openslides/core/signals.py b/openslides/core/signals.py new file mode 100644 index 000000000..9d253035a --- /dev/null +++ b/openslides/core/signals.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.core.signals + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Core Signals. + + :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from django.dispatch import Signal + + +post_database_setup = Signal() diff --git a/openslides/main.py b/openslides/main.py index 1baa2c62e..c6b819cf4 100644 --- a/openslides/main.py +++ b/openslides/main.py @@ -27,6 +27,7 @@ from django.core.management import execute_from_command_line from openslides import get_version from openslides.utils.tornado_webserver import run_tornado + CONFIG_TEMPLATE = """#!/usr/bin/env python # -*- coding: utf-8 -*- diff --git a/openslides/motion/fixtures/initial_data.json b/openslides/motion/fixtures/initial_data.json deleted file mode 100644 index 6dc7091c5..000000000 --- a/openslides/motion/fixtures/initial_data.json +++ /dev/null @@ -1,280 +0,0 @@ -[ - { - "pk":1, - "model":"motion.workflow", - "fields":{ - "name":"Simple Workflow", - "first_state":1 - } - }, - { - "pk":2, - "model":"motion.workflow", - "fields":{ - "name":"Complex Workflow", - "first_state":5 - } - }, - { - "pk":1, - "model":"motion.state", - "fields":{ - "name":"submitted", - "workflow":1, - "dont_set_new_version_active":false, - "allow_submitter_edit":true, - "next_states":[ - 2, - 3, - 4 - ], - "allow_support":true, - "action_word":"", - "icon":"", - "versioning":false, - "allow_create_poll":true - } - }, - { - "pk":2, - "model":"motion.state", - "fields":{ - "name":"accepted", - "workflow":1, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"accept", - "icon":"", - "versioning":false, - "allow_create_poll":false - } - }, - { - "pk":3, - "model":"motion.state", - "fields":{ - "name":"rejected", - "workflow":1, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"reject", - "icon":"", - "versioning":false, - "allow_create_poll":false - } - }, - { - "pk":4, - "model":"motion.state", - "fields":{ - "name":"not decided", - "workflow":1, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"do not decide", - "icon":"", - "versioning":false, - "allow_create_poll":false - } - }, - { - "pk":5, - "model":"motion.state", - "fields":{ - "name":"published", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":true, - "next_states":[ - 6, - 9, - 14 - ], - "allow_support":true, - "action_word":"", - "icon":"", - "versioning":false, - "allow_create_poll":false - } - }, - { - "pk":6, - "model":"motion.state", - "fields":{ - "name":"permitted", - "workflow":2, - "dont_set_new_version_active":true, - "allow_submitter_edit":true, - "next_states":[ - 7, - 8, - 9, - 10, - 11, - 12, - 13 - ], - "allow_support":false, - "action_word":"permit", - "icon":"", - "versioning":true, - "allow_create_poll":true - } - }, - { - "pk":7, - "model":"motion.state", - "fields":{ - "name":"accepted", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"accept", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":8, - "model":"motion.state", - "fields":{ - "name":"rejected", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"reject", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":9, - "model":"motion.state", - "fields":{ - "name":"withdrawed", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"withdraw", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":10, - "model":"motion.state", - "fields":{ - "name":"adjourned", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"adjourn", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":11, - "model":"motion.state", - "fields":{ - "name":"not concerned", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":12, - "model":"motion.state", - "fields":{ - "name":"commited a bill", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"commit a bill", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":13, - "model":"motion.state", - "fields":{ - "name":"needs review", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - }, - { - "pk":14, - "model":"motion.state", - "fields":{ - "name":"rejected (not authorized)", - "workflow":2, - "dont_set_new_version_active":false, - "allow_submitter_edit":false, - "next_states":[ - - ], - "allow_support":false, - "action_word":"reject (not authorized)", - "icon":"", - "versioning":true, - "allow_create_poll":false - } - } -] diff --git a/openslides/motion/models.py b/openslides/motion/models.py index f8de1544d..989fe6857 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -349,7 +349,8 @@ class Motion(SlideMixin, models.Model): if self.state: self.state = self.state.workflow.first_state else: - self.state = Workflow.objects.get(pk=config['motion_workflow']).first_state + self.state = (Workflow.objects.get(pk=config['motion_workflow']).first_state or + Workflow.objects.get(pk=config['motion_workflow']).state_set.all()[0]) def slide(self): """Return the slide dict.""" @@ -716,7 +717,7 @@ class Workflow(models.Model): name = models.CharField(max_length=255) """A string representing the workflow.""" - first_state = models.OneToOneField(State, related_name='+') + first_state = models.OneToOneField(State, related_name='+', null=True) """A one-to-one relation to a state, the starting point for the workflow.""" def __unicode__(self): @@ -733,5 +734,5 @@ class Workflow(models.Model): def check_first_state(self): """Checks whether the first_state itself belongs to the workflow.""" - if not self.first_state.workflow == self: + if self.first_state and not self.first_state.workflow == self: raise WorkflowError('%s can not be first state of %s because it does not belong to it.' % (self.first_state, self)) diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index 3b1cb38d3..526a56783 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -11,9 +11,12 @@ """ from django.dispatch import receiver -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ugettext_noop from openslides.config.signals import default_config_value +from openslides.core.signals import post_database_setup + +from .models import Workflow, State @receiver(default_config_value, dispatch_uid="motion_default_config") @@ -28,3 +31,76 @@ def default_config(sender, key, **kwargs): 'motion_pdf_preamble': '', 'motion_allow_disable_versioning': False, 'motion_workflow': 1}.get(key) + + +@receiver(post_database_setup, dispatch_uid='motion_create_builtin_workflows') +def create_builtin_workflows(sender, **kwargs): + """ + Creates a simple and a complex workflow. + """ + workflow_1 = Workflow.objects.create(name=ugettext_noop('Simple Workflow')) + state_1_1 = State.objects.create(name=ugettext_noop('submitted'), + workflow=workflow_1, + allow_create_poll=True, + allow_support=True, + allow_submitter_edit=True) + state_1_2 = State.objects.create(name=ugettext_noop('accepted'), + workflow=workflow_1, + action_word=ugettext_noop('Accept')) + state_1_3 = State.objects.create(name=ugettext_noop('rejected'), + workflow=workflow_1, + action_word=ugettext_noop('Reject')) + state_1_4 = State.objects.create(name=ugettext_noop('not decided'), + workflow=workflow_1, + action_word=ugettext_noop('Do not decide')) + state_1_1.next_states.add(state_1_2, state_1_3, state_1_4) + workflow_1.first_state = state_1_1 + workflow_1.save() + + workflow_2 = Workflow.objects.create(name=ugettext_noop('Complex Workflow')) + state_2_1 = State.objects.create(name=ugettext_noop('published'), + workflow=workflow_2, + allow_support=True, + allow_submitter_edit=True) + state_2_2 = State.objects.create(name=ugettext_noop('permitted'), + workflow=workflow_2, + action_word=ugettext_noop('Permit'), + allow_create_poll=True, + allow_submitter_edit=True, + versioning=True, + dont_set_new_version_active=True) + state_2_3 = State.objects.create(name=ugettext_noop('accepted'), + workflow=workflow_2, + action_word=ugettext_noop('Accept'), + versioning=True) + state_2_4 = State.objects.create(name=ugettext_noop('rejected'), + workflow=workflow_2, + action_word=ugettext_noop('Reject'), + versioning=True) + state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'), + workflow=workflow_2, + action_word=ugettext_noop('Withdraw'), + versioning=True) + state_2_6 = State.objects.create(name=ugettext_noop('adjourned'), + workflow=workflow_2, + action_word=ugettext_noop('Adjourn'), + versioning=True) + state_2_7 = State.objects.create(name=ugettext_noop('not concerned'), + workflow=workflow_2, + action_word=ugettext_noop('Do not concern'), + versioning=True) + state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'), + workflow=workflow_2, + action_word=ugettext_noop('Commit a bill'), + versioning=True) + state_2_9 = State.objects.create(name=ugettext_noop('needs review'), + workflow=workflow_2, + versioning=True) + state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'), + workflow=workflow_2, + action_word=ugettext_noop('reject (not authorized)'), + versioning=True) + state_2_1.next_states.add(state_2_2, state_2_5, state_2_10) + state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9) + workflow_2.first_state = state_2_1 + workflow_2.save() diff --git a/openslides/motion/workflow.py b/openslides/motion/workflow.py deleted file mode 100644 index 42d1e1814..000000000 --- a/openslides/motion/workflow.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - openslides.motion.workflow - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This file is only for development. It will be moved out of - the openslides module before the next release. - - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. - :license: GNU GPL, see LICENSE for more details. -""" - -from django.utils.translation import ugettext_noop - -from .models import Workflow, State - - -def _init_builtin_workflows(): - """ - Saves a simple and a complex workflow into the database. - This function is only called manually and lives here only for development. - """ - workflow_1 = Workflow(name=ugettext_noop('Simple Workflow'), id=1) - state_1_1 = State.objects.create(name=ugettext_noop('submitted'), workflow=workflow_1, - allow_create_poll=True, allow_support=True, allow_submitter_edit=True) - state_1_2 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_1, action_word=ugettext_noop('accept')) - state_1_3 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_1, action_word=ugettext_noop('reject')) - state_1_4 = State.objects.create(name=ugettext_noop('not decided'), workflow=workflow_1, action_word=ugettext_noop('do not decide')) - state_1_1.next_states.add(state_1_2, state_1_3, state_1_4) - state_1_1.save() # Is this neccessary? - workflow_1.first_state = state_1_1 - workflow_1.save() - - workflow_2 = Workflow(name=ugettext_noop('Complex Workflow'), id=2) - state_2_1 = State.objects.create(name=ugettext_noop('published'), workflow=workflow_2, allow_support=True, allow_submitter_edit=True) - state_2_2 = State.objects.create(name=ugettext_noop('permitted'), workflow=workflow_2, action_word=ugettext_noop('permit'), - allow_create_poll=True, allow_submitter_edit=True, versioning=True, dont_set_new_version_active=True) - state_2_3 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_2, action_word=ugettext_noop('accept'), versioning=True) - state_2_4 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_2, action_word=ugettext_noop('reject'), versioning=True) - state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'), workflow=workflow_2, - action_word=ugettext_noop('withdraw'), versioning=True) - state_2_6 = State.objects.create(name=ugettext_noop('adjourned'), workflow=workflow_2, action_word=ugettext_noop('adjourn'), versioning=True) - state_2_7 = State.objects.create(name=ugettext_noop('not concerned'), workflow=workflow_2, versioning=True) - state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'), workflow=workflow_2, - action_word=ugettext_noop('commit a bill'), versioning=True) - state_2_9 = State.objects.create(name=ugettext_noop('needs review'), workflow=workflow_2, versioning=True) - state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'), workflow=workflow_2, - action_word=ugettext_noop('reject (not authorized)'), versioning=True) - state_2_1.next_states.add(state_2_2, state_2_5, state_2_10) - state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9) - state_2_1.save() # Is this neccessary? - state_2_2.save() # Is this neccessary? - workflow_2.first_state = state_2_1 - workflow_2.save() diff --git a/openslides/utils/management/commands/syncdb.py b/openslides/utils/management/commands/syncdb.py new file mode 100644 index 000000000..2835fb220 --- /dev/null +++ b/openslides/utils/management/commands/syncdb.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.utils.management.commands.syncdb + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Overrides the Django syncdb command to setup the database. + + :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from django.core.management.commands.syncdb import Command as _Command + +from openslides.core.signals import post_database_setup + + +class Command(_Command): + """ + Setup the database and sends the signal post_database_setup. + """ + def handle_noargs(self, *args, **kwargs): + return_value = super(Command, self).handle_noargs(*args, **kwargs) + post_database_setup.send(sender=self) + return return_value diff --git a/tests/motion/test_models.py b/tests/motion/test_models.py index 9c647f296..bc1a304f4 100644 --- a/tests/motion/test_models.py +++ b/tests/motion/test_models.py @@ -10,6 +10,7 @@ from django.test import TestCase +from openslides.core.signals import post_database_setup from openslides.participant.models import User from openslides.config.models import config from openslides.motion.models import Motion, Workflow, State @@ -18,6 +19,7 @@ from openslides.motion.exceptions import WorkflowError class ModelTest(TestCase): def setUp(self): + post_database_setup.send(sender=self) self.motion = Motion.objects.create(title='v1') self.test_user = User.objects.create(username='blub') self.workflow = Workflow.objects.get(pk=1) @@ -113,18 +115,18 @@ class ModelTest(TestCase): self.motion.state = State.objects.get(pk=6) self.assertEqual(self.motion.state.name, 'permitted') - self.assertEqual(self.motion.state.get_action_word(), 'permit') + self.assertEqual(self.motion.state.get_action_word(), 'Permit') with self.assertRaises(WorkflowError): self.motion.support(self.test_user) with self.assertRaises(WorkflowError): self.motion.unsupport(self.test_user) def test_new_states_or_workflows(self): - workflow_1 = Workflow(name='W1', id=1000) + workflow_1 = Workflow.objects.create(name='W1') state_1 = State.objects.create(name='S1', workflow=workflow_1) workflow_1.first_state = state_1 workflow_1.save() - workflow_2 = Workflow(name='W2', id=2000) + workflow_2 = Workflow.objects.create(name='W2') state_2 = State.objects.create(name='S2', workflow=workflow_2) workflow_2.first_state = state_2 workflow_2.save() From 31ba70efd10fa937cbb00c18d441f3d48c07a5df Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Mon, 11 Mar 2013 21:38:07 +0100 Subject: [PATCH 05/14] Category Delete View --- openslides/motion/models.py | 2 ++ openslides/motion/urls.py | 5 +++++ openslides/motion/views.py | 8 ++++++++ openslides/utils/views.py | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 2446747c6..023326f2a 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -535,6 +535,8 @@ class Category(models.Model): def get_absolute_url(self, link='update'): if link == 'update' or link == 'edit': return reverse('motion_category_update', args=[str(self.id)]) + if link == 'delete': + return reverse('motion_category_delete', args=[str(self.id)]) ## class Comment(models.Model): diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index 06bccb7b2..1fd507ad1 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -119,4 +119,9 @@ urlpatterns = patterns('openslides.motion.views', 'category_update', name='motion_category_update', ), + + url(r'^category/(?P\d+)/del/$', + 'category_delete', + name='motion_category_delete', + ), ) diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 6e6c979bd..d7a760855 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -496,6 +496,14 @@ class CategoryUpdateView(UpdateView): category_update = CategoryUpdateView.as_view() +class CategoryDeleteView(DeleteView): + permission_required = 'motion.can_manage_motion' + model = Category + success_url_name = 'motion_category_list' + +category_delete = CategoryDeleteView.as_view() + + class Config(FormView): """The View for the config tab.""" permission_required = 'config.can_manage_config' diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 928e56767..00bc6b9fa 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -304,7 +304,8 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView): return super(DeleteView, self).get(request, *args, **kwargs) def get_redirect_url(self, **kwargs): - if self.request.method == 'GET' and self.question_url_name is None: + if self.question_url_name is None and (self.request.method == 'GET' or + self.get_answer() == 'no'): return self.object.get_absolute_url() else: return super(DeleteView, self).get_redirect_url(**kwargs) From 9679be186710b20df557b7c4e58e93d61036fd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Tue, 12 Mar 2013 20:58:22 +0100 Subject: [PATCH 06/14] Move builtin groups to a signal. --- openslides/config/views.py | 4 - openslides/main.py | 1 - openslides/participant/__init__.py | 15 ++ openslides/participant/api.py | 23 +- .../participant/fixtures/groups_de.json | 233 ------------------ openslides/participant/forms.py | 4 +- openslides/participant/models.py | 4 +- openslides/participant/signals.py | 70 ++++++ 8 files changed, 94 insertions(+), 260 deletions(-) delete mode 100644 openslides/participant/fixtures/groups_de.json create mode 100644 openslides/participant/signals.py diff --git a/openslides/config/views.py b/openslides/config/views.py index 997527384..09f0a6968 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -22,9 +22,6 @@ from openslides.utils.views import FormView, TemplateView from .forms import GeneralConfigForm from .models import config -# TODO: Do not import the participant module in config -from openslides.participant.api import get_or_create_anonymous_group - class GeneralConfig(FormView): """ @@ -62,7 +59,6 @@ class GeneralConfig(FormView): # system if form.cleaned_data['system_enable_anonymous']: config['system_enable_anonymous'] = True - get_or_create_anonymous_group() else: config['system_enable_anonymous'] = False diff --git a/openslides/main.py b/openslides/main.py index c6b819cf4..b7107317d 100644 --- a/openslides/main.py +++ b/openslides/main.py @@ -271,7 +271,6 @@ def run_syncdb(): # now initialize the database argv = ["", "syncdb", "--noinput"] execute_from_command_line(argv) - execute_from_command_line(["", "loaddata", "groups_de"]) def set_system_url(url): diff --git a/openslides/participant/__init__.py b/openslides/participant/__init__.py index f04c201d4..12b9658f8 100644 --- a/openslides/participant/__init__.py +++ b/openslides/participant/__init__.py @@ -1,3 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.participant + ~~~~~~~~~~~~~~~~~~~~~~ + + The OpenSlides participant app. + + :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + from django.utils.translation import ugettext_noop +import openslides.participant.signals + + NAME = ugettext_noop('Participant') diff --git a/openslides/participant/api.py b/openslides/participant/api.py index f7b5a80a2..7ffd2fe93 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -97,21 +97,8 @@ def import_users(csv_file): return (count_success, error_messages) -def get_or_create_registered_group(): - registered, created = Group.objects.get_or_create( - name__iexact='Registered', defaults={'name': 'Registered'}) - if created: - registered.permissions = Permission.objects.filter( - codename__in=DEFAULT_PERMS) - registered.save() - return registered - - -def get_or_create_anonymous_group(): - anonymous, created = Group.objects.get_or_create( - name__iexact='Anonymous', defaults={'name': 'Anonymous'}) - if created: - anonymous.permissions = Permission.objects.filter( - codename__in=DEFAULT_PERMS) - anonymous.save() - return anonymous +def get_registered_group(): + """ + Returns the Group 'Registered'. Upper and lower case is possible. + """ + return Group.objects.get(name__iexact='Registered') diff --git a/openslides/participant/fixtures/groups_de.json b/openslides/participant/fixtures/groups_de.json deleted file mode 100644 index b662beff6..000000000 --- a/openslides/participant/fixtures/groups_de.json +++ /dev/null @@ -1,233 +0,0 @@ -[ - { - "pk": 1, - "model": "auth.group", - "fields": { - "name": "Beobachter/in", - "permissions": [ - [ - "can_see_agenda", - "agenda", - "item" - ], - [ - "can_create_motion", - "motion", - "motion" - ], - [ - "can_see_motion", - "motion", - "motion" - ], - [ - "can_nominate_other", - "assignment", - "assignment" - ], - [ - "can_nominate_self", - "assignment", - "assignment" - ], - [ - "can_see_assignment", - "assignment", - "assignment" - ], - [ - "can_see_participant", - "participant", - "user" - ], - [ - "can_see_projector", - "projector", - "projectorslide" - ], - [ - "can_see_dashboard", - "projector", - "projectorslide" - ] - ] - } - }, - { - "pk": 2, - "model": "auth.group", - "fields": { - "name": "Delegierte/r", - "permissions": [ - [ - "can_see_agenda", - "agenda", - "item" - ], - [ - "can_create_motion", - "motion", - "motion" - ], - [ - "can_see_motion", - "motion", - "motion" - ], - [ - "can_support_motion", - "motion", - "motion" - ], - [ - "can_nominate_other", - "assignment", - "assignment" - ], - [ - "can_nominate_self", - "assignment", - "assignment" - ], - [ - "can_see_assignment", - "assignment", - "assignment" - ], - [ - "can_see_participant", - "participant", - "user" - ], - [ - "can_see_projector", - "projector", - "projectorslide" - ], - [ - "can_see_dashboard", - "projector", - "projectorslide" - ] - ] - } - }, - { - "pk": 3, - "model": "auth.group", - "fields": { - "name": "Versammlungsleitung", - "permissions": [ - [ - "can_manage_agenda", - "agenda", - "item" - ], - [ - "can_see_agenda", - "agenda", - "item" - ], - [ - "can_create_motion", - "motion", - "motion" - ], - [ - "can_manage_motion", - "motion", - "motion" - ], - [ - "can_see_motion", - "motion", - "motion" - ], - [ - "can_manage_assignment", - "assignment", - "assignment" - ], - [ - "can_nominate_other", - "assignment", - "assignment" - ], - [ - "can_nominate_self", - "assignment", - "assignment" - ], - [ - "can_see_assignment", - "assignment", - "assignment" - ], - [ - "can_manage_config", - "config", - "configstore" - ], - [ - "can_manage_participant", - "participant", - "user" - ], - [ - "can_see_participant", - "participant", - "user" - ], - [ - "can_manage_projector", - "projector", - "projectorslide" - ], - [ - "can_see_projector", - "projector", - "projectorslide" - ], - [ - "can_see_dashboard", - "projector", - "projectorslide" - ] - ] - } - }, - { - "pk": 4, - "model": "auth.group", - "fields": { - "name": "Teilnehmerverwaltung", - "permissions": [ - [ - "can_see_agenda", - "agenda", - "item" - ], - [ - "can_manage_participant", - "participant", - "user" - ], - [ - "can_see_participant", - "participant", - "user" - ], - [ - "can_see_projector", - "projector", - "projectorslide" - ], - [ - "can_see_dashboard", - "projector", - "projectorslide" - ] - ] - } - } -] diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 854abe91e..2da72bafe 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -19,7 +19,7 @@ from openslides.utils.forms import ( CssClassMixin, LocalizedModelMultipleChoiceField) from openslides.participant.models import User, Group -from openslides.participant.api import get_or_create_registered_group +from openslides.participant.api import get_registered_group class UserCreateForm(forms.ModelForm, CssClassMixin): @@ -30,7 +30,7 @@ class UserCreateForm(forms.ModelForm, CssClassMixin): def __init__(self, *args, **kwargs): if kwargs.get('instance', None) is None: initial = kwargs.setdefault('initial', {}) - registered = get_or_create_registered_group() + registered = get_registered_group() initial['groups'] = [registered.pk] super(UserCreateForm, self).__init__(*args, **kwargs) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 6cad7364f..754096386 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -257,9 +257,9 @@ def djangogroup_post_save(sender, instance, signal, *args, **kwargs): @receiver(signals.post_save, sender=User) def user_post_save(sender, instance, *args, **kwargs): - from openslides.participant.api import get_or_create_registered_group if not kwargs['created']: return - registered = get_or_create_registered_group() + from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible + registered = get_registered_group() instance.groups.add(registered) instance.save() diff --git a/openslides/participant/signals.py b/openslides/participant/signals.py new file mode 100644 index 000000000..7cace7b4f --- /dev/null +++ b/openslides/participant/signals.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.participant.signals + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Signals for the participant app. + + :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from django.dispatch import receiver +from django.utils.translation import ugettext_noop +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import Permission + +from openslides.core.signals import post_database_setup + +from .models import Group + + +@receiver(post_database_setup, dispatch_uid='participant_create_builtin_groups') +def create_builtin_groups(sender, **kwargs): + """ + Creates the builtin groups: Anonymous, Registered, Delegates and Staff. + """ + # Anonymous and Registered + ct_projector = ContentType.objects.get(app_label='projector', model='projectorslide') + perm_1 = Permission.objects.get(content_type=ct_projector, codename='can_see_projector') + perm_2 = Permission.objects.get(content_type=ct_projector, codename='can_see_dashboard') + + ct_agenda = ContentType.objects.get(app_label='agenda', model='item') + perm_3 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda') + + ct_motion = ContentType.objects.get(app_label='motion', model='motion') + perm_4 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion') + + ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment') + perm_5 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment') + + ct_participant = ContentType.objects.get(app_label='participant', model='user') + perm_6 = Permission.objects.get(content_type=ct_participant, codename='can_see_participant') + + group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous')) + group_anonymous.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6) + group_registered = Group.objects.create(name=ugettext_noop('Registered')) + group_registered.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6) + + # Delegates + perm_7 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion') + perm_8 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion') + perm_9 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other') + perm_10 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self') + + group_delegates = Group.objects.create(name=ugettext_noop('Delegates')) + group_delegates.permissions.add(perm_7, perm_8, perm_9, perm_10) + + # Staff + perm_11 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda') + perm_12 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion') + perm_13 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment') + perm_14 = Permission.objects.get(content_type=ct_participant, codename='can_manage_participant') + perm_15 = Permission.objects.get(content_type=ct_projector, codename='can_manage_projector') + + ct_config = ContentType.objects.get(app_label='config', model='configstore') + perm_16 = Permission.objects.get(content_type=ct_config, codename='can_manage_config') + + group_staff = Group.objects.create(name=ugettext_noop('Staff')) + group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_16) From 03fff46517eb599bab665f6b6622d57817267f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Tue, 12 Mar 2013 21:38:22 +0100 Subject: [PATCH 07/14] Add new TestCase class in utils to call the signal on running every test. --- openslides/utils/test.py | 27 +++++++++++++++++++++++++++ tests/agenda/tests.py | 2 +- tests/motion/test_models.py | 5 +---- tests/test_participant.py | 3 +-- 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 openslides/utils/test.py diff --git a/openslides/utils/test.py b/openslides/utils/test.py new file mode 100644 index 000000000..9d7ca3df9 --- /dev/null +++ b/openslides/utils/test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.utils.test + ~~~~~~~~~~~~~~~~~~~~~ + + Unit test class. + + :copyright: 2011-2013 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + + +from django.test import TestCase as _TestCase + +from openslides.core.signals import post_database_setup + + +class TestCase(_TestCase): + """ + Overwrites Django's TestCase class to call the post_database_setup + signal after the preparation of every test. + """ + def _pre_setup(self, *args, **kwargs): + return_value = super(TestCase, self)._pre_setup(*args, **kwargs) + post_database_setup.send(sender=self) + return return_value diff --git a/tests/agenda/tests.py b/tests/agenda/tests.py index d28c5f993..5472fc6a8 100644 --- a/tests/agenda/tests.py +++ b/tests/agenda/tests.py @@ -10,10 +10,10 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase from django.test.client import Client from django.db.models.query import EmptyQuerySet +from openslides.utils.test import TestCase from openslides.projector.api import get_active_slide from openslides.participant.models import User from openslides.agenda.models import Item diff --git a/tests/motion/test_models.py b/tests/motion/test_models.py index bc1a304f4..86498912f 100644 --- a/tests/motion/test_models.py +++ b/tests/motion/test_models.py @@ -8,9 +8,7 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase - -from openslides.core.signals import post_database_setup +from openslides.utils.test import TestCase from openslides.participant.models import User from openslides.config.models import config from openslides.motion.models import Motion, Workflow, State @@ -19,7 +17,6 @@ from openslides.motion.exceptions import WorkflowError class ModelTest(TestCase): def setUp(self): - post_database_setup.send(sender=self) self.motion = Motion.objects.create(title='v1') self.test_user = User.objects.create(username='blub') self.workflow = Workflow.objects.get(pk=1) diff --git a/tests/test_participant.py b/tests/test_participant.py index 4227dc66b..608ee686f 100644 --- a/tests/test_participant.py +++ b/tests/test_participant.py @@ -10,8 +10,7 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase - +from openslides.utils.test import TestCase from openslides.utils.person import get_person, Persons from openslides.participant.api import gen_username, gen_password from openslides.participant.models import User, Group From edce8c59f18365c490eba90b6feef727a5055cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Tue, 12 Mar 2013 21:56:13 +0100 Subject: [PATCH 08/14] Fix PEP8, and forgotten import change. Update .travis. --- .travis.yml | 1 + tests/agenda/tests.py | 1 - tests/motion/test_models.py | 2 +- tests/test_init.py | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9a87327fd..aa3f468b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,3 +8,4 @@ install: script: - coverage run ./manage.py test tests && coverage report -m - pep8 --max-line-length=150 --exclude="urls.py," --statistics openslides + - pep8 --max-line-length=150 --statistics tests diff --git a/tests/agenda/tests.py b/tests/agenda/tests.py index 5472fc6a8..d1a76cf6f 100644 --- a/tests/agenda/tests.py +++ b/tests/agenda/tests.py @@ -95,7 +95,6 @@ class ItemTest(TestCase): self.assertEqual(self.item5.print_related_type(), 'Releateditem') - class ViewTest(TestCase): def setUp(self): self.item1 = Item.objects.create(title='item1') diff --git a/tests/motion/test_models.py b/tests/motion/test_models.py index 86498912f..8e7ddcd4d 100644 --- a/tests/motion/test_models.py +++ b/tests/motion/test_models.py @@ -75,7 +75,7 @@ class ModelTest(TestCase): self.assertEqual(motion.title, 'v2') motion.version = None - motion.version = None # Test to set a version to None, which is already None + motion.version = None # Test to set a version to None, which is already None self.assertEqual(motion.title, 'v3') with self.assertRaises(ValueError): diff --git a/tests/test_init.py b/tests/test_init.py index 411860d79..aa665c62f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -7,9 +7,9 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase - from openslides import get_version, get_git_commit_id +from openslides.utils.test import TestCase + class InitTest(TestCase): def test_get_version(self): From 9cd69f59e0e08cc7bc2bcf8ad3956d94d8fd08e8 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Tue, 12 Mar 2013 22:03:56 +0100 Subject: [PATCH 09/14] Motion Identifier --- openslides/motion/__init__.py | 1 + openslides/motion/models.py | 46 ++++++++++++++++++- .../templates/motion/motion_detail.html | 4 +- openslides/motion/urls.py | 5 ++ openslides/motion/views.py | 26 ++++++++++- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/openslides/motion/__init__.py b/openslides/motion/__init__.py index ea23517a7..e296be3ce 100644 --- a/openslides/motion/__init__.py +++ b/openslides/motion/__init__.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.motion diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 023326f2a..0e61d9b09 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -16,7 +16,7 @@ from datetime import datetime from django.core.urlresolvers import reverse -from django.db import models +from django.db import models, IntegrityError from django.db.models import Max from django.dispatch import receiver from django.utils import formats @@ -65,6 +65,12 @@ class Motion(SlideMixin, models.Model): unique=True) """A string as human readable identifier for the motion.""" + identifier_number = models.IntegerField(null=True) + """Counts the number of the motion in one category. + + Needed to find the next free motion-identifier. + """ + category = models.ForeignKey('Category', null=True, blank=True) """ForeignKey to one category of motions.""" @@ -170,6 +176,29 @@ class Motion(SlideMixin, models.Model): if link == 'delete': return reverse('motion_delete', args=[str(self.id)]) + def set_identifier(self): + # TODO: into the config-tab + config['motion_identifier'] = ('manuell', 'category', 'all')[0] + + number = Motion.objects.all().aggregate(Max('identifier_number'))['identifier_number__max'] or 0 + if self.category is not None: + prefix = self.category.prefix + ' ' + else: + prefix = '' + + while True: + number += 1 + self.identifier = '%s%d' % (prefix, number) + try: + self.save() + except IntegrityError: + continue + else: + self.number = number + self.save() + break + + def get_title(self): """Get the title of the motion. @@ -346,6 +375,18 @@ class Motion(SlideMixin, models.Model): else: raise WorkflowError('You can not create a poll in state %s.' % self.state.name) + def set_state(self, state): + """Set the state of the motion. + + State can be the id of a state object or a state object. + """ + if type(state) is int: + state = State.objects.get(pk=state) + + if state.set_identifier: + self.set_identifier() + self.state = state + def reset_state(self): """Set the state to the default state. If the motion is new, it chooses the default workflow from config.""" if self.state: @@ -692,6 +733,9 @@ class State(models.Model): dont_set_new_version_active = models.BooleanField(default=False) """If true, new versions are not automaticly set active.""" + set_identifier = models.BooleanField(default=False) + """If true, the motion get a identifier if the state change to this one.""" + def __unicode__(self): """Returns the name of the state.""" return self.name diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html index a9de961b6..c9a8b63aa 100644 --- a/openslides/motion/templates/motion/motion_detail.html +++ b/openslides/motion/templates/motion/motion_detail.html @@ -11,8 +11,8 @@ {{ motion.title }}
- {% if motion.number != None %} - {% trans "Motion" %} {{ motion.number }}, + {% if motion.identifier != None %} + {% trans "Motion" %} {{ motion.identifier }}, {% else %} [{% trans "no number" %}], {% endif %} diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index 1fd507ad1..577073c38 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -40,6 +40,11 @@ urlpatterns = patterns('openslides.motion.views', name='motion_delete', ), + url(r'^(?P\d+)/set_identifier', + 'set_identifier', + name='motion_set_identifier', + ), + url(r'^(?P\d+)/version/(?P\d+)/$', 'motion_detail', name='motion_version_detail', diff --git a/openslides/motion/views.py b/openslides/motion/views.py index d7a760855..6d4d40cc8 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -230,6 +230,30 @@ class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, Redir version_reject = VersionRejectView.as_view() +class SetIdentifierView(SingleObjectMixin, RedirectView): + """Set the identifier of the motion. + + See motion.set_identifier for more informations + """ + permission_required = 'motion.can_manage_motion' + model = Motion + url_name = 'motion_detail' + + def get(self, request, *args, **kwargs): + """Set self.object to a motion.""" + self.object = self.get_object() + return super(SetIdentifierView, self).get(request, *args, **kwargs) + + def pre_redirect(self, request, *args, **kwargs): + """Set the identifier.""" + self.object.set_identifier() + + def get_url_name_args(self): + return [self.object.id] + +set_identifier = SetIdentifierView.as_view() + + class SupportView(SingleObjectMixin, QuestionMixin, RedirectView): """View to support or unsupport a motion. @@ -401,7 +425,7 @@ class MotionSetStateView(SingleObjectMixin, RedirectView): if self.reset: self.object.reset_state() else: - self.object.state = State.objects.get(pk=kwargs['state']) + self.object.set_state(int(kwargs['state'])) except WorkflowError, e: # TODO: Is a WorkflowError still possible here? messages.error(request, e) else: From 304887e8f3be77db92e6a9fb72c11e8c6f1938ba Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Tue, 12 Mar 2013 22:13:17 +0100 Subject: [PATCH 10/14] Changed comlex workflow automaticly set the identifier --- openslides/motion/models.py | 9 ++++++--- openslides/motion/signals.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index d975db7c8..73d18d63c 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -383,7 +383,7 @@ class Motion(SlideMixin, models.Model): if type(state) is int: state = State.objects.get(pk=state) - if state.set_identifier: + if not state.dont_set_identifier: self.set_identifier() self.state = state @@ -734,8 +734,11 @@ class State(models.Model): dont_set_new_version_active = models.BooleanField(default=False) """If true, new versions are not automaticly set active.""" - set_identifier = models.BooleanField(default=False) - """If true, the motion get a identifier if the state change to this one.""" + dont_set_identifier = models.BooleanField(default=False) + """Decides if the motion gets an identifier. + + If true, the motion does not get an identifier if the state change to + this one, else it does.""" def __unicode__(self): """Returns the name of the state.""" diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index 526a56783..ff662ddf1 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -61,7 +61,8 @@ def create_builtin_workflows(sender, **kwargs): state_2_1 = State.objects.create(name=ugettext_noop('published'), workflow=workflow_2, allow_support=True, - allow_submitter_edit=True) + allow_submitter_edit=True, + dont_set_identifier=True) state_2_2 = State.objects.create(name=ugettext_noop('permitted'), workflow=workflow_2, action_word=ugettext_noop('Permit'), From b2c888b151693a040c80aeb1df9240453139e6ba Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Tue, 12 Mar 2013 23:35:08 +0100 Subject: [PATCH 11/14] set identifier --- openslides/motion/forms.py | 6 +++++ openslides/motion/models.py | 22 +++++++++++++------ .../templates/motion/motion_detail.html | 2 +- openslides/motion/views.py | 15 ++++++++++++- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 1ddac7b97..7b81d6524 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -96,6 +96,12 @@ class MotionCategoryMixin(forms.ModelForm): category = forms.ModelChoiceField(queryset=Category.objects.all(), required=False) +class MotionIdentifierMixin(forms.ModelForm): + """Mixin to let the user choose the identifier for the motion.""" + + identifier = forms.CharField(required=False) + + class ConfigForm(CssClassMixin, forms.Form): """Form for the configuration tab of OpenSlides.""" motion_min_supporters = forms.IntegerField( diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 73d18d63c..a1b13b950 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -37,6 +37,10 @@ from openslides.agenda.models import Item from .exceptions import MotionError, WorkflowError +# TODO: into the config-tab +config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[0] + + class Motion(SlideMixin, models.Model): """The Motion Class. @@ -177,14 +181,19 @@ class Motion(SlideMixin, models.Model): return reverse('motion_delete', args=[str(self.id)]) def set_identifier(self): - # TODO: into the config-tab - config['motion_identifier'] = ('manuell', 'category', 'all')[0] - - number = Motion.objects.all().aggregate(Max('identifier_number'))['identifier_number__max'] or 0 - if self.category is not None: - prefix = self.category.prefix + ' ' + if config['motion_identifier'] == 'manually': + # Do not set an identifier. + return + elif config['motion_identifier'] == 'per_category': + motions = Motion.objects.filter(category=self.category) else: + motions = Motion.objects.all() + + number = motions.aggregate(Max('identifier_number'))['identifier_number__max'] or 0 + if self.category is None: prefix = '' + else: + prefix = self.category.prefix + ' ' while True: number += 1 @@ -198,7 +207,6 @@ class Motion(SlideMixin, models.Model): self.save() break - def get_title(self): """Get the title of the motion. diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html index c9a8b63aa..12d26fba3 100644 --- a/openslides/motion/templates/motion/motion_detail.html +++ b/openslides/motion/templates/motion/motion_detail.html @@ -8,7 +8,7 @@ {% block content %}

- {{ motion.title }} + {{ motion.title }} {{ motion.category }}
{% if motion.identifier != None %} diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 6d4d40cc8..8af3fef2c 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -35,7 +35,8 @@ from openslides.agenda.models import Item from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, - MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin) + MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin, + MotionIdentifierMixin) from .pdf import motions_to_pdf, motion_to_pdf @@ -98,6 +99,16 @@ class MotionMixin(object): except KeyError: pass + try: + self.object.category = form.cleaned_data['category'] + except KeyError: + pass + + try: + self.object.identifier = form.cleaned_data['identifier'] + except KeyError: + pass + def post_save(self, form): """Save the submitter an the supporter so the motion.""" super(MotionMixin, self).post_save(form) @@ -127,6 +138,8 @@ class MotionMixin(object): form_classes.append(MotionCategoryMixin) if config['motion_min_supporters'] > 0: form_classes.append(MotionSupporterMixin) + if config['motion_identifier'] == 'manually': + form_classes.append(MotionIdentifierMixin) if self.object: if config['motion_allow_disable_versioning'] and self.object.state.versioning: form_classes.append(MotionDisableVersioningMixin) From 71b9bd0a08a208e168e2d707fd4cb7c258d0d717 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Tue, 12 Mar 2013 23:54:37 +0100 Subject: [PATCH 12/14] dont set identifier prefix, if it is empty --- openslides/motion/models.py | 12 +++++++++--- openslides/motion/urls.py | 2 +- openslides/motion/views.py | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index a1b13b950..e4cf800b4 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -38,7 +38,7 @@ from .exceptions import MotionError, WorkflowError # TODO: into the config-tab -config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[0] +config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[2] class Motion(SlideMixin, models.Model): @@ -190,7 +190,7 @@ class Motion(SlideMixin, models.Model): motions = Motion.objects.all() number = motions.aggregate(Max('identifier_number'))['identifier_number__max'] or 0 - if self.category is None: + if self.category is None or not self.category.prefix: prefix = '' else: prefix = self.category.prefix + ' ' @@ -577,7 +577,13 @@ class MotionSupporter(models.Model): class Category(models.Model): name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) - prefix = models.CharField(max_length=32, verbose_name=ugettext_lazy("Category prefix")) + """Name of the category.""" + + prefix = models.CharField(blank=True, max_length=32, verbose_name=ugettext_lazy("Category prefix")) + """Prefix of the category. + + Used to build the identifier of a motion. + """ def __unicode__(self): return self.name diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index 577073c38..85de6fbfc 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -40,7 +40,7 @@ urlpatterns = patterns('openslides.motion.views', name='motion_delete', ), - url(r'^(?P\d+)/set_identifier', + url(r'^(?P\d+)/set_identifier/', 'set_identifier', name='motion_set_identifier', ), diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 8af3fef2c..d397f8b78 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -131,15 +131,19 @@ class MotionMixin(object): will be mixed in dependence of some config values. See motion.forms for more information on the mixins. """ + form_classes = [] + + if (self.request.user.has_perm('motion.can_manage_motion') and + config['motion_identifier'] == 'manually'): + form_classes.append(MotionIdentifierMixin) + + form_classes.append(BaseMotionForm) - form_classes = [BaseMotionForm] if self.request.user.has_perm('motion.can_manage_motion'): form_classes.append(MotionSubmitterMixin) form_classes.append(MotionCategoryMixin) if config['motion_min_supporters'] > 0: form_classes.append(MotionSupporterMixin) - if config['motion_identifier'] == 'manually': - form_classes.append(MotionIdentifierMixin) if self.object: if config['motion_allow_disable_versioning'] and self.object.state.versioning: form_classes.append(MotionDisableVersioningMixin) From 2944b6ded3e23fa38aa1eb5e6219f8f14fc7360c Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Wed, 13 Mar 2013 00:29:53 +0100 Subject: [PATCH 13/14] fixed travis --- openslides/motion/models.py | 4 ---- openslides/motion/views.py | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index e4cf800b4..1c14a35cb 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -37,10 +37,6 @@ from openslides.agenda.models import Item from .exceptions import MotionError, WorkflowError -# TODO: into the config-tab -config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[2] - - class Motion(SlideMixin, models.Model): """The Motion Class. diff --git a/openslides/motion/views.py b/openslides/motion/views.py index d397f8b78..2165e7060 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -40,6 +40,10 @@ from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, from .pdf import motions_to_pdf, motion_to_pdf +# TODO: into the config-tab +config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[2] + + class MotionListView(ListView): """View, to list all motions.""" permission_required = 'motion.can_see_motion' From 0587c2019a7e6ad201f4cdf1d05ec9cf08e29a38 Mon Sep 17 00:00:00 2001 From: Emanuel Schuetze Date: Tue, 12 Mar 2013 21:33:29 +0100 Subject: [PATCH 14/14] Integrated DataTables function for participants table (for client side sorting/filtering/pagination). Removed old server side sorting/filtering code. Fixed csv import (added success url). Added large csv file for testing import of >1000 participants. Removed lage csv example file for paticipants (will be sustitute by a test script in future). --- openslides/locale/de/LC_MESSAGES/djangojs.mo | Bin 460 -> 1069 bytes openslides/locale/de/LC_MESSAGES/djangojs.po | 42 +++- .../templates/participant/overview.html | 94 +++------ openslides/participant/views.py | 73 +------ .../static/javascript/dataTables.bootstrap.js | 177 +++++++++++++++++ .../javascript/jquery.dataTables.min.js | 155 +++++++++++++++ .../dataTables/dataTables.bootstrap.css | 188 ++++++++++++++++++ .../styles/dataTables/images/sort_asc.png | Bin 0 -> 1118 bytes .../dataTables/images/sort_asc_disabled.png | Bin 0 -> 1050 bytes .../styles/dataTables/images/sort_both.png | Bin 0 -> 1136 bytes .../styles/dataTables/images/sort_desc.png | Bin 0 -> 1127 bytes .../dataTables/images/sort_desc_disabled.png | Bin 0 -> 1045 bytes 12 files changed, 599 insertions(+), 130 deletions(-) create mode 100644 openslides/static/javascript/dataTables.bootstrap.js create mode 100644 openslides/static/javascript/jquery.dataTables.min.js create mode 100644 openslides/static/styles/dataTables/dataTables.bootstrap.css create mode 100644 openslides/static/styles/dataTables/images/sort_asc.png create mode 100644 openslides/static/styles/dataTables/images/sort_asc_disabled.png create mode 100644 openslides/static/styles/dataTables/images/sort_both.png create mode 100644 openslides/static/styles/dataTables/images/sort_desc.png create mode 100644 openslides/static/styles/dataTables/images/sort_desc_disabled.png diff --git a/openslides/locale/de/LC_MESSAGES/djangojs.mo b/openslides/locale/de/LC_MESSAGES/djangojs.mo index 20eab68715fc1353c9fcdf32745435ad214b3595..9bb3fd8c13976a7727d5b7b753985b712367c038 100644 GIT binary patch literal 1069 zcmZXSL2nc{6vqvv&@R1z)JjM_ytJwm1YTw~2hvGXHH3`{%_dQHfU1{~GxK)GWY!+p z-lVO#^aeLn2_bRj3-ns`R8IX4sE2-$@^>;20Z)4Vjh~;NpFRJ7FP?cKV4uK0hd+Q{ zfWL%Cqf_G3Z82GtOo;57To;wsq*9oD+6=_^ml zG1a=N*!0DQc0Pzrg<*?-#$aitLRo7wCC8<8m8XfFn@a3BKG1gV#h8^V>r9Mmdl=Ia zYLyGR)U7fB%U!q_=WDxpY=w&9*3BZeP~_YB+Gg?ghGI0_y7P97T6Rd9GETMkxeW4F zCGWEH+M0~ETQ=iHSL`Y6FZFi0wJyk!pXy59m`}aTZAQJF?Od)qRsnnDO%*t!L6R=X zWJ#t28l>6a>nll`BuJFIxLT}zk~fgabvan3G|iIhSCRqp;v>o2DdQUztV17uUpe|- z)rP*ADcyW$eT_-K;%|{`rgK~%ql$Z@TchDg`)NPvty>fDcVzxci}cAmg<}_O$Oba@1HSlK0b( Ph4OBkT4!k-+Y+w;VP_ZW delta 153 zcmZ3>afUhTo)F7a1|VPpVi_RT0b*7lwgF-g2moS9APxj#FGdE2Y#=QP#M^*uAOZuB zUO3QE$WK!!&&W*9P*5#aNGwWK$jD4dNzK!n{Esn&%ShM2RKd{1%E)AL4AVO?9fg#{ Tviv-d%Cgj=r2L|Eph5-!`^Fn$ diff --git a/openslides/locale/de/LC_MESSAGES/djangojs.po b/openslides/locale/de/LC_MESSAGES/djangojs.po index 39a9dff4a..5067a2a49 100644 --- a/openslides/locale/de/LC_MESSAGES/djangojs.po +++ b/openslides/locale/de/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: OpenSlides 1.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-02-05 14:24+0100\n" +"POT-Creation-Date: 2013-03-12 21:29+0100\n" "PO-Revision-Date: 2012-07-28 11:07+0200\n" "Last-Translator: Oskar Hahn \n" "Language: de\n" @@ -20,3 +20,43 @@ msgstr "" #, c-format msgid ", of which %s are hidden." msgstr ", davon %s verborgen." + +#: static/javascript/dataTables.bootstrap.js:19 +msgid "_MENU_ participants per page" +msgstr "_MENU_ Teilnehmer/innen pro Seite" + +#: static/javascript/dataTables.bootstrap.js:20 +msgid "Search:" +msgstr "Suche:" + +#: static/javascript/dataTables.bootstrap.js:21 +msgid "Showing _START_ to _END_ of _TOTAL_ participants" +msgstr "_START_ bis _END_ von _TOTAL_ Teilnehmer/innen" + +#: static/javascript/dataTables.bootstrap.js:22 +msgid "Showing 0 participants" +msgstr "0 Teilnehmer/innen" + +#: static/javascript/dataTables.bootstrap.js:23 +msgid "(filtered from _MAX_ total entries)" +msgstr "(gefiltert von _MAX_ Einträgen)" + +#: static/javascript/dataTables.bootstrap.js:24 +msgid "No matching records found" +msgstr "Keine passenden Einträge gefunden" + +#: static/javascript/dataTables.bootstrap.js:25 +msgid "First" +msgstr "Erster" + +#: static/javascript/dataTables.bootstrap.js:26 +msgid "Last" +msgstr "Letzter" + +#: static/javascript/dataTables.bootstrap.js:27 +msgid "Next" +msgstr "Weiter" + +#: static/javascript/dataTables.bootstrap.js:28 +msgid "Previous" +msgstr "Zurück" diff --git a/openslides/participant/templates/participant/overview.html b/openslides/participant/templates/participant/overview.html index 07c9ae69a..08f4d182e 100644 --- a/openslides/participant/templates/participant/overview.html +++ b/openslides/participant/templates/participant/overview.html @@ -6,10 +6,16 @@ {% block title %}{{ block.super }} – {% trans "Participants" %}{% endblock %} +{% block header %} + +{% endblock %} + {% block javascript %} {% if perms.participant.can_manage_participant %} {% endif %} + + {% endblock %} {% block content %} @@ -49,67 +55,39 @@

-
- {% trans "Filter" %}: -
- - - - - -
-
- - {% if users.count == allusers %} - {{ users.count }} - {% blocktrans count counter=users.count %}participant{% plural %}participants{% endblocktrans %} - {% else %} - {{ users.count }} {% trans "of" %} {{ allusers }} {% trans "Participants" %} (= {{ percent }} %) - {% endif %} - - +
+ - - - - - + + + + + + {% if perms.participant.can_manage_participant %} - - + + {% endif %} + + {% for user in users %} + @@ -137,13 +115,6 @@ {% endif %} - {% if user != request_user and not user.is_superuser %} - - - - {% endif %} {% endif %} @@ -153,5 +124,6 @@ {% endfor %} +
{% trans "First Name" %}{% trans "Last Name" %}{% trans "Structure level" %}{% trans "Type" %}{% trans "Committee" %}{% trans "Present" %}{% trans "First Name" %}{% trans "Last Name" %}{% trans "Structure level" %}{% trans "Type" %}{% trans "Committee" %}{% trans "Comment" %}{% trans "Last Login" %}{% trans "Comment" %}{% trans "Last Login" %} {% trans "Actions" %}
{% if perms.participant.can_manage_participant %} + {% if user != request_user and not user.is_superuser %} + + + + {% endif %} + {% else %} + + + {% endif %} + {{ user.first_name }} {{ user.last_name }} {{ user.structure_level }} {% trans "No participants available." %}
{% endblock %} diff --git a/openslides/participant/views.py b/openslides/participant/views.py index 479f1d00c..0b63db3a0 100644 --- a/openslides/participant/views.py +++ b/openslides/participant/views.py @@ -53,84 +53,20 @@ class UserOverview(ListView): context_object_name = 'users' def get_queryset(self): - try: - sortfilter = encodedict(parse_qs( - self.request.COOKIES['participant_sortfilter'])) - except KeyError: - sortfilter = {} - - for value in ['gender', 'structure_level', 'type', 'committee', 'status', - 'sort', 'reverse']: - if value in self.request.REQUEST: - if self.request.REQUEST[value] == '---': - try: - del sortfilter[value] - except KeyError: - pass - else: - sortfilter[value] = [self.request.REQUEST[value]] - query = User.objects - if 'gender' in sortfilter: - query = query.filter(gender__iexact=sortfilter['gender'][0]) - if 'structure_level' in sortfilter: - query = query.filter(structure_level__iexact=sortfilter['structure_level'][0]) - if 'type' in sortfilter: - query = query.filter(type__iexact=sortfilter['type'][0]) - if 'committee' in sortfilter: - query = query.filter(committee__iexact=sortfilter['committee'][0]) - if 'status' in sortfilter: - query = query.filter(is_active=sortfilter['status'][0]) - if 'sort' in sortfilter: - if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']: - query = query.order_by(sortfilter['sort'][0]) - elif (sortfilter['sort'][0] in - ['structure_level', 'type', 'committee', 'comment']): - query = query.order_by( - '%s' % sortfilter['sort'][0]) + if config['participant_sort_users_by_first_name']: + query = query.order_by('first_name') else: - if config['participant_sort_users_by_first_name']: - query = query.order_by('first_name') - else: - query = query.order_by('last_name') - - if 'reverse' in sortfilter: - query = query.reverse() - - self.sortfilter = sortfilter - + query = query.order_by('last_name') return query.all() def get_context_data(self, **kwargs): context = super(UserOverview, self).get_context_data(**kwargs) - all_users = User.objects.count() - - # quotient of selected users and all users - if all_users > 0: - percent = self.object_list.count() * 100 / float(all_users) - else: - percent = 0 - - # list of all existing categories - structure_levels = [ - p['structure_level'] for p in - User.objects.values('structure_level').exclude(structure_level='').distinct()] - # list of all existing committees - committees = [ - p['committee'] for p in - User.objects.values('committee').exclude(committee='').distinct()] # context vars context.update({ 'allusers': all_users, - 'request_user': self.request.user, - 'percent': round(percent, 1), - 'structure_levels': structure_levels, - 'committees': committees, - 'cookie': [ - 'participant_sortfilter', urlencode(decodedict(self.sortfilter), - doseq=True)], - 'sortfilter': self.sortfilter}) + 'request_user': self.request.user}) return context @@ -343,6 +279,7 @@ class UserImportView(FormView): permission_required = 'participant.can_manage_participant' template_name = 'participant/import.html' form_class = UserImportForm + success_url_name = 'user_import' def form_valid(self, form): # check for valid encoding (will raise UnicodeDecodeError if not) diff --git a/openslides/static/javascript/dataTables.bootstrap.js b/openslides/static/javascript/dataTables.bootstrap.js new file mode 100644 index 000000000..3abf0447c --- /dev/null +++ b/openslides/static/javascript/dataTables.bootstrap.js @@ -0,0 +1,177 @@ +/*! + * DataTables jquery plugin (for Twitter Bootstrap style) + * + * Copyright 2012 + * DataTables is dual licensed under the GPL v2 license or a BSD (3-point) license. + * http://www.datatables.net + * http://www.datatables.net/blog/Twitter_Bootstrap_2 + * + */ + +/* Table initialisation */ +$(document).ready(function() { + $('#dataTableParticipants').dataTable( { + "aLengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]], + "aoColumnDefs": [ + { "bSortable": false, "aTargets": [ -1 ] } + ], + "oLanguage": { + "sLengthMenu": gettext("_MENU_ participants per page"), + "sSearch": gettext("Search:"), + "sInfo": gettext("Showing _START_ to _END_ of _TOTAL_ participants"), + "sInfoEmpty": gettext("Showing 0 participants"), + "sInfoFiltered": gettext("(filtered from _MAX_ total entries)"), + "sZeroRecords": gettext("No matching records found"), + "sFirst": gettext("First"), + "sLast": gettext("Last"), + "sNext": gettext("Next"), + "sPrevious": gettext("Previous"), + } + } ); +} ); + + +/* Set the defaults for DataTables initialisation */ +$.extend( true, $.fn.dataTable.defaults, { + "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>", + "sPaginationType": "bootstrap", +} ); + + +/* Default class modification */ +$.extend( $.fn.dataTableExt.oStdClasses, { + "sWrapper": "dataTables_wrapper form-inline" +} ); + + +/* API method to get paging information */ +$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings ) +{ + return { + "iStart": oSettings._iDisplayStart, + "iEnd": oSettings.fnDisplayEnd(), + "iLength": oSettings._iDisplayLength, + "iTotal": oSettings.fnRecordsTotal(), + "iFilteredTotal": oSettings.fnRecordsDisplay(), + "iPage": oSettings._iDisplayLength === -1 ? + 0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ), + "iTotalPages": oSettings._iDisplayLength === -1 ? + 0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength ) + }; +}; + + +/* Bootstrap style pagination control */ +$.extend( $.fn.dataTableExt.oPagination, { + "bootstrap": { + "fnInit": function( oSettings, nPaging, fnDraw ) { + var oLang = oSettings.oLanguage.oPaginate; + var fnClickHandler = function ( e ) { + e.preventDefault(); + if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) { + fnDraw( oSettings ); + } + }; + + $(nPaging).addClass('pagination').append( + '' + ); + var els = $('a', nPaging); + $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); + $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); + }, + + "fnUpdate": function ( oSettings, fnDraw ) { + var iListLength = 5; + var oPaging = oSettings.oInstance.fnPagingInfo(); + var an = oSettings.aanFeatures.p; + var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); + + if ( oPaging.iTotalPages < iListLength) { + iStart = 1; + iEnd = oPaging.iTotalPages; + } + else if ( oPaging.iPage <= iHalf ) { + iStart = 1; + iEnd = iListLength; + } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { + iStart = oPaging.iTotalPages - iListLength + 1; + iEnd = oPaging.iTotalPages; + } else { + iStart = oPaging.iPage - iHalf + 1; + iEnd = iStart + iListLength - 1; + } + + for ( i=0, ien=an.length ; i'+j+'') + .insertBefore( $('li:last', an[i])[0] ) + .bind('click', function (e) { + e.preventDefault(); + oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; + fnDraw( oSettings ); + } ); + } + + // Add / remove disabled classes from the static elements + if ( oPaging.iPage === 0 ) { + $('li:first', an[i]).addClass('disabled'); + } else { + $('li:first', an[i]).removeClass('disabled'); + } + + if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { + $('li:last', an[i]).addClass('disabled'); + } else { + $('li:last', an[i]).removeClass('disabled'); + } + } + } + } +} ); + + +/* + * TableTools Bootstrap compatibility + * Required TableTools 2.1+ + */ +if ( $.fn.DataTable.TableTools ) { + // Set the classes that TableTools uses to something suitable for Bootstrap + $.extend( true, $.fn.DataTable.TableTools.classes, { + "container": "DTTT btn-group", + "buttons": { + "normal": "btn", + "disabled": "disabled" + }, + "collection": { + "container": "DTTT_dropdown dropdown-menu", + "buttons": { + "normal": "", + "disabled": "disabled" + } + }, + "print": { + "info": "DTTT_print_info modal" + }, + "select": { + "row": "active" + } + } ); + + // Have the collection use a bootstrap compatible dropdown + $.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { + "collection": { + "container": "ul", + "button": "li", + "liner": "a" + } + } ); +} diff --git a/openslides/static/javascript/jquery.dataTables.min.js b/openslides/static/javascript/jquery.dataTables.min.js new file mode 100644 index 000000000..02694a4a5 --- /dev/null +++ b/openslides/static/javascript/jquery.dataTables.min.js @@ -0,0 +1,155 @@ +/* + * File: jquery.dataTables.min.js + * Version: 1.9.4 + * Author: Allan Jardine (www.sprymedia.co.uk) + * Info: www.datatables.net + * + * Copyright 2008-2012 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + */ +(function(X,l,n){var L=function(h){var j=function(e){function o(a,b){var c=j.defaults.columns,d=a.aoColumns.length,c=h.extend({},j.models.oColumn,c,{sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,nTh:b?b:l.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.oDefaults:d});a.aoColumns.push(c);if(a.aoPreSearchCols[d]===n||null===a.aoPreSearchCols[d])a.aoPreSearchCols[d]=h.extend({},j.models.oSearch);else if(c=a.aoPreSearchCols[d], +c.bRegex===n&&(c.bRegex=!0),c.bSmart===n&&(c.bSmart=!0),c.bCaseInsensitive===n)c.bCaseInsensitive=!0;m(a,d,null)}function m(a,b,c){var d=a.aoColumns[b];c!==n&&null!==c&&(c.mDataProp&&!c.mData&&(c.mData=c.mDataProp),c.sType!==n&&(d.sType=c.sType,d._bAutoType=!1),h.extend(d,c),p(d,c,"sWidth","sWidthOrig"),c.iDataSort!==n&&(d.aDataSort=[c.iDataSort]),p(d,c,"aDataSort"));var i=d.mRender?Q(d.mRender):null,f=Q(d.mData);d.fnGetData=function(a,b){var c=f(a,b);return d.mRender&&b&&""!==b?i(c,b,a):c};d.fnSetData= +L(d.mData);a.oFeatures.bSort||(d.bSortable=!1);!d.bSortable||-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableNone,d.sSortingClassJUI=""):-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortable,d.sSortingClassJUI=a.oClasses.sSortJUI):-1!=h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableAsc,d.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed):-1== +h.inArray("asc",d.asSorting)&&-1!=h.inArray("desc",d.asSorting)&&(d.sSortingClass=a.oClasses.sSortableDesc,d.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed)}function k(a){if(!1===a.oFeatures.bAutoWidth)return!1;da(a);for(var b=0,c=a.aoColumns.length;bj[f])d(a.aoColumns.length+j[f],b[i]);else if("string"===typeof j[f]){e=0;for(w=a.aoColumns.length;eb&&a[d]--; -1!=c&&a.splice(c,1)}function S(a,b,c){var d=a.aoColumns[c];return d.fnRender({iDataRow:b,iDataColumn:c,oSettings:a,aData:a.aoData[b]._aData,mDataProp:d.mData},v(a,b,c,"display"))}function ea(a,b){var c=a.aoData[b],d;if(null===c.nTr){c.nTr=l.createElement("tr");c.nTr._DT_RowIndex=b;c._aData.DT_RowId&&(c.nTr.id=c._aData.DT_RowId);c._aData.DT_RowClass&& +(c.nTr.className=c._aData.DT_RowClass);for(var i=0,f=a.aoColumns.length;i=a.fnRecordsDisplay()?0:a.iInitDisplayStart,a.iInitDisplayStart=-1,y(a));if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++;else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!wa(a))return}else a.iDraw++;if(0!==a.aiDisplay.length){var g= +a._iDisplayStart;d=a._iDisplayEnd;a.oFeatures.bServerSide&&(g=0,d=a.aoData.length);for(;g")[0];a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=h('
')[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),i,f,g,e,w,o,k,m=0;m")[0];w=d[m+ +1];if("'"==w||'"'==w){o="";for(k=2;d[m+k]!=w;)o+=d[m+k],k++;"H"==o?o=a.oClasses.sJUIHeader:"F"==o&&(o=a.oClasses.sJUIFooter);-1!=o.indexOf(".")?(w=o.split("."),e.id=w[0].substr(1,w[0].length-1),e.className=w[1]):"#"==o.charAt(0)?e.id=o.substr(1,o.length-1):e.className=o;m+=k}c.appendChild(e);c=e}else if(">"==g)c=c.parentNode;else if("l"==g&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange)i=ya(a),f=1;else if("f"==g&&a.oFeatures.bFilter)i=za(a),f=1;else if("r"==g&&a.oFeatures.bProcessing)i=Aa(a),f= +1;else if("t"==g)i=Ba(a),f=1;else if("i"==g&&a.oFeatures.bInfo)i=Ca(a),f=1;else if("p"==g&&a.oFeatures.bPaginate)i=Da(a),f=1;else if(0!==j.ext.aoFeatures.length){e=j.ext.aoFeatures;k=0;for(w=e.length;k'):""===c?'':c+' ',d=l.createElement("div");d.className=a.oClasses.sFilter;d.innerHTML="";a.aanFeatures.f||(d.id=a.sTableId+"_filter");c=h('input[type="text"]',d);d._DT_Input=c[0];c.val(b.sSearch.replace('"',"""));c.bind("keyup.DT",function(){for(var c=a.aanFeatures.f,d=this.value===""?"":this.value, +g=0,e=c.length;g=b.length)a.aiDisplay.splice(0,a.aiDisplay.length),a.aiDisplay=a.aiDisplayMaster.slice();else if(a.aiDisplay.length==a.aiDisplayMaster.length||i.sSearch.length>b.length||1==c||0!==b.indexOf(i.sSearch)){a.aiDisplay.splice(0, +a.aiDisplay.length);la(a,1);for(b=0;b").html(c).text()); +return c.replace(/[\n\r]/g," ")}function ma(a,b,c,d){if(c)return a=b?a.split(" "):oa(a).split(" "),a="^(?=.*?"+a.join(")(?=.*?")+").*$",RegExp(a,d?"i":"");a=b?a:oa(a);return RegExp(a,d?"i":"")}function Ja(a,b){return"function"===typeof j.ext.ofnSearch[b]?j.ext.ofnSearch[b](a):null===a?"":"html"==b?a.replace(/[\r\n]/g," ").replace(/<.*?>/g,""):"string"===typeof a?a.replace(/[\r\n]/g," "):a}function oa(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"), +"\\$1")}function Ca(a){var b=l.createElement("div");b.className=a.oClasses.sInfo;a.aanFeatures.i||(a.aoDrawCallback.push({fn:Ka,sName:"information"}),b.id=a.sTableId+"_info");a.nTable.setAttribute("aria-describedby",a.sTableId+"_info");return b}function Ka(a){if(a.oFeatures.bInfo&&0!==a.aanFeatures.i.length){var b=a.oLanguage,c=a._iDisplayStart+1,d=a.fnDisplayEnd(),i=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),g;g=0===f?b.sInfoEmpty:b.sInfo;f!=i&&(g+=" "+b.sInfoFiltered);g+=b.sInfoPostFix;g=ja(a,g); +null!==b.fnInfoCallback&&(g=b.fnInfoCallback.call(a.oInstance,a,c,d,i,f,g));a=a.aanFeatures.i;b=0;for(c=a.length;b",c,d,i=a.aLengthMenu;if(2==i.length&&"object"===typeof i[0]&&"object"===typeof i[1]){c=0;for(d=i[0].length;c'+i[1][c]+""}else{c=0;for(d=i.length;c'+i[c]+""}b+="";i=l.createElement("div");a.aanFeatures.l|| +(i.id=a.sTableId+"_length");i.className=a.oClasses.sLength;i.innerHTML="";h('select option[value="'+a._iDisplayLength+'"]',i).attr("selected",!0);h("select",i).bind("change.DT",function(){var b=h(this).val(),i=a.aanFeatures.l;c=0;for(d=i.length;ca.aiDisplay.length||-1==a._iDisplayLength?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Da(a){if(a.oScroll.bInfinite)return null;var b=l.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;j.ext.oPagination[a.sPaginationType].fnInit(a, +b,function(a){y(a);x(a)});a.aanFeatures.p||a.aoDrawCallback.push({fn:function(a){j.ext.oPagination[a.sPaginationType].fnUpdate(a,function(a){y(a);x(a)})},sName:"pagination"});return b}function qa(a,b){var c=a._iDisplayStart;if("number"===typeof b)a._iDisplayStart=b*a._iDisplayLength,a._iDisplayStart>a.fnRecordsDisplay()&&(a._iDisplayStart=0);else if("first"==b)a._iDisplayStart=0;else if("previous"==b)a._iDisplayStart=0<=a._iDisplayLength?a._iDisplayStart-a._iDisplayLength:0,0>a._iDisplayStart&&(a._iDisplayStart= +0);else if("next"==b)0<=a._iDisplayLength?a._iDisplayStart+a._iDisplayLengthh(a.nTable).height()-a.oScroll.iLoadGap&&a.fnDisplayEnd()d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(h(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else""!==a.oScroll.sXInner?a.nTable.style.width= +q(a.oScroll.sXInner):i==h(d).width()&&h(d).height()i-a.oScroll.iBarWidth&&(a.nTable.style.width=q(i))):a.nTable.style.width=q(i);i=h(a.nTable).outerWidth();C(s,e);C(function(a){p.push(q(h(a).width()))},e);C(function(a,b){a.style.width=p[b]},g);h(e).height(0);null!==a.nTFoot&&(C(s,j),C(function(a){n.push(q(h(a).width()))},j),C(function(a,b){a.style.width=n[b]},o),h(j).height(0));C(function(a,b){a.innerHTML= +"";a.style.width=p[b]},e);null!==a.nTFoot&&C(function(a,b){a.innerHTML="";a.style.width=n[b]},j);if(h(a.nTable).outerWidth()d.offsetHeight||"scroll"==h(d).css("overflow-y")?i+a.oScroll.iBarWidth:i;if(r&&(d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(g-a.oScroll.iBarWidth);d.style.width=q(g);a.nScrollHead.style.width=q(g);null!==a.nTFoot&&(a.nScrollFoot.style.width=q(g));""===a.oScroll.sX?D(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width."): +""!==a.oScroll.sXInner&&D(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else d.style.width=q("100%"),a.nScrollHead.style.width=q("100%"),null!==a.nTFoot&&(a.nScrollFoot.style.width=q("100%"));""===a.oScroll.sY&&r&&(d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth));""!==a.oScroll.sY&&a.oScroll.bCollapse&&(d.style.height=q(a.oScroll.sY),r=""!==a.oScroll.sX&&a.nTable.offsetWidth> +d.offsetWidth?a.oScroll.iBarWidth:0,a.nTable.offsetHeightd.clientHeight||"scroll"==h(d).css("overflow-y");b.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px";null!==a.nTFoot&&(R.style.width=q(r),l.style.width=q(r),l.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px");h(d).scroll();if(a.bSorted||a.bFiltered)d.scrollTop=0}function C(a,b,c){for(var d= +0,i=0,f=b.length,g,e;itd",b));j=N(a,f);for(f=d=0;fc)return null;if(null===a.aoData[c].nTr){var d=l.createElement("td");d.innerHTML=v(a,c,b,"");return d}return J(a,c)[b]}function Pa(a,b){for(var c=-1,d=-1,i=0;i/g,"");e.length>c&&(c=e.length,d=i)}return d}function q(a){if(null===a)return"0px";if("number"==typeof a)return 0>a?"0px":a+"px";var b=a.charCodeAt(a.length-1); +return 48>b||57/g,""),i=q[c].nTh,i.removeAttribute("aria-sort"),i.removeAttribute("aria-label"),q[c].bSortable?0d&&d++;f=RegExp(f+"[123]");var o;b=0;for(c=a.length;b
')[0];l.body.appendChild(b);a.oBrowser.bScrollOversize= +100===h("#DT_BrowserTest",b)[0].offsetWidth?!0:!1;l.body.removeChild(b)}function Va(a){return function(){var b=[s(this[j.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return j.ext.oApi[a].apply(this,b)}}var U=/\[.*?\]$/,Wa=X.JSON?JSON.stringify:function(a){var b=typeof a;if("object"!==b||null===a)return"string"===b&&(a='"'+a+'"'),a+"";var c,d,e=[],f=h.isArray(a);for(c in a)d=a[c],b=typeof d,"string"===b?d='"'+d+'"':"object"===b&&null!==d&&(d=Wa(d)),e.push((f?"":'"'+c+'":')+d);return(f? +"[":"{")+e+(f?"]":"}")};this.$=function(a,b){var c,d,e=[],f;d=s(this[j.ext.iApiIndex]);var g=d.aoData,o=d.aiDisplay,k=d.aiDisplayMaster;b||(b={});b=h.extend({},{filter:"none",order:"current",page:"all"},b);if("current"==b.page){c=d._iDisplayStart;for(d=d.fnDisplayEnd();c=d.fnRecordsDisplay()&&(d._iDisplayStart-=d._iDisplayLength,0>d._iDisplayStart&&(d._iDisplayStart=0));if(c===n||c)y(d),x(d);return g};this.fnDestroy=function(a){var b=s(this[j.ext.iApiIndex]),c=b.nTableWrapper.parentNode,d=b.nTBody,i,f,a=a===n?!1:a;b.bDestroying=!0;A(b,"aoDestroyCallback","destroy",[b]);if(!a){i=0;for(f=b.aoColumns.length;itr>td."+b.oClasses.sRowEmpty,b.nTable).parent().remove();b.nTable!=b.nTHead.parentNode&&(h(b.nTable).children("thead").remove(),b.nTable.appendChild(b.nTHead));b.nTFoot&&b.nTable!=b.nTFoot.parentNode&&(h(b.nTable).children("tfoot").remove(),b.nTable.appendChild(b.nTFoot));b.nTable.parentNode.removeChild(b.nTable);h(b.nTableWrapper).remove();b.aaSorting=[];b.aaSortingFixed=[];P(b);h(T(b)).removeClass(b.asStripeClasses.join(" "));h("th, td",b.nTHead).removeClass([b.oClasses.sSortable,b.oClasses.sSortableAsc, +b.oClasses.sSortableDesc,b.oClasses.sSortableNone].join(" "));b.bJUI&&(h("th span."+b.oClasses.sSortIcon+", td span."+b.oClasses.sSortIcon,b.nTHead).remove(),h("th, td",b.nTHead).each(function(){var a=h("div."+b.oClasses.sSortJUIWrapper,this),c=a.contents();h(this).append(c);a.remove()}));!a&&b.nTableReinsertBefore?c.insertBefore(b.nTable,b.nTableReinsertBefore):a||c.appendChild(b.nTable);i=0;for(f=b.aoData.length;i=t(d);if(!m)for(e=a;et<"F"ip>')):h.extend(g.oClasses,j.ext.oStdClasses);h(this).addClass(g.oClasses.sTable);if(""!==g.oScroll.sX||""!==g.oScroll.sY)g.oScroll.iBarWidth=Qa();g.iInitDisplayStart===n&&(g.iInitDisplayStart=e.iDisplayStart, +g._iDisplayStart=e.iDisplayStart);e.bStateSave&&(g.oFeatures.bStateSave=!0,Sa(g,e),z(g,"aoDrawCallback",ra,"state_save"));null!==e.iDeferLoading&&(g.bDeferLoading=!0,a=h.isArray(e.iDeferLoading),g._iRecordsDisplay=a?e.iDeferLoading[0]:e.iDeferLoading,g._iRecordsTotal=a?e.iDeferLoading[1]:e.iDeferLoading);null!==e.aaData&&(f=!0);""!==e.oLanguage.sUrl?(g.oLanguage.sUrl=e.oLanguage.sUrl,h.getJSON(g.oLanguage.sUrl,null,function(a){pa(a);h.extend(true,g.oLanguage,e.oLanguage,a);ba(g)}),i=!0):h.extend(!0, +g.oLanguage,e.oLanguage);null===e.asStripeClasses&&(g.asStripeClasses=[g.oClasses.sStripeOdd,g.oClasses.sStripeEven]);b=g.asStripeClasses.length;g.asDestroyStripes=[];if(b){c=!1;d=h(this).children("tbody").children("tr:lt("+b+")");for(a=0;a=g.aoColumns.length&&(g.aaSorting[a][0]=0);var k=g.aoColumns[g.aaSorting[a][0]];g.aaSorting[a][2]===n&&(g.aaSorting[a][2]=0);e.aaSorting===n&&g.saved_aaSorting===n&&(g.aaSorting[a][1]= +k.asSorting[0]);c=0;for(d=k.asSorting.length;c=parseInt(n,10)};j.fnIsDataTable=function(e){for(var h=j.settings,m=0;me)return e;for(var h=e+"",e=h.split(""),j="",h=h.length,k=0;k'+k.sPrevious+''+k.sNext+"":'';h(j).append(k);var l=h("a",j), +k=l[0],l=l[1];e.oApi._fnBindAction(k,{action:"previous"},n);e.oApi._fnBindAction(l,{action:"next"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_previous",l.id=e.sTableId+"_next",k.setAttribute("aria-controls",e.sTableId),l.setAttribute("aria-controls",e.sTableId))},fnUpdate:function(e){if(e.aanFeatures.p)for(var h=e.oClasses,j=e.aanFeatures.p,k,l=0,n=j.length;l'+k.sFirst+''+k.sPrevious+''+k.sNext+''+k.sLast+"");var t=h("a",j),k=t[0],l=t[1],r=t[2],t=t[3];e.oApi._fnBindAction(k,{action:"first"},n);e.oApi._fnBindAction(l,{action:"previous"},n);e.oApi._fnBindAction(r,{action:"next"},n);e.oApi._fnBindAction(t,{action:"last"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",r.id=e.sTableId+"_next",t.id=e.sTableId+"_last")}, +fnUpdate:function(e,o){if(e.aanFeatures.p){var m=j.ext.oPagination.iFullNumbersShowPages,k=Math.floor(m/2),l=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),n=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,t="",r,B=e.oClasses,u,M=e.aanFeatures.p,L=function(h){e.oApi._fnBindAction(this,{page:h+r-1},function(h){e.oApi._fnPageChange(e,h.data.page);o(e);h.preventDefault()})};-1===e._iDisplayLength?n=k=r=1:l=l-k?(r=l-m+1,k=l):(r=n-Math.ceil(m/2)+1,k=r+m-1);for(m=r;m<=k;m++)t+= +n!==m?''+e.fnFormatNumber(m)+"":''+e.fnFormatNumber(m)+"";m=0;for(k=M.length;mh?1:0},"string-desc":function(e,h){return eh?-1:0},"html-pre":function(e){return e.replace(/<.*?>/g,"").toLowerCase()},"html-asc":function(e,h){return eh?1:0},"html-desc":function(e,h){return e< +h?1:e>h?-1:0},"date-pre":function(e){e=Date.parse(e);if(isNaN(e)||""===e)e=Date.parse("01/01/1970 00:00:00");return e},"date-asc":function(e,h){return e-h},"date-desc":function(e,h){return h-e},"numeric-pre":function(e){return"-"==e||""===e?0:1*e},"numeric-asc":function(e,h){return e-h},"numeric-desc":function(e,h){return h-e}});h.extend(j.ext.aTypes,[function(e){if("number"===typeof e)return"numeric";if("string"!==typeof e)return null;var h,j=!1;h=e.charAt(0);if(-1=="0123456789-".indexOf(h))return null; +for(var k=1;k")?"html":null}]);h.fn.DataTable=j;h.fn.dataTable=j;h.fn.dataTableSettings=j.settings;h.fn.dataTableExt=j.ext};"function"===typeof define&&define.amd?define(["jquery"],L):jQuery&&!jQuery.fn.dataTable&& +L(jQuery)})(window,document); diff --git a/openslides/static/styles/dataTables/dataTables.bootstrap.css b/openslides/static/styles/dataTables/dataTables.bootstrap.css new file mode 100644 index 000000000..b263c1511 --- /dev/null +++ b/openslides/static/styles/dataTables/dataTables.bootstrap.css @@ -0,0 +1,188 @@ +/*! + * Bootstrap style for DataTables jquery plugin + * + * Copyright 2012 + * DataTables is dual licensed under the GPL v2 license or a BSD (3-point) license. + * http://www.datatables.net + * http://www.datatables.net/blog/Twitter_Bootstrap_2 + * + */ + +div.dataTables_length label { + float: left; + text-align: left; +} + +div.dataTables_length select { + width: 75px; +} + +div.dataTables_filter label { + float: right; +} + +div.dataTables_info { + padding-top: 8px; +} + +div.dataTables_paginate { + float: right; + margin: 0; +} + +table.table { + clear: both; + margin-bottom: 6px !important; + max-width: none !important; +} + +table.table thead .sorting, +table.table thead .sorting_asc, +table.table thead .sorting_desc, +table.table thead .sorting_asc_disabled, +table.table thead .sorting_desc_disabled { + cursor: pointer; + *cursor: hand; +} + +table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; } +table.table thead .sorting_asc { background: url('images/sort_asc.png') no-repeat center right; } +table.table thead .sorting_desc { background: url('images/sort_desc.png') no-repeat center right; } + +table.table thead .sorting_asc_disabled { background: url('images/sort_asc_disabled.png') no-repeat center right; } +table.table thead .sorting_desc_disabled { background: url('images/sort_desc_disabled.png') no-repeat center right; } + +table.dataTable th:active { + outline: none; +} + +/* Scrolling */ +div.dataTables_scrollHead table { + margin-bottom: 0 !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.dataTables_scrollHead table thead tr:last-child th:first-child, +div.dataTables_scrollHead table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.dataTables_scrollBody table { + border-top: none; + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody tbody tr:first-child th, +div.dataTables_scrollBody tbody tr:first-child td { + border-top: none; +} + +div.dataTables_scrollFoot table { + border-top: none; +} + + + + +/* + * TableTools styles + */ +.table tbody tr.active td, +.table tbody tr.active th { + background-color: #08C; + color: white; +} + +.table tbody tr.active:hover td, +.table tbody tr.active:hover th { + background-color: #0075b0 !important; +} + +.table-striped tbody tr.active:nth-child(odd) td, +.table-striped tbody tr.active:nth-child(odd) th { + background-color: #017ebc; +} + +table.DTTT_selectable tbody tr { + cursor: pointer; + *cursor: hand; +} + +div.DTTT .btn { + color: #333 !important; + font-size: 12px; +} + +div.DTTT .btn:hover { + text-decoration: none !important; +} + + +ul.DTTT_dropdown.dropdown-menu a { + color: #333 !important; /* needed only when demo_page.css is included */ +} + +ul.DTTT_dropdown.dropdown-menu li:hover a { + background-color: #0088cc; + color: white !important; +} + +/* TableTools information display */ +div.DTTT_print_info.modal { + height: 150px; + margin-top: -75px; + text-align: center; +} + +div.DTTT_print_info h6 { + font-weight: normal; + font-size: 28px; + line-height: 28px; + margin: 1em; +} + +div.DTTT_print_info p { + font-size: 14px; + line-height: 20px; +} + + + +/* + * FixedColumns styles + */ +div.DTFC_LeftHeadWrapper table, +div.DTFC_LeftFootWrapper table, +table.DTFC_Cloned tr.even { + background-color: white; +} + +div.DTFC_LeftHeadWrapper table { + margin-bottom: 0 !important; + border-top-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, +div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_LeftBodyWrapper table { + border-top: none; + margin-bottom: 0 !important; +} + +div.DTFC_LeftBodyWrapper tbody tr:first-child th, +div.DTFC_LeftBodyWrapper tbody tr:first-child td { + border-top: none; +} + +div.DTFC_LeftFootWrapper table { + border-top: none; +} + diff --git a/openslides/static/styles/dataTables/images/sort_asc.png b/openslides/static/styles/dataTables/images/sort_asc.png new file mode 100644 index 0000000000000000000000000000000000000000..a88d7975fe9017e4e5f2289a94bd1ed66a5f59dc GIT binary patch literal 1118 zcmbVLO=#0l98awuV{uMt6P_}u4rcI3idKFP2SpU%ZJIE?RL`X zK?Oyo=*5GG2SxDYK=7akJqV&hrl{aWJa`y*5xh+1%i2y4V}gO?edPc9{r;a9vjc}) zn|Cxb4AYwFmvVG%_ui)U^y_4!ujsO!qzYuv8YUIR!Aw%KiWp=JrG#@>(I!s4#N7H->?w+cxsH2#GA};A>g8lyFDGPKh!5)vuP_{)}*83+N zJUBU!S0_i+E{*Lu1iGsNB``2iK-CyCU7?y_mv{xb_pUh>ESZqe1Y2{eAZLMSIT%EO zFrdOH1W^=3p>Qk~I{J+k#s5zQ@j{%aIA!l^GQjJ zqA1Uc2%!{8qBKfMNh#9DCnKS_*uZ8?mnf!+8@f8xtz#prVg=E`3bCBLWsNmDAX~PG z<(4fQh=UOzE2?gKXRkc9XeI3Er?HlHECVd%SI}3`hy1_du3@$R$r(qT;k@Sft63UX zv;)2Ea_iH>^6+4jPK-lGM{Zw37Tz>~~zlHzO61x51(V4jcaKrcIVDG$-d>)z}S|7f!xxYhfUE}Kj zug_h&HZN}go22$5Ym1}P8~vYNx7-~$TWFJ;_nh!wFYSAQJF{CCo=xpK8^7?iY1^!H haOA^1D_`VC7fU=jcT literal 0 HcmV?d00001 diff --git a/openslides/static/styles/dataTables/images/sort_asc_disabled.png b/openslides/static/styles/dataTables/images/sort_asc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..4e144cf0b1f786a9248a2998311e8109998d8a2d GIT binary patch literal 1050 zcmaJ=O-K|`93R^(@goeAz6hRU)S-61)?IaU({*Myml|8vh4x}JJM&z}b>>Yo&vx~s z*1>{0Sk$FMM28~iln?~vp$ zL+z1TilQ3g{c@7*P2Q^6L;g-8^nq-LaWstw(J;=d4x}PF%0Lh|)htXxRiC)>9(Gfd zk2X?ioL0_@8ZsHx!!QNYCTxo8?kU)+mV+2%VAin0^v_psXkh4J`eIPw6kCELM*pM( z2PX$o+GI}a)ajlxpt~Rv0TW^s6wEQp6$@dys4J4Qcg@nE2*J59y%|(mNdO5s5Cj>{ zuW=y`gm{Jzw6(Xlp9TWQb1WyYyx`~C#eg7k94LJ>@#g5mVp5Kkd=V>5k6>(zESt~g znS6jjPL}06J3BoMLGTlV-<`0qTJT$LYgs{tuI3mvHjo88MKy!QahN8NZl++`Te6m0 zDibLfTTpG5XE-mVGEhip#w2PB)JKi0I-PD8)*(7w)xTBHA4Yzu(Y*BKcijk8- zXslet#0bL39YHpb27^FRHN*1kB3@C%xaDHi(qLQ;(?o$W3|;IHBC-_Xc|nkPo{#bo zKE_Anh#c$_BEdk1ROZS^8#kea%Upe%D^%oqqhQ*^vkV>MD%4{RGC?tA(byrDwm?jZ z#$wTmdL^!2ITo%WmnFflUSt1hboq)*k9XV}TViYtKD5ZRJ7lb1H!ZJ`CviJ2M^c5A z%=*Sgk8hr8@-S*Kr`Ol~RJX(fddmmK4eR}O=#0l98WD1Hz^GK+C=e@fhgE~b#2$Ux^~T`1v5)mw1NlIe}zC z+ge9alrMQeN|SYi`>tC{zIG}!O_oO7k;UC8kBf>8sknx65F`zy2d1H-4fel=trX>@ z^-LCL<%6P%3`TJ=Ov$hao1$9VN|vJbLJV@SM>nJN{L>dS(6uOiBq(#Tm4F5Pz>p2Q zhq^NAP_G)%=(c^JwImV&17Zb~j6Ty5OHq1RS0sD)n5Dro1ouYi-$7;N6i6T&f*`~B zRW8JV5YO;|=5RQ?2M8R`v7Es2f}anI0YT(Au=3Evo2})=wA8uci&#;*fUzaAY_V8m ziU9`MJuDxIL|hF)@DqgJ88op{@|#XmML~j&YU>u(kqKNyC5HxZlqQk>PQkENWld+L zOr&6JNwHX-;oOueKw17j)G$`j4o<^A@%~fT$qZVMO+yC_*eYpUzR7iEi3uAj7}*(w z`YKgS6%a;F0a+l?9R#wX>ZWTi<7HV)nhsV>6(*%9O%xbi*F?TK!383rh#(|*p6}q} zd?z25;!?0(hzA2Li3(Rj>VN@FT;Xbexbdo7cN7eZc$T28pMYAYjSR4yvZz;&C0tc+ zg{xJMrKKvDCBd+6WB+P&<%mp=yImbyVyq56G|9BvWUP^I>ms=lb4e+lDSgg;Us`JO zKB6{wH+j~F#-A4FY3K3qm~Z6m@V6}oQ%8?p-E$dw`#0C$PJfmCV8)v}3>Ydha%`fZ zJk~G*M^A3LGk$Td;R`icF67R~`sBOHv)Hlqlc%$jy~9_oZJcNyWxkbb_O9u#|7hLF z-<-NMLzh3S0YA@8gd1Pt(Df|3@16Y-n=aSvsF@AkI`ioeFg>&H3bXU&vBnE6gIChkL+(Ey+0iB4Z$Eze7t_CX>Hq)$ literal 0 HcmV?d00001 diff --git a/openslides/static/styles/dataTables/images/sort_desc.png b/openslides/static/styles/dataTables/images/sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..def071ed5afd264a036f6d9e75856366fd6ad153 GIT binary patch literal 1127 zcmbVMOK8+U7*1U&zKRu5sR)h{1;yRWWV^4}ShvZpU2*HWU2!iy(qy)cZ89;Lb+`3m zMbruv!GjkO!3qksP*5)lD)k}=Dp*ht-n@8G5m8XoN!zU+ih_Y;=AZe$?|)|~*Ri8v z(dtDU$2DZy)jV65`|pB!_H}d7Cv0h=sUqzpC0fy3%q0!dg+a#Bx^W(BM*oq=xP{{a zC9_bZ#q2IgCss)FbwX9kVQ7wPX{|b%-is;d!ri7V^Y8E8=YeU+{JuyQW*r6hnC$~D z?i}bS=mWia!r)uCftISo2rNuBP__DOPpZoN6tBeg{;|M=DHYl)^V3chvpJv;7lTL$ z26Y&PAc{gL+#HL=wg3?#C_qs_Vi3iouqZ(YW*(kdbB&UeSJN}Lm?ZN(lsb|iR4SEF zB^)Adw}29fgwG+0L8cM(`faLJgSNN6#-L(PcTI+l@K3y+Xf(g*^61+0|J+O6zN2mb?UNGh6GU@A{1+eF%d@N2(^XdVmhis(y25|iAr;gV=io5OsYy0 zB}Gv|2&GUGrBPB%s*yG^841Ug8a88lRI_zlvuiTDGuXsmv6A9qjS{y&NMEf3ay^6+ zuZK85>5PD^rkl1e`{kLAR>iJ)6dP%mSYRr@k~xQcDE=$%X{_--ITM&Og5Ml}G)wJ> zb)dhUZG9%p4iC23#JFrUCcmwHz{cugMoku~ue-kg{Mj0~%`FeCcz9jAdg}QET-kSG za`+2B_+lRTaeAVz>E`F1pN7h>B=BbGqcz13d%ywZR&4OjkNNrF_U}#EcXDGa@V52B z>JnIW7#s%CHi literal 0 HcmV?d00001 diff --git a/openslides/static/styles/dataTables/images/sort_desc_disabled.png b/openslides/static/styles/dataTables/images/sort_desc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7824973cc60fc1841b16f2cb39323cefcdc3f942 GIT binary patch literal 1045 zcmaJ=&rj1(9IuWjVlWt@h#q(rlc~7%$2P_q>KN??ODrK{#&I!}_Kh{rzS=%m2N%F- zAW={L0VZBJnRrkSCK{q1NKA||(ZmA>6Hgw9o;Z-;>)3_|u*vIt-(X0AeGY5Bm`Mgoq{>2>Xkbiu%Ds= zw2?31f^tL9kQr8eOxQDR!ltPHq-U$zG{j&MP8pU+Z@qp?149?-TQP-IYzdZ(;duv+ z&5z`@`Drbo)5+_g-xG*{39$-1bH;K7Po%550y+EF3=OIfJT20DK^2ryARz~WSeOlI zY%dFXxiA-r#^dp8fM+?DVR?q*LtI>l@B+(%+D8*_j$RaUa;D~sSR!4**cKS3TrP*p zkuY+m7%q`W_!>MPB8ZS%v9RieEVsL^AVXJk3>zEB0=}X;iDt1#lSubcFztq{<<`nX z3dVS<&2VAXPpJ-6l>b9bvw?PT4(`W$ps<^-*pSIV7tJ~vX67YQ8ELa7v~ZoP?{i~^a{W;-ZQ@ymjxh)IjDt*2O<6Dwh=q$vY$VY; zc&J{Ds~-?cjVm3>Wk@iL-`IZ|UB4pJ;~yJiON_?gLyJtiL&kbxZhV_OiPfx}%6s1@ zcXoG^ffrPJ;LQ4(`t<(ickJ1j|E0&fC8lSh8sUh5lwUg=l~QoqsK t`nTanN|e2@a&yVMdhy