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:
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

@ -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

@ -19,9 +19,9 @@ 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)
def __init__(self, item_id: int):
self.pk = item_id
self.cache = OssCache(item_id)
self.engine = PropagationEngine(self.cache)
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])
old_schema = self.cache.get_schema(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.before_delete_cst(old_schema.pk, [cst.pk for cst in old_schema.cache.constituents])
self.cache.remove_schema(old_schema)
operation.setQ_result(schema)
@ -88,7 +88,7 @@ class OperationSchemaCached:
operation.save(update_fields=['alias', 'title', 'description'])
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')))
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
@ -172,7 +172,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])
new_schema = OperationSchema.create_input(self.pk, self.cache.operation_by_id[operation.pk])
receiver = RSFormCached(new_schema.pk)
self.cache.insert_schema(receiver)
parents: dict = {}
@ -190,7 +191,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,9 +205,8 @@ 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'))
receiver_items = list(Constituenta.objects.filter(schema_id=receiver.pk).order_by('order'))
self.after_create_cst(receiver, receiver_items)
receiver.model.save(update_fields=['time_update'])
return True
def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]):
@ -214,7 +214,7 @@ class OperationSchemaCached:
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(destination.pk)
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:
@ -228,7 +228,7 @@ class OperationSchemaCached:
self.cache.insert_schema(source)
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] = {}
for item in self.cache.inheritance[operation.pk]:
if item.parent_id in destination.cache.by_id:
@ -236,7 +236,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(source.pk, item_ids, alias_mapping)
for (cst, new_cst) in new_items:
new_inheritance = Inheritance.objects.create(
operation=operation,
@ -246,7 +246,6 @@ 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'])
return new_constituents
def after_create_cst(
@ -257,7 +256,7 @@ class OperationSchemaCached:
''' Trigger cascade resolutions when new Constituenta is created. '''
self.cache.insert_schema(source)
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:
@ -268,7 +267,7 @@ class OperationSchemaCached:
def after_update_cst(self, source: RSFormCached, 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(source.pk)
depend_aliases = extract_data_references(data, old_data)
alias_mapping: CstMapping = {}
for alias in depend_aliases:
@ -347,7 +346,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

@ -67,7 +67,7 @@ class OssCache:
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 = RSFormCached(operation.result_id)
schema.cache.ensure_loaded()
self._insert_new(schema)
return schema
@ -77,7 +77,7 @@ class OssCache:
if target in self._schema_by_id:
return self._schema_by_id[target]
else:
schema = RSFormCached.from_id(target)
schema = RSFormCached(target)
schema.cache.ensure_loaded()
self._insert_new(schema)
return schema
@ -113,7 +113,7 @@ class OssCache:
def insert_schema(self, schema: RSFormCached) -> None:
''' 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()
self._insert_new(schema)
@ -148,7 +148,7 @@ class OssCache:
def remove_schema(self, schema: RSFormCached) -> None:
''' Remove schema from cache. '''
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:
''' Remove operation from cache. '''
@ -185,4 +185,4 @@ class OssCache:
def _insert_new(self, schema: RSFormCached) -> None:
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. '''
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
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).values_list('pk', flat=True))
class PropagationFacade:
''' 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
def after_create_cst(source: RSFormCached, 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)
hosts = _get_oss_hosts(source.pk)
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:
PropagationFacade.get_oss(host).after_create_cst(source, new_cst)
@staticmethod
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. '''
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:
PropagationFacade.get_oss(host).after_change_cst_type(sourceID, target, new_type)
@staticmethod
def after_update_cst(
@ -42,10 +56,10 @@ class PropagationFacade:
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(source.pk)
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:
PropagationFacade.get_oss(host).after_update_cst(source, target, data, old_data)
@staticmethod
def before_delete_cst(sourceID: int, target: list[int],
@ -53,8 +67,8 @@ class PropagationFacade:
''' 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:
PropagationFacade.get_oss(host).before_delete_cst(sourceID, target)
@staticmethod
def before_substitute(sourceID: int, substitutions: CstSubstitution,
@ -64,31 +78,31 @@ class PropagationFacade:
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:
PropagationFacade.get_oss(host).before_substitute(sourceID, substitutions)
@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. '''
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:
PropagationFacade.get_oss(host).before_delete_cst(target, ids)
del PropagationFacade._oss[host]
@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:
''' 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:
PropagationFacade.get_oss(host).after_create_attribution(sourceID, attributions)
@staticmethod
def before_delete_attribution(sourceID: int,
@ -97,5 +111,5 @@ class PropagationFacade:
''' 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:
PropagationFacade.get_oss(host).before_delete_attribution(sourceID, attributions)

View File

@ -625,15 +625,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, PropagationFacade
from apps.rsform.models import RSForm
from apps.users.models import User
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -11,6 +11,7 @@ class TestChangeAttributes(EndpointTester):
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.user3 = User.objects.create(
username='UserTest3',

View File

@ -1,6 +1,6 @@
''' 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 shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestChangeConstituents(EndpointTester):
''' Testing Constituents change propagation in OSS. '''
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(
title='Test',

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.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestChangeOperations(EndpointTester):
''' Testing Operations change propagation in OSS. '''
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(
title='Test',
@ -388,7 +389,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 +409,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 +419,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 +439,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

@ -1,6 +1,6 @@
''' 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 shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class ReferencePropagationTestCase(EndpointTester):
''' Test propagation through references in OSS. '''
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(
@ -163,7 +164,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.oss.models import OperationSchema, OperationType, PropagationFacade
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -10,6 +10,7 @@ class TestChangeSubstitutions(EndpointTester):
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(
title='Test',

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, PropagationFacade
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +8,7 @@ class TestOssBlocks(EndpointTester):
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk

View File

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

View File

@ -1,6 +1,6 @@
''' Testing API: Operation Schema. '''
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 shared.EndpointTester import EndpointTester, decl_endpoint
@ -9,6 +9,7 @@ class TestOssViewset(EndpointTester):
''' Testing OSS view. '''
def setUp(self):
PropagationFacade.reset_cache()
super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk
@ -220,8 +221,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,6 +14,7 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer
from apps.oss.models import PropagationFacade
from apps.rsform.models import Constituenta, RSFormCached
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
@ -291,7 +292,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 +421,7 @@ 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)
RSFormCached(schema_clone.pk).insert_from(prototype.pk)
new_operation.result = schema_clone
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'])
with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss = PropagationFacade.get_oss(item.pk)
if 'layout' in serializer.validated_data:
layout = serializer.validated_data['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)]
with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss = PropagationFacade.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)
m.PropagationFacade.before_delete_schema(old_schema.pk)
old_schema.delete()
elif old_schema.is_synced(item):
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)]
with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss = PropagationFacade.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 +681,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 +727,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
old_schema = target_operation.result
with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss = PropagationFacade.get_oss(item.pk)
if old_schema is not None:
if old_schema.is_synced(item):
old_schema.visible = True
@ -769,7 +770,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
layout = serializer.validated_data['layout']
with transaction.atomic():
oss = m.OperationSchemaCached(item)
oss = PropagationFacade.get_oss(item.pk)
oss.execute_operation(operation)
m.Layout.update_data(pk, layout)
item.save(update_fields=['time_update'])
@ -823,24 +824,26 @@ 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']]
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']))
oss = PropagationFacade.get_oss(item.pk)
source = RSFormCached(data['source'])
destination = RSFormCached(data['destination'])
if data['move_down']:
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)
else:
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)

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):
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]

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

@ -91,7 +91,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
insert_after = data['insert_after']
with transaction.atomic():
schema = m.RSFormCached(item)
schema = m.RSFormCached(item.pk)
new_cst = schema.create_cst(data, insert_after)
PropagationFacade.after_create_cst(schema, [new_cst])
item.save(update_fields=['time_update'])
@ -125,7 +125,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
data = serializer.validated_data['item_data']
with transaction.atomic():
schema = m.RSFormCached(item)
schema = m.RSFormCached(item.pk)
old_data = schema.update_cst(cst.pk, data)
PropagationFacade.after_update_cst(schema, cst.pk, data, old_data)
if 'alias' in data and data['alias'] != cst.alias:
@ -208,7 +208,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
)
with transaction.atomic():
schema = m.RSFormCached(item)
schema = m.RSFormCached(item.pk)
new_cst = schema.produce_structure(cst, cst_parse)
PropagationFacade.after_create_cst(schema, new_cst)
item.save(update_fields=['time_update'])
@ -456,7 +456,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(
@ -731,7 +731,8 @@ 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'])
receiver = m.RSFormCached(item.pk)
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
@ -752,11 +753,11 @@ def inline_synthesis(request: Request) -> HttpResponse:
replacement = mapping_ids[replacement.pk]
substitutions.append((original, replacement))
PropagationFacade.before_substitute(receiver.model.pk, substitutions)
PropagationFacade.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 }));
}
}