diff --git a/openslides/motion/models.py b/openslides/motion/models.py index e8e55cc56..517ac238d 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -395,6 +395,12 @@ class Motion(SlideMixin, models.Model): else: raise WorkflowError('You can not unsupport a motion in state %s.' % self.state.name) + def clear_supporters(self): + """ + Deletes all supporters of this motion. + """ + MotionSupporter.objects.filter(motion=self).delete() + def create_poll(self): """ Create a new poll for this motion. @@ -469,7 +475,7 @@ class Motion(SlideMixin, models.Model): The dictonary contains the following actions. - * edit + * update / edit * delete * create_poll * support @@ -478,9 +484,11 @@ class Motion(SlideMixin, models.Model): * reset_state """ actions = { - 'edit': ((self.is_submitter(person) and - self.state.allow_submitter_edit) or - person.has_perm('motion.can_manage_motion')), + 'update': ((self.is_submitter(person) and + self.state.allow_submitter_edit) or + person.has_perm('motion.can_manage_motion')), + + 'delete': person.has_perm('motion.can_manage_motion'), 'create_poll': (person.has_perm('motion.can_manage_motion') and self.state.allow_create_poll), @@ -491,14 +499,14 @@ class Motion(SlideMixin, models.Model): not self.is_supporter(person)), 'unsupport': (self.state.allow_support and - not self.is_submitter(person) and self.is_supporter(person)), 'change_state': person.has_perm('motion.can_manage_motion'), - } - actions['delete'] = actions['edit'] # TODO: Only if the motion has no number - actions['reset_state'] = actions['change_state'] + 'reset_state': person.has_perm('motion.can_manage_motion')} + + actions['edit'] = actions['update'] + return actions def write_log(self, message_list, person=None): diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index f418c2ef2..46391a8e8 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -42,6 +42,12 @@ def setup_motion_config_page(sender, **kwargs): min_value=0, max_value=8, help_text=ugettext_lazy('Choose 0 to disable the supporting system'))) + motion_remove_supporters = ConfigVariable( + name='motion_remove_supporters', + default_value=False, + form_field=forms.BooleanField( + label=ugettext_lazy('Supporters of a motion will be removed if a submitter edits his motion in early state.'), + required=False)) motion_preamble = ConfigVariable( name='motion_preamble', default_value=_('The assembly may decide,'), @@ -114,6 +120,7 @@ def setup_motion_config_page(sender, **kwargs): weight=30, variables=(motion_stop_submitting, motion_min_supporters, + motion_remove_supporters, motion_preamble, motion_pdf_ballot_papers_selection, motion_pdf_ballot_papers_number, diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 1fda770e7..f76ab2945 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -209,7 +209,7 @@ class MotionUpdateView(MotionMixin, UpdateView): def has_permission(self, request, *args, **kwargs): """Check, if the request.user has the permission to edit the motion.""" - return self.get_object().get_allowed_actions(request.user)['edit'] + return self.get_object().get_allowed_actions(request.user)['update'] def form_valid(self, form): """Write a log message, if the form is valid.""" @@ -217,6 +217,18 @@ class MotionUpdateView(MotionMixin, UpdateView): self.object.write_log([ugettext_noop('Motion updated')], self.request.user) return value + def manipulate_object(self, *args, **kwargs): + """ + Removes the supporters if config option is True and supporting is still + available in the state. + """ + return_value = super(MotionUpdateView, self).manipulate_object(*args, **kwargs) + if (config['motion_remove_supporters'] and self.object.state.allow_support and + not self.request.user.has_perm('motion.can_manage_motion')): + self.object.clear_supporters() + self.object.write_log([ugettext_noop('All supporters removed')], self.request.user) + return return_value + motion_edit = MotionUpdateView.as_view() diff --git a/tests/motion/test_views.py b/tests/motion/test_views.py index 06a31106a..d77dd1775 100644 --- a/tests/motion/test_views.py +++ b/tests/motion/test_views.py @@ -232,6 +232,50 @@ class TestMotionUpdateView(MotionViewTestCase): self.assertRedirects(response, '/motion/1/') self.assertEqual(Motion.objects.get(pk=self.motion1.pk).state.workflow.pk, 2) + def test_remove_supporters(self): + # Setup a new motion with one supporter + config['motion_min_supporters'] = 1 + motion = Motion.objects.create(title='cuoPhoX4Baifoxoothi3', text='zee7xei3taediR9loote') + response = self.staff_client.get('/motion/%s/' % motion.id) + self.assertNotContains(response, 'aengeing3quair3fieGi') + motion.support(self.registered) + self.registered.last_name = 'aengeing3quair3fieGi' + self.registered.save() + response = self.staff_client.get('/motion/%s/' % motion.id) + self.assertContains(response, 'aengeing3quair3fieGi') + + # Check editing by submitter + response = self.delegate_client.post( + '/motion/%s/edit/' % motion.id, + {'title': 'oori4KiaghaeSeuzaim2', + 'text': 'eequei1Tee1aegeNgee0', + 'submitter': self.delegate.person_id}) + self.assertEqual(response.status_code, 403) + motion.add_submitter(self.delegate) + + # Edit three times, without removal of supporters, with removal and in another state + for i in range(3): + if i == 1: + config['motion_remove_supporters'] = True + response = self.delegate_client.post( + '/motion/%s/edit/' % motion.id, + {'title': 'iezae8reevaT6phiesoa', + 'text': 'Lohjuu1aebewiu2or3oh'}) + self.assertRedirects(response, '/motion/%s/' % motion.id) + if i == 0 or i == 2: + self.assertTrue(self.registered in Motion.objects.get(pk=motion.pk).supporters) + else: + self.assertFalse(self.registered in Motion.objects.get(pk=motion.pk).supporters) + # Preparing the comming (third) run + motion = Motion.objects.get(pk=motion.pk) + motion.support(self.registered) + motion.state = State.objects.create( + name='not_support', + workflow=self.motion1.state.workflow, + allow_submitter_edit=True, + allow_support=False) + motion.save() + class TestMotionDeleteView(MotionViewTestCase): def test_get(self): @@ -247,7 +291,7 @@ class TestMotionDeleteView(MotionViewTestCase): self.assertEqual(response.status_code, 403) motion = Motion.objects.get(pk=2).add_submitter(self.delegate) response = self.delegate_client.post('/motion/2/del/', {}) - self.assertRedirects(response, '/motion/') + self.assertEqual(response.status_code, 403) class TestVersionPermitView(MotionViewTestCase):