From 1555a1bf924e547a3ef751c63ddfdb002acc9cdd Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Thu, 21 Aug 2025 21:26:40 +0300 Subject: [PATCH] F: Rename Nominal relation and improve UI --- .vscode/settings.json | 11 +- .../apps/oss/models/OperationSchemaCached.py | 14 +- .../apps/oss/models/PropagationEngine.py | 54 +++--- .../apps/oss/models/PropagationFacade.py | 16 +- rsconcept/backend/apps/rsform/admin.py | 8 +- .../0006_attribution_delete_association.py | 30 +++ .../backend/apps/rsform/models/Association.py | 28 --- .../backend/apps/rsform/models/Attribution.py | 28 +++ .../backend/apps/rsform/models/__init__.py | 2 +- .../apps/rsform/serializers/__init__.py | 4 +- .../apps/rsform/serializers/data_access.py | 64 +++---- .../apps/rsform/tests/s_models/__init__.py | 2 +- .../rsform/tests/s_models/t_Association.py | 175 ------------------ .../rsform/tests/s_models/t_Attribution.py | 175 ++++++++++++++++++ .../apps/rsform/tests/s_views/__init__.py | 2 +- .../{t_associations.py => t_attribtuions.py} | 64 +++---- .../backend/apps/rsform/views/rsforms.py | 56 +++--- .../help/items/cc/help-concept-relations.tsx | 2 +- .../features/help/items/help-thesaurus.tsx | 2 +- rsconcept/frontend/src/features/oss/labels.ts | 2 +- .../src/features/rsform/backend/api.ts | 22 +-- .../features/rsform/backend/rsform-loader.ts | 12 +- .../src/features/rsform/backend/types.ts | 14 +- ...ociations.ts => use-clear-attributions.ts} | 10 +- ...sociation.ts => use-create-attribution.ts} | 10 +- ...sociation.ts => use-delete-attribution.ts} | 10 +- .../frontend/src/features/rsform/colors.ts | 2 +- .../rsform/components/rsform-stats.tsx | 2 +- .../dialogs/dlg-edit-cst/form-edit-cst.tsx | 42 ++--- .../frontend/src/features/rsform/labels.ts | 4 +- .../src/features/rsform/models/graph-api.ts | 12 +- .../src/features/rsform/models/rsform.ts | 8 +- .../editor-constituenta/form-constituenta.tsx | 42 ++--- .../src/features/rsform/stores/term-graph.ts | 6 +- rsconcept/frontend/src/styling/utilities.css | 1 + 35 files changed, 485 insertions(+), 451 deletions(-) create mode 100644 rsconcept/backend/apps/rsform/migrations/0006_attribution_delete_association.py delete mode 100644 rsconcept/backend/apps/rsform/models/Association.py create mode 100644 rsconcept/backend/apps/rsform/models/Attribution.py delete mode 100644 rsconcept/backend/apps/rsform/tests/s_models/t_Association.py create mode 100644 rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py rename rsconcept/backend/apps/rsform/tests/s_views/{t_associations.py => t_attribtuions.py} (59%) rename rsconcept/frontend/src/features/rsform/backend/{use-clear-associations.ts => use-clear-attributions.ts} (80%) rename rsconcept/frontend/src/features/rsform/backend/{use-create-association.ts => use-create-attribution.ts} (80%) rename rsconcept/frontend/src/features/rsform/backend/{use-delete-association.ts => use-delete-attribution.ts} (80%) diff --git a/.vscode/settings.json b/.vscode/settings.json index cbc32314..288fdbeb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -184,6 +184,9 @@ "Айзенштат", "Акименков", "Астрина", + "Атрибутирование", + "Атрибутирующая", + "Атрибутирующие", "Ашихмин", "Биективная", "биективной", @@ -222,10 +225,10 @@ "неинтерпретируемый", "неитерируемого", "Никанорова", - "Номеноид", - "номеноида", - "номеноидом", - "Номеноиды", + "Номиноид", + "номиноида", + "номиноидом", + "Номиноиды", "операционализации", "операционализированных", "Оргтеор", diff --git a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py index 66ea0687..a6bcee83 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py +++ b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py @@ -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. ''' diff --git a/rsconcept/backend/apps/oss/models/PropagationEngine.py b/rsconcept/backend/apps/oss/models/PropagationEngine.py index c4680ae8..cefd5b5f 100644 --- a/rsconcept/backend/apps/oss/models/PropagationEngine.py +++ b/rsconcept/backend/apps/oss/models/PropagationEngine.py @@ -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. ''' diff --git a/rsconcept/backend/apps/oss/models/PropagationFacade.py b/rsconcept/backend/apps/oss/models/PropagationFacade.py index 68520781..f14cefc6 100644 --- a/rsconcept/backend/apps/oss/models/PropagationFacade.py +++ b/rsconcept/backend/apps/oss/models/PropagationFacade.py @@ -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) diff --git a/rsconcept/backend/apps/rsform/admin.py b/rsconcept/backend/apps/rsform/admin.py index b0c0c09f..43d7fb81 100644 --- a/rsconcept/backend/apps/rsform/admin.py +++ b/rsconcept/backend/apps/rsform/admin.py @@ -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'] diff --git a/rsconcept/backend/apps/rsform/migrations/0006_attribution_delete_association.py b/rsconcept/backend/apps/rsform/migrations/0006_attribution_delete_association.py new file mode 100644 index 00000000..ba753a66 --- /dev/null +++ b/rsconcept/backend/apps/rsform/migrations/0006_attribution_delete_association.py @@ -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', + ), + ] diff --git a/rsconcept/backend/apps/rsform/models/Association.py b/rsconcept/backend/apps/rsform/models/Association.py deleted file mode 100644 index f813d4bd..00000000 --- a/rsconcept/backend/apps/rsform/models/Association.py +++ /dev/null @@ -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}' diff --git a/rsconcept/backend/apps/rsform/models/Attribution.py b/rsconcept/backend/apps/rsform/models/Attribution.py new file mode 100644 index 00000000..3232cd19 --- /dev/null +++ b/rsconcept/backend/apps/rsform/models/Attribution.py @@ -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}' diff --git a/rsconcept/backend/apps/rsform/models/__init__.py b/rsconcept/backend/apps/rsform/models/__init__.py index 25c20890..0d5afb28 100644 --- a/rsconcept/backend/apps/rsform/models/__init__.py +++ b/rsconcept/backend/apps/rsform/models/__init__.py @@ -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 diff --git a/rsconcept/backend/apps/rsform/serializers/__init__.py b/rsconcept/backend/apps/rsform/serializers/__init__.py index fe2ab3e9..83875fca 100644 --- a/rsconcept/backend/apps/rsform/serializers/__init__.py +++ b/rsconcept/backend/apps/rsform/serializers/__init__.py @@ -12,8 +12,8 @@ from .basics import ( WordFormSerializer ) from .data_access import ( - AssociationCreateSerializer, - AssociationDataSerializer, + AttributionCreateSerializer, + AttributionDataSerializer, CrucialUpdateSerializer, CstCreateSerializer, CstInfoSerializer, diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index f75979d5..ecef5d76 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -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): diff --git a/rsconcept/backend/apps/rsform/tests/s_models/__init__.py b/rsconcept/backend/apps/rsform/tests/s_models/__init__.py index d90d7046..e18bd50d 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/__init__.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/__init__.py @@ -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 * diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_Association.py b/rsconcept/backend/apps/rsform/tests/s_models/t_Association.py deleted file mode 100644 index 69acc3bf..00000000 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_Association.py +++ /dev/null @@ -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) diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py b/rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py new file mode 100644 index 00000000..3034e0e0 --- /dev/null +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_Attribution.py @@ -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) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/__init__.py b/rsconcept/backend/apps/rsform/tests/s_views/__init__.py index 151c328f..4edf72e3 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/__init__.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/__init__.py @@ -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 * diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_associations.py b/rsconcept/backend/apps/rsform/tests/s_views/t_attribtuions.py similarity index 59% rename from rsconcept/backend/apps/rsform/tests/s_views/t_associations.py rename to rsconcept/backend/apps/rsform/tests/s_views/t_attribtuions.py index c2151bd0..50b04fbb 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_associations.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_attribtuions.py @@ -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) diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index efcd6605..52406a5a 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -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( diff --git a/rsconcept/frontend/src/features/help/items/cc/help-concept-relations.tsx b/rsconcept/frontend/src/features/help/items/cc/help-concept-relations.tsx index f863bcd2..3ac5b259 100644 --- a/rsconcept/frontend/src/features/help/items/cc/help-concept-relations.tsx +++ b/rsconcept/frontend/src/features/help/items/cc/help-concept-relations.tsx @@ -6,7 +6,7 @@ export function HelpConceptRelations() {

Связи между конституентами

- Наиболее общей связью между конституентами является ассоциация, устанавливаемая между номеноидом и относимыми к + Наиболее общей связью между конституентами является ассоциация, устанавливаемая между номиноидом и относимыми к нему другими конституентами. Такая связь задается до установления точных определений и применяется для предварительной фиксации групп связанных конституент.

diff --git a/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx b/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx index bc30cb8a..15ff3e46 100644 --- a/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx +++ b/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx @@ -121,7 +121,7 @@ export function HelpThesaurus() { Типы конституент
  • - {'\u2009'}Номеноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной + {'\u2009'}Номиноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной группировки конституент и предварительной фиксации содержательных отношений.
  • diff --git a/rsconcept/frontend/src/features/oss/labels.ts b/rsconcept/frontend/src/features/oss/labels.ts index dee55332..9aea617c 100644 --- a/rsconcept/frontend/src/features/oss/labels.ts +++ b/rsconcept/frontend/src/features/oss/labels.ts @@ -43,7 +43,7 @@ export function describeSubstitutionError(error: RO ${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: diff --git a/rsconcept/frontend/src/features/rsform/backend/api.ts b/rsconcept/frontend/src/features/rsform/backend/api.ts index 91641448..8bc91a44 100644 --- a/rsconcept/frontend/src/features/rsform/backend/api.ts +++ b/rsconcept/frontend/src/features/rsform/backend/api.ts @@ -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({ + createAttribution: ({ itemID, data }: { itemID: number; data: IAttribution }) => + axiosPost({ 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({ + deleteAttribution: ({ itemID, data }: { itemID: number; data: IAttribution }) => + axiosPatch({ 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({ + clearAttributions: ({ itemID, data }: { itemID: number; data: IAttributionTargetDTO }) => + axiosPatch({ schema: schemaRSForm, - endpoint: `/api/rsforms/${itemID}/clear-associations`, + endpoint: `/api/rsforms/${itemID}/clear-attributions`, request: { data: data, successMessage: infoMsg.changesSaved diff --git a/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts b/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts index ceababd3..b0de25e0 100644 --- a/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts +++ b/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts @@ -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); }); } diff --git a/rsconcept/frontend/src/features/rsform/backend/types.ts b/rsconcept/frontend/src/features/rsform/backend/types.ts index 9e6f8ada..3c85dff3 100644 --- a/rsconcept/frontend/src/features/rsform/backend/types.ts +++ b/rsconcept/frontend/src/features/rsform/backend/types.ts @@ -94,11 +94,11 @@ export interface ICheckConstituentaDTO { /** Represents data, used in merging multiple {@link IConstituenta}. */ export type ISubstitutionsDTO = z.infer; -/** Represents data for creating or deleting an association. */ -export type IAssociation = z.infer; +/** Represents data for creating or deleting an Attribution. */ +export type IAttribution = z.infer; /** Represents data for clearing all associations for a target constituenta. */ -export type IAssociationTargetDTO = z.infer; +export type IAttributionTargetDTO = z.infer; /** 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() }); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-clear-associations.ts b/rsconcept/frontend/src/features/rsform/backend/use-clear-attributions.ts similarity index 80% rename from rsconcept/frontend/src/features/rsform/backend/use-clear-associations.ts rename to rsconcept/frontend/src/features/rsform/backend/use-clear-attributions.ts index a572c7e8..07aa4a99 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-clear-associations.ts +++ b/rsconcept/frontend/src/features/rsform/backend/use-clear-attributions.ts @@ -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) }; }; diff --git a/rsconcept/frontend/src/features/rsform/backend/use-create-association.ts b/rsconcept/frontend/src/features/rsform/backend/use-create-attribution.ts similarity index 80% rename from rsconcept/frontend/src/features/rsform/backend/use-create-association.ts rename to rsconcept/frontend/src/features/rsform/backend/use-create-attribution.ts index fff2942b..54cb9768 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-create-association.ts +++ b/rsconcept/frontend/src/features/rsform/backend/use-create-attribution.ts @@ -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) }; }; diff --git a/rsconcept/frontend/src/features/rsform/backend/use-delete-association.ts b/rsconcept/frontend/src/features/rsform/backend/use-delete-attribution.ts similarity index 80% rename from rsconcept/frontend/src/features/rsform/backend/use-delete-association.ts rename to rsconcept/frontend/src/features/rsform/backend/use-delete-attribution.ts index 855d4ca5..e2d4cd1c 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-delete-association.ts +++ b/rsconcept/frontend/src/features/rsform/backend/use-delete-attribution.ts @@ -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) }; }; diff --git a/rsconcept/frontend/src/features/rsform/colors.ts b/rsconcept/frontend/src/features/rsform/colors.ts index 596855c1..bbf9a3ca 100644 --- a/rsconcept/frontend/src/features/rsform/colors.ts +++ b/rsconcept/frontend/src/features/rsform/colors.ts @@ -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; } } diff --git a/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx b/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx index ca3ce6ae..e686713b 100644 --- a/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx +++ b/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx @@ -68,7 +68,7 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) { /> 0 ? 'text-destructive' : undefined} />} value={stats.count_nominal} /> diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx index 44dc37f7..da852ad3 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx @@ -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 ? (
    -
    diff --git a/rsconcept/frontend/src/features/rsform/labels.ts b/rsconcept/frontend/src/features/rsform/labels.ts index bdcced4d..45466de6 100644 --- a/rsconcept/frontend/src/features/rsform/labels.ts +++ b/rsconcept/frontend/src/features/rsform/labels.ts @@ -18,7 +18,7 @@ import { type GraphColoring, type GraphType } from './stores/term-graph'; // --- Records for label/describe functions --- const labelCstTypeRecord: Record = { - [CstType.NOMINAL]: 'Номеноид', + [CstType.NOMINAL]: 'Номиноид', [CstType.BASE]: 'Базисное множество', [CstType.CONSTANT]: 'Константное множество', [CstType.STRUCTURED]: 'Родовая структура', @@ -60,7 +60,7 @@ const labelColoringRecord: Record = { const labelGraphTypeRecord: Record = { full: 'Связи: Все', definition: 'Связи: Определения', - association: 'Связи: Ассоциации' + attribution: 'Связи: Атрибутирование' }; const labelCstMatchModeRecord: Record = { diff --git a/rsconcept/frontend/src/features/rsform/models/graph-api.ts b/rsconcept/frontend/src/features/rsform/models/graph-api.ts index 9659d617..450000eb 100644 --- a/rsconcept/frontend/src/features/rsform/models/graph-api.ts +++ b/rsconcept/frontend/src/features/rsform/models/graph-api.ts @@ -59,15 +59,15 @@ export function applyLayout(nodes: Node[], 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[] = []; diff --git a/rsconcept/frontend/src/features/rsform/models/rsform.ts b/rsconcept/frontend/src/features/rsform/models/rsform.ts index e6ea6927..9dbfe9b2 100644 --- a/rsconcept/frontend/src/features/rsform/models/rsform.ts +++ b/rsconcept/frontend/src/features/rsform/models/rsform.ts @@ -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; cstByID: Map; diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx index 00c02a05..da0f366f 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx @@ -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 ? (
    -