R: Refactoring caches

Improve rsform and oss caching in propagation scenarios
This commit is contained in:
Ivan 2025-11-09 13:48:43 +03:00
parent c875a549c9
commit 380877e485
29 changed files with 279 additions and 269 deletions

View File

@ -67,7 +67,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
def perform_destroy(self, instance: m.LibraryItem) -> None:
if instance.item_type == m.LibraryItemType.RSFORM:
PropagationFacade.before_delete_schema(instance)
PropagationFacade().before_delete_schema(instance.pk)
super().perform_destroy(instance)
if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA:
schemas = list(OperationSchema.owned_schemasQ(instance))
@ -172,7 +172,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
clone.location = data.get('location', m.LocationHead.USER)
clone.save()
RSFormCached(clone).insert_from(item.pk, request.data['items'] if 'items' in request.data else None)
RSFormCached(clone.pk).insert_from(item.pk, request.data['items'] if 'items' in request.data else None)
return Response(
status=c.HTTP_201_CREATED,

View File

@ -18,7 +18,7 @@ from .Substitution import Substitution
class OperationSchema:
''' Operations schema API wrapper. No caching, propagation and minimal side effects. '''
def __init__(self, model: LibraryItem):
def __init__(self, model: LibraryItem) -> None:
self.model = model
@staticmethod
@ -43,19 +43,18 @@ class OperationSchema:
return Layout.objects.get(oss_id=itemID)
@staticmethod
def create_input(oss: LibraryItem, operation: Operation) -> RSFormCached:
def create_input(oss_id: int, operation: Operation) -> LibraryItem:
''' Create input RSForm for given Operation. '''
schema = RSFormCached.create(
owner=oss.owner,
alias=operation.alias,
title=operation.title,
description=operation.description,
visible=False,
access_policy=oss.access_policy,
location=oss.location
)
Editor.set(schema.model.pk, oss.getQ_editors().values_list('pk', flat=True))
operation.setQ_result(schema.model)
oss = LibraryItem.objects.get(pk=oss_id)
schema = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, owner=oss.owner,
alias=operation.alias,
title=operation.title,
description=operation.description,
visible=False,
access_policy=oss.access_policy,
location=oss.location)
Editor.set(schema.pk, oss.getQ_editors().values_list('pk', flat=True))
operation.setQ_result(schema)
return schema
def refresh_from_db(self) -> None:
@ -132,7 +131,7 @@ class OperationSchema:
if not schemas:
return
substitutions = operation.getQ_substitutions()
receiver = OperationSchema.create_input(self.model, operation)
receiver = RSFormCached(OperationSchema.create_input(self.model.pk, operation).pk)
parents: dict = {}
children: dict = {}
@ -149,7 +148,7 @@ class OperationSchema:
translated_substitutions.append((original, replacement))
receiver.substitute(translated_substitutions)
for cst in Constituenta.objects.filter(schema=receiver.model).order_by('order'):
for cst in Constituenta.objects.filter(schema_id=receiver.pk).order_by('order'):
parent = parents.get(cst.pk)
assert parent is not None
Inheritance.objects.create(

View File

@ -11,6 +11,7 @@ from .Inheritance import Inheritance
from .Operation import Operation
from .OperationSchema import OperationSchema
from .OssCache import OssCache
from .PropagationContext import PropagationContext
from .PropagationEngine import PropagationEngine
from .Substitution import Substitution
from .utils import CstMapping, CstSubstitution, create_dependant_mapping, extract_data_references
@ -19,10 +20,11 @@ from .utils import CstMapping, CstSubstitution, create_dependant_mapping, extrac
class OperationSchemaCached:
''' Operations schema API with caching. '''
def __init__(self, model: LibraryItem):
self.model = model
self.cache = OssCache(model.pk)
self.engine = PropagationEngine(self.cache)
def __init__(self, item_id: int, context: PropagationContext) -> None:
self.pk = item_id
self.context = context
self.cache = OssCache(item_id, context)
self.engine = PropagationEngine(self.cache, context)
def delete_replica(self, target: int, keep_connections: bool = False, keep_constituents: bool = False):
''' Delete Replica Operation. '''
@ -53,7 +55,7 @@ class OperationSchemaCached:
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)
child_schema = self.cache.get_result(child_operation)
if child_schema is None:
continue
self.engine.undo_substitutions_cst(ids, child_operation, child_schema)
@ -70,15 +72,15 @@ class OperationSchemaCached:
''' Set input schema for operation. '''
operation = self.cache.operation_by_id[target]
has_children = bool(self.cache.extend_graph.outputs[target])
old_schema = self.cache.get_schema(operation)
old_schema = self.cache.get_result(operation)
if schema is None and old_schema is None or \
(schema is not None and old_schema is not None and schema.pk == old_schema.model.pk):
(schema is not None and old_schema is not None and schema.pk == old_schema.pk):
return
if old_schema is not None:
if has_children:
self.before_delete_cst(old_schema.model.pk, [cst.pk for cst in old_schema.cache.constituents])
self.cache.remove_schema(old_schema)
self.before_delete_cst(old_schema.pk, [cst.pk for cst in old_schema.cache.constituents])
self.context.invalidate(old_schema.pk)
operation.setQ_result(schema)
if schema is not None:
@ -88,8 +90,8 @@ class OperationSchemaCached:
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')))
cst_list = list(Constituenta.objects.filter(schema_id=schema.pk).order_by('order'))
self.after_create_cst(schema.pk, cst_list)
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
''' Set arguments of target Operation. '''
@ -126,7 +128,7 @@ class OperationSchemaCached:
''' Clear all arguments for target Operation. '''
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
schema = self.cache.get_schema(operation)
schema = self.cache.get_result(operation)
processed: list[dict] = []
deleted: list[Substitution] = []
for current in operation.getQ_substitutions():
@ -172,8 +174,8 @@ class OperationSchemaCached:
if not schemas:
return False
substitutions = operation.getQ_substitutions()
receiver = OperationSchema.create_input(self.model, self.cache.operation_by_id[operation.pk])
self.cache.insert_schema(receiver)
new_schema = OperationSchema.create_input(self.pk, self.cache.operation_by_id[operation.pk])
receiver = self.context.get_schema(new_schema.pk)
parents: dict = {}
children: dict = {}
@ -190,7 +192,7 @@ class OperationSchemaCached:
translated_substitutions.append((original, replacement))
receiver.substitute(translated_substitutions)
for cst in Constituenta.objects.filter(schema=receiver.model).order_by('order'):
for cst in Constituenta.objects.filter(schema_id=receiver.pk).order_by('order'):
parent = parents.get(cst.pk)
assert parent is not None
Inheritance.objects.create(
@ -204,31 +206,31 @@ class OperationSchemaCached:
receiver.resolve_all_text()
if self.cache.extend_graph.outputs[operation.pk]:
receiver_items = list(Constituenta.objects.filter(schema=receiver.model).order_by('order'))
self.after_create_cst(receiver, receiver_items)
receiver.model.save(update_fields=['time_update'])
receiver_items = list(Constituenta.objects.filter(schema_id=receiver.pk).order_by('order'))
self.after_create_cst(receiver.pk, receiver_items)
return True
def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]):
def relocate_down(self, destinationID: int, items: list[int]):
''' Move list of Constituents to destination Schema inheritor. '''
self.cache.ensure_loaded_subs()
self.cache.insert_schema(source)
self.cache.insert_schema(destination)
operation = self.cache.get_operation(destination.model.pk)
operation = self.cache.get_operation(destinationID)
destination = self.context.get_schema(destinationID)
self.engine.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_id__in=items).delete()
def relocate_up(self, source: RSFormCached, destination: RSFormCached,
def relocate_up(self, sourceID: int, destinationID: int,
item_ids: list[int]) -> list[Constituenta]:
''' Move list of Constituents upstream to destination Schema. '''
self.cache.ensure_loaded_subs()
self.cache.insert_schema(source)
self.cache.insert_schema(destination)
operation = self.cache.get_operation(source.model.pk)
source = self.context.get_schema(sourceID)
destination = self.context.get_schema(destinationID)
operation = self.cache.get_operation(sourceID)
alias_mapping: dict[str, str] = {}
for item in self.cache.inheritance[operation.pk]:
if item.parent_id in destination.cache.by_id:
@ -236,7 +238,7 @@ class OperationSchemaCached:
destination_cst = destination.cache.by_id[item.parent_id]
alias_mapping[source_cst.alias] = destination_cst.alias
new_items = destination.insert_from(source.model.pk, item_ids, alias_mapping)
new_items = destination.insert_from(sourceID, item_ids, alias_mapping)
for (cst, new_cst) in new_items:
new_inheritance = Inheritance.objects.create(
operation=operation,
@ -245,19 +247,18 @@ class OperationSchemaCached:
)
self.cache.insert_inheritance(new_inheritance)
new_constituents = [item[1] for item in new_items]
self.after_create_cst(destination, new_constituents, exclude=[operation.pk])
destination.model.save(update_fields=['time_update'])
self.after_create_cst(destinationID, new_constituents, exclude=[operation.pk])
return new_constituents
def after_create_cst(
self, source: RSFormCached,
self, sourceID: int,
cst_list: list[Constituenta],
exclude: Optional[list[int]] = None
) -> None:
''' Trigger cascade resolutions when new Constituenta is created. '''
self.cache.insert_schema(source)
source = self.context.get_schema(sourceID)
alias_mapping = create_dependant_mapping(source, cst_list)
operation = self.cache.get_operation(source.model.pk)
operation = self.cache.get_operation(source.pk)
self.engine.on_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude)
def after_change_cst_type(self, schemaID: int, target: int, new_type: CstType) -> None:
@ -265,10 +266,10 @@ class OperationSchemaCached:
operation = self.cache.get_operation(schemaID)
self.engine.on_change_cst_type(operation.pk, target, new_type)
def after_update_cst(self, source: RSFormCached, target: int, data: dict, old_data: dict) -> None:
def after_update_cst(self, sourceID: int, target: int, data: dict, old_data: dict) -> None:
''' Trigger cascade resolutions when Constituenta data is changed. '''
self.cache.insert_schema(source)
operation = self.cache.get_operation(source.model.pk)
operation = self.cache.get_operation(sourceID)
source = self.context.get_schema(sourceID)
depend_aliases = extract_data_references(data, old_data)
alias_mapping: CstMapping = {}
for alias in depend_aliases:
@ -298,18 +299,18 @@ class OperationSchemaCached:
if target.result_id is None:
return
for argument in arguments:
parent_schema = self.cache.get_schema(argument)
if parent_schema is not None:
parent_schema = self.cache.get_result(argument)
if parent_schema:
self.engine.delete_inherited(target.pk, [cst.pk for cst in parent_schema.cache.constituents])
def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
''' Trigger cascade resolutions after arguments are created. '''
schema = self.cache.get_schema(target)
if schema is None:
schema = self.cache.get_result(target)
if not schema:
return
for argument in arguments:
parent_schema = self.cache.get_schema(argument)
if parent_schema is None:
parent_schema = self.cache.get_result(argument)
if not parent_schema:
continue
self.engine.inherit_cst(
target_operation=target.pk,
@ -347,7 +348,7 @@ class OperationSchemaCached:
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.model.pk, cst_mapping)
self.before_substitute(schema.pk, cst_mapping)
schema.substitute(cst_mapping)
for sub in added:
self.cache.insert_substitution(sub)

View File

@ -8,6 +8,7 @@ from apps.rsform.models import RSFormCached
from .Argument import Argument
from .Inheritance import Inheritance
from .Operation import Operation, OperationType
from .PropagationContext import PropagationContext
from .Replica import Replica
from .Substitution import Substitution
@ -15,10 +16,9 @@ from .Substitution import Substitution
class OssCache:
''' Cache for OSS data. '''
def __init__(self, item_id: int):
def __init__(self, item_id: int, context: PropagationContext) -> None:
self._item_id = item_id
self._schemas: list[RSFormCached] = []
self._schema_by_id: dict[int, RSFormCached] = {}
self._context = context
self.operations = list(Operation.objects.filter(oss_id=item_id).only('result_id', 'operation_type'))
self.operation_by_id = {operation.pk: operation for operation in self.operations}
@ -60,27 +60,11 @@ class OssCache:
'operation_id', 'parent_id', 'child_id'):
self.inheritance[item.operation_id].append(item)
def get_schema(self, operation: Operation) -> Optional[RSFormCached]:
def get_result(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_schema_by_id(self, target: int) -> RSFormCached:
''' Get schema by Operation. '''
if target in self._schema_by_id:
return self._schema_by_id[target]
else:
schema = RSFormCached.from_id(target)
schema.cache.ensure_loaded()
self._insert_new(schema)
return schema
return self._context.get_schema(operation.result_id)
def get_operation(self, schemaID: int) -> Operation:
''' Get operation by schema. '''
@ -111,12 +95,6 @@ class OssCache:
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_argument(self, argument: Argument) -> None:
''' Insert new argument. '''
self.graph.add_edge(argument.argument_id, argument.operation_id)
@ -145,19 +123,12 @@ class OssCache:
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)
self.extend_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._context.invalidate(target.result_id)
self.operations.remove(self.operation_by_id[operation])
del self.operation_by_id[operation]
if operation in self.replica_original:
@ -182,7 +153,3 @@ class OssCache:
def remove_inheritance(self, target: Inheritance) -> None:
''' Remove inheritance from cache. '''
self.inheritance[target.operation_id].remove(target)
def _insert_new(self, schema: RSFormCached) -> None:
self._schemas.append(schema)
self._schema_by_id[schema.model.pk] = schema

View File

@ -0,0 +1,27 @@
''' Models: Propagation context. '''
from apps.rsform.models import RSFormCached
class PropagationContext:
''' Propagation context. '''
def __init__(self) -> None:
self._cache: dict[int, RSFormCached] = {}
def get_schema(self, item_id: int) -> RSFormCached:
''' Get schema by ID. '''
if item_id not in self._cache:
schema = RSFormCached(item_id)
schema.cache.ensure_loaded()
self._cache[item_id] = schema
return self._cache[item_id]
def clear(self) -> None:
''' Clear cache. '''
self._cache = {}
def invalidate(self, item_id: int | None) -> None:
''' Invalidate schema by ID. '''
if item_id in self._cache:
del self._cache[item_id]

View File

@ -8,6 +8,7 @@ from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached
from .Inheritance import Inheritance
from .Operation import Operation
from .OssCache import OssCache
from .PropagationContext import PropagationContext
from .Substitution import Substitution
from .utils import (
CstMapping,
@ -21,8 +22,9 @@ from .utils import (
class PropagationEngine:
''' OSS changes propagation engine. '''
def __init__(self, cache: OssCache):
def __init__(self, cache: OssCache, context: PropagationContext) -> None:
self.cache = cache
self.context = context
def on_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None:
''' Trigger cascade resolutions when Constituenta type is changed. '''
@ -35,7 +37,7 @@ class PropagationEngine:
successor_id = self.cache.get_inheritor(cst_id, child_id)
if successor_id is None:
continue
child_schema = self.cache.get_schema(child_operation)
child_schema = self.cache.get_result(child_operation)
if child_schema is None:
continue
if child_schema.change_cst_type(successor_id, ctype):
@ -67,7 +69,7 @@ class PropagationEngine:
) -> None:
''' Execute inheritance of Constituenta. '''
operation = self.cache.operation_by_id[target_operation]
destination = self.cache.get_schema(operation)
destination = self.cache.get_result(operation)
if destination is None:
return
@ -104,7 +106,7 @@ class PropagationEngine:
successor_id = self.cache.get_inheritor(cst_id, child_id)
if successor_id is None:
continue
child_schema = self.cache.get_schema(child_operation)
child_schema = self.cache.get_result(child_operation)
assert child_schema is not None
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
alias_mapping = cst_mapping_to_alias(new_mapping)
@ -176,7 +178,7 @@ class PropagationEngine:
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
child_schema = self.cache.get_result(child_operation)
if child_schema is None:
continue
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
@ -193,7 +195,7 @@ class PropagationEngine:
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
child_schema = self.cache.get_result(child_operation)
if child_schema is None:
continue
@ -213,26 +215,26 @@ class PropagationEngine:
self.on_delete_attribution(child_id, deleted)
Attribution.objects.filter(pk__in=[attrib.pk for attrib in deleted]).delete()
def on_delete_inherited(self, operation: int, target: list[int]) -> None:
def on_delete_inherited(self, operationID: int, target: list[int]) -> None:
''' Trigger cascade resolutions when Constituenta inheritance is deleted. '''
children = self.cache.extend_graph.outputs[operation]
children = self.cache.extend_graph.outputs[operationID]
if not children:
return
self.cache.ensure_loaded_subs()
for child_id in children:
self.delete_inherited(child_id, target)
def delete_inherited(self, operation_id: int, parent_ids: list[int]) -> None:
def delete_inherited(self, operationID: int, parents: list[int]) -> None:
''' Execute deletion of Constituenta inheritance. '''
operation = self.cache.operation_by_id[operation_id]
schema = self.cache.get_schema(operation)
operation = self.cache.operation_by_id[operationID]
schema = self.cache.get_result(operation)
if schema is None:
return
self.undo_substitutions_cst(parent_ids, operation, schema)
target_ids = self.cache.get_inheritors_list(parent_ids, operation_id)
self.on_delete_inherited(operation_id, target_ids)
self.undo_substitutions_cst(parents, operation, schema)
target_ids = self.cache.get_inheritors_list(parents, operationID)
self.on_delete_inherited(operationID, target_ids)
if target_ids:
self.cache.remove_cst(operation_id, target_ids)
self.cache.remove_cst(operationID, target_ids)
schema.delete_cst(target_ids)
def undo_substitutions_cst(self, target_ids: list[int], operation: Operation, schema: RSFormCached) -> None:
@ -254,7 +256,7 @@ class PropagationEngine:
if ignore_parents is None:
ignore_parents = []
operation_id = target.operation_id
original_schema = self.cache.get_schema_by_id(target.original.schema_id)
original_schema = self.context.get_schema(target.original.schema_id)
dependant = []
for cst_id in original_schema.get_dependant([target.original_id]):
if cst_id not in ignore_parents:
@ -374,7 +376,7 @@ class PropagationEngine:
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
child_schema = self.cache.get_result(child_operation)
if child_schema is None:
continue
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)

View File

@ -1,101 +1,110 @@
''' Models: Change propagation facade - managing all changes in OSS. '''
from typing import Optional
from apps.library.models import LibraryItem, LibraryItemType
from apps.library.models import LibraryItem
from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
from .PropagationContext import PropagationContext
def _get_oss_hosts(schemaID: int) -> list[LibraryItem]:
''' Get all hosts for LibraryItem. '''
return list(LibraryItem.objects.filter(operations__result_id=schemaID).only('pk').distinct())
def _get_oss_hosts(schemaID: int) -> list[int]:
''' Get all hosts for schema. '''
return list(LibraryItem.objects.filter(operations__result_id=schemaID).distinct().values_list('pk', flat=True))
class PropagationFacade:
''' Change propagation API. '''
@staticmethod
def after_create_cst(source: RSFormCached, new_cst: list[Constituenta],
def __init__(self) -> None:
self._context = PropagationContext()
self._oss: dict[int, OperationSchemaCached] = {}
def get_oss(self, schemaID: int) -> OperationSchemaCached:
''' Get OperationSchemaCached for schemaID. '''
if schemaID not in self._oss:
self._oss[schemaID] = OperationSchemaCached(schemaID, self._context)
return self._oss[schemaID]
def get_schema(self, schemaID: int) -> RSFormCached:
''' Get RSFormCached for schemaID. '''
return self._context.get_schema(schemaID)
def after_create_cst(self, new_cst: list[Constituenta],
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions when new constituenta is created. '''
hosts = _get_oss_hosts(source.model.pk)
if not new_cst:
return
source = new_cst[0].schema_id
hosts = _get_oss_hosts(source)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).after_create_cst(source, new_cst)
if exclude is None or host not in exclude:
self.get_oss(host).after_create_cst(source, new_cst)
@staticmethod
def after_change_cst_type(sourceID: int, target: int, new_type: CstType,
def after_change_cst_type(self, sourceID: int, target: int, new_type: CstType,
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions when constituenta type is changed. '''
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).after_change_cst_type(sourceID, target, new_type)
if exclude is None or host not in exclude:
self.get_oss(host).after_change_cst_type(sourceID, target, new_type)
@staticmethod
# pylint: disable=too-many-arguments, too-many-positional-arguments
def after_update_cst(
source: RSFormCached,
target: int,
data: dict,
old_data: dict,
self, sourceID: int, target: int,
data: dict, old_data: dict,
exclude: Optional[list[int]] = None
) -> None:
''' Trigger cascade resolutions when constituenta data is changed. '''
hosts = _get_oss_hosts(source.model.pk)
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).after_update_cst(source, target, data, old_data)
if exclude is None or host not in exclude:
self.get_oss(host).after_update_cst(sourceID, target, data, old_data)
@staticmethod
def before_delete_cst(sourceID: int, target: list[int],
def before_delete_cst(self, sourceID: int, target: list[int],
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before constituents are deleted. '''
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).before_delete_cst(sourceID, target)
if exclude is None or host not in exclude:
self.get_oss(host).before_delete_cst(sourceID, target)
@staticmethod
def before_substitute(sourceID: int, substitutions: CstSubstitution,
def before_substitute(self, sourceID: int, substitutions: CstSubstitution,
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before constituents are substituted. '''
if not substitutions:
return
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).before_substitute(sourceID, substitutions)
if exclude is None or host not in exclude:
self.get_oss(host).before_substitute(sourceID, substitutions)
@staticmethod
def before_delete_schema(item: LibraryItem, exclude: Optional[list[int]] = None) -> None:
def before_delete_schema(self, target: int, exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before schema is deleted. '''
if item.item_type != LibraryItemType.RSFORM:
return
hosts = _get_oss_hosts(item.pk)
hosts = _get_oss_hosts(target)
if not hosts:
return
ids = list(Constituenta.objects.filter(schema=item).order_by('order').values_list('pk', flat=True))
ids = list(Constituenta.objects.filter(schema_id=target).order_by('order').values_list('pk', flat=True))
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).before_delete_cst(item.pk, ids)
if exclude is None or host not in exclude:
self.get_oss(host).before_delete_cst(target, ids)
del self._oss[host]
@staticmethod
def after_create_attribution(sourceID: int, attributions: list[Attribution],
def after_create_attribution(self, sourceID: int,
attributions: list[Attribution],
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions when Attribution is created. '''
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).after_create_attribution(sourceID, attributions)
if exclude is None or host not in exclude:
self.get_oss(host).after_create_attribution(sourceID, attributions)
@staticmethod
def before_delete_attribution(sourceID: int,
def before_delete_attribution(self, sourceID: int,
attributions: list[Attribution],
exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before Attribution is deleted. '''
hosts = _get_oss_hosts(sourceID)
for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchemaCached(host).before_delete_attribution(sourceID, attributions)
if exclude is None or host not in exclude:
self.get_oss(host).before_delete_attribution(sourceID, attributions)

View File

@ -7,6 +7,7 @@ from .Layout import Layout
from .Operation import Operation, OperationType
from .OperationSchema import OperationSchema
from .OperationSchemaCached import OperationSchemaCached
from .PropagationContext import PropagationContext
from .PropagationFacade import PropagationFacade
from .Replica import Replica
from .Substitution import Substitution

View File

@ -609,8 +609,6 @@ class RelocateConstituentsSerializer(StrictSerializer):
attrs['destination'] = attrs['destination'].id
attrs['source'] = attrs['items'][0].schema_id
# TODO: check permissions for editing source and destination
if attrs['source'] == attrs['destination']:
raise serializers.ValidationError({
'destination': msg.sourceEqualDestination()
@ -625,15 +623,6 @@ class RelocateConstituentsSerializer(StrictSerializer):
'items': msg.RelocatingInherited()
})
oss = LibraryItem.objects \
.filter(operations__result_id=attrs['destination']) \
.filter(operations__result_id=attrs['source']).only('id')
if not oss.exists():
raise serializers.ValidationError({
'destination': msg.schemasNotConnected()
})
attrs['oss'] = oss[0].pk
if Argument.objects.filter(
operation__result_id=attrs['destination'],
argument__result_id=attrs['source']

View File

@ -1,6 +1,6 @@
''' Testing API: Change attributes of OSS and RSForms. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LocationHead
from apps.oss.models import Operation, OperationSchema, OperationType
from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import RSForm
from apps.users.models import User
from shared.EndpointTester import EndpointTester, decl_endpoint

View File

@ -1,7 +1,7 @@
''' Testing API: Change substitutions in OSS. '''
from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import Constituenta, CstType, RSForm
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -388,7 +388,7 @@ class TestChangeOperations(EndpointTester):
self.assertEqual(self.ks5.constituentsQ().count(), 8)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
@decl_endpoint('/api/oss/{item}/relocate-constituents', method='post')
def test_relocate_constituents_up(self):
ks1_old_count = self.ks1.constituentsQ().count()
ks4_old_count = self.ks4.constituentsQ().count()
@ -408,7 +408,7 @@ class TestChangeOperations(EndpointTester):
'items': [ks6A1.pk]
}
self.executeOK(data)
self.executeOK(data, item=self.owned_id)
ks6.model.refresh_from_db()
self.ks1.model.refresh_from_db()
self.ks4.model.refresh_from_db()
@ -418,7 +418,7 @@ class TestChangeOperations(EndpointTester):
self.assertEqual(self.ks4.constituentsQ().count(), ks4_old_count + 1)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
@decl_endpoint('/api/oss/{item}/relocate-constituents', method='post')
def test_relocate_constituents_down(self):
ks1_old_count = self.ks1.constituentsQ().count()
ks4_old_count = self.ks4.constituentsQ().count()
@ -438,7 +438,7 @@ class TestChangeOperations(EndpointTester):
'items': [self.ks1X2.pk]
}
self.executeOK(data)
self.executeOK(data, item=self.owned_id)
ks6.model.refresh_from_db()
self.ks1.model.refresh_from_db()
self.ks4.model.refresh_from_db()

View File

@ -163,7 +163,7 @@ class ReferencePropagationTestCase(EndpointTester):
@decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch')
def test_delete_constituenta(self):
data = {'items': [self.ks1X1.pk]}
response = self.executeOK(data, schema=self.ks1.model.pk)
self.executeOK(data, schema=self.ks1.model.pk)
self.ks4D2.refresh_from_db()
self.ks5D4.refresh_from_db()
self.ks6D2.refresh_from_db()

View File

@ -1,7 +1,7 @@
''' Testing API: Change substitutions in OSS. '''
from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import Constituenta, CstType, RSForm
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint

View File

@ -1,6 +1,5 @@
''' Testing API: Operation Schema - blocks manipulation. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
from apps.oss.models import Operation, OperationSchema, OperationType
from apps.oss.models import OperationSchema, OperationType
from shared.EndpointTester import EndpointTester, decl_endpoint

View File

@ -1,6 +1,12 @@
''' Testing API: Operation Schema - operations manipulation. '''
from apps.library.models import Editor, LibraryItem
from apps.oss.models import Argument, Operation, OperationSchema, OperationType, Replica
from apps.oss.models import (
Argument,
Operation,
OperationSchema,
OperationType,
Replica
)
from apps.rsform.models import Attribution, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint

View File

@ -220,8 +220,9 @@ class TestOssViewset(EndpointTester):
self.executeBadData(data)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
@decl_endpoint('/api/oss/{item}/relocate-constituents', method='post')
def test_relocate_constituents(self):
self.set_params(item=self.owned_id)
self.populateData()
self.ks1X2 = self.ks1.insert_last('X2', convention='test')

View File

@ -14,7 +14,7 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer
from apps.rsform.models import Constituenta, RSFormCached
from apps.rsform.models import Constituenta
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
from shared import permissions
@ -291,7 +291,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'height': position['height']
})
m.Layout.update_data(pk, layout)
m.OperationSchema.create_input(item, new_operation)
m.OperationSchema.create_input(item.pk, new_operation)
item.save(update_fields=['time_update'])
return Response(
@ -420,7 +420,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
schema_clone.access_policy = item.access_policy
schema_clone.location = item.location
schema_clone.save()
RSFormCached(schema_clone).insert_from(prototype.pk)
m.PropagationFacade().get_schema(schema_clone.pk).insert_from(prototype.pk)
new_operation.result = schema_clone
new_operation.save(update_fields=["result"])
@ -544,7 +545,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
with transaction.atomic():
oss = m.OperationSchemaCached(item)
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
if 'layout' in serializer.validated_data:
layout = serializer.validated_data['layout']
m.Layout.update_data(pk, layout)
@ -599,12 +601,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic():
oss = m.OperationSchemaCached(item)
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
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)
propagation.before_delete_schema(old_schema.pk)
old_schema.delete()
elif old_schema.is_synced(item):
old_schema.visible = True
@ -640,7 +643,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic():
oss = m.OperationSchemaCached(item)
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
m.Layout.update_data(pk, layout)
oss.delete_replica(operation.pk, keep_connections, keep_constituents)
item.save(update_fields=['time_update'])
@ -680,13 +684,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
with transaction.atomic():
m.Layout.update_data(pk, layout)
schema = m.OperationSchema.create_input(item, operation)
schema = m.OperationSchema.create_input(item.pk, operation)
item.save(update_fields=['time_update'])
return Response(
status=c.HTTP_200_OK,
data={
'new_schema': LibraryItemSerializer(schema.model).data,
'new_schema': LibraryItemSerializer(schema).data,
'oss': s.OperationSchemaSerializer(item).data
}
)
@ -726,7 +730,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
old_schema = target_operation.result
with transaction.atomic():
oss = m.OperationSchemaCached(item)
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
if old_schema is not None:
if old_schema.is_synced(item):
old_schema.visible = True
@ -769,7 +774,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
layout = serializer.validated_data['layout']
with transaction.atomic():
oss = m.OperationSchemaCached(item)
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
oss.execute_operation(operation)
m.Layout.update_data(pk, layout)
item.save(update_fields=['time_update'])
@ -823,24 +829,27 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=False, methods=['post'], url_path='relocate-constituents')
def relocate_constituents(self, request: Request) -> Response:
@action(detail=True, methods=['post'], url_path='relocate-constituents')
def relocate_constituents(self, request: Request, pk) -> Response:
''' Relocate constituents from one schema to another. '''
item = self._get_item()
serializer = s.RelocateConstituentsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
ids = [cst.pk for cst in data['items']]
destinationID = data['destination']
with transaction.atomic():
oss = m.OperationSchemaCached(LibraryItem.objects.get(pk=data['oss']))
source = RSFormCached(LibraryItem.objects.get(pk=data['source']))
destination = RSFormCached(LibraryItem.objects.get(pk=data['destination']))
propagation = m.PropagationFacade()
oss = propagation.get_oss(item.pk)
source = propagation.get_schema(data['source'])
if data['move_down']:
oss.relocate_down(source, destination, ids)
m.PropagationFacade.before_delete_cst(data['source'], ids)
oss.relocate_down(destinationID, ids)
propagation.before_delete_cst(source.pk, ids)
source.delete_cst(ids)
else:
new_items = oss.relocate_up(source, destination, ids)
m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk])
new_items = oss.relocate_up(source.pk, destinationID, ids)
propagation.after_create_cst(new_items, exclude=[oss.pk])
item.save(update_fields=['time_update'])
return Response(status=c.HTTP_200_OK)

View File

@ -8,7 +8,7 @@ ItemType = TypeVar("ItemType")
class Graph(Generic[ItemType]):
''' Directed graph. '''
def __init__(self, graph: Optional[dict[ItemType, list[ItemType]]] = None):
def __init__(self, graph: Optional[dict[ItemType, list[ItemType]]] = None) -> None:
if graph is None:
self.outputs: dict[ItemType, list[ItemType]] = {}
self.inputs: dict[ItemType, list[ItemType]] = {}

View File

@ -8,7 +8,7 @@ from .SemanticInfo import SemanticInfo
class OrderManager:
''' Ordering helper class '''
def __init__(self, schema: RSFormCached):
def __init__(self, schema: RSFormCached) -> None:
self._semantic = SemanticInfo(schema)
self._items = schema.cache.constituents
self._cst_by_ID = schema.cache.by_id

View File

@ -21,7 +21,7 @@ DELETED_ALIAS = 'DEL'
class RSForm:
''' RSForm wrapper. No caching, each mutation requires querying. '''
def __init__(self, model: LibraryItem):
def __init__(self, model: LibraryItem) -> None:
assert model.item_type == LibraryItemType.RSFORM
self.model = model

View File

@ -20,21 +20,15 @@ from .RSForm import DELETED_ALIAS, RSForm
class RSFormCached:
''' RSForm cached. Caching allows to avoid querying for each method call. '''
def __init__(self, model: LibraryItem):
self.model = model
def __init__(self, item_id: int) -> None:
self.pk = item_id
self.cache: _RSFormCache = _RSFormCache(self)
@staticmethod
def create(**kwargs) -> 'RSFormCached':
''' Create LibraryItem via RSForm. '''
model = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, **kwargs)
return RSFormCached(model)
@staticmethod
def from_id(pk: int) -> 'RSFormCached':
''' Get LibraryItem by pk. '''
model = LibraryItem.objects.get(pk=pk)
return RSFormCached(model)
return RSFormCached(model.pk)
def get_dependant(self, target: Iterable[int]) -> set[int]:
''' Get list of constituents depending on target (only 1st degree). '''
@ -51,7 +45,7 @@ class RSFormCached:
def constituentsQ(self) -> QuerySet[Constituenta]:
''' Get QuerySet containing all constituents of current RSForm. '''
return Constituenta.objects.filter(schema=self.model)
return Constituenta.objects.filter(schema_id=self.pk)
def insert_last(
self,
@ -62,9 +56,9 @@ class RSFormCached:
''' Insert new constituenta at last position. '''
if cst_type is None:
cst_type = guess_type(alias)
position = Constituenta.objects.filter(schema=self.model).count()
position = Constituenta.objects.filter(schema_id=self.pk).count()
result = Constituenta.objects.create(
schema=self.model,
schema_id=self.pk,
order=position,
alias=alias,
cst_type=cst_type,
@ -83,7 +77,7 @@ class RSFormCached:
RSForm.shift_positions(position, 1, self.cache.constituents)
result = Constituenta.objects.create(
schema=self.model,
schema_id=self.pk,
order=position,
alias=data['alias'],
cst_type=data['cst_type'],
@ -160,7 +154,7 @@ class RSFormCached:
new_constituents = deepcopy(items)
for cst in new_constituents:
cst.pk = None
cst.schema = self.model
cst.schema_id = self.pk
cst.order = position
if mapping_alias:
cst.alias = mapping_alias[cst.alias]
@ -263,7 +257,7 @@ class RSFormCached:
deleted.append(original)
replacements.append(substitution.pk)
attributions = list(Attribution.objects.filter(container__schema=self.model))
attributions = list(Attribution.objects.filter(container__schema_id=self.pk))
if attributions:
orig_to_sub = {original.pk: substitution.pk for original, substitution in substitutions}
orig_pks = set(orig_to_sub.keys())
@ -374,7 +368,7 @@ class RSFormCached:
prefix = get_type_prefix(cst_type)
for text in expressions:
new_item = Constituenta.objects.create(
schema=self.model,
schema_id=self.pk,
order=position,
alias=f'{prefix}{free_index}',
definition_formal=text,
@ -392,7 +386,7 @@ class RSFormCached:
cst_list: Iterable[Constituenta] = []
if not self.cache.is_loaded:
cst_list = Constituenta.objects \
.filter(schema=self.model, cst_type=cst_type) \
.filter(schema_id=self.pk, cst_type=cst_type) \
.only('alias')
else:
cst_list = [cst for cst in self.cache.constituents if cst.cst_type == cst_type]
@ -406,7 +400,7 @@ class RSFormCached:
class _RSFormCache:
''' Cache for RSForm constituents. '''
def __init__(self, schema: 'RSFormCached'):
def __init__(self, schema: 'RSFormCached') -> None:
self._schema = schema
self.constituents: list[Constituenta] = []
self.by_id: dict[int, Constituenta] = {}

View File

@ -16,7 +16,7 @@ from .RSFormCached import RSFormCached
class SemanticInfo:
''' Semantic information derived from constituents. '''
def __init__(self, schema: RSFormCached):
def __init__(self, schema: RSFormCached) -> None:
schema.cache.ensure_loaded()
self._items = schema.cache.constituents
self._cst_by_ID = schema.cache.by_id

View File

@ -123,7 +123,7 @@ class RSFormTRSSerializer(serializers.Serializer):
result['description'] = data.get('description', '')
if 'id' in data:
result['id'] = data['id']
self.instance = RSFormCached.from_id(result['id'])
self.instance = RSFormCached(result['id'])
return result
def validate(self, attrs: dict):
@ -151,7 +151,7 @@ class RSFormTRSSerializer(serializers.Serializer):
for cst_data in validated_data['items']:
cst = Constituenta(
alias=cst_data['alias'],
schema=self.instance.model,
schema_id=self.instance.pk,
order=order,
cst_type=cst_data['cstType'],
)
@ -163,12 +163,13 @@ class RSFormTRSSerializer(serializers.Serializer):
@transaction.atomic
def update(self, instance: RSFormCached, validated_data) -> RSFormCached:
model = LibraryItem.objects.get(pk=instance.pk)
if 'alias' in validated_data:
instance.model.alias = validated_data['alias']
model.alias = validated_data['alias']
if 'title' in validated_data:
instance.model.title = validated_data['title']
model.title = validated_data['title']
if 'description' in validated_data:
instance.model.description = validated_data['description']
model.description = validated_data['description']
order = 0
prev_constituents = instance.constituentsQ()
@ -185,7 +186,7 @@ class RSFormTRSSerializer(serializers.Serializer):
else:
cst = Constituenta(
alias=cst_data['alias'],
schema=instance.model,
schema_id=instance.pk,
order=order,
cst_type=cst_data['cstType'],
)
@ -199,7 +200,7 @@ class RSFormTRSSerializer(serializers.Serializer):
prev_cst.delete()
instance.resolve_all_text()
instance.model.save()
model.save()
return instance
@staticmethod

View File

@ -12,7 +12,7 @@ from ..models import Constituenta, CstType
class PyConceptAdapter:
''' RSForm adapter for interacting with pyconcept module. '''
def __init__(self, data: Union[int, dict]):
def __init__(self, data: Union[int, dict]) -> None:
try:
if 'items' in cast(dict, data):
self.data = self._prepare_request_raw(cast(dict, data))

View File

@ -22,8 +22,8 @@ class TestRSFormCached(DBTester):
self.assertFalse(schema1.constituentsQ().exists())
self.assertFalse(schema2.constituentsQ().exists())
Constituenta.objects.create(alias='X1', schema=schema1.model, order=0)
Constituenta.objects.create(alias='X2', schema=schema1.model, order=1)
Constituenta.objects.create(alias='X1', schema_id=schema1.pk, order=0)
Constituenta.objects.create(alias='X2', schema_id=schema1.pk, order=1)
self.assertTrue(schema1.constituentsQ().exists())
self.assertFalse(schema2.constituentsQ().exists())
self.assertEqual(schema1.constituentsQ().count(), 2)
@ -32,7 +32,7 @@ class TestRSFormCached(DBTester):
def test_insert_last(self):
x1 = self.schema.insert_last('X1')
self.assertEqual(x1.order, 0)
self.assertEqual(x1.schema, self.schema.model)
self.assertEqual(x1.schema_id, self.schema.pk)
def test_create_cst(self):
@ -115,8 +115,8 @@ class TestRSFormCached(DBTester):
definition_formal='X2 = X3'
)
test_ks = RSFormCached.create(title='Test')
test_ks.insert_from(self.schema.model.pk)
items = Constituenta.objects.filter(schema=test_ks.model).order_by('order')
test_ks.insert_from(self.schema.pk)
items = Constituenta.objects.filter(schema_id=test_ks.pk).order_by('order')
self.assertEqual(len(items), 4)
self.assertEqual(items[0].alias, 'X2')
self.assertEqual(items[1].alias, 'D2')
@ -200,7 +200,7 @@ class TestRSFormCached(DBTester):
self.schema.substitute([(x1, x2)])
self.assertEqual(self.schema.constituentsQ().count(), 3)
self.assertEqual(Attribution.objects.filter(container__schema=self.schema.model).count(), 2)
self.assertEqual(Attribution.objects.filter(container__schema_id=self.schema.pk).count(), 2)
self.assertTrue(Attribution.objects.filter(container=x2, attribute=d2).exists())
self.assertTrue(Attribution.objects.filter(container=x2, attribute=d1).exists())

View File

@ -91,9 +91,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
insert_after = data['insert_after']
with transaction.atomic():
schema = m.RSFormCached(item)
propagation = PropagationFacade()
schema = propagation.get_schema(item.pk)
new_cst = schema.create_cst(data, insert_after)
PropagationFacade.after_create_cst(schema, [new_cst])
propagation.after_create_cst([new_cst])
item.save(update_fields=['time_update'])
return Response(
@ -125,9 +126,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
data = serializer.validated_data['item_data']
with transaction.atomic():
schema = m.RSFormCached(item)
propagation = PropagationFacade()
schema = propagation.get_schema(item.pk)
old_data = schema.update_cst(cst.pk, data)
PropagationFacade.after_update_cst(schema, cst.pk, data, old_data)
propagation.after_update_cst(item.pk, cst.pk, data, old_data)
if 'alias' in data and data['alias'] != cst.alias:
cst.refresh_from_db()
changed_type = 'cst_type' in data and cst.cst_type != data['cst_type']
@ -138,7 +140,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
cst.save()
schema.apply_mapping(mapping=mapping, change_aliases=False)
if changed_type:
PropagationFacade.after_change_cst_type(item.pk, cst.pk, cast(m.CstType, cst.cst_type))
propagation.after_change_cst_type(item.pk, cst.pk, cast(m.CstType, cst.cst_type))
item.save(update_fields=['time_update'])
return Response(
status=c.HTTP_200_OK,
@ -208,9 +210,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
)
with transaction.atomic():
schema = m.RSFormCached(item)
propagation = PropagationFacade()
schema = propagation.get_schema(item.pk)
new_cst = schema.produce_structure(cst, cst_parse)
PropagationFacade.after_create_cst(schema, new_cst)
propagation.after_create_cst(new_cst)
item.save(update_fields=['time_update'])
return Response(
status=c.HTTP_200_OK,
@ -245,7 +248,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
original = cast(m.Constituenta, substitution['original'])
replacement = cast(m.Constituenta, substitution['substitution'])
substitutions.append((original, replacement))
PropagationFacade.before_substitute(item.pk, substitutions)
PropagationFacade().before_substitute(item.pk, substitutions)
schema.substitute(substitutions)
item.save(update_fields=['time_update'])
@ -275,7 +278,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
with transaction.atomic():
schema = m.RSForm(item)
PropagationFacade.before_delete_cst(item.pk, [cst.pk for cst in cst_list])
PropagationFacade().before_delete_cst(item.pk, [cst.pk for cst in cst_list])
schema.delete_cst(cst_list)
item.save(update_fields=['time_update'])
@ -305,11 +308,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
attribute = serializer.validated_data['attribute']
with transaction.atomic():
new_association = m.Attribution.objects.create(
new_attribution = m.Attribution.objects.create(
container=container,
attribute=attribute
)
PropagationFacade.after_create_attribution(item.pk, [new_association])
PropagationFacade().after_create_attribution(item.pk, [new_attribution])
item.save(update_fields=['time_update'])
return Response(
@ -345,7 +348,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
'container': msg.invalidAssociation()
})
PropagationFacade.before_delete_attribution(item.pk, target)
PropagationFacade().before_delete_attribution(item.pk, target)
m.Attribution.objects.filter(pk__in=[attrib.pk for attrib in target]).delete()
item.save(update_fields=['time_update'])
@ -375,7 +378,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
with transaction.atomic():
target = list(m.Attribution.objects.filter(container=serializer.validated_data['target']))
if target:
PropagationFacade.before_delete_attribution(item.pk, target)
PropagationFacade().before_delete_attribution(item.pk, target)
m.Attribution.objects.filter(pk__in=[attrib.pk for attrib in target]).delete()
item.save(update_fields=['time_update'])
@ -456,7 +459,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
item = self._get_item()
with transaction.atomic():
m.OrderManager(m.RSFormCached(item)).restore_order()
m.OrderManager(m.RSFormCached(item.pk)).restore_order()
item.save(update_fields=['time_update'])
return Response(
@ -493,10 +496,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
serializer.is_valid(raise_exception=True)
result: m.RSForm = serializer.save()
result: m.RSFormCached = serializer.save()
return Response(
status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(result.model).data
data=s.RSFormParseSerializer(LibraryItem.objects.get(pk=result.pk)).data
)
@extend_schema(
@ -651,10 +654,10 @@ class TrsImportView(views.APIView):
_prepare_rsform_data(data, request, owner)
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
serializer.is_valid(raise_exception=True)
schema: m.RSForm = serializer.save()
schema: m.RSFormCached = serializer.save()
return Response(
status=c.HTTP_201_CREATED,
data=LibraryItemSerializer(schema.model).data
data=LibraryItemSerializer(LibraryItem.objects.get(pk=schema.pk)).data
)
@ -687,10 +690,10 @@ def create_rsform(request: Request) -> HttpResponse:
_prepare_rsform_data(data, request, owner)
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
serializer_rsform.is_valid(raise_exception=True)
schema: m.RSForm = serializer_rsform.save()
schema: m.RSFormCached = serializer_rsform.save()
return Response(
status=c.HTTP_201_CREATED,
data=LibraryItemSerializer(schema.model).data
data=LibraryItemSerializer(LibraryItem.objects.get(pk=schema.pk)).data
)
@ -731,16 +734,18 @@ def inline_synthesis(request: Request) -> HttpResponse:
serializer = s.InlineSynthesisSerializer(data=request.data, context={'user': request.user})
serializer.is_valid(raise_exception=True)
receiver = m.RSFormCached(serializer.validated_data['receiver'])
item = cast(LibraryItem, serializer.validated_data['receiver'])
target_cst = cast(list[m.Constituenta], serializer.validated_data['items'])
source = cast(LibraryItem, serializer.validated_data['source'])
target_ids = [item.pk for item in target_cst] if target_cst else None
with transaction.atomic():
propagation = PropagationFacade()
receiver = propagation.get_schema(item.pk)
new_items = receiver.insert_from(source.pk, target_ids)
target_ids = [item[0].pk for item in new_items]
mapping_ids = {cst.pk: new_cst for (cst, new_cst) in new_items}
PropagationFacade.after_create_cst(receiver, [item[1] for item in new_items])
propagation.after_create_cst([item[1] for item in new_items])
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
for substitution in serializer.validated_data['substitutions']:
@ -752,11 +757,11 @@ def inline_synthesis(request: Request) -> HttpResponse:
replacement = mapping_ids[replacement.pk]
substitutions.append((original, replacement))
PropagationFacade.before_substitute(receiver.model.pk, substitutions)
propagation.before_substitute(receiver.pk, substitutions)
receiver.substitute(substitutions)
receiver.model.save(update_fields=['time_update'])
item.save(update_fields=['time_update'])
return Response(
status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(receiver.model).data
data=s.RSFormParseSerializer(item).data
)

View File

@ -215,9 +215,9 @@ export const ossApi = {
}
}),
relocateConstituents: (data: IRelocateConstituentsDTO) =>
relocateConstituents: ({ itemID, data }: { itemID: number; data: IRelocateConstituentsDTO }) =>
axiosPost<IRelocateConstituentsDTO>({
endpoint: `/api/oss/relocate-constituents`,
endpoint: `/api/oss/${itemID}/relocate-constituents`,
request: {
data: data,
successMessage: infoMsg.changesSaved

View File

@ -19,6 +19,6 @@ export const useRelocateConstituents = () => {
onError: () => client.invalidateQueries()
});
return {
relocateConstituents: (data: IRelocateConstituentsDTO) => mutation.mutateAsync(data)
relocateConstituents: (data: { itemID: number; data: IRelocateConstituentsDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -106,13 +106,13 @@ export function DlgRelocateConstituents() {
function onSubmit(data: IRelocateConstituentsDTO) {
data.items = moveTarget;
if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) {
return relocateConstituents(data);
return relocateConstituents({ itemID: oss.id, data: data });
} else {
return updatePositions({
isSilent: true,
itemID: oss.id,
data: layout
}).then(() => relocateConstituents(data));
}).then(() => relocateConstituents({ itemID: oss.id, data: data }));
}
}