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] 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()