R: Segregate Cache models pt2
This commit is contained in:
parent
687e646bf7
commit
601ab8ce7b
|
@ -9,7 +9,6 @@ from apps.library.models import (
|
|||
LibraryTemplate,
|
||||
LocationHead
|
||||
)
|
||||
from apps.oss.models import OperationSchema
|
||||
from apps.rsform.models import RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
from shared.testing_utils import response_contains
|
||||
|
@ -59,8 +58,8 @@ class TestLibraryViewset(EndpointTester):
|
|||
'read_only': True
|
||||
}
|
||||
response = self.executeCreated(data=data)
|
||||
oss = OperationSchema(LibraryItem.objects.get(pk=response.data['id']))
|
||||
self.assertEqual(oss.model.owner, self.user)
|
||||
oss = LibraryItem.objects.get(pk=response.data['id'])
|
||||
self.assertEqual(oss.owner, self.user)
|
||||
self.assertEqual(response.data['owner'], self.user.pk)
|
||||
self.assertEqual(response.data['item_type'], data['item_type'])
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
|
|
|
@ -70,7 +70,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
PropagationFacade.before_delete_schema(instance)
|
||||
super().perform_destroy(instance)
|
||||
if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||
schemas = list(OperationSchema(instance).owned_schemas())
|
||||
schemas = list(OperationSchema.owned_schemasQ(instance))
|
||||
super().perform_destroy(instance)
|
||||
for schema in schemas:
|
||||
self.perform_destroy(schema)
|
||||
|
@ -204,7 +204,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
|
||||
with transaction.atomic():
|
||||
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||
owned_schemas = OperationSchema(item).owned_schemas().only('owner')
|
||||
owned_schemas = OperationSchema.owned_schemasQ(item).only('owner')
|
||||
for schema in owned_schemas:
|
||||
schema.owner_id = new_owner
|
||||
m.LibraryItem.objects.bulk_update(owned_schemas, ['owner'])
|
||||
|
@ -238,7 +238,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
|
||||
with transaction.atomic():
|
||||
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||
owned_schemas = OperationSchema(item).owned_schemas().only('location')
|
||||
owned_schemas = OperationSchema.owned_schemasQ(item).only('location')
|
||||
for schema in owned_schemas:
|
||||
schema.location = location
|
||||
m.LibraryItem.objects.bulk_update(owned_schemas, ['location'])
|
||||
|
@ -270,7 +270,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
|
||||
with transaction.atomic():
|
||||
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||
owned_schemas = OperationSchema(item).owned_schemas().only('access_policy')
|
||||
owned_schemas = OperationSchema.owned_schemasQ(item).only('access_policy')
|
||||
for schema in owned_schemas:
|
||||
schema.access_policy = new_policy
|
||||
m.LibraryItem.objects.bulk_update(owned_schemas, ['access_policy'])
|
||||
|
@ -300,7 +300,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
with transaction.atomic():
|
||||
added, deleted = m.Editor.set_and_return_diff(item.pk, editors)
|
||||
if len(added) >= 0 or len(deleted) >= 0:
|
||||
owned_schemas = OperationSchema(item).owned_schemas().only('pk')
|
||||
owned_schemas = OperationSchema.owned_schemasQ(item).only('pk')
|
||||
if owned_schemas.exists():
|
||||
m.Editor.objects.filter(
|
||||
item__in=owned_schemas,
|
||||
|
|
|
@ -23,3 +23,10 @@ class Layout(Model):
|
|||
|
||||
def __str__(self) -> str:
|
||||
return f'Схема расположения {self.oss.alias}'
|
||||
|
||||
@staticmethod
|
||||
def update_data(itemID: int, data: dict) -> None:
|
||||
''' Update layout data. '''
|
||||
layout = Layout.objects.get(oss_id=itemID)
|
||||
layout.data = data
|
||||
layout.save()
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
''' Models: OSS API. '''
|
||||
from typing import Optional, cast
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
from cctext import extract_entities
|
||||
from django.db.models import QuerySet
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
||||
from apps.rsform.graph import Graph
|
||||
from apps.rsform.models import (
|
||||
DELETED_ALIAS,
|
||||
INSERT_LAST,
|
||||
Constituenta,
|
||||
CstType,
|
||||
RSFormCached,
|
||||
extract_globals,
|
||||
replace_entities,
|
||||
replace_globals
|
||||
)
|
||||
from apps.rsform.models import Constituenta, RSFormCached
|
||||
|
||||
from .Argument import Argument
|
||||
from .Block import Block
|
||||
|
@ -26,16 +14,12 @@ from .Operation import Operation, OperationType
|
|||
from .Reference import Reference
|
||||
from .Substitution import Substitution
|
||||
|
||||
CstMapping = dict[str, Optional[Constituenta]]
|
||||
CstSubstitution = list[tuple[Constituenta, Constituenta]]
|
||||
|
||||
|
||||
class OperationSchema:
|
||||
''' Operations schema API. '''
|
||||
''' Operations schema API wrapper. No caching, propagation and minimal side effects. '''
|
||||
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
self.cache = OssCache(self)
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs) -> 'OperationSchema':
|
||||
|
@ -44,60 +28,27 @@ class OperationSchema:
|
|||
Layout.objects.create(oss=model, data=[])
|
||||
return OperationSchema(model)
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
''' Save wrapper. '''
|
||||
self.model.save(*args, **kwargs)
|
||||
@staticmethod
|
||||
def owned_schemasQ(item: LibraryItem) -> QuerySet[LibraryItem]:
|
||||
''' Get QuerySet containing all result schemas owned by current OSS. '''
|
||||
return LibraryItem.objects.filter(
|
||||
producer__oss=item,
|
||||
owner_id=item.owner_id,
|
||||
location=item.location
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def layoutQ(itemID: int) -> Layout:
|
||||
''' OSS layout. '''
|
||||
return Layout.objects.get(oss_id=itemID)
|
||||
|
||||
def refresh_from_db(self) -> None:
|
||||
''' Model wrapper. '''
|
||||
self.model.refresh_from_db()
|
||||
self.cache = OssCache(self)
|
||||
|
||||
def operations(self) -> QuerySet[Operation]:
|
||||
''' 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)
|
||||
|
||||
def layout(self) -> Layout:
|
||||
''' OSS layout. '''
|
||||
result = Layout.objects.filter(oss=self.model).first()
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
def substitutions(self) -> QuerySet[Substitution]:
|
||||
''' Operation substitutions. '''
|
||||
return Substitution.objects.filter(operation__oss=self.model)
|
||||
|
||||
def inheritance(self) -> QuerySet[Inheritance]:
|
||||
''' Operation inheritances. '''
|
||||
return Inheritance.objects.filter(operation__oss=self.model)
|
||||
|
||||
def owned_schemas(self) -> QuerySet[LibraryItem]:
|
||||
''' Get QuerySet containing all result schemas owned by current OSS. '''
|
||||
return LibraryItem.objects.filter(
|
||||
producer__oss=self.model,
|
||||
owner_id=self.model.owner_id,
|
||||
location=self.model.location
|
||||
)
|
||||
|
||||
def update_layout(self, data: dict) -> None:
|
||||
''' Update graphical layout. '''
|
||||
layout = self.layout()
|
||||
layout.data = data
|
||||
layout.save()
|
||||
|
||||
def create_operation(self, **kwargs) -> Operation:
|
||||
''' Create Operation. '''
|
||||
result = Operation.objects.create(oss=self.model, **kwargs)
|
||||
self.cache.insert_operation(result)
|
||||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def create_reference(self, target: Operation) -> Operation:
|
||||
|
@ -109,61 +60,13 @@ class OperationSchema:
|
|||
parent=target.parent
|
||||
)
|
||||
Reference.objects.create(reference=result, target=target)
|
||||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def create_block(self, **kwargs) -> Block:
|
||||
''' Create Block. '''
|
||||
result = Block.objects.create(oss=self.model, **kwargs)
|
||||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def delete_reference(self, target: Operation, keep_connections: bool = False):
|
||||
''' Delete Reference Operation. '''
|
||||
if keep_connections:
|
||||
referred_operations = target.getQ_reference_target()
|
||||
if len(referred_operations) == 1:
|
||||
referred_operation = referred_operations[0]
|
||||
for arg in target.getQ_as_argument():
|
||||
arg.pk = None
|
||||
arg.argument = referred_operation
|
||||
arg.save()
|
||||
else:
|
||||
pass
|
||||
# if target.result_id is not None:
|
||||
# self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema
|
||||
target.delete()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def delete_operation(self, target: int, keep_constituents: bool = False):
|
||||
''' Delete Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
schema = self.cache.get_schema(operation)
|
||||
children = self.cache.graph.outputs[target]
|
||||
if schema is not None and len(children) > 0:
|
||||
if not keep_constituents:
|
||||
self.before_delete_cst(schema, schema.cache.constituents)
|
||||
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)
|
||||
for item in self.cache.inheritance[child_id]:
|
||||
if item.parent_id in ids:
|
||||
inheritance_to_delete.append(item)
|
||||
for item in inheritance_to_delete:
|
||||
self.cache.remove_inheritance(item)
|
||||
Inheritance.objects.filter(pk__in=[item.pk for item in inheritance_to_delete]).delete()
|
||||
self.cache.remove_operation(target)
|
||||
operation.delete()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def delete_block(self, target: Block):
|
||||
''' Delete Block. '''
|
||||
new_parent = target.parent
|
||||
|
@ -176,104 +79,6 @@ class OperationSchema:
|
|||
operation.parent = new_parent
|
||||
operation.save(update_fields=['parent'])
|
||||
target.delete()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
|
||||
''' Set input schema for operation. '''
|
||||
operation = self.cache.operation_by_id[target]
|
||||
has_children = len(self.cache.graph.outputs[target]) > 0
|
||||
old_schema = self.cache.get_schema(operation)
|
||||
if schema == old_schema:
|
||||
return
|
||||
|
||||
if old_schema is not None:
|
||||
if has_children:
|
||||
self.before_delete_cst(old_schema, old_schema.cache.constituents)
|
||||
self.cache.remove_schema(old_schema)
|
||||
|
||||
operation.setQ_result(schema)
|
||||
if schema is not None:
|
||||
operation.alias = schema.alias
|
||||
operation.title = schema.title
|
||||
operation.description = schema.description
|
||||
operation.save(update_fields=['alias', 'title', 'description'])
|
||||
|
||||
if schema is not None and has_children:
|
||||
rsform = RSFormCached(schema)
|
||||
self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order')))
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
|
||||
''' Set arguments of target Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
processed: list[Operation] = []
|
||||
updated: list[Argument] = []
|
||||
deleted: list[Argument] = []
|
||||
for current in operation.getQ_arguments():
|
||||
if current.argument not in arguments:
|
||||
deleted.append(current)
|
||||
else:
|
||||
processed.append(current.argument)
|
||||
current.order = arguments.index(current.argument)
|
||||
updated.append(current)
|
||||
if len(deleted) > 0:
|
||||
self.before_delete_arguments(operation, [x.argument for x in deleted])
|
||||
for deleted_arg in deleted:
|
||||
self.cache.remove_argument(deleted_arg)
|
||||
Argument.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||
Argument.objects.bulk_update(updated, ['order'])
|
||||
|
||||
added: list[Operation] = []
|
||||
for order, arg in enumerate(arguments):
|
||||
if arg not in processed:
|
||||
processed.append(arg)
|
||||
new_arg = Argument.objects.create(operation=operation, argument=arg, order=order)
|
||||
self.cache.insert_argument(new_arg)
|
||||
added.append(arg)
|
||||
if len(added) > 0:
|
||||
self.after_create_arguments(operation, added)
|
||||
if len(added) > 0 or len(deleted) > 0:
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def set_substitutions(self, target: int, substitutes: list[dict]) -> None:
|
||||
''' Clear all arguments for target Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
schema = self.cache.get_schema(operation)
|
||||
processed: list[dict] = []
|
||||
deleted: list[Substitution] = []
|
||||
for current in operation.getQ_substitutions():
|
||||
subs = [
|
||||
x for x in substitutes
|
||||
if x['original'] == current.original and x['substitution'] == current.substitution
|
||||
]
|
||||
if len(subs) == 0:
|
||||
deleted.append(current)
|
||||
else:
|
||||
processed.append(subs[0])
|
||||
if len(deleted) > 0:
|
||||
if schema is not None:
|
||||
for sub in deleted:
|
||||
self._undo_substitution(schema, sub)
|
||||
else:
|
||||
for sub in deleted:
|
||||
self.cache.remove_substitution(sub)
|
||||
Substitution.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||
|
||||
added: list[Substitution] = []
|
||||
for sub_item in substitutes:
|
||||
if sub_item not in processed:
|
||||
new_sub = Substitution.objects.create(
|
||||
operation=operation,
|
||||
original=sub_item['original'],
|
||||
substitution=sub_item['substitution']
|
||||
)
|
||||
added.append(new_sub)
|
||||
self._process_added_substitutions(schema, added)
|
||||
|
||||
if len(added) > 0 or len(deleted) > 0:
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def create_input(self, operation: Operation) -> RSFormCached:
|
||||
''' Create input RSForm for given Operation. '''
|
||||
|
@ -288,26 +93,50 @@ class OperationSchema:
|
|||
)
|
||||
Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True))
|
||||
operation.setQ_result(schema.model)
|
||||
self.save(update_fields=['time_update'])
|
||||
return schema
|
||||
|
||||
def execute_operation(self, operation: Operation) -> bool:
|
||||
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
|
||||
''' Set arguments of target Operation. '''
|
||||
Argument.objects.filter(operation_id=target).delete()
|
||||
order = 0
|
||||
for arg in arguments:
|
||||
Argument.objects.create(
|
||||
operation_id=target,
|
||||
argument=arg,
|
||||
order=order
|
||||
)
|
||||
order += 1
|
||||
|
||||
def set_substitutions(self, target: int, substitutes: list[dict]) -> None:
|
||||
''' Set Substitutions for target Operation. '''
|
||||
Substitution.objects.filter(operation_id=target).delete()
|
||||
for sub_item in substitutes:
|
||||
Substitution.objects.create(
|
||||
operation_id=target,
|
||||
original=sub_item['original'],
|
||||
substitution=sub_item['substitution']
|
||||
)
|
||||
|
||||
def execute_operation(self, operation: Operation) -> None:
|
||||
''' Execute target Operation. '''
|
||||
schemas = [
|
||||
arg.argument.result
|
||||
for arg in operation.getQ_arguments().order_by('order')
|
||||
if arg.argument.result is not None
|
||||
schemas: list[int] = [
|
||||
arg.argument.result_id
|
||||
for arg in Argument.objects
|
||||
.filter(operation=operation)
|
||||
.select_related('argument')
|
||||
.only('argument__result_id')
|
||||
.order_by('order')
|
||||
if arg.argument.result_id is not None
|
||||
]
|
||||
if len(schemas) == 0:
|
||||
return False
|
||||
return
|
||||
substitutions = operation.getQ_substitutions()
|
||||
receiver = self.create_input(self.cache.operation_by_id[operation.pk])
|
||||
receiver = self.create_input(operation)
|
||||
|
||||
parents: dict = {}
|
||||
children: dict = {}
|
||||
for operand in schemas:
|
||||
schema = RSFormCached(operand)
|
||||
items = list(schema.constituentsQ().order_by('order'))
|
||||
items = list(Constituenta.objects.filter(schema_id=operand).order_by('order'))
|
||||
new_items = receiver.insert_copy(items)
|
||||
for (i, cst) in enumerate(new_items):
|
||||
parents[cst.pk] = items[i]
|
||||
|
@ -320,7 +149,7 @@ class OperationSchema:
|
|||
translated_substitutions.append((original, replacement))
|
||||
receiver.substitute(translated_substitutions)
|
||||
|
||||
for cst in receiver.constituentsQ().order_by('order'):
|
||||
for cst in Constituenta.objects.filter(schema=receiver.model).order_by('order'):
|
||||
parent = parents.get(cst.pk)
|
||||
assert parent is not None
|
||||
Inheritance.objects.create(
|
||||
|
@ -332,645 +161,3 @@ class OperationSchema:
|
|||
receiver.restore_order()
|
||||
receiver.reset_aliases()
|
||||
receiver.resolve_all_text()
|
||||
|
||||
if len(self.cache.graph.outputs[operation.pk]) > 0:
|
||||
self.after_create_cst(receiver, list(receiver.constituentsQ().order_by('order')))
|
||||
self.save(update_fields=['time_update'])
|
||||
return True
|
||||
|
||||
def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[Constituenta]):
|
||||
''' Move list of Constituents to destination Schema inheritor. '''
|
||||
self.cache.ensure_loaded()
|
||||
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()
|
||||
|
||||
def relocate_up(self, source: RSFormCached, destination: RSFormCached,
|
||||
items: list[Constituenta]) -> list[Constituenta]:
|
||||
''' Move list of Constituents upstream to destination Schema. '''
|
||||
self.cache.ensure_loaded()
|
||||
self.cache.insert_schema(source)
|
||||
self.cache.insert_schema(destination)
|
||||
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
alias_mapping: dict[str, str] = {}
|
||||
for item in self.cache.inheritance[operation.pk]:
|
||||
if item.parent_id in destination.cache.by_id:
|
||||
source_cst = source.cache.by_id[item.child_id]
|
||||
destination_cst = destination.cache.by_id[item.parent_id]
|
||||
alias_mapping[source_cst.alias] = destination_cst.alias
|
||||
|
||||
new_items = destination.insert_copy(items, initial_mapping=alias_mapping)
|
||||
for index, cst in enumerate(new_items):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=operation,
|
||||
child=items[index],
|
||||
parent=cst
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
self.after_create_cst(destination, new_items, exclude=[operation.pk])
|
||||
|
||||
return new_items
|
||||
|
||||
def after_create_cst(
|
||||
self, source: RSFormCached,
|
||||
cst_list: list[Constituenta],
|
||||
exclude: Optional[list[int]] = None
|
||||
) -> None:
|
||||
''' Trigger cascade resolutions when new Constituenta is created. '''
|
||||
self.cache.insert_schema(source)
|
||||
inserted_aliases = [cst.alias for cst in cst_list]
|
||||
depend_aliases: set[str] = set()
|
||||
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.model.pk)
|
||||
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude)
|
||||
|
||||
def after_change_cst_type(self, source: RSFormCached, target: Constituenta) -> None:
|
||||
''' Trigger cascade resolutions when Constituenta type is changed. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type))
|
||||
|
||||
def after_update_cst(self, source: RSFormCached, target: Constituenta, 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)
|
||||
depend_aliases = self._extract_data_references(data, old_data)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
self._cascade_update_cst(
|
||||
operation=operation.pk,
|
||||
cst_id=target.pk,
|
||||
data=data,
|
||||
old_data=old_data,
|
||||
mapping=alias_mapping
|
||||
)
|
||||
|
||||
def before_delete_cst(self, source: RSFormCached, target: list[Constituenta]) -> None:
|
||||
''' Trigger cascade resolutions before Constituents are deleted. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_delete_inherited(operation.pk, target)
|
||||
|
||||
def before_substitute(self, source: RSFormCached, substitutions: CstSubstitution) -> None:
|
||||
''' Trigger cascade resolutions before Constituents are substituted. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_before_substitute(substitutions, operation)
|
||||
|
||||
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||
''' Trigger cascade resolutions before arguments are deleted. '''
|
||||
if target.result_id is None:
|
||||
return
|
||||
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)
|
||||
|
||||
def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||
''' Trigger cascade resolutions after arguments are created. '''
|
||||
schema = self.cache.get_schema(target)
|
||||
if schema is None:
|
||||
return
|
||||
for argument in arguments:
|
||||
parent_schema = self.cache.get_schema(argument)
|
||||
if parent_schema is None:
|
||||
continue
|
||||
self._execute_inherit_cst(
|
||||
target_operation=target.pk,
|
||||
source=parent_schema,
|
||||
items=list(parent_schema.constituentsQ().order_by('order')),
|
||||
mapping={}
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
||||
def _cascade_inherit_cst(
|
||||
self, target_operation: int,
|
||||
source: RSFormCached,
|
||||
items: list[Constituenta],
|
||||
mapping: CstMapping,
|
||||
exclude: Optional[list[int]] = None
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[target_operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
for child_id in children:
|
||||
if not exclude or child_id not in exclude:
|
||||
self._execute_inherit_cst(child_id, source, items, mapping)
|
||||
|
||||
def _execute_inherit_cst(
|
||||
self,
|
||||
target_operation: int,
|
||||
source: RSFormCached,
|
||||
items: list[Constituenta],
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
operation = self.cache.operation_by_id[target_operation]
|
||||
destination = self.cache.get_schema(operation)
|
||||
if destination is None:
|
||||
return
|
||||
|
||||
self.cache.ensure_loaded()
|
||||
new_mapping = self._transform_mapping(mapping, operation, destination)
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
|
||||
insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
|
||||
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
|
||||
for index, cst in enumerate(new_cst_list):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=operation,
|
||||
child=cst,
|
||||
parent=items[index]
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_inherit_cst(operation.pk, destination, new_cst_list, new_mapping)
|
||||
|
||||
def _cascade_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None:
|
||||
children = self.cache.graph.outputs[operation_id]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
if child_schema.change_cst_type(successor_id, ctype):
|
||||
self._cascade_change_cst_type(child_id, successor_id, ctype)
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
||||
def _cascade_update_cst(
|
||||
self,
|
||||
operation: int,
|
||||
cst_id: int,
|
||||
data: dict, old_data: dict,
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
assert child_schema is not None
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
|
||||
successor = child_schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
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)
|
||||
if len(new_old_data) == 0:
|
||||
continue
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_update_cst(
|
||||
operation=child_id,
|
||||
cst_id=successor_id,
|
||||
data=new_data,
|
||||
old_data=new_old_data,
|
||||
mapping=new_mapping
|
||||
)
|
||||
|
||||
def _cascade_delete_inherited(self, operation: int, target: list[Constituenta]) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
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:
|
||||
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.cache.remove_cst(operation_id, target_ids)
|
||||
schema.delete_cst(target_cst)
|
||||
|
||||
def _cascade_before_substitute(self, substitutions: CstSubstitution, 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
|
||||
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
|
||||
if len(new_substitutions) == 0:
|
||||
continue
|
||||
self._cascade_before_substitute(new_substitutions, child_operation)
|
||||
child_schema.substitute(new_substitutions)
|
||||
|
||||
def _cascade_partial_mapping(
|
||||
self,
|
||||
mapping: CstMapping,
|
||||
target: list[int],
|
||||
operation: int,
|
||||
schema: RSFormCached
|
||||
) -> None:
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(mapping)
|
||||
schema.apply_partial_mapping(alias_mapping, target)
|
||||
children = self.cache.graph.outputs[operation]
|
||||
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
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
if not new_mapping:
|
||||
continue
|
||||
new_target = self.cache.get_inheritors_list(target, child_id)
|
||||
if len(new_target) == 0:
|
||||
continue
|
||||
self._cascade_partial_mapping(new_mapping, new_target, child_id, child_schema)
|
||||
|
||||
@staticmethod
|
||||
def _produce_alias_mapping(mapping: CstMapping) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = DELETED_ALIAS
|
||||
else:
|
||||
result[alias] = cst.alias
|
||||
return result
|
||||
|
||||
def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSFormCached) -> CstMapping:
|
||||
if len(mapping) == 0:
|
||||
return mapping
|
||||
result: CstMapping = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = None
|
||||
continue
|
||||
successor_id = self.cache.get_successor(cst.pk, operation.pk)
|
||||
if successor_id is None:
|
||||
continue
|
||||
successor = schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
result[alias] = successor
|
||||
return result
|
||||
|
||||
def _determine_insert_position(
|
||||
self, prototype_id: int,
|
||||
operation: Operation,
|
||||
source: RSFormCached,
|
||||
destination: RSFormCached
|
||||
) -> int:
|
||||
''' Determine insert_after for new constituenta. '''
|
||||
prototype = source.cache.by_id[prototype_id]
|
||||
prototype_index = source.cache.constituents.index(prototype)
|
||||
if prototype_index == 0:
|
||||
return 0
|
||||
prev_cst = source.cache.constituents[prototype_index - 1]
|
||||
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
|
||||
if inherited_prev_id is None:
|
||||
return INSERT_LAST
|
||||
prev_cst = destination.cache.by_id[inherited_prev_id]
|
||||
prev_index = destination.cache.constituents.index(prev_cst)
|
||||
return prev_index + 1
|
||||
|
||||
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
|
||||
result: set[str] = set()
|
||||
if 'definition_formal' in data:
|
||||
result.update(extract_globals(data['definition_formal']))
|
||||
result.update(extract_globals(old_data['definition_formal']))
|
||||
if 'term_raw' in data:
|
||||
result.update(extract_entities(data['term_raw']))
|
||||
result.update(extract_entities(old_data['term_raw']))
|
||||
if 'definition_raw' in data:
|
||||
result.update(extract_entities(data['definition_raw']))
|
||||
result.update(extract_entities(old_data['definition_raw']))
|
||||
return result
|
||||
|
||||
def _prepare_update_data(self, cst: Constituenta, data: dict, old_data: dict, mapping: dict[str, str]) -> dict:
|
||||
new_data = {}
|
||||
if 'term_forms' in data:
|
||||
if old_data['term_forms'] == cst.term_forms:
|
||||
new_data['term_forms'] = data['term_forms']
|
||||
if 'convention' in data:
|
||||
new_data['convention'] = data['convention']
|
||||
if 'definition_formal' in data:
|
||||
new_data['definition_formal'] = replace_globals(data['definition_formal'], mapping)
|
||||
if 'term_raw' in data:
|
||||
if replace_entities(old_data['term_raw'], mapping) == cst.term_raw:
|
||||
new_data['term_raw'] = replace_entities(data['term_raw'], mapping)
|
||||
if 'definition_raw' in data:
|
||||
if replace_entities(old_data['definition_raw'], mapping) == cst.definition_raw:
|
||||
new_data['definition_raw'] = replace_entities(data['definition_raw'], mapping)
|
||||
return new_data
|
||||
|
||||
def _transform_substitutions(
|
||||
self,
|
||||
target: CstSubstitution,
|
||||
operation: int,
|
||||
schema: RSFormCached
|
||||
) -> CstSubstitution:
|
||||
result: CstSubstitution = []
|
||||
for current_sub in target:
|
||||
sub_replaced = False
|
||||
new_substitution_id = self.cache.get_inheritor(current_sub[1].pk, operation)
|
||||
if new_substitution_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[1].pk:
|
||||
sub_replaced = True
|
||||
new_substitution_id = self.cache.get_inheritor(sub.original_id, operation)
|
||||
break
|
||||
|
||||
new_original_id = self.cache.get_inheritor(current_sub[0].pk, operation)
|
||||
original_replaced = False
|
||||
if new_original_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[0].pk:
|
||||
original_replaced = True
|
||||
sub.original_id = current_sub[1].pk
|
||||
sub.save()
|
||||
new_original_id = new_substitution_id
|
||||
new_substitution_id = self.cache.get_inheritor(sub.substitution_id, operation)
|
||||
break
|
||||
|
||||
if sub_replaced and original_replaced:
|
||||
raise ValidationError({'propagation': 'Substitution breaks OSS substitutions.'})
|
||||
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.substitution_id == current_sub[0].pk:
|
||||
sub.substitution_id = current_sub[1].pk
|
||||
sub.save()
|
||||
|
||||
if new_original_id is not None and new_substitution_id is not None:
|
||||
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]
|
||||
to_process = []
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.original_id in target_ids or sub.substitution_id in target_ids:
|
||||
to_process.append(sub)
|
||||
for sub in to_process:
|
||||
self._undo_substitution(schema, sub, target_ids)
|
||||
|
||||
def _undo_substitution(
|
||||
self,
|
||||
schema: RSFormCached,
|
||||
target: Substitution,
|
||||
ignore_parents: Optional[list[int]] = None
|
||||
) -> None:
|
||||
if ignore_parents is None:
|
||||
ignore_parents = []
|
||||
operation_id = target.operation_id
|
||||
original_schema, _, original_cst, substitution_cst = self.cache.unfold_sub(target)
|
||||
|
||||
dependant = []
|
||||
for cst_id in original_schema.get_dependant([original_cst.pk]):
|
||||
if cst_id not in ignore_parents:
|
||||
inheritor_id = self.cache.get_inheritor(cst_id, operation_id)
|
||||
if inheritor_id is not None:
|
||||
dependant.append(inheritor_id)
|
||||
|
||||
self.cache.substitutions[operation_id].remove(target)
|
||||
target.delete()
|
||||
|
||||
new_original: Optional[Constituenta] = None
|
||||
if original_cst.pk not in ignore_parents:
|
||||
full_cst = Constituenta.objects.get(pk=original_cst.pk)
|
||||
self.after_create_cst(original_schema, [full_cst])
|
||||
new_original_id = self.cache.get_inheritor(original_cst.pk, operation_id)
|
||||
assert new_original_id is not None
|
||||
new_original = schema.cache.by_id[new_original_id]
|
||||
if len(dependant) == 0:
|
||||
return
|
||||
|
||||
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation_id)
|
||||
assert substitution_id is not None
|
||||
substitution_inheritor = schema.cache.by_id[substitution_id]
|
||||
mapping = {substitution_inheritor.alias: new_original}
|
||||
self._cascade_partial_mapping(mapping, dependant, operation_id, schema)
|
||||
|
||||
def _process_added_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
|
||||
if len(added) == 0:
|
||||
return
|
||||
if schema is None:
|
||||
for sub in added:
|
||||
self.cache.insert_substitution(sub)
|
||||
return
|
||||
|
||||
cst_mapping: CstSubstitution = []
|
||||
for sub in added:
|
||||
original_id = self.cache.get_inheritor(sub.original_id, sub.operation_id)
|
||||
substitution_id = self.cache.get_inheritor(sub.substitution_id, sub.operation_id)
|
||||
if original_id is None or substitution_id is None:
|
||||
raise ValueError('Substitutions not found.')
|
||||
original_cst = schema.cache.by_id[original_id]
|
||||
substitution_cst = schema.cache.by_id[substitution_id]
|
||||
cst_mapping.append((original_cst, substitution_cst))
|
||||
self.before_substitute(schema, cst_mapping)
|
||||
schema.substitute(cst_mapping)
|
||||
for sub in added:
|
||||
self.cache.insert_substitution(sub)
|
||||
|
||||
|
||||
class OssCache:
|
||||
''' Cache for OSS data. '''
|
||||
|
||||
def __init__(self, oss: OperationSchema):
|
||||
self._oss = oss
|
||||
self._schemas: list[RSFormCached] = []
|
||||
self._schema_by_id: dict[int, RSFormCached] = {}
|
||||
|
||||
self.operations = list(oss.operations().only('result_id'))
|
||||
self.operation_by_id = {operation.pk: operation for operation in self.operations}
|
||||
self.graph = Graph[int]()
|
||||
for operation in self.operations:
|
||||
self.graph.add_node(operation.pk)
|
||||
for argument in self._oss.arguments().only('operation_id', 'argument_id').order_by('order'):
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
self.is_loaded = False
|
||||
self.substitutions: dict[int, list[Substitution]] = {}
|
||||
self.inheritance: dict[int, list[Inheritance]] = {}
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
''' Ensure cache is fully loaded. '''
|
||||
if self.is_loaded:
|
||||
return
|
||||
self.is_loaded = True
|
||||
for operation in self.operations:
|
||||
self.inheritance[operation.pk] = []
|
||||
self.substitutions[operation.pk] = []
|
||||
for sub in self._oss.substitutions().only('operation_id', 'original_id', 'substitution_id'):
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
for item in self._oss.inheritance().only('operation_id', 'parent_id', 'child_id'):
|
||||
self.inheritance[item.operation_id].append(item)
|
||||
|
||||
def get_schema(self, operation: Operation) -> Optional[RSFormCached]:
|
||||
''' Get schema by Operation. '''
|
||||
if operation.result_id is None:
|
||||
return None
|
||||
if operation.result_id in self._schema_by_id:
|
||||
return self._schema_by_id[operation.result_id]
|
||||
else:
|
||||
schema = RSFormCached.from_id(operation.result_id)
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
return schema
|
||||
|
||||
def get_operation(self, schema: int) -> Operation:
|
||||
''' Get operation by schema. '''
|
||||
for operation in self.operations:
|
||||
if operation.result_id == schema:
|
||||
return operation
|
||||
raise ValueError(f'Operation for schema {schema} not found')
|
||||
|
||||
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id == parent_cst:
|
||||
return item.child_id
|
||||
return None
|
||||
|
||||
def get_inheritors_list(self, target: list[int], operation: int) -> list[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
result = []
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id in target:
|
||||
result.append(item.child_id)
|
||||
return result
|
||||
|
||||
def get_successor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom including substitutions. '''
|
||||
for sub in self.substitutions[operation]:
|
||||
if sub.original_id == parent_cst:
|
||||
return self.get_inheritor(sub.substitution_id, operation)
|
||||
return self.get_inheritor(parent_cst, operation)
|
||||
|
||||
def insert_schema(self, schema: RSFormCached) -> None:
|
||||
''' Insert new schema. '''
|
||||
if not self._schema_by_id.get(schema.model.pk):
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
|
||||
def insert_operation(self, operation: Operation) -> None:
|
||||
''' Insert new operation. '''
|
||||
self.operations.append(operation)
|
||||
self.operation_by_id[operation.pk] = operation
|
||||
self.graph.add_node(operation.pk)
|
||||
if self.is_loaded:
|
||||
self.substitutions[operation.pk] = []
|
||||
self.inheritance[operation.pk] = []
|
||||
|
||||
def insert_argument(self, argument: Argument) -> None:
|
||||
''' Insert new argument. '''
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
||||
''' Insert new inheritance. '''
|
||||
self.inheritance[inheritance.operation_id].append(inheritance)
|
||||
|
||||
def insert_substitution(self, sub: Substitution) -> None:
|
||||
''' Insert new substitution. '''
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
|
||||
def remove_cst(self, operation: int, target: list[int]) -> None:
|
||||
''' Remove constituents from operation. '''
|
||||
subs_to_delete = [
|
||||
sub for sub in self.substitutions[operation]
|
||||
if sub.original_id in target or sub.substitution_id in target
|
||||
]
|
||||
for sub in subs_to_delete:
|
||||
self.substitutions[operation].remove(sub)
|
||||
inherit_to_delete = [item for item in self.inheritance[operation] if item.child_id in target]
|
||||
for item in inherit_to_delete:
|
||||
self.inheritance[operation].remove(item)
|
||||
|
||||
def remove_schema(self, schema: RSFormCached) -> None:
|
||||
''' Remove schema from cache. '''
|
||||
self._schemas.remove(schema)
|
||||
del self._schema_by_id[schema.model.pk]
|
||||
|
||||
def remove_operation(self, operation: int) -> None:
|
||||
''' Remove operation from cache. '''
|
||||
target = self.operation_by_id[operation]
|
||||
self.graph.remove_node(operation)
|
||||
if target.result_id in self._schema_by_id:
|
||||
self._schemas.remove(self._schema_by_id[target.result_id])
|
||||
del self._schema_by_id[target.result_id]
|
||||
self.operations.remove(self.operation_by_id[operation])
|
||||
del self.operation_by_id[operation]
|
||||
if self.is_loaded:
|
||||
del self.substitutions[operation]
|
||||
del self.inheritance[operation]
|
||||
|
||||
def remove_argument(self, argument: Argument) -> None:
|
||||
''' Remove argument from cache. '''
|
||||
self.graph.remove_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
def remove_substitution(self, target: Substitution) -> None:
|
||||
''' Remove substitution from cache. '''
|
||||
self.substitutions[target.operation_id].remove(target)
|
||||
|
||||
def remove_inheritance(self, target: Inheritance) -> None:
|
||||
''' Remove inheritance from cache. '''
|
||||
self.inheritance[target.operation_id].remove(target)
|
||||
|
||||
def unfold_sub(self, sub: Substitution) -> tuple[RSFormCached, RSFormCached, Constituenta, Constituenta]:
|
||||
''' Unfold substitution into original and substitution forms. '''
|
||||
operation = self.operation_by_id[sub.operation_id]
|
||||
parents = self.graph.inputs[operation.pk]
|
||||
original_cst = None
|
||||
substitution_cst = None
|
||||
original_schema = None
|
||||
substitution_schema = None
|
||||
for parent_id in parents:
|
||||
parent_schema = self.get_schema(self.operation_by_id[parent_id])
|
||||
if parent_schema is None:
|
||||
continue
|
||||
if sub.original_id in parent_schema.cache.by_id:
|
||||
original_schema = parent_schema
|
||||
original_cst = original_schema.cache.by_id[sub.original_id]
|
||||
if sub.substitution_id in parent_schema.cache.by_id:
|
||||
substitution_schema = parent_schema
|
||||
substitution_cst = substitution_schema.cache.by_id[sub.substitution_id]
|
||||
if original_schema is None or substitution_schema is None or original_cst is None or substitution_cst is None:
|
||||
raise ValueError(f'Parent schema for Substitution-{sub.pk} not found.')
|
||||
return original_schema, substitution_schema, original_cst, substitution_cst
|
||||
|
||||
def _insert_new(self, schema: RSFormCached) -> None:
|
||||
self._schemas.append(schema)
|
||||
self._schema_by_id[schema.model.pk] = schema
|
||||
|
|
879
rsconcept/backend/apps/oss/models/OperationSchemaCached.py
Normal file
879
rsconcept/backend/apps/oss/models/OperationSchemaCached.py
Normal file
|
@ -0,0 +1,879 @@
|
|||
''' Models: OSS API. '''
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
from cctext import extract_entities
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from apps.library.models import Editor, LibraryItem
|
||||
from apps.rsform.graph import Graph
|
||||
from apps.rsform.models import (
|
||||
DELETED_ALIAS,
|
||||
INSERT_LAST,
|
||||
Constituenta,
|
||||
CstType,
|
||||
RSFormCached,
|
||||
extract_globals,
|
||||
replace_entities,
|
||||
replace_globals
|
||||
)
|
||||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation
|
||||
from .Substitution import Substitution
|
||||
|
||||
CstMapping = dict[str, Optional[Constituenta]]
|
||||
CstSubstitution = list[tuple[Constituenta, Constituenta]]
|
||||
|
||||
|
||||
class OperationSchemaCached:
|
||||
''' Operations schema API with caching. '''
|
||||
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
self.cache = OssCache(self)
|
||||
|
||||
def delete_reference(self, target: Operation, keep_connections: bool = False):
|
||||
''' Delete Reference Operation. '''
|
||||
if keep_connections:
|
||||
referred_operations = target.getQ_reference_target()
|
||||
if len(referred_operations) == 1:
|
||||
referred_operation = referred_operations[0]
|
||||
for arg in target.getQ_as_argument():
|
||||
arg.pk = None
|
||||
arg.argument = referred_operation
|
||||
arg.save()
|
||||
else:
|
||||
pass
|
||||
# if target.result_id is not None:
|
||||
# self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema
|
||||
target.delete()
|
||||
|
||||
def delete_operation(self, target: int, keep_constituents: bool = False):
|
||||
''' Delete Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
schema = self.cache.get_schema(operation)
|
||||
children = self.cache.graph.outputs[target]
|
||||
if schema is not None and len(children) > 0:
|
||||
if not keep_constituents:
|
||||
self.before_delete_cst(schema, schema.cache.constituents)
|
||||
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)
|
||||
for item in self.cache.inheritance[child_id]:
|
||||
if item.parent_id in ids:
|
||||
inheritance_to_delete.append(item)
|
||||
for item in inheritance_to_delete:
|
||||
self.cache.remove_inheritance(item)
|
||||
Inheritance.objects.filter(pk__in=[item.pk for item in inheritance_to_delete]).delete()
|
||||
self.cache.remove_operation(target)
|
||||
operation.delete()
|
||||
|
||||
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
|
||||
''' Set input schema for operation. '''
|
||||
operation = self.cache.operation_by_id[target]
|
||||
has_children = len(self.cache.graph.outputs[target]) > 0
|
||||
old_schema = self.cache.get_schema(operation)
|
||||
if schema == old_schema:
|
||||
return
|
||||
|
||||
if old_schema is not None:
|
||||
if has_children:
|
||||
self.before_delete_cst(old_schema, old_schema.cache.constituents)
|
||||
self.cache.remove_schema(old_schema)
|
||||
|
||||
operation.setQ_result(schema)
|
||||
if schema is not None:
|
||||
operation.alias = schema.alias
|
||||
operation.title = schema.title
|
||||
operation.description = schema.description
|
||||
operation.save(update_fields=['alias', 'title', 'description'])
|
||||
|
||||
if schema is not None and has_children:
|
||||
rsform = RSFormCached(schema)
|
||||
self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order')))
|
||||
|
||||
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
|
||||
''' Set arguments of target Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
processed: list[Operation] = []
|
||||
updated: list[Argument] = []
|
||||
deleted: list[Argument] = []
|
||||
for current in operation.getQ_arguments():
|
||||
if current.argument not in arguments:
|
||||
deleted.append(current)
|
||||
else:
|
||||
processed.append(current.argument)
|
||||
current.order = arguments.index(current.argument)
|
||||
updated.append(current)
|
||||
if len(deleted) > 0:
|
||||
self.before_delete_arguments(operation, [x.argument for x in deleted])
|
||||
for deleted_arg in deleted:
|
||||
self.cache.remove_argument(deleted_arg)
|
||||
Argument.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||
Argument.objects.bulk_update(updated, ['order'])
|
||||
|
||||
added: list[Operation] = []
|
||||
for order, arg in enumerate(arguments):
|
||||
if arg not in processed:
|
||||
processed.append(arg)
|
||||
new_arg = Argument.objects.create(operation=operation, argument=arg, order=order)
|
||||
self.cache.insert_argument(new_arg)
|
||||
added.append(arg)
|
||||
if len(added) > 0:
|
||||
self.after_create_arguments(operation, added)
|
||||
|
||||
def set_substitutions(self, target: int, substitutes: list[dict]) -> None:
|
||||
''' Clear all arguments for target Operation. '''
|
||||
self.cache.ensure_loaded()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
schema = self.cache.get_schema(operation)
|
||||
processed: list[dict] = []
|
||||
deleted: list[Substitution] = []
|
||||
for current in operation.getQ_substitutions():
|
||||
subs = [
|
||||
x for x in substitutes
|
||||
if x['original'] == current.original and x['substitution'] == current.substitution
|
||||
]
|
||||
if len(subs) == 0:
|
||||
deleted.append(current)
|
||||
else:
|
||||
processed.append(subs[0])
|
||||
if len(deleted) > 0:
|
||||
if schema is not None:
|
||||
for sub in deleted:
|
||||
self._undo_substitution(schema, sub)
|
||||
else:
|
||||
for sub in deleted:
|
||||
self.cache.remove_substitution(sub)
|
||||
Substitution.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||
|
||||
added: list[Substitution] = []
|
||||
for sub_item in substitutes:
|
||||
if sub_item not in processed:
|
||||
new_sub = Substitution.objects.create(
|
||||
operation=operation,
|
||||
original=sub_item['original'],
|
||||
substitution=sub_item['substitution']
|
||||
)
|
||||
added.append(new_sub)
|
||||
self._process_added_substitutions(schema, added)
|
||||
|
||||
def _create_input(self, operation: Operation) -> RSFormCached:
|
||||
''' Create input RSForm for given Operation. '''
|
||||
schema = RSFormCached.create(
|
||||
owner=self.model.owner,
|
||||
alias=operation.alias,
|
||||
title=operation.title,
|
||||
description=operation.description,
|
||||
visible=False,
|
||||
access_policy=self.model.access_policy,
|
||||
location=self.model.location
|
||||
)
|
||||
Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True))
|
||||
operation.setQ_result(schema.model)
|
||||
return schema
|
||||
|
||||
def execute_operation(self, operation: Operation) -> bool:
|
||||
''' Execute target Operation. '''
|
||||
schemas: list[int] = [
|
||||
arg.argument.result_id
|
||||
for arg in Argument.objects
|
||||
.filter(operation=operation)
|
||||
.select_related('argument')
|
||||
.only('argument__result_id')
|
||||
.order_by('order')
|
||||
if arg.argument.result_id is not None
|
||||
]
|
||||
if len(schemas) == 0:
|
||||
return False
|
||||
substitutions = operation.getQ_substitutions()
|
||||
receiver = self._create_input(self.cache.operation_by_id[operation.pk])
|
||||
|
||||
parents: dict = {}
|
||||
children: dict = {}
|
||||
for operand in schemas:
|
||||
items = list(Constituenta.objects.filter(schema_id=operand).order_by('order'))
|
||||
new_items = receiver.insert_copy(items)
|
||||
for (i, cst) in enumerate(new_items):
|
||||
parents[cst.pk] = items[i]
|
||||
children[items[i].pk] = cst
|
||||
|
||||
translated_substitutions: list[tuple[Constituenta, Constituenta]] = []
|
||||
for sub in substitutions:
|
||||
original = children[sub.original.pk]
|
||||
replacement = children[sub.substitution.pk]
|
||||
translated_substitutions.append((original, replacement))
|
||||
receiver.substitute(translated_substitutions)
|
||||
|
||||
for cst in Constituenta.objects.filter(schema=receiver.model).order_by('order'):
|
||||
parent = parents.get(cst.pk)
|
||||
assert parent is not None
|
||||
Inheritance.objects.create(
|
||||
operation_id=operation.pk,
|
||||
child=cst,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
receiver.restore_order()
|
||||
receiver.reset_aliases()
|
||||
receiver.resolve_all_text()
|
||||
|
||||
if len(self.cache.graph.outputs[operation.pk]) > 0:
|
||||
receiver_items = list(Constituenta.objects.filter(schema=receiver.model).order_by('order'))
|
||||
self.after_create_cst(receiver, receiver_items)
|
||||
return True
|
||||
|
||||
def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[Constituenta]):
|
||||
''' Move list of Constituents to destination Schema inheritor. '''
|
||||
self.cache.ensure_loaded()
|
||||
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()
|
||||
|
||||
def relocate_up(self, source: RSFormCached, destination: RSFormCached,
|
||||
items: list[Constituenta]) -> list[Constituenta]:
|
||||
''' Move list of Constituents upstream to destination Schema. '''
|
||||
self.cache.ensure_loaded()
|
||||
self.cache.insert_schema(source)
|
||||
self.cache.insert_schema(destination)
|
||||
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
alias_mapping: dict[str, str] = {}
|
||||
for item in self.cache.inheritance[operation.pk]:
|
||||
if item.parent_id in destination.cache.by_id:
|
||||
source_cst = source.cache.by_id[item.child_id]
|
||||
destination_cst = destination.cache.by_id[item.parent_id]
|
||||
alias_mapping[source_cst.alias] = destination_cst.alias
|
||||
|
||||
new_items = destination.insert_copy(items, initial_mapping=alias_mapping)
|
||||
for index, cst in enumerate(new_items):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=operation,
|
||||
child=items[index],
|
||||
parent=cst
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
self.after_create_cst(destination, new_items, exclude=[operation.pk])
|
||||
|
||||
return new_items
|
||||
|
||||
def after_create_cst(
|
||||
self, source: RSFormCached,
|
||||
cst_list: list[Constituenta],
|
||||
exclude: Optional[list[int]] = None
|
||||
) -> None:
|
||||
''' Trigger cascade resolutions when new Constituenta is created. '''
|
||||
self.cache.insert_schema(source)
|
||||
inserted_aliases = [cst.alias for cst in cst_list]
|
||||
depend_aliases: set[str] = set()
|
||||
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.model.pk)
|
||||
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude)
|
||||
|
||||
def after_change_cst_type(self, source: RSFormCached, target: Constituenta) -> None:
|
||||
''' Trigger cascade resolutions when Constituenta type is changed. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type))
|
||||
|
||||
def after_update_cst(self, source: RSFormCached, target: Constituenta, 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)
|
||||
depend_aliases = self._extract_data_references(data, old_data)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
self._cascade_update_cst(
|
||||
operation=operation.pk,
|
||||
cst_id=target.pk,
|
||||
data=data,
|
||||
old_data=old_data,
|
||||
mapping=alias_mapping
|
||||
)
|
||||
|
||||
def before_delete_cst(self, source: RSFormCached, target: list[Constituenta]) -> None:
|
||||
''' Trigger cascade resolutions before Constituents are deleted. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_delete_inherited(operation.pk, target)
|
||||
|
||||
def before_substitute(self, source: RSFormCached, substitutions: CstSubstitution) -> None:
|
||||
''' Trigger cascade resolutions before Constituents are substituted. '''
|
||||
self.cache.insert_schema(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_before_substitute(substitutions, operation)
|
||||
|
||||
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||
''' Trigger cascade resolutions before arguments are deleted. '''
|
||||
if target.result_id is None:
|
||||
return
|
||||
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)
|
||||
|
||||
def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||
''' Trigger cascade resolutions after arguments are created. '''
|
||||
schema = self.cache.get_schema(target)
|
||||
if schema is None:
|
||||
return
|
||||
for argument in arguments:
|
||||
parent_schema = self.cache.get_schema(argument)
|
||||
if parent_schema is None:
|
||||
continue
|
||||
self._execute_inherit_cst(
|
||||
target_operation=target.pk,
|
||||
source=parent_schema,
|
||||
items=list(parent_schema.constituentsQ().order_by('order')),
|
||||
mapping={}
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
||||
def _cascade_inherit_cst(
|
||||
self, target_operation: int,
|
||||
source: RSFormCached,
|
||||
items: list[Constituenta],
|
||||
mapping: CstMapping,
|
||||
exclude: Optional[list[int]] = None
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[target_operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
for child_id in children:
|
||||
if not exclude or child_id not in exclude:
|
||||
self._execute_inherit_cst(child_id, source, items, mapping)
|
||||
|
||||
def _execute_inherit_cst(
|
||||
self,
|
||||
target_operation: int,
|
||||
source: RSFormCached,
|
||||
items: list[Constituenta],
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
operation = self.cache.operation_by_id[target_operation]
|
||||
destination = self.cache.get_schema(operation)
|
||||
if destination is None:
|
||||
return
|
||||
|
||||
self.cache.ensure_loaded()
|
||||
new_mapping = self._transform_mapping(mapping, operation, destination)
|
||||
alias_mapping = OperationSchemaCached._produce_alias_mapping(new_mapping)
|
||||
insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
|
||||
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
|
||||
for index, cst in enumerate(new_cst_list):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=operation,
|
||||
child=cst,
|
||||
parent=items[index]
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_inherit_cst(operation.pk, destination, new_cst_list, new_mapping)
|
||||
|
||||
def _cascade_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None:
|
||||
children = self.cache.graph.outputs[operation_id]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
if child_schema.change_cst_type(successor_id, ctype):
|
||||
self._cascade_change_cst_type(child_id, successor_id, ctype)
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
||||
def _cascade_update_cst(
|
||||
self,
|
||||
operation: int,
|
||||
cst_id: int,
|
||||
data: dict, old_data: dict,
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
assert child_schema is not None
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = OperationSchemaCached._produce_alias_mapping(new_mapping)
|
||||
successor = child_schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
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)
|
||||
if len(new_old_data) == 0:
|
||||
continue
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_update_cst(
|
||||
operation=child_id,
|
||||
cst_id=successor_id,
|
||||
data=new_data,
|
||||
old_data=new_old_data,
|
||||
mapping=new_mapping
|
||||
)
|
||||
|
||||
def _cascade_delete_inherited(self, operation: int, target: list[Constituenta]) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
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:
|
||||
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.cache.remove_cst(operation_id, target_ids)
|
||||
schema.delete_cst(target_cst)
|
||||
|
||||
def _cascade_before_substitute(self, substitutions: CstSubstitution, 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
|
||||
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
|
||||
if len(new_substitutions) == 0:
|
||||
continue
|
||||
self._cascade_before_substitute(new_substitutions, child_operation)
|
||||
child_schema.substitute(new_substitutions)
|
||||
|
||||
def _cascade_partial_mapping(
|
||||
self,
|
||||
mapping: CstMapping,
|
||||
target: list[int],
|
||||
operation: int,
|
||||
schema: RSFormCached
|
||||
) -> None:
|
||||
alias_mapping = OperationSchemaCached._produce_alias_mapping(mapping)
|
||||
schema.apply_partial_mapping(alias_mapping, target)
|
||||
children = self.cache.graph.outputs[operation]
|
||||
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
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
if not new_mapping:
|
||||
continue
|
||||
new_target = self.cache.get_inheritors_list(target, child_id)
|
||||
if len(new_target) == 0:
|
||||
continue
|
||||
self._cascade_partial_mapping(new_mapping, new_target, child_id, child_schema)
|
||||
|
||||
@staticmethod
|
||||
def _produce_alias_mapping(mapping: CstMapping) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = DELETED_ALIAS
|
||||
else:
|
||||
result[alias] = cst.alias
|
||||
return result
|
||||
|
||||
def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSFormCached) -> CstMapping:
|
||||
if len(mapping) == 0:
|
||||
return mapping
|
||||
result: CstMapping = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = None
|
||||
continue
|
||||
successor_id = self.cache.get_successor(cst.pk, operation.pk)
|
||||
if successor_id is None:
|
||||
continue
|
||||
successor = schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
result[alias] = successor
|
||||
return result
|
||||
|
||||
def _determine_insert_position(
|
||||
self, prototype_id: int,
|
||||
operation: Operation,
|
||||
source: RSFormCached,
|
||||
destination: RSFormCached
|
||||
) -> int:
|
||||
''' Determine insert_after for new constituenta. '''
|
||||
prototype = source.cache.by_id[prototype_id]
|
||||
prototype_index = source.cache.constituents.index(prototype)
|
||||
if prototype_index == 0:
|
||||
return 0
|
||||
prev_cst = source.cache.constituents[prototype_index - 1]
|
||||
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
|
||||
if inherited_prev_id is None:
|
||||
return INSERT_LAST
|
||||
prev_cst = destination.cache.by_id[inherited_prev_id]
|
||||
prev_index = destination.cache.constituents.index(prev_cst)
|
||||
return prev_index + 1
|
||||
|
||||
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
|
||||
result: set[str] = set()
|
||||
if 'definition_formal' in data:
|
||||
result.update(extract_globals(data['definition_formal']))
|
||||
result.update(extract_globals(old_data['definition_formal']))
|
||||
if 'term_raw' in data:
|
||||
result.update(extract_entities(data['term_raw']))
|
||||
result.update(extract_entities(old_data['term_raw']))
|
||||
if 'definition_raw' in data:
|
||||
result.update(extract_entities(data['definition_raw']))
|
||||
result.update(extract_entities(old_data['definition_raw']))
|
||||
return result
|
||||
|
||||
def _prepare_update_data(self, cst: Constituenta, data: dict, old_data: dict, mapping: dict[str, str]) -> dict:
|
||||
new_data = {}
|
||||
if 'term_forms' in data:
|
||||
if old_data['term_forms'] == cst.term_forms:
|
||||
new_data['term_forms'] = data['term_forms']
|
||||
if 'convention' in data:
|
||||
new_data['convention'] = data['convention']
|
||||
if 'definition_formal' in data:
|
||||
new_data['definition_formal'] = replace_globals(data['definition_formal'], mapping)
|
||||
if 'term_raw' in data:
|
||||
if replace_entities(old_data['term_raw'], mapping) == cst.term_raw:
|
||||
new_data['term_raw'] = replace_entities(data['term_raw'], mapping)
|
||||
if 'definition_raw' in data:
|
||||
if replace_entities(old_data['definition_raw'], mapping) == cst.definition_raw:
|
||||
new_data['definition_raw'] = replace_entities(data['definition_raw'], mapping)
|
||||
return new_data
|
||||
|
||||
def _transform_substitutions(
|
||||
self,
|
||||
target: CstSubstitution,
|
||||
operation: int,
|
||||
schema: RSFormCached
|
||||
) -> CstSubstitution:
|
||||
result: CstSubstitution = []
|
||||
for current_sub in target:
|
||||
sub_replaced = False
|
||||
new_substitution_id = self.cache.get_inheritor(current_sub[1].pk, operation)
|
||||
if new_substitution_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[1].pk:
|
||||
sub_replaced = True
|
||||
new_substitution_id = self.cache.get_inheritor(sub.original_id, operation)
|
||||
break
|
||||
|
||||
new_original_id = self.cache.get_inheritor(current_sub[0].pk, operation)
|
||||
original_replaced = False
|
||||
if new_original_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[0].pk:
|
||||
original_replaced = True
|
||||
sub.original_id = current_sub[1].pk
|
||||
sub.save()
|
||||
new_original_id = new_substitution_id
|
||||
new_substitution_id = self.cache.get_inheritor(sub.substitution_id, operation)
|
||||
break
|
||||
|
||||
if sub_replaced and original_replaced:
|
||||
raise ValidationError({'propagation': 'Substitution breaks OSS substitutions.'})
|
||||
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.substitution_id == current_sub[0].pk:
|
||||
sub.substitution_id = current_sub[1].pk
|
||||
sub.save()
|
||||
|
||||
if new_original_id is not None and new_substitution_id is not None:
|
||||
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]
|
||||
to_process = []
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.original_id in target_ids or sub.substitution_id in target_ids:
|
||||
to_process.append(sub)
|
||||
for sub in to_process:
|
||||
self._undo_substitution(schema, sub, target_ids)
|
||||
|
||||
def _undo_substitution(
|
||||
self,
|
||||
schema: RSFormCached,
|
||||
target: Substitution,
|
||||
ignore_parents: Optional[list[int]] = None
|
||||
) -> None:
|
||||
if ignore_parents is None:
|
||||
ignore_parents = []
|
||||
operation_id = target.operation_id
|
||||
original_schema, _, original_cst, substitution_cst = self.cache.unfold_sub(target)
|
||||
|
||||
dependant = []
|
||||
for cst_id in original_schema.get_dependant([original_cst.pk]):
|
||||
if cst_id not in ignore_parents:
|
||||
inheritor_id = self.cache.get_inheritor(cst_id, operation_id)
|
||||
if inheritor_id is not None:
|
||||
dependant.append(inheritor_id)
|
||||
|
||||
self.cache.substitutions[operation_id].remove(target)
|
||||
target.delete()
|
||||
|
||||
new_original: Optional[Constituenta] = None
|
||||
if original_cst.pk not in ignore_parents:
|
||||
full_cst = Constituenta.objects.get(pk=original_cst.pk)
|
||||
self.after_create_cst(original_schema, [full_cst])
|
||||
new_original_id = self.cache.get_inheritor(original_cst.pk, operation_id)
|
||||
assert new_original_id is not None
|
||||
new_original = schema.cache.by_id[new_original_id]
|
||||
if len(dependant) == 0:
|
||||
return
|
||||
|
||||
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation_id)
|
||||
assert substitution_id is not None
|
||||
substitution_inheritor = schema.cache.by_id[substitution_id]
|
||||
mapping = {substitution_inheritor.alias: new_original}
|
||||
self._cascade_partial_mapping(mapping, dependant, operation_id, schema)
|
||||
|
||||
def _process_added_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
|
||||
if len(added) == 0:
|
||||
return
|
||||
if schema is None:
|
||||
for sub in added:
|
||||
self.cache.insert_substitution(sub)
|
||||
return
|
||||
|
||||
cst_mapping: CstSubstitution = []
|
||||
for sub in added:
|
||||
original_id = self.cache.get_inheritor(sub.original_id, sub.operation_id)
|
||||
substitution_id = self.cache.get_inheritor(sub.substitution_id, sub.operation_id)
|
||||
if original_id is None or substitution_id is None:
|
||||
raise ValueError('Substitutions not found.')
|
||||
original_cst = schema.cache.by_id[original_id]
|
||||
substitution_cst = schema.cache.by_id[substitution_id]
|
||||
cst_mapping.append((original_cst, substitution_cst))
|
||||
self.before_substitute(schema, cst_mapping)
|
||||
schema.substitute(cst_mapping)
|
||||
for sub in added:
|
||||
self.cache.insert_substitution(sub)
|
||||
|
||||
|
||||
class OssCache:
|
||||
''' Cache for OSS data. '''
|
||||
|
||||
def __init__(self, oss: OperationSchemaCached):
|
||||
self._oss = oss
|
||||
self._schemas: list[RSFormCached] = []
|
||||
self._schema_by_id: dict[int, RSFormCached] = {}
|
||||
|
||||
self.operations = list(Operation.objects.filter(oss=oss.model).only('result_id'))
|
||||
self.operation_by_id = {operation.pk: operation for operation in self.operations}
|
||||
self.graph = Graph[int]()
|
||||
for operation in self.operations:
|
||||
self.graph.add_node(operation.pk)
|
||||
arguments = Argument.objects \
|
||||
.filter(operation__oss=self._oss.model) \
|
||||
.only('operation_id', 'argument_id') \
|
||||
.order_by('order')
|
||||
for argument in arguments:
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
self.is_loaded = False
|
||||
self.substitutions: dict[int, list[Substitution]] = {}
|
||||
self.inheritance: dict[int, list[Inheritance]] = {}
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
''' Ensure cache is fully loaded. '''
|
||||
if self.is_loaded:
|
||||
return
|
||||
self.is_loaded = True
|
||||
for operation in self.operations:
|
||||
self.inheritance[operation.pk] = []
|
||||
self.substitutions[operation.pk] = []
|
||||
for sub in Substitution.objects.filter(operation__oss=self._oss.model).only(
|
||||
'operation_id', 'original_id', 'substitution_id'):
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
for item in Inheritance.objects.filter(operation__oss=self._oss.model).only(
|
||||
'operation_id', 'parent_id', 'child_id'):
|
||||
self.inheritance[item.operation_id].append(item)
|
||||
|
||||
def get_schema(self, operation: Operation) -> Optional[RSFormCached]:
|
||||
''' Get schema by Operation. '''
|
||||
if operation.result_id is None:
|
||||
return None
|
||||
if operation.result_id in self._schema_by_id:
|
||||
return self._schema_by_id[operation.result_id]
|
||||
else:
|
||||
schema = RSFormCached.from_id(operation.result_id)
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
return schema
|
||||
|
||||
def get_operation(self, schema: int) -> Operation:
|
||||
''' Get operation by schema. '''
|
||||
for operation in self.operations:
|
||||
if operation.result_id == schema:
|
||||
return operation
|
||||
raise ValueError(f'Operation for schema {schema} not found')
|
||||
|
||||
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id == parent_cst:
|
||||
return item.child_id
|
||||
return None
|
||||
|
||||
def get_inheritors_list(self, target: list[int], operation: int) -> list[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
result = []
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id in target:
|
||||
result.append(item.child_id)
|
||||
return result
|
||||
|
||||
def get_successor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom including substitutions. '''
|
||||
for sub in self.substitutions[operation]:
|
||||
if sub.original_id == parent_cst:
|
||||
return self.get_inheritor(sub.substitution_id, operation)
|
||||
return self.get_inheritor(parent_cst, operation)
|
||||
|
||||
def insert_schema(self, schema: RSFormCached) -> None:
|
||||
''' Insert new schema. '''
|
||||
if not self._schema_by_id.get(schema.model.pk):
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
|
||||
def insert_operation(self, operation: Operation) -> None:
|
||||
''' Insert new operation. '''
|
||||
self.operations.append(operation)
|
||||
self.operation_by_id[operation.pk] = operation
|
||||
self.graph.add_node(operation.pk)
|
||||
if self.is_loaded:
|
||||
self.substitutions[operation.pk] = []
|
||||
self.inheritance[operation.pk] = []
|
||||
|
||||
def insert_argument(self, argument: Argument) -> None:
|
||||
''' Insert new argument. '''
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
||||
''' Insert new inheritance. '''
|
||||
self.inheritance[inheritance.operation_id].append(inheritance)
|
||||
|
||||
def insert_substitution(self, sub: Substitution) -> None:
|
||||
''' Insert new substitution. '''
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
|
||||
def remove_cst(self, operation: int, target: list[int]) -> None:
|
||||
''' Remove constituents from operation. '''
|
||||
subs_to_delete = [
|
||||
sub for sub in self.substitutions[operation]
|
||||
if sub.original_id in target or sub.substitution_id in target
|
||||
]
|
||||
for sub in subs_to_delete:
|
||||
self.substitutions[operation].remove(sub)
|
||||
inherit_to_delete = [item for item in self.inheritance[operation] if item.child_id in target]
|
||||
for item in inherit_to_delete:
|
||||
self.inheritance[operation].remove(item)
|
||||
|
||||
def remove_schema(self, schema: RSFormCached) -> None:
|
||||
''' Remove schema from cache. '''
|
||||
self._schemas.remove(schema)
|
||||
del self._schema_by_id[schema.model.pk]
|
||||
|
||||
def remove_operation(self, operation: int) -> None:
|
||||
''' Remove operation from cache. '''
|
||||
target = self.operation_by_id[operation]
|
||||
self.graph.remove_node(operation)
|
||||
if target.result_id in self._schema_by_id:
|
||||
self._schemas.remove(self._schema_by_id[target.result_id])
|
||||
del self._schema_by_id[target.result_id]
|
||||
self.operations.remove(self.operation_by_id[operation])
|
||||
del self.operation_by_id[operation]
|
||||
if self.is_loaded:
|
||||
del self.substitutions[operation]
|
||||
del self.inheritance[operation]
|
||||
|
||||
def remove_argument(self, argument: Argument) -> None:
|
||||
''' Remove argument from cache. '''
|
||||
self.graph.remove_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
def remove_substitution(self, target: Substitution) -> None:
|
||||
''' Remove substitution from cache. '''
|
||||
self.substitutions[target.operation_id].remove(target)
|
||||
|
||||
def remove_inheritance(self, target: Inheritance) -> None:
|
||||
''' Remove inheritance from cache. '''
|
||||
self.inheritance[target.operation_id].remove(target)
|
||||
|
||||
def unfold_sub(self, sub: Substitution) -> tuple[RSFormCached, RSFormCached, Constituenta, Constituenta]:
|
||||
''' Unfold substitution into original and substitution forms. '''
|
||||
operation = self.operation_by_id[sub.operation_id]
|
||||
parents = self.graph.inputs[operation.pk]
|
||||
original_cst = None
|
||||
substitution_cst = None
|
||||
original_schema = None
|
||||
substitution_schema = None
|
||||
for parent_id in parents:
|
||||
parent_schema = self.get_schema(self.operation_by_id[parent_id])
|
||||
if parent_schema is None:
|
||||
continue
|
||||
if sub.original_id in parent_schema.cache.by_id:
|
||||
original_schema = parent_schema
|
||||
original_cst = original_schema.cache.by_id[sub.original_id]
|
||||
if sub.substitution_id in parent_schema.cache.by_id:
|
||||
substitution_schema = parent_schema
|
||||
substitution_cst = substitution_schema.cache.by_id[sub.substitution_id]
|
||||
if original_schema is None or substitution_schema is None or original_cst is None or substitution_cst is None:
|
||||
raise ValueError(f'Parent schema for Substitution-{sub.pk} not found.')
|
||||
return original_schema, substitution_schema, original_cst, substitution_cst
|
||||
|
||||
def _insert_new(self, schema: RSFormCached) -> None:
|
||||
self._schemas.append(schema)
|
||||
self._schema_by_id[schema.model.pk] = schema
|
|
@ -4,7 +4,7 @@ from typing import Optional
|
|||
from apps.library.models import LibraryItem, LibraryItemType
|
||||
from apps.rsform.models import Constituenta, RSFormCached
|
||||
|
||||
from .OperationSchema import CstSubstitution, OperationSchema
|
||||
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
|
||||
|
||||
|
||||
def _get_oss_hosts(item: LibraryItem) -> list[LibraryItem]:
|
||||
|
@ -22,7 +22,7 @@ class PropagationFacade:
|
|||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchema(host).after_create_cst(source, new_cst)
|
||||
OperationSchemaCached(host).after_create_cst(source, new_cst)
|
||||
|
||||
@staticmethod
|
||||
def after_change_cst_type(source: RSFormCached, target: Constituenta, exclude: Optional[list[int]] = None) -> None:
|
||||
|
@ -30,7 +30,7 @@ class PropagationFacade:
|
|||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchema(host).after_change_cst_type(source, target)
|
||||
OperationSchemaCached(host).after_change_cst_type(source, target)
|
||||
|
||||
@staticmethod
|
||||
def after_update_cst(
|
||||
|
@ -44,7 +44,7 @@ class PropagationFacade:
|
|||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchema(host).after_update_cst(source, target, data, old_data)
|
||||
OperationSchemaCached(host).after_update_cst(source, target, data, old_data)
|
||||
|
||||
@staticmethod
|
||||
def before_delete_cst(source: RSFormCached, target: list[Constituenta],
|
||||
|
@ -53,7 +53,7 @@ class PropagationFacade:
|
|||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchema(host).before_delete_cst(source, target)
|
||||
OperationSchemaCached(host).before_delete_cst(source, target)
|
||||
|
||||
@staticmethod
|
||||
def before_substitute(source: RSFormCached, substitutions: CstSubstitution,
|
||||
|
@ -62,7 +62,7 @@ class PropagationFacade:
|
|||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchema(host).before_substitute(source, substitutions)
|
||||
OperationSchemaCached(host).before_substitute(source, substitutions)
|
||||
|
||||
@staticmethod
|
||||
def before_delete_schema(item: LibraryItem, exclude: Optional[list[int]] = None) -> None:
|
||||
|
|
|
@ -6,6 +6,7 @@ from .Inheritance import Inheritance
|
|||
from .Layout import Layout
|
||||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .OperationSchemaCached import OperationSchemaCached
|
||||
from .PropagationFacade import PropagationFacade
|
||||
from .Reference import Reference
|
||||
from .Substitution import Substitution
|
||||
|
|
|
@ -13,7 +13,7 @@ from apps.rsform.serializers import SubstitutionSerializerBase
|
|||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType
|
||||
from ..models import Argument, Block, Inheritance, Layout, Operation, OperationType, Substitution
|
||||
from .basics import NodeSerializer, PositionSerializer, SubstitutionExSerializer
|
||||
|
||||
|
||||
|
@ -529,13 +529,12 @@ class OperationSchemaSerializer(StrictModelSerializer):
|
|||
def to_representation(self, instance: LibraryItem):
|
||||
result = LibraryItemDetailsSerializer(instance).data
|
||||
del result['versions']
|
||||
oss = OperationSchema(instance)
|
||||
result['layout'] = oss.layout().data
|
||||
result['layout'] = Layout.objects.get(oss=instance).data
|
||||
result['operations'] = []
|
||||
result['blocks'] = []
|
||||
result['arguments'] = []
|
||||
result['substitutions'] = []
|
||||
for operation in oss.operations().order_by('pk'):
|
||||
for operation in Operation.objects.filter(oss=instance).order_by('pk'):
|
||||
operation_data = OperationSerializer(operation).data
|
||||
operation_result = operation.result
|
||||
operation_data['is_import'] = \
|
||||
|
@ -543,11 +542,11 @@ class OperationSchemaSerializer(StrictModelSerializer):
|
|||
(operation_result.owner_id != instance.owner_id or
|
||||
operation_result.location != instance.location)
|
||||
result['operations'].append(operation_data)
|
||||
for block in oss.blocks().order_by('pk'):
|
||||
for block in Block.objects.filter(oss=instance).order_by('pk'):
|
||||
result['blocks'].append(BlockSerializer(block).data)
|
||||
for argument in oss.arguments().order_by('order'):
|
||||
for argument in Argument.objects.filter(operation__oss=instance).order_by('order'):
|
||||
result['arguments'].append(ArgumentSerializer(argument).data)
|
||||
for substitution in oss.substitutions().values(
|
||||
for substitution in Substitution.objects.filter(operation__oss=instance).values(
|
||||
'operation',
|
||||
'original',
|
||||
'substitution',
|
||||
|
|
|
@ -64,7 +64,7 @@ class TestChangeAttributes(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class TestChangeConstituents(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ class TestChangeOperations(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ class TestChangeSubstitutions(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class TestOssBlocks(EndpointTester):
|
|||
{'nodeID': 'b' + str(self.block2.pk), 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5},
|
||||
]
|
||||
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
''' Testing API: Operation Schema - operations manipulation. '''
|
||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
|
||||
from apps.oss.models import Operation, OperationSchema, OperationType, Reference
|
||||
from apps.oss.models import Argument, Operation, OperationSchema, OperationType, Reference
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
|
||||
|
@ -64,7 +64,7 @@ class TestOssOperations(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
@ -264,7 +264,7 @@ class TestOssOperations(EndpointTester):
|
|||
self.owned.refresh_from_db()
|
||||
new_operation_id = response.data['new_operation']
|
||||
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
|
||||
arguments = self.owned.arguments()
|
||||
arguments = Argument.objects.filter(operation__oss=self.owned.model)
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation_id, argument=self.operation1))
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation_id, argument=self.operation3))
|
||||
self.assertNotEqual(new_operation['result'], None)
|
||||
|
|
|
@ -60,7 +60,7 @@ class TestOssViewset(EndpointTester):
|
|||
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||
{'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
|
||||
]
|
||||
layout = self.owned.layout()
|
||||
layout = OperationSchema.layoutQ(self.owned_id)
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
@ -139,7 +139,7 @@ class TestOssViewset(EndpointTester):
|
|||
self.toggle_admin(False)
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
self.assertEqual(self.owned.layout().data, data['data'])
|
||||
self.assertEqual(OperationSchema.layoutQ(self.owned_id).data, data['data'])
|
||||
|
||||
self.executeForbidden(data=data, item=self.unowned_id)
|
||||
self.executeForbidden(data=data, item=self.private_id)
|
||||
|
|
|
@ -117,11 +117,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
''' Endpoint: Update schema layout. '''
|
||||
serializer = s.LayoutSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
item = self._get_item()
|
||||
with transaction.atomic():
|
||||
oss.update_layout(serializer.validated_data['data'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
return Response(status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(oss.model).data)
|
||||
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(
|
||||
summary='create block',
|
||||
|
@ -137,13 +137,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='create-block')
|
||||
def create_block(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create Block. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CreateBlockSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
|
||||
|
@ -157,7 +158,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'width': position['width'],
|
||||
'height': position['height'],
|
||||
})
|
||||
oss.update_layout(layout)
|
||||
m.Layout.update_data(pk, layout)
|
||||
if len(children_blocks) > 0:
|
||||
for block in children_blocks:
|
||||
block.parent = new_block
|
||||
|
@ -166,13 +167,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
for operation in children_operations:
|
||||
operation.parent = new_block
|
||||
m.Operation.objects.bulk_update(children_operations, ['parent'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_block': new_block.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -190,17 +191,15 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='update-block')
|
||||
def update_block(self, request: Request, pk) -> HttpResponse:
|
||||
''' Update Block. '''
|
||||
item = self._get_item()
|
||||
serializer = s.UpdateBlockSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
block: m.Block = cast(m.Block, serializer.validated_data['target'])
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
if 'layout' in serializer.validated_data:
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
if 'title' in serializer.validated_data['item_data']:
|
||||
block.title = serializer.validated_data['item_data']['title']
|
||||
if 'description' in serializer.validated_data['item_data']:
|
||||
|
@ -208,10 +207,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
if 'parent' in serializer.validated_data['item_data']:
|
||||
block.parent = serializer.validated_data['item_data']['parent']
|
||||
block.save(update_fields=['title', 'description', 'parent'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
if 'layout' in serializer.validated_data:
|
||||
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(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -228,24 +230,25 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='delete-block')
|
||||
def delete_block(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Block. '''
|
||||
item = self._get_item()
|
||||
serializer = s.DeleteBlockSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
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.delete_block(block)
|
||||
oss.update_layout(layout)
|
||||
oss.save(update_fields=['time_update'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -262,26 +265,27 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='move-items')
|
||||
def move_items(self, request: Request, pk) -> HttpResponse:
|
||||
''' Move items to another parent. '''
|
||||
item = self._get_item()
|
||||
serializer = s.MoveItemsSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
layout = serializer.validated_data['layout']
|
||||
with transaction.atomic():
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
for operation in serializer.validated_data['operations']:
|
||||
operation.parent = serializer.validated_data['destination']
|
||||
operation.save(update_fields=['parent'])
|
||||
for block in serializer.validated_data['blocks']:
|
||||
block.parent = serializer.validated_data['destination']
|
||||
block.save(update_fields=['parent'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -298,13 +302,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='create-schema')
|
||||
def create_schema(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create schema. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CreateSchemaSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
data = serializer.validated_data['item_data']
|
||||
|
@ -318,15 +323,15 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'width': position['width'],
|
||||
'height': position['height']
|
||||
})
|
||||
oss.update_layout(layout)
|
||||
m.Layout.update_data(pk, layout)
|
||||
oss.create_input(new_operation)
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': new_operation.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -345,13 +350,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='clone-schema')
|
||||
def clone_schema(self, request: Request, pk) -> HttpResponse:
|
||||
''' Clone schema. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CloneSchemaSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
with transaction.atomic():
|
||||
|
@ -363,7 +368,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
|
||||
new_schema = source_schema
|
||||
new_schema.pk = None
|
||||
new_schema.owner = oss.model.owner
|
||||
new_schema.owner = item.owner
|
||||
new_schema.title = title
|
||||
new_schema.alias = alias
|
||||
new_schema.save()
|
||||
|
@ -380,6 +385,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
new_operation.operation_type = m.OperationType.INPUT
|
||||
new_operation.result = None
|
||||
new_operation.save()
|
||||
new_operation.setQ_result(new_schema)
|
||||
|
||||
layout.append({
|
||||
'nodeID': 'o' + str(new_operation.pk),
|
||||
|
@ -388,16 +394,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'width': position['width'],
|
||||
'height': position['height']
|
||||
})
|
||||
oss.refresh_from_db()
|
||||
oss.set_input(new_operation.pk, new_schema)
|
||||
oss.update_layout(layout)
|
||||
oss.save(update_fields=['time_update'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': new_operation.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -416,13 +420,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='import-schema')
|
||||
def import_schema(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create operation with existing schema. '''
|
||||
item = self._get_item()
|
||||
serializer = s.ImportSchemaSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
data = serializer.validated_data['item_data']
|
||||
|
@ -438,20 +443,20 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'width': position['width'],
|
||||
'height': position['height']
|
||||
})
|
||||
oss.update_layout(layout)
|
||||
m.Layout.update_data(pk, layout)
|
||||
|
||||
if serializer.validated_data['clone_source']:
|
||||
prototype: LibraryItem = serializer.validated_data['source']
|
||||
new_operation.result = _create_clone(prototype, new_operation, oss.model)
|
||||
new_operation.result = _create_clone(prototype, new_operation, item)
|
||||
new_operation.save(update_fields=["result"])
|
||||
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': new_operation.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -470,13 +475,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='create-reference')
|
||||
def create_reference(self, request: Request, pk) -> HttpResponse:
|
||||
''' Clone schema. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CreateReferenceSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
with transaction.atomic():
|
||||
|
@ -489,14 +495,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'width': position['width'],
|
||||
'height': position['height']
|
||||
})
|
||||
oss.update_layout(layout)
|
||||
oss.save(update_fields=['time_update'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': new_operation.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -514,13 +520,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='create-synthesis')
|
||||
def create_synthesis(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create Synthesis operation from arguments. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CreateSynthesisSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
position = serializer.validated_data['position']
|
||||
data = serializer.validated_data['item_data']
|
||||
|
@ -537,14 +544,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
oss.set_arguments(new_operation.pk, serializer.validated_data['arguments'])
|
||||
oss.set_substitutions(new_operation.pk, serializer.validated_data['substitutions'])
|
||||
oss.execute_operation(new_operation)
|
||||
oss.update_layout(layout)
|
||||
oss.save(update_fields=['time_update'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': new_operation.pk,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -562,17 +569,19 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='update-operation')
|
||||
def update_operation(self, request: Request, pk) -> HttpResponse:
|
||||
''' Update Operation arguments and parameters. '''
|
||||
item = self._get_item()
|
||||
serializer = s.UpdateOperationSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchemaCached(item)
|
||||
with transaction.atomic():
|
||||
if 'layout' in serializer.validated_data:
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
layout = serializer.validated_data['layout']
|
||||
m.Layout.update_data(pk, layout)
|
||||
if 'alias' in serializer.validated_data['item_data']:
|
||||
operation.alias = serializer.validated_data['item_data']['alias']
|
||||
if 'title' in serializer.validated_data['item_data']:
|
||||
|
@ -594,11 +603,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
oss.set_arguments(operation.pk, serializer.validated_data['arguments'])
|
||||
if 'substitutions' in serializer.validated_data:
|
||||
oss.set_substitutions(operation.pk, serializer.validated_data['substitutions'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -615,32 +624,33 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||
def delete_operation(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Operation. '''
|
||||
item = self._get_item()
|
||||
serializer = s.DeleteOperationSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
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.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
|
||||
oss.update_layout(layout)
|
||||
m.Layout.update_data(pk, layout)
|
||||
if old_schema is not None:
|
||||
if serializer.validated_data['delete_schema']:
|
||||
m.PropagationFacade.before_delete_schema(old_schema)
|
||||
old_schema.delete()
|
||||
elif old_schema.is_synced(oss.model):
|
||||
elif old_schema.is_synced(item):
|
||||
old_schema.visible = True
|
||||
old_schema.save(update_fields=['visible'])
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -657,23 +667,25 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='delete-reference')
|
||||
def delete_reference(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Reference Operation. '''
|
||||
item = self._get_item()
|
||||
serializer = s.DeleteReferenceSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
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.update_layout(layout)
|
||||
m.Layout.update_data(pk, layout)
|
||||
oss.delete_reference(operation, serializer.validated_data['keep_connections'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -690,9 +702,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='create-input')
|
||||
def create_input(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create input RSForm. '''
|
||||
item = self._get_item()
|
||||
serializer = s.TargetOperationSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
@ -706,17 +719,18 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'target': msg.operationResultNotEmpty(operation.alias)
|
||||
})
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchema(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
with transaction.atomic():
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
schema = oss.create_input(operation)
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
'new_schema': LibraryItemSerializer(schema.model).data,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
'oss': s.OperationSchemaSerializer(item).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -734,12 +748,14 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='set-input')
|
||||
def set_input(self, request: Request, pk) -> HttpResponse:
|
||||
''' Set input schema for target operation. '''
|
||||
item = self._get_item()
|
||||
serializer = s.SetOperationInputSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
layout = serializer.validated_data['layout']
|
||||
target_operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
schema: Optional[LibraryItem] = serializer.validated_data['input']
|
||||
if schema is not None:
|
||||
|
@ -753,20 +769,20 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
raise serializers.ValidationError({
|
||||
'input': msg.operationInputAlreadyConnected()
|
||||
})
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchemaCached(item)
|
||||
old_schema = target_operation.result
|
||||
with transaction.atomic():
|
||||
if old_schema is not None:
|
||||
if old_schema.is_synced(oss.model):
|
||||
if old_schema.is_synced(item):
|
||||
old_schema.visible = True
|
||||
old_schema.save(update_fields=['visible'])
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
oss.set_input(target_operation.pk, schema)
|
||||
oss.save(update_fields=['time_update'])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -783,9 +799,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='execute-operation')
|
||||
def execute_operation(self, request: Request, pk) -> HttpResponse:
|
||||
''' Execute operation. '''
|
||||
item = self._get_item()
|
||||
serializer = s.TargetOperationSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
context={'oss': item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
@ -799,15 +816,16 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
'target': msg.operationResultNotEmpty(operation.alias)
|
||||
})
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
oss = m.OperationSchemaCached(item)
|
||||
layout = serializer.validated_data['layout']
|
||||
with transaction.atomic():
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
oss.execute_operation(operation)
|
||||
oss.save(update_fields=['time_update'])
|
||||
m.Layout.update_data(pk, layout)
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
data=s.OperationSchemaSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -861,7 +879,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
data = serializer.validated_data
|
||||
oss = m.OperationSchema(LibraryItem.objects.get(pk=data['oss']))
|
||||
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']))
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user