From 600b0c01efd91600343222b24aa2432b7f42e81f Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Thu, 1 Aug 2024 00:36:06 +0300 Subject: [PATCH] F: Improve OSS <-> RSForm UI --- .../apps/library/serializers/__init__.py | 1 + .../apps/library/serializers/data_access.py | 8 +++ .../apps/oss/serializers/data_access.py | 8 +-- .../apps/rsform/serializers/data_access.py | 49 +++++++++++++----- .../apps/rsform/tests/s_views/t_rsforms.py | 2 + rsconcept/frontend/src/components/Icons.tsx | 1 + .../src/components/info/BadgeConstituenta.tsx | 1 + .../src/components/info/InfoConstituenta.tsx | 5 +- .../components/select/PickSubstitutions.tsx | 2 - rsconcept/frontend/src/models/RSFormLoader.ts | 5 ++ rsconcept/frontend/src/models/library.ts | 11 ++-- rsconcept/frontend/src/models/rsform.ts | 23 +++++---- rsconcept/frontend/src/models/rsformAPI.ts | 2 + .../OssPage/EditorOssGraph/InputNode.tsx | 2 +- .../OssPage/EditorOssGraph/OperationNode.tsx | 2 +- .../EditorConstituenta/FormConstituenta.tsx | 33 ++++++++---- .../EditorRSFormCard/FormRSForm.tsx | 51 +++++++++++++++---- .../EditorRSFormCard/RSFormStats.tsx | 11 ++-- .../src/pages/RSFormPage/RSEditContext.tsx | 24 ++++++++- .../ViewConstituents/ConstituentsSearch.tsx | 2 +- rsconcept/frontend/src/utils/constants.ts | 1 + 21 files changed, 185 insertions(+), 59 deletions(-) diff --git a/rsconcept/backend/apps/library/serializers/__init__.py b/rsconcept/backend/apps/library/serializers/__init__.py index 3347a3cf..37a5df78 100644 --- a/rsconcept/backend/apps/library/serializers/__init__.py +++ b/rsconcept/backend/apps/library/serializers/__init__.py @@ -5,6 +5,7 @@ from .data_access import ( LibraryItemBaseSerializer, LibraryItemCloneSerializer, LibraryItemDetailsSerializer, + LibraryItemReferenceSerializer, LibraryItemSerializer, UsersListSerializer, UserTargetSerializer, diff --git a/rsconcept/backend/apps/library/serializers/data_access.py b/rsconcept/backend/apps/library/serializers/data_access.py index b91c37fa..fc43d7a6 100644 --- a/rsconcept/backend/apps/library/serializers/data_access.py +++ b/rsconcept/backend/apps/library/serializers/data_access.py @@ -17,6 +17,14 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer): read_only_fields = ('id',) +class LibraryItemReferenceSerializer(serializers.ModelSerializer): + ''' Serializer: reference to LibraryItem. ''' + class Meta: + ''' serializer metadata. ''' + model = LibraryItem + fields = 'id', 'alias' + + class LibraryItemSerializer(serializers.ModelSerializer): ''' Serializer: LibraryItem entry limited access. ''' class Meta: diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 97148598..e3935530 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -84,7 +84,7 @@ class OperationUpdateSerializer(serializers.Serializer): oss = cast(LibraryItem, self.context['oss']) for operation in attrs['arguments']: - if operation.oss != oss: + if operation.oss_id != oss.pk: raise serializers.ValidationError({ 'arguments': msg.operationNotInOSS(oss.title) }) @@ -110,7 +110,7 @@ class OperationUpdateSerializer(serializers.Serializer): raise serializers.ValidationError({ f'{original_cst.pk}': msg.substituteDouble(original_cst.alias) }) - if original_cst.schema == substitution_cst.schema: + if original_cst.schema_id == substitution_cst.schema_id: raise serializers.ValidationError({ 'alias': msg.substituteTrivial(original_cst.alias) }) @@ -131,7 +131,7 @@ class OperationTargetSerializer(serializers.Serializer): def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) operation = cast(Operation, attrs['target']) - if oss and operation.oss != oss: + if oss and operation.oss_id != oss.pk: raise serializers.ValidationError({ 'target': msg.operationNotInOSS(oss.title) }) @@ -155,7 +155,7 @@ class SetOperationInputSerializer(serializers.Serializer): def validate(self, attrs): oss = cast(LibraryItem, self.context['oss']) operation = cast(Operation, attrs['target']) - if oss and operation.oss != oss: + if oss and operation.oss_id != oss.pk: raise serializers.ValidationError({ 'target': msg.operationNotInOSS(oss.title) }) diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index e768c8e6..77b4e0cb 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -4,11 +4,17 @@ from typing import Optional, cast from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.db import transaction +from django.db.models import Q from rest_framework import serializers from rest_framework.serializers import PrimaryKeyRelatedField as PKField from apps.library.models import LibraryItem -from apps.library.serializers import LibraryItemBaseSerializer, LibraryItemDetailsSerializer +from apps.library.serializers import ( + LibraryItemBaseSerializer, + LibraryItemDetailsSerializer, + LibraryItemReferenceSerializer +) +from apps.oss.models import Inheritance from shared import messages as msg from ..models import Constituenta, CstType, RSForm @@ -90,6 +96,12 @@ class RSFormSerializer(serializers.ModelSerializer): items = serializers.ListField( child=CstSerializer() ) + inheritance = serializers.ListField( + child=serializers.ListField(child=serializers.IntegerField()) + ) + oss = serializers.ListField( + child=LibraryItemReferenceSerializer() + ) class Meta: ''' serializer metadata. ''' @@ -101,6 +113,15 @@ class RSFormSerializer(serializers.ModelSerializer): result['items'] = [] for cst in RSForm(instance).constituents().order_by('order'): result['items'].append(CstSerializer(cst).data) + result['inheritance'] = [] + for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)): + result['inheritance'].append([link.child.pk, link.parent.pk]) + result['oss'] = [] + for oss in LibraryItem.objects.filter(items__result=instance).only('alias'): + result['oss'].append({ + 'id': oss.pk, + 'alias': oss.alias + }) return result def to_versioned_data(self) -> dict: @@ -109,6 +130,8 @@ class RSFormSerializer(serializers.ModelSerializer): del result['versions'] del result['subscribers'] del result['editors'] + del result['inheritance'] + del result['oss'] del result['owner'] del result['visible'] @@ -210,7 +233,7 @@ class CstTargetSerializer(serializers.Serializer): def validate(self, attrs): schema = cast(LibraryItem, self.context['schema']) cst = cast(Constituenta, attrs['target']) - if schema and cst.schema != schema: + if schema and cst.schema_id != schema.pk: raise serializers.ValidationError({ f'{cst.pk}': msg.constituentaNotInRSform(schema.title) }) @@ -224,7 +247,7 @@ class CstTargetSerializer(serializers.Serializer): class CstRenameSerializer(serializers.Serializer): ''' Serializer: Constituenta renaming. ''' - target = PKField(many=False, queryset=Constituenta.objects.all()) + target = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema')) alias = serializers.CharField() cst_type = serializers.CharField() @@ -232,7 +255,7 @@ class CstRenameSerializer(serializers.Serializer): attrs = super().validate(attrs) schema = cast(LibraryItem, self.context['schema']) cst = cast(Constituenta, attrs['target']) - if cst.schema != schema: + if cst.schema_id != schema.pk: raise serializers.ValidationError({ f'{cst.pk}': msg.constituentaNotInRSform(schema.title) }) @@ -258,7 +281,7 @@ class CstListSerializer(serializers.Serializer): return attrs for item in attrs['items']: - if item.schema != schema: + if item.schema_id != schema.pk: raise serializers.ValidationError({ f'{item.pk}': msg.constituentaNotInRSform(schema.title) }) @@ -272,8 +295,8 @@ class CstMoveSerializer(CstListSerializer): class SubstitutionSerializerBase(serializers.Serializer): ''' Serializer: Basic substitution. ''' - original = PKField(many=False, queryset=Constituenta.objects.all()) - substitution = PKField(many=False, queryset=Constituenta.objects.all()) + original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema')) + substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema')) class CstSubstituteSerializer(serializers.Serializer): @@ -297,11 +320,11 @@ class CstSubstituteSerializer(serializers.Serializer): raise serializers.ValidationError({ 'alias': msg.substituteTrivial(original_cst.alias) }) - if original_cst.schema != schema: + if original_cst.schema_id != schema.pk: raise serializers.ValidationError({ 'original': msg.constituentaNotInRSform(schema.title) }) - if substitution_cst.schema != schema: + if substitution_cst.schema_id != schema.pk: raise serializers.ValidationError({ 'substitution': msg.constituentaNotInRSform(schema.title) }) @@ -329,7 +352,7 @@ class InlineSynthesisSerializer(serializers.Serializer): }) constituents = cast(list[Constituenta], attrs['items']) for cst in constituents: - if cst.schema != schema_in: + if cst.schema_id != schema_in.pk: raise serializers.ValidationError({ f'{cst.pk}': msg.constituentaNotInRSform(schema_in.title) }) @@ -337,12 +360,12 @@ class InlineSynthesisSerializer(serializers.Serializer): for item in attrs['substitutions']: original_cst = cast(Constituenta, item['original']) substitution_cst = cast(Constituenta, item['substitution']) - if original_cst.schema == schema_in: + if original_cst.schema_id == schema_in.pk: if original_cst not in constituents: raise serializers.ValidationError({ f'{original_cst.pk}': msg.substitutionNotInList() }) - if substitution_cst.schema != schema_out: + if substitution_cst.schema_id != schema_out.pk: raise serializers.ValidationError({ f'{substitution_cst.pk}': msg.constituentaNotInRSform(schema_out.title) }) @@ -351,7 +374,7 @@ class InlineSynthesisSerializer(serializers.Serializer): raise serializers.ValidationError({ f'{substitution_cst.pk}': msg.substitutionNotInList() }) - if original_cst.schema != schema_out: + if original_cst.schema_id != schema_out.pk: raise serializers.ValidationError({ f'{original_cst.pk}': msg.constituentaNotInRSform(schema_out.title) }) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py index e8e8ecd5..152d497b 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py @@ -102,6 +102,8 @@ class TestRSFormViewset(EndpointTester): self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved) self.assertEqual(response.data['subscribers'], [self.user.pk]) self.assertEqual(response.data['editors'], []) + self.assertEqual(response.data['inheritance'], []) + self.assertEqual(response.data['oss'], []) self.executeOK(item=self.unowned_id) self.executeForbidden(item=self.private_id) diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index 6dac71f8..c9a507d1 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -62,6 +62,7 @@ export { VscLibrary as IconLibrary } from 'react-icons/vsc'; export { IoLibrary as IconLibrary2 } from 'react-icons/io5'; export { BiDiamond as IconTemplates } from 'react-icons/bi'; export { GiHoneycomb as IconOSS } from 'react-icons/gi'; +export { LuBaby as IconChild } from 'react-icons/lu'; export { RiHexagonLine as IconRSForm } from 'react-icons/ri'; export { LuArchive as IconArchive } from 'react-icons/lu'; export { LuDatabase as IconDatabase } from 'react-icons/lu'; diff --git a/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx b/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx index 207868f6..f70ab2fd 100644 --- a/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx +++ b/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx @@ -20,6 +20,7 @@ function BadgeConstituenta({ value, prefixID, theme }: BadgeConstituentaProps) { 'min-w-[3.1rem] max-w-[3.1rem]', // prettier: split lines 'px-1', 'border rounded-md', + value.is_inherited && 'border-dashed', 'text-center font-medium whitespace-nowrap' )} style={{ diff --git a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx index e59362ee..a192c1d2 100644 --- a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx +++ b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx @@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div { function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) { return (
Термин:
diff --git a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
index 706dc334..46fa9a2d 100644
--- a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
+++ b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
@@ -102,8 +102,6 @@ function PickSubstitutions({
};
const toDelete = substitutions.map(item => item.original);
const replacements = substitutions.map(item => item.substitution);
- console.log(toDelete, replacements);
- console.log(newSubstitution);
if (
toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) ||
diff --git a/rsconcept/frontend/src/models/RSFormLoader.ts b/rsconcept/frontend/src/models/RSFormLoader.ts
index ad27b56f..67a3f5f1 100644
--- a/rsconcept/frontend/src/models/RSFormLoader.ts
+++ b/rsconcept/frontend/src/models/RSFormLoader.ts
@@ -59,6 +59,8 @@ export class RSFormLoader {
}
private inferCstAttributes() {
+ const inherit_children = new Set(this.schema.inheritance.map(item => item[0]));
+ const inherit_parents = new Set(this.schema.inheritance.map(item => item[1]));
this.graph.topologicalOrder().forEach(cstID => {
const cst = this.cstByID.get(cstID)!;
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
@@ -66,6 +68,8 @@ export class RSFormLoader {
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.children = [];
cst.children_alias = [];
+ cst.is_inherited = inherit_children.has(cst.id);
+ cst.is_inherited_parent = inherit_parents.has(cst.id);
cst.is_simple_expression = this.inferSimpleExpression(cst);
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
return;
@@ -165,6 +169,7 @@ export class RSFormLoader {
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
0
),
+ count_inherited: items.reduce((sum, cst) => sum + ((cst as IConstituenta).is_inherited ? 1 : 0), 0),
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),
diff --git a/rsconcept/frontend/src/models/library.ts b/rsconcept/frontend/src/models/library.ts
index 6ad1abd9..975689db 100644
--- a/rsconcept/frontend/src/models/library.ts
+++ b/rsconcept/frontend/src/models/library.ts
@@ -75,7 +75,7 @@ export interface ILibraryItem {
}
/**
- * Represents library item constant data loaded for both OSS and RSForm.
+ * Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
*/
export interface ILibraryItemData extends ILibraryItem {
subscribers: UserID[];
@@ -83,7 +83,12 @@ export interface ILibraryItemData extends ILibraryItem {
}
/**
- * Represents library item extended data with versions.
+ * Represents {@link ILibraryItem} minimal reference data.
+ */
+export interface ILibraryItemReference extends Pick
в операционной схеме синтеза'
+ />
+