Refactor insert functions for RSForm

Add option to insert copy constituents
This commit is contained in:
IRBorisov 2024-03-22 17:01:14 +03:00
parent a6822e2f2b
commit 68b6891ae4
5 changed files with 135 additions and 67 deletions

View File

@ -10,7 +10,7 @@ def renameTrivial(name: str):
def substituteTrivial(name: str):
return f'Отождествление конституенты с собой не корректно: {name}'
def renameTaken(name: str):
def aliasTaken(name: str):
return f'Имя уже используется: {name}'
def pyconceptFailure():
@ -25,7 +25,7 @@ def libraryTypeUnexpected():
def exteorFileVersionNotSupported():
return 'Некорректный формат файла Экстеор. Сохраните файл в новой версии'
def positionNegative():
def invalidPosition():
return 'Invalid position: should be positive integer'
def constituentaNoStructure():

View File

@ -1,5 +1,5 @@
''' Models: RSForm API. '''
from typing import Iterable, Optional, cast
from typing import Dict, Iterable, Optional, cast
from django.db import transaction
from django.db.models import QuerySet
@ -15,6 +15,9 @@ from ..graph import Graph
from .. import messages as msg
_INSERT_LAST: int = -1
class RSForm:
''' RSForm is math form of conceptual schema. '''
def __init__(self, item: LibraryItem):
@ -87,17 +90,13 @@ class RSForm:
return result
@transaction.atomic
def insert_at(self, position: int, alias: str, insert_type: CstType) -> 'Constituenta':
def insert_new(self, alias: str, insert_type: CstType, position: int = _INSERT_LAST) -> Constituenta:
''' Insert new constituenta at given position.
All following constituents order is shifted by 1 position. '''
if position <= 0:
raise ValidationError(msg.positionNegative())
if self.constituents().filter(alias=alias).exists():
raise ValidationError(msg.renameTaken(alias))
currentSize = self.constituents().count()
position = max(1, min(position, currentSize + 1))
raise ValidationError(msg.aliasTaken(alias))
position = self._get_insert_position(position)
self._shift_positions(position, 1)
result = Constituenta.objects.create(
schema=self.item,
order=position,
@ -109,25 +108,49 @@ class RSForm:
return result
@transaction.atomic
def insert_last(self, alias: str, insert_type: CstType) -> 'Constituenta':
''' Insert new constituenta at last position. '''
if self.constituents().filter(alias=alias).exists():
raise ValidationError(msg.renameTaken(alias))
position = 1
if self.constituents().exists():
position += self.constituents().count()
result = Constituenta.objects.create(
schema=self.item,
order=position,
alias=alias,
cst_type=insert_type
)
def insert_copy(self, items: list[Constituenta], position: int = _INSERT_LAST) -> list[Constituenta]:
''' Insert copy of target constituents updating references. '''
count = len(items)
if count == 0:
return []
position = self._get_insert_position(position)
self._shift_positions(position, count)
indices: Dict[str, int] = {}
for (value, _) in CstType.choices:
indices[value] = self.get_max_index(cast(CstType, value))
mapping: Dict[str, str] = {}
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[cst.alias] = newAlias
result: list[Constituenta] = []
for cst in items:
newCst = Constituenta.objects.create(
schema=self.item,
order=position,
alias=mapping[cst.alias],
cst_type=cst.cst_type,
convention=cst.convention,
term_raw=cst.term_raw,
term_resolved=cst.term_resolved,
term_forms=cst.term_forms,
definition_raw=cst.definition_raw,
definition_formal=cst.definition_formal,
definition_resolved=cst.definition_resolved
)
newCst.apply_mapping(mapping)
newCst.save()
position = position + 1
result.append(newCst)
self.item.save()
result.refresh_from_db()
return result
@transaction.atomic
def move_cst(self, listCst: list['Constituenta'], target: int):
def move_cst(self, listCst: list[Constituenta], target: int):
''' Move list of constituents to specific position '''
count_moved = 0
count_top = 0
@ -159,7 +182,7 @@ class RSForm:
self.item.save()
@transaction.atomic
def create_cst(self, data: dict, insert_after: Optional[str]=None) -> 'Constituenta':
def create_cst(self, data: dict, insert_after: Optional[str]=None) -> Constituenta:
''' Create new cst from data. '''
resolver = self.resolver()
cst = self._insert_new(data, insert_after)
@ -182,8 +205,8 @@ class RSForm:
@transaction.atomic
def substitute(
self,
original: 'Constituenta',
substitution: 'Constituenta',
original: Constituenta,
substitution: Constituenta,
transfer_term: bool
):
''' Execute constituenta substitution. '''
@ -296,6 +319,22 @@ class RSForm:
cst.order += shift
Constituenta.objects.bulk_update(update_list, ['order'])
def _get_last_position(self):
if self.constituents().exists():
return self.constituents().count()
else:
return 0
def _get_insert_position(self, position: int) -> int:
if position <= 0 and position != _INSERT_LAST:
raise ValidationError(msg.invalidPosition())
lastPosition = self._get_last_position()
if position == _INSERT_LAST:
position = lastPosition + 1
else:
position = max(1, min(position, lastPosition + 1))
return position
@transaction.atomic
def _reset_order(self):
order = 1
@ -305,12 +344,12 @@ class RSForm:
cst.save()
order += 1
def _insert_new(self, data: dict, insert_after: Optional[str]=None) -> 'Constituenta':
def _insert_new(self, data: dict, insert_after: Optional[str]=None) -> Constituenta:
if insert_after is not None:
cst_after = Constituenta.objects.get(pk=insert_after)
return self.insert_at(cst_after.order + 1, data['alias'], data['cst_type'])
return self.insert_new(data['alias'], data['cst_type'], cst_after.order + 1)
else:
return self.insert_last(data['alias'], data['cst_type'])
return self.insert_new(data['alias'], data['cst_type'])
def _term_graph(self) -> Graph:
result = Graph()

View File

@ -260,7 +260,7 @@ class CstRenameSerializer(serializers.Serializer):
})
if RSForm(schema).constituents().filter(alias=new_alias).exists():
raise serializers.ValidationError({
'alias': msg.renameTaken(new_alias)
'alias': msg.aliasTaken(new_alias)
})
return attrs

View File

@ -47,14 +47,14 @@ class TestRSFormViewset(APITestCase):
def test_contents(self):
schema = RSForm.create(title='Title1')
schema.insert_last(alias='X1', insert_type=CstType.BASE)
schema.insert_new(alias='X1', insert_type=CstType.BASE)
response = self.client.get(f'/api/rsforms/{schema.item.id}/contents')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_details(self):
schema = RSForm.create(title='Test', owner=self.user)
x1 = schema.insert_at(1, 'X1', CstType.BASE)
x2 = schema.insert_at(2, 'X2', CstType.BASE)
x1 = schema.insert_new('X1', CstType.BASE, 1)
x2 = schema.insert_new('X2', CstType.BASE, 2)
x1.term_raw = 'человек'
x1.term_resolved = 'человек'
x2.term_raw = '@{X1|plur}'
@ -78,7 +78,7 @@ class TestRSFormViewset(APITestCase):
def test_check(self):
schema = RSForm.create(title='Test')
schema.insert_at(1, 'X1', CstType.BASE)
schema.insert_new('X1', CstType.BASE, 1)
data = {'expression': 'X1=X1'}
response = self.client.post(
f'/api/rsforms/{schema.item.id}/check',
@ -99,7 +99,7 @@ class TestRSFormViewset(APITestCase):
def test_resolve(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_at(1, 'X1', CstType.BASE)
x1 = schema.insert_new('X1', CstType.BASE, 1)
x1.term_resolved = 'синий слон'
x1.save()
data = {'text': '@{1|редкий} @{X1|plur,datv}'}
@ -139,7 +139,7 @@ class TestRSFormViewset(APITestCase):
def test_export_trs(self):
schema = RSForm.create(title='Test')
schema.insert_at(1, 'X1', CstType.BASE)
schema.insert_new('X1', CstType.BASE, 1)
response = self.client.get(f'/api/rsforms/{schema.item.id}/export-trs')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename=Schema.trs')

View File

@ -170,17 +170,17 @@ class TestRSForm(TestCase):
def test_insert_at(self):
schema = RSForm.create(title='Test')
cst1 = schema.insert_at(1, 'X1', CstType.BASE)
cst1 = schema.insert_new('X1', CstType.BASE, 1)
self.assertEqual(cst1.order, 1)
self.assertEqual(cst1.schema, schema.item)
cst2 = schema.insert_at(1, 'X2', CstType.BASE)
cst2 = schema.insert_new('X2', CstType.BASE, 1)
cst1.refresh_from_db()
self.assertEqual(cst2.order, 1)
self.assertEqual(cst2.schema, schema.item)
self.assertEqual(cst1.order, 2)
cst3 = schema.insert_at(4, 'X3', CstType.BASE)
cst3 = schema.insert_new('X3', CstType.BASE, 4)
cst2.refresh_from_db()
cst1.refresh_from_db()
self.assertEqual(cst3.order, 3)
@ -188,7 +188,7 @@ class TestRSForm(TestCase):
self.assertEqual(cst2.order, 1)
self.assertEqual(cst1.order, 2)
cst4 = schema.insert_at(3, 'X4', CstType.BASE)
cst4 = schema.insert_new('X4', CstType.BASE, 3)
cst3.refresh_from_db()
cst2.refresh_from_db()
cst1.refresh_from_db()
@ -201,40 +201,40 @@ class TestRSForm(TestCase):
def test_insert_at_invalid_position(self):
schema = RSForm.create(title='Test')
with self.assertRaises(ValidationError):
schema.insert_at(0, 'X5', CstType.BASE)
schema.insert_new('X5', CstType.BASE, 0)
def test_insert_at_invalid_alias(self):
schema = RSForm.create(title='Test')
schema.insert_at(1, 'X1', CstType.BASE)
schema.insert_new('X1', CstType.BASE, 1)
with self.assertRaises(ValidationError):
schema.insert_at(2, 'X1', CstType.BASE)
schema.insert_new('X1', CstType.BASE, 2)
def test_insert_at_reorder(self):
schema = RSForm.create(title='Test')
schema.insert_at(1, 'X1', CstType.BASE)
d1 = schema.insert_at(2, 'D1', CstType.TERM)
d2 = schema.insert_at(1, 'D2', CstType.TERM)
schema.insert_new('X1', CstType.BASE, 1)
d1 = schema.insert_new('D1', CstType.TERM, 2)
d2 = schema.insert_new('D2', CstType.TERM, 1)
d1.refresh_from_db()
self.assertEqual(d1.order, 3)
self.assertEqual(d2.order, 1)
x2 = schema.insert_at(4, 'X2', CstType.BASE)
x2 = schema.insert_new('X2', CstType.BASE, 4)
self.assertEqual(x2.order, 4)
def test_insert_last(self):
schema = RSForm.create(title='Test')
cst1 = schema.insert_last('X1', CstType.BASE)
cst1 = schema.insert_new('X1', CstType.BASE)
self.assertEqual(cst1.order, 1)
self.assertEqual(cst1.schema, schema.item)
cst2 = schema.insert_last('X2', CstType.BASE)
cst2 = schema.insert_new('X2', CstType.BASE)
self.assertEqual(cst2.order, 2)
self.assertEqual(cst2.schema, schema.item)
self.assertEqual(cst1.order, 1)
def test_create_cst_resolve(self):
schema = RSForm.create(title='Test')
cst1 = schema.insert_last('X1', CstType.BASE)
cst1 = schema.insert_new('X1', CstType.BASE)
cst1.term_raw = '@{X2|datv}'
cst1.definition_raw = '@{X1|datv} @{X2|datv}'
cst1.save()
@ -250,11 +250,40 @@ class TestRSForm(TestCase):
self.assertEqual(cst2.term_resolved, 'слон')
self.assertEqual(cst2.definition_resolved, 'слонам слоны')
def test_insert_copy(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_new('X10', CstType.BASE)
s1 = schema.insert_new('S11', CstType.STRUCTURED)
x1.convention = 'Test'
s1.definition_formal = x1.alias
s1.definition_raw = '@{X10|plur}'
x1.save()
s1.save()
result = schema.insert_copy([s1, x1], 2)
self.assertEqual(len(result), 2)
s1.refresh_from_db()
self.assertEqual(s1.order, 4)
x2 = result[1]
self.assertEqual(x2.order, 3)
self.assertEqual(x2.alias, 'X11')
self.assertEqual(x2.cst_type, CstType.BASE)
self.assertEqual(x2.convention, x1.convention)
s2 = result[0]
self.assertEqual(s2.order, 2)
self.assertEqual(s2.alias, 'S12')
self.assertEqual(s2.cst_type, CstType.STRUCTURED)
self.assertEqual(s2.definition_formal, x2.alias)
self.assertEqual(s2.definition_raw, '@{X11|plur}')
def test_apply_mapping(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X11', CstType.BASE)
d1 = schema.insert_last('D1', CstType.TERM)
x1 = schema.insert_new('X1', CstType.BASE)
x2 = schema.insert_new('X11', CstType.BASE)
d1 = schema.insert_new('D1', CstType.TERM)
d1.definition_formal = 'X1 = X11 = X2'
d1.definition_raw = '@{X11|sing}'
d1.convention = 'X1'
@ -272,9 +301,9 @@ class TestRSForm(TestCase):
def test_substitute(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X2', CstType.BASE)
d1 = schema.insert_last('D1', CstType.TERM)
x1 = schema.insert_new('X1', CstType.BASE)
x2 = schema.insert_new('X2', CstType.BASE)
d1 = schema.insert_new('D1', CstType.TERM)
d1.definition_formal = x1.alias
d1.save()
x1.term_raw = 'Test'
@ -291,10 +320,10 @@ class TestRSForm(TestCase):
def test_move_cst(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X2', CstType.BASE)
d1 = schema.insert_last('D1', CstType.TERM)
d2 = schema.insert_last('D2', CstType.TERM)
x1 = schema.insert_new('X1', CstType.BASE)
x2 = schema.insert_new('X2', CstType.BASE)
d1 = schema.insert_new('D1', CstType.TERM)
d2 = schema.insert_new('D2', CstType.TERM)
schema.move_cst([x2, d2], 1)
x1.refresh_from_db()
x2.refresh_from_db()
@ -307,8 +336,8 @@ class TestRSForm(TestCase):
def test_move_cst_down(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X2', CstType.BASE)
x1 = schema.insert_new('X1', CstType.BASE)
x2 = schema.insert_new('X2', CstType.BASE)
schema.move_cst([x1], 2)
x1.refresh_from_db()
x2.refresh_from_db()
@ -317,9 +346,9 @@ class TestRSForm(TestCase):
def test_reset_aliases(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_last('X11', CstType.BASE)
x2 = schema.insert_last('X21', CstType.BASE)
d1 = schema.insert_last('D11', CstType.TERM)
x1 = schema.insert_new('X11', CstType.BASE)
x2 = schema.insert_new('X21', CstType.BASE)
d1 = schema.insert_new('D11', CstType.TERM)
x1.term_raw = 'человек'
x1.term_resolved = 'человек'
d1.convention = 'D11 - cool'