R: Refactoring cache models pt2
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Backend CI / notify-failure (push) Has been cancelled

This commit is contained in:
Ivan 2025-08-03 15:47:00 +03:00
parent 25ec175d79
commit 2bacaf34b6
8 changed files with 138 additions and 97 deletions

View File

@ -1,7 +1,7 @@
''' Models: OSS API. ''' ''' Models: OSS API. '''
# pylint: disable=duplicate-code # pylint: disable=duplicate-code
from typing import Optional, cast from typing import Optional
from cctext import extract_entities from cctext import extract_entities
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
@ -59,18 +59,17 @@ class OperationSchemaCached:
schema = self.cache.get_schema(operation) schema = self.cache.get_schema(operation)
children = self.cache.graph.outputs[target] children = self.cache.graph.outputs[target]
if schema is not None and len(children) > 0: if schema is not None and len(children) > 0:
ids = [cst.pk for cst in schema.cache.constituents]
if not keep_constituents: if not keep_constituents:
self.before_delete_cst(schema, schema.cache.constituents) self.before_delete_cst(schema.model.pk, ids)
else: else:
items = schema.cache.constituents
ids = [cst.pk for cst in items]
inheritance_to_delete: list[Inheritance] = [] inheritance_to_delete: list[Inheritance] = []
for child_id in children: for child_id in children:
child_operation = self.cache.operation_by_id[child_id] child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation) child_schema = self.cache.get_schema(child_operation)
if child_schema is None: if child_schema is None:
continue 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]: for item in self.cache.inheritance[child_id]:
if item.parent_id in ids: if item.parent_id in ids:
inheritance_to_delete.append(item) inheritance_to_delete.append(item)
@ -91,7 +90,7 @@ class OperationSchemaCached:
if old_schema is not None: if old_schema is not None:
if has_children: 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) self.cache.remove_schema(old_schema)
operation.setQ_result(schema) operation.setQ_result(schema)
@ -238,19 +237,17 @@ class OperationSchemaCached:
receiver.model.save(update_fields=['time_update']) receiver.model.save(update_fields=['time_update'])
return True 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. ''' ''' Move list of Constituents to destination Schema inheritor. '''
self.cache.ensure_loaded_subs() self.cache.ensure_loaded_subs()
self.cache.insert_schema(source) self.cache.insert_schema(source)
self.cache.insert_schema(destination) self.cache.insert_schema(destination)
operation = self.cache.get_operation(destination.model.pk) operation = self.cache.get_operation(destination.model.pk)
self._undo_substitutions_cst(items, operation, destination) 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] inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items]
for item in inheritance_to_delete: for item in inheritance_to_delete:
self.cache.remove_inheritance(item) 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, def relocate_up(self, source: RSFormCached, destination: RSFormCached,
items: list[Constituenta]) -> list[Constituenta]: items: list[Constituenta]) -> list[Constituenta]:
@ -285,6 +282,7 @@ class OperationSchemaCached:
exclude: Optional[list[int]] = None exclude: Optional[list[int]] = None
) -> None: ) -> None:
''' Trigger cascade resolutions when new Constituenta is created. ''' ''' Trigger cascade resolutions when new Constituenta is created. '''
source.cache.ensure_loaded()
self.cache.insert_schema(source) self.cache.insert_schema(source)
inserted_aliases = [cst.alias for cst in cst_list] inserted_aliases = [cst.alias for cst in cst_list]
depend_aliases: set[str] = set() depend_aliases: set[str] = set()
@ -299,12 +297,12 @@ class OperationSchemaCached:
operation = self.cache.get_operation(source.model.pk) operation = self.cache.get_operation(source.model.pk)
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude) 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. ''' ''' Trigger cascade resolutions when Constituenta type is changed. '''
operation = self.cache.get_operation(target.schema.pk) operation = self.cache.get_operation(schemaID)
self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type)) 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. ''' ''' Trigger cascade resolutions when Constituenta data is changed. '''
self.cache.insert_schema(source) self.cache.insert_schema(source)
operation = self.cache.get_operation(source.model.pk) operation = self.cache.get_operation(source.model.pk)
@ -316,16 +314,15 @@ class OperationSchemaCached:
alias_mapping[alias] = cst alias_mapping[alias] = cst
self._cascade_update_cst( self._cascade_update_cst(
operation=operation.pk, operation=operation.pk,
cst_id=target.pk, cst_id=target,
data=data, data=data,
old_data=old_data, old_data=old_data,
mapping=alias_mapping 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. ''' ''' Trigger cascade resolutions before Constituents are deleted. '''
self.cache.insert_schema(source) operation = self.cache.get_operation(sourceID)
operation = self.cache.get_operation(source.model.pk)
self._cascade_delete_inherited(operation.pk, target) self._cascade_delete_inherited(operation.pk, target)
def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None: def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None:
@ -340,7 +337,7 @@ class OperationSchemaCached:
for argument in arguments: for argument in arguments:
parent_schema = self.cache.get_schema(argument) parent_schema = self.cache.get_schema(argument)
if parent_schema is not None: 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: def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
''' Trigger cascade resolutions after arguments are created. ''' ''' 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) new_data = self._prepare_update_data(successor, data, old_data, alias_mapping)
if len(new_data) == 0: if len(new_data) == 0:
continue 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: if len(new_old_data) == 0:
continue continue
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()} new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
@ -455,7 +452,7 @@ class OperationSchemaCached:
mapping=new_mapping 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] children = self.cache.graph.outputs[operation]
if len(children) == 0: if len(children) == 0:
return return
@ -463,18 +460,17 @@ class OperationSchemaCached:
for child_id in children: for child_id in children:
self._execute_delete_inherited(child_id, target) 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] operation = self.cache.operation_by_id[operation_id]
schema = self.cache.get_schema(operation) schema = self.cache.get_schema(operation)
if schema is None: if schema is None:
return return
self._undo_substitutions_cst(parent_cst, operation, schema) self._undo_substitutions_cst(parent_ids, operation, schema)
target_ids = self.cache.get_inheritors_list([cst.pk for cst in parent_cst], operation_id) target_ids = self.cache.get_inheritors_list(parent_ids, operation_id)
target_cst = [schema.cache.by_id[cst_id] for cst_id in target_ids] self._cascade_delete_inherited(operation_id, target_ids)
self._cascade_delete_inherited(operation_id, target_cst) if len(target_ids) > 0:
if len(target_cst) > 0:
self.cache.remove_cst(operation_id, target_ids) 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: def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
children = self.cache.graph.outputs[operation.pk] 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])) result.append((schema.cache.by_id[new_original_id], schema.cache.by_id[new_substitution_id]))
return result return result
def _undo_substitutions_cst(self, target: list[Constituenta], operation: Operation, schema: RSFormCached) -> None: def _undo_substitutions_cst(self, target_ids: list[int], operation: Operation, schema: RSFormCached) -> None:
target_ids = [cst.pk for cst in target]
to_process = [] to_process = []
for sub in self.cache.substitutions[operation.pk]: for sub in self.cache.substitutions[operation.pk]:
if sub.original_id in target_ids or sub.substitution_id in target_ids: if sub.original_id in target_ids or sub.substitution_id in target_ids:

View File

@ -2,7 +2,7 @@
from typing import Optional from typing import Optional
from apps.library.models import LibraryItem, LibraryItemType 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 from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
@ -25,17 +25,18 @@ class PropagationFacade:
OperationSchemaCached(host).after_create_cst(source, new_cst) OperationSchemaCached(host).after_create_cst(source, new_cst)
@staticmethod @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. ''' ''' Trigger cascade resolutions when constituenta type is changed. '''
hosts = _get_oss_hosts(target.schema.pk) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: 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 @staticmethod
def after_update_cst( def after_update_cst(
source: RSFormCached, source: RSFormCached,
target: Constituenta, target: int,
data: dict, data: dict,
old_data: dict, old_data: dict,
exclude: Optional[list[int]] = None exclude: Optional[list[int]] = None
@ -47,13 +48,13 @@ class PropagationFacade:
OperationSchemaCached(host).after_update_cst(source, target, data, old_data) OperationSchemaCached(host).after_update_cst(source, target, data, old_data)
@staticmethod @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: exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before constituents are deleted. ''' ''' Trigger cascade resolutions before constituents are deleted. '''
hosts = _get_oss_hosts(source.model.pk) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: 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 @staticmethod
def before_substitute(sourceID: int, substitutions: CstSubstitution, def before_substitute(sourceID: int, substitutions: CstSubstitution,
@ -75,5 +76,5 @@ class PropagationFacade:
if len(hosts) == 0: if len(hosts) == 0:
return return
schema = RSFormCached(item) ids = list(Constituenta.objects.filter(schema=item).order_by('order').values_list('pk', flat=True))
PropagationFacade.before_delete_cst(schema, list(schema.constituentsQ().order_by('order')), exclude) PropagationFacade.before_delete_cst(item.pk, ids, exclude)

View File

@ -118,9 +118,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
serializer = s.LayoutSerializer(data=request.data) serializer = s.LayoutSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
item = self._get_item() item = self._get_item()
with transaction.atomic(): with transaction.atomic():
m.Layout.update_data(pk, serializer.validated_data['data']) m.Layout.update_data(pk, serializer.validated_data['data'])
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response(status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(item).data) return Response(status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(item).data)
@extend_schema( @extend_schema(
@ -143,13 +145,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
children_blocks: list[m.Block] = serializer.validated_data['children_blocks'] children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
children_operations: list[m.Operation] = serializer.validated_data['children_operations'] children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
new_block = oss.create_block(**serializer.validated_data['item_data']) new_block = oss.create_block(**serializer.validated_data['item_data'])
layout.append({ layout.append({
'nodeID': 'b' + str(new_block.pk), 'nodeID': 'b' + str(new_block.pk),
@ -197,8 +199,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
block: m.Block = cast(m.Block, serializer.validated_data['target']) block: m.Block = cast(m.Block, serializer.validated_data['target'])
with transaction.atomic(): with transaction.atomic():
if 'title' in serializer.validated_data['item_data']: if 'title' in serializer.validated_data['item_data']:
block.title = serializer.validated_data['item_data']['title'] 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'] layout = serializer.validated_data['layout']
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(item).data data=s.OperationSchemaSerializer(item).data
@ -236,12 +239,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
block = cast(m.Block, serializer.validated_data['target']) block = cast(m.Block, serializer.validated_data['target'])
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
layout = [x for x in layout if x['nodeID'] != 'b' + str(block.pk)] layout = [x for x in layout if x['nodeID'] != 'b' + str(block.pk)]
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
oss.delete_block(block) oss.delete_block(block)
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -271,8 +274,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
with transaction.atomic(): with transaction.atomic():
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
for operation in serializer.validated_data['operations']: for operation in serializer.validated_data['operations']:
@ -308,13 +311,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
data = serializer.validated_data['item_data'] data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.INPUT data['operation_type'] = m.OperationType.INPUT
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
new_operation = oss.create_operation(**serializer.validated_data['item_data']) new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({ layout.append({
'nodeID': 'o' + str(new_operation.pk), 'nodeID': 'o' + str(new_operation.pk),
@ -356,9 +359,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
with transaction.atomic(): with transaction.atomic():
source = cast(m.Operation, serializer.validated_data['source_operation']) source = cast(m.Operation, serializer.validated_data['source_operation'])
alias = '+' + source.alias alias = '+' + source.alias
@ -426,15 +429,15 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
data = serializer.validated_data['item_data'] data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.INPUT data['operation_type'] = m.OperationType.INPUT
if not serializer.validated_data['clone_source']: if not serializer.validated_data['clone_source']:
data['result'] = serializer.validated_data['source'] data['result'] = serializer.validated_data['source']
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
new_operation = oss.create_operation(**serializer.validated_data['item_data']) new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({ layout.append({
'nodeID': 'o' + str(new_operation.pk), 'nodeID': 'o' + str(new_operation.pk),
@ -481,11 +484,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
target = cast(m.Operation, serializer.validated_data['target']) target = cast(m.Operation, serializer.validated_data['target'])
new_operation = oss.create_reference(target) new_operation = oss.create_reference(target)
layout.append({ layout.append({
@ -526,13 +529,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
position = serializer.validated_data['position'] position = serializer.validated_data['position']
data = serializer.validated_data['item_data'] data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.SYNTHESIS data['operation_type'] = m.OperationType.SYNTHESIS
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
new_operation = oss.create_operation(**serializer.validated_data['item_data']) new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({ layout.append({
'nodeID': 'o' + str(new_operation.pk), 'nodeID': 'o' + str(new_operation.pk),
@ -575,10 +578,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
oss = m.OperationSchemaCached(item)
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item)
if 'layout' in serializer.validated_data: if 'layout' in serializer.validated_data:
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
@ -630,13 +633,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchemaCached(item)
operation = cast(m.Operation, serializer.validated_data['target']) operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result old_schema = operation.result
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
if old_schema is not None: if old_schema is not None:
@ -673,12 +676,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
oss = m.OperationSchemaCached(item)
operation = cast(m.Operation, serializer.validated_data['target']) operation = cast(m.Operation, serializer.validated_data['target'])
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item)
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
oss.delete_reference(operation, serializer.validated_data['keep_connections']) oss.delete_reference(operation, serializer.validated_data['keep_connections'])
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -708,7 +711,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
if len(operation.getQ_arguments()) > 0: if len(operation.getQ_arguments()) > 0:
raise serializers.ValidationError({ raise serializers.ValidationError({
@ -718,10 +720,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
raise serializers.ValidationError({ raise serializers.ValidationError({
'target': msg.operationResultNotEmpty(operation.alias) 'target': msg.operationResultNotEmpty(operation.alias)
}) })
oss = m.OperationSchema(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchema(item)
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
schema = oss.create_input(operation) schema = oss.create_input(operation)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -769,9 +771,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
raise serializers.ValidationError({ raise serializers.ValidationError({
'input': msg.operationInputAlreadyConnected() 'input': msg.operationInputAlreadyConnected()
}) })
oss = m.OperationSchemaCached(item)
old_schema = target_operation.result old_schema = target_operation.result
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item)
if old_schema is not None: if old_schema is not None:
if old_schema.is_synced(item): if old_schema.is_synced(item):
old_schema.visible = True old_schema.visible = True
@ -805,7 +808,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
context={'oss': item} context={'oss': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
if operation.operation_type != m.OperationType.SYNTHESIS: if operation.operation_type != m.OperationType.SYNTHESIS:
raise serializers.ValidationError({ raise serializers.ValidationError({
@ -815,10 +817,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
raise serializers.ValidationError({ raise serializers.ValidationError({
'target': msg.operationResultNotEmpty(operation.alias) 'target': msg.operationResultNotEmpty(operation.alias)
}) })
oss = m.OperationSchemaCached(item)
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss.execute_operation(operation) oss.execute_operation(operation)
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
item.save(update_fields=['time_update']) 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. ''' ''' Relocate constituents from one schema to another. '''
serializer = s.RelocateConstituentsSerializer(data=request.data) serializer = s.RelocateConstituentsSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
data = serializer.validated_data data = serializer.validated_data
oss = m.OperationSchemaCached(LibraryItem.objects.get(pk=data['oss'])) ids = [cst.pk for cst in data['items']]
source = RSFormCached(LibraryItem.objects.get(pk=data['source']))
destination = RSFormCached(LibraryItem.objects.get(pk=data['destination']))
with transaction.atomic(): 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']: if data['move_down']:
oss.relocate_down(source, destination, data['items']) oss.relocate_down(source, destination, ids)
m.PropagationFacade.before_delete_cst(source, data['items']) m.PropagationFacade.before_delete_cst(data['source'], ids)
source.delete_cst(data['items']) source.delete_cst(ids)
else: else:
new_items = oss.relocate_up(source, destination, data['items']) new_items = oss.relocate_up(source, destination, data['items'])
m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk]) m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk])

View File

@ -233,6 +233,17 @@ class RSForm:
count_bot += 1 count_bot += 1
Constituenta.objects.bulk_update(cst_list, ['order']) 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: def reset_aliases(self) -> None:
''' Recreate all aliases based on constituents order. ''' ''' Recreate all aliases based on constituents order. '''
bases = cast(dict[str, int], {}) bases = cast(dict[str, int], {})

View File

@ -153,12 +153,12 @@ class RSFormCached:
return result return result
# pylint: disable=too-many-branches # 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. ''' ''' Update persistent attributes of a given constituenta. Return old values. '''
self.cache.ensure_loaded_terms() self.cache.ensure_loaded_terms()
cst = self.cache.by_id.get(target.pk) cst = self.cache.by_id.get(target)
if cst is None: if cst is None:
raise ValidationError(msg.constituentaNotInRSform(target.alias)) raise ValidationError(msg.constituentaNotInRSform(str(target)))
old_data = {} old_data = {}
term_changed = False term_changed = False
@ -211,13 +211,14 @@ class RSFormCached:
) )
return old_data return old_data
def delete_cst(self, target: Iterable[Constituenta]) -> None: def delete_cst(self, target: list[int]) -> None:
''' Delete multiple constituents. ''' ''' Delete multiple constituents. '''
mapping = {cst.alias: DELETED_ALIAS for cst in target}
self.cache.ensure_loaded() 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) 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) RSForm.save_order(self.cache.constituents)
def substitute(self, substitutions: list[tuple[Constituenta, Constituenta]]) -> None: def substitute(self, substitutions: list[tuple[Constituenta, Constituenta]]) -> None:

View File

@ -97,3 +97,23 @@ class TestRSForm(DBTester):
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 0) 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}')

View File

@ -118,7 +118,7 @@ class TestRSFormCached(DBTester):
term_raw='@{X2|plur}' term_raw='@{X2|plur}'
) )
self.schema.delete_cst([x1]) self.schema.delete_cst([x1.pk])
x2.refresh_from_db() x2.refresh_from_db()
d1.refresh_from_db() d1.refresh_from_db()
self.assertEqual(self.schema.constituentsQ().count(), 2) self.assertEqual(self.schema.constituentsQ().count(), 2)

View File

@ -86,11 +86,13 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
insert_after = None insert_after = None
else: else:
insert_after = data['insert_after'] insert_after = data['insert_after']
schema = m.RSFormCached(item)
with transaction.atomic(): with transaction.atomic():
schema = m.RSFormCached(item)
new_cst = schema.create_cst(data, insert_after) new_cst = schema.create_cst(data, insert_after)
PropagationFacade.after_create_cst(schema, [new_cst]) PropagationFacade.after_create_cst(schema, [new_cst])
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_201_CREATED, status=c.HTTP_201_CREATED,
data={ 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 = s.CstUpdateSerializer(data=request.data, partial=True, context={'schema': item})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cst = cast(m.Constituenta, serializer.validated_data['target']) cst = cast(m.Constituenta, serializer.validated_data['target'])
schema = m.RSFormCached(item)
data = serializer.validated_data['item_data'] data = serializer.validated_data['item_data']
with transaction.atomic(): with transaction.atomic():
old_data = schema.update_cst(cst, data) schema = m.RSFormCached(item)
PropagationFacade.after_update_cst(schema, cst, data, old_data) 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: if 'alias' in data and data['alias'] != cst.alias:
cst.refresh_from_db() cst.refresh_from_db()
changed_type = 'cst_type' in data and cst.cst_type != data['cst_type'] 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.cst_type = data['cst_type']
cst.save() cst.save()
schema.apply_mapping(mapping=mapping, change_aliases=False) schema.apply_mapping(mapping=mapping, change_aliases=False)
cst.refresh_from_db()
if changed_type: 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']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
@ -202,8 +204,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
data={f'{cst.pk}': msg.constituentaNoStructure()} data={f'{cst.pk}': msg.constituentaNoStructure()}
) )
schema = m.RSFormCached(item)
with transaction.atomic(): with transaction.atomic():
schema = m.RSFormCached(item)
new_cst = schema.produce_structure(cst, cst_parse) new_cst = schema.produce_structure(cst, cst_parse)
PropagationFacade.after_create_cst(schema, new_cst) PropagationFacade.after_create_cst(schema, new_cst)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -215,7 +217,6 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
} }
) )
@extend_schema( @extend_schema(
summary='execute substitutions', summary='execute substitutions',
tags=['RSForm'], tags=['RSForm'],
@ -236,9 +237,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
context={'schema': item} context={'schema': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
schema = m.RSForm(item)
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = [] substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
with transaction.atomic(): with transaction.atomic():
schema = m.RSForm(item)
for substitution in serializer.validated_data['substitutions']: for substitution in serializer.validated_data['substitutions']:
original = cast(m.Constituenta, substitution['original']) original = cast(m.Constituenta, substitution['original'])
replacement = cast(m.Constituenta, substitution['substitution']) replacement = cast(m.Constituenta, substitution['substitution'])
@ -246,6 +248,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
PropagationFacade.before_substitute(item.pk, substitutions) PropagationFacade.before_substitute(item.pk, substitutions)
schema.substitute(substitutions) schema.substitute(substitutions)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(item).data data=s.RSFormParseSerializer(item).data
@ -272,14 +275,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cst_list: list[m.Constituenta] = serializer.validated_data['items'] cst_list: list[m.Constituenta] = serializer.validated_data['items']
schema = m.RSFormCached(item)
with transaction.atomic(): 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) schema.delete_cst(cst_list)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(schema.model).data data=s.RSFormParseSerializer(item).data
) )
@extend_schema( @extend_schema(
@ -302,13 +307,15 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
context={'schema': item} context={'schema': item}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
schema = m.RSForm(item)
with transaction.atomic(): with transaction.atomic():
schema = m.RSForm(item)
schema.move_cst( schema.move_cst(
target=serializer.validated_data['items'], target=serializer.validated_data['items'],
destination=serializer.validated_data['move_to'] destination=serializer.validated_data['move_to']
) )
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(item).data 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: def reset_aliases(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Recreate all aliases based on order. ''' ''' Endpoint: Recreate all aliases based on order. '''
item = self._get_item() item = self._get_item()
schema = m.RSForm(item)
with transaction.atomic(): with transaction.atomic():
schema = m.RSForm(item)
schema.reset_aliases() schema.reset_aliases()
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(item).data 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: 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. '''
item = self._get_item() item = self._get_item()
with transaction.atomic(): with transaction.atomic():
m.OrderManager(m.RSFormCached(item)).restore_order() m.OrderManager(m.RSFormCached(item)).restore_order()
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(item).data data=s.RSFormParseSerializer(item).data