mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Implementing Reference operation pt1
This commit is contained in:
parent
fc05d53d31
commit
a7428a4af4
|
@ -1,10 +1,12 @@
|
||||||
''' Admin view: Library. '''
|
''' Admin view: Library. '''
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.LibraryItem)
|
||||||
class LibraryItemAdmin(admin.ModelAdmin):
|
class LibraryItemAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: LibraryItem. '''
|
''' Admin model: LibraryItem. '''
|
||||||
date_hierarchy = 'time_update'
|
date_hierarchy = 'time_update'
|
||||||
|
@ -17,6 +19,7 @@ class LibraryItemAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['alias', 'title', 'location']
|
search_fields = ['alias', 'title', 'location']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.LibraryTemplate)
|
||||||
class LibraryTemplateAdmin(admin.ModelAdmin):
|
class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: LibraryTemplate. '''
|
''' Admin model: LibraryTemplate. '''
|
||||||
list_display = ['id', 'alias']
|
list_display = ['id', 'alias']
|
||||||
|
@ -29,6 +32,7 @@ class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Editor)
|
||||||
class EditorAdmin(admin.ModelAdmin):
|
class EditorAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Editors. '''
|
''' Admin model: Editors. '''
|
||||||
list_display = ['id', 'item', 'editor']
|
list_display = ['id', 'item', 'editor']
|
||||||
|
@ -38,16 +42,10 @@ class EditorAdmin(admin.ModelAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Version)
|
||||||
class VersionAdmin(admin.ModelAdmin):
|
class VersionAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Versions. '''
|
''' Admin model: Versions. '''
|
||||||
list_display = ['id', 'item', 'version', 'description', 'time_create']
|
list_display = ['id', 'item', 'version', 'description', 'time_create']
|
||||||
search_fields = [
|
search_fields = [
|
||||||
'item__title', 'item__alias'
|
'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)
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib import admin
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Operation)
|
||||||
class OperationAdmin(admin.ModelAdmin):
|
class OperationAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Operation. '''
|
''' Admin model: Operation. '''
|
||||||
ordering = ['oss']
|
ordering = ['oss']
|
||||||
|
@ -19,6 +20,7 @@ class OperationAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['id', 'operation_type', 'title', 'alias']
|
search_fields = ['id', 'operation_type', 'title', 'alias']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Block)
|
||||||
class BlockAdmin(admin.ModelAdmin):
|
class BlockAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Block. '''
|
''' Admin model: Block. '''
|
||||||
ordering = ['oss']
|
ordering = ['oss']
|
||||||
|
@ -26,6 +28,7 @@ class BlockAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['oss']
|
search_fields = ['oss']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Layout)
|
||||||
class LayoutAdmin(admin.ModelAdmin):
|
class LayoutAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Layout. '''
|
''' Admin model: Layout. '''
|
||||||
ordering = ['oss']
|
ordering = ['oss']
|
||||||
|
@ -33,6 +36,7 @@ class LayoutAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['oss']
|
search_fields = ['oss']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Argument)
|
||||||
class ArgumentAdmin(admin.ModelAdmin):
|
class ArgumentAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Operation arguments. '''
|
''' Admin model: Operation arguments. '''
|
||||||
ordering = ['operation']
|
ordering = ['operation']
|
||||||
|
@ -40,6 +44,7 @@ class ArgumentAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['id', 'operation', 'argument']
|
search_fields = ['id', 'operation', 'argument']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Substitution)
|
||||||
class SynthesisSubstitutionAdmin(admin.ModelAdmin):
|
class SynthesisSubstitutionAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Substitutions as part of Synthesis operation. '''
|
''' Admin model: Substitutions as part of Synthesis operation. '''
|
||||||
ordering = ['operation']
|
ordering = ['operation']
|
||||||
|
@ -47,6 +52,7 @@ class SynthesisSubstitutionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['id', 'operation', 'original', 'substitution']
|
search_fields = ['id', 'operation', 'original', 'substitution']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Inheritance)
|
||||||
class InheritanceAdmin(admin.ModelAdmin):
|
class InheritanceAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Inheritance. '''
|
''' Admin model: Inheritance. '''
|
||||||
ordering = ['operation']
|
ordering = ['operation']
|
||||||
|
@ -54,9 +60,9 @@ class InheritanceAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['id', 'operation', 'parent', 'child']
|
search_fields = ['id', 'operation', 'parent', 'child']
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.Operation, OperationAdmin)
|
@admin.register(models.Reference)
|
||||||
admin.site.register(models.Block, BlockAdmin)
|
class ReferenceAdmin(admin.ModelAdmin):
|
||||||
admin.site.register(models.Layout, LayoutAdmin)
|
''' Admin model: Reference. '''
|
||||||
admin.site.register(models.Argument, ArgumentAdmin)
|
ordering = ['reference', 'target']
|
||||||
admin.site.register(models.Substitution, SynthesisSubstitutionAdmin)
|
list_display = ['id', 'reference', 'target']
|
||||||
admin.site.register(models.Inheritance, InheritanceAdmin)
|
search_fields = ['id', 'reference', 'target']
|
||||||
|
|
|
@ -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='Тип'),
|
||||||
|
),
|
||||||
|
]
|
27
rsconcept/backend/apps/oss/migrations/0015_reference.py
Normal file
27
rsconcept/backend/apps/oss/migrations/0015_reference.py
Normal 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')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,5 +1,7 @@
|
||||||
''' Models: Operation in OSS. '''
|
''' Models: Operation in OSS. '''
|
||||||
# pylint: disable=duplicate-code
|
# pylint: disable=duplicate-code
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
CASCADE,
|
CASCADE,
|
||||||
SET_NULL,
|
SET_NULL,
|
||||||
|
@ -11,7 +13,10 @@ from django.db.models import (
|
||||||
TextField
|
TextField
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
|
from .Reference import Reference
|
||||||
from .Substitution import Substitution
|
from .Substitution import Substitution
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +24,7 @@ class OperationType(TextChoices):
|
||||||
''' Type of operation. '''
|
''' Type of operation. '''
|
||||||
INPUT = 'input'
|
INPUT = 'input'
|
||||||
SYNTHESIS = 'synthesis'
|
SYNTHESIS = 'synthesis'
|
||||||
|
REFERENCE = 'reference'
|
||||||
|
|
||||||
|
|
||||||
class Operation(Model):
|
class Operation(Model):
|
||||||
|
@ -76,9 +82,37 @@ class Operation(Model):
|
||||||
return f'Операция {self.alias}'
|
return f'Операция {self.alias}'
|
||||||
|
|
||||||
def getQ_arguments(self) -> QuerySet[Argument]:
|
def getQ_arguments(self) -> QuerySet[Argument]:
|
||||||
''' Operation arguments. '''
|
''' Operation Arguments for current operation. '''
|
||||||
return Argument.objects.filter(operation=self)
|
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]:
|
def getQ_substitutions(self) -> QuerySet[Substitution]:
|
||||||
''' Operation substitutions. '''
|
''' Operation substitutions. '''
|
||||||
return Substitution.objects.filter(operation=self)
|
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)
|
||||||
|
|
|
@ -22,7 +22,8 @@ from .Argument import Argument
|
||||||
from .Block import Block
|
from .Block import Block
|
||||||
from .Inheritance import Inheritance
|
from .Inheritance import Inheritance
|
||||||
from .Layout import Layout
|
from .Layout import Layout
|
||||||
from .Operation import Operation
|
from .Operation import Operation, OperationType
|
||||||
|
from .Reference import Reference
|
||||||
from .Substitution import Substitution
|
from .Substitution import Substitution
|
||||||
|
|
||||||
CstMapping = dict[str, Optional[Constituenta]]
|
CstMapping = dict[str, Optional[Constituenta]]
|
||||||
|
@ -105,12 +106,41 @@ class OperationSchema:
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
return result
|
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:
|
def create_block(self, **kwargs) -> Block:
|
||||||
''' Create Block. '''
|
''' Create Block. '''
|
||||||
result = Block.objects.create(oss=self.model, **kwargs)
|
result = Block.objects.create(oss=self.model, **kwargs)
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
return result
|
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):
|
def delete_operation(self, target: int, keep_constituents: bool = False):
|
||||||
''' Delete Operation. '''
|
''' Delete Operation. '''
|
||||||
self.cache.ensure_loaded()
|
self.cache.ensure_loaded()
|
||||||
|
@ -167,12 +197,12 @@ class OperationSchema:
|
||||||
self.before_delete_cst(old_schema, old_schema.cache.constituents)
|
self.before_delete_cst(old_schema, old_schema.cache.constituents)
|
||||||
self.cache.remove_schema(old_schema)
|
self.cache.remove_schema(old_schema)
|
||||||
|
|
||||||
operation.result = schema
|
operation.setQ_result(schema)
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
operation.alias = schema.alias
|
operation.alias = schema.alias
|
||||||
operation.title = schema.title
|
operation.title = schema.title
|
||||||
operation.description = schema.description
|
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:
|
if schema is not None and has_children:
|
||||||
rsform = RSForm(schema)
|
rsform = RSForm(schema)
|
||||||
|
@ -263,8 +293,7 @@ class OperationSchema:
|
||||||
location=self.model.location
|
location=self.model.location
|
||||||
)
|
)
|
||||||
Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True))
|
Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True))
|
||||||
operation.result = schema.model
|
operation.setQ_result(schema.model)
|
||||||
operation.save()
|
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
@ -926,6 +955,7 @@ class OssCache:
|
||||||
self.inheritance[target.operation_id].remove(target)
|
self.inheritance[target.operation_id].remove(target)
|
||||||
|
|
||||||
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
|
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]
|
operation = self.operation_by_id[sub.operation_id]
|
||||||
parents = self.graph.inputs[operation.pk]
|
parents = self.graph.inputs[operation.pk]
|
||||||
original_cst = None
|
original_cst = None
|
||||||
|
|
27
rsconcept/backend/apps/oss/models/Reference.py
Normal file
27
rsconcept/backend/apps/oss/models/Reference.py
Normal 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}'
|
|
@ -7,4 +7,5 @@ from .Layout import Layout
|
||||||
from .Operation import Operation, OperationType
|
from .Operation import Operation, OperationType
|
||||||
from .OperationSchema import OperationSchema
|
from .OperationSchema import OperationSchema
|
||||||
from .PropagationFacade import PropagationFacade
|
from .PropagationFacade import PropagationFacade
|
||||||
|
from .Reference import Reference
|
||||||
from .Substitution import Substitution
|
from .Substitution import Substitution
|
||||||
|
|
|
@ -6,10 +6,12 @@ from .data_access import (
|
||||||
BlockSerializer,
|
BlockSerializer,
|
||||||
CloneSchemaSerializer,
|
CloneSchemaSerializer,
|
||||||
CreateBlockSerializer,
|
CreateBlockSerializer,
|
||||||
|
CreateReferenceSerializer,
|
||||||
CreateSchemaSerializer,
|
CreateSchemaSerializer,
|
||||||
CreateSynthesisSerializer,
|
CreateSynthesisSerializer,
|
||||||
DeleteBlockSerializer,
|
DeleteBlockSerializer,
|
||||||
DeleteOperationSerializer,
|
DeleteOperationSerializer,
|
||||||
|
DeleteReferenceSerializer,
|
||||||
ImportSchemaSerializer,
|
ImportSchemaSerializer,
|
||||||
MoveItemsSerializer,
|
MoveItemsSerializer,
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
|
|
|
@ -234,6 +234,30 @@ class CloneSchemaSerializer(StrictSerializer):
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'source_operation': msg.operationResultEmpty(source_operation.alias)
|
'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
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
@ -267,7 +291,7 @@ class CreateSynthesisSerializer(StrictSerializer):
|
||||||
|
|
||||||
arguments = PKField(
|
arguments = PKField(
|
||||||
many=True,
|
many=True,
|
||||||
queryset=Operation.objects.all().only('pk')
|
queryset=Operation.objects.all().only('pk', 'result_id')
|
||||||
)
|
)
|
||||||
substitutions = serializers.ListField(
|
substitutions = serializers.ListField(
|
||||||
child=SubstitutionSerializerBase(),
|
child=SubstitutionSerializerBase(),
|
||||||
|
@ -391,11 +415,11 @@ class UpdateOperationSerializer(StrictSerializer):
|
||||||
|
|
||||||
|
|
||||||
class DeleteOperationSerializer(StrictSerializer):
|
class DeleteOperationSerializer(StrictSerializer):
|
||||||
''' Serializer: Delete operation. '''
|
''' Serializer: Delete non-reference operation. '''
|
||||||
layout = serializers.ListField(
|
layout = serializers.ListField(
|
||||||
child=NodeSerializer()
|
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)
|
keep_constituents = serializers.BooleanField(default=False, required=False)
|
||||||
delete_schema = serializers.BooleanField(default=False, required=False)
|
delete_schema = serializers.BooleanField(default=False, required=False)
|
||||||
|
|
||||||
|
@ -406,6 +430,32 @@ class DeleteOperationSerializer(StrictSerializer):
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'target': msg.operationNotInOSS()
|
'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
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
class TestChangeOperations(EndpointTester):
|
class TestChangeOperations(EndpointTester):
|
||||||
''' Testing Operations change propagation in OSS. '''
|
''' Testing Operations change propagation in OSS. '''
|
||||||
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = OperationSchema.create(
|
self.owned = OperationSchema.create(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
''' Testing API: Operation Schema - operations manipulation. '''
|
''' Testing API: Operation Schema - operations manipulation. '''
|
||||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
|
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 apps.rsform.models import Constituenta, RSForm
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
|
|
||||||
|
@ -54,6 +54,11 @@ class TestOssOperations(EndpointTester):
|
||||||
alias='3',
|
alias='3',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
|
self.unowned_operation = self.unowned.create_operation(
|
||||||
|
alias='42',
|
||||||
|
operation_type=OperationType.INPUT,
|
||||||
|
result=None
|
||||||
|
)
|
||||||
self.layout_data = [
|
self.layout_data = [
|
||||||
{'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
{'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},
|
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
|
||||||
|
@ -69,7 +74,6 @@ class TestOssOperations(EndpointTester):
|
||||||
'substitution': self.ks2X1
|
'substitution': self.ks2X1
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
|
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
|
||||||
def test_create_schema(self):
|
def test_create_schema(self):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
@ -165,6 +169,10 @@ class TestOssOperations(EndpointTester):
|
||||||
self.assertEqual(new_schema.description, new_operation['description'])
|
self.assertEqual(new_schema.description, new_operation['description'])
|
||||||
self.assertEqual(self.ks1.constituents().count(), RSForm(new_schema).constituents().count())
|
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')
|
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
|
||||||
def test_create_schema_parent(self):
|
def test_create_schema_parent(self):
|
||||||
|
@ -201,6 +209,37 @@ class TestOssOperations(EndpointTester):
|
||||||
self.assertEqual(new_operation['parent'], block_owned.id)
|
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')
|
@decl_endpoint('/api/oss/{item}/create-synthesis', method='post')
|
||||||
def test_create_synthesis(self):
|
def test_create_synthesis(self):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
@ -242,6 +281,9 @@ class TestOssOperations(EndpointTester):
|
||||||
}
|
}
|
||||||
self.executeBadData(data=data)
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
|
data['target'] = self.unowned_operation.pk
|
||||||
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
data['target'] = self.operation1.pk
|
data['target'] = self.operation1.pk
|
||||||
self.toggle_admin(True)
|
self.toggle_admin(True)
|
||||||
self.executeBadData(data=data, item=self.unowned_id)
|
self.executeBadData(data=data, item=self.unowned_id)
|
||||||
|
@ -256,6 +298,39 @@ class TestOssOperations(EndpointTester):
|
||||||
self.assertEqual(len(deleted_items), 0)
|
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')
|
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
|
||||||
def test_create_input(self):
|
def test_create_input(self):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
@ -291,6 +366,9 @@ class TestOssOperations(EndpointTester):
|
||||||
data['target'] = self.operation3.pk
|
data['target'] = self.operation3.pk
|
||||||
self.executeBadData(data=data)
|
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')
|
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||||
def test_set_input_null(self):
|
def test_set_input_null(self):
|
||||||
|
@ -411,6 +489,10 @@ class TestOssOperations(EndpointTester):
|
||||||
data['layout'] = self.layout_data
|
data['layout'] = self.layout_data
|
||||||
self.executeOK(data=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')
|
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||||
def test_update_operation_sync(self):
|
def test_update_operation_sync(self):
|
||||||
|
@ -418,7 +500,7 @@ class TestOssOperations(EndpointTester):
|
||||||
self.executeBadData(item=self.owned_id)
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'target': self.operation1.pk,
|
'target': self.unowned_operation.pk,
|
||||||
'item_data': {
|
'item_data': {
|
||||||
'alias': 'Test3 mod',
|
'alias': 'Test3 mod',
|
||||||
'title': 'Test title mod',
|
'title': 'Test title mod',
|
||||||
|
@ -426,7 +508,9 @@ class TestOssOperations(EndpointTester):
|
||||||
},
|
},
|
||||||
'layout': self.layout_data
|
'layout': self.layout_data
|
||||||
}
|
}
|
||||||
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
data['target'] = self.operation1.pk
|
||||||
response = self.executeOK(data=data)
|
response = self.executeOK(data=data)
|
||||||
self.operation1.refresh_from_db()
|
self.operation1.refresh_from_db()
|
||||||
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
|
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.title, data['item_data']['title'])
|
||||||
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
|
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')
|
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||||
def test_update_operation_invalid_substitution(self):
|
def test_update_operation_invalid_substitution(self):
|
||||||
|
@ -477,6 +566,9 @@ class TestOssOperations(EndpointTester):
|
||||||
}
|
}
|
||||||
self.executeBadData(data=data)
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
|
data['target'] = self.unowned_operation.pk
|
||||||
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
data['target'] = self.operation3.pk
|
data['target'] = self.operation3.pk
|
||||||
self.toggle_admin(True)
|
self.toggle_admin(True)
|
||||||
self.executeBadData(data=data, item=self.unowned_id)
|
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.access_policy, self.owned.model.access_policy)
|
||||||
self.assertEqual(schema.location, self.owned.model.location)
|
self.assertEqual(schema.location, self.owned.model.location)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
|
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
|
||||||
def test_import_schema_bad_data(self):
|
def test_import_schema_bad_data(self):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
|
|
@ -66,9 +66,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
'create_schema',
|
'create_schema',
|
||||||
'clone_schema',
|
'clone_schema',
|
||||||
'import_schema',
|
'import_schema',
|
||||||
|
'create_reference',
|
||||||
'create_synthesis',
|
'create_synthesis',
|
||||||
'update_operation',
|
'update_operation',
|
||||||
'delete_operation',
|
'delete_operation',
|
||||||
|
'delete_reference',
|
||||||
'create_input',
|
'create_input',
|
||||||
'set_input',
|
'set_input',
|
||||||
'execute_operation',
|
'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(
|
@extend_schema(
|
||||||
summary='create synthesis operation',
|
summary='create synthesis operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
|
@ -596,6 +643,39 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
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(
|
@extend_schema(
|
||||||
summary='create input schema for target operation',
|
summary='create input schema for target operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
|
|
|
@ -64,7 +64,7 @@ class RSForm:
|
||||||
def refresh_from_db(self) -> None:
|
def refresh_from_db(self) -> None:
|
||||||
''' Model wrapper. '''
|
''' Model wrapper. '''
|
||||||
self.model.refresh_from_db()
|
self.model.refresh_from_db()
|
||||||
self.cache = RSFormCache(self)
|
self.cache.is_loaded = False
|
||||||
|
|
||||||
def constituents(self) -> QuerySet[Constituenta]:
|
def constituents(self) -> QuerySet[Constituenta]:
|
||||||
''' Get QuerySet containing all constituents of current RSForm. '''
|
''' Get QuerySet containing all constituents of current RSForm. '''
|
||||||
|
|
|
@ -4,8 +4,10 @@ from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
admin.site.unregister(User)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(User)
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
''' Admin model: User. '''
|
''' Admin model: User. '''
|
||||||
fieldsets = UserAdmin.fieldsets
|
fieldsets = UserAdmin.fieldsets
|
||||||
|
@ -21,7 +23,3 @@ class CustomUserAdmin(UserAdmin):
|
||||||
ordering = ['date_joined', 'username']
|
ordering = ['date_joined', 'username']
|
||||||
search_fields = ['email', 'first_name', 'last_name', 'username']
|
search_fields = ['email', 'first_name', 'last_name', 'username']
|
||||||
list_filter = ['is_staff', 'is_superuser', 'is_active']
|
list_filter = ['is_staff', 'is_superuser', 'is_active']
|
||||||
|
|
||||||
|
|
||||||
admin.site.unregister(User)
|
|
||||||
admin.site.register(User, CustomUserAdmin)
|
|
||||||
|
|
|
@ -86,6 +86,14 @@ def operationInputAlreadyConnected():
|
||||||
return 'Схема уже подключена к другой операции'
|
return 'Схема уже подключена к другой операции'
|
||||||
|
|
||||||
|
|
||||||
|
def referenceTypeNotAllowed():
|
||||||
|
return 'Ссылки не поддерживаются'
|
||||||
|
|
||||||
|
|
||||||
|
def referenceTypeRequired():
|
||||||
|
return 'Операция должна быть ссылкой'
|
||||||
|
|
||||||
|
|
||||||
def operationNotSynthesis(title: str):
|
def operationNotSynthesis(title: str):
|
||||||
return f'Операция не является Синтезом: {title}'
|
return f'Операция не является Синтезом: {title}'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user