mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Propagate cst_delete
This commit is contained in:
parent
71ba81ea0e
commit
5d6c911583
|
@ -26,7 +26,7 @@ CstMapping = dict[str, Constituenta]
|
|||
|
||||
|
||||
class ChangeManager:
|
||||
''' Change propagation API. '''
|
||||
''' Change propagation wrapper for OSS. '''
|
||||
class Cache:
|
||||
''' Cache for RSForm constituents. '''
|
||||
|
||||
|
@ -102,6 +102,15 @@ class ChangeManager:
|
|||
''' Insert new inheritance. '''
|
||||
self.inheritance[inheritance.operation_id].append((inheritance.parent_id, inheritance.child_id))
|
||||
|
||||
def remove_cst(self, target: list[int], operation: int) -> None:
|
||||
''' Remove constituents from operation. '''
|
||||
subs = [sub for sub in self.substitutions if sub.original_id in target or sub.substitution_id in target]
|
||||
for sub in subs:
|
||||
self.substitutions.remove(sub)
|
||||
to_delete = [item for item in self.inheritance[operation] if item[1] in target]
|
||||
for item in to_delete:
|
||||
self.inheritance[operation].remove(item)
|
||||
|
||||
def _insert_new(self, schema: RSForm) -> None:
|
||||
self._schemas.append(schema)
|
||||
self._schema_by_id[schema.model.pk] = schema
|
||||
|
@ -142,6 +151,37 @@ class ChangeManager:
|
|||
alias_mapping[alias] = cst
|
||||
self._cascade_update_cst(target.pk, operation, data, old_data, alias_mapping)
|
||||
|
||||
def before_delete(self, target: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source)
|
||||
self._cascade_before_delete(target, operation)
|
||||
|
||||
def _cascade_before_delete(self, target: list[Constituenta], operation: Operation) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
child_schema.cache.ensure_loaded()
|
||||
|
||||
# TODO: check if substitutions are affected. Undo substitutions before deletion
|
||||
|
||||
child_target_cst = []
|
||||
child_target_ids = []
|
||||
for cst in target:
|
||||
successor_id = self.cache.get_successor_for(cst.pk, child_id, ignore_substitution=True)
|
||||
if successor_id is not None:
|
||||
child_target_ids.append(successor_id)
|
||||
child_target_cst.append(child_schema.cache.by_id[successor_id])
|
||||
self._cascade_before_delete(child_target_cst, child_operation)
|
||||
self.cache.remove_cst(child_target_ids, child_id)
|
||||
child_schema.delete_cst(child_target_cst)
|
||||
|
||||
def _cascade_create_cst(self, prototype: Constituenta, operation: Operation, mapping: CstMapping) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
|
|
42
rsconcept/backend/apps/oss/models/PropagationFacade.py
Normal file
42
rsconcept/backend/apps/oss/models/PropagationFacade.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
''' Models: Change propagation facade - managing all changes in OSS. '''
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
|
||||
from .ChangeManager import ChangeManager
|
||||
|
||||
|
||||
def _get_oss_hosts(item: LibraryItem) -> list[LibraryItem]:
|
||||
''' Get all hosts for LibraryItem. '''
|
||||
return list(LibraryItem.objects.filter(operations__result=item).only('pk'))
|
||||
|
||||
|
||||
class PropagationFacade:
|
||||
''' Change propagation API. '''
|
||||
|
||||
@classmethod
|
||||
def on_create_cst(cls, new_cst: Constituenta, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when new constituent is created. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_create_cst(new_cst, source)
|
||||
|
||||
@classmethod
|
||||
def on_change_cst_type(cls, target: Constituenta, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_change_cst_type(target, source)
|
||||
|
||||
@classmethod
|
||||
def on_update_cst(cls, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_update_cst(target, data, old_data, source)
|
||||
|
||||
@classmethod
|
||||
def before_delete(cls, target: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).before_delete(target, source)
|
|
@ -6,3 +6,4 @@ from .Inheritance import Inheritance
|
|||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .Substitution import Substitution
|
||||
from .PropagationFacade import PropagationFacade
|
||||
|
|
|
@ -57,7 +57,6 @@ class TestChangeConstituents(EndpointTester):
|
|||
self.ks3 = RSForm(self.operation3.result)
|
||||
self.assertEqual(self.ks3.constituents().count(), 4)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/create-cst', method='post')
|
||||
def test_create_constituenta(self):
|
||||
data = {
|
||||
|
@ -107,3 +106,14 @@ class TestChangeConstituents(EndpointTester):
|
|||
self.assertEqual(inherited_cst.term_raw, data['item_data']['term_raw'])
|
||||
self.assertEqual(inherited_cst.definition_formal, r'X1\X1')
|
||||
self.assertEqual(inherited_cst.definition_raw, r'@{X2|sing,datv}')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch')
|
||||
def test_delete_constituenta(self):
|
||||
data = {'items': [self.ks2X1.pk]}
|
||||
response = self.executeOK(data=data, schema=self.ks2.model.pk)
|
||||
inherited_cst = Constituenta.objects.get(as_child__parent_id=self.ks2D1.pk)
|
||||
self.ks2D1.refresh_from_db()
|
||||
self.assertEqual(self.ks2.constituents().count(), 1)
|
||||
self.assertEqual(self.ks3.constituents().count(), 3)
|
||||
self.assertEqual(self.ks2D1.definition_formal, r'DEL\DEL')
|
||||
self.assertEqual(inherited_cst.definition_formal, r'DEL\DEL')
|
||||
|
|
|
@ -23,6 +23,7 @@ from .api_RSLanguage import (
|
|||
from .Constituenta import Constituenta, CstType, extract_globals
|
||||
|
||||
INSERT_LAST: int = -1
|
||||
DELETED_ALIAS = 'DEL'
|
||||
|
||||
|
||||
class RSForm:
|
||||
|
@ -348,10 +349,12 @@ class RSForm:
|
|||
|
||||
def delete_cst(self, target: Iterable[Constituenta]) -> None:
|
||||
''' Delete multiple constituents. Do not check if listCst are from this schema. '''
|
||||
mapping = {cst.alias: DELETED_ALIAS for cst in target}
|
||||
self.cache.ensure_loaded()
|
||||
self.cache.remove_multi(target)
|
||||
self.apply_mapping(mapping)
|
||||
Constituenta.objects.filter(pk__in=[cst.pk for cst in target]).delete()
|
||||
self._reset_order()
|
||||
self.resolve_all_text()
|
||||
self.save()
|
||||
|
||||
def substitute(self, substitutions: list[tuple[Constituenta, Constituenta]]) -> None:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from .Constituenta import Constituenta, CstType, extract_globals, replace_entities, replace_globals
|
||||
from .RSForm import INSERT_LAST, RSForm
|
||||
from .RSForm import DELETED_ALIAS, INSERT_LAST, RSForm, SemanticInfo
|
||||
|
|
|
@ -174,6 +174,26 @@ class TestRSForm(DBTester):
|
|||
self.assertEqual(s2.definition_raw, '@{X11|plur}')
|
||||
|
||||
|
||||
def test_delete_cst(self):
|
||||
x1 = self.schema.insert_new('X1')
|
||||
x2 = self.schema.insert_new('X2')
|
||||
d1 = self.schema.insert_new(
|
||||
alias='D1',
|
||||
definition_formal='X1 = X2',
|
||||
definition_raw='@{X1|sing}',
|
||||
term_raw='@{X2|plur}'
|
||||
)
|
||||
|
||||
self.schema.delete_cst([x1])
|
||||
x2.refresh_from_db()
|
||||
d1.refresh_from_db()
|
||||
self.assertEqual(self.schema.constituents().count(), 2)
|
||||
self.assertEqual(x2.order, 1)
|
||||
self.assertEqual(d1.order, 2)
|
||||
self.assertEqual(d1.definition_formal, 'DEL = X2')
|
||||
self.assertEqual(d1.definition_raw, '@{DEL|sing}')
|
||||
self.assertEqual(d1.term_raw, '@{X2|plur}')
|
||||
|
||||
def test_apply_mapping(self):
|
||||
x1 = self.schema.insert_new('X1')
|
||||
x2 = self.schema.insert_new('X11')
|
||||
|
|
|
@ -16,7 +16,7 @@ from rest_framework.serializers import ValidationError
|
|||
|
||||
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||
from apps.library.serializers import LibraryItemSerializer
|
||||
from apps.oss.models import ChangeManager
|
||||
from apps.oss.models import PropagationFacade
|
||||
from apps.users.models import User
|
||||
from shared import messages as msg
|
||||
from shared import permissions, utility
|
||||
|
@ -84,15 +84,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
insert_after = None
|
||||
else:
|
||||
insert_after = data['insert_after']
|
||||
|
||||
schema = m.RSForm(self._get_item())
|
||||
with transaction.atomic():
|
||||
new_cst = schema.create_cst(data, insert_after)
|
||||
hosts = LibraryItem.objects.filter(operations__result=schema.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_create_cst(new_cst, schema)
|
||||
|
||||
|
||||
PropagationFacade.on_create_cst(new_cst, schema)
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
|
@ -118,16 +113,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
model = self._get_item()
|
||||
serializer = s.CstUpdateSerializer(data=request.data, partial=True, context={'schema': model})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
cst = cast(m.Constituenta, serializer.validated_data['target'])
|
||||
schema = m.RSForm(model)
|
||||
data = serializer.validated_data['item_data']
|
||||
with transaction.atomic():
|
||||
hosts = LibraryItem.objects.filter(operations__result=model)
|
||||
old_data = schema.update_cst(cst, data)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_update_cst(cst, data, old_data, schema)
|
||||
|
||||
PropagationFacade.on_update_cst(cst, data, old_data, schema)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.CstSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data
|
||||
|
@ -164,13 +155,15 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
status=c.HTTP_400_BAD_REQUEST,
|
||||
data={f'{cst.pk}': msg.constituentaNoStructure()}
|
||||
)
|
||||
schema = m.RSForm(model)
|
||||
|
||||
with transaction.atomic():
|
||||
result = m.RSForm(model).produce_structure(cst, cst_parse)
|
||||
result = schema.produce_structure(cst, cst_parse)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
'cst_list': result,
|
||||
'schema': s.RSFormParseSerializer(model).data
|
||||
'schema': s.RSFormParseSerializer(schema.model).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -191,28 +184,23 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
model = self._get_item()
|
||||
serializer = s.CstRenameSerializer(data=request.data, context={'schema': model})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
cst = cast(m.Constituenta, serializer.validated_data['target'])
|
||||
changed_type = cst.cst_type != serializer.validated_data['cst_type']
|
||||
mapping = {cst.alias: serializer.validated_data['alias']}
|
||||
cst.alias = serializer.validated_data['alias']
|
||||
cst.cst_type = serializer.validated_data['cst_type']
|
||||
schema = m.RSForm(model)
|
||||
|
||||
with transaction.atomic():
|
||||
cst.alias = serializer.validated_data['alias']
|
||||
cst.cst_type = serializer.validated_data['cst_type']
|
||||
cst.save()
|
||||
schema.apply_mapping(mapping=mapping, change_aliases=False)
|
||||
cst.refresh_from_db()
|
||||
if changed_type:
|
||||
hosts = LibraryItem.objects.filter(operations__result=model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).on_change_cst_type(cst, schema)
|
||||
|
||||
PropagationFacade.on_change_cst_type(cst, schema)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
'new_cst': s.CstSerializer(cst).data,
|
||||
'schema': s.RSFormParseSerializer(model).data
|
||||
'schema': s.RSFormParseSerializer(schema.model).data
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -236,19 +224,17 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
context={'schema': model}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
schema = m.RSForm(model)
|
||||
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
|
||||
with transaction.atomic():
|
||||
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'])
|
||||
substitutions.append((original, replacement))
|
||||
m.RSForm(model).substitute(substitutions)
|
||||
|
||||
model.refresh_from_db()
|
||||
schema.substitute(substitutions)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(model).data
|
||||
data=s.RSFormParseSerializer(schema.model).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -271,11 +257,14 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
context={'schema': model}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
||||
schema = m.RSForm(model)
|
||||
with transaction.atomic():
|
||||
m.RSForm(model).delete_cst(serializer.validated_data['items'])
|
||||
PropagationFacade.before_delete(cst_list, schema)
|
||||
schema.delete_cst(cst_list)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(model).data
|
||||
data=s.RSFormParseSerializer(schema.model).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
|
Loading…
Reference in New Issue
Block a user