R: Refactoring caches pt1

This commit is contained in:
Ivan 2025-11-08 19:32:42 +03:00
parent b94e643043
commit 41e2df21ef
21 changed files with 163 additions and 146 deletions

View File

@ -67,7 +67,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
def perform_destroy(self, instance: m.LibraryItem) -> None: def perform_destroy(self, instance: m.LibraryItem) -> None:
if instance.item_type == m.LibraryItemType.RSFORM: if instance.item_type == m.LibraryItemType.RSFORM:
PropagationFacade.before_delete_schema(instance) PropagationFacade.before_delete_schema(instance.pk)
super().perform_destroy(instance) super().perform_destroy(instance)
if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA: if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA:
schemas = list(OperationSchema.owned_schemasQ(instance)) schemas = list(OperationSchema.owned_schemasQ(instance))
@ -172,7 +172,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
clone.location = data.get('location', m.LocationHead.USER) clone.location = data.get('location', m.LocationHead.USER)
clone.save() 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( return Response(
status=c.HTTP_201_CREATED, status=c.HTTP_201_CREATED,

View File

@ -43,19 +43,18 @@ class OperationSchema:
return Layout.objects.get(oss_id=itemID) return Layout.objects.get(oss_id=itemID)
@staticmethod @staticmethod
def create_input(oss: LibraryItem, operation: Operation) -> RSFormCached: def create_input(oss_id: int, operation: Operation) -> LibraryItem:
''' Create input RSForm for given Operation. ''' ''' Create input RSForm for given Operation. '''
schema = RSFormCached.create( oss = LibraryItem.objects.get(pk=oss_id)
owner=oss.owner, schema = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, owner=oss.owner,
alias=operation.alias, alias=operation.alias,
title=operation.title, title=operation.title,
description=operation.description, description=operation.description,
visible=False, visible=False,
access_policy=oss.access_policy, access_policy=oss.access_policy,
location=oss.location location=oss.location)
) Editor.set(schema.pk, oss.getQ_editors().values_list('pk', flat=True))
Editor.set(schema.model.pk, oss.getQ_editors().values_list('pk', flat=True)) operation.setQ_result(schema)
operation.setQ_result(schema.model)
return schema return schema
def refresh_from_db(self) -> None: def refresh_from_db(self) -> None:
@ -132,7 +131,7 @@ class OperationSchema:
if not schemas: if not schemas:
return return
substitutions = operation.getQ_substitutions() substitutions = operation.getQ_substitutions()
receiver = OperationSchema.create_input(self.model, operation) receiver = RSFormCached(OperationSchema.create_input(self.model.pk, operation).pk)
parents: dict = {} parents: dict = {}
children: dict = {} children: dict = {}
@ -149,7 +148,7 @@ class OperationSchema:
translated_substitutions.append((original, replacement)) translated_substitutions.append((original, replacement))
receiver.substitute(translated_substitutions) 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) parent = parents.get(cst.pk)
assert parent is not None assert parent is not None
Inheritance.objects.create( Inheritance.objects.create(

View File

@ -19,9 +19,9 @@ from .utils import CstMapping, CstSubstitution, create_dependant_mapping, extrac
class OperationSchemaCached: class OperationSchemaCached:
''' Operations schema API with caching. ''' ''' Operations schema API with caching. '''
def __init__(self, model: LibraryItem): def __init__(self, item_id: int):
self.model = model self.pk = item_id
self.cache = OssCache(model.pk) self.cache = OssCache(item_id)
self.engine = PropagationEngine(self.cache) self.engine = PropagationEngine(self.cache)
def delete_replica(self, target: int, keep_connections: bool = False, keep_constituents: bool = False): def delete_replica(self, target: int, keep_connections: bool = False, keep_constituents: bool = False):
@ -72,12 +72,12 @@ class OperationSchemaCached:
has_children = bool(self.cache.extend_graph.outputs[target]) has_children = bool(self.cache.extend_graph.outputs[target])
old_schema = self.cache.get_schema(operation) old_schema = self.cache.get_schema(operation)
if schema is None and old_schema is None or \ 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 return
if old_schema is not None: if old_schema is not None:
if has_children: if has_children:
self.before_delete_cst(old_schema.model.pk, [cst.pk for cst in old_schema.cache.constituents]) self.before_delete_cst(old_schema.pk, [cst.pk for cst in old_schema.cache.constituents])
self.cache.remove_schema(old_schema) self.cache.remove_schema(old_schema)
operation.setQ_result(schema) operation.setQ_result(schema)
@ -88,7 +88,7 @@ class OperationSchemaCached:
operation.save(update_fields=['alias', 'title', 'description']) operation.save(update_fields=['alias', 'title', 'description'])
if schema is not None and has_children: if schema is not None and has_children:
rsform = RSFormCached(schema) rsform = RSFormCached(schema.pk)
self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order'))) self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order')))
def set_arguments(self, target: int, arguments: list[Operation]) -> None: def set_arguments(self, target: int, arguments: list[Operation]) -> None:
@ -172,7 +172,8 @@ class OperationSchemaCached:
if not schemas: if not schemas:
return False return False
substitutions = operation.getQ_substitutions() substitutions = operation.getQ_substitutions()
receiver = OperationSchema.create_input(self.model, self.cache.operation_by_id[operation.pk]) new_schema = OperationSchema.create_input(self.pk, self.cache.operation_by_id[operation.pk])
receiver = RSFormCached(new_schema.pk)
self.cache.insert_schema(receiver) self.cache.insert_schema(receiver)
parents: dict = {} parents: dict = {}
@ -190,7 +191,7 @@ class OperationSchemaCached:
translated_substitutions.append((original, replacement)) translated_substitutions.append((original, replacement))
receiver.substitute(translated_substitutions) 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) parent = parents.get(cst.pk)
assert parent is not None assert parent is not None
Inheritance.objects.create( Inheritance.objects.create(
@ -204,9 +205,8 @@ class OperationSchemaCached:
receiver.resolve_all_text() receiver.resolve_all_text()
if self.cache.extend_graph.outputs[operation.pk]: if self.cache.extend_graph.outputs[operation.pk]:
receiver_items = list(Constituenta.objects.filter(schema=receiver.model).order_by('order')) receiver_items = list(Constituenta.objects.filter(schema_id=receiver.pk).order_by('order'))
self.after_create_cst(receiver, receiver_items) self.after_create_cst(receiver, receiver_items)
receiver.model.save(update_fields=['time_update'])
return True return True
def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]): def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]):
@ -214,7 +214,7 @@ class OperationSchemaCached:
self.cache.ensure_loaded_subs() self.cache.ensure_loaded_subs()
self.cache.insert_schema(source) self.cache.insert_schema(source)
self.cache.insert_schema(destination) self.cache.insert_schema(destination)
operation = self.cache.get_operation(destination.model.pk) operation = self.cache.get_operation(destination.pk)
self.engine.undo_substitutions_cst(items, operation, destination) 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] inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items]
for item in inheritance_to_delete: for item in inheritance_to_delete:
@ -228,7 +228,7 @@ class OperationSchemaCached:
self.cache.insert_schema(source) self.cache.insert_schema(source)
self.cache.insert_schema(destination) self.cache.insert_schema(destination)
operation = self.cache.get_operation(source.model.pk) operation = self.cache.get_operation(source.pk)
alias_mapping: dict[str, str] = {} alias_mapping: dict[str, str] = {}
for item in self.cache.inheritance[operation.pk]: for item in self.cache.inheritance[operation.pk]:
if item.parent_id in destination.cache.by_id: if item.parent_id in destination.cache.by_id:
@ -236,7 +236,7 @@ class OperationSchemaCached:
destination_cst = destination.cache.by_id[item.parent_id] destination_cst = destination.cache.by_id[item.parent_id]
alias_mapping[source_cst.alias] = destination_cst.alias 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(source.pk, item_ids, alias_mapping)
for (cst, new_cst) in new_items: for (cst, new_cst) in new_items:
new_inheritance = Inheritance.objects.create( new_inheritance = Inheritance.objects.create(
operation=operation, operation=operation,
@ -246,7 +246,6 @@ class OperationSchemaCached:
self.cache.insert_inheritance(new_inheritance) self.cache.insert_inheritance(new_inheritance)
new_constituents = [item[1] for item in new_items] new_constituents = [item[1] for item in new_items]
self.after_create_cst(destination, new_constituents, exclude=[operation.pk]) self.after_create_cst(destination, new_constituents, exclude=[operation.pk])
destination.model.save(update_fields=['time_update'])
return new_constituents return new_constituents
def after_create_cst( def after_create_cst(
@ -257,7 +256,7 @@ class OperationSchemaCached:
''' Trigger cascade resolutions when new Constituenta is created. ''' ''' Trigger cascade resolutions when new Constituenta is created. '''
self.cache.insert_schema(source) self.cache.insert_schema(source)
alias_mapping = create_dependant_mapping(source, cst_list) 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) 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: def after_change_cst_type(self, schemaID: int, target: int, new_type: CstType) -> None:
@ -268,7 +267,7 @@ class OperationSchemaCached:
def after_update_cst(self, source: RSFormCached, target: int, data: dict, old_data: dict) -> None: def after_update_cst(self, source: RSFormCached, target: int, data: dict, old_data: dict) -> None:
''' Trigger cascade resolutions when Constituenta data is changed. ''' ''' Trigger cascade resolutions when Constituenta data is changed. '''
self.cache.insert_schema(source) self.cache.insert_schema(source)
operation = self.cache.get_operation(source.model.pk) operation = self.cache.get_operation(source.pk)
depend_aliases = extract_data_references(data, old_data) depend_aliases = extract_data_references(data, old_data)
alias_mapping: CstMapping = {} alias_mapping: CstMapping = {}
for alias in depend_aliases: for alias in depend_aliases:
@ -347,7 +346,7 @@ class OperationSchemaCached:
original_cst = schema.cache.by_id[original_id] original_cst = schema.cache.by_id[original_id]
substitution_cst = schema.cache.by_id[substitution_id] substitution_cst = schema.cache.by_id[substitution_id]
cst_mapping.append((original_cst, substitution_cst)) 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) schema.substitute(cst_mapping)
for sub in added: for sub in added:
self.cache.insert_substitution(sub) self.cache.insert_substitution(sub)

View File

@ -67,7 +67,7 @@ class OssCache:
if operation.result_id in self._schema_by_id: if operation.result_id in self._schema_by_id:
return self._schema_by_id[operation.result_id] return self._schema_by_id[operation.result_id]
else: else:
schema = RSFormCached.from_id(operation.result_id) schema = RSFormCached(operation.result_id)
schema.cache.ensure_loaded() schema.cache.ensure_loaded()
self._insert_new(schema) self._insert_new(schema)
return schema return schema
@ -77,7 +77,7 @@ class OssCache:
if target in self._schema_by_id: if target in self._schema_by_id:
return self._schema_by_id[target] return self._schema_by_id[target]
else: else:
schema = RSFormCached.from_id(target) schema = RSFormCached(target)
schema.cache.ensure_loaded() schema.cache.ensure_loaded()
self._insert_new(schema) self._insert_new(schema)
return schema return schema
@ -113,7 +113,7 @@ class OssCache:
def insert_schema(self, schema: RSFormCached) -> None: def insert_schema(self, schema: RSFormCached) -> None:
''' Insert new schema. ''' ''' Insert new schema. '''
if not self._schema_by_id.get(schema.model.pk): if not self._schema_by_id.get(schema.pk):
schema.cache.ensure_loaded() schema.cache.ensure_loaded()
self._insert_new(schema) self._insert_new(schema)
@ -148,7 +148,7 @@ class OssCache:
def remove_schema(self, schema: RSFormCached) -> None: def remove_schema(self, schema: RSFormCached) -> None:
''' Remove schema from cache. ''' ''' Remove schema from cache. '''
self._schemas.remove(schema) self._schemas.remove(schema)
del self._schema_by_id[schema.model.pk] del self._schema_by_id[schema.pk]
def remove_operation(self, operation: int) -> None: def remove_operation(self, operation: int) -> None:
''' Remove operation from cache. ''' ''' Remove operation from cache. '''
@ -185,4 +185,4 @@ class OssCache:
def _insert_new(self, schema: RSFormCached) -> None: def _insert_new(self, schema: RSFormCached) -> None:
self._schemas.append(schema) self._schemas.append(schema)
self._schema_by_id[schema.model.pk] = schema self._schema_by_id[schema.pk] = schema

View File

@ -1,28 +1,42 @@
''' Models: Change propagation facade - managing all changes in OSS. ''' ''' Models: Change propagation facade - managing all changes in OSS. '''
from typing import Optional 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 apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
def _get_oss_hosts(schemaID: int) -> list[LibraryItem]: def _get_oss_hosts(schemaID: int) -> list[int]:
''' Get all hosts for LibraryItem. ''' ''' Get all hosts for schema. '''
return list(LibraryItem.objects.filter(operations__result_id=schemaID).only('pk').distinct()) return list(LibraryItem.objects.filter(operations__result_id=schemaID).values_list('pk', flat=True))
class PropagationFacade: class PropagationFacade:
''' Change propagation API. ''' ''' Change propagation API. '''
_oss: dict[int, OperationSchemaCached] = {}
@staticmethod
def get_oss(schemaID: int) -> OperationSchemaCached:
''' Get OperationSchemaCached for schemaID. '''
if schemaID not in PropagationFacade._oss:
PropagationFacade._oss[schemaID] = OperationSchemaCached(schemaID)
return PropagationFacade._oss[schemaID]
@staticmethod
def reset_cache() -> None:
''' Reset cache. '''
PropagationFacade._oss = {}
@staticmethod @staticmethod
def after_create_cst(source: RSFormCached, new_cst: list[Constituenta], def after_create_cst(source: RSFormCached, new_cst: list[Constituenta],
exclude: Optional[list[int]] = None) -> None: exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions when new constituenta is created. ''' ''' Trigger cascade resolutions when new constituenta is created. '''
hosts = _get_oss_hosts(source.model.pk) hosts = _get_oss_hosts(source.pk)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).after_create_cst(source, new_cst) PropagationFacade.get_oss(host).after_create_cst(source, new_cst)
@staticmethod @staticmethod
def after_change_cst_type(sourceID: int, target: int, new_type: CstType, def after_change_cst_type(sourceID: int, target: int, new_type: CstType,
@ -30,8 +44,8 @@ class PropagationFacade:
''' Trigger cascade resolutions when constituenta type is changed. ''' ''' Trigger cascade resolutions when constituenta type is changed. '''
hosts = _get_oss_hosts(sourceID) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).after_change_cst_type(sourceID, target, new_type) PropagationFacade.get_oss(host).after_change_cst_type(sourceID, target, new_type)
@staticmethod @staticmethod
def after_update_cst( def after_update_cst(
@ -42,10 +56,10 @@ class PropagationFacade:
exclude: Optional[list[int]] = None exclude: Optional[list[int]] = None
) -> None: ) -> None:
''' Trigger cascade resolutions when constituenta data is changed. ''' ''' Trigger cascade resolutions when constituenta data is changed. '''
hosts = _get_oss_hosts(source.model.pk) hosts = _get_oss_hosts(source.pk)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).after_update_cst(source, target, data, old_data) PropagationFacade.get_oss(host).after_update_cst(source, target, data, old_data)
@staticmethod @staticmethod
def before_delete_cst(sourceID: int, target: list[int], def before_delete_cst(sourceID: int, target: list[int],
@ -53,8 +67,8 @@ class PropagationFacade:
''' Trigger cascade resolutions before constituents are deleted. ''' ''' Trigger cascade resolutions before constituents are deleted. '''
hosts = _get_oss_hosts(sourceID) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).before_delete_cst(sourceID, target) PropagationFacade.get_oss(host).before_delete_cst(sourceID, target)
@staticmethod @staticmethod
def before_substitute(sourceID: int, substitutions: CstSubstitution, def before_substitute(sourceID: int, substitutions: CstSubstitution,
@ -64,31 +78,31 @@ class PropagationFacade:
return return
hosts = _get_oss_hosts(sourceID) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).before_substitute(sourceID, substitutions) PropagationFacade.get_oss(host).before_substitute(sourceID, substitutions)
@staticmethod @staticmethod
def before_delete_schema(item: LibraryItem, exclude: Optional[list[int]] = None) -> None: def before_delete_schema(target: int, exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions before schema is deleted. ''' ''' Trigger cascade resolutions before schema is deleted. '''
if item.item_type != LibraryItemType.RSFORM: hosts = _get_oss_hosts(target)
return
hosts = _get_oss_hosts(item.pk)
if not hosts: if not hosts:
return 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: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).before_delete_cst(item.pk, ids) PropagationFacade.get_oss(host).before_delete_cst(target, ids)
del PropagationFacade._oss[host]
@staticmethod @staticmethod
def after_create_attribution(sourceID: int, attributions: list[Attribution], def after_create_attribution(sourceID: int,
attributions: list[Attribution],
exclude: Optional[list[int]] = None) -> None: exclude: Optional[list[int]] = None) -> None:
''' Trigger cascade resolutions when Attribution is created. ''' ''' Trigger cascade resolutions when Attribution is created. '''
hosts = _get_oss_hosts(sourceID) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).after_create_attribution(sourceID, attributions) PropagationFacade.get_oss(host).after_create_attribution(sourceID, attributions)
@staticmethod @staticmethod
def before_delete_attribution(sourceID: int, def before_delete_attribution(sourceID: int,
@ -97,5 +111,5 @@ class PropagationFacade:
''' Trigger cascade resolutions before Attribution is deleted. ''' ''' Trigger cascade resolutions before Attribution is deleted. '''
hosts = _get_oss_hosts(sourceID) hosts = _get_oss_hosts(sourceID)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude: if exclude is None or host not in exclude:
OperationSchemaCached(host).before_delete_attribution(sourceID, attributions) PropagationFacade.get_oss(host).before_delete_attribution(sourceID, attributions)

View File

@ -625,15 +625,6 @@ class RelocateConstituentsSerializer(StrictSerializer):
'items': msg.RelocatingInherited() '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( if Argument.objects.filter(
operation__result_id=attrs['destination'], operation__result_id=attrs['destination'],
argument__result_id=attrs['source'] argument__result_id=attrs['source']

View File

@ -1,6 +1,6 @@
''' Testing API: Change attributes of OSS and RSForms. ''' ''' Testing API: Change attributes of OSS and RSForms. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LocationHead from apps.library.models import AccessPolicy, Editor, LibraryItem, LocationHead
from apps.oss.models import Operation, OperationSchema, OperationType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import RSForm from apps.rsform.models import RSForm
from apps.users.models import User from apps.users.models import User
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -11,6 +11,7 @@ class TestChangeAttributes(EndpointTester):
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.user3 = User.objects.create( self.user3 = User.objects.create(
username='UserTest3', username='UserTest3',

View File

@ -1,6 +1,6 @@
''' Testing API: Change constituents in OSS. ''' ''' Testing API: Change constituents in OSS. '''
from apps.oss.models import OperationSchema, OperationType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Attribution, Constituenta, CstType, RSForm from apps.rsform.models import Attribution, Constituenta, CstType, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestChangeConstituents(EndpointTester):
''' Testing Constituents change propagation in OSS. ''' ''' Testing Constituents change propagation in OSS. '''
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create( self.owned = OperationSchema.create(
title='Test', title='Test',

View File

@ -1,7 +1,7 @@
''' Testing API: Change substitutions in OSS. ''' ''' Testing API: Change substitutions in OSS. '''
from apps.oss.models import OperationSchema, OperationType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, CstType, RSForm from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestChangeOperations(EndpointTester):
''' Testing Operations change propagation in OSS. ''' ''' Testing Operations change propagation in OSS. '''
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create( self.owned = OperationSchema.create(
title='Test', title='Test',
@ -388,7 +389,7 @@ class TestChangeOperations(EndpointTester):
self.assertEqual(self.ks5.constituentsQ().count(), 8) 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): def test_relocate_constituents_up(self):
ks1_old_count = self.ks1.constituentsQ().count() ks1_old_count = self.ks1.constituentsQ().count()
ks4_old_count = self.ks4.constituentsQ().count() ks4_old_count = self.ks4.constituentsQ().count()
@ -408,7 +409,7 @@ class TestChangeOperations(EndpointTester):
'items': [ks6A1.pk] 'items': [ks6A1.pk]
} }
self.executeOK(data) self.executeOK(data, item=self.owned_id)
ks6.model.refresh_from_db() ks6.model.refresh_from_db()
self.ks1.model.refresh_from_db() self.ks1.model.refresh_from_db()
self.ks4.model.refresh_from_db() self.ks4.model.refresh_from_db()
@ -418,7 +419,7 @@ class TestChangeOperations(EndpointTester):
self.assertEqual(self.ks4.constituentsQ().count(), ks4_old_count + 1) 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): def test_relocate_constituents_down(self):
ks1_old_count = self.ks1.constituentsQ().count() ks1_old_count = self.ks1.constituentsQ().count()
ks4_old_count = self.ks4.constituentsQ().count() ks4_old_count = self.ks4.constituentsQ().count()
@ -438,7 +439,7 @@ class TestChangeOperations(EndpointTester):
'items': [self.ks1X2.pk] 'items': [self.ks1X2.pk]
} }
self.executeOK(data) self.executeOK(data, item=self.owned_id)
ks6.model.refresh_from_db() ks6.model.refresh_from_db()
self.ks1.model.refresh_from_db() self.ks1.model.refresh_from_db()
self.ks4.model.refresh_from_db() self.ks4.model.refresh_from_db()

View File

@ -1,6 +1,6 @@
''' Testing API: Propagate changes through references in OSS. ''' ''' Testing API: Propagate changes through references in OSS. '''
from apps.oss.models import Inheritance, OperationSchema, OperationType from apps.oss.models import Inheritance, OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, CstType, RSForm from apps.rsform.models import Constituenta, CstType, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class ReferencePropagationTestCase(EndpointTester):
''' Test propagation through references in OSS. ''' ''' Test propagation through references in OSS. '''
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create( self.owned = OperationSchema.create(
@ -163,7 +164,7 @@ class ReferencePropagationTestCase(EndpointTester):
@decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch')
def test_delete_constituenta(self): def test_delete_constituenta(self):
data = {'items': [self.ks1X1.pk]} 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.ks4D2.refresh_from_db()
self.ks5D4.refresh_from_db() self.ks5D4.refresh_from_db()
self.ks6D2.refresh_from_db() self.ks6D2.refresh_from_db()

View File

@ -1,7 +1,7 @@
''' Testing API: Change substitutions in OSS. ''' ''' Testing API: Change substitutions in OSS. '''
from apps.oss.models import OperationSchema, OperationType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, CstType, RSForm from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -10,6 +10,7 @@ class TestChangeSubstitutions(EndpointTester):
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create( self.owned = OperationSchema.create(
title='Test', title='Test',

View File

@ -1,6 +1,5 @@
''' Testing API: Operation Schema - blocks manipulation. ''' ''' Testing API: Operation Schema - blocks manipulation. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.oss.models import Operation, OperationSchema, OperationType
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +8,7 @@ class TestOssBlocks(EndpointTester):
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk self.owned_id = self.owned.model.pk

View File

@ -1,6 +1,13 @@
''' Testing API: Operation Schema - operations manipulation. ''' ''' Testing API: Operation Schema - operations manipulation. '''
from apps.library.models import Editor, LibraryItem 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,
PropagationFacade,
Replica
)
from apps.rsform.models import Attribution, RSForm from apps.rsform.models import Attribution, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -10,6 +17,7 @@ class TestOssOperations(EndpointTester):
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk self.owned_id = self.owned.model.pk

View File

@ -1,6 +1,6 @@
''' Testing API: Operation Schema. ''' ''' Testing API: Operation Schema. '''
from apps.library.models import AccessPolicy, LibraryItemType from apps.library.models import AccessPolicy, LibraryItemType
from apps.oss.models import OperationSchema, OperationType from apps.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, RSForm from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestOssViewset(EndpointTester):
''' Testing OSS view. ''' ''' Testing OSS view. '''
def setUp(self): def setUp(self):
PropagationFacade.reset_cache()
super().setUp() super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk self.owned_id = self.owned.model.pk
@ -220,8 +221,9 @@ class TestOssViewset(EndpointTester):
self.executeBadData(data) 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): def test_relocate_constituents(self):
self.set_params(item=self.owned_id)
self.populateData() self.populateData()
self.ks1X2 = self.ks1.insert_last('X2', convention='test') self.ks1X2 = self.ks1.insert_last('X2', convention='test')

View File

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

View File

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

View File

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

View File

@ -91,7 +91,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
insert_after = data['insert_after'] insert_after = data['insert_after']
with transaction.atomic(): with transaction.atomic():
schema = m.RSFormCached(item) schema = m.RSFormCached(item.pk)
new_cst = schema.create_cst(data, insert_after) new_cst = schema.create_cst(data, insert_after)
PropagationFacade.after_create_cst(schema, [new_cst]) PropagationFacade.after_create_cst(schema, [new_cst])
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -125,7 +125,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
data = serializer.validated_data['item_data'] data = serializer.validated_data['item_data']
with transaction.atomic(): with transaction.atomic():
schema = m.RSFormCached(item) schema = m.RSFormCached(item.pk)
old_data = schema.update_cst(cst.pk, data) old_data = schema.update_cst(cst.pk, data)
PropagationFacade.after_update_cst(schema, cst.pk, data, old_data) PropagationFacade.after_update_cst(schema, cst.pk, data, old_data)
if 'alias' in data and data['alias'] != cst.alias: if 'alias' in data and data['alias'] != cst.alias:
@ -208,7 +208,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
) )
with transaction.atomic(): with transaction.atomic():
schema = m.RSFormCached(item) schema = m.RSFormCached(item.pk)
new_cst = schema.produce_structure(cst, cst_parse) new_cst = schema.produce_structure(cst, cst_parse)
PropagationFacade.after_create_cst(schema, new_cst) PropagationFacade.after_create_cst(schema, new_cst)
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
@ -456,7 +456,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
item = self._get_item() item = self._get_item()
with transaction.atomic(): with transaction.atomic():
m.OrderManager(m.RSFormCached(item)).restore_order() m.OrderManager(m.RSFormCached(item.pk)).restore_order()
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
@ -731,7 +731,8 @@ def inline_synthesis(request: Request) -> HttpResponse:
serializer = s.InlineSynthesisSerializer(data=request.data, context={'user': request.user}) serializer = s.InlineSynthesisSerializer(data=request.data, context={'user': request.user})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
receiver = m.RSFormCached(serializer.validated_data['receiver']) item = cast(LibraryItem, serializer.validated_data['receiver'])
receiver = m.RSFormCached(item.pk)
target_cst = cast(list[m.Constituenta], serializer.validated_data['items']) target_cst = cast(list[m.Constituenta], serializer.validated_data['items'])
source = cast(LibraryItem, serializer.validated_data['source']) source = cast(LibraryItem, serializer.validated_data['source'])
target_ids = [item.pk for item in target_cst] if target_cst else None target_ids = [item.pk for item in target_cst] if target_cst else None
@ -752,11 +753,11 @@ def inline_synthesis(request: Request) -> HttpResponse:
replacement = mapping_ids[replacement.pk] replacement = mapping_ids[replacement.pk]
substitutions.append((original, replacement)) substitutions.append((original, replacement))
PropagationFacade.before_substitute(receiver.model.pk, substitutions) PropagationFacade.before_substitute(receiver.pk, substitutions)
receiver.substitute(substitutions) receiver.substitute(substitutions)
receiver.model.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.RSFormParseSerializer(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>({ axiosPost<IRelocateConstituentsDTO>({
endpoint: `/api/oss/relocate-constituents`, endpoint: `/api/oss/${itemID}/relocate-constituents`,
request: { request: {
data: data, data: data,
successMessage: infoMsg.changesSaved successMessage: infoMsg.changesSaved

View File

@ -19,6 +19,6 @@ export const useRelocateConstituents = () => {
onError: () => client.invalidateQueries() onError: () => client.invalidateQueries()
}); });
return { 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) { function onSubmit(data: IRelocateConstituentsDTO) {
data.items = moveTarget; data.items = moveTarget;
if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) { if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) {
return relocateConstituents(data); return relocateConstituents({ itemID: oss.id, data: data });
} else { } else {
return updatePositions({ return updatePositions({
isSilent: true, isSilent: true,
itemID: oss.id, itemID: oss.id,
data: layout data: layout
}).then(() => relocateConstituents(data)); }).then(() => relocateConstituents({ itemID: oss.id, data: data }));
} }
} }