From 09071a6e8f92210eaf02888d4c82077b046606b9 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:18:27 +0300 Subject: [PATCH] F: Implement delete-block and update-block backend --- .../apps/oss/models/OperationSchema.py | 44 +++-- .../apps/oss/models/PropagationFacade.py | 2 +- .../backend/apps/oss/serializers/__init__.py | 10 +- .../apps/oss/serializers/data_access.py | 99 +++++++--- .../apps/oss/tests/s_views/t_blocks.py | 79 +++++++- .../apps/oss/tests/s_views/t_operations.py | 8 +- rsconcept/backend/apps/oss/views/oss.py | 178 ++++++++++++------ .../backend/apps/rsform/models/RSForm.py | 2 +- .../backend/apps/rsform/views/rsforms.py | 14 +- rsconcept/backend/shared/messages.py | 16 +- 10 files changed, 336 insertions(+), 116 deletions(-) diff --git a/rsconcept/backend/apps/oss/models/OperationSchema.py b/rsconcept/backend/apps/oss/models/OperationSchema.py index 7ae8066a..79b1a7fc 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchema.py +++ b/rsconcept/backend/apps/oss/models/OperationSchema.py @@ -92,26 +92,26 @@ class OperationSchema: ) def update_layout(self, data: dict) -> None: - ''' Update positions. ''' + ''' Update graphical layout. ''' layout = self.layout() layout.data = data layout.save() def create_operation(self, **kwargs) -> Operation: - ''' Insert new operation. ''' + ''' Create Operation. ''' result = Operation.objects.create(oss=self.model, **kwargs) self.cache.insert_operation(result) self.save(update_fields=['time_update']) return result def create_block(self, **kwargs) -> Block: - ''' Insert new block. ''' + ''' Create Block. ''' result = Block.objects.create(oss=self.model, **kwargs) self.save(update_fields=['time_update']) return result def delete_operation(self, target: int, keep_constituents: bool = False): - ''' Delete operation. ''' + ''' Delete Operation. ''' self.cache.ensure_loaded() operation = self.cache.operation_by_id[target] schema = self.cache.get_schema(operation) @@ -139,6 +139,20 @@ class OperationSchema: operation.delete() self.save(update_fields=['time_update']) + def delete_block(self, target: Block): + ''' Delete Block. ''' + new_parent = target.parent + if new_parent is not None: + for block in Block.objects.filter(parent=target): + if block != new_parent: + block.parent = new_parent + block.save(update_fields=['parent']) + for operation in Operation.objects.filter(parent=target): + operation.parent = new_parent + operation.save(update_fields=['parent']) + target.delete() + self.save(update_fields=['time_update']) + def set_input(self, target: int, schema: Optional[LibraryItem]) -> None: ''' Set input schema for operation. ''' operation = self.cache.operation_by_id[target] @@ -165,7 +179,7 @@ class OperationSchema: self.save(update_fields=['time_update']) def set_arguments(self, target: int, arguments: list[Operation]) -> None: - ''' Set arguments to operation. ''' + ''' Set arguments of target Operation. ''' self.cache.ensure_loaded() operation = self.cache.operation_by_id[target] processed: list[Operation] = [] @@ -198,7 +212,7 @@ class OperationSchema: self.save(update_fields=['time_update']) def set_substitutions(self, target: int, substitutes: list[dict]) -> None: - ''' Clear all arguments for operation. ''' + ''' Clear all arguments for target Operation. ''' self.cache.ensure_loaded() operation = self.cache.operation_by_id[target] schema = self.cache.get_schema(operation) @@ -237,7 +251,7 @@ class OperationSchema: self.save(update_fields=['time_update']) def create_input(self, operation: Operation) -> RSForm: - ''' Create input RSForm. ''' + ''' Create input RSForm for given Operation. ''' schema = RSForm.create( owner=self.model.owner, alias=operation.alias, @@ -254,7 +268,7 @@ class OperationSchema: return schema def execute_operation(self, operation: Operation) -> bool: - ''' Execute target operation. ''' + ''' Execute target Operation. ''' schemas = [ arg.argument.result for arg in operation.getQ_arguments().order_by('order') @@ -301,7 +315,7 @@ class OperationSchema: return True def relocate_down(self, source: RSForm, destination: RSForm, items: list[Constituenta]): - ''' Move list of constituents to specific schema inheritor. ''' + ''' Move list of Constituents to destination Schema inheritor. ''' self.cache.ensure_loaded() self.cache.insert_schema(source) self.cache.insert_schema(destination) @@ -315,7 +329,7 @@ class OperationSchema: Inheritance.objects.filter(operation_id=operation.pk, parent__in=items).delete() def relocate_up(self, source: RSForm, destination: RSForm, items: list[Constituenta]) -> list[Constituenta]: - ''' Move list of constituents to specific schema upstream. ''' + ''' Move list of Constituents upstream to destination Schema. ''' self.cache.ensure_loaded() self.cache.insert_schema(source) self.cache.insert_schema(destination) @@ -345,7 +359,7 @@ class OperationSchema: cst_list: list[Constituenta], exclude: Optional[list[int]] = None ) -> None: - ''' Trigger cascade resolutions when new constituent is created. ''' + ''' Trigger cascade resolutions when new Constituenta is created. ''' self.cache.insert_schema(source) inserted_aliases = [cst.alias for cst in cst_list] depend_aliases: set[str] = set() @@ -361,13 +375,13 @@ class OperationSchema: self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude) def after_change_cst_type(self, source: RSForm, target: Constituenta) -> None: - ''' Trigger cascade resolutions when constituenta type is changed. ''' + ''' Trigger cascade resolutions when Constituenta type is changed. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type)) def after_update_cst(self, source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None: - ''' Trigger cascade resolutions when constituenta data is changed. ''' + ''' Trigger cascade resolutions when Constituenta data is changed. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) depend_aliases = self._extract_data_references(data, old_data) @@ -385,13 +399,13 @@ class OperationSchema: ) def before_delete_cst(self, source: RSForm, target: list[Constituenta]) -> None: - ''' Trigger cascade resolutions before constituents are deleted. ''' + ''' Trigger cascade resolutions before Constituents are deleted. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) self._cascade_delete_inherited(operation.pk, target) def before_substitute(self, source: RSForm, substitutions: CstSubstitution) -> None: - ''' Trigger cascade resolutions before constituents are substituted. ''' + ''' Trigger cascade resolutions before Constituents are substituted. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) self._cascade_before_substitute(substitutions, operation) diff --git a/rsconcept/backend/apps/oss/models/PropagationFacade.py b/rsconcept/backend/apps/oss/models/PropagationFacade.py index 635e88c5..b2bf4412 100644 --- a/rsconcept/backend/apps/oss/models/PropagationFacade.py +++ b/rsconcept/backend/apps/oss/models/PropagationFacade.py @@ -17,7 +17,7 @@ class PropagationFacade: @staticmethod def after_create_cst(source: RSForm, new_cst: list[Constituenta], exclude: Optional[list[int]] = None) -> None: - ''' Trigger cascade resolutions when new constituent is created. ''' + ''' Trigger cascade resolutions when new constituenta is created. ''' hosts = _get_oss_hosts(source.model) for host in hosts: if exclude is None or host.pk not in exclude: diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py index 54d82695..c0a07435 100644 --- a/rsconcept/backend/apps/oss/serializers/__init__.py +++ b/rsconcept/backend/apps/oss/serializers/__init__.py @@ -3,16 +3,18 @@ from .basics import LayoutSerializer, SubstitutionExSerializer from .data_access import ( ArgumentSerializer, - BlockCreateSerializer, BlockSerializer, - OperationCreateSerializer, + CreateBlockSerializer, + CreateOperationSerializer, + DeleteBlockSerializer, OperationDeleteSerializer, OperationSchemaSerializer, OperationSerializer, OperationTargetSerializer, - OperationUpdateSerializer, RelocateConstituentsSerializer, - SetOperationInputSerializer + SetOperationInputSerializer, + UpdateBlockSerializer, + UpdateOperationSerializer ) from .responses import ( ConstituentaReferenceResponse, diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index a39cc0e3..05e37c86 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -41,7 +41,7 @@ class ArgumentSerializer(serializers.ModelSerializer): fields = ('operation', 'argument') -class BlockCreateSerializer(serializers.Serializer): +class CreateBlockSerializer(serializers.Serializer): ''' Serializer: Block creation. ''' class BlockCreateData(serializers.ModelSerializer): ''' Serializer: Block creation data. ''' @@ -52,7 +52,6 @@ class BlockCreateSerializer(serializers.Serializer): fields = 'title', 'description', 'parent' layout = LayoutSerializer() - item_data = BlockCreateData() width = serializers.FloatField() height = serializers.FloatField() @@ -84,9 +83,9 @@ class BlockCreateSerializer(serializers.Serializer): return attrs -class OperationCreateSerializer(serializers.Serializer): +class CreateOperationSerializer(serializers.Serializer): ''' Serializer: Operation creation. ''' - class OperationCreateData(serializers.ModelSerializer): + class CreateOperationData(serializers.ModelSerializer): ''' Serializer: Operation creation data. ''' alias = serializers.CharField() operation_type = serializers.ChoiceField(OperationType.choices) @@ -99,8 +98,7 @@ class OperationCreateSerializer(serializers.Serializer): 'description', 'result', 'parent' layout = LayoutSerializer() - - item_data = OperationCreateData() + item_data = CreateOperationData() position_x = serializers.FloatField() position_y = serializers.FloatField() create_schema = serializers.BooleanField(default=False, required=False) @@ -120,23 +118,23 @@ class OperationCreateSerializer(serializers.Serializer): for operation in attrs['arguments']: if operation.oss_id != oss.pk: raise serializers.ValidationError({ - 'arguments': msg.operationNotInOSS(oss.title) + 'arguments': msg.operationNotInOSS() }) return attrs -class OperationUpdateSerializer(serializers.Serializer): +class UpdateOperationSerializer(serializers.Serializer): ''' Serializer: Operation update. ''' - class OperationUpdateData(serializers.ModelSerializer): + class UpdateOperationData(serializers.ModelSerializer): ''' Serializer: Operation update data. ''' class Meta: ''' serializer metadata. ''' model = Operation - fields = 'alias', 'title', 'description' + fields = 'alias', 'title', 'description', 'parent' - layout = LayoutSerializer() + layout = LayoutSerializer(required=False) target = PKField(many=False, queryset=Operation.objects.all()) - item_data = OperationUpdateData() + item_data = UpdateOperationData() arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False) substitutions = serializers.ListField( child=SubstitutionSerializerBase(), @@ -145,6 +143,12 @@ class OperationUpdateSerializer(serializers.Serializer): def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) + target = cast(Block, attrs['target']) + if target.oss_id != oss.pk: + raise serializers.ValidationError({ + 'target': msg.operationNotInOSS() + }) + if 'parent' in attrs['item_data'] and attrs['item_data']['parent'].oss_id != oss.pk: raise serializers.ValidationError({ 'parent': msg.parentNotInOSS() @@ -160,7 +164,7 @@ class OperationUpdateSerializer(serializers.Serializer): for operation in attrs['arguments']: if operation.oss_id != oss.pk: raise serializers.ValidationError({ - 'arguments': msg.operationNotInOSS(oss.title) + 'arguments': msg.operationNotInOSS() }) if 'substitutions' not in attrs: @@ -192,17 +196,51 @@ class OperationUpdateSerializer(serializers.Serializer): return attrs -class OperationTargetSerializer(serializers.Serializer): - ''' Serializer: Target single operation. ''' - layout = LayoutSerializer() - target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) +class UpdateBlockSerializer(serializers.Serializer): + ''' Serializer: Block update. ''' + class UpdateBlockData(serializers.ModelSerializer): + ''' Serializer: Block update data. ''' + class Meta: + ''' serializer metadata. ''' + model = Block + fields = 'title', 'description', 'parent' + + layout = LayoutSerializer(required=False) + target = PKField(many=False, queryset=Block.objects.all()) + item_data = UpdateBlockData() def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) - operation = cast(Operation, attrs['target']) - if oss and operation.oss_id != oss.pk: + block = cast(Block, attrs['target']) + if block.oss_id != oss.pk: raise serializers.ValidationError({ - 'target': msg.operationNotInOSS(oss.title) + 'target': msg.blockNotInOSS() + }) + + if 'parent' in attrs['item_data'] and \ + attrs['item_data']['parent'] is not None: + if attrs['item_data']['parent'].oss_id != oss.pk: + raise serializers.ValidationError({ + 'parent': msg.parentNotInOSS() + }) + if attrs['item_data']['parent'] == attrs['target']: + raise serializers.ValidationError({ + 'parent': msg.blockSelfParent() + }) + return attrs + + +class DeleteBlockSerializer(serializers.Serializer): + ''' Serializer: Delete block. ''' + layout = LayoutSerializer() + target = PKField(many=False, queryset=Block.objects.all().only('oss_id')) + + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + block = cast(Block, attrs['target']) + if block.oss_id != oss.pk: + raise serializers.ValidationError({ + 'target': msg.blockNotInOSS() }) return attrs @@ -217,9 +255,24 @@ class OperationDeleteSerializer(serializers.Serializer): def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) operation = cast(Operation, attrs['target']) - if oss and operation.oss_id != oss.pk: + if operation.oss_id != oss.pk: raise serializers.ValidationError({ - 'target': msg.operationNotInOSS(oss.title) + 'target': msg.operationNotInOSS() + }) + return attrs + + +class OperationTargetSerializer(serializers.Serializer): + ''' Serializer: Target single operation. ''' + layout = LayoutSerializer() + target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) + + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + operation = cast(Operation, attrs['target']) + if operation.oss_id != oss.pk: + raise serializers.ValidationError({ + 'target': msg.operationNotInOSS() }) return attrs @@ -240,7 +293,7 @@ class SetOperationInputSerializer(serializers.Serializer): operation = cast(Operation, attrs['target']) if oss and operation.oss_id != oss.pk: raise serializers.ValidationError({ - 'target': msg.operationNotInOSS(oss.title) + 'target': msg.operationNotInOSS() }) if operation.operation_type != OperationType.INPUT: raise serializers.ValidationError({ diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py index 1eb56631..6a4229c5 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py @@ -27,6 +27,10 @@ class TestOssBlocks(EndpointTester): self.block1 = self.owned.create_block( title='1', ) + self.block2 = self.owned.create_block( + title='2', + parent=self.block1 + ) self.operation1 = self.owned.create_operation( alias='1', operation_type=OperationType.INPUT, @@ -35,15 +39,12 @@ class TestOssBlocks(EndpointTester): self.operation2 = self.owned.create_operation( alias='2', operation_type=OperationType.INPUT, + parent=self.block2, ) self.operation3 = self.unowned.create_operation( alias='3', operation_type=OperationType.INPUT ) - self.block2 = self.owned.create_block( - title='2', - parent=self.block1 - ) self.block3 = self.unowned.create_block( title='3', parent=self.block1 @@ -165,3 +166,73 @@ class TestOssBlocks(EndpointTester): self.block1.refresh_from_db() self.assertEqual(self.operation1.parent.pk, new_block['id']) self.assertEqual(self.block1.parent.pk, new_block['id']) + + + @decl_endpoint('/api/oss/{item}/delete-block', method='patch') + def test_delete_block(self): + self.populateData() + self.executeNotFound(item=self.invalid_id) + self.executeBadData(item=self.owned_id) + + data = { + 'layout': self.layout_data + } + self.executeBadData(data=data) + + data['target'] = self.operation1.pk + self.executeBadData(data=data) + + data['target'] = self.block3.pk + self.executeBadData(data=data) + + data['target'] = self.block2.pk + self.logout() + self.executeForbidden(data=data) + + self.login() + response = self.executeOK(data=data) + self.operation2.refresh_from_db() + self.assertEqual(len(response.data['blocks']), 1) + self.assertEqual(self.operation2.parent.pk, self.block1.pk) + + data['target'] = self.block1.pk + response = self.executeOK(data=data) + self.operation1.refresh_from_db() + self.operation2.refresh_from_db() + self.assertEqual(len(response.data['blocks']), 0) + self.assertEqual(self.operation1.parent, None) + self.assertEqual(self.operation2.parent, None) + + + @decl_endpoint('/api/oss/{item}/update-block', method='patch') + def test_update_block(self): + self.populateData() + self.executeBadData(item=self.owned_id) + + data = { + 'target': self.invalid_id, + 'item_data': { + 'title': 'Test title mod', + 'description': 'Comment mod', + 'parent': None + }, + } + self.executeBadData(data=data) + + data['target'] = self.block3.pk + self.toggle_admin(True) + self.executeBadData(data=data) + + data['target'] = self.block2.pk + self.logout() + self.executeForbidden(data=data) + + self.login() + response = self.executeOK(data=data) + self.block2.refresh_from_db() + self.assertEqual(self.block2.title, data['item_data']['title']) + self.assertEqual(self.block2.description, data['item_data']['description']) + self.assertEqual(self.block2.parent, data['item_data']['parent']) + + data['layout'] = self.layout_data + self.executeOK(data=data) 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 4d234bf5..249f47bd 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py @@ -226,9 +226,8 @@ class TestOssOperations(EndpointTester): @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation(self): - self.executeNotFound(item=self.invalid_id) - self.populateData() + self.executeNotFound(item=self.invalid_id) self.executeBadData(item=self.owned_id) data = { @@ -371,7 +370,6 @@ class TestOssOperations(EndpointTester): 'title': 'Test title mod', 'description': 'Comment mod' }, - 'layout': self.layout_data, 'arguments': [self.operation2.pk, self.operation1.pk], 'substitutions': [ { @@ -403,6 +401,10 @@ class TestOssOperations(EndpointTester): self.assertEqual(sub.original.pk, data['substitutions'][0]['original']) self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution']) + data['layout'] = self.layout_data + self.executeOK(data=data) + + @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_update_operation_sync(self): self.populateData() diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index e2d13a0f..1afc0e95 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -40,9 +40,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev 'create_operation', 'create_block', 'delete_operation', + 'delete_block', + 'update_operation', + 'update_block', 'create_input', 'set_input', - 'update_operation', 'execute_operation', 'relocate_constituents' ]: @@ -94,7 +96,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @extend_schema( summary='create operation', tags=['OSS'], - request=s.OperationCreateSerializer(), + request=s.CreateOperationSerializer(), responses={ c.HTTP_201_CREATED: s.NewOperationResponse, c.HTTP_400_BAD_REQUEST: None, @@ -104,8 +106,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) @action(detail=True, methods=['post'], url_path='create-operation') def create_operation(self, request: Request, pk) -> HttpResponse: - ''' Create new operation. ''' - serializer = s.OperationCreateSerializer( + ''' Create Operation. ''' + serializer = s.CreateOperationSerializer( data=request.data, context={'oss': self.get_object()} ) @@ -155,7 +157,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @extend_schema( summary='create block', tags=['OSS'], - request=s.BlockCreateSerializer(), + request=s.CreateBlockSerializer(), responses={ c.HTTP_201_CREATED: s.NewBlockResponse, c.HTTP_400_BAD_REQUEST: None, @@ -165,8 +167,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) @action(detail=True, methods=['post'], url_path='create-block') def create_block(self, request: Request, pk) -> HttpResponse: - ''' Create new block. ''' - serializer = s.BlockCreateSerializer( + ''' Create Block. ''' + serializer = s.CreateBlockSerializer( data=request.data, context={'oss': self.get_object()} ) @@ -216,7 +218,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) @action(detail=True, methods=['patch'], url_path='delete-operation') def delete_operation(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Delete operation. ''' + ''' Endpoint: Delete Operation. ''' serializer = s.OperationDeleteSerializer( data=request.data, context={'oss': self.get_object()} @@ -243,6 +245,119 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev data=s.OperationSchemaSerializer(oss.model).data ) + @extend_schema( + summary='delete block', + tags=['OSS'], + request=s.DeleteBlockSerializer, + responses={ + c.HTTP_200_OK: s.OperationSchemaSerializer, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['patch'], url_path='delete-block') + def delete_block(self, request: Request, pk) -> HttpResponse: + ''' Endpoint: Delete Block. ''' + serializer = s.DeleteBlockSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + oss = m.OperationSchema(self.get_object()) + block = cast(m.Block, serializer.validated_data['target']) + layout = serializer.validated_data['layout'] + layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk] + with transaction.atomic(): + oss.delete_block(block) + oss.update_layout(layout) + + return Response( + status=c.HTTP_200_OK, + data=s.OperationSchemaSerializer(oss.model).data + ) + + @extend_schema( + summary='update operation', + tags=['OSS'], + request=s.UpdateOperationSerializer(), + responses={ + c.HTTP_200_OK: s.OperationSchemaSerializer, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['patch'], url_path='update-operation') + def update_operation(self, request: Request, pk) -> HttpResponse: + ''' Update Operation arguments and parameters. ''' + serializer = s.UpdateOperationSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) + oss = m.OperationSchema(self.get_object()) + with transaction.atomic(): + if 'layout' in serializer.validated_data: + oss.update_layout(serializer.validated_data['layout']) + operation.alias = serializer.validated_data['item_data']['alias'] + operation.title = serializer.validated_data['item_data']['title'] + operation.description = serializer.validated_data['item_data']['description'] + operation.save(update_fields=['alias', 'title', 'description']) + + if operation.result is not None: + can_edit = permissions.can_edit_item(request.user, operation.result) + if can_edit or operation.operation_type == m.OperationType.SYNTHESIS: + operation.result.alias = operation.alias + operation.result.title = operation.title + operation.result.description = operation.description + operation.result.save() + if 'arguments' in serializer.validated_data: + oss.set_arguments(operation.pk, serializer.validated_data['arguments']) + if 'substitutions' in serializer.validated_data: + oss.set_substitutions(operation.pk, serializer.validated_data['substitutions']) + return Response( + status=c.HTTP_200_OK, + data=s.OperationSchemaSerializer(oss.model).data + ) + + @extend_schema( + summary='update block', + tags=['OSS'], + request=s.UpdateBlockSerializer(), + responses={ + c.HTTP_200_OK: s.OperationSchemaSerializer, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['patch'], url_path='update-block') + def update_block(self, request: Request, pk) -> HttpResponse: + ''' Update Block. ''' + serializer = s.UpdateBlockSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + block: m.Block = cast(m.Block, serializer.validated_data['target']) + oss = m.OperationSchema(self.get_object()) + with transaction.atomic(): + if 'layout' in serializer.validated_data: + oss.update_layout(serializer.validated_data['layout']) + block.title = serializer.validated_data['item_data']['title'] + block.description = serializer.validated_data['item_data']['description'] + block.parent = serializer.validated_data['item_data']['parent'] + block.save(update_fields=['title', 'description', 'parent']) + return Response( + status=c.HTTP_200_OK, + data=s.OperationSchemaSerializer(oss.model).data + ) + @extend_schema( summary='create input schema for target operation', tags=['OSS'], @@ -256,7 +371,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) @action(detail=True, methods=['patch'], url_path='create-input') def create_input(self, request: Request, pk) -> HttpResponse: - ''' Create new input RSForm. ''' + ''' Create input RSForm. ''' serializer = s.OperationTargetSerializer( data=request.data, context={'oss': self.get_object()} @@ -333,51 +448,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev data=s.OperationSchemaSerializer(oss.model).data ) - @extend_schema( - summary='update operation', - tags=['OSS'], - request=s.OperationUpdateSerializer(), - responses={ - c.HTTP_200_OK: s.OperationSchemaSerializer, - c.HTTP_400_BAD_REQUEST: None, - c.HTTP_403_FORBIDDEN: None, - c.HTTP_404_NOT_FOUND: None - } - ) - @action(detail=True, methods=['patch'], url_path='update-operation') - def update_operation(self, request: Request, pk) -> HttpResponse: - ''' Update operation arguments and parameters. ''' - serializer = s.OperationUpdateSerializer( - data=request.data, - context={'oss': self.get_object()} - ) - serializer.is_valid(raise_exception=True) - - operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) - oss = m.OperationSchema(self.get_object()) - with transaction.atomic(): - oss.update_layout(serializer.validated_data['layout']) - operation.alias = serializer.validated_data['item_data']['alias'] - operation.title = serializer.validated_data['item_data']['title'] - operation.description = serializer.validated_data['item_data']['description'] - operation.save(update_fields=['alias', 'title', 'description']) - - if operation.result is not None: - can_edit = permissions.can_edit_item(request.user, operation.result) - if can_edit or operation.operation_type == m.OperationType.SYNTHESIS: - operation.result.alias = operation.alias - operation.result.title = operation.title - operation.result.description = operation.description - operation.result.save() - if 'arguments' in serializer.validated_data: - oss.set_arguments(operation.pk, serializer.validated_data['arguments']) - if 'substitutions' in serializer.validated_data: - oss.set_substitutions(operation.pk, serializer.validated_data['substitutions']) - return Response( - status=c.HTTP_200_OK, - data=s.OperationSchemaSerializer(oss.model).data - ) - @extend_schema( summary='execute operation', tags=['OSS'], diff --git a/rsconcept/backend/apps/rsform/models/RSForm.py b/rsconcept/backend/apps/rsform/models/RSForm.py index 2fac40ff..1c721bec 100644 --- a/rsconcept/backend/apps/rsform/models/RSForm.py +++ b/rsconcept/backend/apps/rsform/models/RSForm.py @@ -137,7 +137,7 @@ class RSForm: return result def create_cst(self, data: dict, insert_after: Optional[Constituenta] = None) -> Constituenta: - ''' Create new cst from data. ''' + ''' Create constituenta from data. ''' if insert_after is None: position = INSERT_LAST else: diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index ac17b2b4..e0ac43e8 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -77,7 +77,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['post'], url_path='create-cst') def create_cst(self, request: Request, pk) -> HttpResponse: - ''' Create new constituenta. ''' + ''' Create Constituenta. ''' serializer = s.CstCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) data = serializer.validated_data @@ -254,7 +254,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['patch'], url_path='delete-multiple-cst') def delete_multiple_cst(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Delete multiple constituents. ''' + ''' Endpoint: Delete multiple Constituents. ''' model = self._get_item() serializer = s.CstListSerializer( data=request.data, @@ -284,7 +284,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['patch'], url_path='move-cst') def move_cst(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Move multiple constituents. ''' + ''' Endpoint: Move multiple Constituents. ''' model = self._get_item() serializer = s.CstMoveSerializer( data=request.data, @@ -334,7 +334,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['patch'], url_path='restore-order') def restore_order(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Restore order based on types and term graph. ''' + ''' Endpoint: Restore order based on types and Term graph. ''' model = self._get_item() m.RSForm(model).restore_order() return Response( @@ -449,7 +449,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['post'], url_path='check-constituenta') def check_constituenta(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Check RSLang expression against schema context. ''' + ''' Endpoint: Check RSLang expression against Schema context. ''' serializer = s.ConstituentaCheckSerializer(data=request.data) serializer.is_valid(raise_exception=True) expression = serializer.validated_data['definition_formal'] @@ -474,7 +474,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) @action(detail=True, methods=['post'], url_path='resolve') def resolve(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Resolve references in text against schema terms context. ''' + ''' Endpoint: Resolve references in text against Schema terms context. ''' serializer = s.TextSerializer(data=request.data) serializer.is_valid(raise_exception=True) text = serializer.validated_data['text'] @@ -543,7 +543,7 @@ class TrsImportView(views.APIView): @extend_schema( - summary='create new RSForm empty or from file', + summary='create RSForm empty or from file', tags=['RSForm'], request=LibraryItemSerializer, responses={ diff --git a/rsconcept/backend/shared/messages.py b/rsconcept/backend/shared/messages.py index 408ecc8c..9bf67c53 100644 --- a/rsconcept/backend/shared/messages.py +++ b/rsconcept/backend/shared/messages.py @@ -7,15 +7,23 @@ def constituentaNotInRSform(title: str): def constituentaNotFromOperation(): - return f'Конституента не соответствую аргументам операции' + return 'Конституента не соответствую аргументам операции' -def operationNotInOSS(title: str): - return f'Операция не принадлежит ОСС: {title}' +def operationNotInOSS(): + return 'Операция не принадлежит ОСС' + + +def blockNotInOSS(): + return 'Блок не принадлежит ОСС' def parentNotInOSS(): - return f'Родительский блок не принадлежит ОСС' + return 'Родительский блок не принадлежит ОСС' + + +def blockSelfParent(): + return 'Попытка создания циклического вложения' def childNotInOSS():