mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implement termform editor
This commit is contained in:
parent
f7a7a1b173
commit
83242dfb69
|
@ -12,9 +12,9 @@ from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from cctext import Resolver, Entity, extract_entities
|
from cctext import Resolver, Entity, extract_entities, split_grams, TermForm
|
||||||
from .graph import Graph
|
from .graph import Graph
|
||||||
from .utils import apply_mapping_pattern
|
from .utils import apply_pattern
|
||||||
|
|
||||||
|
|
||||||
_REF_ENTITY_PATTERN = re.compile(r'@{([^0-9\-].*?)\|.*?}')
|
_REF_ENTITY_PATTERN = re.compile(r'@{([^0-9\-].*?)\|.*?}')
|
||||||
|
@ -273,7 +273,14 @@ class RSForm:
|
||||||
''' Create resolver for text references based on schema terms. '''
|
''' Create resolver for text references based on schema terms. '''
|
||||||
result = Resolver({})
|
result = Resolver({})
|
||||||
for cst in self.constituents():
|
for cst in self.constituents():
|
||||||
entity = Entity(alias=cst.alias, nominal=cst.term_resolved, manual_forms=cst.term_forms)
|
entity = Entity(
|
||||||
|
alias=cst.alias,
|
||||||
|
nominal=cst.term_resolved,
|
||||||
|
manual_forms=[
|
||||||
|
TermForm(text=form['text'], grams=split_grams(form['tags']))
|
||||||
|
for form in cst.term_forms
|
||||||
|
]
|
||||||
|
)
|
||||||
result.context[cst.alias] = entity
|
result.context[cst.alias] = entity
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -314,7 +321,10 @@ class RSForm:
|
||||||
raise ValidationError('Invalid position: should be positive integer')
|
raise ValidationError('Invalid position: should be positive integer')
|
||||||
currentSize = self.constituents().count()
|
currentSize = self.constituents().count()
|
||||||
position = max(1, min(position, currentSize + 1))
|
position = max(1, min(position, currentSize + 1))
|
||||||
update_list = Constituenta.objects.only('id', 'order', 'schema').filter(schema=self.item, order__gte=position)
|
update_list = \
|
||||||
|
Constituenta.objects \
|
||||||
|
.only('id', 'order', 'schema') \
|
||||||
|
.filter(schema=self.item, order__gte=position)
|
||||||
for cst in update_list:
|
for cst in update_list:
|
||||||
cst.order += 1
|
cst.order += 1
|
||||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||||
|
@ -424,19 +434,19 @@ class RSForm:
|
||||||
if change_aliases and cst.alias in mapping:
|
if change_aliases and cst.alias in mapping:
|
||||||
modified = True
|
modified = True
|
||||||
cst.alias = mapping[cst.alias]
|
cst.alias = mapping[cst.alias]
|
||||||
expression = apply_mapping_pattern(cst.definition_formal, mapping, _GLOBAL_ID_PATTERN)
|
expression = apply_pattern(cst.definition_formal, mapping, _GLOBAL_ID_PATTERN)
|
||||||
if expression != cst.definition_formal:
|
if expression != cst.definition_formal:
|
||||||
modified = True
|
modified = True
|
||||||
cst.definition_formal = expression
|
cst.definition_formal = expression
|
||||||
convention = apply_mapping_pattern(cst.convention, mapping, _GLOBAL_ID_PATTERN)
|
convention = apply_pattern(cst.convention, mapping, _GLOBAL_ID_PATTERN)
|
||||||
if convention != cst.convention:
|
if convention != cst.convention:
|
||||||
modified = True
|
modified = True
|
||||||
cst.convention = convention
|
cst.convention = convention
|
||||||
term = apply_mapping_pattern(cst.term_raw, mapping, _REF_ENTITY_PATTERN)
|
term = apply_pattern(cst.term_raw, mapping, _REF_ENTITY_PATTERN)
|
||||||
if term != cst.term_raw:
|
if term != cst.term_raw:
|
||||||
modified = True
|
modified = True
|
||||||
cst.term_raw = term
|
cst.term_raw = term
|
||||||
definition = apply_mapping_pattern(cst.definition_raw, mapping, _REF_ENTITY_PATTERN)
|
definition = apply_pattern(cst.definition_raw, mapping, _REF_ENTITY_PATTERN)
|
||||||
if definition != cst.definition_raw:
|
if definition != cst.definition_raw:
|
||||||
modified = True
|
modified = True
|
||||||
cst.definition_raw = definition
|
cst.definition_raw = definition
|
||||||
|
|
|
@ -148,18 +148,19 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ('id', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
|
read_only_fields = ('id', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
|
||||||
|
|
||||||
def update(self, instance: Constituenta, validated_data) -> Constituenta:
|
def update(self, instance: Constituenta, validated_data) -> Constituenta:
|
||||||
|
data = validated_data # Note: create alias for better code readability
|
||||||
schema = RSForm(instance.schema)
|
schema = RSForm(instance.schema)
|
||||||
definition: Optional[str] = validated_data['definition_raw'] if 'definition_raw' in validated_data else None
|
definition: Optional[str] = data['definition_raw'] if 'definition_raw' in data else None
|
||||||
term: Optional[str] = validated_data['term_raw'] if 'term_raw' in validated_data else None
|
term: Optional[str] = data['term_raw'] if 'term_raw' in data else None
|
||||||
term_changed = False
|
term_changed = 'term_forms' in data
|
||||||
if definition is not None and definition != instance.definition_raw :
|
if definition is not None and definition != instance.definition_raw :
|
||||||
validated_data['definition_resolved'] = schema.resolver().resolve(definition)
|
data['definition_resolved'] = schema.resolver().resolve(definition)
|
||||||
if term is not None and term != instance.term_raw:
|
if term is not None and term != instance.term_raw:
|
||||||
validated_data['term_resolved'] = schema.resolver().resolve(term)
|
data['term_resolved'] = schema.resolver().resolve(term)
|
||||||
if validated_data['term_resolved'] != instance.term_resolved:
|
if data['term_resolved'] != instance.term_resolved and 'term_forms' not in data:
|
||||||
validated_data['term_forms'] = []
|
data['term_forms'] = []
|
||||||
term_changed = validated_data['term_resolved'] != instance.term_resolved
|
term_changed = data['term_resolved'] != instance.term_resolved
|
||||||
result: Constituenta = super().update(instance, validated_data)
|
result: Constituenta = super().update(instance, data)
|
||||||
if term_changed:
|
if term_changed:
|
||||||
schema.on_term_change([result.alias])
|
schema.on_term_change([result.alias])
|
||||||
result.refresh_from_db()
|
result.refresh_from_db()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from apps.rsform.utils import apply_mapping_pattern, fix_old_references
|
from apps.rsform.utils import apply_pattern, fix_old_references
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
|
@ -10,10 +10,10 @@ class TestUtils(unittest.TestCase):
|
||||||
def test_apply_mapping_patter(self):
|
def test_apply_mapping_patter(self):
|
||||||
mapping = {'X101': 'X20'}
|
mapping = {'X101': 'X20'}
|
||||||
pattern = re.compile(r'(X[0-9]+)')
|
pattern = re.compile(r'(X[0-9]+)')
|
||||||
self.assertEqual(apply_mapping_pattern('', mapping, pattern), '')
|
self.assertEqual(apply_pattern('', mapping, pattern), '')
|
||||||
self.assertEqual(apply_mapping_pattern('X20', mapping, pattern), 'X20')
|
self.assertEqual(apply_pattern('X20', mapping, pattern), 'X20')
|
||||||
self.assertEqual(apply_mapping_pattern('X101', mapping, pattern), 'X20')
|
self.assertEqual(apply_pattern('X101', mapping, pattern), 'X20')
|
||||||
self.assertEqual(apply_mapping_pattern('asdf X101 asdf', mapping, pattern), 'asdf X20 asdf')
|
self.assertEqual(apply_pattern('asdf X101 asdf', mapping, pattern), 'asdf X20 asdf')
|
||||||
|
|
||||||
def test_fix_old_references(self):
|
def test_fix_old_references(self):
|
||||||
self.assertEqual(fix_old_references(''), '')
|
self.assertEqual(fix_old_references(''), '')
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
''' Testing views '''
|
''' Testing views '''
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
@ -52,30 +51,43 @@ class TestConstituentaAPI(APITestCase):
|
||||||
self.assertEqual(response.data['convention'], self.cst1.convention)
|
self.assertEqual(response.data['convention'], self.cst1.convention)
|
||||||
|
|
||||||
def test_partial_update(self):
|
def test_partial_update(self):
|
||||||
data = json.dumps({'convention': 'tt'})
|
data = {'convention': 'tt'}
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst2.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst2.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst1.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst1.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst1.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst1.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.cst1.refresh_from_db()
|
self.cst1.refresh_from_db()
|
||||||
self.assertEqual(response.data['convention'], 'tt')
|
self.assertEqual(response.data['convention'], 'tt')
|
||||||
self.assertEqual(self.cst1.convention, 'tt')
|
self.assertEqual(self.cst1.convention, 'tt')
|
||||||
|
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst1.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst1.id}',
|
||||||
|
data=data,
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_update_resolved_norefs(self):
|
def test_update_resolved_norefs(self):
|
||||||
data = json.dumps({
|
data = {
|
||||||
'term_raw': 'New term',
|
'term_raw': 'New term',
|
||||||
'definition_raw': 'New def'
|
'definition_raw': 'New def'
|
||||||
})
|
}
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst3.id}', data, content_type='application/json')
|
response = self.client.patch(f'/api/constituents/{self.cst3.id}', data, format='json')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.cst3.refresh_from_db()
|
self.cst3.refresh_from_db()
|
||||||
self.assertEqual(response.data['term_resolved'], 'New term')
|
self.assertEqual(response.data['term_resolved'], 'New term')
|
||||||
|
@ -84,11 +96,14 @@ class TestConstituentaAPI(APITestCase):
|
||||||
self.assertEqual(self.cst3.definition_resolved, 'New def')
|
self.assertEqual(self.cst3.definition_resolved, 'New def')
|
||||||
|
|
||||||
def test_update_resolved_refs(self):
|
def test_update_resolved_refs(self):
|
||||||
data = json.dumps({
|
data = {
|
||||||
'term_raw': '@{X1|nomn,sing}',
|
'term_raw': '@{X1|nomn,sing}',
|
||||||
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
|
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
|
||||||
})
|
}
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst3.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst3.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.cst3.refresh_from_db()
|
self.cst3.refresh_from_db()
|
||||||
self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved)
|
self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved)
|
||||||
|
@ -97,8 +112,11 @@ class TestConstituentaAPI(APITestCase):
|
||||||
self.assertEqual(response.data['definition_resolved'], f'{self.cst1.term_resolved} form1')
|
self.assertEqual(response.data['definition_resolved'], f'{self.cst1.term_resolved} form1')
|
||||||
|
|
||||||
def test_readonly_cst_fields(self):
|
def test_readonly_cst_fields(self):
|
||||||
data = json.dumps({'alias': 'X33', 'order': 10})
|
data = {'alias': 'X33', 'order': 10}
|
||||||
response = self.client.patch(f'/api/constituents/{self.cst1.id}', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
f'/api/constituents/{self.cst1.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['alias'], 'X1')
|
self.assertEqual(response.data['alias'], 'X1')
|
||||||
self.assertEqual(response.data['alias'], self.cst1.alias)
|
self.assertEqual(response.data['alias'], self.cst1.alias)
|
||||||
|
@ -132,29 +150,33 @@ class TestLibraryViewset(APITestCase):
|
||||||
|
|
||||||
def test_create_anonymous(self):
|
def test_create_anonymous(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
data = json.dumps({'title': 'Title'})
|
data = {'title': 'Title'}
|
||||||
response = self.client.post('/api/library', data=data, content_type='application/json')
|
response = self.client.post('/api/library', data=data, format='json')
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_create_populate_user(self):
|
def test_create_populate_user(self):
|
||||||
data = json.dumps({'title': 'Title'})
|
data = {'title': 'Title'}
|
||||||
response = self.client.post('/api/library', data=data, content_type='application/json')
|
response = self.client.post('/api/library', data=data, format='json')
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['title'], 'Title')
|
self.assertEqual(response.data['title'], 'Title')
|
||||||
self.assertEqual(response.data['owner'], self.user.id)
|
self.assertEqual(response.data['owner'], self.user.id)
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
data = json.dumps({'id': self.owned.id, 'title': 'New title'})
|
data = {'id': self.owned.id, 'title': 'New title'}
|
||||||
response = self.client.patch(f'/api/library/{self.owned.id}',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/library/{self.owned.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['title'], 'New title')
|
self.assertEqual(response.data['title'], 'New title')
|
||||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||||
|
|
||||||
def test_update_unowned(self):
|
def test_update_unowned(self):
|
||||||
data = json.dumps({'id': self.unowned.id, 'title': 'New title'})
|
data = {'id': self.unowned.id, 'title': 'New title'}
|
||||||
response = self.client.patch(f'/api/library/{self.unowned.id}',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/library/{self.unowned.id}',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_destroy(self):
|
def test_destroy(self):
|
||||||
|
@ -304,8 +326,11 @@ class TestRSFormViewset(APITestCase):
|
||||||
def test_check(self):
|
def test_check(self):
|
||||||
schema = RSForm.create(title='Test')
|
schema = RSForm.create(title='Test')
|
||||||
schema.insert_at(1, 'X1', CstType.BASE)
|
schema.insert_at(1, 'X1', CstType.BASE)
|
||||||
data = json.dumps({'expression': 'X1=X1'})
|
data = {'expression': 'X1=X1'}
|
||||||
response = self.client.post(f'/api/rsforms/{schema.item.id}/check', data=data, content_type='application/json')
|
response = self.client.post(
|
||||||
|
f'/api/rsforms/{schema.item.id}/check',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['parseResult'], True)
|
self.assertEqual(response.data['parseResult'], True)
|
||||||
self.assertEqual(response.data['syntax'], Syntax.MATH)
|
self.assertEqual(response.data['syntax'], Syntax.MATH)
|
||||||
|
@ -318,8 +343,11 @@ class TestRSFormViewset(APITestCase):
|
||||||
x1 = schema.insert_at(1, 'X1', CstType.BASE)
|
x1 = schema.insert_at(1, 'X1', CstType.BASE)
|
||||||
x1.term_resolved = 'синий слон'
|
x1.term_resolved = 'синий слон'
|
||||||
x1.save()
|
x1.save()
|
||||||
data = json.dumps({'text': '@{1|редкий} @{X1|plur,datv}'})
|
data = {'text': '@{1|редкий} @{X1|plur,datv}'}
|
||||||
response = self.client.post(f'/api/rsforms/{schema.item.id}/resolve', data=data, content_type='application/json')
|
response = self.client.post(
|
||||||
|
f'/api/rsforms/{schema.item.id}/resolve',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}')
|
self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}')
|
||||||
self.assertEqual(response.data['output'], 'редким синим слонам')
|
self.assertEqual(response.data['output'], 'редким синим слонам')
|
||||||
|
@ -362,24 +390,30 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertIn('document.json', zipped_file.namelist())
|
self.assertIn('document.json', zipped_file.namelist())
|
||||||
|
|
||||||
def test_create_constituenta(self):
|
def test_create_constituenta(self):
|
||||||
data = json.dumps({'alias': 'X3', 'cst_type': 'basic'})
|
data = {'alias': 'X3', 'cst_type': 'basic'}
|
||||||
response = self.client.post(f'/api/rsforms/{self.unowned.item.id}/cst-create',
|
response = self.client.post(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{self.unowned.item.id}/cst-create',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
item = self.owned.item
|
item = self.owned.item
|
||||||
Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
||||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create',
|
response = self.client.post(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-create',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||||
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||||
self.assertEqual(x3.order, 3)
|
self.assertEqual(x3.order, 3)
|
||||||
|
|
||||||
data = json.dumps({'alias': 'X4', 'cst_type': 'basic', 'insert_after': x2.id})
|
data = {'alias': 'X4', 'cst_type': 'basic', 'insert_after': x2.id}
|
||||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create',
|
response = self.client.post(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-create',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['new_cst']['alias'], 'X4')
|
self.assertEqual(response.data['new_cst']['alias'], 'X4')
|
||||||
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||||
|
@ -389,30 +423,39 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.cst1 = Constituenta.objects.create(
|
self.cst1 = Constituenta.objects.create(
|
||||||
alias='X1', schema=self.owned.item, order=1, convention='Test',
|
alias='X1', schema=self.owned.item, order=1, convention='Test',
|
||||||
term_raw='Test1', term_resolved='Test1',
|
term_raw='Test1', term_resolved='Test1',
|
||||||
term_forms=[{'text':'form1', 'tags':'sing,datv'}])
|
term_forms=[{'text':'form1', 'tags':'sing,datv'}]
|
||||||
|
)
|
||||||
self.cst2 = Constituenta.objects.create(
|
self.cst2 = Constituenta.objects.create(
|
||||||
alias='X2', schema=self.unowned.item, order=1, convention='Test1',
|
alias='X2', schema=self.unowned.item, order=1, convention='Test1',
|
||||||
term_raw='Test2', term_resolved='Test2')
|
term_raw='Test2', term_resolved='Test2'
|
||||||
|
)
|
||||||
self.cst3 = Constituenta.objects.create(
|
self.cst3 = Constituenta.objects.create(
|
||||||
alias='X3', schema=self.owned.item, order=2,
|
alias='X3', schema=self.owned.item, order=2,
|
||||||
term_raw='Test3', term_resolved='Test3',
|
term_raw='Test3', term_resolved='Test3',
|
||||||
definition_raw='Test1', definition_resolved='Test2')
|
definition_raw='Test1', definition_resolved='Test2'
|
||||||
|
)
|
||||||
|
|
||||||
data = json.dumps({'alias': 'D2', 'cst_type': 'term', 'id': self.cst2.pk})
|
data = {'alias': 'D2', 'cst_type': 'term', 'id': self.cst2.pk}
|
||||||
response = self.client.patch(f'/api/rsforms/{self.unowned.item.id}/cst-rename',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{self.unowned.item.id}/cst-rename',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = self.client.patch(f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
data = json.dumps({'alias': self.cst1.alias, 'cst_type': 'term', 'id': self.cst1.pk})
|
data = {'alias': self.cst1.alias, 'cst_type': 'term', 'id': self.cst1.pk}
|
||||||
response = self.client.patch(f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
data = json.dumps({'alias': 'D2', 'cst_type': 'term', 'id': self.cst1.pk})
|
data = {'alias': 'D2', 'cst_type': 'term', 'id': self.cst1.pk}
|
||||||
item = self.owned.item
|
item = self.owned.item
|
||||||
d1 = Constituenta.objects.create(schema=item, alias='D1', cst_type='term', order=4)
|
d1 = Constituenta.objects.create(schema=item, alias='D1', cst_type='term', order=4)
|
||||||
d1.term_raw = '@{X1|plur}'
|
d1.term_raw = '@{X1|plur}'
|
||||||
|
@ -423,8 +466,10 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(self.cst1.order, 1)
|
self.assertEqual(self.cst1.order, 1)
|
||||||
self.assertEqual(self.cst1.alias, 'X1')
|
self.assertEqual(self.cst1.alias, 'X1')
|
||||||
self.assertEqual(self.cst1.cst_type, CstType.BASE)
|
self.assertEqual(self.cst1.cst_type, CstType.BASE)
|
||||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-rename',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-rename',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['new_cst']['alias'], 'D2')
|
self.assertEqual(response.data['new_cst']['alias'], 'D2')
|
||||||
self.assertEqual(response.data['new_cst']['cst_type'], 'term')
|
self.assertEqual(response.data['new_cst']['cst_type'], 'term')
|
||||||
|
@ -438,17 +483,19 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(self.cst1.cst_type, CstType.TERM)
|
self.assertEqual(self.cst1.cst_type, CstType.TERM)
|
||||||
|
|
||||||
def test_create_constituenta_data(self):
|
def test_create_constituenta_data(self):
|
||||||
data = json.dumps({
|
data = {
|
||||||
'alias': 'X3',
|
'alias': 'X3',
|
||||||
'cst_type': 'basic',
|
'cst_type': 'basic',
|
||||||
'convention': '1',
|
'convention': '1',
|
||||||
'term_raw': '2',
|
'term_raw': '2',
|
||||||
'definition_formal': '3',
|
'definition_formal': '3',
|
||||||
'definition_raw': '4'
|
'definition_raw': '4'
|
||||||
})
|
}
|
||||||
item = self.owned.item
|
item = self.owned.item
|
||||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create',
|
response = self.client.post(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-create',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||||
self.assertEqual(response.data['new_cst']['cst_type'], 'basic')
|
self.assertEqual(response.data['new_cst']['cst_type'], 'basic')
|
||||||
|
@ -461,16 +508,20 @@ class TestRSFormViewset(APITestCase):
|
||||||
|
|
||||||
def test_delete_constituenta(self):
|
def test_delete_constituenta(self):
|
||||||
schema = self.owned
|
schema = self.owned
|
||||||
data = json.dumps({'items': [1337]})
|
data = {'items': [1337]}
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=schema.item, alias='X2', cst_type='basic', order=2)
|
x2 = Constituenta.objects.create(schema=schema.item, alias='X2', cst_type='basic', order=2)
|
||||||
data = json.dumps({'items': [x1.id]})
|
data = {'items': [x1.id]}
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
x2.refresh_from_db()
|
x2.refresh_from_db()
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
self.assertEqual(response.status_code, 202)
|
self.assertEqual(response.status_code, 202)
|
||||||
|
@ -480,23 +531,29 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||||
data = json.dumps({'items': [x3.id]})
|
data = {'items': [x3.id]}
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_move_constituenta(self):
|
def test_move_constituenta(self):
|
||||||
item = self.owned.item
|
item = self.owned.item
|
||||||
data = json.dumps({'items': [1337], 'move_to': 1})
|
data = {'items': [1337], 'move_to': 1}
|
||||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
||||||
data = json.dumps({'items': [x2.id], 'move_to': 1})
|
data = {'items': [x2.id], 'move_to': 1}
|
||||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
x1.refresh_from_db()
|
x1.refresh_from_db()
|
||||||
x2.refresh_from_db()
|
x2.refresh_from_db()
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -505,9 +562,11 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||||
data = json.dumps({'items': [x3.id], 'move_to': 1})
|
data = {'items': [x3.id], 'move_to': 1}
|
||||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
response = self.client.patch(
|
||||||
data=data, content_type='application/json')
|
f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_reset_aliases(self):
|
def test_reset_aliases(self):
|
||||||
|
@ -542,7 +601,10 @@ class TestRSFormViewset(APITestCase):
|
||||||
work_dir = os.path.dirname(os.path.abspath(__file__))
|
work_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
|
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
|
||||||
data = {'file': file, 'load_metadata': False}
|
data = {'file': file, 'load_metadata': False}
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/load-trs', data=data, format='multipart')
|
response = self.client.patch(
|
||||||
|
f'/api/rsforms/{schema.item.id}/load-trs',
|
||||||
|
data=data, format='multipart'
|
||||||
|
)
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(schema.item.title, 'Testt11')
|
self.assertEqual(schema.item.title, 'Testt11')
|
||||||
|
@ -563,8 +625,11 @@ class TestRSFormViewset(APITestCase):
|
||||||
x1.save()
|
x1.save()
|
||||||
d1.save()
|
d1.save()
|
||||||
|
|
||||||
data = json.dumps({'title': 'Title'})
|
data = {'title': 'Title'}
|
||||||
response = self.client.post(f'/api/library/{item.id}/clone', data=data, content_type='application/json')
|
response = self.client.post(
|
||||||
|
f'/api/library/{item.id}/clone',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['title'], 'Title')
|
self.assertEqual(response.data['title'], 'Title')
|
||||||
|
@ -586,7 +651,10 @@ class TestRSLanguageViews(APITestCase):
|
||||||
work_dir = os.path.dirname(os.path.abspath(__file__))
|
work_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
|
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
|
||||||
data = {'file': file, 'title': 'Test123', 'comment': '123', 'alias': 'ks1'}
|
data = {'file': file, 'title': 'Test123', 'comment': '123', 'alias': 'ks1'}
|
||||||
response = self.client.post('/api/rsforms/create-detailed', data=data, format='multipart')
|
response = self.client.post(
|
||||||
|
'/api/rsforms/create-detailed',
|
||||||
|
data=data, format='multipart'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['owner'], self.user.pk)
|
self.assertEqual(response.data['owner'], self.user.pk)
|
||||||
self.assertEqual(response.data['title'], 'Test123')
|
self.assertEqual(response.data['title'], 'Test123')
|
||||||
|
@ -595,7 +663,10 @@ class TestRSLanguageViews(APITestCase):
|
||||||
|
|
||||||
def test_create_rsform_fallback(self):
|
def test_create_rsform_fallback(self):
|
||||||
data = {'title': 'Test123', 'comment': '123', 'alias': 'ks1'}
|
data = {'title': 'Test123', 'comment': '123', 'alias': 'ks1'}
|
||||||
response = self.client.post('/api/rsforms/create-detailed', data=data)
|
response = self.client.post(
|
||||||
|
'/api/rsforms/create-detailed',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(response.data['owner'], self.user.pk)
|
self.assertEqual(response.data['owner'], self.user.pk)
|
||||||
self.assertEqual(response.data['title'], 'Test123')
|
self.assertEqual(response.data['title'], 'Test123')
|
||||||
|
@ -604,35 +675,50 @@ class TestRSLanguageViews(APITestCase):
|
||||||
|
|
||||||
def test_convert_to_ascii(self):
|
def test_convert_to_ascii(self):
|
||||||
data = {'expression': '1=1'}
|
data = {'expression': '1=1'}
|
||||||
request = self.factory.post('/api/rslang/to-ascii', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/to-ascii',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = convert_to_ascii(request)
|
response = convert_to_ascii(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['result'], r'1 \eq 1')
|
self.assertEqual(response.data['result'], r'1 \eq 1')
|
||||||
|
|
||||||
def test_convert_to_ascii_missing_data(self):
|
def test_convert_to_ascii_missing_data(self):
|
||||||
data = {'data': '1=1'}
|
data = {'data': '1=1'}
|
||||||
request = self.factory.post('/api/rslang/to-ascii', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/to-ascii',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = convert_to_ascii(request)
|
response = convert_to_ascii(request)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
||||||
|
|
||||||
def test_convert_to_math(self):
|
def test_convert_to_math(self):
|
||||||
data = {'expression': r'1 \eq 1'}
|
data = {'expression': r'1 \eq 1'}
|
||||||
request = self.factory.post('/api/rslang/to-math', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/to-math',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = convert_to_math(request)
|
response = convert_to_math(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['result'], r'1=1')
|
self.assertEqual(response.data['result'], r'1=1')
|
||||||
|
|
||||||
def test_convert_to_math_missing_data(self):
|
def test_convert_to_math_missing_data(self):
|
||||||
data = {'data': r'1 \eq 1'}
|
data = {'data': r'1 \eq 1'}
|
||||||
request = self.factory.post('/api/rslang/to-math', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/to-math',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = convert_to_math(request)
|
response = convert_to_math(request)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
||||||
|
|
||||||
def test_parse_expression(self):
|
def test_parse_expression(self):
|
||||||
data = {'expression': r'1=1'}
|
data = {'expression': r'1=1'}
|
||||||
request = self.factory.post('/api/rslang/parse-expression', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/parse-expression',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = parse_expression(request)
|
response = parse_expression(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['parseResult'], True)
|
self.assertEqual(response.data['parseResult'], True)
|
||||||
|
@ -641,7 +727,10 @@ class TestRSLanguageViews(APITestCase):
|
||||||
|
|
||||||
def test_parse_expression_missing_data(self):
|
def test_parse_expression_missing_data(self):
|
||||||
data = {'data': r'1=1'}
|
data = {'data': r'1=1'}
|
||||||
request = self.factory.post('/api/rslang/parse-expression', data)
|
request = self.factory.post(
|
||||||
|
'/api/rslang/parse-expression',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = parse_expression(request)
|
response = parse_expression(request)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
||||||
|
@ -657,21 +746,30 @@ class TestNaturalLanguageViews(APITestCase):
|
||||||
|
|
||||||
def test_parse_text(self):
|
def test_parse_text(self):
|
||||||
data = {'text': 'синим слонам'}
|
data = {'text': 'синим слонам'}
|
||||||
request = self.factory.post('/api/cctext/parse', data)
|
request = self.factory.post(
|
||||||
|
'/api/cctext/parse',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = parse_text(request)
|
response = parse_text(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self._assert_tags(response.data['result'], 'datv,NOUN,plur,anim,masc')
|
self._assert_tags(response.data['result'], 'datv,NOUN,plur,anim,masc')
|
||||||
|
|
||||||
def test_inflect(self):
|
def test_inflect(self):
|
||||||
data = {'text': 'синий слон', 'grams': 'plur,datv'}
|
data = {'text': 'синий слон', 'grams': 'plur,datv'}
|
||||||
request = self.factory.post('/api/cctext/inflect', data)
|
request = self.factory.post(
|
||||||
|
'/api/cctext/inflect',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = inflect(request)
|
response = inflect(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['result'], 'синим слонам')
|
self.assertEqual(response.data['result'], 'синим слонам')
|
||||||
|
|
||||||
def test_generate_lexeme(self):
|
def test_generate_lexeme(self):
|
||||||
data = {'text': 'синий слон'}
|
data = {'text': 'синий слон'}
|
||||||
request = self.factory.post('/api/cctext/generate-lexeme', data)
|
request = self.factory.post(
|
||||||
|
'/api/cctext/generate-lexeme',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
response = generate_lexeme(request)
|
response = generate_lexeme(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response.data['items']), 12)
|
self.assertEqual(len(response.data['items']), 12)
|
||||||
|
|
|
@ -50,7 +50,7 @@ def write_trs(json_data: dict) -> bytes:
|
||||||
archive.writestr('document.json', data=data)
|
archive.writestr('document.json', data=data)
|
||||||
return content.getvalue()
|
return content.getvalue()
|
||||||
|
|
||||||
def apply_mapping_pattern(text: str, mapping: dict[str, str], pattern: re.Pattern[str]) -> str:
|
def apply_pattern(text: str, mapping: dict[str, str], pattern: re.Pattern[str]) -> str:
|
||||||
''' Apply mapping to matching in regular expression patter subgroup 1. '''
|
''' Apply mapping to matching in regular expression patter subgroup 1. '''
|
||||||
if text == '' or pattern == '':
|
if text == '' or pattern == '':
|
||||||
return text
|
return text
|
||||||
|
|
|
@ -202,7 +202,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer = s.CstCreateSerializer(data=request.data)
|
serializer = s.CstCreateSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
new_cst = schema.create_cst(data, data['insert_after'] if 'insert_after' in data else None)
|
new_cst = schema.create_cst(
|
||||||
|
data=data,
|
||||||
|
insert_after=data['insert_after'] if 'insert_after' in data else None
|
||||||
|
)
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
response = Response(
|
response = Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
|
@ -251,7 +254,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
def cst_multidelete(self, request, pk):
|
def cst_multidelete(self, request, pk):
|
||||||
''' Endpoint: Delete multiple constituents. '''
|
''' Endpoint: Delete multiple constituents. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_schema()
|
||||||
serializer = s.CstListSerializer(data=request.data, context={'schema': schema})
|
serializer = s.CstListSerializer(
|
||||||
|
data=request.data,
|
||||||
|
context={'schema': schema}
|
||||||
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.delete_cst(serializer.validated_data['constituents'])
|
schema.delete_cst(serializer.validated_data['constituents'])
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
|
@ -270,9 +276,15 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
def cst_moveto(self, request, pk):
|
def cst_moveto(self, request, pk):
|
||||||
''' Endpoint: Move multiple constituents. '''
|
''' Endpoint: Move multiple constituents. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_schema()
|
||||||
serializer = s.CstMoveSerializer(data=request.data, context={'schema': schema})
|
serializer = s.CstMoveSerializer(
|
||||||
|
data=request.data,
|
||||||
|
context={'schema': schema}
|
||||||
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
|
schema.move_cst(
|
||||||
|
listCst=serializer.validated_data['constituents'],
|
||||||
|
target=serializer.validated_data['move_to']
|
||||||
|
)
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -311,7 +323,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data = utils.read_trs(request.FILES['file'].file)
|
data = utils.read_trs(request.FILES['file'].file)
|
||||||
data['id'] = schema.item.pk
|
data['id'] = schema.item.pk
|
||||||
|
|
||||||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
|
serializer = s.RSFormTRSSerializer(
|
||||||
|
data=data,
|
||||||
|
context={'load_meta': load_metadata}
|
||||||
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
schema = serializer.save()
|
||||||
return Response(
|
return Response(
|
||||||
|
@ -427,7 +442,10 @@ class TrsImportView(views.APIView):
|
||||||
if owner.is_anonymous:
|
if owner.is_anonymous:
|
||||||
owner = None
|
owner = None
|
||||||
_prepare_rsform_data(data, request, owner)
|
_prepare_rsform_data(data, request, owner)
|
||||||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
serializer = s.RSFormTRSSerializer(
|
||||||
|
data=data,
|
||||||
|
context={'load_meta': True}
|
||||||
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
schema = serializer.save()
|
||||||
result = s.LibraryItemSerializer(schema.item)
|
result = s.LibraryItemSerializer(schema.item)
|
||||||
|
@ -575,7 +593,7 @@ def inflect(request):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='basic set of wordforms',
|
summary='all wordforms for current lexeme',
|
||||||
tags=['NaturalLanguage'],
|
tags=['NaturalLanguage'],
|
||||||
request=s.TextSerializer,
|
request=s.TextSerializer,
|
||||||
responses={200: s.MultiFormSerializer},
|
responses={200: s.MultiFormSerializer},
|
||||||
|
@ -583,7 +601,7 @@ def inflect(request):
|
||||||
)
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def generate_lexeme(request):
|
def generate_lexeme(request):
|
||||||
''' Endpoint: Generate basic set of wordforms. '''
|
''' Endpoint: Generate complete set of wordforms for lexeme. '''
|
||||||
serializer = s.TextSerializer(data=request.data)
|
serializer = s.TextSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
nominal = serializer.validated_data['text']
|
nominal = serializer.validated_data['text']
|
||||||
|
@ -595,7 +613,7 @@ def generate_lexeme(request):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='get all language parse variants',
|
summary='get likely parse grammemes',
|
||||||
tags=['NaturalLanguage'],
|
tags=['NaturalLanguage'],
|
||||||
request=s.TextSerializer,
|
request=s.TextSerializer,
|
||||||
responses={200: s.ResultTextResponse},
|
responses={200: s.ResultTextResponse},
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
''' Testing views '''
|
''' Testing views '''
|
||||||
import json
|
|
||||||
from rest_framework.test import APITestCase, APIClient
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType
|
from apps.rsform.models import LibraryItem, LibraryItemType
|
||||||
|
|
||||||
|
|
||||||
# TODO: test ACTIVE_USERS
|
|
||||||
class TestUserAPIViews(APITestCase):
|
class TestUserAPIViews(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.username = 'UserTest'
|
self.username = 'UserTest'
|
||||||
|
@ -18,8 +16,11 @@ class TestUserAPIViews(APITestCase):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
|
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
data = json.dumps({'username': self.username, 'password': self.password})
|
data = {'username': self.username, 'password': self.password}
|
||||||
response = self.client.post('/users/api/login', data=data, content_type='application/json')
|
response = self.client.post(
|
||||||
|
'/users/api/login',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 202)
|
self.assertEqual(response.status_code, 202)
|
||||||
|
|
||||||
def test_logout(self):
|
def test_logout(self):
|
||||||
|
@ -81,12 +82,15 @@ class TestUserUserProfileAPIView(APITestCase):
|
||||||
|
|
||||||
def test_patch_profile(self):
|
def test_patch_profile(self):
|
||||||
self.client.force_login(user=self.user)
|
self.client.force_login(user=self.user)
|
||||||
data = json.dumps({
|
data = {
|
||||||
'email': '123@mail.ru',
|
'email': '123@mail.ru',
|
||||||
'first_name': 'firstName',
|
'first_name': 'firstName',
|
||||||
'last_name': 'lastName',
|
'last_name': 'lastName',
|
||||||
})
|
}
|
||||||
response = self.client.patch('/users/api/profile', data=data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
'/users/api/profile',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['email'], '123@mail.ru')
|
self.assertEqual(response.data['email'], '123@mail.ru')
|
||||||
self.assertEqual(response.data['first_name'], 'firstName')
|
self.assertEqual(response.data['first_name'], 'firstName')
|
||||||
|
@ -94,31 +98,52 @@ class TestUserUserProfileAPIView(APITestCase):
|
||||||
|
|
||||||
def test_edit_profile(self):
|
def test_edit_profile(self):
|
||||||
newmail = 'newmail@gmail.com'
|
newmail = 'newmail@gmail.com'
|
||||||
data = json.dumps({'email': newmail})
|
data = {'email': newmail}
|
||||||
response = self.client.patch('/users/api/profile', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
'/users/api/profile',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
self.client.force_login(user=self.user)
|
self.client.force_login(user=self.user)
|
||||||
response = self.client.patch('/users/api/profile', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
'/users/api/profile',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data['username'], self.username)
|
self.assertEqual(response.data['username'], self.username)
|
||||||
self.assertEqual(response.data['email'], newmail)
|
self.assertEqual(response.data['email'], newmail)
|
||||||
|
|
||||||
def test_change_password(self):
|
def test_change_password(self):
|
||||||
newpassword = 'pw2'
|
newpassword = 'pw2'
|
||||||
data = json.dumps({'old_password': self.password, 'new_password': newpassword})
|
data = {
|
||||||
response = self.client.patch('/users/api/change-password', data, content_type='application/json')
|
'old_password': self.password,
|
||||||
|
'new_password': newpassword
|
||||||
|
}
|
||||||
|
response = self.client.patch(
|
||||||
|
'/users/api/change-password',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
self.assertFalse(self.client.login(username=self.user.username, password=newpassword))
|
self.assertFalse(self.client.login(username=self.user.username, password=newpassword))
|
||||||
self.assertTrue(self.client.login(username=self.user.username, password=self.password))
|
self.assertTrue(self.client.login(username=self.user.username, password=self.password))
|
||||||
|
|
||||||
invalid = json.dumps({'old_password': 'invalid', 'new_password': newpassword})
|
invalid = {
|
||||||
response = self.client.patch('/users/api/change-password', invalid, content_type='application/json')
|
'old_password': 'invalid',
|
||||||
|
'new_password': newpassword
|
||||||
|
}
|
||||||
|
response = self.client.patch(
|
||||||
|
'/users/api/change-password',
|
||||||
|
data=invalid, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
oldHash = self.user.password
|
oldHash = self.user.password
|
||||||
self.client.force_login(user=self.user)
|
self.client.force_login(user=self.user)
|
||||||
response = self.client.patch('/users/api/change-password', data, content_type='application/json')
|
response = self.client.patch(
|
||||||
|
'/users/api/change-password',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
self.assertNotEqual(self.user.password, oldHash)
|
self.assertNotEqual(self.user.password, oldHash)
|
||||||
|
@ -131,15 +156,18 @@ class TestSignupAPIView(APITestCase):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
|
|
||||||
def test_signup(self):
|
def test_signup(self):
|
||||||
data = json.dumps({
|
data = {
|
||||||
'username': 'TestUser',
|
'username': 'TestUser',
|
||||||
'email': 'email@mail.ru',
|
'email': 'email@mail.ru',
|
||||||
'password': 'Test@@123',
|
'password': 'Test@@123',
|
||||||
'password2': 'Test@@123',
|
'password2': 'Test@@123',
|
||||||
'first_name': 'firstName',
|
'first_name': 'firstName',
|
||||||
'last_name': 'lastName',
|
'last_name': 'lastName',
|
||||||
})
|
}
|
||||||
response = self.client.post('/users/api/signup', data, content_type='application/json')
|
response = self.client.post(
|
||||||
|
'/users/api/signup',
|
||||||
|
data=data, format='json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertTrue('id' in response.data)
|
self.assertTrue('id' in response.data)
|
||||||
self.assertEqual(response.data['username'], 'TestUser')
|
self.assertEqual(response.data['username'], 'TestUser')
|
||||||
|
|
|
@ -1,24 +1,40 @@
|
||||||
''' Term context for reference resolution. '''
|
''' Term context for reference resolution. '''
|
||||||
from typing import Iterable, Dict, Optional, TypedDict
|
from typing import Iterable, Dict, Optional, TypedDict
|
||||||
|
|
||||||
from .conceptapi import inflect
|
from .ruparser import PhraseParser
|
||||||
|
from .rumodel import WordTag
|
||||||
|
|
||||||
|
|
||||||
|
parser = PhraseParser()
|
||||||
|
|
||||||
|
|
||||||
class TermForm(TypedDict):
|
class TermForm(TypedDict):
|
||||||
''' Term in a specific form. '''
|
''' Represents term in a specific form. '''
|
||||||
text: str
|
text: str
|
||||||
tags: str
|
grams: Iterable[str]
|
||||||
|
|
||||||
|
|
||||||
def _search_form(query: str, data: Iterable[TermForm]) -> Optional[str]:
|
def _match_grams(query: Iterable[str], test: Iterable[str]) -> bool:
|
||||||
for tf in data:
|
''' Check if grams from test fit query. '''
|
||||||
if tf['tags'] == query:
|
for gram in test:
|
||||||
return tf['text']
|
if not gram in query:
|
||||||
|
if not gram in WordTag.PARTS_OF_SPEECH:
|
||||||
|
return False
|
||||||
|
for pos in WordTag.PARTS_OF_SPEECH:
|
||||||
|
if pos in query:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _search_form(query: Iterable[str], data: Iterable[TermForm]) -> Optional[str]:
|
||||||
|
for form in data:
|
||||||
|
if _match_grams(query, form['grams']):
|
||||||
|
return form['text']
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Entity:
|
class Entity:
|
||||||
''' Text entity. '''
|
''' Represents text entity. '''
|
||||||
def __init__(self, alias: str, nominal: str, manual_forms: Optional[Iterable[TermForm]]=None):
|
def __init__(self, alias: str, nominal: str, manual_forms: Optional[Iterable[TermForm]]=None):
|
||||||
if manual_forms is None:
|
if manual_forms is None:
|
||||||
self.manual = []
|
self.manual = []
|
||||||
|
@ -41,20 +57,28 @@ class Entity:
|
||||||
self.manual = []
|
self.manual = []
|
||||||
self._cached = []
|
self._cached = []
|
||||||
|
|
||||||
def get_form(self, form: str) -> str:
|
def get_form(self, grams: Iterable[str]) -> str:
|
||||||
''' Get specific term form. '''
|
''' Get specific term form. '''
|
||||||
if form == '':
|
if all(False for _ in grams):
|
||||||
return self._nominal
|
return self._nominal
|
||||||
text = _search_form(form, self.manual)
|
text = _search_form(grams, self.manual)
|
||||||
if text is None:
|
if text is not None:
|
||||||
text = _search_form(form, self._cached)
|
return text
|
||||||
if text is None:
|
text = _search_form(grams, self._cached)
|
||||||
try:
|
if text is not None:
|
||||||
text = inflect(self._nominal, form)
|
return text
|
||||||
except ValueError as error:
|
|
||||||
text = f'!{error}!'.replace('Unknown grammeme', 'Неизвестная граммема')
|
model = parser.parse(self._nominal)
|
||||||
self._cached.append({'text': text, 'tags': form})
|
if model is None:
|
||||||
|
text = self._nominal
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
text = model.inflect(grams)
|
||||||
|
except ValueError as error:
|
||||||
|
text = f'!{error}!'.replace('Unknown grammeme', 'Неизвестная граммема')
|
||||||
|
self._cached.append({'text': text, 'grams': grams})
|
||||||
return text
|
return text
|
||||||
|
|
||||||
# Term context for resolving entity references.
|
|
||||||
|
# Represents term context for resolving entity references.
|
||||||
TermContext = Dict[str, Entity]
|
TermContext = Dict[str, Entity]
|
||||||
|
|
|
@ -3,6 +3,8 @@ import re
|
||||||
from typing import cast, Optional
|
from typing import cast, Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .rumodel import split_grams
|
||||||
|
|
||||||
from .conceptapi import inflect_dependant
|
from .conceptapi import inflect_dependant
|
||||||
from .context import TermContext
|
from .context import TermContext
|
||||||
from .reference import EntityReference, SyntacticReference, parse_reference, Reference
|
from .reference import EntityReference, SyntacticReference, parse_reference, Reference
|
||||||
|
@ -24,7 +26,8 @@ def resolve_entity(ref: EntityReference, context: TermContext) -> str:
|
||||||
alias = ref.entity
|
alias = ref.entity
|
||||||
if alias not in context:
|
if alias not in context:
|
||||||
return f'!Неизвестная сущность: {alias}!'
|
return f'!Неизвестная сущность: {alias}!'
|
||||||
resolved = context[alias].get_form(ref.form)
|
grams = split_grams(ref.form)
|
||||||
|
resolved = context[alias].get_form(grams)
|
||||||
if resolved == '':
|
if resolved == '':
|
||||||
return f'!Отсутствует термин: {alias}!'
|
return f'!Отсутствует термин: {alias}!'
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -9,24 +9,24 @@ class TestEntity(unittest.TestCase):
|
||||||
self.alias = 'X1'
|
self.alias = 'X1'
|
||||||
self.nominal = 'человек'
|
self.nominal = 'человек'
|
||||||
self.text1 = 'test1'
|
self.text1 = 'test1'
|
||||||
self.form1 = 'sing,datv'
|
self.form1 = ['sing','datv']
|
||||||
self.entity = Entity(self.alias, self.nominal, [{'text': self.text1, 'tags': self.form1}])
|
self.entity = Entity(self.alias, self.nominal, [{'text': self.text1, 'grams': self.form1}])
|
||||||
|
|
||||||
def test_attributes(self):
|
def test_attributes(self):
|
||||||
self.assertEqual(self.entity.alias, self.alias)
|
self.assertEqual(self.entity.alias, self.alias)
|
||||||
self.assertEqual(self.entity.get_nominal(), self.nominal)
|
self.assertEqual(self.entity.get_nominal(), self.nominal)
|
||||||
self.assertEqual(self.entity.manual, [{'text': self.text1, 'tags': self.form1}])
|
self.assertEqual(self.entity.manual, [{'text': self.text1, 'grams': self.form1}])
|
||||||
|
|
||||||
def test_get_form(self):
|
def test_get_form(self):
|
||||||
self.assertEqual(self.entity.get_form(''), self.nominal)
|
self.assertEqual(self.entity.get_form([]), self.nominal)
|
||||||
self.assertEqual(self.entity.get_form(self.form1), self.text1)
|
self.assertEqual(self.entity.get_form(self.form1), self.text1)
|
||||||
self.assertEqual(self.entity.get_form('invalid tags'), '!Неизвестная граммема: invalid tags!')
|
self.assertEqual(self.entity.get_form(['invalid tags']), '!Неизвестная граммема: invalid tags!')
|
||||||
self.assertEqual(self.entity.get_form('plur'), 'люди')
|
self.assertEqual(self.entity.get_form(['plur']), 'люди')
|
||||||
|
|
||||||
def test_set_nominal(self):
|
def test_set_nominal(self):
|
||||||
new_nomial = 'TEST'
|
new_nomial = 'TEST'
|
||||||
self.assertEqual(self.entity.get_form('plur'), 'люди')
|
self.assertEqual(self.entity.get_form(['plur']), 'люди')
|
||||||
self.entity.set_nominal(new_nomial)
|
self.entity.set_nominal(new_nomial)
|
||||||
self.assertEqual(self.entity.get_nominal(), new_nomial)
|
self.assertEqual(self.entity.get_nominal(), new_nomial)
|
||||||
self.assertEqual(self.entity.get_form('plur'), new_nomial)
|
self.assertEqual(self.entity.get_form(['plur']), new_nomial)
|
||||||
self.assertEqual(self.entity.manual, [])
|
self.assertEqual(self.entity.manual, [])
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
import unittest
|
import unittest
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
|
from django.test import tag
|
||||||
|
|
||||||
from cctext import (
|
from cctext import (
|
||||||
EntityReference, TermContext, Entity, SyntacticReference,
|
EntityReference, TermContext, Entity, SyntacticReference,
|
||||||
Resolver, ResolvedReference, Position,
|
Resolver, ResolvedReference, Position, TermForm,
|
||||||
resolve_entity, resolve_syntactic, extract_entities
|
resolve_entity, resolve_syntactic, extract_entities
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,3 +90,20 @@ class TestResolver(unittest.TestCase):
|
||||||
self.assertEqual(self.resolver.refs[1].pos_output, Position(9, 15))
|
self.assertEqual(self.resolver.refs[1].pos_output, Position(9, 15))
|
||||||
self.assertEqual(self.resolver.refs[2].pos_input, Position(28, 38))
|
self.assertEqual(self.resolver.refs[2].pos_input, Position(28, 38))
|
||||||
self.assertEqual(self.resolver.refs[2].pos_output, Position(16, 20))
|
self.assertEqual(self.resolver.refs[2].pos_output, Position(16, 20))
|
||||||
|
|
||||||
|
def test_resolve_manual_forms(self):
|
||||||
|
self.context['X1'] = Entity(
|
||||||
|
alias='X1',
|
||||||
|
nominal='человек',
|
||||||
|
manual_forms=[
|
||||||
|
TermForm(text='тест1', grams='NOUN,sing'.split(',')),
|
||||||
|
TermForm(text='тест2', grams='NOUN,datv,plur'.split(','))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|NOUN,sing,nomn}'), 'тест1', 'Match subset')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|NOUN,sing}'), 'тест1', 'Match full')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|NOUN,datv,plur}'), 'тест2')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|NOUN,plur,datv}'), 'тест2', 'Match any order')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|datv,plur}'), 'тест2', 'Match missing POS')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|NOUN,datv,sing}'), 'тест1')
|
||||||
|
self.assertEqual(self.resolver.resolve('@{X1|VERB,datv,plur}'), 'человек')
|
||||||
|
|
|
@ -150,7 +150,7 @@ export default function DataTable<TData extends RowData>({
|
||||||
style={conditionalRowStyles && getRowStyles(row)}
|
style={conditionalRowStyles && getRowStyles(row)}
|
||||||
>
|
>
|
||||||
{enableRowSelection &&
|
{enableRowSelection &&
|
||||||
<td className='pl-3 pr-1 border-y'>
|
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
||||||
<SelectRow row={row} />
|
<SelectRow row={row} />
|
||||||
</td>}
|
</td>}
|
||||||
{row.getVisibleCells().map(
|
{row.getVisibleCells().map(
|
||||||
|
|
|
@ -252,6 +252,22 @@ export function ArrowDownIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ArrowLeftIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
<path d='M12.707 17.293L8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ArrowRightIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
<path d='M11.293 17.293l1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function CloneIcon(props: IconProps) {
|
export function CloneIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||||
|
@ -407,6 +423,15 @@ export function ChevronDoubleUpIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ChevronDoubleDownIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
<path d='M12 15.586l-4.293-4.293-1.414 1.414L12 18.414l5.707-5.707-1.414-1.414z' />
|
||||||
|
<path d='M17.707 7.707l-1.414-1.414L12 10.586 7.707 6.293 6.293 7.707 12 13.414z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function CheckIcon(props: IconProps) {
|
export function CheckIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
|
56
rsconcept/frontend/src/hooks/useConceptText.ts
Normal file
56
rsconcept/frontend/src/hooks/useConceptText.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
|
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '../models/language';
|
||||||
|
import { DataCallback, postGenerateLexeme, postInflectText, postParseText } from '../utils/backendAPI';
|
||||||
|
|
||||||
|
function useConceptText() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
|
const inflect = useCallback(
|
||||||
|
(data: IWordFormPlain, onSuccess: DataCallback<ITextResult>) => {
|
||||||
|
setError(undefined);
|
||||||
|
postInflectText({
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading,
|
||||||
|
onError: error => setError(error),
|
||||||
|
onSuccess: data => {
|
||||||
|
if (onSuccess) onSuccess(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const parse = useCallback(
|
||||||
|
(data: ITextRequest, onSuccess: DataCallback<ITextResult>) => {
|
||||||
|
setError(undefined);
|
||||||
|
postParseText({
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading,
|
||||||
|
onError: error => setError(error),
|
||||||
|
onSuccess: data => {
|
||||||
|
if (onSuccess) onSuccess(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generateLexeme = useCallback(
|
||||||
|
(data: ITextRequest, onSuccess: DataCallback<ILexemeData>) => {
|
||||||
|
setError(undefined);
|
||||||
|
postGenerateLexeme({
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading,
|
||||||
|
onError: error => setError(error),
|
||||||
|
onSuccess: data => {
|
||||||
|
if (onSuccess) onSuccess(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { inflect, parse, generateLexeme, error, setError, loading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useConceptText;
|
|
@ -1,18 +1,18 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { IReferenceData } from '../models/language';
|
import { IResolutionData } from '../models/language';
|
||||||
import { IRSForm } from '../models/rsform';
|
import { IRSForm } from '../models/rsform';
|
||||||
import { DataCallback, postResolveText } from '../utils/backendAPI';
|
import { DataCallback, postResolveText } from '../utils/backendAPI';
|
||||||
|
|
||||||
function useResolveText({ schema }: { schema?: IRSForm }) {
|
function useResolveText({ schema }: { schema?: IRSForm }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
const [refsData, setRefsData] = useState<IReferenceData | undefined>(undefined);
|
const [refsData, setRefsData] = useState<IResolutionData | undefined>(undefined);
|
||||||
|
|
||||||
const resetData = useCallback(() => setRefsData(undefined), []);
|
const resetData = useCallback(() => setRefsData(undefined), []);
|
||||||
|
|
||||||
function resolveText(text: string, onSuccess?: DataCallback<IReferenceData>) {
|
function resolveText(text: string, onSuccess?: DataCallback<IResolutionData>) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postResolveText(String(schema!.id), {
|
postResolveText(String(schema!.id), {
|
||||||
data: { text: text },
|
data: { text: text },
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
// Module: Natural language model declarations.
|
// Module: Natural language model declarations.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents API result for text output.
|
||||||
|
*/
|
||||||
|
export interface ITextResult {
|
||||||
|
result: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents single unit of language Morphology.
|
* Represents single unit of language Morphology.
|
||||||
*/
|
*/
|
||||||
|
@ -172,7 +179,6 @@ export const GrammemeGroups = [
|
||||||
*/
|
*/
|
||||||
export const NounGrams = [
|
export const NounGrams = [
|
||||||
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS,
|
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS,
|
||||||
...Gender,
|
|
||||||
...Case,
|
...Case,
|
||||||
...Plurality
|
...Plurality
|
||||||
];
|
];
|
||||||
|
@ -211,6 +217,21 @@ export interface IWordForm {
|
||||||
grams: IGramData[]
|
grams: IGramData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents wordform data used for backend communication.
|
||||||
|
*/
|
||||||
|
export interface IWordFormPlain {
|
||||||
|
text: string
|
||||||
|
grams: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents lexeme response containing multiple {@link Wordform}s.
|
||||||
|
*/
|
||||||
|
export interface ILexemeData {
|
||||||
|
items: IWordFormPlain[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equality comparator for {@link IGramData}. Compares text data for unknown grammemes
|
* Equality comparator for {@link IGramData}. Compares text data for unknown grammemes
|
||||||
*/
|
*/
|
||||||
|
@ -275,30 +296,48 @@ export function parseGrammemes(termForm: string): IGramData[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Reference resolution =====
|
// ====== Reference resolution =====
|
||||||
export interface IRefsText {
|
/**
|
||||||
|
* Represents text request.
|
||||||
|
*/
|
||||||
|
export interface ITextRequest {
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents text reference type.
|
||||||
|
*/
|
||||||
export enum ReferenceType {
|
export enum ReferenceType {
|
||||||
ENTITY = 'entity',
|
ENTITY = 'entity',
|
||||||
SYNTACTIC = 'syntax'
|
SYNTACTIC = 'syntax'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents entity reference payload.
|
||||||
|
*/
|
||||||
export interface IEntityReference {
|
export interface IEntityReference {
|
||||||
entity: string
|
entity: string
|
||||||
form: string
|
form: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents syntactic reference payload.
|
||||||
|
*/
|
||||||
export interface ISyntacticReference {
|
export interface ISyntacticReference {
|
||||||
offset: number
|
offset: number
|
||||||
nominal: string
|
nominal: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents text 0-indexed position inside another text.
|
||||||
|
*/
|
||||||
export interface ITextPosition {
|
export interface ITextPosition {
|
||||||
start: number
|
start: number
|
||||||
finish: number
|
finish: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents single resolved reference data.
|
||||||
|
*/
|
||||||
export interface IResolvedReference {
|
export interface IResolvedReference {
|
||||||
type: ReferenceType
|
type: ReferenceType
|
||||||
data: IEntityReference | ISyntacticReference
|
data: IEntityReference | ISyntacticReference
|
||||||
|
@ -306,7 +345,10 @@ export interface IResolvedReference {
|
||||||
pos_output: ITextPosition
|
pos_output: ITextPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReferenceData {
|
/**
|
||||||
|
* Represents resolved references data for the whole text.
|
||||||
|
*/
|
||||||
|
export interface IResolutionData {
|
||||||
input: string
|
input: string
|
||||||
output: string
|
output: string
|
||||||
refs: IResolvedReference[]
|
refs: IResolvedReference[]
|
||||||
|
|
|
@ -81,7 +81,8 @@ export interface ICstMovetoData extends IConstituentaList {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICstUpdateData
|
export interface ICstUpdateData
|
||||||
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'convention' | 'definition_formal' | 'definition_raw' | 'term_raw'> {}
|
extends Pick<IConstituentaMeta, 'id'>,
|
||||||
|
Partial<Pick<IConstituentaMeta, | 'alias' | 'convention' | 'definition_formal' | 'definition_raw' | 'term_raw' | 'term_forms'>> {}
|
||||||
|
|
||||||
export interface ICstRenameData
|
export interface ICstRenameData
|
||||||
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'cst_type' > {}
|
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'cst_type' > {}
|
||||||
|
|
|
@ -6,17 +6,19 @@ import Modal from '../../components/Common/Modal';
|
||||||
import SelectMulti from '../../components/Common/SelectMulti';
|
import SelectMulti from '../../components/Common/SelectMulti';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
||||||
import { CheckIcon, ChevronDoubleUpIcon, ChevronUpIcon, CrossIcon } from '../../components/Icons';
|
import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossIcon } from '../../components/Icons';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import useConceptText from '../../hooks/useConceptText';
|
||||||
import {
|
import {
|
||||||
Grammeme, GrammemeGroups, IWordForm,
|
Grammeme, GrammemeGroups, ITextRequest, IWordForm,
|
||||||
|
IWordFormPlain,
|
||||||
matchWordForm, NounGrams, parseGrammemes,
|
matchWordForm, NounGrams, parseGrammemes,
|
||||||
sortGrammemes, VerbGrams
|
sortGrammemes, VerbGrams
|
||||||
} from '../../models/language';
|
} from '../../models/language';
|
||||||
import { IConstituenta, TermForm } from '../../models/rsform';
|
import { IConstituenta, TermForm } from '../../models/rsform';
|
||||||
import { colorfgGrammeme } from '../../utils/color';
|
import { colorfgGrammeme } from '../../utils/color';
|
||||||
import { labelGrammeme } from '../../utils/labels';
|
import { labelGrammeme } from '../../utils/labels';
|
||||||
import { IGrammemeOption, SelectorGrammems } from '../../utils/selectors';
|
import { IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
|
||||||
|
|
||||||
interface DlgEditTermProps {
|
interface DlgEditTermProps {
|
||||||
hideWindow: () => void
|
hideWindow: () => void
|
||||||
|
@ -27,6 +29,7 @@ interface DlgEditTermProps {
|
||||||
const columnHelper = createColumnHelper<IWordForm>();
|
const columnHelper = createColumnHelper<IWordForm>();
|
||||||
|
|
||||||
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
|
const textProcessor = useConceptText();
|
||||||
const { colors } = useConceptTheme();
|
const { colors } = useConceptTheme();
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
|
|
||||||
|
@ -41,7 +44,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
forms.forEach(
|
forms.forEach(
|
||||||
({text, grams}) => result.push({
|
({text, grams}) => result.push({
|
||||||
text: text,
|
text: text,
|
||||||
tags: grams.join(',')
|
tags: grams.map(gram => gram.data).join(',')
|
||||||
}));
|
}));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -58,6 +61,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
setForms(initForms);
|
setForms(initForms);
|
||||||
setTerm(target.term_resolved);
|
setTerm(target.term_resolved);
|
||||||
setInputText(target.term_resolved);
|
setInputText(target.term_resolved);
|
||||||
|
setInputGrams([]);
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
// Filter grammemes when input changes
|
// Filter grammemes when input changes
|
||||||
|
@ -102,8 +106,8 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
setForms(forms => [
|
setForms(forms => [
|
||||||
...forms.filter(value => !matchWordForm(value, newForm)),
|
newForm,
|
||||||
newForm
|
...forms.filter(value => !matchWordForm(value, newForm))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,22 +125,54 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRowClicked(form: IWordForm) {
|
||||||
|
setInputText(form.text);
|
||||||
|
setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test.type === gram.type)));
|
||||||
|
}
|
||||||
|
|
||||||
function handleResetForm() {
|
function handleResetForm() {
|
||||||
setInputText('');
|
setInputText('');
|
||||||
setInputGrams([]);
|
setInputGrams([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGenerateSelected() {
|
function handleInflect() {
|
||||||
|
const data: IWordFormPlain = {
|
||||||
|
text: term,
|
||||||
|
grams: inputGrams.map(gram => gram.data).join(',')
|
||||||
|
}
|
||||||
|
textProcessor.inflect(data, response => setInputText(response.result));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGenerateBasics() {
|
function handleParse() {
|
||||||
|
const data: ITextRequest = {
|
||||||
|
text: inputText
|
||||||
|
}
|
||||||
|
textProcessor.parse(data, response => {
|
||||||
|
const grams = parseGrammemes(response.result);
|
||||||
|
setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test.type === gram.type)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGenerateLexeme() {
|
||||||
if (forms.length > 0) {
|
if (forms.length > 0) {
|
||||||
if (!window.confirm('Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?')) {
|
if (!window.confirm('Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const data: ITextRequest = {
|
||||||
|
text: inputText
|
||||||
|
}
|
||||||
|
textProcessor.generateLexeme(data, response => {
|
||||||
|
const newForms: IWordForm[] = response.items.map(
|
||||||
|
form => ({
|
||||||
|
text: form.text,
|
||||||
|
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram.type))
|
||||||
|
}));
|
||||||
|
setForms(forms => [
|
||||||
|
...newForms,
|
||||||
|
...forms.filter(value => !newForms.find(test => matchWordForm(value, test))),
|
||||||
|
]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
|
@ -156,10 +192,11 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
minSize: 250,
|
minSize: 250,
|
||||||
maxSize: 250,
|
maxSize: 250,
|
||||||
cell: props =>
|
cell: props =>
|
||||||
<div className='flex justify-start gap-1 select-none'>
|
<div className='flex flex-wrap justify-start gap-1 select-none'>
|
||||||
{ props.getValue().map(
|
{ props.getValue().map(
|
||||||
gram =>
|
gram =>
|
||||||
<div
|
<div
|
||||||
|
key={`${props.cell.id}-${gram.type}`}
|
||||||
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
|
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
|
||||||
title=''
|
title=''
|
||||||
style={{
|
style={{
|
||||||
|
@ -217,34 +254,47 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
<TextArea
|
<TextArea
|
||||||
placeholder='Введите текст'
|
placeholder='Введите текст'
|
||||||
rows={2}
|
rows={2}
|
||||||
dimensions='min-w-[20rem]'
|
dimensions='min-w-[20rem] min-h-[4.2rem]'
|
||||||
|
|
||||||
|
disabled={textProcessor.loading}
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChange={event => setInputText(event.target.value)}
|
onChange={event => setInputText(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='flex items-center justify-start'>
|
<div className='flex items-center justify-between'>
|
||||||
<MiniButton
|
<div className='flex items-center justify-start'>
|
||||||
tooltip='Добавить словоформу'
|
<MiniButton
|
||||||
icon={<CheckIcon size={6} color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}/>}
|
tooltip='Добавить словоформу'
|
||||||
disabled={!inputText || inputGrams.length == 0}
|
icon={<CheckIcon size={6} color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}/>}
|
||||||
onClick={handleAddForm}
|
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||||
/>
|
onClick={handleAddForm}
|
||||||
<MiniButton
|
/>
|
||||||
tooltip='Сбросить словоформу'
|
<MiniButton
|
||||||
icon={<CrossIcon size={6} color='text-warning'/>}
|
tooltip='Сбросить словоформу'
|
||||||
onClick={handleResetForm}
|
icon={<CrossIcon size={6} color='text-warning'/>}
|
||||||
/>
|
disabled={textProcessor.loading}
|
||||||
<MiniButton
|
onClick={handleResetForm}
|
||||||
tooltip='Генерировать словоформу'
|
/>
|
||||||
icon={<ChevronUpIcon size={6} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}/>}
|
<MiniButton
|
||||||
disabled={inputGrams.length == 0}
|
tooltip='Генерировать все словоформы'
|
||||||
onClick={handleGenerateSelected}
|
icon={<ChevronDoubleDownIcon size={6} color='text-primary'/>}
|
||||||
/>
|
disabled={textProcessor.loading}
|
||||||
<MiniButton
|
onClick={handleGenerateLexeme}
|
||||||
tooltip='Генерировать базовые словоформы'
|
/>
|
||||||
icon={<ChevronDoubleUpIcon size={6} color='text-primary'/>}
|
</div>
|
||||||
onClick={handleGenerateBasics}
|
<div className='flex items-center justify-start'>
|
||||||
/>
|
<MiniButton
|
||||||
|
tooltip='Генерировать словоформу'
|
||||||
|
icon={<ArrowLeftIcon size={6} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}/>}
|
||||||
|
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||||
|
onClick={handleInflect}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Определить граммемы'
|
||||||
|
icon={<ArrowRightIcon size={6} color={!inputText ? 'text-disabled' : 'text-primary'}/>}
|
||||||
|
disabled={textProcessor.loading || !inputText}
|
||||||
|
onClick={handleParse}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectMulti
|
<SelectMulti
|
||||||
|
@ -253,6 +303,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
placeholder='Выберите граммемы'
|
placeholder='Выберите граммемы'
|
||||||
|
|
||||||
value={inputGrams}
|
value={inputGrams}
|
||||||
|
isDisabled={textProcessor.loading}
|
||||||
onChange={newValue => setInputGrams(sortGrammemes([...newValue]))}
|
onChange={newValue => setInputGrams(sortGrammemes([...newValue]))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -270,8 +321,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
// onRowDoubleClicked={handleDoubleClick}
|
onRowDoubleClicked={handleRowClicked}
|
||||||
// onRowClicked={handleRowClicked}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -140,14 +140,15 @@ function EditorConstituenta({
|
||||||
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
|
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
<div className='absolute top-0 right-0 flex items-start justify-between w-full'>
|
<div className='absolute top-0 right-0 flex items-start justify-between w-full'>
|
||||||
|
{activeCst &&
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Редактировать словоформы термина'
|
tooltip={`Редактировать словоформы термина: ${activeCst.term_forms.length}`}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
dimensions='w-fit pl-[3.2rem] pt-[0.4rem]'
|
dimensions='w-fit ml-[3.2rem] pt-[0.4rem]'
|
||||||
noHover
|
noHover
|
||||||
onClick={onEditTerm}
|
onClick={onEditTerm}
|
||||||
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
|
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
|
||||||
/>
|
/>}
|
||||||
<div className='flex items-center justify-center w-full gap-1'>
|
<div className='flex items-center justify-center w-full gap-1'>
|
||||||
<div className='font-semibold w-fit'>
|
<div className='font-semibold w-fit'>
|
||||||
<span className=''>Конституента </span>
|
<span className=''>Конституента </span>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { useConceptNavigation } from '../../context/NagivationContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { ICstCreateData, ICstRenameData } from '../../models/rsform';
|
import { ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '../../models/rsform';
|
||||||
import { SyntaxTree } from '../../models/rslang';
|
import { SyntaxTree } from '../../models/rslang';
|
||||||
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
||||||
import { createAliasFor } from '../../utils/misc';
|
import { createAliasFor } from '../../utils/misc';
|
||||||
|
@ -57,7 +57,7 @@ function RSTabs() {
|
||||||
const search = useLocation().search;
|
const search = useLocation().search;
|
||||||
const {
|
const {
|
||||||
error, schema, loading, claim, download, isTracking,
|
error, schema, loading, claim, download, isTracking,
|
||||||
cstCreate, cstDelete, cstRename, subscribe, unsubscribe
|
cstCreate, cstDelete, cstRename, subscribe, unsubscribe, cstUpdate
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
const { destroySchema } = useLibrary();
|
const { destroySchema } = useLibrary();
|
||||||
const { setNoFooter } = useConceptTheme();
|
const { setNoFooter } = useConceptTheme();
|
||||||
|
@ -304,6 +304,19 @@ function RSTabs() {
|
||||||
setShowEditTerm(true);
|
setShowEditTerm(true);
|
||||||
}, [isModified, activeCst]);
|
}, [isModified, activeCst]);
|
||||||
|
|
||||||
|
const handleSaveWordforms = useCallback(
|
||||||
|
(forms: TermForm[]) => {
|
||||||
|
if (!activeID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data: ICstUpdateData = {
|
||||||
|
id: activeID,
|
||||||
|
term_forms: forms
|
||||||
|
};
|
||||||
|
console.log(data);
|
||||||
|
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
||||||
|
}, [cstUpdate, activeID]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
{ loading && <ConceptLoader /> }
|
{ loading && <ConceptLoader /> }
|
||||||
|
@ -344,7 +357,7 @@ function RSTabs() {
|
||||||
{showEditTerm &&
|
{showEditTerm &&
|
||||||
<DlgEditTerm
|
<DlgEditTerm
|
||||||
hideWindow={() => setShowEditTerm(false)}
|
hideWindow={() => setShowEditTerm(false)}
|
||||||
onSave={() => {}} // TODO: implement cst update
|
onSave={handleSaveWordforms}
|
||||||
target={activeCst!}
|
target={activeCst!}
|
||||||
/>}
|
/>}
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import axios, { AxiosError, AxiosRequestConfig } from 'axios'
|
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError'
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { IReferenceData } from '../models/language'
|
import {
|
||||||
import { IRefsText } from '../models/language'
|
ILexemeData,
|
||||||
import { ICurrentUser } from '../models/library'
|
IResolutionData, ITextRequest,
|
||||||
import { IUserLoginData } from '../models/library'
|
ITextResult, IWordFormPlain
|
||||||
import { IUserSignupData } from '../models/library'
|
} from '../models/language';
|
||||||
import { IUserProfile } from '../models/library'
|
import {
|
||||||
import { IUserUpdateData } from '../models/library'
|
ICurrentUser, ILibraryItem, ILibraryUpdateData,
|
||||||
import { IUserInfo } from '../models/library'
|
IUserInfo, IUserLoginData, IUserProfile, IUserSignupData,
|
||||||
import { IUserUpdatePassword } from '../models/library'
|
IUserUpdateData, IUserUpdatePassword
|
||||||
import { ILibraryItem } from '../models/library'
|
} from '../models/library';
|
||||||
import { ILibraryUpdateData } from '../models/library'
|
|
||||||
import {
|
import {
|
||||||
IConstituentaList, IConstituentaMeta,
|
IConstituentaList, IConstituentaMeta,
|
||||||
ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstRenameData, ICstUpdateData,
|
ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstRenameData, ICstUpdateData,
|
||||||
IRSFormCreateData, IRSFormData, IRSFormUploadData} from '../models/rsform'
|
IRSFormCreateData, IRSFormData, IRSFormUploadData} from '../models/rsform';
|
||||||
import { IExpressionParse, IRSExpression } from '../models/rslang'
|
import { IExpressionParse, IRSExpression } from '../models/rslang';
|
||||||
import { config } from './constants'
|
import { config } from './constants';
|
||||||
|
|
||||||
export function initBackend() {
|
export function initBackend() {
|
||||||
axios.defaults.withCredentials = true;
|
axios.defaults.withCredentials = true;
|
||||||
|
@ -258,14 +257,6 @@ export function postCheckExpression(schema: string, request: FrontExchange<IRSEx
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postResolveText(schema: string, request: FrontExchange<IRefsText, IReferenceData>) {
|
|
||||||
AxiosPost({
|
|
||||||
title: `Resolve text references for RSForm id=${schema}: ${request.data.text }`,
|
|
||||||
endpoint: `/api/rsforms/${schema}/resolve`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchResetAliases(target: string, request: FrontPull<IRSFormData>) {
|
export function patchResetAliases(target: string, request: FrontPull<IRSFormData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
title: `Reset alias for RSForm id=${target}`,
|
title: `Reset alias for RSForm id=${target}`,
|
||||||
|
@ -287,6 +278,38 @@ export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUpl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postResolveText(schema: string, request: FrontExchange<ITextRequest, IResolutionData>) {
|
||||||
|
AxiosPost({
|
||||||
|
title: `Resolve text references for RSForm id=${schema}: ${request.data.text}`,
|
||||||
|
endpoint: `/api/rsforms/${schema}/resolve`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
||||||
|
AxiosPost({
|
||||||
|
title: `Inflect text ${request.data.text} to ${request.data.grams}`,
|
||||||
|
endpoint: `/api/cctext/inflect`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postParseText(request: FrontExchange<ITextRequest, ITextResult>) {
|
||||||
|
AxiosPost({
|
||||||
|
title: `Parse text ${request.data.text}`,
|
||||||
|
endpoint: `/api/cctext/parse`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postGenerateLexeme(request: FrontExchange<ITextRequest, ILexemeData>) {
|
||||||
|
AxiosPost({
|
||||||
|
title: `Parse text ${request.data.text}`,
|
||||||
|
endpoint: `/api/cctext/generate-lexeme`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ============ Helper functions =============
|
// ============ Helper functions =============
|
||||||
function AxiosGet<ResponseData>({ endpoint, request, title, options }: IAxiosRequest<undefined, ResponseData>) {
|
function AxiosGet<ResponseData>({ endpoint, request, title, options }: IAxiosRequest<undefined, ResponseData>) {
|
||||||
console.log(`REQUEST: [[${title}]]`);
|
console.log(`REQUEST: [[${title}]]`);
|
||||||
|
|
|
@ -395,7 +395,10 @@ export function colorfgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||||
if (VerbGrams.includes(gram)) {
|
if (VerbGrams.includes(gram)) {
|
||||||
return colors.fgTeal;
|
return colors.fgTeal;
|
||||||
}
|
}
|
||||||
return colors.fgDefault;
|
if (gram === Grammeme.UNKN) {
|
||||||
|
return colors.fgRed;
|
||||||
|
}
|
||||||
|
return colors.fgPurple;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||||
|
|
|
@ -39,12 +39,11 @@ export const SelectorCstType = (
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface IGrammemeOption extends IGramData {
|
export interface IGrammemeOption extends IGramData {
|
||||||
value: Grammeme
|
value: string
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectorGrammems: IGrammemeOption[] =
|
export const SelectorGrammemesList = [
|
||||||
[
|
|
||||||
Grammeme.NOUN, Grammeme.VERB,
|
Grammeme.NOUN, Grammeme.VERB,
|
||||||
|
|
||||||
Grammeme.sing, Grammeme.plur,
|
Grammeme.sing, Grammeme.plur,
|
||||||
|
@ -61,10 +60,13 @@ export const SelectorGrammems: IGrammemeOption[] =
|
||||||
Grammeme.impr, Grammeme.indc,
|
Grammeme.impr, Grammeme.indc,
|
||||||
Grammeme.incl, Grammeme.excl,
|
Grammeme.incl, Grammeme.excl,
|
||||||
Grammeme.pssv, Grammeme.actv,
|
Grammeme.pssv, Grammeme.actv,
|
||||||
].map(
|
];
|
||||||
|
|
||||||
|
export const SelectorGrammems: IGrammemeOption[] =
|
||||||
|
SelectorGrammemesList.map(
|
||||||
gram => ({
|
gram => ({
|
||||||
type: gram,
|
type: gram,
|
||||||
data: gram as string,
|
data: gram as string,
|
||||||
value: gram,
|
value: gram as string,
|
||||||
label: labelGrammeme({type: gram, data: ''} as IGramData)
|
label: labelGrammeme({type: gram, data: ''} as IGramData)
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user