mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-13 12:20:36 +03:00
F: Implementing Reference operation pt1
This commit is contained in:
parent
fc05d53d31
commit
a7428a4af4
|
@ -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)
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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. '''
|
||||
# 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)
|
||||
|
|
|
@ -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
|
||||
|
|
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 .OperationSchema import OperationSchema
|
||||
from .PropagationFacade import PropagationFacade
|
||||
from .Reference import Reference
|
||||
from .Substitution import Substitution
|
||||
|
|
|
@ -6,10 +6,12 @@ from .data_access import (
|
|||
BlockSerializer,
|
||||
CloneSchemaSerializer,
|
||||
CreateBlockSerializer,
|
||||
CreateReferenceSerializer,
|
||||
CreateSchemaSerializer,
|
||||
CreateSynthesisSerializer,
|
||||
DeleteBlockSerializer,
|
||||
DeleteOperationSerializer,
|
||||
DeleteReferenceSerializer,
|
||||
ImportSchemaSerializer,
|
||||
MoveItemsSerializer,
|
||||
OperationSchemaSerializer,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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. '''
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -86,6 +86,14 @@ def operationInputAlreadyConnected():
|
|||
return 'Схема уже подключена к другой операции'
|
||||
|
||||
|
||||
def referenceTypeNotAllowed():
|
||||
return 'Ссылки не поддерживаются'
|
||||
|
||||
|
||||
def referenceTypeRequired():
|
||||
return 'Операция должна быть ссылкой'
|
||||
|
||||
|
||||
def operationNotSynthesis(title: str):
|
||||
return f'Операция не является Синтезом: {title}'
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user