From 9170692f3336fb71a59c0fe23edc8ba1d525cff8 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:23:45 +0300 Subject: [PATCH] R: Improve cst creation propagation --- .../backend/apps/oss/models/ChangeManager.py | 87 ++++++++++--------- .../apps/oss/models/PropagationFacade.py | 12 +-- .../backend/apps/rsform/models/RSForm.py | 6 +- .../backend/apps/rsform/views/rsforms.py | 16 ++-- 4 files changed, 65 insertions(+), 56 deletions(-) diff --git a/rsconcept/backend/apps/oss/models/ChangeManager.py b/rsconcept/backend/apps/oss/models/ChangeManager.py index bd7d1d22..524db3e9 100644 --- a/rsconcept/backend/apps/oss/models/ChangeManager.py +++ b/rsconcept/backend/apps/oss/models/ChangeManager.py @@ -22,8 +22,6 @@ from .Substitution import Substitution CstMapping = dict[str, Constituenta] -# TODO: add more variety tests for cascade resolutions model - class ChangeManager: ''' Change propagation wrapper for OSS. ''' @@ -121,25 +119,29 @@ class ChangeManager: self.cache = ChangeManager.Cache(self.oss) - def on_create_cst(self, new_cst: Constituenta, source: RSForm) -> None: + def after_create_cst(self, cst_list: list[Constituenta], source: RSForm) -> None: ''' Trigger cascade resolutions when new constituent is created. ''' self.cache.insert(source) - depend_aliases = new_cst.extract_references() + inserted_aliases = [cst.alias for cst in cst_list] + depend_aliases: set[str] = set() + for new_cst in cst_list: + depend_aliases.update(new_cst.extract_references()) + depend_aliases.difference_update(inserted_aliases) alias_mapping: CstMapping = {} for alias in depend_aliases: cst = source.cache.by_alias.get(alias) if cst is not None: alias_mapping[alias] = cst operation = self.cache.get_operation(source) - self._cascade_create_cst(new_cst, operation, alias_mapping) + self._cascade_create_cst(cst_list, operation, alias_mapping) - def on_change_cst_type(self, target: Constituenta, source: RSForm) -> None: + def after_change_cst_type(self, target: Constituenta, source: RSForm) -> None: ''' Trigger cascade resolutions when constituenta type is changed. ''' self.cache.insert(source) operation = self.cache.get_operation(source) self._cascade_change_cst_type(target.pk, target.cst_type, operation) - def on_update_cst(self, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None: + def after_update_cst(self, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None: ''' Trigger cascade resolutions when constituenta data is changed. ''' self.cache.insert(source) operation = self.cache.get_operation(source) @@ -157,32 +159,7 @@ class ChangeManager: operation = self.cache.get_operation(source) self._cascade_before_delete(target, operation) - def _cascade_before_delete(self, target: list[Constituenta], operation: Operation) -> None: - children = self.cache.graph.outputs[operation.pk] - if len(children) == 0: - return - self.cache.ensure_loaded() - 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 - child_schema.cache.ensure_loaded() - - # TODO: check if substitutions are affected. Undo substitutions before deletion - - child_target_cst = [] - child_target_ids = [] - for cst in target: - successor_id = self.cache.get_successor_for(cst.pk, child_id, ignore_substitution=True) - if successor_id is not None: - child_target_ids.append(successor_id) - child_target_cst.append(child_schema.cache.by_id[successor_id]) - self._cascade_before_delete(child_target_cst, child_operation) - self.cache.remove_cst(child_target_ids, child_id) - child_schema.delete_cst(child_target_cst) - - def _cascade_create_cst(self, prototype: Constituenta, operation: Operation, mapping: CstMapping) -> None: + def _cascade_create_cst(self, cst_list: list[Constituenta], operation: Operation, mapping: CstMapping) -> None: children = self.cache.graph.outputs[operation.pk] if len(children) == 0: return @@ -199,16 +176,17 @@ class ChangeManager: self.cache.ensure_loaded() new_mapping = self._transform_mapping(mapping, child_operation, child_schema) alias_mapping = {alias: cst.alias for alias, cst in new_mapping.items()} - insert_where = self._determine_insert_position(prototype, child_operation, source_schema, child_schema) - new_cst = child_schema.insert_copy([prototype], insert_where, alias_mapping)[0] - new_inheritance = Inheritance.objects.create( - operation=child_operation, - child=new_cst, - parent=prototype - ) - self.cache.insert_inheritance(new_inheritance) + insert_where = self._determine_insert_position(cst_list[0], child_operation, source_schema, child_schema) + new_cst_list = child_schema.insert_copy(cst_list, insert_where, alias_mapping) + for index, cst in enumerate(new_cst_list): + new_inheritance = Inheritance.objects.create( + operation=child_operation, + child=cst, + parent=cst_list[index] + ) + self.cache.insert_inheritance(new_inheritance) new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()} - self._cascade_create_cst(new_cst, child_operation, new_mapping) + self._cascade_create_cst(new_cst_list, child_operation, new_mapping) def _cascade_change_cst_type(self, cst_id: int, ctype: CstType, operation: Operation) -> None: children = self.cache.graph.outputs[operation.pk] @@ -256,6 +234,31 @@ class ChangeManager: new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()} self._cascade_update_cst(successor_id, child_operation, new_data, new_old_data, new_mapping) + def _cascade_before_delete(self, target: list[Constituenta], operation: Operation) -> None: + children = self.cache.graph.outputs[operation.pk] + if len(children) == 0: + return + self.cache.ensure_loaded() + 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 + child_schema.cache.ensure_loaded() + + # TODO: check if substitutions are affected. Undo substitutions before deletion + + child_target_cst = [] + child_target_ids = [] + for cst in target: + successor_id = self.cache.get_successor_for(cst.pk, child_id, ignore_substitution=True) + if successor_id is not None: + child_target_ids.append(successor_id) + child_target_cst.append(child_schema.cache.by_id[successor_id]) + self._cascade_before_delete(child_target_cst, child_operation) + self.cache.remove_cst(child_target_ids, child_id) + child_schema.delete_cst(child_target_cst) + def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSForm) -> CstMapping: if len(mapping) == 0: return mapping diff --git a/rsconcept/backend/apps/oss/models/PropagationFacade.py b/rsconcept/backend/apps/oss/models/PropagationFacade.py index abeef19f..4017a124 100644 --- a/rsconcept/backend/apps/oss/models/PropagationFacade.py +++ b/rsconcept/backend/apps/oss/models/PropagationFacade.py @@ -14,25 +14,25 @@ class PropagationFacade: ''' Change propagation API. ''' @classmethod - def on_create_cst(cls, new_cst: Constituenta, source: RSForm) -> None: + def after_create_cst(cls, new_cst: list[Constituenta], source: RSForm) -> None: ''' Trigger cascade resolutions when new constituent is created. ''' hosts = _get_oss_hosts(source.model) for host in hosts: - ChangeManager(host).on_create_cst(new_cst, source) + ChangeManager(host).after_create_cst(new_cst, source) @classmethod - def on_change_cst_type(cls, target: Constituenta, source: RSForm) -> None: + def after_change_cst_type(cls, target: Constituenta, source: RSForm) -> None: ''' Trigger cascade resolutions when constituenta type is changed. ''' hosts = _get_oss_hosts(source.model) for host in hosts: - ChangeManager(host).on_change_cst_type(target, source) + ChangeManager(host).after_change_cst_type(target, source) @classmethod - def on_update_cst(cls, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None: + def after_update_cst(cls, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None: ''' Trigger cascade resolutions when constituenta data is changed. ''' hosts = _get_oss_hosts(source.model) for host in hosts: - ChangeManager(host).on_update_cst(target, data, old_data, source) + ChangeManager(host).after_update_cst(target, data, old_data, source) @classmethod def before_delete(cls, target: list[Constituenta], source: RSForm) -> None: diff --git a/rsconcept/backend/apps/rsform/models/RSForm.py b/rsconcept/backend/apps/rsform/models/RSForm.py index fbbc70b8..457fc2d9 100644 --- a/rsconcept/backend/apps/rsform/models/RSForm.py +++ b/rsconcept/backend/apps/rsform/models/RSForm.py @@ -448,7 +448,7 @@ class RSForm: data=data ) - def produce_structure(self, target: Constituenta, parse: dict) -> list[int]: + def produce_structure(self, target: Constituenta, parse: dict) -> list[Constituenta]: ''' Add constituents for each structural element of the target. ''' expressions = generate_structure( alias=target.alias, @@ -474,11 +474,11 @@ class RSForm: definition_formal=text, cst_type=cst_type ) - result.append(new_item.pk) + result.append(new_item) free_index = free_index + 1 position = position + 1 - self.cache.clear() + self.cache.insert_multi(result) self.save() return result diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index eeb61034..07ac9960 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -87,7 +87,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr schema = m.RSForm(self._get_item()) with transaction.atomic(): new_cst = schema.create_cst(data, insert_after) - PropagationFacade.on_create_cst(new_cst, schema) + PropagationFacade.after_create_cst([new_cst], schema) return Response( status=c.HTTP_201_CREATED, data={ @@ -118,7 +118,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr data = serializer.validated_data['item_data'] with transaction.atomic(): old_data = schema.update_cst(cst, data) - PropagationFacade.on_update_cst(cst, data, old_data, schema) + PropagationFacade.after_update_cst(cst, data, old_data, schema) return Response( status=c.HTTP_200_OK, data=s.CstSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data @@ -158,11 +158,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr schema = m.RSForm(model) with transaction.atomic(): - result = schema.produce_structure(cst, cst_parse) + new_cst = schema.produce_structure(cst, cst_parse) + PropagationFacade.after_create_cst(new_cst, schema) return Response( status=c.HTTP_200_OK, data={ - 'cst_list': result, + 'cst_list': [cst.pk for cst in new_cst], 'schema': s.RSFormParseSerializer(schema.model).data } ) @@ -195,7 +196,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr schema.apply_mapping(mapping=mapping, change_aliases=False) cst.refresh_from_db() if changed_type: - PropagationFacade.on_change_cst_type(cst, schema) + PropagationFacade.after_change_cst_type(cst, schema) return Response( status=c.HTTP_200_OK, data={ @@ -577,6 +578,8 @@ def inline_synthesis(request: Request) -> HttpResponse: with transaction.atomic(): new_items = receiver.insert_copy(items) + PropagationFacade.after_create_cst(new_items, receiver) + substitutions: list[tuple[m.Constituenta, m.Constituenta]] = [] for substitution in serializer.validated_data['substitutions']: original = cast(m.Constituenta, substitution['original']) @@ -589,6 +592,9 @@ def inline_synthesis(request: Request) -> HttpResponse: replacement = new_items[index] substitutions.append((original, replacement)) receiver.substitute(substitutions) + + # TODO: propagate substitutions + receiver.restore_order() return Response(