F: Rename Nominal relation and improve UI
This commit is contained in:
parent
14cda60b0d
commit
1555a1bf92
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
|
|
@ -184,6 +184,9 @@
|
|||
"Айзенштат",
|
||||
"Акименков",
|
||||
"Астрина",
|
||||
"Атрибутирование",
|
||||
"Атрибутирующая",
|
||||
"Атрибутирующие",
|
||||
"Ашихмин",
|
||||
"Биективная",
|
||||
"биективной",
|
||||
|
|
@ -222,10 +225,10 @@
|
|||
"неинтерпретируемый",
|
||||
"неитерируемого",
|
||||
"Никанорова",
|
||||
"Номеноид",
|
||||
"номеноида",
|
||||
"номеноидом",
|
||||
"Номеноиды",
|
||||
"Номиноид",
|
||||
"номиноида",
|
||||
"номиноидом",
|
||||
"Номиноиды",
|
||||
"операционализации",
|
||||
"операционализированных",
|
||||
"Оргтеор",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.rsform.models import Association, Constituenta, CstType, OrderManager, RSFormCached
|
||||
from apps.rsform.models import Attribution, Constituenta, CstType, OrderManager, RSFormCached
|
||||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
|
|
@ -318,16 +318,16 @@ class OperationSchemaCached:
|
|||
mapping={}
|
||||
)
|
||||
|
||||
def after_create_association(self, schemaID: int, associations: list[Association],
|
||||
def after_create_attribution(self, schemaID: int, associations: list[Attribution],
|
||||
exclude: Optional[list[int]] = None) -> None:
|
||||
''' Trigger cascade resolutions when association is created. '''
|
||||
''' Trigger cascade resolutions when Attribution is created. '''
|
||||
operation = self.cache.get_operation(schemaID)
|
||||
self.engine.on_inherit_association(operation.pk, associations, exclude)
|
||||
self.engine.on_inherit_attribution(operation.pk, associations, exclude)
|
||||
|
||||
def before_delete_association(self, schemaID: int, associations: list[Association]) -> None:
|
||||
''' Trigger cascade resolutions when association is deleted. '''
|
||||
def before_delete_attribution(self, schemaID: int, associations: list[Attribution]) -> None:
|
||||
''' Trigger cascade resolutions when Attribution is deleted. '''
|
||||
operation = self.cache.get_operation(schemaID)
|
||||
self.engine.on_delete_association(operation.pk, associations)
|
||||
self.engine.on_delete_attribution(operation.pk, associations)
|
||||
|
||||
def _on_add_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
|
||||
''' Trigger cascade resolutions when Constituenta substitution is added. '''
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional
|
|||
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from apps.rsform.models import INSERT_LAST, Association, Constituenta, CstType, RSFormCached
|
||||
from apps.rsform.models import INSERT_LAST, Attribution, Constituenta, CstType, RSFormCached
|
||||
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation
|
||||
|
|
@ -126,10 +126,10 @@ class PropagationEngine:
|
|||
mapping=new_mapping
|
||||
)
|
||||
|
||||
def on_inherit_association(self, operationID: int,
|
||||
items: list[Association],
|
||||
def on_inherit_attribution(self, operationID: int,
|
||||
items: list[Attribution],
|
||||
exclude: Optional[list[int]] = None) -> None:
|
||||
''' Trigger cascade resolutions when association is inherited. '''
|
||||
''' Trigger cascade resolutions when Attribution is inherited. '''
|
||||
children = self.cache.extend_graph.outputs[operationID]
|
||||
if not children:
|
||||
return
|
||||
|
|
@ -137,7 +137,7 @@ class PropagationEngine:
|
|||
if not exclude or child_id not in exclude:
|
||||
self.inherit_association(child_id, items)
|
||||
|
||||
def inherit_association(self, target: int, items: list[Association]) -> None:
|
||||
def inherit_association(self, target: int, items: list[Attribution]) -> None:
|
||||
''' Execute inheritance of Associations. '''
|
||||
operation = self.cache.operation_by_id[target]
|
||||
if operation.result is None or not items:
|
||||
|
|
@ -146,27 +146,27 @@ class PropagationEngine:
|
|||
self.cache.ensure_loaded_subs()
|
||||
|
||||
existing_associations = set(
|
||||
Association.objects.filter(
|
||||
Attribution.objects.filter(
|
||||
container__schema_id=operation.result_id,
|
||||
).values_list('container_id', 'associate_id')
|
||||
).values_list('container_id', 'attribute_id')
|
||||
)
|
||||
|
||||
new_associations: list[Association] = []
|
||||
new_associations: list[Attribution] = []
|
||||
for assoc in items:
|
||||
new_container = self.cache.get_inheritor(assoc.container_id, target)
|
||||
new_associate = self.cache.get_inheritor(assoc.associate_id, target)
|
||||
if new_container is None or new_associate is None \
|
||||
or new_associate == new_container \
|
||||
or (new_container, new_associate) in existing_associations:
|
||||
new_attribute = self.cache.get_inheritor(assoc.attribute_id, target)
|
||||
if new_container is None or new_attribute is None \
|
||||
or new_attribute == new_container \
|
||||
or (new_container, new_attribute) in existing_associations:
|
||||
continue
|
||||
|
||||
new_associations.append(Association(
|
||||
new_associations.append(Attribution(
|
||||
container_id=new_container,
|
||||
associate_id=new_associate
|
||||
attribute_id=new_attribute
|
||||
))
|
||||
if new_associations:
|
||||
new_associations = Association.objects.bulk_create(new_associations)
|
||||
self.on_inherit_association(target, new_associations)
|
||||
new_associations = Attribution.objects.bulk_create(new_associations)
|
||||
self.on_inherit_attribution(target, new_associations)
|
||||
|
||||
def on_before_substitute(self, operationID: int, substitutions: CstSubstitution) -> None:
|
||||
''' Trigger cascade resolutions when Constituenta substitution is executed. '''
|
||||
|
|
@ -185,8 +185,8 @@ class PropagationEngine:
|
|||
self.on_before_substitute(child_operation.pk, new_substitutions)
|
||||
child_schema.substitute(new_substitutions)
|
||||
|
||||
def on_delete_association(self, operationID: int, associations: list[Association]) -> None:
|
||||
''' Trigger cascade resolutions when association is deleted. '''
|
||||
def on_delete_attribution(self, operationID: int, associations: list[Attribution]) -> None:
|
||||
''' Trigger cascade resolutions when Attribution is deleted. '''
|
||||
children = self.cache.extend_graph.outputs[operationID]
|
||||
if not children:
|
||||
return
|
||||
|
|
@ -197,21 +197,21 @@ class PropagationEngine:
|
|||
if child_schema is None:
|
||||
continue
|
||||
|
||||
deleted: list[Association] = []
|
||||
for assoc in associations:
|
||||
new_container = self.cache.get_inheritor(assoc.container_id, child_id)
|
||||
new_associate = self.cache.get_inheritor(assoc.associate_id, child_id)
|
||||
if new_container is None or new_associate is None:
|
||||
deleted: list[Attribution] = []
|
||||
for attr in associations:
|
||||
new_container = self.cache.get_inheritor(attr.container_id, child_id)
|
||||
new_attribute = self.cache.get_inheritor(attr.attribute_id, child_id)
|
||||
if new_container is None or new_attribute is None:
|
||||
continue
|
||||
deleted_assoc = Association.objects.filter(
|
||||
deleted_assoc = Attribution.objects.filter(
|
||||
container=new_container,
|
||||
associate=new_associate
|
||||
attribute=new_attribute
|
||||
)
|
||||
if deleted_assoc.exists():
|
||||
deleted.append(deleted_assoc[0])
|
||||
if deleted:
|
||||
self.on_delete_association(child_id, deleted)
|
||||
Association.objects.filter(pk__in=[assoc.pk for assoc in deleted]).delete()
|
||||
self.on_delete_attribution(child_id, deleted)
|
||||
Attribution.objects.filter(pk__in=[assoc.pk for assoc in deleted]).delete()
|
||||
|
||||
def on_delete_inherited(self, operation: int, target: list[int]) -> None:
|
||||
''' Trigger cascade resolutions when Constituenta inheritance is deleted. '''
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from apps.library.models import LibraryItem, LibraryItemType
|
||||
from apps.rsform.models import Association, Constituenta, CstType, RSFormCached
|
||||
from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached
|
||||
|
||||
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
|
||||
|
||||
|
|
@ -82,20 +82,20 @@ class PropagationFacade:
|
|||
OperationSchemaCached(host).before_delete_cst(item.pk, ids)
|
||||
|
||||
@staticmethod
|
||||
def after_create_association(sourceID: int, associations: list[Association],
|
||||
def after_create_attribution(sourceID: int, associations: list[Attribution],
|
||||
exclude: Optional[list[int]] = None) -> None:
|
||||
''' Trigger cascade resolutions when association is created. '''
|
||||
''' Trigger cascade resolutions when Attribution is created. '''
|
||||
hosts = _get_oss_hosts(sourceID)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchemaCached(host).after_create_association(sourceID, associations)
|
||||
OperationSchemaCached(host).after_create_attribution(sourceID, associations)
|
||||
|
||||
@staticmethod
|
||||
def before_delete_association(sourceID: int,
|
||||
associations: list[Association],
|
||||
def before_delete_attribution(sourceID: int,
|
||||
associations: list[Attribution],
|
||||
exclude: Optional[list[int]] = None) -> None:
|
||||
''' Trigger cascade resolutions before association is deleted. '''
|
||||
''' Trigger cascade resolutions before Attribution is deleted. '''
|
||||
hosts = _get_oss_hosts(sourceID)
|
||||
for host in hosts:
|
||||
if exclude is None or host.pk not in exclude:
|
||||
OperationSchemaCached(host).before_delete_association(sourceID, associations)
|
||||
OperationSchemaCached(host).before_delete_attribution(sourceID, associations)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ class ConstituentaAdmin(admin.ModelAdmin):
|
|||
search_fields = ['term_resolved', 'definition_resolved']
|
||||
|
||||
|
||||
@admin.register(models.Association)
|
||||
@admin.register(models.Attribution)
|
||||
class AssociationAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Association. '''
|
||||
ordering = ['container__schema', 'container', 'associate']
|
||||
list_display = ['container__schema__alias', 'container__alias', 'associate__alias']
|
||||
search_fields = ['container', 'associate']
|
||||
ordering = ['container__schema', 'container', 'attribute']
|
||||
list_display = ['container__schema__alias', 'container__alias', 'attribute__alias']
|
||||
search_fields = ['container', 'attribute']
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 5.2.4 on 2025-08-21 11:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rsform', '0005_alter_constituenta_cst_type_association'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attribution',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_attribute', to='rsform.constituenta', verbose_name='Атрибутирующая конституента')),
|
||||
('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_container', to='rsform.constituenta', verbose_name='Составная конституента')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Атрибутирование конституент',
|
||||
'verbose_name_plural': 'Атрибутирования конституент',
|
||||
'unique_together': {('container', 'attribute')},
|
||||
},
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Association',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
''' Models: Synthesis Inheritance. '''
|
||||
from django.db.models import CASCADE, ForeignKey, Model
|
||||
|
||||
|
||||
class Association(Model):
|
||||
''' Association links nominal constituent to its content.'''
|
||||
container = ForeignKey(
|
||||
verbose_name='Составная конституента',
|
||||
to='rsform.Constituenta',
|
||||
on_delete=CASCADE,
|
||||
related_name='as_container'
|
||||
)
|
||||
associate = ForeignKey(
|
||||
verbose_name='Ассоциированная конституента',
|
||||
to='rsform.Constituenta',
|
||||
on_delete=CASCADE,
|
||||
related_name='as_associate'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Ассоциация конституент'
|
||||
verbose_name_plural = 'Ассоциации конституент'
|
||||
unique_together = [['container', 'associate']]
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.container} -> {self.associate}'
|
||||
28
rsconcept/backend/apps/rsform/models/Attribution.py
Normal file
28
rsconcept/backend/apps/rsform/models/Attribution.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
''' Models: Attribution of nominal constituents. '''
|
||||
from django.db.models import CASCADE, ForeignKey, Model
|
||||
|
||||
|
||||
class Attribution(Model):
|
||||
''' Attribution links nominal constituent to its content.'''
|
||||
container = ForeignKey(
|
||||
verbose_name='Составная конституента',
|
||||
to='rsform.Constituenta',
|
||||
on_delete=CASCADE,
|
||||
related_name='as_container'
|
||||
)
|
||||
attribute = ForeignKey(
|
||||
verbose_name='Атрибутирующая конституента',
|
||||
to='rsform.Constituenta',
|
||||
on_delete=CASCADE,
|
||||
related_name='as_attribute'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Атрибутирование конституент'
|
||||
verbose_name_plural = 'Атрибутирования конституент'
|
||||
unique_together = [['container', 'attribute']]
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.container} -> {self.attribute}'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from .Association import Association
|
||||
from .Attribution import Attribution
|
||||
from .Constituenta import Constituenta, CstType, extract_globals, replace_entities, replace_globals
|
||||
from .OrderManager import OrderManager
|
||||
from .RSForm import DELETED_ALIAS, INSERT_LAST, RSForm
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ from .basics import (
|
|||
WordFormSerializer
|
||||
)
|
||||
from .data_access import (
|
||||
AssociationCreateSerializer,
|
||||
AssociationDataSerializer,
|
||||
AttributionCreateSerializer,
|
||||
AttributionDataSerializer,
|
||||
CrucialUpdateSerializer,
|
||||
CstCreateSerializer,
|
||||
CstInfoSerializer,
|
||||
|
|
|
|||
|
|
@ -17,23 +17,23 @@ from apps.oss.models import Inheritance
|
|||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from ..models import Association, Constituenta, CstType, RSForm
|
||||
from ..models import Attribution, Constituenta, CstType, RSForm
|
||||
from .basics import CstParseSerializer, InheritanceDataSerializer
|
||||
from .io_pyconcept import PyConceptAdapter
|
||||
|
||||
|
||||
class AssociationSerializer(StrictModelSerializer):
|
||||
''' Serializer: Association relation. '''
|
||||
class AttributionSerializer(StrictModelSerializer):
|
||||
''' Serializer: Attribution relation. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Association
|
||||
fields = ('container', 'associate')
|
||||
model = Attribution
|
||||
fields = ('container', 'attribute')
|
||||
|
||||
|
||||
class AssociationDataSerializer(StrictSerializer):
|
||||
''' Serializer: Association data. '''
|
||||
class AttributionDataSerializer(StrictSerializer):
|
||||
''' Serializer: Attribution data. '''
|
||||
container = PKField(many=False, queryset=Constituenta.objects.all().only('schema_id'))
|
||||
associate = PKField(many=False, queryset=Constituenta.objects.all().only('schema_id'))
|
||||
attribute = PKField(many=False, queryset=Constituenta.objects.all().only('schema_id'))
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
|
|
@ -41,26 +41,26 @@ class AssociationDataSerializer(StrictSerializer):
|
|||
raise serializers.ValidationError({
|
||||
'container': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
if schema and attrs['associate'].schema_id != schema.id:
|
||||
if schema and attrs['attribute'].schema_id != schema.id:
|
||||
raise serializers.ValidationError({
|
||||
'associate': msg.constituentaNotInRSform(schema.title)
|
||||
'attribute': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class AssociationCreateSerializer(AssociationDataSerializer):
|
||||
''' Serializer: Data for creating new association. '''
|
||||
class AttributionCreateSerializer(AttributionDataSerializer):
|
||||
''' Serializer: Data for creating new Attribution. '''
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
if attrs['container'].pk == attrs['associate'].pk:
|
||||
if attrs['container'].pk == attrs['attribute'].pk:
|
||||
raise serializers.ValidationError({
|
||||
'container': msg.associationSelf()
|
||||
})
|
||||
if Association.objects.filter(container=attrs['container'], associate=attrs['associate']).exists():
|
||||
if Attribution.objects.filter(container=attrs['container'], attribute=attrs['attribute']).exists():
|
||||
raise serializers.ValidationError({
|
||||
'associate': msg.associationAlreadyExists()
|
||||
'attribute': msg.associationAlreadyExists()
|
||||
})
|
||||
|
||||
return attrs
|
||||
|
|
@ -187,8 +187,8 @@ class RSFormSerializer(StrictModelSerializer):
|
|||
inheritance = serializers.ListField(
|
||||
child=InheritanceDataSerializer()
|
||||
)
|
||||
association = serializers.ListField(
|
||||
child=AssociationSerializer()
|
||||
attribution = serializers.ListField(
|
||||
child=AttributionSerializer()
|
||||
)
|
||||
oss = serializers.ListField(
|
||||
child=LibraryItemReferenceSerializer()
|
||||
|
|
@ -220,7 +220,7 @@ class RSFormSerializer(StrictModelSerializer):
|
|||
result['items'] = []
|
||||
result['oss'] = []
|
||||
result['inheritance'] = []
|
||||
result['association'] = []
|
||||
result['attribution'] = []
|
||||
for cst in Constituenta.objects.filter(schema=instance).defer('order').order_by('order'):
|
||||
result['items'].append(CstInfoSerializer(cst).data)
|
||||
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
|
||||
|
|
@ -228,10 +228,10 @@ class RSFormSerializer(StrictModelSerializer):
|
|||
'id': oss.pk,
|
||||
'alias': oss.alias
|
||||
})
|
||||
for assoc in Association.objects.filter(container__schema=instance).only('container_id', 'associate_id'):
|
||||
result['association'].append({
|
||||
for assoc in Attribution.objects.filter(container__schema=instance).only('container_id', 'attribute_id'):
|
||||
result['attribution'].append({
|
||||
'container': assoc.container_id,
|
||||
'associate': assoc.associate_id
|
||||
'attribute': assoc.attribute_id
|
||||
})
|
||||
return result
|
||||
|
||||
|
|
@ -302,22 +302,22 @@ class RSFormSerializer(StrictModelSerializer):
|
|||
validated_data=loaded_item.validated_data
|
||||
)
|
||||
|
||||
Association.objects.filter(container__schema=instance).delete()
|
||||
associations_to_create: list[Association] = []
|
||||
for assoc in data.get('association', []):
|
||||
Attribution.objects.filter(container__schema=instance).delete()
|
||||
attributions_to_create: list[Attribution] = []
|
||||
for assoc in data.get('attribution', []):
|
||||
old_container_id = assoc['container']
|
||||
old_associate_id = assoc['associate']
|
||||
old_attribute_id = assoc['attribute']
|
||||
container_id = id_map.get(old_container_id)
|
||||
associate_id = id_map.get(old_associate_id)
|
||||
if container_id and associate_id:
|
||||
associations_to_create.append(
|
||||
Association(
|
||||
attribute_id = id_map.get(old_attribute_id)
|
||||
if container_id and attribute_id:
|
||||
attributions_to_create.append(
|
||||
Attribution(
|
||||
container_id=container_id,
|
||||
associate_id=associate_id
|
||||
attribute_id=attribute_id
|
||||
)
|
||||
)
|
||||
if associations_to_create:
|
||||
Association.objects.bulk_create(associations_to_create)
|
||||
if attributions_to_create:
|
||||
Attribution.objects.bulk_create(attributions_to_create)
|
||||
|
||||
|
||||
class RSFormParseSerializer(StrictModelSerializer):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
''' Tests for Django Models. '''
|
||||
from .t_Association import *
|
||||
from .t_Attribution import *
|
||||
from .t_Constituenta import *
|
||||
from .t_RSForm import *
|
||||
from .t_RSFormCached import *
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
''' Testing models: Association. '''
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.rsform.models import Association, Constituenta, CstType, RSForm
|
||||
|
||||
|
||||
class TestAssociation(TestCase):
|
||||
''' Testing Association model. '''
|
||||
|
||||
def setUp(self):
|
||||
self.schema = RSForm.create(title='Test1')
|
||||
|
||||
# Create test constituents
|
||||
self.container1 = Constituenta.objects.create(
|
||||
alias='C1',
|
||||
schema=self.schema.model,
|
||||
order=1,
|
||||
cst_type=CstType.NOMINAL
|
||||
)
|
||||
self.associate1 = Constituenta.objects.create(
|
||||
alias='A1',
|
||||
schema=self.schema.model,
|
||||
order=2,
|
||||
cst_type=CstType.BASE
|
||||
)
|
||||
|
||||
def test_str(self):
|
||||
''' Test string representation. '''
|
||||
association = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
expected_str = f'{self.container1} -> {self.associate1}'
|
||||
self.assertEqual(str(association), expected_str)
|
||||
|
||||
def test_create_association(self):
|
||||
''' Test basic association creation. '''
|
||||
association = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
self.assertEqual(association.container, self.container1)
|
||||
self.assertEqual(association.associate, self.associate1)
|
||||
self.assertIsNotNone(association.id)
|
||||
|
||||
def test_unique_constraint(self):
|
||||
''' Test unique constraint on container and associate. '''
|
||||
# Create first association
|
||||
Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
|
||||
# Try to create duplicate association
|
||||
with self.assertRaises(IntegrityError):
|
||||
Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
|
||||
def test_container_not_null(self):
|
||||
''' Test container field cannot be null. '''
|
||||
with self.assertRaises(IntegrityError):
|
||||
Association.objects.create(
|
||||
container=None,
|
||||
associate=self.associate1
|
||||
)
|
||||
|
||||
def test_associate_not_null(self):
|
||||
''' Test associate field cannot be null. '''
|
||||
with self.assertRaises(IntegrityError):
|
||||
Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=None
|
||||
)
|
||||
|
||||
def test_cascade_delete_container(self):
|
||||
''' Test cascade delete when container is deleted. '''
|
||||
association = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
association_id = association.id
|
||||
|
||||
# Delete the container
|
||||
self.container1.delete()
|
||||
|
||||
# Association should be deleted due to CASCADE
|
||||
with self.assertRaises(Association.DoesNotExist):
|
||||
Association.objects.get(id=association_id)
|
||||
|
||||
def test_cascade_delete_associate(self):
|
||||
''' Test cascade delete when associate is deleted. '''
|
||||
association = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
association_id = association.id
|
||||
|
||||
# Delete the associate
|
||||
self.associate1.delete()
|
||||
|
||||
# Association should be deleted due to CASCADE
|
||||
with self.assertRaises(Association.DoesNotExist):
|
||||
Association.objects.get(id=association_id)
|
||||
|
||||
def test_related_names(self):
|
||||
''' Test related names for foreign key relationships. '''
|
||||
association = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
|
||||
# Test container related name
|
||||
container_associations = self.container1.as_container.all()
|
||||
self.assertEqual(len(container_associations), 1)
|
||||
self.assertEqual(container_associations[0], association)
|
||||
|
||||
# Test associate related name
|
||||
associate_associations = self.associate1.as_associate.all()
|
||||
self.assertEqual(len(associate_associations), 1)
|
||||
self.assertEqual(associate_associations[0], association)
|
||||
|
||||
def test_multiple_associations_same_container(self):
|
||||
''' Test multiple associations with same container. '''
|
||||
associate3 = Constituenta.objects.create(
|
||||
alias='A3',
|
||||
schema=self.schema.model,
|
||||
order=3,
|
||||
cst_type=CstType.BASE
|
||||
)
|
||||
|
||||
association1 = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
association2 = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=associate3
|
||||
)
|
||||
|
||||
container_associations = self.container1.as_container.all()
|
||||
self.assertEqual(len(container_associations), 2)
|
||||
self.assertIn(association1, container_associations)
|
||||
self.assertIn(association2, container_associations)
|
||||
|
||||
def test_multiple_associations_same_associate(self):
|
||||
''' Test multiple associations with same associate. '''
|
||||
container3 = Constituenta.objects.create(
|
||||
alias='C3',
|
||||
schema=self.schema.model,
|
||||
order=3,
|
||||
cst_type=CstType.NOMINAL
|
||||
)
|
||||
|
||||
association1 = Association.objects.create(
|
||||
container=self.container1,
|
||||
associate=self.associate1
|
||||
)
|
||||
association2 = Association.objects.create(
|
||||
container=container3,
|
||||
associate=self.associate1
|
||||
)
|
||||
|
||||
associate_associations = self.associate1.as_associate.all()
|
||||
self.assertEqual(len(associate_associations), 2)
|
||||
self.assertIn(association1, associate_associations)
|
||||
self.assertIn(association2, associate_associations)
|
||||
|
||||
def test_meta_unique_together(self):
|
||||
''' Test Meta class unique_together constraint. '''
|
||||
unique_together = Association._meta.unique_together
|
||||
self.assertEqual(len(unique_together), 1)
|
||||
self.assertIn(('container', 'associate'), unique_together)
|
||||
175
rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py
Normal file
175
rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
''' Testing models: Association. '''
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.rsform.models import Attribution, Constituenta, CstType, RSForm
|
||||
|
||||
|
||||
class TestAttribution(TestCase):
|
||||
''' Testing Attribution model. '''
|
||||
|
||||
def setUp(self):
|
||||
self.schema = RSForm.create(title='Test1')
|
||||
|
||||
# Create test constituents
|
||||
self.container1 = Constituenta.objects.create(
|
||||
alias='C1',
|
||||
schema=self.schema.model,
|
||||
order=1,
|
||||
cst_type=CstType.NOMINAL
|
||||
)
|
||||
self.attribute1 = Constituenta.objects.create(
|
||||
alias='A1',
|
||||
schema=self.schema.model,
|
||||
order=2,
|
||||
cst_type=CstType.BASE
|
||||
)
|
||||
|
||||
def test_str(self):
|
||||
''' Test string representation. '''
|
||||
attribution = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
expected_str = f'{self.container1} -> {self.attribute1}'
|
||||
self.assertEqual(str(attribution), expected_str)
|
||||
|
||||
def test_create_attribution(self):
|
||||
''' Test basic Attribution creation. '''
|
||||
attr = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
self.assertEqual(attr.container, self.container1)
|
||||
self.assertEqual(attr.attribute, self.attribute1)
|
||||
self.assertIsNotNone(attr.id)
|
||||
|
||||
def test_unique_constraint(self):
|
||||
''' Test unique constraint on container and attribute. '''
|
||||
# Create first Attribution
|
||||
Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
|
||||
# Try to create duplicate Attribution
|
||||
with self.assertRaises(IntegrityError):
|
||||
Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
|
||||
def test_container_not_null(self):
|
||||
''' Test container field cannot be null. '''
|
||||
with self.assertRaises(IntegrityError):
|
||||
Attribution.objects.create(
|
||||
container=None,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
|
||||
def test_attribute_not_null(self):
|
||||
''' Test attribute field cannot be null. '''
|
||||
with self.assertRaises(IntegrityError):
|
||||
Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=None
|
||||
)
|
||||
|
||||
def test_cascade_delete_container(self):
|
||||
''' Test cascade delete when container is deleted. '''
|
||||
attribution = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
association_id = attribution.id
|
||||
|
||||
# Delete the container
|
||||
self.container1.delete()
|
||||
|
||||
# Attribution should be deleted due to CASCADE
|
||||
with self.assertRaises(Attribution.DoesNotExist):
|
||||
Attribution.objects.get(id=association_id)
|
||||
|
||||
def test_cascade_delete_attribute(self):
|
||||
''' Test cascade delete when attribute is deleted. '''
|
||||
attribution = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
association_id = attribution.id
|
||||
|
||||
# Delete the attribute
|
||||
self.attribute1.delete()
|
||||
|
||||
# Attribution should be deleted due to CASCADE
|
||||
with self.assertRaises(Attribution.DoesNotExist):
|
||||
Attribution.objects.get(id=association_id)
|
||||
|
||||
def test_related_names(self):
|
||||
''' Test related names for foreign key relationships. '''
|
||||
attribution = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
|
||||
# Test container related name
|
||||
container_associations = self.container1.as_container.all()
|
||||
self.assertEqual(len(container_associations), 1)
|
||||
self.assertEqual(container_associations[0], attribution)
|
||||
|
||||
# Test attribute related name
|
||||
attribute_associations = self.attribute1.as_attribute.all()
|
||||
self.assertEqual(len(attribute_associations), 1)
|
||||
self.assertEqual(attribute_associations[0], attribution)
|
||||
|
||||
def test_multiple_attributions_same_container(self):
|
||||
''' Test multiple Attributions with same container. '''
|
||||
attribute3 = Constituenta.objects.create(
|
||||
alias='A3',
|
||||
schema=self.schema.model,
|
||||
order=3,
|
||||
cst_type=CstType.BASE
|
||||
)
|
||||
|
||||
attr1 = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
attr2 = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=attribute3
|
||||
)
|
||||
|
||||
container_associations = self.container1.as_container.all()
|
||||
self.assertEqual(len(container_associations), 2)
|
||||
self.assertIn(attr1, container_associations)
|
||||
self.assertIn(attr2, container_associations)
|
||||
|
||||
def test_multiple_attributions_same_attribute(self):
|
||||
''' Test multiple Attributions with same attribute. '''
|
||||
container3 = Constituenta.objects.create(
|
||||
alias='C3',
|
||||
schema=self.schema.model,
|
||||
order=3,
|
||||
cst_type=CstType.NOMINAL
|
||||
)
|
||||
|
||||
attr1 = Attribution.objects.create(
|
||||
container=self.container1,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
attr2 = Attribution.objects.create(
|
||||
container=container3,
|
||||
attribute=self.attribute1
|
||||
)
|
||||
|
||||
attribute_associations = self.attribute1.as_attribute.all()
|
||||
self.assertEqual(len(attribute_associations), 2)
|
||||
self.assertIn(attr1, attribute_associations)
|
||||
self.assertIn(attr2, attribute_associations)
|
||||
|
||||
def test_meta_unique_together(self):
|
||||
''' Test Meta class unique_together constraint. '''
|
||||
unique_together = Attribution._meta.unique_together
|
||||
self.assertEqual(len(unique_together), 1)
|
||||
self.assertIn(('container', 'attribute'), unique_together)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
''' Tests for REST API. '''
|
||||
from .t_associations import *
|
||||
from .t_attribtuions import *
|
||||
from .t_cctext import *
|
||||
from .t_constituenta import *
|
||||
from .t_rsforms import *
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
''' Testing API: Association. '''
|
||||
''' Testing API: Attribution. '''
|
||||
import io
|
||||
import os
|
||||
from zipfile import ZipFile
|
||||
|
|
@ -7,13 +7,13 @@ from cctext import ReferenceType
|
|||
from rest_framework import status
|
||||
|
||||
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||
from apps.rsform.models import Association, Constituenta, CstType, RSForm
|
||||
from apps.rsform.models import Attribution, Constituenta, CstType, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
from shared.testing_utils import response_contains
|
||||
|
||||
|
||||
class TestAssociationsEndpoints(EndpointTester):
|
||||
''' Testing basic Association API. '''
|
||||
class TestAttributionsEndpoints(EndpointTester):
|
||||
''' Testing basic Attribution API. '''
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
|
@ -28,73 +28,73 @@ class TestAssociationsEndpoints(EndpointTester):
|
|||
self.invalid_id = self.n2.pk + 1337
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/create-association', method='post')
|
||||
def test_create_association(self):
|
||||
@decl_endpoint('/api/rsforms/{item}/create-attribution', method='post')
|
||||
def test_create_attribution(self):
|
||||
self.executeBadData({}, item=self.owned_id)
|
||||
|
||||
data = {'container': self.n1.pk, 'associate': self.invalid_id}
|
||||
data = {'container': self.n1.pk, 'attribute': self.invalid_id}
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
data['associate'] = self.unowned_cst.pk
|
||||
data['attribute'] = self.unowned_cst.pk
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
data['associate'] = data['container']
|
||||
data['attribute'] = data['container']
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
data = {'container': self.n1.pk, 'associate': self.x1.pk}
|
||||
data = {'container': self.n1.pk, 'attribute': self.x1.pk}
|
||||
self.executeBadData(data, item=self.unowned_id)
|
||||
|
||||
response = self.executeCreated(data, item=self.owned_id)
|
||||
associations = response.data['association']
|
||||
associations = response.data['attribution']
|
||||
self.assertEqual(len(associations), 1)
|
||||
self.assertEqual(associations[0]['container'], self.n1.pk)
|
||||
self.assertEqual(associations[0]['associate'], self.x1.pk)
|
||||
self.assertEqual(associations[0]['attribute'], self.x1.pk)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/create-association', method='post')
|
||||
def test_create_association_duplicate(self):
|
||||
data = {'container': self.n1.pk, 'associate': self.x1.pk}
|
||||
@decl_endpoint('/api/rsforms/{item}/create-attribution', method='post')
|
||||
def test_create_attribution_duplicate(self):
|
||||
data = {'container': self.n1.pk, 'attribute': self.x1.pk}
|
||||
self.executeCreated(data, item=self.owned_id)
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/delete-association', method='patch')
|
||||
def test_delete_association(self):
|
||||
data = {'container': self.n1.pk, 'associate': self.x1.pk}
|
||||
@decl_endpoint('/api/rsforms/{item}/delete-attribution', method='patch')
|
||||
def test_delete_attribution(self):
|
||||
data = {'container': self.n1.pk, 'attribute': self.x1.pk}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
Association.objects.create(
|
||||
Attribution.objects.create(
|
||||
container=self.n1,
|
||||
associate=self.x1
|
||||
attribute=self.x1
|
||||
)
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
response = self.executeOK(data, item=self.owned_id)
|
||||
associations = response.data['association']
|
||||
self.assertEqual(len(associations), 0)
|
||||
attributions = response.data['attribution']
|
||||
self.assertEqual(len(attributions), 0)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/clear-associations', method='patch')
|
||||
def test_clear_associations(self):
|
||||
@decl_endpoint('/api/rsforms/{item}/clear-attributions', method='patch')
|
||||
def test_clear_attributions(self):
|
||||
data = {'target': self.n1.pk}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
self.executeNotFound(data, item=self.invalid_id)
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
|
||||
Association.objects.create(
|
||||
Attribution.objects.create(
|
||||
container=self.n1,
|
||||
associate=self.x1
|
||||
attribute=self.x1
|
||||
)
|
||||
Association.objects.create(
|
||||
Attribution.objects.create(
|
||||
container=self.n1,
|
||||
associate=self.n2
|
||||
attribute=self.n2
|
||||
)
|
||||
Association.objects.create(
|
||||
Attribution.objects.create(
|
||||
container=self.n2,
|
||||
associate=self.n1
|
||||
attribute=self.n1
|
||||
)
|
||||
response = self.executeOK(data, item=self.owned_id)
|
||||
associations = response.data['association']
|
||||
associations = response.data['attribution']
|
||||
self.assertEqual(len(associations), 1)
|
||||
self.assertEqual(associations[0]['container'], self.n2.pk)
|
||||
self.assertEqual(associations[0]['associate'], self.n1.pk)
|
||||
self.assertEqual(associations[0]['attribute'], self.n1.pk)
|
||||
|
|
@ -49,9 +49,9 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
'restore_order',
|
||||
'reset_aliases',
|
||||
'produce_structure',
|
||||
'add_association',
|
||||
'delete_association',
|
||||
'clear_associations'
|
||||
'add_attribution',
|
||||
'delete_attribution',
|
||||
'clear_attributions'
|
||||
]:
|
||||
permission_list = [permissions.ItemEditor]
|
||||
elif self.action in [
|
||||
|
|
@ -285,9 +285,9 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
)
|
||||
|
||||
@extend_schema(
|
||||
summary='create Association',
|
||||
summary='create Attribution',
|
||||
tags=['Constituenta'],
|
||||
request=s.AssociationCreateSerializer,
|
||||
request=s.AttributionCreateSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
|
|
@ -295,21 +295,21 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='create-association')
|
||||
def create_association(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create Association. '''
|
||||
@action(detail=True, methods=['post'], url_path='create-attribution')
|
||||
def create_attribution(self, request: Request, pk) -> HttpResponse:
|
||||
''' Create Attribution. '''
|
||||
item = self._get_item()
|
||||
serializer = s.AssociationCreateSerializer(data=request.data, context={'schema': item})
|
||||
serializer = s.AttributionCreateSerializer(data=request.data, context={'schema': item})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
container = serializer.validated_data['container']
|
||||
associate = serializer.validated_data['associate']
|
||||
attribute = serializer.validated_data['attribute']
|
||||
|
||||
with transaction.atomic():
|
||||
new_association = m.Association.objects.create(
|
||||
new_association = m.Attribution.objects.create(
|
||||
container=container,
|
||||
associate=associate
|
||||
attribute=attribute
|
||||
)
|
||||
PropagationFacade.after_create_association(item.pk, [new_association])
|
||||
PropagationFacade.after_create_attribution(item.pk, [new_association])
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
|
|
@ -320,7 +320,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
@extend_schema(
|
||||
summary='delete Association',
|
||||
tags=['RSForm'],
|
||||
request=s.AssociationDataSerializer,
|
||||
request=s.AttributionDataSerializer,
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
|
|
@ -328,25 +328,25 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='delete-association')
|
||||
def delete_association(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Association. '''
|
||||
@action(detail=True, methods=['patch'], url_path='delete-attribution')
|
||||
def delete_attribution(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Attribution. '''
|
||||
item = self._get_item()
|
||||
serializer = s.AssociationDataSerializer(data=request.data, context={'schema': item})
|
||||
serializer = s.AttributionDataSerializer(data=request.data, context={'schema': item})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
target = list(m.Association.objects.filter(
|
||||
target = list(m.Attribution.objects.filter(
|
||||
container=serializer.validated_data['container'],
|
||||
associate=serializer.validated_data['associate']
|
||||
attribute=serializer.validated_data['attribute']
|
||||
))
|
||||
if not target:
|
||||
raise ValidationError({
|
||||
'container': msg.invalidAssociation()
|
||||
})
|
||||
|
||||
PropagationFacade.before_delete_association(item.pk, target)
|
||||
m.Association.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||
PropagationFacade.before_delete_attribution(item.pk, target)
|
||||
m.Attribution.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
|
|
@ -355,7 +355,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
)
|
||||
|
||||
@extend_schema(
|
||||
summary='delete all associations for target constituenta',
|
||||
summary='delete all Attributions for target constituenta',
|
||||
tags=['RSForm'],
|
||||
request=s.CstTargetSerializer,
|
||||
responses={
|
||||
|
|
@ -365,18 +365,18 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='clear-associations')
|
||||
def clear_associations(self, request: Request, pk) -> HttpResponse:
|
||||
@action(detail=True, methods=['patch'], url_path='clear-attributions')
|
||||
def clear_attributions(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete Associations for target Constituenta. '''
|
||||
item = self._get_item()
|
||||
serializer = s.CstTargetSerializer(data=request.data, context={'schema': item})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
target = list(m.Association.objects.filter(container=serializer.validated_data['target']))
|
||||
target = list(m.Attribution.objects.filter(container=serializer.validated_data['target']))
|
||||
if target:
|
||||
PropagationFacade.before_delete_association(item.pk, target)
|
||||
m.Association.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||
PropagationFacade.before_delete_attribution(item.pk, target)
|
||||
m.Attribution.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||
item.save(update_fields=['time_update'])
|
||||
|
||||
return Response(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export function HelpConceptRelations() {
|
|||
<div className='text-justify'>
|
||||
<h1>Связи между конституентами</h1>
|
||||
<p>
|
||||
Наиболее общей связью между конституентами является ассоциация, устанавливаемая между номеноидом и относимыми к
|
||||
Наиболее общей связью между конституентами является ассоциация, устанавливаемая между номиноидом и относимыми к
|
||||
нему другими конституентами. Такая связь задается до установления точных определений и применяется для
|
||||
предварительной фиксации групп связанных конституент.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export function HelpThesaurus() {
|
|||
<b>Типы конституент</b>
|
||||
<li>
|
||||
<IconCstNominal size='1rem' className='inline-icon' />
|
||||
{'\u2009'}Номеноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной
|
||||
{'\u2009'}Номиноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной
|
||||
группировки конституент и предварительной фиксации содержательных отношений.
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export function describeSubstitutionError(error: RO<ISubstitutionErrorDescriptio
|
|||
case SubstitutionErrorType.invalidConstant:
|
||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: подстановка константного множества возможна только вместо другого константного`;
|
||||
case SubstitutionErrorType.invalidNominal:
|
||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: подстановка номеноида возможна только вместо другого номеноида`;
|
||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: подстановка номиноида возможна только вместо другого номиноида`;
|
||||
case SubstitutionErrorType.invalidClasses:
|
||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: классы конституент не совпадают`;
|
||||
case SubstitutionErrorType.typificationCycle:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { DELAYS, KEYS } from '@/backend/configuration';
|
|||
import { infoMsg } from '@/utils/labels';
|
||||
|
||||
import {
|
||||
type IAssociation,
|
||||
type IAssociationTargetDTO,
|
||||
type IAttribution,
|
||||
type IAttributionTargetDTO,
|
||||
type ICheckConstituentaDTO,
|
||||
type IConstituentaCreatedResponse,
|
||||
type IConstituentaList,
|
||||
|
|
@ -154,28 +154,28 @@ export const rsformsApi = {
|
|||
request: { data: data }
|
||||
}),
|
||||
|
||||
createAssociation: ({ itemID, data }: { itemID: number; data: IAssociation }) =>
|
||||
axiosPost<IAssociation, IRSFormDTO>({
|
||||
createAttribution: ({ itemID, data }: { itemID: number; data: IAttribution }) =>
|
||||
axiosPost<IAttribution, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/rsforms/${itemID}/create-association`,
|
||||
endpoint: `/api/rsforms/${itemID}/create-attribution`,
|
||||
request: {
|
||||
data: data,
|
||||
successMessage: infoMsg.changesSaved
|
||||
}
|
||||
}),
|
||||
deleteAssociation: ({ itemID, data }: { itemID: number; data: IAssociation }) =>
|
||||
axiosPatch<IAssociation, IRSFormDTO>({
|
||||
deleteAttribution: ({ itemID, data }: { itemID: number; data: IAttribution }) =>
|
||||
axiosPatch<IAttribution, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/rsforms/${itemID}/delete-association`,
|
||||
endpoint: `/api/rsforms/${itemID}/delete-attribution`,
|
||||
request: {
|
||||
data: data,
|
||||
successMessage: infoMsg.changesSaved
|
||||
}
|
||||
}),
|
||||
clearAssociations: ({ itemID, data }: { itemID: number; data: IAssociationTargetDTO }) =>
|
||||
axiosPatch<IAssociationTargetDTO, IRSFormDTO>({
|
||||
clearAttributions: ({ itemID, data }: { itemID: number; data: IAttributionTargetDTO }) =>
|
||||
axiosPatch<IAttributionTargetDTO, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/rsforms/${itemID}/clear-associations`,
|
||||
endpoint: `/api/rsforms/${itemID}/clear-attributions`,
|
||||
request: {
|
||||
data: data,
|
||||
successMessage: infoMsg.changesSaved
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class RSFormLoader {
|
|||
result.graph = this.graph;
|
||||
result.cstByAlias = this.cstByAlias;
|
||||
result.cstByID = this.cstByID;
|
||||
result.association_graph = this.association_graph;
|
||||
result.attribution_graph = this.association_graph;
|
||||
result.full_graph = this.full_graph;
|
||||
return result;
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ export class RSFormLoader {
|
|||
cst.is_template = inferTemplate(cst.definition_formal);
|
||||
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
|
||||
cst.spawn = [];
|
||||
cst.associations = [];
|
||||
cst.attributes = [];
|
||||
cst.spawn_alias = [];
|
||||
cst.parent_schema = schemaByCst.get(cst.id);
|
||||
cst.parent_schema_index = cst.parent_schema ? parents.indexOf(cst.parent_schema) + 1 : 0;
|
||||
|
|
@ -110,11 +110,11 @@ export class RSFormLoader {
|
|||
parent.spawn_alias.push(cst.alias);
|
||||
}
|
||||
});
|
||||
this.schema.association.forEach(assoc => {
|
||||
this.schema.attribution.forEach(assoc => {
|
||||
const container = this.cstByID.get(assoc.container)!;
|
||||
container.associations.push(assoc.associate);
|
||||
this.full_graph.addEdge(container.id, assoc.associate);
|
||||
this.association_graph.addEdge(container.id, assoc.associate);
|
||||
container.attributes.push(assoc.attribute);
|
||||
this.full_graph.addEdge(container.id, assoc.attribute);
|
||||
this.association_graph.addEdge(container.id, assoc.attribute);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,11 +94,11 @@ export interface ICheckConstituentaDTO {
|
|||
/** Represents data, used in merging multiple {@link IConstituenta}. */
|
||||
export type ISubstitutionsDTO = z.infer<typeof schemaSubstitutions>;
|
||||
|
||||
/** Represents data for creating or deleting an association. */
|
||||
export type IAssociation = z.infer<typeof schemaAssociation>;
|
||||
/** Represents data for creating or deleting an Attribution. */
|
||||
export type IAttribution = z.infer<typeof schemaAttribution>;
|
||||
|
||||
/** Represents data for clearing all associations for a target constituenta. */
|
||||
export type IAssociationTargetDTO = z.infer<typeof schemaAssociationTarget>;
|
||||
export type IAttributionTargetDTO = z.infer<typeof schemaAttributionTarget>;
|
||||
|
||||
/** Represents Constituenta list. */
|
||||
export interface IConstituentaList {
|
||||
|
|
@ -308,9 +308,9 @@ export const schemaConstituenta = schemaConstituentaBasics.extend({
|
|||
.optional()
|
||||
});
|
||||
|
||||
export const schemaAssociation = z.strictObject({
|
||||
export const schemaAttribution = z.strictObject({
|
||||
container: z.number(),
|
||||
associate: z.number()
|
||||
attribute: z.number()
|
||||
});
|
||||
|
||||
export const schemaRSForm = schemaLibraryItem.extend({
|
||||
|
|
@ -320,7 +320,7 @@ export const schemaRSForm = schemaLibraryItem.extend({
|
|||
versions: z.array(schemaVersionInfo),
|
||||
|
||||
items: z.array(schemaConstituenta),
|
||||
association: z.array(schemaAssociation),
|
||||
attribution: z.array(schemaAttribution),
|
||||
inheritance: z.array(
|
||||
z.strictObject({
|
||||
child: z.number(),
|
||||
|
|
@ -397,7 +397,7 @@ export const schemaSubstitutions = z.strictObject({
|
|||
substitutions: z.array(schemaSubstituteConstituents).min(1, { message: errorMsg.emptySubstitutions })
|
||||
});
|
||||
|
||||
export const schemaAssociationTarget = z.strictObject({
|
||||
export const schemaAttributionTarget = z.strictObject({
|
||||
target: z.number()
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { rsformsApi } from './api';
|
||||
import { type IAssociationTargetDTO } from './types';
|
||||
import { type IAttributionTargetDTO } from './types';
|
||||
|
||||
export const useClearAssociations = () => {
|
||||
export const useClearAttributions = () => {
|
||||
const client = useQueryClient();
|
||||
const { updateTimestamp } = useUpdateTimestamp();
|
||||
const mutation = useMutation({
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'clear-associations'],
|
||||
mutationFn: rsformsApi.clearAssociations,
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'clear-attributions'],
|
||||
mutationFn: rsformsApi.clearAttributions,
|
||||
onSuccess: async data => {
|
||||
updateTimestamp(data.id, data.time_update);
|
||||
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||
|
|
@ -24,6 +24,6 @@ export const useClearAssociations = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
clearAssociations: (data: { itemID: number; data: IAssociationTargetDTO }) => mutation.mutateAsync(data)
|
||||
clearAttributions: (data: { itemID: number; data: IAttributionTargetDTO }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
@ -5,14 +5,14 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { rsformsApi } from './api';
|
||||
import { type IAssociation } from './types';
|
||||
import { type IAttribution } from './types';
|
||||
|
||||
export const useCreateAssociation = () => {
|
||||
export const useCreateAttribution = () => {
|
||||
const client = useQueryClient();
|
||||
const { updateTimestamp } = useUpdateTimestamp();
|
||||
const mutation = useMutation({
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-association'],
|
||||
mutationFn: rsformsApi.createAssociation,
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-attribution'],
|
||||
mutationFn: rsformsApi.createAttribution,
|
||||
onSuccess: async data => {
|
||||
updateTimestamp(data.id, data.time_update);
|
||||
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||
|
|
@ -24,6 +24,6 @@ export const useCreateAssociation = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
createAssociation: (data: { itemID: number; data: IAssociation }) => mutation.mutateAsync(data)
|
||||
createAttribution: (data: { itemID: number; data: IAttribution }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
@ -5,14 +5,14 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { rsformsApi } from './api';
|
||||
import { type IAssociation } from './types';
|
||||
import { type IAttribution } from './types';
|
||||
|
||||
export const useDeleteAssociation = () => {
|
||||
export const useDeleteAttribution = () => {
|
||||
const client = useQueryClient();
|
||||
const { updateTimestamp } = useUpdateTimestamp();
|
||||
const mutation = useMutation({
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-association'],
|
||||
mutationFn: rsformsApi.deleteAssociation,
|
||||
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-attribution'],
|
||||
mutationFn: rsformsApi.deleteAttribution,
|
||||
onSuccess: async data => {
|
||||
updateTimestamp(data.id, data.time_update);
|
||||
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||
|
|
@ -24,6 +24,6 @@ export const useDeleteAssociation = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
deleteAssociation: (data: { itemID: number; data: IAssociation }) => mutation.mutateAsync(data)
|
||||
deleteAttribution: (data: { itemID: number; data: IAttribution }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
@ -259,7 +259,7 @@ export function colorGraphEdge(edgeType: GraphType): string {
|
|||
return APP_COLORS.bgGreen;
|
||||
case 'definition':
|
||||
return APP_COLORS.border;
|
||||
case 'association':
|
||||
case 'attribution':
|
||||
return APP_COLORS.bgPurple;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
|
|||
/>
|
||||
<ValueStats
|
||||
id='count_nominal'
|
||||
title='Номеноиды'
|
||||
title='Номиноиды'
|
||||
icon={<IconCstNominal size='1.25rem' className={stats.count_nominal > 0 ? 'text-destructive' : undefined} />}
|
||||
value={stats.count_nominal}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import { MiniButton } from '@/components/control';
|
|||
import { Label, TextArea, TextInput } from '@/components/input';
|
||||
|
||||
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
||||
import { useClearAssociations } from '../../backend/use-clear-associations';
|
||||
import { useCreateAssociation } from '../../backend/use-create-association';
|
||||
import { useDeleteAssociation } from '../../backend/use-delete-association';
|
||||
import { useClearAttributions } from '../../backend/use-clear-attributions';
|
||||
import { useCreateAttribution } from '../../backend/use-create-attribution';
|
||||
import { useDeleteAttribution } from '../../backend/use-delete-attribution';
|
||||
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { SelectCstType } from '../../components/select-cst-type';
|
||||
|
|
@ -25,9 +25,9 @@ interface FormEditCstProps {
|
|||
}
|
||||
|
||||
export function FormEditCst({ target, schema }: FormEditCstProps) {
|
||||
const { createAssociation } = useCreateAssociation();
|
||||
const { deleteAssociation } = useDeleteAssociation();
|
||||
const { clearAssociations } = useClearAssociations();
|
||||
const { createAttribution } = useCreateAttribution();
|
||||
const { deleteAttribution } = useDeleteAttribution();
|
||||
const { clearAttributions } = useClearAttributions();
|
||||
|
||||
const {
|
||||
setValue,
|
||||
|
|
@ -44,7 +44,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(cst_type);
|
||||
const showConvention = !!convention || forceComment || isBasic;
|
||||
const associations = target.associations.map(id => schema.cstByID.get(id)!);
|
||||
const attributions = target.attributes.map(id => schema.cstByID.get(id)!);
|
||||
|
||||
function handleTypeChange(newValue: CstType) {
|
||||
setValue('item_data.cst_type', newValue);
|
||||
|
|
@ -56,28 +56,28 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
setValue('item_data.crucial', !crucial);
|
||||
}
|
||||
|
||||
function handleAddAssociation(item: IConstituenta) {
|
||||
void createAssociation({
|
||||
function handleAddAttribution(item: IConstituenta) {
|
||||
void createAttribution({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: target.id,
|
||||
associate: item.id
|
||||
attribute: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveAssociation(item: IConstituenta) {
|
||||
void deleteAssociation({
|
||||
function handleRemoveAttribution(item: IConstituenta) {
|
||||
void deleteAttribution({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: target.id,
|
||||
associate: item.id
|
||||
attribute: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleClearAssociations() {
|
||||
void clearAssociations({
|
||||
function handleClearAttributions() {
|
||||
void clearAttributions({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
target: target.id
|
||||
|
|
@ -121,15 +121,15 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
error={errors.item_data?.term_raw}
|
||||
/>
|
||||
|
||||
{target.cst_type === CstType.NOMINAL || target.associations.length > 0 ? (
|
||||
{target.cst_type === CstType.NOMINAL || target.attributes.length > 0 ? (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Label text='Ассоциируемые конституенты' />
|
||||
<Label text='Атрибутирующие конституенты' />
|
||||
<SelectMultiConstituenta
|
||||
items={schema.items.filter(item => item.id !== target.id)}
|
||||
value={associations}
|
||||
onAdd={handleAddAssociation}
|
||||
onClear={handleClearAssociations}
|
||||
onRemove={handleRemoveAssociation}
|
||||
value={attributions}
|
||||
onAdd={handleAddAttribution}
|
||||
onClear={handleClearAttributions}
|
||||
onRemove={handleRemoveAttribution}
|
||||
placeholder={'Выберите конституенты'}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { type GraphColoring, type GraphType } from './stores/term-graph';
|
|||
|
||||
// --- Records for label/describe functions ---
|
||||
const labelCstTypeRecord: Record<CstType, string> = {
|
||||
[CstType.NOMINAL]: 'Номеноид',
|
||||
[CstType.NOMINAL]: 'Номиноид',
|
||||
[CstType.BASE]: 'Базисное множество',
|
||||
[CstType.CONSTANT]: 'Константное множество',
|
||||
[CstType.STRUCTURED]: 'Родовая структура',
|
||||
|
|
@ -60,7 +60,7 @@ const labelColoringRecord: Record<GraphColoring, string> = {
|
|||
const labelGraphTypeRecord: Record<GraphType, string> = {
|
||||
full: 'Связи: Все',
|
||||
definition: 'Связи: Определения',
|
||||
association: 'Связи: Ассоциации'
|
||||
attribution: 'Связи: Атрибутирование'
|
||||
};
|
||||
|
||||
const labelCstMatchModeRecord: Record<CstMatchMode, string> = {
|
||||
|
|
|
|||
|
|
@ -59,15 +59,15 @@ export function applyLayout(nodes: Node<TGNodeState>[], edges: Edge[], subLabels
|
|||
|
||||
export function inferEdgeType(schema: IRSForm, source: number, target: number): GraphType | null {
|
||||
const isDefinition = schema.graph.hasEdge(source, target);
|
||||
const isAssociation = schema.association_graph.hasEdge(source, target);
|
||||
if (!isDefinition && !isAssociation) {
|
||||
const isAttribution = schema.attribution_graph.hasEdge(source, target);
|
||||
if (!isDefinition && !isAttribution) {
|
||||
return null;
|
||||
} else if (isDefinition && isAssociation) {
|
||||
} else if (isDefinition && isAttribution) {
|
||||
return 'full';
|
||||
} else if (isDefinition) {
|
||||
return 'definition';
|
||||
} else {
|
||||
return 'association';
|
||||
return 'attribution';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,8 +75,8 @@ export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams,
|
|||
const filtered =
|
||||
params.graphType === 'full'
|
||||
? schema.full_graph.clone()
|
||||
: params.graphType === 'association'
|
||||
? schema.association_graph.clone()
|
||||
: params.graphType === 'attribution'
|
||||
? schema.attribution_graph.clone()
|
||||
: schema.graph.clone();
|
||||
const allowedTypes: CstType[] = (() => {
|
||||
const result: CstType[] = [];
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
|
||||
import { type Graph } from '@/models/graph';
|
||||
|
||||
import { CstType, type IAssociation, type ParsingStatus, type ValueClass } from '../backend/types';
|
||||
import { CstType, type IAttribution, type ParsingStatus, type ValueClass } from '../backend/types';
|
||||
|
||||
import { type IArgumentInfo } from './rslang';
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export interface IConstituenta {
|
|||
term_raw: string;
|
||||
term_resolved: string;
|
||||
term_forms: TermForm[];
|
||||
associations: number[];
|
||||
attributes: number[];
|
||||
|
||||
parse?: {
|
||||
status: ParsingStatus;
|
||||
|
|
@ -142,12 +142,12 @@ export interface IRSForm extends ILibraryItemData {
|
|||
|
||||
items: IConstituenta[];
|
||||
inheritance: IInheritanceInfo[];
|
||||
association: IAssociation[];
|
||||
attribution: IAttribution[];
|
||||
oss: ILibraryItemReference[];
|
||||
|
||||
stats: IRSFormStats;
|
||||
graph: Graph;
|
||||
association_graph: Graph;
|
||||
attribution_graph: Graph;
|
||||
full_graph: Graph;
|
||||
cstByAlias: Map<string, IConstituenta>;
|
||||
cstByID: Map<number, IConstituenta>;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import {
|
|||
ParsingStatus,
|
||||
schemaUpdateConstituenta
|
||||
} from '../../../backend/types';
|
||||
import { useClearAssociations } from '../../../backend/use-clear-associations';
|
||||
import { useCreateAssociation } from '../../../backend/use-create-association';
|
||||
import { useDeleteAssociation } from '../../../backend/use-delete-association';
|
||||
import { useClearAttributions } from '../../../backend/use-clear-attributions';
|
||||
import { useCreateAttribution } from '../../../backend/use-create-attribution';
|
||||
import { useDeleteAttribution } from '../../../backend/use-delete-attribution';
|
||||
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
|
||||
import { useUpdateConstituenta } from '../../../backend/use-update-constituenta';
|
||||
import { useUpdateCrucial } from '../../../backend/use-update-crucial';
|
||||
|
|
@ -60,9 +60,9 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
|
||||
const { updateConstituenta } = useUpdateConstituenta();
|
||||
const { updateCrucial } = useUpdateCrucial();
|
||||
const { createAssociation } = useCreateAssociation();
|
||||
const { deleteAssociation } = useDeleteAssociation();
|
||||
const { clearAssociations } = useClearAssociations();
|
||||
const { createAttribution } = useCreateAttribution();
|
||||
const { deleteAttribution } = useDeleteAttribution();
|
||||
const { clearAttributions } = useClearAttributions();
|
||||
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
||||
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
||||
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
||||
|
|
@ -113,8 +113,8 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
);
|
||||
|
||||
const associations = useMemo(
|
||||
() => activeCst.associations.map(id => schema.cstByID.get(id)!),
|
||||
[activeCst.associations, schema.cstByID]
|
||||
() => activeCst.attributes.map(id => schema.cstByID.get(id)!),
|
||||
[activeCst.attributes, schema.cstByID]
|
||||
);
|
||||
|
||||
const isBasic = isBasicConcept(activeCst.cst_type) || activeCst.cst_type === CstType.NOMINAL;
|
||||
|
|
@ -196,28 +196,28 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
});
|
||||
}
|
||||
|
||||
function handleAddAssociation(item: IConstituenta) {
|
||||
void createAssociation({
|
||||
function handleAddAttribution(item: IConstituenta) {
|
||||
void createAttribution({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: activeCst.id,
|
||||
associate: item.id
|
||||
attribute: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveAssociation(item: IConstituenta) {
|
||||
void deleteAssociation({
|
||||
function handleRemoveAttribution(item: IConstituenta) {
|
||||
void deleteAttribution({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: activeCst.id,
|
||||
associate: item.id
|
||||
attribute: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleClearAssociations() {
|
||||
void clearAssociations({
|
||||
function handleClearAttributions() {
|
||||
void clearAttributions({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
target: activeCst.id
|
||||
|
|
@ -279,15 +279,15 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
)}
|
||||
/>
|
||||
|
||||
{activeCst.cst_type === CstType.NOMINAL || activeCst.associations.length > 0 ? (
|
||||
{activeCst.cst_type === CstType.NOMINAL || activeCst.attributes.length > 0 ? (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Label text='Ассоциируемые конституенты' />
|
||||
<Label text='Атрибутирующие конституенты' />
|
||||
<SelectMultiConstituenta
|
||||
items={schema.items.filter(item => item.id !== activeCst.id)}
|
||||
value={associations}
|
||||
onAdd={handleAddAssociation}
|
||||
onClear={handleClearAssociations}
|
||||
onRemove={handleRemoveAssociation}
|
||||
onAdd={handleAddAttribution}
|
||||
onClear={handleClearAttributions}
|
||||
onRemove={handleRemoveAttribution}
|
||||
disabled={disabled || isModified}
|
||||
placeholder={disabled ? '' : 'Выберите конституенты'}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { persist } from 'zustand/middleware';
|
|||
import { CstType } from '../backend/types';
|
||||
|
||||
export const graphColorings = ['none', 'status', 'type', 'schemas'] as const;
|
||||
export const graphTypes = ['full', 'association', 'definition'] as const;
|
||||
export const graphTypes = ['full', 'attribution', 'definition'] as const;
|
||||
|
||||
/** Represents graph node coloring scheme. */
|
||||
export type GraphColoring = (typeof graphColorings)[number];
|
||||
|
|
@ -104,8 +104,8 @@ export const useTermGraphStore = create<TermGraphStore>()(
|
|||
...state.filter,
|
||||
graphType:
|
||||
state.filter.graphType === 'full'
|
||||
? 'association'
|
||||
: state.filter.graphType === 'association'
|
||||
? 'attribution'
|
||||
: state.filter.graphType === 'attribution'
|
||||
? 'definition'
|
||||
: 'full'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
@utility cc-hover-pulse {
|
||||
&:hover:not(:disabled) {
|
||||
transform-origin: center;
|
||||
animation: pulse-scale var(--duration-cycle) infinite;
|
||||
animation-delay: var(--duration-select);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user