From fd47a4560a85ba044839df371c78f16fbaf0d44c Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:10:19 +0300 Subject: [PATCH] F: Implement create-block API --- .../apps/oss/models/OperationSchema.py | 11 ++ .../backend/apps/oss/serializers/__init__.py | 9 +- .../apps/oss/serializers/data_access.py | 97 +++++++++- .../backend/apps/oss/serializers/responses.py | 8 +- .../apps/oss/tests/s_models/t_Argument.py | 1 + .../apps/oss/tests/s_models/t_Inheritance.py | 1 + .../apps/oss/tests/s_models/t_Operation.py | 1 + .../apps/oss/tests/s_models/t_Substitution.py | 1 + .../oss/tests/s_propagation/t_attributes.py | 8 + .../oss/tests/s_propagation/t_constituents.py | 6 + .../oss/tests/s_propagation/t_operations.py | 15 ++ .../tests/s_propagation/t_substitutions.py | 5 + .../apps/oss/tests/s_views/__init__.py | 1 + .../apps/oss/tests/s_views/t_blocks.py | 167 ++++++++++++++++++ .../apps/oss/tests/s_views/t_operations.py | 53 +++++- .../backend/apps/oss/tests/s_views/t_oss.py | 6 +- rsconcept/backend/apps/oss/views/oss.py | 57 +++++- .../rsform/tests/s_models/t_Constituenta.py | 3 + .../apps/rsform/tests/s_models/t_RSForm.py | 3 + .../apps/rsform/tests/s_views/t_rsforms.py | 2 + .../backend/apps/rsform/tests/t_graph.py | 7 + .../backend/apps/rsform/tests/t_utils.py | 1 + rsconcept/backend/run_testfile.py | 10 +- rsconcept/backend/shared/messages.py | 12 ++ .../src/features/oss/backend/types.ts | 34 +++- 25 files changed, 491 insertions(+), 28 deletions(-) create mode 100644 rsconcept/backend/apps/oss/tests/s_views/t_blocks.py diff --git a/rsconcept/backend/apps/oss/models/OperationSchema.py b/rsconcept/backend/apps/oss/models/OperationSchema.py index 5796c4b3..7ae8066a 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchema.py +++ b/rsconcept/backend/apps/oss/models/OperationSchema.py @@ -19,6 +19,7 @@ from apps.rsform.models import ( ) from .Argument import Argument +from .Block import Block from .Inheritance import Inheritance from .Layout import Layout from .Operation import Operation @@ -60,6 +61,10 @@ class OperationSchema: ''' Get QuerySet containing all operations of current OSS. ''' return Operation.objects.filter(oss=self.model) + def blocks(self) -> QuerySet[Block]: + ''' Get QuerySet containing all blocks of current OSS. ''' + return Block.objects.filter(oss=self.model) + def arguments(self) -> QuerySet[Argument]: ''' Operation arguments. ''' return Argument.objects.filter(operation__oss=self.model) @@ -99,6 +104,12 @@ class OperationSchema: self.save(update_fields=['time_update']) return result + def create_block(self, **kwargs) -> Block: + ''' Insert new 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. ''' self.cache.ensure_loaded() diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py index 468c770a..54d82695 100644 --- a/rsconcept/backend/apps/oss/serializers/__init__.py +++ b/rsconcept/backend/apps/oss/serializers/__init__.py @@ -3,6 +3,8 @@ from .basics import LayoutSerializer, SubstitutionExSerializer from .data_access import ( ArgumentSerializer, + BlockCreateSerializer, + BlockSerializer, OperationCreateSerializer, OperationDeleteSerializer, OperationSchemaSerializer, @@ -12,4 +14,9 @@ from .data_access import ( RelocateConstituentsSerializer, SetOperationInputSerializer ) -from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse +from .responses import ( + ConstituentaReferenceResponse, + NewBlockResponse, + NewOperationResponse, + NewSchemaResponse +) diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index d5f7fe85..a39cc0e3 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -11,7 +11,7 @@ from apps.rsform.models import Constituenta from apps.rsform.serializers import SubstitutionSerializerBase from shared import messages as msg -from ..models import Argument, Inheritance, Operation, OperationSchema, OperationType +from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType from .basics import LayoutSerializer, SubstitutionExSerializer @@ -24,6 +24,15 @@ class OperationSerializer(serializers.ModelSerializer): read_only_fields = ('id', 'oss') +class BlockSerializer(serializers.ModelSerializer): + ''' Serializer: Block data. ''' + class Meta: + ''' serializer metadata. ''' + model = Block + fields = '__all__' + read_only_fields = ('id', 'oss') + + class ArgumentSerializer(serializers.ModelSerializer): ''' Serializer: Operation data. ''' class Meta: @@ -32,6 +41,49 @@ class ArgumentSerializer(serializers.ModelSerializer): fields = ('operation', 'argument') +class BlockCreateSerializer(serializers.Serializer): + ''' Serializer: Block creation. ''' + class BlockCreateData(serializers.ModelSerializer): + ''' Serializer: Block creation data. ''' + + class Meta: + ''' serializer metadata. ''' + model = Block + fields = 'title', 'description', 'parent' + + layout = LayoutSerializer() + + item_data = BlockCreateData() + width = serializers.FloatField() + height = serializers.FloatField() + position_x = serializers.FloatField() + position_y = serializers.FloatField() + children_operations = PKField(many=True, queryset=Operation.objects.all().only('oss_id')) + children_blocks = PKField(many=True, queryset=Block.objects.all().only('oss_id')) + + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + if 'parent' in attrs['item_data'] and \ + attrs['item_data']['parent'] is not None and \ + attrs['item_data']['parent'].oss_id != oss.pk: + raise serializers.ValidationError({ + 'parent': msg.parentNotInOSS() + }) + + for operation in attrs['children_operations']: + if operation.oss_id != oss.pk: + raise serializers.ValidationError({ + 'children_operations': msg.childNotInOSS() + }) + + for block in attrs['children_blocks']: + if block.oss_id != oss.pk: + raise serializers.ValidationError({ + 'children_blocks': msg.childNotInOSS() + }) + return attrs + + class OperationCreateSerializer(serializers.Serializer): ''' Serializer: Operation creation. ''' class OperationCreateData(serializers.ModelSerializer): @@ -47,13 +99,31 @@ class OperationCreateSerializer(serializers.Serializer): 'description', 'result', 'parent' layout = LayoutSerializer() - position_x = serializers.FloatField() - position_y = serializers.FloatField() item_data = OperationCreateData() + position_x = serializers.FloatField() + position_y = serializers.FloatField() create_schema = serializers.BooleanField(default=False, required=False) arguments = PKField(many=True, queryset=Operation.objects.all().only('pk'), required=False) + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + if 'parent' in attrs['item_data'] and \ + attrs['item_data']['parent'] is not None and \ + attrs['item_data']['parent'].oss_id != oss.pk: + raise serializers.ValidationError({ + 'parent': msg.parentNotInOSS() + }) + + if 'arguments' not in attrs: + return attrs + for operation in attrs['arguments']: + if operation.oss_id != oss.pk: + raise serializers.ValidationError({ + 'arguments': msg.operationNotInOSS(oss.title) + }) + return attrs + class OperationUpdateSerializer(serializers.Serializer): ''' Serializer: Operation update. ''' @@ -74,10 +144,19 @@ class OperationUpdateSerializer(serializers.Serializer): ) def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + if 'parent' in attrs['item_data'] and attrs['item_data']['parent'].oss_id != oss.pk: + raise serializers.ValidationError({ + 'parent': msg.parentNotInOSS() + }) + if 'arguments' not in attrs: + if 'substitutions' in attrs: + raise serializers.ValidationError({ + 'arguments': msg.missingArguments() + }) return attrs - oss = cast(LibraryItem, self.context['oss']) for operation in attrs['arguments']: if operation.oss_id != oss.pk: raise serializers.ValidationError({ @@ -175,6 +254,9 @@ class OperationSchemaSerializer(serializers.ModelSerializer): operations = serializers.ListField( child=OperationSerializer() ) + blocks = serializers.ListField( + child=BlockSerializer() + ) arguments = serializers.ListField( child=ArgumentSerializer() ) @@ -194,12 +276,15 @@ class OperationSchemaSerializer(serializers.ModelSerializer): oss = OperationSchema(instance) result['layout'] = oss.layout().data result['operations'] = [] + result['blocks'] = [] + result['arguments'] = [] + result['substitutions'] = [] for operation in oss.operations().order_by('pk'): result['operations'].append(OperationSerializer(operation).data) - result['arguments'] = [] + for block in oss.blocks().order_by('pk'): + result['blocks'].append(BlockSerializer(block).data) for argument in oss.arguments().order_by('order'): result['arguments'].append(ArgumentSerializer(argument).data) - result['substitutions'] = [] for substitution in oss.substitutions().values( 'operation', 'original', diff --git a/rsconcept/backend/apps/oss/serializers/responses.py b/rsconcept/backend/apps/oss/serializers/responses.py index 7dfb9017..9320d03a 100644 --- a/rsconcept/backend/apps/oss/serializers/responses.py +++ b/rsconcept/backend/apps/oss/serializers/responses.py @@ -3,7 +3,7 @@ from rest_framework import serializers from apps.library.serializers import LibraryItemSerializer -from .data_access import OperationSchemaSerializer, OperationSerializer +from .data_access import BlockSerializer, OperationSchemaSerializer, OperationSerializer class NewOperationResponse(serializers.Serializer): @@ -12,6 +12,12 @@ class NewOperationResponse(serializers.Serializer): oss = OperationSchemaSerializer() +class NewBlockResponse(serializers.Serializer): + ''' Serializer: Create block response. ''' + new_block = BlockSerializer() + oss = OperationSchemaSerializer() + + class NewSchemaResponse(serializers.Serializer): ''' Serializer: Create RSForm for input operation response. ''' new_schema = LibraryItemSerializer() diff --git a/rsconcept/backend/apps/oss/tests/s_models/t_Argument.py b/rsconcept/backend/apps/oss/tests/s_models/t_Argument.py index 0b48dd91..6f79255e 100644 --- a/rsconcept/backend/apps/oss/tests/s_models/t_Argument.py +++ b/rsconcept/backend/apps/oss/tests/s_models/t_Argument.py @@ -8,6 +8,7 @@ from apps.oss.models import Argument, Operation, OperationSchema, OperationType class TestArgument(TestCase): ''' Testing Argument model. ''' + def setUp(self): self.oss = OperationSchema.create(alias='T1') diff --git a/rsconcept/backend/apps/oss/tests/s_models/t_Inheritance.py b/rsconcept/backend/apps/oss/tests/s_models/t_Inheritance.py index 8b759655..106033ca 100644 --- a/rsconcept/backend/apps/oss/tests/s_models/t_Inheritance.py +++ b/rsconcept/backend/apps/oss/tests/s_models/t_Inheritance.py @@ -8,6 +8,7 @@ from apps.rsform.models import Constituenta, RSForm class TestInheritance(TestCase): ''' Testing Inheritance model. ''' + def setUp(self): self.oss = OperationSchema.create(alias='T1') diff --git a/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py b/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py index efcd9d50..a84ee9ad 100644 --- a/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py +++ b/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py @@ -9,6 +9,7 @@ from apps.rsform.models import RSForm class TestOperation(TestCase): ''' Testing Operation model. ''' + def setUp(self): self.oss = OperationSchema.create(alias='T1') self.operation = Operation.objects.create( diff --git a/rsconcept/backend/apps/oss/tests/s_models/t_Substitution.py b/rsconcept/backend/apps/oss/tests/s_models/t_Substitution.py index bc1eb813..37854dcb 100644 --- a/rsconcept/backend/apps/oss/tests/s_models/t_Substitution.py +++ b/rsconcept/backend/apps/oss/tests/s_models/t_Substitution.py @@ -10,6 +10,7 @@ from apps.rsform.models import RSForm class TestSynthesisSubstitution(TestCase): ''' Testing Synthesis Substitution model. ''' + def setUp(self): self.oss = OperationSchema.create(alias='T1') diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py index 6a692417..371f115e 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py @@ -9,6 +9,7 @@ from shared.EndpointTester import EndpointTester, decl_endpoint class TestChangeAttributes(EndpointTester): ''' Testing LibraryItem view when OSS is associated with RSForms. ''' + def setUp(self): super().setUp() self.user3 = User.objects.create( @@ -70,6 +71,7 @@ class TestChangeAttributes(EndpointTester): layout.data = self.layout_data layout.save() + @decl_endpoint('/api/library/{item}/set-owner', method='patch') def test_set_owner(self): data = {'user': self.user3.pk} @@ -85,6 +87,7 @@ class TestChangeAttributes(EndpointTester): self.assertEqual(self.ks2.model.owner, self.user2) self.assertEqual(self.ks3.model.owner, self.user3) + @decl_endpoint('/api/library/{item}/set-location', method='patch') def test_set_location(self): data = {'location': '/U/temp'} @@ -100,6 +103,7 @@ class TestChangeAttributes(EndpointTester): self.assertNotEqual(self.ks2.model.location, data['location']) self.assertEqual(self.ks3.model.location, data['location']) + @decl_endpoint('/api/library/{item}/set-access-policy', method='patch') def test_set_access_policy(self): data = {'access_policy': AccessPolicy.PROTECTED} @@ -115,6 +119,7 @@ class TestChangeAttributes(EndpointTester): self.assertNotEqual(self.ks2.model.access_policy, data['access_policy']) self.assertEqual(self.ks3.model.access_policy, data['access_policy']) + @decl_endpoint('/api/library/{item}/set-editors', method='patch') def test_set_editors(self): Editor.set(self.owned.model.pk, [self.user2.pk]) @@ -133,6 +138,7 @@ class TestChangeAttributes(EndpointTester): self.assertEqual(list(self.ks2.model.getQ_editors()), []) self.assertEqual(set(self.ks3.model.getQ_editors()), set([self.user, self.user3])) + @decl_endpoint('/api/library/{item}', method='patch') def test_sync_from_result(self): data = {'alias': 'KS111', 'title': 'New Title', 'description': 'New description'} @@ -145,6 +151,7 @@ class TestChangeAttributes(EndpointTester): self.assertEqual(self.operation1.title, data['title']) self.assertEqual(self.operation1.description, data['description']) + @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_sync_from_operation(self): data = { @@ -163,6 +170,7 @@ class TestChangeAttributes(EndpointTester): self.assertEqual(self.ks3.model.title, data['item_data']['title']) self.assertEqual(self.ks3.model.description, data['item_data']['description']) + @decl_endpoint('/api/library/{item}', method='delete') def test_destroy_oss_consequence(self): response = self.executeNoContent(item=self.owned_id) diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py index 4afd5a38..b27283a6 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py @@ -69,6 +69,7 @@ class TestChangeConstituents(EndpointTester): layout.data = self.layout_data layout.save() + @decl_endpoint('/api/rsforms/{item}/details', method='get') def test_retrieve_inheritance(self): response = self.executeOK(item=self.ks3.model.pk) @@ -96,6 +97,7 @@ class TestChangeConstituents(EndpointTester): }, ]) + @decl_endpoint('/api/rsforms/{schema}/create-cst', method='post') def test_create_constituenta(self): data = { @@ -112,6 +114,7 @@ class TestChangeConstituents(EndpointTester): self.assertEqual(inherited_cst.order, 2) self.assertEqual(inherited_cst.definition_formal, 'X1 = X2') + @decl_endpoint('/api/rsforms/{schema}/rename-cst', method='patch') def test_rename_constituenta(self): data = {'target': self.ks1X1.pk, 'alias': 'D21', 'cst_type': CstType.TERM} @@ -123,6 +126,7 @@ class TestChangeConstituents(EndpointTester): self.assertEqual(inherited_cst.alias, 'D2') self.assertEqual(inherited_cst.cst_type, data['cst_type']) + @decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') def test_update_constituenta(self): d2 = self.ks3.insert_new('D2', cst_type=CstType.TERM, definition_raw='@{X1|sing,nomn}') @@ -149,6 +153,7 @@ class TestChangeConstituents(EndpointTester): self.assertEqual(inherited_cst.definition_formal, r'X1\X1') self.assertEqual(inherited_cst.definition_raw, r'@{X2|sing,datv}') + @decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch') def test_delete_constituenta(self): data = {'items': [self.ks2X1.pk]} @@ -160,6 +165,7 @@ class TestChangeConstituents(EndpointTester): self.assertEqual(self.ks2D1.definition_formal, r'DEL\DEL') self.assertEqual(inherited_cst.definition_formal, r'DEL\DEL') + @decl_endpoint('/api/rsforms/{schema}/substitute', method='patch') def test_substitute(self): d2 = self.ks3.insert_new('D2', cst_type=CstType.TERM, definition_formal=r'X1\X2\X3') diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py index 4ee3e715..eddad306 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py @@ -8,6 +8,7 @@ from shared.EndpointTester import EndpointTester, decl_endpoint class TestChangeOperations(EndpointTester): ''' Testing Operations change propagation in OSS. ''' + def setUp(self): super().setUp() self.owned = OperationSchema.create( @@ -120,6 +121,7 @@ class TestChangeOperations(EndpointTester): layout.data = self.layout_data layout.save() + def test_oss_setup(self): self.assertEqual(self.ks1.constituents().count(), 3) self.assertEqual(self.ks2.constituents().count(), 3) @@ -128,6 +130,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks5.constituents().count(), 8) self.assertEqual(self.ks4D1.definition_formal, 'S1 X1') + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_input_operation(self): data = { @@ -148,6 +151,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1') self.assertEqual(self.ks5D4.definition_formal, r'DEL DEL X3 DEL D1 D2 D3') + @decl_endpoint('/api/oss/{item}/set-input', method='patch') def test_set_input_null(self): data = { @@ -171,6 +175,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1') self.assertEqual(self.ks5D4.definition_formal, r'DEL DEL X3 DEL D1 D2 D3') + @decl_endpoint('/api/oss/{item}/set-input', method='patch') def test_set_input_change_schema(self): ks6 = RSForm.create( @@ -206,6 +211,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1') self.assertEqual(self.ks5D4.definition_formal, r'DEL DEL X3 DEL D1 D2 D3') + @decl_endpoint('/api/library/{item}', method='delete') def test_delete_schema(self): self.executeNoContent(item=self.ks1.model.pk) @@ -222,6 +228,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 DEL D2 D3') + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation_and_constituents(self): data = { @@ -243,6 +250,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 DEL D2 D3') + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation_keep_constituents(self): data = { @@ -264,6 +272,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 D1') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3') + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation_keep_schema(self): data = { @@ -288,6 +297,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3') self.assertFalse(Constituenta.objects.filter(as_child__parent_id=self.ks1D1.pk).exists()) + @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_change_substitutions(self): data = { @@ -298,6 +308,7 @@ class TestChangeOperations(EndpointTester): 'description': 'Comment mod' }, 'layout': self.layout_data, + 'arguments': [self.operation1.pk, self.operation2.pk], 'substitutions': [ { 'original': self.ks1X1.pk, @@ -322,6 +333,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 D1 X3 S1 D1') self.assertEqual(self.ks5D4.definition_formal, r'D1 X2 X3 S1 D1 D2 D3') + @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_change_arguments(self): data = { @@ -360,6 +372,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1') self.assertEqual(self.ks5D4.definition_formal, r'DEL DEL X3 DEL D1 D2 D3') + @decl_endpoint('/api/oss/{item}/execute-operation', method='post') def test_execute_middle_operation(self): self.client.delete(f'/api/library/{self.ks4.model.pk}') @@ -378,6 +391,7 @@ class TestChangeOperations(EndpointTester): self.assertNotEqual(self.operation4.result, None) self.assertEqual(self.ks5.constituents().count(), 8) + @decl_endpoint('/api/oss/relocate-constituents', method='post') def test_relocate_constituents_up(self): ks1_old_count = self.ks1.constituents().count() @@ -407,6 +421,7 @@ class TestChangeOperations(EndpointTester): self.assertEqual(self.ks1.constituents().count(), ks1_old_count + 1) self.assertEqual(self.ks4.constituents().count(), ks4_old_count + 1) + @decl_endpoint('/api/oss/relocate-constituents', method='post') def test_relocate_constituents_down(self): ks1_old_count = self.ks1.constituents().count() diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py index 6d85bc73..c99bfb29 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py @@ -8,6 +8,7 @@ from shared.EndpointTester import EndpointTester, decl_endpoint class TestChangeSubstitutions(EndpointTester): ''' Testing Substitutions change propagation in OSS. ''' + def setUp(self): super().setUp() self.owned = OperationSchema.create( @@ -129,6 +130,7 @@ class TestChangeSubstitutions(EndpointTester): self.assertEqual(self.ks5.constituents().count(), 8) self.assertEqual(self.ks4D1.definition_formal, 'S1 X1') + @decl_endpoint('/api/rsforms/{schema}/substitute', method='patch') def test_substitute_original(self): data = {'substitutions': [{ @@ -151,6 +153,7 @@ class TestChangeSubstitutions(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'S1 X2 X3 S1 D1') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 X3 D1 D2 D3') + @decl_endpoint('/api/rsforms/{schema}/substitute', method='patch') def test_substitute_substitution(self): data = { @@ -175,6 +178,7 @@ class TestChangeSubstitutions(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 X2 D1') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 X1 D1 D2 D3') + @decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch') def test_delete_original(self): data = {'items': [self.ks1X1.pk, self.ks1D1.pk]} @@ -189,6 +193,7 @@ class TestChangeSubstitutions(EndpointTester): self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 DEL') self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 DEL D2 D3') + @decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch') def test_delete_substitution(self): data = {'items': [self.ks2S1.pk, self.ks2X2.pk]} diff --git a/rsconcept/backend/apps/oss/tests/s_views/__init__.py b/rsconcept/backend/apps/oss/tests/s_views/__init__.py index c6532902..1e4377be 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/__init__.py +++ b/rsconcept/backend/apps/oss/tests/s_views/__init__.py @@ -1,3 +1,4 @@ ''' Tests for REST API. ''' +from .t_blocks import * from .t_operations import * from .t_oss import * diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py new file mode 100644 index 00000000..1eb56631 --- /dev/null +++ b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py @@ -0,0 +1,167 @@ +''' Testing API: Operation Schema - blocks manipulation. ''' +from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType +from apps.oss.models import Operation, OperationSchema, OperationType +from apps.rsform.models import Constituenta, RSForm +from shared.EndpointTester import EndpointTester, decl_endpoint + + +class TestOssBlocks(EndpointTester): + ''' Testing OSS view - Blocks. ''' + + + def setUp(self): + super().setUp() + self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) + self.owned_id = self.owned.model.pk + self.unowned = OperationSchema.create(title='Test2', alias='T2') + self.unowned_id = self.unowned.model.pk + self.invalid_id = self.unowned_id + 1337 + + + def populateData(self): + self.unowned.create_block() + self.unowned.create_block() + self.unowned.create_block() + self.unowned.create_block() + + self.block1 = self.owned.create_block( + title='1', + ) + self.operation1 = self.owned.create_operation( + alias='1', + operation_type=OperationType.INPUT, + parent=self.block1, + ) + self.operation2 = self.owned.create_operation( + alias='2', + operation_type=OperationType.INPUT, + ) + 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 + ) + self.layout_data = { + 'operations': [ + {'id': self.operation1.pk, 'x': 0, 'y': 0}, + {'id': self.operation2.pk, 'x': 0, 'y': 0}, + ], + 'blocks': [ + {'id': self.block1.pk, 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5}, + {'id': self.block2.pk, 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5}, + ] + } + layout = self.owned.layout() + layout.data = self.layout_data + layout.save() + + + @decl_endpoint('/api/oss/{item}/create-block', method='post') + def test_create_block(self): + self.populateData() + self.executeBadData(item=self.owned_id) + + data = { + 'item_data': { + 'title': 'Test title', + 'description': 'Тест кириллицы', + }, + 'layout': self.layout_data, + 'position_x': 1337, + 'position_y': 1337, + 'width': 0.42, + 'height': 0.42, + 'children_operations': [], + 'children_blocks': [] + } + self.executeNotFound(data=data, item=self.invalid_id) + + response = self.executeCreated(data=data, item=self.owned_id) + self.assertEqual(len(response.data['oss']['blocks']), 3) + new_block = response.data['new_block'] + layout = response.data['oss']['layout'] + item = [item for item in layout['blocks'] if item['id'] == new_block['id']][0] + self.assertEqual(new_block['title'], data['item_data']['title']) + self.assertEqual(new_block['description'], data['item_data']['description']) + self.assertEqual(new_block['parent'], None) + self.assertEqual(item['x'], data['position_x']) + self.assertEqual(item['y'], data['position_y']) + self.assertEqual(item['width'], data['width']) + self.assertEqual(item['height'], data['height']) + self.operation1.refresh_from_db() + + self.executeForbidden(data=data, item=self.unowned_id) + self.toggle_admin(True) + self.executeCreated(data=data, item=self.unowned_id) + + + @decl_endpoint('/api/oss/{item}/create-block', method='post') + def test_create_block_parent(self): + self.populateData() + data = { + 'item_data': { + 'title': 'Test title', + 'description': 'Тест кириллицы', + 'parent': self.invalid_id + }, + 'layout': self.layout_data, + 'position_x': 1337, + 'position_y': 1337, + 'width': 0.42, + 'height': 0.42, + 'children_operations': [], + 'children_blocks': [] + } + self.executeBadData(data=data, item=self.owned_id) + + data['item_data']['parent'] = self.block3.pk + self.executeBadData(data=data) + + data['item_data']['parent'] = self.block1.pk + response = self.executeCreated(data=data) + new_block = response.data['new_block'] + self.assertEqual(new_block['parent'], self.block1.pk) + + + @decl_endpoint('/api/oss/{item}/create-block', method='post') + def test_create_block_children(self): + self.populateData() + data = { + 'item_data': { + 'title': 'Test title', + 'description': 'Тест кириллицы', + }, + 'layout': self.layout_data, + 'position_x': 1337, + 'position_y': 1337, + 'width': 0.42, + 'height': 0.42, + 'children_operations': [self.invalid_id], + 'children_blocks': [] + } + self.executeBadData(data=data, item=self.owned_id) + + data['children_operations'] = [self.operation3.pk] + self.executeBadData(data=data) + + data['children_operations'] = [self.block1.pk] + self.executeBadData(data=data) + + data['children_operations'] = [self.operation1.pk] + data['children_blocks'] = [self.operation1.pk] + self.executeBadData(data=data) + + data['children_blocks'] = [self.block1.pk] + response = self.executeCreated(data=data) + new_block = response.data['new_block'] + self.operation1.refresh_from_db() + self.block1.refresh_from_db() + self.assertEqual(self.operation1.parent.pk, new_block['id']) + self.assertEqual(self.block1.parent.pk, new_block['id']) 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 4509a4c4..4d234bf5 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py @@ -1,4 +1,4 @@ -''' Testing API: Operation Schema. ''' +''' Testing API: Operation Schema - operations manipulation. ''' from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType from apps.oss.models import Operation, OperationSchema, OperationType from apps.rsform.models import Constituenta, RSForm @@ -6,7 +6,8 @@ from shared.EndpointTester import EndpointTester, decl_endpoint class TestOssOperations(EndpointTester): - ''' Testing OSS view - operations. ''' + ''' Testing OSS view - Operations. ''' + def setUp(self): super().setUp() @@ -14,9 +15,8 @@ class TestOssOperations(EndpointTester): self.owned_id = self.owned.model.pk self.unowned = OperationSchema.create(title='Test2', alias='T2') self.unowned_id = self.unowned.model.pk - self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE) - self.private_id = self.private.model.pk - self.invalid_id = self.private.model.pk + 1337 + self.invalid_id = self.unowned_id + 1337 + def populateData(self): self.ks1 = RSForm.create( @@ -72,6 +72,7 @@ class TestOssOperations(EndpointTester): 'substitution': self.ks2X1 }]) + @decl_endpoint('/api/oss/{item}/create-operation', method='post') def test_create_operation(self): self.populateData() @@ -116,6 +117,38 @@ class TestOssOperations(EndpointTester): self.toggle_admin(True) self.executeCreated(data=data, item=self.unowned_id) + + @decl_endpoint('/api/oss/{item}/create-operation', method='post') + def test_create_operation_parent(self): + self.populateData() + data = { + 'item_data': { + 'parent': self.invalid_id, + 'alias': 'Test3', + 'title': 'Test title', + 'description': '', + 'operation_type': OperationType.INPUT + + }, + 'layout': self.layout_data, + 'position_x': 1, + 'position_y': 1 + + } + self.executeBadData(data=data, item=self.owned_id) + + block_unowned = self.unowned.create_block(title='TestBlock1') + data['item_data']['parent'] = block_unowned.id + self.executeBadData(data=data, item=self.owned_id) + + block_owned = self.owned.create_block(title='TestBlock2') + data['item_data']['parent'] = block_owned.id + response = self.executeCreated(data=data, item=self.owned_id) + self.assertEqual(len(response.data['oss']['operations']), 4) + new_operation = response.data['new_operation'] + self.assertEqual(new_operation['parent'], block_owned.id) + + @decl_endpoint('/api/oss/{item}/create-operation', method='post') def test_create_operation_arguments(self): self.populateData() @@ -136,6 +169,7 @@ class TestOssOperations(EndpointTester): self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1)) self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3)) + @decl_endpoint('/api/oss/{item}/create-operation', method='post') def test_create_operation_result(self): self.populateData() @@ -154,10 +188,10 @@ class TestOssOperations(EndpointTester): 'position_y': 1 } response = self.executeCreated(data=data, item=self.owned_id) - self.owned.refresh_from_db() new_operation = response.data['new_operation'] self.assertEqual(new_operation['result'], self.ks1.model.pk) + @decl_endpoint('/api/oss/{item}/create-operation', method='post') def test_create_operation_schema(self): self.populateData() @@ -189,6 +223,7 @@ class TestOssOperations(EndpointTester): self.assertEqual(schema.location, self.owned.model.location) self.assertIn(self.user2, schema.getQ_editors()) + @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation(self): self.executeNotFound(item=self.invalid_id) @@ -214,6 +249,7 @@ class TestOssOperations(EndpointTester): self.assertEqual(len(response.data['operations']), 2) self.assertEqual(len(deleted_items), 0) + @decl_endpoint('/api/oss/{item}/create-input', method='patch') def test_create_input(self): self.populateData() @@ -249,6 +285,7 @@ class TestOssOperations(EndpointTester): data['target'] = self.operation3.pk self.executeBadData(data=data) + @decl_endpoint('/api/oss/{item}/set-input', method='patch') def test_set_input_null(self): self.populateData() @@ -283,6 +320,7 @@ class TestOssOperations(EndpointTester): self.assertEqual(self.operation1.title, self.ks1.model.title) self.assertEqual(self.operation1.description, self.ks1.model.description) + @decl_endpoint('/api/oss/{item}/set-input', method='patch') def test_set_input_change_schema(self): self.populateData() @@ -317,6 +355,7 @@ class TestOssOperations(EndpointTester): 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): self.populateData() @@ -388,6 +427,7 @@ class TestOssOperations(EndpointTester): self.assertEqual(self.operation1.result.title, data['item_data']['title']) self.assertEqual(self.operation1.result.description, data['item_data']['description']) + @decl_endpoint('/api/oss/{item}/update-operation', method='patch') def test_update_operation_invalid_substitution(self): self.populateData() @@ -416,6 +456,7 @@ class TestOssOperations(EndpointTester): } self.executeBadData(data=data, item=self.owned_id) + @decl_endpoint('/api/oss/{item}/execute-operation', method='post') def test_execute_operation(self): self.populateData() 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 a6c526dc..a43b632f 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -16,7 +16,8 @@ class TestOssViewset(EndpointTester): self.unowned_id = self.unowned.model.pk self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE) self.private_id = self.private.model.pk - self.invalid_id = self.private.model.pk + 1337 + self.invalid_id = self.private_id + 1337 + def populateData(self): self.ks1 = RSForm.create( @@ -68,6 +69,7 @@ class TestOssViewset(EndpointTester): 'substitution': self.ks2X1 }]) + @decl_endpoint('/api/oss/{item}/details', method='get') def test_details(self): self.populateData() @@ -117,6 +119,7 @@ class TestOssViewset(EndpointTester): self.executeOK(item=self.unowned_id) self.executeForbidden(item=self.private_id) + @decl_endpoint('/api/oss/{item}/update-layout', method='patch') def test_update_layout(self): self.populateData() @@ -143,6 +146,7 @@ class TestOssViewset(EndpointTester): self.executeForbidden(data=data, item=self.unowned_id) self.executeForbidden(data=data, item=self.private_id) + @decl_endpoint('/api/oss/get-predecessor', method='post') def test_get_predecessor(self): self.populateData() diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index 948d7c5c..e2d13a0f 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -38,6 +38,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev if self.action in [ 'update_layout', 'create_operation', + 'create_block', 'delete_operation', 'create_input', 'set_input', @@ -104,7 +105,10 @@ 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(data=request.data) + serializer = s.OperationCreateSerializer( + data=request.data, + context={'oss': self.get_object()} + ) serializer.is_valid(raise_exception=True) oss = m.OperationSchema(self.get_object()) @@ -148,6 +152,57 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev } ) + @extend_schema( + summary='create block', + tags=['OSS'], + request=s.BlockCreateSerializer(), + responses={ + c.HTTP_201_CREATED: s.NewBlockResponse, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['post'], url_path='create-block') + def create_block(self, request: Request, pk) -> HttpResponse: + ''' Create new block. ''' + serializer = s.BlockCreateSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + oss = m.OperationSchema(self.get_object()) + layout = serializer.validated_data['layout'] + children_blocks: list[m.Block] = serializer.validated_data['children_blocks'] + children_operations: list[m.Operation] = serializer.validated_data['children_operations'] + with transaction.atomic(): + new_block = oss.create_block(**serializer.validated_data['item_data']) + layout['blocks'].append({ + 'id': new_block.pk, + 'x': serializer.validated_data['position_x'], + 'y': serializer.validated_data['position_y'], + 'width': serializer.validated_data['width'], + 'height': serializer.validated_data['height'], + }) + oss.update_layout(layout) + if len(children_blocks) > 0: + for block in children_blocks: + block.parent = new_block + m.Block.objects.bulk_update(children_blocks, ['parent']) + if len(children_operations) > 0: + for operation in children_operations: + operation.parent = new_block + m.Operation.objects.bulk_update(children_operations, ['parent']) + + return Response( + status=c.HTTP_201_CREATED, + data={ + 'new_block': s.BlockSerializer(new_block).data, + 'oss': s.OperationSchemaSerializer(oss.model).data + } + ) + @extend_schema( summary='delete operation', tags=['OSS'], diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_Constituenta.py b/rsconcept/backend/apps/rsform/tests/s_models/t_Constituenta.py index 9cca2f6d..abefbd91 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_Constituenta.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_Constituenta.py @@ -9,6 +9,7 @@ from apps.rsform.models import Constituenta, CstType, RSForm class TestConstituenta(TestCase): ''' Testing Constituenta model. ''' + def setUp(self): self.schema1 = RSForm.create(title='Test1') self.schema2 = RSForm.create(title='Test2') @@ -47,6 +48,7 @@ class TestConstituenta(TestCase): self.assertEqual(cst.definition_resolved, '') self.assertEqual(cst.definition_raw, '') + def test_extract_references(self): cst = Constituenta.objects.create( alias='X1', @@ -57,6 +59,7 @@ class TestConstituenta(TestCase): ) self.assertEqual(cst.extract_references(), set(['X1', 'X2', 'X3', 'X4', 'X5'])) + def text_apply_mapping(self): cst = Constituenta.objects.create( alias='X1', diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py b/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py index 0e2b9994..294077a3 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py @@ -9,6 +9,7 @@ from shared.DBTester import DBTester class TestRSForm(DBTester): ''' Testing RSForm wrapper. ''' + def setUp(self): super().setUp() self.user1 = User.objects.create(username='User1') @@ -103,6 +104,7 @@ class TestRSForm(DBTester): self.assertEqual(x2.schema, self.schema.model) self.assertEqual(x1.order, 0) + def test_create_cst(self): data = { 'alias': 'X3', @@ -194,6 +196,7 @@ class TestRSForm(DBTester): self.assertEqual(d1.definition_raw, '@{DEL|sing}') self.assertEqual(d1.term_raw, '@{X2|plur}') + def test_apply_mapping(self): x1 = self.schema.insert_new('X1') x2 = self.schema.insert_new('X11') diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py index 171aca18..de281dad 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py @@ -138,6 +138,7 @@ class TestRSFormViewset(EndpointTester): self.assertEqual(response.data['typification'], 'LOGIC') self.assertEqual(response.data['valueClass'], 'value') + @decl_endpoint('/api/rsforms/{item}/check-constituenta', method='post') def test_check_constituenta_error(self): self.owned.insert_new('X1') @@ -145,6 +146,7 @@ class TestRSFormViewset(EndpointTester): response = self.executeOK(data=data, item=self.owned_id) self.assertEqual(response.data['parseResult'], False) + @decl_endpoint('/api/rsforms/{item}/resolve', method='post') def test_resolve(self): x1 = self.owned.insert_new( diff --git a/rsconcept/backend/apps/rsform/tests/t_graph.py b/rsconcept/backend/apps/rsform/tests/t_graph.py index 3c99462a..19b69a7b 100644 --- a/rsconcept/backend/apps/rsform/tests/t_graph.py +++ b/rsconcept/backend/apps/rsform/tests/t_graph.py @@ -7,6 +7,7 @@ from apps.rsform.graph import Graph class TestGraph(unittest.TestCase): ''' Test class for graph. ''' + def test_construction(self): graph = Graph() self.assertFalse(graph.contains(1)) @@ -26,6 +27,7 @@ class TestGraph(unittest.TestCase): self.assertTrue(graph.has_edge(1, 3)) self.assertTrue(graph.has_edge(2, 1)) + def test_remove_node(self): graph = Graph({ 1: [2], @@ -39,6 +41,7 @@ class TestGraph(unittest.TestCase): self.assertEqual(graph.outputs[1], []) self.assertEqual(len(graph.outputs), 3) + def test_remove_edge(self): graph = Graph({ 1: [2], @@ -53,6 +56,7 @@ class TestGraph(unittest.TestCase): self.assertEqual(graph.outputs[1], []) graph.remove_edge(1, 2) + def test_expand_outputs(self): graph = Graph({ 1: [2], @@ -67,6 +71,7 @@ class TestGraph(unittest.TestCase): self.assertEqual(graph.expand_outputs([7]), []) self.assertEqual(graph.expand_outputs([2, 5]), [3, 6, 1]) + def test_expand_inputs(self): graph = Graph({ 1: [2], @@ -101,6 +106,7 @@ class TestGraph(unittest.TestCase): 7: [6] }) + def test_topological_order(self): self.assertEqual(Graph().topological_order(), []) graph = Graph({ @@ -122,6 +128,7 @@ class TestGraph(unittest.TestCase): }) self.assertEqual(graph.topological_order(), [5, 3, 2, 4, 1]) + def test_sort_stable(self): graph = Graph({ 1: [2], diff --git a/rsconcept/backend/apps/rsform/tests/t_utils.py b/rsconcept/backend/apps/rsform/tests/t_utils.py index 27f0d1de..d59d3d71 100644 --- a/rsconcept/backend/apps/rsform/tests/t_utils.py +++ b/rsconcept/backend/apps/rsform/tests/t_utils.py @@ -8,6 +8,7 @@ from apps.rsform.utils import apply_pattern, fix_old_references class TestUtils(unittest.TestCase): ''' Test various utility functions. ''' + def test_apply_mapping_patter(self): mapping = {'X101': 'X20'} pattern = re.compile(r'(X[0-9]+)') diff --git a/rsconcept/backend/run_testfile.py b/rsconcept/backend/run_testfile.py index f254b949..ed8a376f 100644 --- a/rsconcept/backend/run_testfile.py +++ b/rsconcept/backend/run_testfile.py @@ -1,9 +1,13 @@ import os +import runpy import sys +# Build the module path from the test file filepath = sys.argv[1] -project_root = os.path.join(os.path.dirname(__file__)) +project_root = os.path.dirname(__file__) relpath = os.path.relpath(filepath, project_root) -module_path = relpath.replace('/', '.').replace('\\', '.').rstrip('.py') +module_path = relpath.replace('/', '.').replace('\\', '.').removesuffix('.py') -os.system(f"python manage.py test {module_path}") +# Run manage.py in-process so breakpoints work +sys.argv = ["manage.py", "test", module_path] +runpy.run_path("manage.py", run_name="__main__") diff --git a/rsconcept/backend/shared/messages.py b/rsconcept/backend/shared/messages.py index ed2963c8..408ecc8c 100644 --- a/rsconcept/backend/shared/messages.py +++ b/rsconcept/backend/shared/messages.py @@ -14,6 +14,18 @@ def operationNotInOSS(title: str): return f'Операция не принадлежит ОСС: {title}' +def parentNotInOSS(): + return f'Родительский блок не принадлежит ОСС' + + +def childNotInOSS(): + return f'Дочерний элемент блок не принадлежит ОСС' + + +def missingArguments(): + return 'Операция не содержит аргументов, при этом содержит отождествления' + + def exteorFileCorrupted(): return 'Файл Экстеор не соответствует ожидаемому формату. Попробуйте сохранить файл в новой версии' diff --git a/rsconcept/frontend/src/features/oss/backend/types.ts b/rsconcept/frontend/src/features/oss/backend/types.ts index c38ee20a..6fe043b8 100644 --- a/rsconcept/frontend/src/features/oss/backend/types.ts +++ b/rsconcept/frontend/src/features/oss/backend/types.ts @@ -5,9 +5,7 @@ import { schemaCstSubstitute } from '@/features/rsform/backend/types'; import { errorMsg } from '@/utils/labels'; -/** - * Represents {@link IOperation} type. - */ +/** Represents {@link IOperation} type. */ export const OperationType = { INPUT: 'input', SYNTHESIS: 'synthesis' @@ -20,10 +18,13 @@ export type ICstSubstituteInfo = z.infer; /** Represents {@link IOperation} data from server. */ export type IOperationDTO = z.infer; +/** Represents {@link IOperation} data from server. */ +export type IBlockDTO = z.infer; + /** Represents backend data for {@link IOperationSchema}. */ export type IOperationSchemaDTO = z.infer; -/** Represents {@link schemaOperation} layout. */ +/** Represents {@link IOperationSchema} layout. */ export type IOssLayout = z.infer; /** Represents {@link IOperation} data, used in creation process. */ @@ -64,15 +65,21 @@ export const schemaOperation = z.strictObject({ id: z.number(), operation_type: schemaOperationType, oss: z.number(), - alias: z.string(), title: z.string(), description: z.string(), - parent: z.number().nullable(), result: z.number().nullable() }); +export const schemaBlock = z.strictObject({ + id: z.number(), + oss: z.number(), + title: z.string(), + description: z.string(), + parent: z.number().nullable() +}); + export const schemaCstSubstituteInfo = schemaCstSubstitute.extend({ operation: z.number(), original_alias: z.string(), @@ -81,20 +88,29 @@ export const schemaCstSubstituteInfo = schemaCstSubstitute.extend({ substitution_term: z.string() }); -export const schemaPosition = z.strictObject({ +export const schemaOperationPosition = z.strictObject({ id: z.number(), x: z.number(), y: z.number() }); +export const schemaBlockPosition = z.strictObject({ + id: z.number(), + x: z.number(), + y: z.number(), + width: z.number(), + height: z.number() +}); + export const schemaOssLayout = z.strictObject({ - operations: z.array(schemaPosition), - blocks: z.array(schemaPosition) + operations: z.array(schemaOperationPosition), + blocks: z.array(schemaBlockPosition) }); export const schemaOperationSchema = schemaLibraryItem.extend({ editors: z.number().array(), operations: z.array(schemaOperation), + blocks: z.array(schemaBlock), layout: schemaOssLayout, arguments: z .object({