diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 16320176b..cd86171d5 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -16,7 +16,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CleanHtmlFormMixin from openslides.utils.person import PersonFormField, MultiplePersonFormField -from .models import Motion, Category +from .models import Motion, Category, Workflow class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm): @@ -134,6 +134,19 @@ class MotionIdentifierMixin(forms.ModelForm): fields = ('identifier',) +class MotionSetWorkflowMixin(forms.ModelForm): + """ + Mixin to let the user change the workflow of the motion. When he does + so, the motion's state is reset. + """ + + set_workflow = forms.ModelChoiceField( + queryset=Workflow.objects.all(), + required=False, + label=ugettext_lazy('Workflow'), + help_text=ugettext_lazy('Set a specific workflow to switch to it. If you do so, the state of the motion will be reset.')) + + class MotionImportForm(CssClassMixin, forms.Form): """ Form for motion import via csv file. diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 6074c513a..02821b6ef 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -421,13 +421,16 @@ class Motion(SlideMixin, models.Model): self.set_identifier() self.state = state - def reset_state(self): + def reset_state(self, workflow=None): """ Set the state to the default state. - If the motion is new, it chooses the default workflow from config. + If the motion is new and workflow is None, it chooses the default + workflow from config. """ - if self.state: + if workflow: + new_state = workflow.first_state + elif self.state: new_state = self.state.workflow.first_state else: new_state = (Workflow.objects.get(pk=config['motion_workflow']).first_state or diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index 6c5d30f07..f418c2ef2 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -187,10 +187,11 @@ def create_builtin_workflows(sender, **kwargs): versioning=True) state_2_9 = State.objects.create(name=ugettext_noop('needs review'), workflow=workflow_2, + action_word=ugettext_noop('Needs review'), versioning=True) state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'), workflow=workflow_2, - action_word=ugettext_noop('reject (not authorized)'), + 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) diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html index ac1546352..38a13e401 100644 --- a/openslides/motion/templates/motion/motion_detail.html +++ b/openslides/motion/templates/motion/motion_detail.html @@ -57,7 +57,7 @@
- {# TODO: show only for complex workflow #} + {# TODO: show only for workflow with versioning #} {% if motion.version.version_number < motion.last_version.version_number %} {% trans "This is not the newest version." %} @@ -289,7 +289,7 @@

{% trans "Manage motion" %}

{% for state in motion.state.next_states.all %} - {% trans state.name %} + {% trans state.get_action_word %} {% endfor %}

diff --git a/openslides/motion/views.py b/openslides/motion/views.py index a79f4cb40..2d368f9c9 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -38,7 +38,7 @@ from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, MotionDisableVersioningMixin, MotionCategoryMixin, - MotionIdentifierMixin, MotionImportForm) + MotionIdentifierMixin, MotionSetWorkflowMixin, MotionImportForm) from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf from .csv_import import import_motions @@ -91,8 +91,9 @@ class MotionMixin(object): def manipulate_object(self, form): """ - Save the version data into the motion object before it is saved in - the Database. + Saves the version data into the motion object before it is saved in + the Database. Does also set category, identifier and new workflow + if given. """ super(MotionMixin, self).manipulate_object(form) for attr in ['title', 'text', 'reason']: @@ -120,6 +121,10 @@ class MotionMixin(object): except KeyError: pass + workflow = form.cleaned_data.get('set_workflow', None) + if workflow: + self.object.reset_state(workflow) + def post_save(self, form): """ Save the submitter an the supporter so the motion. @@ -161,6 +166,8 @@ class MotionMixin(object): if self.object: if config['motion_allow_disable_versioning'] and self.object.state.versioning: form_classes.append(MotionDisableVersioningMixin) + if self.request.user.has_perm('motion.can_manage_motion'): + form_classes.append(MotionSetWorkflowMixin) return type('MotionForm', tuple(form_classes), {}) diff --git a/tests/motion/test_views.py b/tests/motion/test_views.py index c641de3b0..bb16683be 100644 --- a/tests/motion/test_views.py +++ b/tests/motion/test_views.py @@ -219,6 +219,19 @@ class TestMotionUpdateView(MotionViewTestCase): motion = Motion.objects.get(pk=self.motion1.pk) self.assertEqual(motion.versions.count(), 1) + def test_set_another_workflow(self): + self.assertEqual(self.motion1.state.workflow.pk, 1) + response = self.admin_client.post(self.url, {'title': 'oori4KiaghaeSeuzaim2', + 'text': 'eequei1Tee1aegeNgee0', + 'submitter': self.admin}) + self.assertEqual(Motion.objects.get(pk=self.motion1.pk).state.workflow.pk, 1) + response = self.admin_client.post(self.url, {'title': 'oori4KiaghaeSeuzaim2', + 'text': 'eequei1Tee1aegeNgee0', + 'submitter': self.admin, + 'set_workflow': 2}) + self.assertRedirects(response, '/motion/1/') + self.assertEqual(Motion.objects.get(pk=self.motion1.pk).state.workflow.pk, 2) + class TestMotionDeleteView(MotionViewTestCase): def test_get(self):