F: Implementing Reference operation pt1
Some checks are pending
Backend CI / build (3.12) (push) Waiting to run
Backend CI / notify-failure (push) Blocked by required conditions

This commit is contained in:
Ivan 2025-07-31 20:23:35 +03:00
parent fc05d53d31
commit a7428a4af4
16 changed files with 402 additions and 31 deletions

View File

@ -1,10 +1,12 @@
''' Admin view: Library. '''
from typing import cast
from django.contrib import admin
from . import models
@admin.register(models.LibraryItem)
class LibraryItemAdmin(admin.ModelAdmin):
''' Admin model: LibraryItem. '''
date_hierarchy = 'time_update'
@ -17,6 +19,7 @@ class LibraryItemAdmin(admin.ModelAdmin):
search_fields = ['alias', 'title', 'location']
@admin.register(models.LibraryTemplate)
class LibraryTemplateAdmin(admin.ModelAdmin):
''' Admin model: LibraryTemplate. '''
list_display = ['id', 'alias']
@ -29,6 +32,7 @@ class LibraryTemplateAdmin(admin.ModelAdmin):
return 'N/A'
@admin.register(models.Editor)
class EditorAdmin(admin.ModelAdmin):
''' Admin model: Editors. '''
list_display = ['id', 'item', 'editor']
@ -38,16 +42,10 @@ class EditorAdmin(admin.ModelAdmin):
]
@admin.register(models.Version)
class VersionAdmin(admin.ModelAdmin):
''' Admin model: Versions. '''
list_display = ['id', 'item', 'version', 'description', 'time_create']
search_fields = [
'item__title', 'item__alias'
]
admin.site.register(models.LibraryItem, LibraryItemAdmin)
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
admin.site.register(models.Version, VersionAdmin)
admin.site.register(models.Editor, EditorAdmin)

View File

@ -4,6 +4,7 @@ from django.contrib import admin
from . import models
@admin.register(models.Operation)
class OperationAdmin(admin.ModelAdmin):
''' Admin model: Operation. '''
ordering = ['oss']
@ -19,6 +20,7 @@ class OperationAdmin(admin.ModelAdmin):
search_fields = ['id', 'operation_type', 'title', 'alias']
@admin.register(models.Block)
class BlockAdmin(admin.ModelAdmin):
''' Admin model: Block. '''
ordering = ['oss']
@ -26,6 +28,7 @@ class BlockAdmin(admin.ModelAdmin):
search_fields = ['oss']
@admin.register(models.Layout)
class LayoutAdmin(admin.ModelAdmin):
''' Admin model: Layout. '''
ordering = ['oss']
@ -33,6 +36,7 @@ class LayoutAdmin(admin.ModelAdmin):
search_fields = ['oss']
@admin.register(models.Argument)
class ArgumentAdmin(admin.ModelAdmin):
''' Admin model: Operation arguments. '''
ordering = ['operation']
@ -40,6 +44,7 @@ class ArgumentAdmin(admin.ModelAdmin):
search_fields = ['id', 'operation', 'argument']
@admin.register(models.Substitution)
class SynthesisSubstitutionAdmin(admin.ModelAdmin):
''' Admin model: Substitutions as part of Synthesis operation. '''
ordering = ['operation']
@ -47,6 +52,7 @@ class SynthesisSubstitutionAdmin(admin.ModelAdmin):
search_fields = ['id', 'operation', 'original', 'substitution']
@admin.register(models.Inheritance)
class InheritanceAdmin(admin.ModelAdmin):
''' Admin model: Inheritance. '''
ordering = ['operation']
@ -54,9 +60,9 @@ class InheritanceAdmin(admin.ModelAdmin):
search_fields = ['id', 'operation', 'parent', 'child']
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)
@admin.register(models.Reference)
class ReferenceAdmin(admin.ModelAdmin):
''' Admin model: Reference. '''
ordering = ['reference', 'target']
list_display = ['id', 'reference', 'target']
search_fields = ['id', 'reference', 'target']

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-07-31 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('oss', '0013_alter_layout_data'),
]
operations = [
migrations.AlterField(
model_name='operation',
name='operation_type',
field=models.CharField(choices=[('input', 'Input'), ('synthesis', 'Synthesis'), ('reference', 'Reference')], default='input', max_length=10, verbose_name='Тип'),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 5.2.4 on 2025-07-31 08:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('oss', '0014_alter_operation_operation_type'),
]
operations = [
migrations.CreateModel(
name='Reference',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='references', to='oss.operation', verbose_name='Отсылка')),
('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='oss.operation', verbose_name='Целевая Операция')),
],
options={
'verbose_name': 'Отсылка',
'verbose_name_plural': 'Отсылки',
'unique_together': {('reference', 'target')},
},
),
]

View File

@ -1,5 +1,7 @@
''' Models: Operation in OSS. '''
# pylint: disable=duplicate-code
from typing import Optional
from django.db.models import (
CASCADE,
SET_NULL,
@ -11,7 +13,10 @@ from django.db.models import (
TextField
)
from apps.library.models import LibraryItem
from .Argument import Argument
from .Reference import Reference
from .Substitution import Substitution
@ -19,6 +24,7 @@ class OperationType(TextChoices):
''' Type of operation. '''
INPUT = 'input'
SYNTHESIS = 'synthesis'
REFERENCE = 'reference'
class Operation(Model):
@ -76,9 +82,37 @@ class Operation(Model):
return f'Операция {self.alias}'
def getQ_arguments(self) -> QuerySet[Argument]:
''' Operation arguments. '''
''' Operation Arguments for current operation. '''
return Argument.objects.filter(operation=self)
def getQ_as_argument(self) -> QuerySet[Argument]:
''' Operation Arguments where the operation is used as an argument. '''
return Argument.objects.filter(argument=self)
def getQ_substitutions(self) -> QuerySet[Substitution]:
''' Operation substitutions. '''
return Substitution.objects.filter(operation=self)
def getQ_references(self) -> QuerySet[Reference]:
''' Operation references. '''
return Reference.objects.filter(target=self)
def getQ_reference_target(self) -> list['Operation']:
''' Operation target for current reference. '''
return [x.target for x in Reference.objects.filter(reference=self)]
def setQ_result(self, result: Optional[LibraryItem]) -> None:
''' Set result schema. '''
if result == self.result:
return
self.result = result
self.save(update_fields=['result'])
for reference in self.getQ_references():
reference.reference.result = result
reference.reference.save(update_fields=['result'])
def delete(self, *args, **kwargs):
''' Delete operation. '''
for ref in self.getQ_references():
ref.reference.delete()
super().delete(*args, **kwargs)

View File

@ -22,7 +22,8 @@ from .Argument import Argument
from .Block import Block
from .Inheritance import Inheritance
from .Layout import Layout
from .Operation import Operation
from .Operation import Operation, OperationType
from .Reference import Reference
from .Substitution import Substitution
CstMapping = dict[str, Optional[Constituenta]]
@ -105,12 +106,41 @@ class OperationSchema:
self.save(update_fields=['time_update'])
return result
def create_reference(self, target: Operation) -> Operation:
''' Create Reference Operation. '''
result = Operation.objects.create(
oss=self.model,
operation_type=OperationType.REFERENCE,
result=target.result,
parent=target.parent
)
Reference.objects.create(reference=result, target=target)
self.save(update_fields=['time_update'])
return result
def create_block(self, **kwargs) -> Block:
''' Create Block. '''
result = Block.objects.create(oss=self.model, **kwargs)
self.save(update_fields=['time_update'])
return result
def delete_reference(self, target: Operation, keep_connections: bool = False):
''' Delete Reference Operation. '''
if keep_connections:
referred_operations = target.getQ_reference_target()
if len(referred_operations) == 1:
referred_operation = referred_operations[0]
for arg in target.getQ_as_argument():
arg.pk = None
arg.argument = referred_operation
arg.save()
else:
pass
# if target.result_id is not None:
# self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema
target.delete()
self.save(update_fields=['time_update'])
def delete_operation(self, target: int, keep_constituents: bool = False):
''' Delete Operation. '''
self.cache.ensure_loaded()
@ -167,12 +197,12 @@ class OperationSchema:
self.before_delete_cst(old_schema, old_schema.cache.constituents)
self.cache.remove_schema(old_schema)
operation.result = schema
operation.setQ_result(schema)
if schema is not None:
operation.alias = schema.alias
operation.title = schema.title
operation.description = schema.description
operation.save(update_fields=['result', 'alias', 'title', 'description'])
operation.save(update_fields=['alias', 'title', 'description'])
if schema is not None and has_children:
rsform = RSForm(schema)
@ -263,8 +293,7 @@ class OperationSchema:
location=self.model.location
)
Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True))
operation.result = schema.model
operation.save()
operation.setQ_result(schema.model)
self.save(update_fields=['time_update'])
return schema
@ -926,6 +955,7 @@ class OssCache:
self.inheritance[target.operation_id].remove(target)
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
''' Unfold substitution into original and substitution forms. '''
operation = self.operation_by_id[sub.operation_id]
parents = self.graph.inputs[operation.pk]
original_cst = None

View File

@ -0,0 +1,27 @@
''' Models: Operation Reference in OSS. '''
from django.db.models import CASCADE, ForeignKey, Model
class Reference(Model):
''' Operation Reference. '''
reference = ForeignKey(
verbose_name='Отсылка',
to='oss.Operation',
on_delete=CASCADE,
related_name='references'
)
target = ForeignKey(
verbose_name='Целевая Операция',
to='oss.Operation',
on_delete=CASCADE,
related_name='targets'
)
class Meta:
''' Model metadata. '''
verbose_name = 'Отсылка'
verbose_name_plural = 'Отсылки'
unique_together = [['reference', 'target']]
def __str__(self) -> str:
return f'{self.reference} -> {self.target}'

View File

@ -7,4 +7,5 @@ from .Layout import Layout
from .Operation import Operation, OperationType
from .OperationSchema import OperationSchema
from .PropagationFacade import PropagationFacade
from .Reference import Reference
from .Substitution import Substitution

View File

@ -6,10 +6,12 @@ from .data_access import (
BlockSerializer,
CloneSchemaSerializer,
CreateBlockSerializer,
CreateReferenceSerializer,
CreateSchemaSerializer,
CreateSynthesisSerializer,
DeleteBlockSerializer,
DeleteOperationSerializer,
DeleteReferenceSerializer,
ImportSchemaSerializer,
MoveItemsSerializer,
OperationSchemaSerializer,

View File

@ -234,6 +234,30 @@ class CloneSchemaSerializer(StrictSerializer):
raise serializers.ValidationError({
'source_operation': msg.operationResultEmpty(source_operation.alias)
})
if source_operation.operation_type == OperationType.REFERENCE:
raise serializers.ValidationError({
'source_operation': msg.referenceTypeNotAllowed()
})
return attrs
class CreateReferenceSerializer(StrictSerializer):
''' Serializer: Create reference operation. '''
layout = serializers.ListField(child=NodeSerializer())
target = PKField(many=False, queryset=Operation.objects.all())
position = PositionSerializer()
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
target = cast(Operation, attrs['target'])
if target.oss_id != oss.pk:
raise serializers.ValidationError({
'target_operation': msg.operationNotInOSS()
})
if target.operation_type == OperationType.REFERENCE:
raise serializers.ValidationError({
'target_operation': msg.referenceTypeNotAllowed()
})
return attrs
@ -267,7 +291,7 @@ class CreateSynthesisSerializer(StrictSerializer):
arguments = PKField(
many=True,
queryset=Operation.objects.all().only('pk')
queryset=Operation.objects.all().only('pk', 'result_id')
)
substitutions = serializers.ListField(
child=SubstitutionSerializerBase(),
@ -391,11 +415,11 @@ class UpdateOperationSerializer(StrictSerializer):
class DeleteOperationSerializer(StrictSerializer):
''' Serializer: Delete operation. '''
''' Serializer: Delete non-reference operation. '''
layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'operation_type', 'result'))
keep_constituents = serializers.BooleanField(default=False, required=False)
delete_schema = serializers.BooleanField(default=False, required=False)
@ -406,6 +430,32 @@ class DeleteOperationSerializer(StrictSerializer):
raise serializers.ValidationError({
'target': msg.operationNotInOSS()
})
if operation.operation_type == OperationType.REFERENCE:
raise serializers.ValidationError({
'target': msg.referenceTypeNotAllowed()
})
return attrs
class DeleteReferenceSerializer(StrictSerializer):
''' Serializer: Delete reference operation. '''
layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'operation_type'))
keep_connections = serializers.BooleanField(default=False, required=False)
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if operation.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.operationNotInOSS()
})
if operation.operation_type != OperationType.REFERENCE:
raise serializers.ValidationError({
'target': msg.referenceTypeRequired()
})
return attrs

View File

@ -8,7 +8,6 @@ from shared.EndpointTester import EndpointTester, decl_endpoint
class TestChangeOperations(EndpointTester):
''' Testing Operations change propagation in OSS. '''
def setUp(self):
super().setUp()
self.owned = OperationSchema.create(

View File

@ -1,6 +1,6 @@
''' Testing API: Operation Schema - operations manipulation. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
from apps.oss.models import Operation, OperationSchema, OperationType
from apps.oss.models import Operation, OperationSchema, OperationType, Reference
from apps.rsform.models import Constituenta, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
@ -54,6 +54,11 @@ class TestOssOperations(EndpointTester):
alias='3',
operation_type=OperationType.SYNTHESIS
)
self.unowned_operation = self.unowned.create_operation(
alias='42',
operation_type=OperationType.INPUT,
result=None
)
self.layout_data = [
{'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
@ -69,7 +74,6 @@ class TestOssOperations(EndpointTester):
'substitution': self.ks2X1
}])
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
def test_create_schema(self):
self.populateData()
@ -165,6 +169,10 @@ class TestOssOperations(EndpointTester):
self.assertEqual(new_schema.description, new_operation['description'])
self.assertEqual(self.ks1.constituents().count(), RSForm(new_schema).constituents().count())
unrelated_data = dict(data)
unrelated_data['source_operation'] = self.unowned_operation.pk
self.executeBadData(data=unrelated_data, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
def test_create_schema_parent(self):
@ -201,6 +209,37 @@ class TestOssOperations(EndpointTester):
self.assertEqual(new_operation['parent'], block_owned.id)
@decl_endpoint('/api/oss/{item}/create-reference', method='post')
def test_create_reference(self):
self.populateData()
data = {
'target': self.invalid_id,
'layout': self.layout_data,
'position': {
'x': 10,
'y': 20,
'width': 100,
'height': 40
}
}
self.executeBadData(data=data, item=self.owned_id)
data['target'] = self.unowned_operation.pk
self.executeBadData(data=data, item=self.owned_id)
data['target'] = self.operation1.pk
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
self.assertEqual(new_operation['operation_type'], OperationType.REFERENCE)
self.assertEqual(new_operation['parent'], self.operation1.parent_id)
self.assertEqual(new_operation['result'], self.operation1.result_id)
ref = Reference.objects.filter(reference_id=new_operation_id, target_id=self.operation1.pk).first()
self.assertIsNotNone(ref)
self.assertTrue(Operation.objects.filter(pk=new_operation_id, oss=self.owned.model).exists())
@decl_endpoint('/api/oss/{item}/create-synthesis', method='post')
def test_create_synthesis(self):
self.populateData()
@ -242,6 +281,9 @@ class TestOssOperations(EndpointTester):
}
self.executeBadData(data=data)
data['target'] = self.unowned_operation.pk
self.executeBadData(data=data, item=self.owned_id)
data['target'] = self.operation1.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
@ -256,6 +298,39 @@ class TestOssOperations(EndpointTester):
self.assertEqual(len(deleted_items), 0)
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
def test_delete_reference_operation_invalid(self):
self.populateData()
reference_operation = self.owned.create_reference(self.operation1)
data = {
'layout': self.layout_data,
'target': reference_operation.pk
}
self.executeBadData(data=data, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/delete-reference', method='patch')
def test_delete_reference_operation(self):
self.populateData()
data = {
'layout': self.layout_data,
'target': self.invalid_id
}
self.executeBadData(data=data, item=self.owned_id)
reference_operation = self.owned.create_reference(self.operation1)
self.assertEqual(len(self.operation1.getQ_references()), 1)
data['target'] = reference_operation.pk
self.executeForbidden(data=data, item=self.unowned_id)
data['target'] = self.operation1.pk
self.executeBadData(data=data, item=self.owned_id)
data['target'] = reference_operation.pk
self.executeOK(data=data, item=self.owned_id)
self.assertEqual(len(self.operation1.getQ_references()), 0)
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
def test_create_input(self):
self.populateData()
@ -291,6 +366,9 @@ class TestOssOperations(EndpointTester):
data['target'] = self.operation3.pk
self.executeBadData(data=data)
data['target'] = self.unowned_operation.pk
self.executeBadData(data=data, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_null(self):
@ -411,6 +489,10 @@ class TestOssOperations(EndpointTester):
data['layout'] = self.layout_data
self.executeOK(data=data)
data_bad = dict(data)
data_bad['target'] = self.unowned_operation.pk
self.executeBadData(data=data_bad, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_sync(self):
@ -418,7 +500,7 @@ class TestOssOperations(EndpointTester):
self.executeBadData(item=self.owned_id)
data = {
'target': self.operation1.pk,
'target': self.unowned_operation.pk,
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
@ -426,7 +508,9 @@ class TestOssOperations(EndpointTester):
},
'layout': self.layout_data
}
self.executeBadData(data=data, item=self.owned_id)
data['target'] = self.operation1.pk
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
@ -436,6 +520,11 @@ class TestOssOperations(EndpointTester):
self.assertEqual(self.operation1.result.title, data['item_data']['title'])
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
# Try to update an operation from an unrelated OSS (should fail)
data_bad = dict(data)
data_bad['target'] = self.unowned_operation.pk
self.executeBadData(data=data_bad, item=self.owned_id)
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_invalid_substitution(self):
@ -477,6 +566,9 @@ class TestOssOperations(EndpointTester):
}
self.executeBadData(data=data)
data['target'] = self.unowned_operation.pk
self.executeBadData(data=data, item=self.owned_id)
data['target'] = self.operation3.pk
self.toggle_admin(True)
self.executeBadData(data=data, item=self.unowned_id)
@ -583,6 +675,7 @@ class TestOssOperations(EndpointTester):
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
def test_import_schema_bad_data(self):
self.populateData()

View File

@ -66,9 +66,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'create_schema',
'clone_schema',
'import_schema',
'create_reference',
'create_synthesis',
'update_operation',
'delete_operation',
'delete_reference',
'create_input',
'set_input',
'execute_operation',
@ -453,6 +455,51 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
}
)
@extend_schema(
summary='create reference for operation',
tags=['OSS'],
request=s.CreateReferenceSerializer(),
responses={
c.HTTP_201_CREATED: s.OperationCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='create-reference')
def create_reference(self, request: Request, pk) -> HttpResponse:
''' Clone schema. '''
serializer = s.CreateReferenceSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
position = serializer.validated_data['position']
with transaction.atomic():
target = cast(m.Operation, serializer.validated_data['target'])
new_operation = oss.create_reference(target)
layout.append({
'nodeID': 'o' + str(new_operation.pk),
'x': position['x'],
'y': position['y'],
'width': position['width'],
'height': position['height']
})
oss.update_layout(layout)
oss.save(update_fields=['time_update'])
return Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': new_operation.pk,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='create synthesis operation',
tags=['OSS'],
@ -596,6 +643,39 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='delete reference',
tags=['OSS'],
request=s.DeleteReferenceSerializer(),
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='delete-reference')
def delete_reference(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Reference Operation. '''
serializer = s.DeleteReferenceSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
operation = cast(m.Operation, serializer.validated_data['target'])
layout = serializer.validated_data['layout']
layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic():
oss.update_layout(layout)
oss.delete_reference(operation, serializer.validated_data['keep_connections'])
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='create input schema for target operation',
tags=['OSS'],

View File

@ -64,7 +64,7 @@ class RSForm:
def refresh_from_db(self) -> None:
''' Model wrapper. '''
self.model.refresh_from_db()
self.cache = RSFormCache(self)
self.cache.is_loaded = False
def constituents(self) -> QuerySet[Constituenta]:
''' Get QuerySet containing all constituents of current RSForm. '''

View File

@ -4,8 +4,10 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
User = get_user_model()
admin.site.unregister(User)
@admin.register(User)
class CustomUserAdmin(UserAdmin):
''' Admin model: User. '''
fieldsets = UserAdmin.fieldsets
@ -21,7 +23,3 @@ class CustomUserAdmin(UserAdmin):
ordering = ['date_joined', 'username']
search_fields = ['email', 'first_name', 'last_name', 'username']
list_filter = ['is_staff', 'is_superuser', 'is_active']
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)

View File

@ -86,6 +86,14 @@ def operationInputAlreadyConnected():
return 'Схема уже подключена к другой операции'
def referenceTypeNotAllowed():
return 'Ссылки не поддерживаются'
def referenceTypeRequired():
return 'Операция должна быть ссылкой'
def operationNotSynthesis(title: str):
return f'Операция не является Синтезом: {title}'