diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index 5684f59d..c4338ae3 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -201,6 +201,9 @@ class TestOssViewset(EndpointTester): def test_create_operation_result(self): self.populateData() + self.operation1.result = None + self.operation1.save() + data = { 'item_data': { 'alias': 'Test4', @@ -223,11 +226,14 @@ class TestOssViewset(EndpointTester): 'alias': 'Test4', 'title': 'Test title', 'comment': 'Comment', - 'operation_type': OperationType.INPUT + 'operation_type': OperationType.INPUT, + 'result': self.ks1.model.pk }, 'create_schema': True, 'positions': [], } + self.executeBadData(data=data, item=self.owned_id) + data['item_data']['result'] = None response = self.executeCreated(data=data, item=self.owned_id) self.owned.refresh_from_db() new_operation = response.data['new_operation'] @@ -341,9 +347,25 @@ class TestOssViewset(EndpointTester): 'target': self.operation1.pk, 'input': self.ks2.model.pk } - response = self.executeOK(data=data, item=self.owned_id) + self.executeBadData(data=data, item=self.owned_id) + + data = { + 'positions': [], + 'target': self.operation2.pk, + 'input': None + } + self.executeOK(data=data, item=self.owned_id) self.operation2.refresh_from_db() - self.assertEqual(self.operation2.result, self.ks2.model) + self.assertEqual(self.operation2.result, None) + + data = { + 'positions': [], + 'target': self.operation1.pk, + 'input': self.ks2.model.pk + } + self.executeOK(data=data, item=self.owned_id) + self.operation1.refresh_from_db() + self.assertEqual(self.operation1.result, self.ks2.model) @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_update_operation(self): diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index a536d9aa..1e214911 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -1,7 +1,8 @@ ''' Endpoints for OSS. ''' -from typing import cast +from typing import Optional, cast from django.db import transaction +from django.db.models import Q from django.http import HttpResponse from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import generics, serializers @@ -109,6 +110,21 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev with transaction.atomic(): oss.update_positions(serializer.validated_data['positions']) new_operation = oss.create_operation(**serializer.validated_data['item_data']) + schema = new_operation.result + if schema is not None: + connected_operations = \ + m.Operation.objects \ + .filter(Q(result=schema) & ~Q(pk=new_operation.pk)) \ + .only('operation_type', 'oss_id') + for operation in connected_operations: + if operation.operation_type != m.OperationType.INPUT: + raise serializers.ValidationError({ + 'item_data': msg.operationResultFromAnotherOSS() + }) + if operation.oss_id == new_operation.oss_id: + raise serializers.ValidationError({ + 'item_data': msg.operationInputAlreadyConnected() + }) if new_operation.operation_type == m.OperationType.INPUT and serializer.validated_data['create_schema']: oss.create_input(new_operation) if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data: @@ -220,11 +236,24 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) serializer.is_valid(raise_exception=True) - operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) + target_operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) + schema: Optional[LibraryItem] = serializer.validated_data['input'] + if schema is not None: + connected_operations = m.Operation.objects.filter(result=schema).only('operation_type', 'oss_id') + for operation in connected_operations: + if operation.operation_type != m.OperationType.INPUT: + raise serializers.ValidationError({ + 'input': msg.operationResultFromAnotherOSS() + }) + if operation != target_operation and operation.oss_id == target_operation.oss_id: + raise serializers.ValidationError({ + 'input': msg.operationInputAlreadyConnected() + }) + oss = m.OperationSchema(self.get_object()) with transaction.atomic(): oss.update_positions(serializer.validated_data['positions']) - oss.set_input(operation.pk, serializer.validated_data['input']) + oss.set_input(target_operation.pk, schema) return Response( status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(oss.model).data diff --git a/rsconcept/backend/shared/messages.py b/rsconcept/backend/shared/messages.py index 3ca92eaf..2c228051 100644 --- a/rsconcept/backend/shared/messages.py +++ b/rsconcept/backend/shared/messages.py @@ -30,6 +30,14 @@ def operationNotInput(title: str): return f'Операция не является Загрузкой: {title}' +def operationResultFromAnotherOSS(): + return 'Схема является результатом другой ОСС' + + +def operationInputAlreadyConnected(): + return 'Схема уже подключена к другой операции' + + def operationNotSynthesis(title: str): return f'Операция не является Синтезом: {title}' diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx index 1e0cc8d2..e80e3f04 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx @@ -132,8 +132,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { const positions = getPositions(); if (positions.length == 0) { target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); - } - if (inputs.length <= 1) { + } else if (inputs.length <= 1) { let inputsNodes = positions.filter(pos => controller.schema!.items.find( operation => operation.operation_type === OperationType.INPUT && operation.id === pos.id @@ -167,6 +166,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { target.y += PARAMETER.ossMinDistance; } } while (flagIntersect); + controller.promptCreateOperation({ x: target.x, y: target.y,