R: Restructuring layout data pt1

This commit is contained in:
Ivan 2025-04-06 13:28:00 +03:00
parent f1faffd063
commit 3271d9244c
26 changed files with 808 additions and 479 deletions

View File

@ -9,6 +9,7 @@ from apps.library.models import (
LibraryTemplate,
LocationHead
)
from apps.oss.models import OperationSchema
from apps.rsform.models import RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
from shared.testing_utils import response_contains
@ -58,6 +59,8 @@ class TestLibraryViewset(EndpointTester):
'read_only': True
}
response = self.executeCreated(data=data)
oss = OperationSchema(LibraryItem.objects.get(pk=response.data['id']))
self.assertEqual(oss.model.owner, self.user)
self.assertEqual(response.data['owner'], self.user.pk)
self.assertEqual(response.data['item_type'], data['item_type'])
self.assertEqual(response.data['title'], data['title'])
@ -65,6 +68,8 @@ class TestLibraryViewset(EndpointTester):
self.assertEqual(response.data['access_policy'], data['access_policy'])
self.assertEqual(response.data['visible'], data['visible'])
self.assertEqual(response.data['read_only'], data['read_only'])
self.assertEqual(oss.layout().data['operations'], [])
self.assertEqual(oss.layout().data['blocks'], [])
self.logout()
data = {'title': 'Title2'}

View File

@ -13,7 +13,7 @@ from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from apps.oss.models import Operation, OperationSchema, PropagationFacade
from apps.oss.models import Layout, Operation, OperationSchema, PropagationFacade
from apps.rsform.models import RSForm
from apps.rsform.serializers import RSFormParseSerializer
from apps.users.models import User
@ -40,6 +40,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
serializer.save(owner=self.request.user)
else:
serializer.save()
if serializer.data.get('item_type') == m.LibraryItemType.OPERATION_SCHEMA:
Layout.objects.create(oss=serializer.instance, data={'operations': [], 'blocks': []})
def perform_update(self, serializer) -> None:
instance = serializer.save()

View File

@ -15,11 +15,24 @@ class OperationAdmin(admin.ModelAdmin):
'alias',
'title',
'description',
'position_x',
'position_y']
'parent']
search_fields = ['id', 'operation_type', 'title', 'alias']
class BlockAdmin(admin.ModelAdmin):
''' Admin model: Block. '''
ordering = ['oss']
list_display = ['id', 'oss', 'title', 'description', 'parent']
search_fields = ['oss']
class LayoutAdmin(admin.ModelAdmin):
''' Admin model: Layout. '''
ordering = ['oss']
list_display = ['id', 'oss', 'data']
search_fields = ['oss']
class ArgumentAdmin(admin.ModelAdmin):
''' Admin model: Operation arguments. '''
ordering = ['operation']
@ -42,6 +55,8 @@ class InheritanceAdmin(admin.ModelAdmin):
admin.site.register(models.Operation, OperationAdmin)
admin.site.register(models.Block, BlockAdmin)
admin.site.register(models.Layout, LayoutAdmin)
admin.site.register(models.Argument, ArgumentAdmin)
admin.site.register(models.Substitution, SynthesisSubstitutionAdmin)
admin.site.register(models.Inheritance, InheritanceAdmin)

View File

@ -0,0 +1,84 @@
# Generated by Django 5.1.7 on 2025-03-26 16:04
import django.db.models.deletion
from django.db import migrations, models
def migrate_layout(apps, schema_editor):
LibraryItem = apps.get_model('library', 'LibraryItem')
Operation = apps.get_model('oss', 'Operation')
Layout = apps.get_model('oss', 'Layout')
for library_item in LibraryItem.objects.filter(item_type='oss'):
layout_data = {'operations': [], 'blocks': []}
operations = Operation.objects.filter(oss=library_item)
for operation in operations:
layout_data['operations'].append({
'id': operation.id,
'x': operation.position_x,
'y': operation.position_y
})
Layout.objects.create(oss=library_item, data=layout_data)
class Migration(migrations.Migration):
dependencies = [
('library', '0007_rename_libraryitem_comment_libraryitem_description'),
('oss', '0010_rename_comment_operation_description'),
]
operations = [
migrations.CreateModel(
name='Layout',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.JSONField(default=dict, verbose_name='Расположение')),
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='layout', to='library.libraryitem', verbose_name='Схема синтеза')),
],
options={
'verbose_name': 'Схема расположения',
'verbose_name_plural': 'Схемы расположения',
},
),
migrations.RunPython(migrate_layout),
migrations.RemoveField(
model_name='operation',
name='position_x',
),
migrations.RemoveField(
model_name='operation',
name='position_y',
),
migrations.CreateModel(
name='Block',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.TextField(blank=True, verbose_name='Название')),
('description', models.TextField(blank=True, verbose_name='Описание')),
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='blocks', to='library.libraryitem', verbose_name='Схема синтеза')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='as_child_block', to='oss.block', verbose_name='Содержащий блок')),
],
options={
'verbose_name': 'Блок',
'verbose_name_plural': 'Блоки',
},
),
migrations.AddField(
model_name='operation',
name='parent',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='as_child_operation',
to='oss.block',
verbose_name='Содержащий блок'),
),
]

View File

@ -0,0 +1,39 @@
''' Models: Content Block in OSS. '''
# pylint: disable=duplicate-code
from django.db.models import CASCADE, SET_NULL, ForeignKey, Model, TextField
class Block(Model):
''' Block of content in OSS.'''
oss = ForeignKey(
verbose_name='Схема синтеза',
to='library.LibraryItem',
on_delete=CASCADE,
related_name='blocks'
)
title = TextField(
verbose_name='Название',
blank=True
)
description = TextField(
verbose_name='Описание',
blank=True
)
parent = ForeignKey(
verbose_name='Содержащий блок',
to='oss.Block',
blank=True,
null=True,
on_delete=SET_NULL,
related_name='as_child_block'
)
class Meta:
''' Model metadata. '''
verbose_name = 'Блок'
verbose_name_plural = 'Блоки'
def __str__(self) -> str:
return f'Блок {self.title}'

View File

@ -0,0 +1,25 @@
''' Models: Content Block in OSS. '''
from django.db.models import CASCADE, ForeignKey, JSONField, Model
class Layout(Model):
''' Node layout in OSS.'''
oss = ForeignKey(
verbose_name='Схема синтеза',
to='library.LibraryItem',
on_delete=CASCADE,
related_name='layout'
)
data = JSONField(
verbose_name='Расположение',
default=dict
)
class Meta:
''' Model metadata. '''
verbose_name = 'Схема расположения'
verbose_name_plural = 'Схемы расположения'
def __str__(self) -> str:
return f'Схема расположения {self.oss.alias}'

View File

@ -1,9 +1,9 @@
''' Models: Operation in OSS. '''
# pylint: disable=duplicate-code
from django.db.models import (
CASCADE,
SET_NULL,
CharField,
FloatField,
ForeignKey,
Model,
QuerySet,
@ -44,6 +44,15 @@ class Operation(Model):
related_name='producer'
)
parent = ForeignKey(
verbose_name='Содержащий блок',
to='oss.Block',
blank=True,
null=True,
on_delete=SET_NULL,
related_name='as_child_operation'
)
alias = CharField(
verbose_name='Шифр',
max_length=255,
@ -58,15 +67,6 @@ class Operation(Model):
blank=True
)
position_x = FloatField(
verbose_name='Положение по горизонтали',
default=0
)
position_y = FloatField(
verbose_name='Положение по вертикали',
default=0
)
class Meta:
''' Model metadata. '''
verbose_name = 'Операция'

View File

@ -20,6 +20,7 @@ from apps.rsform.models import (
from .Argument import Argument
from .Inheritance import Inheritance
from .Layout import Layout
from .Operation import Operation
from .Substitution import Substitution
@ -38,6 +39,7 @@ class OperationSchema:
def create(**kwargs) -> 'OperationSchema':
''' Create LibraryItem via OperationSchema. '''
model = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs)
Layout.objects.create(oss=model, data={'operations': [], 'blocks': []})
return OperationSchema(model)
@staticmethod
@ -62,6 +64,12 @@ class OperationSchema:
''' Operation arguments. '''
return Argument.objects.filter(operation__oss=self.model)
def layout(self) -> Layout:
''' OSS layout. '''
result = Layout.objects.filter(oss=self.model).first()
assert result is not None
return result
def substitutions(self) -> QuerySet[Substitution]:
''' Operation substitutions. '''
return Substitution.objects.filter(operation__oss=self.model)
@ -78,15 +86,11 @@ class OperationSchema:
location=self.model.location
)
def update_positions(self, data: list[dict]) -> None:
def update_layout(self, data: dict) -> None:
''' Update positions. '''
lookup = {x['id']: x for x in data}
operations = self.operations()
for item in operations:
if item.pk in lookup:
item.position_x = lookup[item.pk]['position_x']
item.position_y = lookup[item.pk]['position_y']
Operation.objects.bulk_update(operations, ['position_x', 'position_y'])
layout = self.layout()
layout.data = data
layout.save()
def create_operation(self, **kwargs) -> Operation:
''' Insert new operation. '''

View File

@ -1,7 +1,9 @@
''' Django: Models. '''
from .Argument import Argument
from .Block import Block
from .Inheritance import Inheritance
from .Layout import Layout
from .Operation import Operation, OperationType
from .OperationSchema import OperationSchema
from .PropagationFacade import PropagationFacade

View File

@ -1,6 +1,6 @@
''' REST API: Serializers. '''
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
from .basics import LayoutSerializer, SubstitutionExSerializer
from .data_access import (
ArgumentSerializer,
OperationCreateSerializer,

View File

@ -2,17 +2,29 @@
from rest_framework import serializers
class OperationPositionSerializer(serializers.Serializer):
class OperationNodeSerializer(serializers.Serializer):
''' Operation position. '''
id = serializers.IntegerField()
position_x = serializers.FloatField()
position_y = serializers.FloatField()
x = serializers.FloatField()
y = serializers.FloatField()
class PositionsSerializer(serializers.Serializer):
''' Operations position for OperationSchema. '''
positions = serializers.ListField(
child=OperationPositionSerializer()
class BlockNodeSerializer(serializers.Serializer):
''' Block position. '''
id = serializers.IntegerField()
x = serializers.FloatField()
y = serializers.FloatField()
width = serializers.FloatField()
height = serializers.FloatField()
class LayoutSerializer(serializers.Serializer):
''' Layout for OperationSchema. '''
blocks = serializers.ListField(
child=BlockNodeSerializer()
)
operations = serializers.ListField(
child=OperationNodeSerializer()
)

View File

@ -12,7 +12,7 @@ from apps.rsform.serializers import SubstitutionSerializerBase
from shared import messages as msg
from ..models import Argument, Inheritance, Operation, OperationSchema, OperationType
from .basics import OperationPositionSerializer, SubstitutionExSerializer
from .basics import LayoutSerializer, SubstitutionExSerializer
class OperationSerializer(serializers.ModelSerializer):
@ -44,17 +44,16 @@ class OperationCreateSerializer(serializers.Serializer):
model = Operation
fields = \
'alias', 'operation_type', 'title', \
'description', 'result', 'position_x', 'position_y'
'description', 'result', 'parent'
layout = LayoutSerializer()
position_x = serializers.FloatField()
position_y = serializers.FloatField()
create_schema = serializers.BooleanField(default=False, required=False)
item_data = OperationCreateData()
create_schema = serializers.BooleanField(default=False, required=False)
arguments = PKField(many=True, queryset=Operation.objects.all().only('pk'), required=False)
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
class OperationUpdateSerializer(serializers.Serializer):
''' Serializer: Operation update. '''
@ -65,6 +64,7 @@ class OperationUpdateSerializer(serializers.Serializer):
model = Operation
fields = 'alias', 'title', 'description'
layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all())
item_data = OperationUpdateData()
arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False)
@ -73,11 +73,6 @@ class OperationUpdateSerializer(serializers.Serializer):
required=False
)
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
def validate(self, attrs):
if 'arguments' not in attrs:
return attrs
@ -120,11 +115,8 @@ class OperationUpdateSerializer(serializers.Serializer):
class OperationTargetSerializer(serializers.Serializer):
''' Serializer: Target single operation. '''
layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id'))
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
@ -138,11 +130,8 @@ class OperationTargetSerializer(serializers.Serializer):
class OperationDeleteSerializer(serializers.Serializer):
''' Serializer: Delete operation. '''
layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
keep_constituents = serializers.BooleanField(default=False, required=False)
delete_schema = serializers.BooleanField(default=False, required=False)
@ -158,6 +147,7 @@ class OperationDeleteSerializer(serializers.Serializer):
class SetOperationInputSerializer(serializers.Serializer):
''' Serializer: Set input schema for operation. '''
layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all())
input = PKField(
many=False,
@ -165,10 +155,6 @@ class SetOperationInputSerializer(serializers.Serializer):
allow_null=True,
default=None
)
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
@ -195,6 +181,7 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
substitutions = serializers.ListField(
child=SubstitutionExSerializer()
)
layout = LayoutSerializer()
class Meta:
''' serializer metadata. '''
@ -205,6 +192,7 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
result = LibraryItemDetailsSerializer(instance).data
del result['versions']
oss = OperationSchema(instance)
result['layout'] = oss.layout().data
result['items'] = []
for operation in oss.operations().order_by('pk'):
result['items'].append(OperationSerializer(operation).data)

View File

@ -25,9 +25,8 @@ class TestOperation(TestCase):
def test_create_default(self):
self.assertEqual(self.operation.oss, self.oss.model)
self.assertEqual(self.operation.operation_type, OperationType.INPUT)
self.assertEqual(self.operation.parent, None)
self.assertEqual(self.operation.result, None)
self.assertEqual(self.operation.alias, 'KS1')
self.assertEqual(self.operation.title, '')
self.assertEqual(self.operation.description, '')
self.assertEqual(self.operation.position_x, 0)
self.assertEqual(self.operation.position_y, 0)

View File

@ -58,6 +58,18 @@ class TestChangeAttributes(EndpointTester):
self.operation3.refresh_from_db()
self.ks3 = RSForm(self.operation3.result)
self.layout_data = {
'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
],
'blocks': []
}
layout = self.owned.layout()
layout.data = self.layout_data
layout.save()
@decl_endpoint('/api/library/{item}/set-owner', method='patch')
def test_set_owner(self):
data = {'user': self.user3.pk}
@ -142,7 +154,7 @@ class TestChangeAttributes(EndpointTester):
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
'layout': self.layout_data
}
response = self.executeOK(data=data, item=self.owned_id)

View File

@ -57,6 +57,18 @@ class TestChangeConstituents(EndpointTester):
self.ks3 = RSForm(self.operation3.result)
self.assertEqual(self.ks3.constituents().count(), 4)
self.layout_data = {
'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
],
'blocks': []
}
layout = self.owned.layout()
layout.data = self.layout_data
layout.save()
@decl_endpoint('/api/rsforms/{item}/details', method='get')
def test_retrieve_inheritance(self):
response = self.executeOK(item=self.ks3.model.pk)

View File

@ -106,6 +106,20 @@ class TestChangeOperations(EndpointTester):
convention='KS5D4'
)
self.layout_data = {
'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
{'id': self.operation4.pk, 'x': 0, 'y': 0},
{'id': self.operation5.pk, 'x': 0, 'y': 0},
],
'blocks': []
}
layout = self.owned.layout()
layout.data = self.layout_data
layout.save()
def test_oss_setup(self):
self.assertEqual(self.ks1.constituents().count(), 3)
self.assertEqual(self.ks2.constituents().count(), 3)
@ -117,7 +131,7 @@ class TestChangeOperations(EndpointTester):
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_input_operation(self):
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation2.pk
}
self.executeOK(data=data, item=self.owned_id)
@ -137,7 +151,7 @@ class TestChangeOperations(EndpointTester):
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_null(self):
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation2.pk,
'input': None
}
@ -169,7 +183,7 @@ class TestChangeOperations(EndpointTester):
ks6D1 = ks6.insert_new('D1', definition_formal='X1 X2', convention='KS6D1')
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation2.pk,
'input': ks6.model.pk
}
@ -211,7 +225,7 @@ class TestChangeOperations(EndpointTester):
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_operation_and_constituents(self):
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation1.pk,
'keep_constituents': False,
'delete_schema': True
@ -232,7 +246,7 @@ class TestChangeOperations(EndpointTester):
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_operation_keep_constituents(self):
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation1.pk,
'keep_constituents': True,
'delete_schema': True
@ -253,7 +267,7 @@ class TestChangeOperations(EndpointTester):
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_operation_keep_schema(self):
data = {
'positions': [],
'layout': self.layout_data,
'target': self.operation1.pk,
'keep_constituents': True,
'delete_schema': False
@ -283,7 +297,7 @@ class TestChangeOperations(EndpointTester):
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
'layout': self.layout_data,
'substitutions': [
{
'original': self.ks1X1.pk,
@ -317,7 +331,7 @@ class TestChangeOperations(EndpointTester):
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
'layout': self.layout_data,
'arguments': [self.operation1.pk],
}
@ -356,7 +370,7 @@ class TestChangeOperations(EndpointTester):
data = {
'target': self.operation4.pk,
'positions': []
'layout': self.layout_data
}
self.executeOK(data=data, item=self.owned_id)
self.operation4.refresh_from_db()

View File

@ -106,6 +106,20 @@ class TestChangeSubstitutions(EndpointTester):
convention='KS5D4'
)
self.layout_data = {
'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
{'id': self.operation4.pk, 'x': 0, 'y': 0},
{'id': self.operation5.pk, 'x': 0, 'y': 0},
],
'blocks': []
}
layout = self.owned.layout()
layout.data = self.layout_data
layout.save()
def test_oss_setup(self):
self.assertEqual(self.ks1.constituents().count(), 3)
@ -139,10 +153,12 @@ class TestChangeSubstitutions(EndpointTester):
@decl_endpoint('/api/rsforms/{schema}/substitute', method='patch')
def test_substitute_substitution(self):
data = {'substitutions': [{
data = {
'substitutions': [{
'original': self.ks2S1.pk,
'substitution': self.ks2X1.pk
}]}
}]
}
self.executeOK(data=data, schema=self.ks2.model.pk)
self.ks4D1.refresh_from_db()
self.ks4D2.refresh_from_db()

View File

@ -1,2 +1,3 @@
''' Tests for REST API. '''
from .t_operations import *
from .t_oss import *

View File

@ -0,0 +1,447 @@
''' Testing API: Operation Schema. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
from apps.oss.models import Operation, OperationSchema, OperationType
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestOssOperations(EndpointTester):
''' Testing OSS view - operations. '''
def setUp(self):
super().setUp()
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
self.owned_id = self.owned.model.pk
self.unowned = OperationSchema.create(title='Test2', alias='T2')
self.unowned_id = self.unowned.model.pk
self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
self.private_id = self.private.model.pk
self.invalid_id = self.private.model.pk + 1337
def populateData(self):
self.ks1 = RSForm.create(
alias='KS1',
title='Test1',
owner=self.user
)
self.ks1X1 = self.ks1.insert_new(
'X1',
term_raw='X1_1',
term_resolved='X1_1'
)
self.ks2 = RSForm.create(
alias='KS2',
title='Test2',
owner=self.user
)
self.ks2X1 = self.ks2.insert_new(
'X2',
term_raw='X1_2',
term_resolved='X1_2'
)
self.operation1 = self.owned.create_operation(
alias='1',
operation_type=OperationType.INPUT,
result=self.ks1.model
)
self.operation2 = self.owned.create_operation(
alias='2',
operation_type=OperationType.INPUT,
result=self.ks2.model
)
self.operation3 = self.owned.create_operation(
alias='3',
operation_type=OperationType.SYNTHESIS
)
self.layout_data = {
'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
],
'blocks': []
}
layout = self.owned.layout()
layout.data = self.layout_data
layout.save()
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
self.owned.set_substitutions(self.operation3.pk, [{
'original': self.ks1X1,
'substitution': self.ks2X1
}])
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'item_data': {
'alias': 'Test3',
'title': 'Test title',
'description': 'Тест кириллицы',
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1
}
self.executeBadData(data=data)
data['item_data']['operation_type'] = 'invalid'
self.executeBadData(data=data)
data['item_data']['operation_type'] = OperationType.INPUT
self.executeNotFound(data=data, item=self.invalid_id)
response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(len(response.data['oss']['items']), 4)
new_operation = response.data['new_operation']
layout = response.data['oss']['layout']
item = [item for item in layout['operations'] if item['id'] == new_operation['id']][0]
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
self.assertEqual(new_operation['title'], data['item_data']['title'])
self.assertEqual(new_operation['description'], data['item_data']['description'])
self.assertEqual(new_operation['result'], None)
self.assertEqual(new_operation['parent'], None)
self.assertEqual(item['x'], data['position_x'])
self.assertEqual(item['y'], data['position_y'])
self.operation1.refresh_from_db()
self.executeForbidden(data=data, item=self.unowned_id)
self.toggle_admin(True)
self.executeCreated(data=data, item=self.unowned_id)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_arguments(self):
self.populateData()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.SYNTHESIS
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'arguments': [self.operation1.pk, self.operation3.pk]
}
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
arguments = self.owned.arguments()
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_result(self):
self.populateData()
self.operation1.result = None
self.operation1.save()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1
}
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
self.assertEqual(new_operation['result'], self.ks1.model.pk)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_schema(self):
self.populateData()
Editor.add(self.owned.model.pk, self.user2.pk)
data = {
'item_data': {
'alias': 'Test4',
'title': 'Test title',
'description': 'Comment',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
},
'create_schema': True,
'layout': self.layout_data,
'position_x': 1,
'position_y': 1
}
self.executeBadData(data=data, item=self.owned_id)
data['item_data']['result'] = None
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(schema.alias, data['item_data']['alias'])
self.assertEqual(schema.title, data['item_data']['title'])
self.assertEqual(schema.description, data['item_data']['description'])
self.assertEqual(schema.visible, False)
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
self.assertIn(self.user2, schema.getQ_editors())
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_operation(self):
self.executeNotFound(item=self.invalid_id)
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'layout': self.layout_data
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
layout = response.data['layout']
items = [item for item in layout['operations'] if item['id'] == data['target']]
self.assertEqual(len(response.data['items']), 2)
self.assertEqual(len(items), 0)
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
def test_create_input(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'layout': self.layout_data
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
self.executeBadData(data=data, item=self.owned_id)
self.operation1.result = None
self.operation1.description = 'TestComment'
self.operation1.title = 'TestTitle'
self.operation1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
new_schema = response.data['new_schema']
self.assertEqual(new_schema['id'], self.operation1.result.pk)
self.assertEqual(new_schema['alias'], self.operation1.alias)
self.assertEqual(new_schema['title'], self.operation1.title)
self.assertEqual(new_schema['description'], self.operation1.description)
data['target'] = self.operation3.pk
self.executeBadData(data=data)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_null(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'layout': self.layout_data
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
data['input'] = None
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, None)
data['input'] = self.ks1.model.pk
self.ks1.model.alias = 'Test42'
self.ks1.model.title = 'Test421'
self.ks1.model.description = 'TestComment42'
self.ks1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, self.ks1.model)
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
self.assertEqual(self.operation1.title, self.ks1.model.title)
self.assertEqual(self.operation1.description, self.ks1.model.description)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_change_schema(self):
self.populateData()
self.operation2.result = None
data = {
'layout': self.layout_data,
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
self.executeBadData(data=data, item=self.owned_id)
self.ks2.model.visible = False
self.ks2.model.save(update_fields=['visible'])
data = {
'layout': self.layout_data,
'target': self.operation2.pk,
'input': None
}
self.executeOK(data=data, item=self.owned_id)
self.operation2.refresh_from_db()
self.ks2.model.refresh_from_db()
self.assertEqual(self.operation2.result, None)
self.assertEqual(self.ks2.model.visible, True)
data = {
'layout': self.layout_data,
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
self.executeOK(data=data, item=self.owned_id)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, self.ks2.model)
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
ks3 = RSForm.create(alias='KS3', title='Test3', owner=self.user)
ks3x1 = ks3.insert_new('X1', term_resolved='X1_1')
data = {
'target': self.operation3.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'layout': self.layout_data,
'arguments': [self.operation2.pk, self.operation1.pk],
'substitutions': [
{
'original': self.ks1X1.pk,
'substitution': ks3x1.pk
}
]
}
self.executeBadData(data=data)
data['substitutions'][0]['substitution'] = self.ks2X1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
self.operation3.refresh_from_db()
self.assertEqual(self.operation3.alias, data['item_data']['alias'])
self.assertEqual(self.operation3.title, data['item_data']['title'])
self.assertEqual(self.operation3.description, data['item_data']['description'])
args = self.operation3.getQ_arguments().order_by('order')
self.assertEqual(args[0].argument.pk, data['arguments'][0])
self.assertEqual(args[0].order, 0)
self.assertEqual(args[1].argument.pk, data['arguments'][1])
self.assertEqual(args[1].order, 1)
sub = self.operation3.getQ_substitutions()[0]
self.assertEqual(sub.original.pk, data['substitutions'][0]['original'])
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_sync(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'target': self.operation1.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'layout': self.layout_data
}
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
self.assertEqual(self.operation1.title, data['item_data']['title'])
self.assertEqual(self.operation1.description, data['item_data']['description'])
self.assertEqual(self.operation1.result.alias, data['item_data']['alias'])
self.assertEqual(self.operation1.result.title, data['item_data']['title'])
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_invalid_substitution(self):
self.populateData()
self.ks1X2 = self.ks1.insert_new('X2')
data = {
'target': self.operation3.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'layout': self.layout_data,
'arguments': [self.operation1.pk, self.operation2.pk],
'substitutions': [
{
'original': self.ks1X1.pk,
'substitution': self.ks2X1.pk
},
{
'original': self.ks2X1.pk,
'substitution': self.ks1X2.pk
}
]
}
self.executeBadData(data=data, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/execute-operation', method='post')
def test_execute_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'layout': self.layout_data,
'target': self.operation1.pk
}
self.executeBadData(data=data)
data['target'] = self.operation3.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
self.executeOK(data=data)
self.operation3.refresh_from_db()
schema = self.operation3.result
self.assertEqual(schema.alias, self.operation3.alias)
self.assertEqual(schema.description, self.operation3.description)
self.assertEqual(schema.title, self.operation3.title)
self.assertEqual(schema.visible, False)
items = list(RSForm(schema).constituents())
self.assertEqual(len(items), 1)
self.assertEqual(items[0].alias, 'X1')
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)

View File

@ -1,6 +1,6 @@
''' Testing API: Operation Schema. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
from apps.oss.models import Operation, OperationSchema, OperationType
from apps.library.models import AccessPolicy, LibraryItemType
from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -54,6 +54,14 @@ class TestOssViewset(EndpointTester):
alias='3',
operation_type=OperationType.SYNTHESIS
)
layout = self.owned.layout()
layout.data = {'operations': [
{'id': self.operation1.pk, 'x': 0, 'y': 0},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
{'id': self.operation3.pk, 'x': 0, 'y': 0},
], 'blocks': []}
layout.save()
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
self.owned.set_substitutions(self.operation3.pk, [{
'original': self.ks1X1,
@ -95,6 +103,12 @@ class TestOssViewset(EndpointTester):
self.assertEqual(arguments[1]['operation'], self.operation3.pk)
self.assertEqual(arguments[1]['argument'], self.operation2.pk)
layout = response.data['layout']
self.assertEqual(layout['blocks'], [])
self.assertEqual(layout['operations'][0], {'id': self.operation1.pk, 'x': 0, 'y': 0})
self.assertEqual(layout['operations'][1], {'id': self.operation2.pk, 'x': 0, 'y': 0})
self.assertEqual(layout['operations'][2], {'id': self.operation3.pk, 'x': 0, 'y': 0})
self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id)
@ -103,401 +117,30 @@ class TestOssViewset(EndpointTester):
self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id)
@decl_endpoint('/api/oss/{item}/update-positions', method='patch')
def test_update_positions(self):
@decl_endpoint('/api/oss/{item}/update-layout', method='patch')
def test_update_layout(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {'positions': []}
data = {'operations': [], 'blocks': []}
self.executeOK(data=data)
data = {'positions': [
{'id': self.operation1.pk, 'position_x': 42.1, 'position_y': 1337},
{'id': self.operation2.pk, 'position_x': 36.1, 'position_y': 1437},
{'id': self.invalid_id, 'position_x': 31, 'position_y': 12},
]}
data = {'operations': [
{'id': self.operation1.pk, 'x': 42.1, 'y': 1337},
{'id': self.operation2.pk, 'x': 36.1, 'y': 1437},
{'id': self.operation3.pk, 'x': 36.1, 'y': 1435}
], 'blocks': []}
self.toggle_admin(True)
self.executeOK(data=data, item=self.unowned_id)
self.operation1.refresh_from_db()
self.assertNotEqual(self.operation1.position_x, data['positions'][0]['position_x'])
self.assertNotEqual(self.operation1.position_y, data['positions'][0]['position_y'])
self.toggle_admin(False)
self.executeOK(data=data, item=self.owned_id)
self.operation1.refresh_from_db()
self.operation2.refresh_from_db()
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
self.assertEqual(self.operation2.position_x, data['positions'][1]['position_x'])
self.assertEqual(self.operation2.position_y, data['positions'][1]['position_y'])
self.owned.refresh_from_db()
self.assertEqual(self.owned.layout().data, data)
self.executeForbidden(data=data, item=self.unowned_id)
self.executeForbidden(data=data, item=self.private_id)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'item_data': {
'alias': 'Test3',
'title': 'Test title',
'description': 'Тест кириллицы',
'position_x': 1,
'position_y': 1,
},
'positions': [
{'id': self.operation1.pk, 'position_x': 42.1, 'position_y': 1337}
]
}
self.executeBadData(data=data)
data['item_data']['operation_type'] = 'invalid'
self.executeBadData(data=data)
data['item_data']['operation_type'] = OperationType.INPUT
self.executeNotFound(data=data, item=self.invalid_id)
response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(len(response.data['oss']['items']), 4)
new_operation = response.data['new_operation']
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
self.assertEqual(new_operation['title'], data['item_data']['title'])
self.assertEqual(new_operation['description'], data['item_data']['description'])
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['result'], None)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
self.executeForbidden(data=data, item=self.unowned_id)
self.toggle_admin(True)
self.executeCreated(data=data, item=self.unowned_id)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_arguments(self):
self.populateData()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.SYNTHESIS
},
'positions': [],
'arguments': [self.operation1.pk, self.operation3.pk]
}
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
arguments = self.owned.arguments()
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_result(self):
self.populateData()
self.operation1.result = None
self.operation1.save()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
},
'positions': [],
}
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
self.assertEqual(new_operation['result'], self.ks1.model.pk)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_schema(self):
self.populateData()
Editor.add(self.owned.model.pk, self.user2.pk)
data = {
'item_data': {
'alias': 'Test4',
'title': 'Test title',
'description': 'Comment',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
},
'create_schema': True,
'positions': [],
}
self.executeBadData(data=data, item=self.owned_id)
data['item_data']['result'] = None
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(schema.alias, data['item_data']['alias'])
self.assertEqual(schema.title, data['item_data']['title'])
self.assertEqual(schema.description, data['item_data']['description'])
self.assertEqual(schema.visible, False)
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
self.assertIn(self.user2, schema.getQ_editors())
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_operation(self):
self.executeNotFound(item=self.invalid_id)
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'positions': []
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
self.assertEqual(len(response.data['items']), 2)
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
def test_create_input(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'positions': []
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
self.executeBadData(data=data, item=self.owned_id)
self.operation1.result = None
self.operation1.description = 'TestComment'
self.operation1.title = 'TestTitle'
self.operation1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
new_schema = response.data['new_schema']
self.assertEqual(new_schema['id'], self.operation1.result.pk)
self.assertEqual(new_schema['alias'], self.operation1.alias)
self.assertEqual(new_schema['title'], self.operation1.title)
self.assertEqual(new_schema['description'], self.operation1.description)
data['target'] = self.operation3.pk
self.executeBadData(data=data)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_null(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'positions': []
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
data['input'] = None
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, None)
data['input'] = self.ks1.model.pk
self.ks1.model.alias = 'Test42'
self.ks1.model.title = 'Test421'
self.ks1.model.description = 'TestComment42'
self.ks1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, self.ks1.model)
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
self.assertEqual(self.operation1.title, self.ks1.model.title)
self.assertEqual(self.operation1.description, self.ks1.model.description)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_change_schema(self):
self.populateData()
self.operation2.result = None
data = {
'positions': [],
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
self.executeBadData(data=data, item=self.owned_id)
self.ks2.model.visible = False
self.ks2.model.save(update_fields=['visible'])
data = {
'positions': [],
'target': self.operation2.pk,
'input': None
}
self.executeOK(data=data, item=self.owned_id)
self.operation2.refresh_from_db()
self.ks2.model.refresh_from_db()
self.assertEqual(self.operation2.result, None)
self.assertEqual(self.ks2.model.visible, True)
data = {
'positions': [],
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
self.executeOK(data=data, item=self.owned_id)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.result, self.ks2.model)
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
ks3 = RSForm.create(alias='KS3', title='Test3', owner=self.user)
ks3x1 = ks3.insert_new('X1', term_resolved='X1_1')
data = {
'target': self.operation3.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
'arguments': [self.operation2.pk, self.operation1.pk],
'substitutions': [
{
'original': self.ks1X1.pk,
'substitution': ks3x1.pk
}
]
}
self.executeBadData(data=data)
data['substitutions'][0]['substitution'] = self.ks2X1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
response = self.executeOK(data=data)
self.operation3.refresh_from_db()
self.assertEqual(self.operation3.alias, data['item_data']['alias'])
self.assertEqual(self.operation3.title, data['item_data']['title'])
self.assertEqual(self.operation3.description, data['item_data']['description'])
args = self.operation3.getQ_arguments().order_by('order')
self.assertEqual(args[0].argument.pk, data['arguments'][0])
self.assertEqual(args[0].order, 0)
self.assertEqual(args[1].argument.pk, data['arguments'][1])
self.assertEqual(args[1].order, 1)
sub = self.operation3.getQ_substitutions()[0]
self.assertEqual(sub.original.pk, data['substitutions'][0]['original'])
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_sync(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'target': self.operation1.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
}
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
self.assertEqual(self.operation1.title, data['item_data']['title'])
self.assertEqual(self.operation1.description, data['item_data']['description'])
self.assertEqual(self.operation1.result.alias, data['item_data']['alias'])
self.assertEqual(self.operation1.result.title, data['item_data']['title'])
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_invalid_substitution(self):
self.populateData()
self.ks1X2 = self.ks1.insert_new('X2')
data = {
'target': self.operation3.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'description': 'Comment mod'
},
'positions': [],
'arguments': [self.operation1.pk, self.operation2.pk],
'substitutions': [
{
'original': self.ks1X1.pk,
'substitution': self.ks2X1.pk
},
{
'original': self.ks2X1.pk,
'substitution': self.ks1X2.pk
}
]
}
self.executeBadData(data=data, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/execute-operation', method='post')
def test_execute_operation(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'positions': [],
'target': self.operation1.pk
}
self.executeBadData(data=data)
data['target'] = self.operation3.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
self.login()
self.executeOK(data=data)
self.operation3.refresh_from_db()
schema = self.operation3.result
self.assertEqual(schema.alias, self.operation3.alias)
self.assertEqual(schema.description, self.operation3.description)
self.assertEqual(schema.title, self.operation3.title)
self.assertEqual(schema.visible, False)
items = list(RSForm(schema).constituents())
self.assertEqual(len(items), 1)
self.assertEqual(items[0].alias, 'X1')
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)
@decl_endpoint('/api/oss/get-predecessor', method='post')
def test_get_predecessor(self):
self.populateData()

View File

@ -36,9 +36,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
def get_permissions(self):
''' Determine permission class. '''
if self.action in [
'update_layout',
'create_operation',
'delete_operation',
'update_positions',
'create_input',
'set_input',
'update_operation',
@ -73,21 +73,21 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
)
@extend_schema(
summary='update positions',
summary='update layout',
tags=['OSS'],
request=s.PositionsSerializer,
request=s.LayoutSerializer,
responses={
c.HTTP_200_OK: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='update-positions')
def update_positions(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Update operations positions. '''
serializer = s.PositionsSerializer(data=request.data)
@action(detail=True, methods=['patch'], url_path='update-layout')
def update_layout(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Update schema layout. '''
serializer = s.LayoutSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
m.OperationSchema(self.get_object()).update_positions(serializer.validated_data['positions'])
m.OperationSchema(self.get_object()).update_layout(serializer.validated_data)
return Response(status=c.HTTP_200_OK)
@extend_schema(
@ -108,9 +108,16 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout['operations'].append({
'id': new_operation.pk,
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y']
})
oss.update_layout(layout)
schema = new_operation.result
if schema is not None:
connected_operations = \
@ -164,9 +171,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object())
operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result
layout = serializer.validated_data['layout']
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk]
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
oss.update_layout(layout)
if old_schema is not None:
if serializer.validated_data['delete_schema']:
m.PropagationFacade.before_delete_schema(old_schema)
@ -211,7 +220,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
oss.update_layout(serializer.validated_data['layout'])
schema = oss.create_input(operation)
return Response(
@ -262,7 +271,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
if old_schema.is_synced(oss.model):
old_schema.visible = True
old_schema.save(update_fields=['visible'])
oss.update_positions(serializer.validated_data['positions'])
oss.update_layout(serializer.validated_data['layout'])
oss.set_input(target_operation.pk, schema)
return Response(
status=c.HTTP_200_OK,
@ -292,7 +301,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
oss.update_layout(serializer.validated_data['layout'])
operation.alias = serializer.validated_data['item_data']['alias']
operation.title = serializer.validated_data['item_data']['title']
operation.description = serializer.validated_data['item_data']['description']
@ -346,7 +355,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
oss.update_layout(serializer.validated_data['layout'])
oss.execute_operation(operation)
return Response(

View File

@ -39,7 +39,7 @@ export const ossApi = {
});
},
updatePositions: ({
updateLayout: ({
itemID,
positions,
isSilent
@ -49,7 +49,7 @@ export const ossApi = {
isSilent?: boolean;
}) =>
axiosPatch({
endpoint: `/api/oss/${itemID}/update-positions`,
endpoint: `/api/oss/${itemID}/update-layout`,
request: {
data: { positions: positions },
successMessage: isSilent ? undefined : infoMsg.changesSaved

View File

@ -7,17 +7,17 @@ import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type IOperationPosition } from './types';
export const useUpdatePositions = () => {
export const useUpdateLayout = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-positions'],
mutationFn: ossApi.updatePositions,
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-layout'],
mutationFn: ossApi.updateLayout,
onSuccess: (_, variables) => updateTimestamp(variables.itemID),
onError: () => client.invalidateQueries()
});
return {
updatePositions: (data: {
updateLayout: (data: {
itemID: number; //
positions: IOperationPosition[];
isSilent?: boolean;

View File

@ -18,7 +18,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICstRelocateDTO, type IOperationPosition, schemaCstRelocate } from '../backend/types';
import { useRelocateConstituents } from '../backend/use-relocate-constituents';
import { useUpdatePositions } from '../backend/use-update-positions';
import { useUpdateLayout } from '../backend/use-update-layout';
import { IconRelocationUp } from '../components/icon-relocation-up';
import { type IOperation, type IOperationSchema } from '../models/oss';
import { getRelocateCandidates } from '../models/oss-api';
@ -32,7 +32,7 @@ export interface DlgRelocateConstituentsProps {
export function DlgRelocateConstituents() {
const { oss, initialTarget, positions } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
const { items: libraryItems } = useLibrary();
const { updatePositions } = useUpdatePositions();
const { updateLayout: updatePositions } = useUpdateLayout();
const { relocateConstituents } = useRelocateConstituents();
const {

View File

@ -16,7 +16,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdatePositions } from '../../../backend/use-update-positions';
import { useUpdateLayout } from '../../../backend/use-update-layout';
import { GRID_SIZE } from '../../../models/oss-api';
import { type OssNode } from '../../../models/oss-layout';
import { useOperationTooltipStore } from '../../../stores/operation-tooltip';
@ -53,7 +53,7 @@ export function OssFlow() {
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
const getPositions = useGetPositions();
const { updatePositions } = useUpdatePositions();
const { updateLayout: updatePositions } = useUpdateLayout();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);

View File

@ -6,7 +6,7 @@ import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { useOperationExecute } from '@/features/oss/backend/use-operation-execute';
import { useUpdatePositions } from '@/features/oss/backend/use-update-positions';
import { useUpdateLayout } from '@/features/oss/backend/use-update-layout';
import { MiniButton } from '@/components/control';
import {
@ -62,7 +62,7 @@ export function ToolbarOssGraph({
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
const { updatePositions } = useUpdatePositions();
const { updateLayout: updatePositions } = useUpdateLayout();
const { operationExecute } = useOperationExecute();
const showEditOperation = useDialogsStore(state => state.showEditOperation);