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