From 2bacaf34b65d6b57e7dd804b33d647bde50ed761 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:47:00 +0300 Subject: [PATCH] R: Refactoring cache models pt2 --- .../apps/oss/models/OperationSchemaCached.py | 55 +++++++------- .../apps/oss/models/PropagationFacade.py | 21 +++--- rsconcept/backend/apps/oss/views/oss.py | 72 ++++++++++--------- .../backend/apps/rsform/models/RSForm.py | 11 +++ .../apps/rsform/models/RSFormCached.py | 15 ++-- .../apps/rsform/tests/s_models/t_RSForm.py | 20 ++++++ .../rsform/tests/s_models/t_RSFormCached.py | 2 +- .../backend/apps/rsform/views/rsforms.py | 39 ++++++---- 8 files changed, 138 insertions(+), 97 deletions(-) diff --git a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py index 55d9acbb..9fd63189 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py +++ b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py @@ -1,7 +1,7 @@ ''' Models: OSS API. ''' # pylint: disable=duplicate-code -from typing import Optional, cast +from typing import Optional from cctext import extract_entities from rest_framework.serializers import ValidationError @@ -59,18 +59,17 @@ class OperationSchemaCached: schema = self.cache.get_schema(operation) children = self.cache.graph.outputs[target] if schema is not None and len(children) > 0: + ids = [cst.pk for cst in schema.cache.constituents] if not keep_constituents: - self.before_delete_cst(schema, schema.cache.constituents) + self.before_delete_cst(schema.model.pk, ids) else: - items = schema.cache.constituents - ids = [cst.pk for cst in items] inheritance_to_delete: list[Inheritance] = [] for child_id in children: child_operation = self.cache.operation_by_id[child_id] child_schema = self.cache.get_schema(child_operation) if child_schema is None: continue - self._undo_substitutions_cst(items, child_operation, child_schema) + self._undo_substitutions_cst(ids, child_operation, child_schema) for item in self.cache.inheritance[child_id]: if item.parent_id in ids: inheritance_to_delete.append(item) @@ -91,7 +90,7 @@ class OperationSchemaCached: if old_schema is not None: if has_children: - self.before_delete_cst(old_schema, old_schema.cache.constituents) + self.before_delete_cst(old_schema.model.pk, [cst.pk for cst in old_schema.cache.constituents]) self.cache.remove_schema(old_schema) operation.setQ_result(schema) @@ -238,19 +237,17 @@ class OperationSchemaCached: receiver.model.save(update_fields=['time_update']) return True - def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[Constituenta]): + def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]): ''' Move list of Constituents to destination Schema inheritor. ''' self.cache.ensure_loaded_subs() self.cache.insert_schema(source) self.cache.insert_schema(destination) operation = self.cache.get_operation(destination.model.pk) - self._undo_substitutions_cst(items, operation, destination) - inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items] for item in inheritance_to_delete: self.cache.remove_inheritance(item) - Inheritance.objects.filter(operation_id=operation.pk, parent__in=items).delete() + Inheritance.objects.filter(operation_id=operation.pk, parent_id__in=items).delete() def relocate_up(self, source: RSFormCached, destination: RSFormCached, items: list[Constituenta]) -> list[Constituenta]: @@ -285,6 +282,7 @@ class OperationSchemaCached: exclude: Optional[list[int]] = None ) -> None: ''' Trigger cascade resolutions when new Constituenta is created. ''' + source.cache.ensure_loaded() self.cache.insert_schema(source) inserted_aliases = [cst.alias for cst in cst_list] depend_aliases: set[str] = set() @@ -299,12 +297,12 @@ class OperationSchemaCached: operation = self.cache.get_operation(source.model.pk) self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude) - def after_change_cst_type(self, target: Constituenta) -> None: + def after_change_cst_type(self, schemaID: int, target: int, new_type: CstType) -> None: ''' Trigger cascade resolutions when Constituenta type is changed. ''' - operation = self.cache.get_operation(target.schema.pk) - self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type)) + operation = self.cache.get_operation(schemaID) + self._cascade_change_cst_type(operation.pk, target, new_type) - def after_update_cst(self, source: RSFormCached, target: Constituenta, data: dict, old_data: dict) -> None: + def after_update_cst(self, source: RSFormCached, target: int, data: dict, old_data: dict) -> None: ''' Trigger cascade resolutions when Constituenta data is changed. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) @@ -316,16 +314,15 @@ class OperationSchemaCached: alias_mapping[alias] = cst self._cascade_update_cst( operation=operation.pk, - cst_id=target.pk, + cst_id=target, data=data, old_data=old_data, mapping=alias_mapping ) - def before_delete_cst(self, source: RSFormCached, target: list[Constituenta]) -> None: + def before_delete_cst(self, sourceID: int, target: list[int]) -> None: ''' Trigger cascade resolutions before Constituents are deleted. ''' - self.cache.insert_schema(source) - operation = self.cache.get_operation(source.model.pk) + operation = self.cache.get_operation(sourceID) self._cascade_delete_inherited(operation.pk, target) def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None: @@ -340,7 +337,7 @@ class OperationSchemaCached: for argument in arguments: parent_schema = self.cache.get_schema(argument) if parent_schema is not None: - self._execute_delete_inherited(target.pk, parent_schema.cache.constituents) + self._execute_delete_inherited(target.pk, [cst.pk for cst in parent_schema.cache.constituents]) def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None: ''' Trigger cascade resolutions after arguments are created. ''' @@ -443,7 +440,7 @@ class OperationSchemaCached: new_data = self._prepare_update_data(successor, data, old_data, alias_mapping) if len(new_data) == 0: continue - new_old_data = child_schema.update_cst(successor, new_data) + new_old_data = child_schema.update_cst(successor.pk, new_data) if len(new_old_data) == 0: continue new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()} @@ -455,7 +452,7 @@ class OperationSchemaCached: mapping=new_mapping ) - def _cascade_delete_inherited(self, operation: int, target: list[Constituenta]) -> None: + def _cascade_delete_inherited(self, operation: int, target: list[int]) -> None: children = self.cache.graph.outputs[operation] if len(children) == 0: return @@ -463,18 +460,17 @@ class OperationSchemaCached: for child_id in children: self._execute_delete_inherited(child_id, target) - def _execute_delete_inherited(self, operation_id: int, parent_cst: list[Constituenta]) -> None: + def _execute_delete_inherited(self, operation_id: int, parent_ids: list[int]) -> None: operation = self.cache.operation_by_id[operation_id] schema = self.cache.get_schema(operation) if schema is None: return - self._undo_substitutions_cst(parent_cst, operation, schema) - target_ids = self.cache.get_inheritors_list([cst.pk for cst in parent_cst], operation_id) - target_cst = [schema.cache.by_id[cst_id] for cst_id in target_ids] - self._cascade_delete_inherited(operation_id, target_cst) - if len(target_cst) > 0: + self._undo_substitutions_cst(parent_ids, operation, schema) + target_ids = self.cache.get_inheritors_list(parent_ids, operation_id) + self._cascade_delete_inherited(operation_id, target_ids) + if len(target_ids) > 0: self.cache.remove_cst(operation_id, target_ids) - schema.delete_cst(target_cst) + schema.delete_cst(target_ids) def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None: children = self.cache.graph.outputs[operation.pk] @@ -635,8 +631,7 @@ class OperationSchemaCached: result.append((schema.cache.by_id[new_original_id], schema.cache.by_id[new_substitution_id])) return result - def _undo_substitutions_cst(self, target: list[Constituenta], operation: Operation, schema: RSFormCached) -> None: - target_ids = [cst.pk for cst in target] + def _undo_substitutions_cst(self, target_ids: list[int], operation: Operation, schema: RSFormCached) -> None: to_process = [] for sub in self.cache.substitutions[operation.pk]: if sub.original_id in target_ids or sub.substitution_id in target_ids: diff --git a/rsconcept/backend/apps/oss/models/PropagationFacade.py b/rsconcept/backend/apps/oss/models/PropagationFacade.py index 600d161a..ec670c97 100644 --- a/rsconcept/backend/apps/oss/models/PropagationFacade.py +++ b/rsconcept/backend/apps/oss/models/PropagationFacade.py @@ -2,7 +2,7 @@ from typing import Optional from apps.library.models import LibraryItem, LibraryItemType -from apps.rsform.models import Constituenta, RSFormCached +from apps.rsform.models import Constituenta, CstType, RSFormCached from .OperationSchemaCached import CstSubstitution, OperationSchemaCached @@ -25,17 +25,18 @@ class PropagationFacade: OperationSchemaCached(host).after_create_cst(source, new_cst) @staticmethod - def after_change_cst_type(target: Constituenta, exclude: Optional[list[int]] = None) -> None: + def after_change_cst_type(sourceID: int, target: int, new_type: CstType, + exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions when constituenta type is changed. ''' - hosts = _get_oss_hosts(target.schema.pk) + hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host.pk not in exclude: - OperationSchemaCached(host).after_change_cst_type(target) + OperationSchemaCached(host).after_change_cst_type(sourceID, target, new_type) @staticmethod def after_update_cst( source: RSFormCached, - target: Constituenta, + target: int, data: dict, old_data: dict, exclude: Optional[list[int]] = None @@ -47,13 +48,13 @@ class PropagationFacade: OperationSchemaCached(host).after_update_cst(source, target, data, old_data) @staticmethod - def before_delete_cst(source: RSFormCached, target: list[Constituenta], + def before_delete_cst(sourceID: int, target: list[int], exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions before constituents are deleted. ''' - hosts = _get_oss_hosts(source.model.pk) + hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host.pk not in exclude: - OperationSchemaCached(host).before_delete_cst(source, target) + OperationSchemaCached(host).before_delete_cst(sourceID, target) @staticmethod def before_substitute(sourceID: int, substitutions: CstSubstitution, @@ -75,5 +76,5 @@ class PropagationFacade: if len(hosts) == 0: return - schema = RSFormCached(item) - PropagationFacade.before_delete_cst(schema, list(schema.constituentsQ().order_by('order')), exclude) + ids = list(Constituenta.objects.filter(schema=item).order_by('order').values_list('pk', flat=True)) + PropagationFacade.before_delete_cst(item.pk, ids, exclude) diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index be6b6d05..c82eb99e 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -118,9 +118,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev serializer = s.LayoutSerializer(data=request.data) serializer.is_valid(raise_exception=True) item = self._get_item() + with transaction.atomic(): m.Layout.update_data(pk, serializer.validated_data['data']) item.save(update_fields=['time_update']) + return Response(status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(item).data) @extend_schema( @@ -143,13 +145,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] children_blocks: list[m.Block] = serializer.validated_data['children_blocks'] children_operations: list[m.Operation] = serializer.validated_data['children_operations'] + with transaction.atomic(): + oss = m.OperationSchema(item) new_block = oss.create_block(**serializer.validated_data['item_data']) layout.append({ 'nodeID': 'b' + str(new_block.pk), @@ -197,8 +199,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - block: m.Block = cast(m.Block, serializer.validated_data['target']) + with transaction.atomic(): if 'title' in serializer.validated_data['item_data']: block.title = serializer.validated_data['item_data']['title'] @@ -211,6 +213,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev layout = serializer.validated_data['layout'] m.Layout.update_data(pk, layout) item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(item).data @@ -236,12 +239,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) block = cast(m.Block, serializer.validated_data['target']) layout = serializer.validated_data['layout'] layout = [x for x in layout if x['nodeID'] != 'b' + str(block.pk)] + with transaction.atomic(): + oss = m.OperationSchema(item) oss.delete_block(block) m.Layout.update_data(pk, layout) item.save(update_fields=['time_update']) @@ -271,8 +274,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - layout = serializer.validated_data['layout'] + with transaction.atomic(): m.Layout.update_data(pk, layout) for operation in serializer.validated_data['operations']: @@ -308,13 +311,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] data = serializer.validated_data['item_data'] data['operation_type'] = m.OperationType.INPUT + with transaction.atomic(): + oss = m.OperationSchema(item) new_operation = oss.create_operation(**serializer.validated_data['item_data']) layout.append({ 'nodeID': 'o' + str(new_operation.pk), @@ -356,9 +359,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] + with transaction.atomic(): source = cast(m.Operation, serializer.validated_data['source_operation']) alias = '+' + source.alias @@ -426,15 +429,15 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] data = serializer.validated_data['item_data'] data['operation_type'] = m.OperationType.INPUT if not serializer.validated_data['clone_source']: data['result'] = serializer.validated_data['source'] + with transaction.atomic(): + oss = m.OperationSchema(item) new_operation = oss.create_operation(**serializer.validated_data['item_data']) layout.append({ 'nodeID': 'o' + str(new_operation.pk), @@ -481,11 +484,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] + with transaction.atomic(): + oss = m.OperationSchema(item) target = cast(m.Operation, serializer.validated_data['target']) new_operation = oss.create_reference(target) layout.append({ @@ -526,13 +529,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] position = serializer.validated_data['position'] data = serializer.validated_data['item_data'] data['operation_type'] = m.OperationType.SYNTHESIS + with transaction.atomic(): + oss = m.OperationSchema(item) new_operation = oss.create_operation(**serializer.validated_data['item_data']) layout.append({ 'nodeID': 'o' + str(new_operation.pk), @@ -575,10 +578,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) - oss = m.OperationSchemaCached(item) + with transaction.atomic(): + oss = m.OperationSchemaCached(item) if 'layout' in serializer.validated_data: layout = serializer.validated_data['layout'] m.Layout.update_data(pk, layout) @@ -630,13 +633,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchemaCached(item) operation = cast(m.Operation, serializer.validated_data['target']) old_schema = operation.result layout = serializer.validated_data['layout'] layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] + with transaction.atomic(): + oss = m.OperationSchemaCached(item) oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) m.Layout.update_data(pk, layout) if old_schema is not None: @@ -673,12 +676,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - - oss = m.OperationSchemaCached(item) operation = cast(m.Operation, serializer.validated_data['target']) layout = serializer.validated_data['layout'] layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] + with transaction.atomic(): + oss = m.OperationSchemaCached(item) m.Layout.update_data(pk, layout) oss.delete_reference(operation, serializer.validated_data['keep_connections']) item.save(update_fields=['time_update']) @@ -708,7 +711,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) if len(operation.getQ_arguments()) > 0: raise serializers.ValidationError({ @@ -718,10 +720,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev raise serializers.ValidationError({ 'target': msg.operationResultNotEmpty(operation.alias) }) - - oss = m.OperationSchema(item) layout = serializer.validated_data['layout'] + with transaction.atomic(): + oss = m.OperationSchema(item) m.Layout.update_data(pk, layout) schema = oss.create_input(operation) item.save(update_fields=['time_update']) @@ -769,9 +771,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev raise serializers.ValidationError({ 'input': msg.operationInputAlreadyConnected() }) - oss = m.OperationSchemaCached(item) old_schema = target_operation.result + with transaction.atomic(): + oss = m.OperationSchemaCached(item) if old_schema is not None: if old_schema.is_synced(item): old_schema.visible = True @@ -805,7 +808,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev context={'oss': item} ) serializer.is_valid(raise_exception=True) - operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) if operation.operation_type != m.OperationType.SYNTHESIS: raise serializers.ValidationError({ @@ -815,10 +817,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev raise serializers.ValidationError({ 'target': msg.operationResultNotEmpty(operation.alias) }) - - oss = m.OperationSchemaCached(item) layout = serializer.validated_data['layout'] + with transaction.atomic(): + oss = m.OperationSchemaCached(item) oss.execute_operation(operation) m.Layout.update_data(pk, layout) item.save(update_fields=['time_update']) @@ -877,17 +879,17 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ''' Relocate constituents from one schema to another. ''' serializer = s.RelocateConstituentsSerializer(data=request.data) serializer.is_valid(raise_exception=True) - data = serializer.validated_data - oss = m.OperationSchemaCached(LibraryItem.objects.get(pk=data['oss'])) - source = RSFormCached(LibraryItem.objects.get(pk=data['source'])) - destination = RSFormCached(LibraryItem.objects.get(pk=data['destination'])) + ids = [cst.pk for cst in data['items']] with transaction.atomic(): + oss = m.OperationSchemaCached(LibraryItem.objects.get(pk=data['oss'])) + source = RSFormCached(LibraryItem.objects.get(pk=data['source'])) + destination = RSFormCached(LibraryItem.objects.get(pk=data['destination'])) if data['move_down']: - oss.relocate_down(source, destination, data['items']) - m.PropagationFacade.before_delete_cst(source, data['items']) - source.delete_cst(data['items']) + oss.relocate_down(source, destination, ids) + m.PropagationFacade.before_delete_cst(data['source'], ids) + source.delete_cst(ids) else: new_items = oss.relocate_up(source, destination, data['items']) m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk]) diff --git a/rsconcept/backend/apps/rsform/models/RSForm.py b/rsconcept/backend/apps/rsform/models/RSForm.py index e52f6367..3bc0fcdf 100644 --- a/rsconcept/backend/apps/rsform/models/RSForm.py +++ b/rsconcept/backend/apps/rsform/models/RSForm.py @@ -233,6 +233,17 @@ class RSForm: count_bot += 1 Constituenta.objects.bulk_update(cst_list, ['order']) + def delete_cst(self, target: list[Constituenta]) -> None: + ''' Delete multiple constituents. ''' + ids = [cst.pk for cst in target] + mapping = {cst.alias: DELETED_ALIAS for cst in target} + Constituenta.objects.filter(pk__in=ids).delete() + all_cst = Constituenta.objects.filter(schema=self.model).only( + 'alias', 'definition_formal', 'term_raw', 'definition_raw', 'order' + ).order_by('order') + RSForm.apply_mapping(mapping, all_cst, change_aliases=False) + RSForm.save_order(all_cst) + def reset_aliases(self) -> None: ''' Recreate all aliases based on constituents order. ''' bases = cast(dict[str, int], {}) diff --git a/rsconcept/backend/apps/rsform/models/RSFormCached.py b/rsconcept/backend/apps/rsform/models/RSFormCached.py index 3553505a..2d20d8f6 100644 --- a/rsconcept/backend/apps/rsform/models/RSFormCached.py +++ b/rsconcept/backend/apps/rsform/models/RSFormCached.py @@ -153,12 +153,12 @@ class RSFormCached: return result # pylint: disable=too-many-branches - def update_cst(self, target: Constituenta, data: dict) -> dict: + def update_cst(self, target: int, data: dict) -> dict: ''' Update persistent attributes of a given constituenta. Return old values. ''' self.cache.ensure_loaded_terms() - cst = self.cache.by_id.get(target.pk) + cst = self.cache.by_id.get(target) if cst is None: - raise ValidationError(msg.constituentaNotInRSform(target.alias)) + raise ValidationError(msg.constituentaNotInRSform(str(target))) old_data = {} term_changed = False @@ -211,13 +211,14 @@ class RSFormCached: ) return old_data - def delete_cst(self, target: Iterable[Constituenta]) -> None: + def delete_cst(self, target: list[int]) -> None: ''' Delete multiple constituents. ''' - mapping = {cst.alias: DELETED_ALIAS for cst in target} self.cache.ensure_loaded() - self.cache.remove_multi(target) + cst_list = [self.cache.by_id[cst_id] for cst_id in target] + mapping = {cst.alias: DELETED_ALIAS for cst in cst_list} + self.cache.remove_multi(cst_list) self.apply_mapping(mapping) - Constituenta.objects.filter(pk__in=[cst.pk for cst in target]).delete() + Constituenta.objects.filter(pk__in=target).delete() RSForm.save_order(self.cache.constituents) def substitute(self, substitutions: list[tuple[Constituenta, Constituenta]]) -> None: 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 6c299394..d292451a 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_RSForm.py @@ -97,3 +97,23 @@ class TestRSForm(DBTester): x2.refresh_from_db() self.assertEqual(x1.order, 1) self.assertEqual(x2.order, 0) + + def test_delete_cst(self): + x1 = self.schema.insert_last('X1') + x2 = self.schema.insert_last('X2') + d1 = self.schema.insert_last( + alias='D1', + definition_formal='X1 = X2', + definition_raw='@{X1|sing}', + term_raw='@{X2|plur}' + ) + + self.schema.delete_cst([x1]) + x2.refresh_from_db() + d1.refresh_from_db() + self.assertEqual(self.schema.constituentsQ().count(), 2) + self.assertEqual(x2.order, 0) + self.assertEqual(d1.order, 1) + self.assertEqual(d1.definition_formal, 'DEL = X2') + self.assertEqual(d1.definition_raw, '@{DEL|sing}') + self.assertEqual(d1.term_raw, '@{X2|plur}') diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py b/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py index 8c4a9beb..e4fa8191 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py @@ -118,7 +118,7 @@ class TestRSFormCached(DBTester): term_raw='@{X2|plur}' ) - self.schema.delete_cst([x1]) + self.schema.delete_cst([x1.pk]) x2.refresh_from_db() d1.refresh_from_db() self.assertEqual(self.schema.constituentsQ().count(), 2) diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index 3019c5e2..7ebd0a9e 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -86,11 +86,13 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr insert_after = None else: insert_after = data['insert_after'] - schema = m.RSFormCached(item) + with transaction.atomic(): + schema = m.RSFormCached(item) new_cst = schema.create_cst(data, insert_after) PropagationFacade.after_create_cst(schema, [new_cst]) item.save(update_fields=['time_update']) + return Response( status=c.HTTP_201_CREATED, data={ @@ -117,11 +119,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer = s.CstUpdateSerializer(data=request.data, partial=True, context={'schema': item}) serializer.is_valid(raise_exception=True) cst = cast(m.Constituenta, serializer.validated_data['target']) - schema = m.RSFormCached(item) data = serializer.validated_data['item_data'] + with transaction.atomic(): - old_data = schema.update_cst(cst, data) - PropagationFacade.after_update_cst(schema, cst, data, old_data) + schema = m.RSFormCached(item) + old_data = schema.update_cst(cst.pk, data) + PropagationFacade.after_update_cst(schema, cst.pk, data, old_data) if 'alias' in data and data['alias'] != cst.alias: cst.refresh_from_db() changed_type = 'cst_type' in data and cst.cst_type != data['cst_type'] @@ -131,9 +134,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr cst.cst_type = data['cst_type'] cst.save() schema.apply_mapping(mapping=mapping, change_aliases=False) - cst.refresh_from_db() if changed_type: - PropagationFacade.after_change_cst_type(cst) + PropagationFacade.after_change_cst_type(item.pk, cst.pk, cast(m.CstType, cst.cst_type)) item.save(update_fields=['time_update']) return Response( status=c.HTTP_200_OK, @@ -202,8 +204,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr data={f'{cst.pk}': msg.constituentaNoStructure()} ) - schema = m.RSFormCached(item) with transaction.atomic(): + schema = m.RSFormCached(item) new_cst = schema.produce_structure(cst, cst_parse) PropagationFacade.after_create_cst(schema, new_cst) item.save(update_fields=['time_update']) @@ -215,7 +217,6 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr } ) - @extend_schema( summary='execute substitutions', tags=['RSForm'], @@ -236,9 +237,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr context={'schema': item} ) serializer.is_valid(raise_exception=True) - schema = m.RSForm(item) substitutions: list[tuple[m.Constituenta, m.Constituenta]] = [] + with transaction.atomic(): + schema = m.RSForm(item) for substitution in serializer.validated_data['substitutions']: original = cast(m.Constituenta, substitution['original']) replacement = cast(m.Constituenta, substitution['substitution']) @@ -246,6 +248,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr PropagationFacade.before_substitute(item.pk, substitutions) schema.substitute(substitutions) item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, data=s.RSFormParseSerializer(item).data @@ -272,14 +275,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) serializer.is_valid(raise_exception=True) cst_list: list[m.Constituenta] = serializer.validated_data['items'] - schema = m.RSFormCached(item) + with transaction.atomic(): - PropagationFacade.before_delete_cst(schema, cst_list) + schema = m.RSForm(item) + PropagationFacade.before_delete_cst(item.pk, [cst.pk for cst in cst_list]) schema.delete_cst(cst_list) item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, - data=s.RSFormParseSerializer(schema.model).data + data=s.RSFormParseSerializer(item).data ) @extend_schema( @@ -302,13 +307,15 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr context={'schema': item} ) serializer.is_valid(raise_exception=True) - schema = m.RSForm(item) + with transaction.atomic(): + schema = m.RSForm(item) schema.move_cst( target=serializer.validated_data['items'], destination=serializer.validated_data['move_to'] ) item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, data=s.RSFormParseSerializer(item).data @@ -328,10 +335,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr def reset_aliases(self, request: Request, pk) -> HttpResponse: ''' Endpoint: Recreate all aliases based on order. ''' item = self._get_item() - schema = m.RSForm(item) + with transaction.atomic(): + schema = m.RSForm(item) schema.reset_aliases() item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, data=s.RSFormParseSerializer(item).data @@ -351,9 +360,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr def restore_order(self, request: Request, pk) -> HttpResponse: ''' Endpoint: Restore order based on types and Term graph. ''' item = self._get_item() + with transaction.atomic(): m.OrderManager(m.RSFormCached(item)).restore_order() item.save(update_fields=['time_update']) + return Response( status=c.HTTP_200_OK, data=s.RSFormParseSerializer(item).data