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/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/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..b7107317d 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 -*-
@@ -270,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/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/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/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 ac9a41854..7ff707a7a 100644
--- a/openslides/motion/models.py
+++ b/openslides/motion/models.py
@@ -18,7 +18,7 @@ from datetime import datetime
import difflib
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
@@ -67,6 +67,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."""
@@ -172,6 +178,33 @@ class Motion(SlideMixin, models.Model):
if link == 'delete':
return reverse('motion_delete', args=[str(self.id)])
+ def set_identifier(self):
+ 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 or not self.category.prefix:
+ prefix = ''
+ else:
+ prefix = self.category.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.
@@ -348,12 +381,25 @@ 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 not state.dont_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:
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."""
@@ -535,7 +581,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("Prefix"))
+ """Name of the category."""
+
+ prefix = models.CharField(blank=True, max_length=32, verbose_name=ugettext_lazy("Prefix"))
+ """Prefix of the category.
+
+ Used to build the identifier of a motion.
+ """
def __unicode__(self):
return self.name
@@ -702,6 +754,12 @@ class State(models.Model):
dont_set_new_version_active = models.BooleanField(default=False)
"""If true, new versions are not automaticly set active."""
+ 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."""
return self.name
@@ -734,7 +792,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):
@@ -751,5 +809,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..ff662ddf1 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,77 @@ 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,
+ dont_set_identifier=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/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html
index 3548beba8..d392cb9bf 100644
--- a/openslides/motion/templates/motion/motion_detail.html
+++ b/openslides/motion/templates/motion/motion_detail.html
@@ -8,11 +8,11 @@
{% block content %}
- {{ motion.title }}
+ {{ motion.title }} {{ motion.category }}
- {% if motion.number != None %}
- {% trans "Motion" %} {{ motion.number }},
+ {% if motion.identifier != None %}
+ {% trans "Motion" %} {{ motion.identifier }},
{% else %}
[{% trans "no number" %}],
{% endif %}
@@ -56,14 +56,12 @@
-
{% endblock %}
diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py
index 946aab95b..f230a8dd9 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 27df47795..77e946112 100644
--- a/openslides/motion/views.py
+++ b/openslides/motion/views.py
@@ -35,10 +35,15 @@ 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
+# 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'
@@ -98,6 +103,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)
@@ -120,8 +135,14 @@ 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)
@@ -262,7 +283,32 @@ class VersionDiffView(GetVersionMixin, DetailView):
version_diff = VersionDiffView.as_view()
-class SupportView(SingleObjectMixin, RedirectView):
+
+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.
If self.support is True, the view will append a request.user to the supporter list.
@@ -427,7 +473,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:
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/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)
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/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..d1a76cf6f 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
@@ -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 9c647f296..8e7ddcd4d 100644
--- a/tests/motion/test_models.py
+++ b/tests/motion/test_models.py
@@ -8,8 +8,7 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.test import TestCase
-
+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
@@ -76,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):
@@ -113,18 +112,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()
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):
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