Merge pull request #3785 from FinnStutzenstein/no-changeable-first-state
Do not allow changing a workflow's first state (closes #3778)
This commit is contained in:
commit
768c97e89c
@ -15,7 +15,7 @@ Motions:
|
|||||||
- New possibility to sort submitters [#3647].
|
- New possibility to sort submitters [#3647].
|
||||||
- New representation of amendments (paragraph based creation, new diff
|
- New representation of amendments (paragraph based creation, new diff
|
||||||
and list views for amendments) [#3637].
|
and list views for amendments) [#3637].
|
||||||
- New feature to customize workflows and states [#3772].
|
- New feature to customize workflows and states [#3772, #3785].
|
||||||
- New config options to show logos on the right side in PDF [#3768].
|
- New config options to show logos on the right side in PDF [#3768].
|
||||||
- New table of contents with page numbers and categories in PDF [#3766].
|
- New table of contents with page numbers and categories in PDF [#3766].
|
||||||
- New teporal field "modified final version" where the final version can
|
- New teporal field "modified final version" where the final version can
|
||||||
|
@ -101,12 +101,11 @@ class WorkflowSerializer(ModelSerializer):
|
|||||||
Serializer for motion.models.Workflow objects.
|
Serializer for motion.models.Workflow objects.
|
||||||
"""
|
"""
|
||||||
states = StateSerializer(many=True, read_only=True)
|
states = StateSerializer(many=True, read_only=True)
|
||||||
# The first_state is checked in the update() method
|
|
||||||
first_state = PrimaryKeyRelatedField(queryset=State.objects.all(), required=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Workflow
|
model = Workflow
|
||||||
fields = ('id', 'name', 'states', 'first_state',)
|
fields = ('id', 'name', 'states', 'first_state',)
|
||||||
|
read_only_fields = ('first_state',)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
@ -127,17 +126,6 @@ class WorkflowSerializer(ModelSerializer):
|
|||||||
workflow.save()
|
workflow.save()
|
||||||
return workflow
|
return workflow
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, workflow, validated_data):
|
|
||||||
"""
|
|
||||||
Check, if the first state is in the right workflow.
|
|
||||||
"""
|
|
||||||
first_state = validated_data.get('first_state')
|
|
||||||
if first_state is not None:
|
|
||||||
if workflow.pk != first_state.workflow.pk:
|
|
||||||
raise ValidationError({'detail': 'You cannot select a state which is not in the workflow as the first state.'})
|
|
||||||
return super().update(workflow, validated_data)
|
|
||||||
|
|
||||||
|
|
||||||
class MotionCommentsJSONSerializerField(Field):
|
class MotionCommentsJSONSerializerField(Field):
|
||||||
"""
|
"""
|
||||||
|
@ -54,7 +54,7 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
name: 'motions/workflow',
|
name: 'motions/workflow',
|
||||||
methods: {
|
methods: {
|
||||||
getFirstState: function () {
|
getFirstState: function () {
|
||||||
return DS.get('motions/state', this.first_state);
|
return DS.get('motions/state', this.first_state_id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -120,13 +120,6 @@ angular.module('OpenSlidesApp.motions.workflow', [])
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setFirstState = function (state) {
|
|
||||||
$scope.workflow.first_state = state.id;
|
|
||||||
Workflow.save($scope.workflow).then(null, function (error) {
|
|
||||||
$scope.alert = ErrorMessage.forAlert(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save expand state so the session
|
// Save expand state so the session
|
||||||
if ($sessionStorage.motionStateTableExpandState) {
|
if ($sessionStorage.motionStateTableExpandState) {
|
||||||
$scope.toggleExpandContent();
|
$scope.toggleExpandContent();
|
||||||
|
@ -24,22 +24,9 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3 ng-mouseover="firstStateHover=true" ng-mouseleave="firstStateHover=false">
|
<h3>
|
||||||
<translate>First state</translate>:
|
<translate>First state</translate>:
|
||||||
{{ workflow.getFirstState().name | translate }}
|
{{ workflow.getFirstState().name | translate }}
|
||||||
<span uib-dropdown>
|
|
||||||
<span id="firstStateDropdown" class="pointer" uib-dropdown-toggle>
|
|
||||||
<i class="fa fa-cog" ng-if="firstStateHover"></i>
|
|
||||||
</span>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="firstStateDropdown">
|
|
||||||
<li ng-repeat="state in workflow.states">
|
|
||||||
<a href ng-click="setFirstState(state)">
|
|
||||||
<i class="fa fa-check" ng-if="workflow.first_state === state.id"></i>
|
|
||||||
{{ state.name | translate }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,7 +54,7 @@
|
|||||||
<i class="fa fa-pencil fa-lg"></i></a>
|
<i class="fa fa-pencil fa-lg"></i></a>
|
||||||
|
|
||||||
<!--delete-->
|
<!--delete-->
|
||||||
<a href="" class="text-danger" ng-if="state.id !== workflow.first_state"
|
<a href="" class="text-danger" ng-if="state.id !== workflow.first_state_id"
|
||||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||||
<b>{{ state.name | translate }}</b>"
|
<b>{{ state.name | translate }}</b>"
|
||||||
ng-bootbox-confirm-action="delete(state)">
|
ng-bootbox-confirm-action="delete(state)">
|
||||||
|
@ -919,13 +919,6 @@ class WorkflowViewSet(ModelViewSet, ProtectedErrorMessageMixin):
|
|||||||
result = False
|
result = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def create(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
response = super().create(*args, **kwargs)
|
|
||||||
except WorkflowError as e:
|
|
||||||
raise ValidationError({'detail': e.args[0]})
|
|
||||||
return response
|
|
||||||
|
|
||||||
def destroy(self, *args, **kwargs):
|
def destroy(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Customized view endpoint to delete a motion poll.
|
Customized view endpoint to delete a motion poll.
|
||||||
|
@ -1258,21 +1258,6 @@ class CreateWorkflow(TestCase):
|
|||||||
first_state = workflow.first_state
|
first_state = workflow.first_state
|
||||||
self.assertEqual(type(first_state), State)
|
self.assertEqual(type(first_state), State)
|
||||||
|
|
||||||
def test_creation_with_wrong_first_state(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('workflow-list'),
|
|
||||||
{'name': 'test_name_OoCoo3MeiT9li5Iengu9',
|
|
||||||
'first_state': 1})
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
def test_creation_with_not_existing_first_state(self):
|
|
||||||
Workflow.objects.all().delete()
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('workflow-list'),
|
|
||||||
{'name': 'test_name_OoCoo3MeiT9li5Iengu9',
|
|
||||||
'first_state': 49})
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateWorkflow(TestCase):
|
class UpdateWorkflow(TestCase):
|
||||||
"""
|
"""
|
||||||
@ -1292,38 +1277,6 @@ class UpdateWorkflow(TestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(workflow.name, 'test_name_wofi38DiWLT"8d3lwfo3')
|
self.assertEqual(workflow.name, 'test_name_wofi38DiWLT"8d3lwfo3')
|
||||||
|
|
||||||
def test_change_first_state_correct(self):
|
|
||||||
first_state = self.workflow.first_state
|
|
||||||
other_workflow_state = self.workflow.states.exclude(pk=first_state.pk).first()
|
|
||||||
response = self.client.patch(
|
|
||||||
reverse('workflow-detail', args=[self.workflow.pk]),
|
|
||||||
{'first_state': other_workflow_state.pk})
|
|
||||||
|
|
||||||
workflow = Workflow.objects.get(pk=self.workflow.id)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(workflow.first_state, other_workflow_state)
|
|
||||||
|
|
||||||
def test_change_first_state_not_existing(self):
|
|
||||||
first_state = self.workflow.first_state
|
|
||||||
response = self.client.patch(
|
|
||||||
reverse('workflow-detail', args=[self.workflow.pk]),
|
|
||||||
{'first_state': 42})
|
|
||||||
|
|
||||||
workflow = Workflow.objects.get(pk=self.workflow.id)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertEqual(workflow.first_state, first_state)
|
|
||||||
|
|
||||||
def test_change_first_state_wrong_workflow(self):
|
|
||||||
first_state = self.workflow.first_state
|
|
||||||
other_workflow = Workflow.objects.exclude(pk=self.workflow.pk).first()
|
|
||||||
response = self.client.patch(
|
|
||||||
reverse('workflow-detail', args=[self.workflow.pk]),
|
|
||||||
{'first_state': other_workflow.first_state.pk})
|
|
||||||
|
|
||||||
workflow = Workflow.objects.get(pk=self.workflow.id)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertEqual(workflow.first_state, first_state)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteWorkflow(TestCase):
|
class DeleteWorkflow(TestCase):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user