R: Refactor constituenta insertion

This commit is contained in:
Ivan 2025-11-07 00:01:37 +03:00
parent 48de17acc7
commit e9f31c0e7a
12 changed files with 118 additions and 131 deletions

View File

@ -54,7 +54,7 @@ class LibraryItemCloneSerializer(StrictSerializer):
model = LibraryItem
exclude = ['id', 'item_type', 'owner', 'read_only']
items = PKField(many=True, queryset=Constituenta.objects.all().only('pk', 'schema_id'))
items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id'))
item_data = ItemCloneData()
def validate_items(self, value):

View File

@ -14,7 +14,7 @@ from rest_framework.request import Request
from rest_framework.response import Response
from apps.oss.models import Layout, Operation, OperationSchema, PropagationFacade
from apps.rsform.models import Attribution, RSFormCached
from apps.rsform.models import RSFormCached
from apps.rsform.serializers import RSFormParseSerializer
from apps.users.models import User
from shared import permissions
@ -172,22 +172,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
clone.location = data.get('location', m.LocationHead.USER)
clone.save()
cst_map: dict[int, int] = {}
cst_list: list[int] = []
need_filter = 'items' in request.data and request.data['items']
for cst in RSFormCached(item).constituentsQ():
if not need_filter or cst.pk in request.data['items']:
old_pk = cst.pk
cst.pk = None
cst.schema = clone
cst.save()
cst_map[old_pk] = cst.pk
cst_list.append(old_pk)
for attr in Attribution.objects.filter(container__in=cst_list, attribute__in=cst_list):
attr.pk = None
attr.container_id = cst_map[attr.container_id]
attr.attribute_id = cst_map[attr.attribute_id]
attr.save()
RSFormCached(clone).insert_from(item.pk, request.data['items'] if 'items' in request.data else None)
return Response(
status=c.HTTP_201_CREATED,

View File

@ -137,11 +137,10 @@ class OperationSchema:
parents: dict = {}
children: dict = {}
for operand in schemas:
items = list(Constituenta.objects.filter(schema_id=operand).order_by('order'))
new_items = receiver.insert_copy(items)
for (i, cst) in enumerate(new_items):
parents[cst.pk] = items[i]
children[items[i].pk] = cst
new_items = receiver.insert_from(operand)
for (old_cst, new_cst) in new_items:
parents[new_cst.pk] = old_cst
children[old_cst.pk] = new_cst
translated_substitutions: list[tuple[Constituenta, Constituenta]] = []
for sub in substitutions:

View File

@ -178,11 +178,10 @@ class OperationSchemaCached:
parents: dict = {}
children: dict = {}
for operand in schemas:
items = list(Constituenta.objects.filter(schema_id=operand).order_by('order'))
new_items = receiver.insert_copy(items)
for (i, cst) in enumerate(new_items):
parents[cst.pk] = items[i]
children[items[i].pk] = cst
new_items = receiver.insert_from(operand)
for (old_cst, new_cst) in new_items:
parents[new_cst.pk] = old_cst
children[old_cst.pk] = new_cst
translated_substitutions: list[tuple[Constituenta, Constituenta]] = []
for sub in substitutions:
@ -223,7 +222,7 @@ class OperationSchemaCached:
Inheritance.objects.filter(operation_id=operation.pk, parent_id__in=items).delete()
def relocate_up(self, source: RSFormCached, destination: RSFormCached,
items: list[Constituenta]) -> list[Constituenta]:
item_ids: list[int]) -> list[Constituenta]:
''' Move list of Constituents upstream to destination Schema. '''
self.cache.ensure_loaded_subs()
self.cache.insert_schema(source)
@ -237,17 +236,18 @@ class OperationSchemaCached:
destination_cst = destination.cache.by_id[item.parent_id]
alias_mapping[source_cst.alias] = destination_cst.alias
new_items = destination.insert_copy(items, initial_mapping=alias_mapping)
for index, cst in enumerate(new_items):
new_items = destination.insert_from(source.model.pk, item_ids, alias_mapping)
for (cst, new_cst) in new_items:
new_inheritance = Inheritance.objects.create(
operation=operation,
child=items[index],
parent=cst
child=cst,
parent=new_cst
)
self.cache.insert_inheritance(new_inheritance)
self.after_create_cst(destination, new_items, exclude=[operation.pk])
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_items
return new_constituents
def after_create_cst(
self, source: RSFormCached,

View File

@ -3,7 +3,7 @@ from typing import Optional
from rest_framework.serializers import ValidationError
from apps.rsform.models import INSERT_LAST, Attribution, Constituenta, CstType, RSFormCached
from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached
from .Inheritance import Inheritance
from .Operation import Operation
@ -76,11 +76,11 @@ class PropagationEngine:
alias_mapping = cst_mapping_to_alias(new_mapping)
insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
for index, cst in enumerate(new_cst_list):
for (cst, new_cst) in zip(items, new_cst_list):
new_inheritance = Inheritance.objects.create(
operation=operation,
child=cst,
parent=items[index]
child=new_cst,
parent=cst
)
self.cache.insert_inheritance(new_inheritance)
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
@ -145,28 +145,28 @@ class PropagationEngine:
self.cache.ensure_loaded_subs()
existing_associations = set(
existing_attributions = set(
Attribution.objects.filter(
container__schema_id=operation.result_id,
).values_list('container_id', 'attribute_id')
)
new_associations: list[Attribution] = []
for assoc in items:
new_container = self.cache.get_inheritor(assoc.container_id, target)
new_attribute = self.cache.get_inheritor(assoc.attribute_id, target)
new_attributions: list[Attribution] = []
for attrib in items:
new_container = self.cache.get_inheritor(attrib.container_id, target)
new_attribute = self.cache.get_inheritor(attrib.attribute_id, target)
if new_container is None or new_attribute is None \
or new_attribute == new_container \
or (new_container, new_attribute) in existing_associations:
or (new_container, new_attribute) in existing_attributions:
continue
new_associations.append(Attribution(
new_attributions.append(Attribution(
container_id=new_container,
attribute_id=new_attribute
))
if new_associations:
new_associations = Attribution.objects.bulk_create(new_associations)
self.on_inherit_attribution(target, new_associations)
if new_attributions:
new_attributions = Attribution.objects.bulk_create(new_attributions)
self.on_inherit_attribution(target, new_attributions)
def on_before_substitute(self, operationID: int, substitutions: CstSubstitution) -> None:
''' Trigger cascade resolutions when Constituenta substitution is executed. '''
@ -286,7 +286,7 @@ class PropagationEngine:
operation: Operation,
source: RSFormCached,
destination: RSFormCached
) -> int:
) -> Optional[int]:
''' Determine insert_after for new constituenta. '''
prototype = source.cache.by_id[prototype_id]
prototype_index = source.cache.constituents.index(prototype)
@ -295,7 +295,7 @@ class PropagationEngine:
prev_cst = source.cache.constituents[prototype_index - 1]
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
if inherited_prev_id is None:
return INSERT_LAST
return None
prev_cst = destination.cache.by_id[inherited_prev_id]
prev_index = destination.cache.constituents.index(prev_cst)
return prev_index + 1

View File

@ -602,7 +602,7 @@ class RelocateConstituentsSerializer(StrictSerializer):
items = PKField(
many=True,
allow_empty=False,
queryset=Constituenta.objects.all()
queryset=Constituenta.objects.all().only('schema_id')
)
def validate(self, attrs):

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 Attribution, Constituenta, RSFormCached
from apps.rsform.models import Constituenta, RSFormCached
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
from shared import permissions
@ -23,40 +23,6 @@ from .. import models as m
from .. import serializers as s
def _create_clone(prototype: LibraryItem, operation: m.Operation, oss: LibraryItem) -> LibraryItem:
''' Create clone of prototype schema for operation. '''
clone = deepcopy(prototype)
clone.pk = None
clone.owner = oss.owner
clone.title = operation.title
clone.alias = operation.alias
clone.description = operation.description
clone.visible = False
clone.read_only = False
clone.access_policy = oss.access_policy
clone.location = oss.location
clone.save()
cst_map: dict[int, int] = {}
cst_list: list[int] = []
for cst in Constituenta.objects.filter(schema_id=prototype.pk):
old_pk = cst.pk
cst_copy = deepcopy(cst)
cst_copy.pk = None
cst_copy.schema = clone
cst_copy.save()
cst_map[old_pk] = cst_copy.pk
cst_list.append(old_pk)
for attr in Attribution.objects.filter(container__in=cst_list, attribute__in=cst_list):
attr.pk = None
attr.container_id = cst_map[attr.container_id]
attr.attribute_id = cst_map[attr.attribute_id]
attr.save()
return clone
@extend_schema(tags=['OSS'])
@extend_schema_view()
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
@ -442,7 +408,21 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
if serializer.validated_data['clone_source']:
prototype: LibraryItem = serializer.validated_data['source']
new_operation.result = _create_clone(prototype, new_operation, item)
schema_clone = deepcopy(prototype)
schema_clone.pk = None
schema_clone.owner = item.owner
schema_clone.title = new_operation.title
schema_clone.alias = new_operation.alias
schema_clone.description = new_operation.description
schema_clone.visible = False
schema_clone.read_only = False
schema_clone.access_policy = item.access_policy
schema_clone.location = item.location
schema_clone.save()
RSFormCached(schema_clone).insert_from(prototype.pk)
new_operation.result = schema_clone
new_operation.save(update_fields=["result"])
item.save(update_fields=['time_update'])
@ -860,7 +840,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
m.PropagationFacade.before_delete_cst(data['source'], ids)
source.delete_cst(ids)
else:
new_items = oss.relocate_up(source, destination, data['items'])
new_items = oss.relocate_up(source, destination, ids)
m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk])
return Response(status=c.HTTP_200_OK)

View File

@ -15,7 +15,6 @@ from .api_RSLanguage import get_type_prefix, guess_type
from .Attribution import Attribution
from .Constituenta import Constituenta, CstType, extract_entities, extract_globals
INSERT_LAST: int = -1
DELETED_ALIAS = 'DEL'

View File

@ -14,7 +14,7 @@ from shared import messages as msg
from .api_RSLanguage import generate_structure, get_type_prefix, guess_type
from .Attribution import Attribution
from .Constituenta import Constituenta, CstType
from .RSForm import DELETED_ALIAS, INSERT_LAST, RSForm
from .RSForm import DELETED_ALIAS, RSForm
class RSFormCached:
@ -76,7 +76,7 @@ class RSFormCached:
def create_cst(self, data: dict, insert_after: Optional[Constituenta] = None) -> Constituenta:
''' Create constituenta from data. '''
self.cache.ensure_loaded_terms()
if insert_after is not None:
if insert_after:
position = self.cache.by_id[insert_after.pk].order + 1
else:
position = len(self.cache.constituents)
@ -109,52 +109,77 @@ class RSFormCached:
RSForm.resolve_term_change(self.cache.constituents, [result.pk], self.cache.by_alias, self.cache.by_id)
return result
def insert_from(
self, sourceID: int,
items_list: Optional[list[int]] = None,
initial_mapping: Optional[dict[str, str]] = None
) -> list[tuple[Constituenta, Constituenta]]:
''' Insert copy of constituents from source schema. '''
if not items_list:
items = list(Constituenta.objects.filter(schema_id=sourceID).order_by('order'))
else:
items = list(Constituenta.objects.filter(pk__in=items_list, schema_id=sourceID).order_by('order'))
if not items:
return []
new_constituents = self.insert_copy(items=items, initial_mapping=initial_mapping)
return list(zip(items, new_constituents))
def insert_copy(
self,
items: list[Constituenta],
position: int = INSERT_LAST,
position: Optional[int] = None,
initial_mapping: Optional[dict[str, str]] = None
) -> list[Constituenta]:
''' Insert copy of target constituents updating references. '''
count = len(items)
if count == 0:
if not items:
return []
self.cache.ensure_loaded()
lastPosition = len(self.cache.constituents)
if position == INSERT_LAST:
position = lastPosition
last_position = len(self.cache.constituents)
if not position:
position = last_position
else:
position = max(0, min(position, lastPosition))
RSForm.shift_positions(position, count, self.cache.constituents)
position = max(0, min(position, last_position))
indices: dict[str, int] = {}
for (value, _) in CstType.choices:
indices[value] = -1
was_empty = last_position == 0
if not was_empty and position != last_position:
RSForm.shift_positions(position, len(items), self.cache.constituents)
mapping: dict[str, str] = initial_mapping.copy() if initial_mapping else {}
for cst in items:
if indices[cst.cst_type] == -1:
indices[cst.cst_type] = self._get_max_index(cst.cst_type)
indices[cst.cst_type] = indices[cst.cst_type] + 1
newAlias = f'{get_type_prefix(cst.cst_type)}{indices[cst.cst_type]}'
mapping[cst.alias] = newAlias
mapping_alias: dict[str, str] = initial_mapping.copy() if initial_mapping else {}
if not was_empty:
indices: dict[str, int] = {}
for (value, _) in CstType.choices:
indices[value] = self._get_max_index(value)
result = deepcopy(items)
for cst in result:
for cst in items:
indices[cst.cst_type] = indices[cst.cst_type] + 1
newAlias = f'{get_type_prefix(cst.cst_type)}{indices[cst.cst_type]}'
mapping_alias[cst.alias] = newAlias
source_ids = [cst.id for cst in items]
new_constituents = deepcopy(items)
for cst in new_constituents:
cst.pk = None
cst.schema = self.model
cst.order = position
cst.alias = mapping[cst.alias]
cst.apply_mapping(mapping)
if mapping_alias:
cst.alias = mapping_alias[cst.alias]
cst.apply_mapping(mapping_alias)
position = position + 1
new_cst = Constituenta.objects.bulk_create(result)
new_constituents = Constituenta.objects.bulk_create(new_constituents)
# TODO: duplicate attributions
mapping_id: dict[int, int] = {source_ids[i]: new_constituents[i].id for i in range(len(source_ids))}
attributions = list(Attribution.objects.filter(container__in=source_ids, attribute__in=source_ids))
for attr in attributions:
attr.pk = None
attr.container_id = mapping_id[attr.container_id]
attr.attribute_id = mapping_id[attr.attribute_id]
self.cache.insert_multi(new_cst)
return result
Attribution.objects.bulk_create(attributions)
self.cache.insert_multi(new_constituents)
return new_constituents
# pylint: disable=too-many-branches
def update_cst(self, target: int, data: dict) -> dict:

View File

@ -3,5 +3,5 @@
from .Attribution import Attribution
from .Constituenta import Constituenta, CstType, extract_globals, replace_entities, replace_globals
from .OrderManager import OrderManager
from .RSForm import DELETED_ALIAS, INSERT_LAST, RSForm
from .RSForm import DELETED_ALIAS, RSForm
from .RSFormCached import RSFormCached

View File

@ -436,7 +436,7 @@ class InlineSynthesisSerializer(StrictSerializer):
''' Serializer: Inline synthesis operation input. '''
receiver = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id'))
source = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id')) # type: ignore
items = PKField(many=True, queryset=Constituenta.objects.all())
items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id'))
substitutions = serializers.ListField(
child=SubstitutionSerializerBase()
)

View File

@ -732,25 +732,24 @@ def inline_synthesis(request: Request) -> HttpResponse:
serializer.is_valid(raise_exception=True)
receiver = m.RSFormCached(serializer.validated_data['receiver'])
items = cast(list[m.Constituenta], serializer.validated_data['items'])
if not items:
source = cast(LibraryItem, serializer.validated_data['source'])
items = list(m.Constituenta.objects.filter(schema=source).order_by('order'))
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():
new_items = receiver.insert_copy(items)
PropagationFacade.after_create_cst(receiver, new_items)
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])
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
for substitution in serializer.validated_data['substitutions']:
original = cast(m.Constituenta, substitution['original'])
replacement = cast(m.Constituenta, substitution['substitution'])
if original in items:
index = next(i for (i, cst) in enumerate(items) if cst.pk == original.pk)
original = new_items[index]
if original.pk in target_ids:
original = mapping_ids[original.pk]
else:
index = next(i for (i, cst) in enumerate(items) if cst.pk == replacement.pk)
replacement = new_items[index]
replacement = mapping_ids[replacement.pk]
substitutions.append((original, replacement))
PropagationFacade.before_substitute(receiver.model.pk, substitutions)