mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Add sync functionality for Operation
This commit is contained in:
parent
8376c6bda1
commit
4c413ca0f4
|
@ -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='Связанная КС'),
|
||||
),
|
||||
]
|
|
@ -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='Шифр',
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -40,6 +40,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
const [comment, setComment] = useState('');
|
||||
const [inputs, setInputs] = useState<OperationID[]>([]);
|
||||
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(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}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[alias, comment, title, attachedID]
|
||||
[alias, comment, title, attachedID, syncText]
|
||||
);
|
||||
|
||||
const synthesisPanel = useMemo(
|
||||
|
|
|
@ -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<React.SetStateAction<string>>;
|
||||
attachedID: LibraryItemID | undefined;
|
||||
setAttachedID: React.Dispatch<React.SetStateAction<LibraryItemID | undefined>>;
|
||||
syncText: boolean;
|
||||
setSyncText: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function TabInputOperation({
|
||||
|
@ -25,7 +31,9 @@ function TabInputOperation({
|
|||
comment,
|
||||
setComment,
|
||||
attachedID,
|
||||
setAttachedID
|
||||
setAttachedID,
|
||||
syncText,
|
||||
setSyncText
|
||||
}: TabInputOperationProps) {
|
||||
return (
|
||||
<AnimateFade className='cc-column'>
|
||||
|
@ -34,17 +42,27 @@ function TabInputOperation({
|
|||
label='Полное название'
|
||||
value={title}
|
||||
onChange={event => setTitle(event.target.value)}
|
||||
disabled={syncText && attachedID !== undefined}
|
||||
/>
|
||||
<div className='flex gap-6'>
|
||||
<TextInput
|
||||
id='operation_alias'
|
||||
label='Сокращение'
|
||||
className='w-[14rem]'
|
||||
pattern={patterns.library_alias}
|
||||
title={`не более ${limits.library_alias_len} символов`}
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<FlexColumn>
|
||||
<TextInput
|
||||
id='operation_alias'
|
||||
label='Сокращение'
|
||||
className='w-[14rem]'
|
||||
pattern={patterns.library_alias}
|
||||
title={`не более ${limits.library_alias_len} символов`}
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
disabled={syncText && attachedID !== undefined}
|
||||
/>
|
||||
<Checkbox
|
||||
value={syncText}
|
||||
setValue={setSyncText}
|
||||
label='Синхронизировать текст'
|
||||
title='Брать текст из концептуальной схемы'
|
||||
/>
|
||||
</FlexColumn>
|
||||
|
||||
<TextArea
|
||||
id='operation_comment'
|
||||
|
@ -53,10 +71,22 @@ function TabInputOperation({
|
|||
rows={3}
|
||||
value={comment}
|
||||
onChange={event => setComment(event.target.value)}
|
||||
disabled={syncText && attachedID !== undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<Label text='Загружаемая концептуальная схема' />
|
||||
<MiniButton
|
||||
title='Сбросить выбор схемы'
|
||||
noHover
|
||||
noPadding
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => setAttachedID(undefined)}
|
||||
disabled={attachedID == undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label text='Загружаемая концептуальная схема' />
|
||||
<PickSchema value={attachedID} onSelectValue={setAttachedID} rows={8} />
|
||||
</AnimateFade>
|
||||
);
|
||||
|
|
|
@ -30,6 +30,8 @@ export interface IOperation {
|
|||
alias: string;
|
||||
title: string;
|
||||
comment: string;
|
||||
sync_text: boolean;
|
||||
|
||||
position_x: number;
|
||||
position_y: number;
|
||||
|
||||
|
@ -61,7 +63,7 @@ export interface ITargetOperation extends IPositionsData {
|
|||
export interface IOperationCreateData extends IPositionsData {
|
||||
item_data: Pick<
|
||||
IOperation,
|
||||
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result'
|
||||
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result' | 'sync_text'
|
||||
>;
|
||||
arguments: OperationID[] | undefined;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user