diff --git a/rsconcept/backend/apps/oss/migrations/0003_operation_sync_text_alter_operation_result.py b/rsconcept/backend/apps/oss/migrations/0003_operation_sync_text_alter_operation_result.py new file mode 100644 index 00000000..0bb63a04 --- /dev/null +++ b/rsconcept/backend/apps/oss/migrations/0003_operation_sync_text_alter_operation_result.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.7 on 2024-07-24 18:12 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oss', '0002_operationschema_alter_operation_oss'), + ('rsform', '0009_rsform_alter_constituenta_schema_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='operation', + name='sync_text', + field=models.BooleanField(default=True, verbose_name='Синхронизация'), + ), + migrations.AlterField( + model_name='operation', + name='result', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='producer', to='rsform.rsform', verbose_name='Связанная КС'), + ), + ] diff --git a/rsconcept/backend/apps/oss/models/Operation.py b/rsconcept/backend/apps/oss/models/Operation.py index 03e9e45e..cd1d9b24 100644 --- a/rsconcept/backend/apps/oss/models/Operation.py +++ b/rsconcept/backend/apps/oss/models/Operation.py @@ -2,6 +2,7 @@ from django.db.models import ( CASCADE, SET_NULL, + BooleanField, CharField, FloatField, ForeignKey, @@ -33,11 +34,15 @@ class Operation(Model): ) result: ForeignKey = ForeignKey( verbose_name='Связанная КС', - to='rsform.LibraryItem', + to='rsform.RSForm', null=True, on_delete=SET_NULL, related_name='producer' ) + sync_text: BooleanField = BooleanField( + verbose_name='Синхронизация', + default=True + ) alias: CharField = CharField( verbose_name='Шифр', diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 74bbd142..0211ff62 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -41,7 +41,7 @@ class OperationCreateSerializer(serializers.Serializer): ''' serializer metadata. ''' model = Operation fields = \ - 'alias', 'operation_type', 'title', \ + 'alias', 'operation_type', 'title', 'sync_text', \ 'comment', 'result', 'position_x', 'position_y' item_data = OperationData() diff --git a/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py b/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py index 3278bf3e..18b9dd9a 100644 --- a/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py +++ b/rsconcept/backend/apps/oss/tests/s_models/t_Operation.py @@ -1,7 +1,8 @@ ''' Testing models: Operation. ''' from django.test import TestCase -from apps.oss.models import Operation, OperationSchema, OperationType +from apps.oss.models import LibraryItem, LibraryItemType, Operation, OperationSchema, OperationType +from apps.rsform.models import RSForm class TestOperation(TestCase): @@ -27,5 +28,48 @@ class TestOperation(TestCase): self.assertEqual(self.operation.alias, 'KS1') self.assertEqual(self.operation.title, '') self.assertEqual(self.operation.comment, '') + self.assertEqual(self.operation.sync_text, True) self.assertEqual(self.operation.position_x, 0) self.assertEqual(self.operation.position_y, 0) + + + def test_sync_from_result(self): + schema = RSForm.objects.create(alias=self.operation.alias) + self.operation.result = schema + self.operation.save() + + schema.alias = 'KS2' + schema.comment = 'Comment' + schema.title = 'Title' + schema.save() + self.operation.refresh_from_db() + + self.assertEqual(self.operation.result, schema) + self.assertEqual(self.operation.alias, schema.alias) + self.assertEqual(self.operation.title, schema.title) + self.assertEqual(self.operation.comment, schema.comment) + + self.operation.sync_text = False + self.operation.save() + + schema.alias = 'KS3' + schema.save() + self.operation.refresh_from_db() + self.assertEqual(self.operation.result, schema) + self.assertNotEqual(self.operation.alias, schema.alias) + + def test_sync_from_library_item(self): + schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM) + self.operation.result = schema + self.operation.save() + + schema.alias = 'KS2' + schema.comment = 'Comment' + schema.title = 'Title' + schema.save() + self.operation.refresh_from_db() + + self.assertEqual(self.operation.result, schema) + self.assertEqual(self.operation.alias, schema.alias) + self.assertEqual(self.operation.title, schema.title) + self.assertEqual(self.operation.comment, schema.comment) diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index a7bd57c8..3c9eb990 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -136,6 +136,7 @@ class TestOssViewset(EndpointTester): 'alias': 'Test3', 'title': 'Test title', 'comment': 'Тест кириллицы', + 'sync_text': False, 'position_x': 1, 'position_y': 1, }, @@ -158,6 +159,7 @@ class TestOssViewset(EndpointTester): self.assertEqual(new_operation['comment'], data['item_data']['comment']) self.assertEqual(new_operation['position_x'], data['item_data']['position_x']) self.assertEqual(new_operation['position_y'], data['item_data']['position_y']) + self.assertEqual(new_operation['sync_text'], data['item_data']['sync_text']) self.assertEqual(new_operation['result'], None) self.operation1.refresh_from_db() self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x']) diff --git a/rsconcept/backend/apps/rsform/models/LibraryItem.py b/rsconcept/backend/apps/rsform/models/LibraryItem.py index d4daf365..11f5f90e 100644 --- a/rsconcept/backend/apps/rsform/models/LibraryItem.py +++ b/rsconcept/backend/apps/rsform/models/LibraryItem.py @@ -128,7 +128,30 @@ class LibraryItem(Model): @transaction.atomic def save(self, *args, **kwargs): - subscribe = not self.pk and self.owner + ''' Save updating subscriptions and connected operations. ''' + if not self._state.adding: + self._update_connected_operations() + subscribe = self._state.adding and self.owner super().save(*args, **kwargs) if subscribe: Subscription.subscribe(user=self.owner, item=self) + + def _update_connected_operations(self): + # using method level import to prevent circular dependency + from apps.oss.models import Operation # pylint: disable=import-outside-toplevel + operations = Operation.objects.filter(result__pk=self.pk, sync_text=True) + if not operations.exists(): + return + for operation in operations: + changed = False + if operation.alias != self.alias: + operation.alias = self.alias + changed = True + if operation.title != self.title: + operation.title = self.title + changed = True + if operation.comment != self.comment: + operation.comment = self.comment + changed = True + if changed: + operation.save() diff --git a/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx b/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx index 9b258aae..6ccbfa19 100644 --- a/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx +++ b/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx @@ -40,6 +40,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea const [comment, setComment] = useState(''); const [inputs, setInputs] = useState([]); const [attachedID, setAttachedID] = useState(undefined); + const [syncText, setSyncText] = useState(true); const isValid = useMemo(() => alias !== '', [alias]); @@ -62,6 +63,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea alias: alias, title: title, comment: comment, + sync_text: activeTab === TabID.INPUT ? syncText : true, operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS, result: activeTab === TabID.INPUT ? attachedID ?? null : null }, @@ -83,10 +85,12 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea setTitle={setTitle} attachedID={attachedID} setAttachedID={setAttachedID} + syncText={syncText} + setSyncText={setSyncText} /> ), - [alias, comment, title, attachedID] + [alias, comment, title, attachedID, syncText] ); const synthesisPanel = useMemo( diff --git a/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx b/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx index b4e015c4..75ab2071 100644 --- a/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx +++ b/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx @@ -1,5 +1,9 @@ +import { IconReset } from '@/components/Icons'; import PickSchema from '@/components/select/PickSchema'; +import Checkbox from '@/components/ui/Checkbox'; +import FlexColumn from '@/components/ui/FlexColumn'; import Label from '@/components/ui/Label'; +import MiniButton from '@/components/ui/MiniButton'; import TextArea from '@/components/ui/TextArea'; import TextInput from '@/components/ui/TextInput'; import AnimateFade from '@/components/wrap/AnimateFade'; @@ -15,6 +19,8 @@ interface TabInputOperationProps { setComment: React.Dispatch>; attachedID: LibraryItemID | undefined; setAttachedID: React.Dispatch>; + syncText: boolean; + setSyncText: React.Dispatch>; } function TabInputOperation({ @@ -25,7 +31,9 @@ function TabInputOperation({ comment, setComment, attachedID, - setAttachedID + setAttachedID, + syncText, + setSyncText }: TabInputOperationProps) { return ( @@ -34,17 +42,27 @@ function TabInputOperation({ label='Полное название' value={title} onChange={event => setTitle(event.target.value)} + disabled={syncText && attachedID !== undefined} />
- setAlias(event.target.value)} - /> + + setAlias(event.target.value)} + disabled={syncText && attachedID !== undefined} + /> + +