diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 9c3a266a..97b431f6 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -328,6 +328,10 @@ class CreateSynthesisSerializer(StrictSerializer): }) schemas = [arg.result_id for arg in attrs['arguments'] if arg.result is not None] + if len(schemas) != len(set(schemas)): + raise serializers.ValidationError({ + 'arguments': msg.duplicateSchemasInArguments() + }) substitutions = attrs['substitutions'] to_delete = {x['original'].pk for x in substitutions} deleted = set() @@ -375,6 +379,7 @@ class UpdateOperationSerializer(StrictSerializer): required=False ) + # pylint: disable=too-many-branches def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) parent = attrs['item_data'].get('parent') @@ -405,6 +410,10 @@ class UpdateOperationSerializer(StrictSerializer): if 'substitutions' not in attrs: return attrs schemas = [arg.result_id for arg in attrs['arguments'] if arg.result is not None] + if len(schemas) != len(set(schemas)): + raise serializers.ValidationError({ + 'arguments': msg.duplicateSchemasInArguments() + }) substitutions = attrs['substitutions'] to_delete = {x['original'].pk for x in substitutions} deleted = set() diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py index 1f9406d1..b146f209 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py @@ -270,6 +270,37 @@ class TestOssOperations(EndpointTester): self.assertNotEqual(new_operation['result'], None) + @decl_endpoint('/api/oss/{item}/create-synthesis', method='post') + def test_create_synthesis_replicas(self): + self.populateData() + operation4 = self.owned.create_replica(self.operation1) + operation5 = self.owned.create_replica(self.operation1) + data = { + 'item_data': { + 'alias': 'Test5', + 'title': 'Test title', + 'description': '', + 'parent': None + }, + 'layout': self.layout_data, + 'position': { + 'x': 1, + 'y': 1, + 'width': 500, + 'height': 50 + }, + 'arguments': [self.operation1.pk, operation4.pk], + 'substitutions': [] + } + self.executeBadData(data=data, item=self.owned_id) + + data['arguments'] = [operation4.pk, operation5.pk] + self.executeBadData(data=data, item=self.owned_id) + + data['arguments'] = [operation4.pk, self.operation3.pk] + self.executeCreated(data=data, item=self.owned_id) + + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation(self): self.populateData() diff --git a/rsconcept/backend/shared/messages.py b/rsconcept/backend/shared/messages.py index d8e66b95..c8cd72a9 100644 --- a/rsconcept/backend/shared/messages.py +++ b/rsconcept/backend/shared/messages.py @@ -22,6 +22,10 @@ def operationNotInOSS(): return 'Операция не принадлежит ОСС' +def duplicateSchemasInArguments(): + return 'Аргументы не должны содержать повторяющиеся КС' + + def blockNotInOSS(): return 'Блок не принадлежит ОСС' diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-synthesis/tab-arguments.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-synthesis/tab-arguments.tsx index 228247d2..7ebe544c 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-synthesis/tab-arguments.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-synthesis/tab-arguments.tsx @@ -20,8 +20,11 @@ export function TabArguments() { } = useFormContext(); const inputs = useWatch({ control, name: 'arguments' }); - const references = manager.oss.replicas.filter(item => inputs.includes(item.original)).map(item => item.replica); - const filtered = manager.oss.operations.filter(item => !references.includes(item.id)); + const replicas = manager.oss.replicas + .filter(item => inputs.includes(item.original)) + .map(item => item.replica) + .concat(manager.oss.replicas.filter(item => inputs.includes(item.replica)).map(item => item.original)); + const filtered = manager.oss.operations.filter(item => !replicas.includes(item.id)); return (
diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx index 7936df75..3155c02a 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx @@ -15,10 +15,11 @@ export function TabArguments() { const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps); const args = useWatch({ control, name: 'arguments' }); - const references = manager.oss.replicas + const replicas = manager.oss.replicas .filter(item => args.includes(item.original) || item.original === target.id) - .map(item => item.replica); - const potentialCycle = [target.id, ...references, ...manager.oss.graph.expandAllOutputs([target.id])]; + .map(item => item.replica) + .concat(manager.oss.replicas.filter(item => args.includes(item.replica)).map(item => item.original)); + const potentialCycle = [target.id, ...replicas, ...manager.oss.graph.expandAllOutputs([target.id])]; const filtered = manager.oss.operations.filter(item => !potentialCycle.includes(item.id)); function handleChangeArguments(prev: number[], newValue: number[]) {