Portal/rsconcept/backend/apps/oss/views/oss.py

556 lines
22 KiB
Python
Raw Normal View History

''' Endpoints for OSS. '''
2024-08-14 23:36:52 +03:00
from typing import Optional, cast
2024-07-19 19:28:55 +03:00
from django.db import transaction
2024-08-14 23:36:52 +03:00
from django.db.models import Q
2024-08-07 21:54:14 +03:00
from django.http import HttpResponse
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import generics, serializers
from rest_framework import status as c
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer
2024-10-28 14:52:30 +03:00
from apps.rsform.models import Constituenta, RSForm
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
from shared import permissions
from .. import models as m
from .. import serializers as s
@extend_schema(tags=['OSS'])
@extend_schema_view()
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
''' Endpoint: OperationSchema. '''
queryset = LibraryItem.objects.filter(item_type=LibraryItemType.OPERATION_SCHEMA)
serializer_class = LibraryItemSerializer
def _get_item(self) -> LibraryItem:
return cast(LibraryItem, self.get_object())
def get_permissions(self):
''' Determine permission class. '''
if self.action in [
2025-04-06 13:28:00 +03:00
'update_layout',
2025-04-14 23:02:35 +03:00
'create_block',
2025-04-20 18:06:18 +03:00
'update_block',
'delete_block',
2025-04-20 18:06:18 +03:00
'create_operation',
'update_operation',
2025-04-20 18:06:18 +03:00
'delete_operation',
2024-07-28 21:29:46 +03:00
'create_input',
2024-07-29 22:30:24 +03:00
'set_input',
2024-10-28 14:52:30 +03:00
'execute_operation',
'relocate_constituents'
]:
permission_list = [permissions.ItemEditor]
elif self.action in ['details']:
permission_list = [permissions.ItemAnyone]
elif self.action in ['get_predecessor']:
permission_list = [permissions.Anyone]
else:
permission_list = [permissions.Anyone]
return [permission() for permission in permission_list]
@extend_schema(
summary='get operations data',
tags=['OSS'],
request=None,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['get'], url_path='details')
2024-08-07 21:54:14 +03:00
def details(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Detailed OSS data. '''
serializer = s.OperationSchemaSerializer(self._get_item())
return Response(
status=c.HTTP_200_OK,
data=serializer.data
)
2024-07-19 19:28:55 +03:00
@extend_schema(
2025-04-06 13:28:00 +03:00
summary='update layout',
2024-07-19 19:28:55 +03:00
tags=['OSS'],
2025-04-06 13:28:00 +03:00
request=s.LayoutSerializer,
2024-07-19 19:28:55 +03:00
responses={
c.HTTP_200_OK: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
2025-04-06 13:28:00 +03:00
@action(detail=True, methods=['patch'], url_path='update-layout')
def update_layout(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Update schema layout. '''
serializer = s.LayoutSerializer(data=request.data)
2024-07-19 19:28:55 +03:00
serializer.is_valid(raise_exception=True)
2025-04-06 13:28:00 +03:00
m.OperationSchema(self.get_object()).update_layout(serializer.validated_data)
2024-07-19 19:28:55 +03:00
return Response(status=c.HTTP_200_OK)
2025-04-14 23:02:35 +03:00
@extend_schema(
summary='create block',
tags=['OSS'],
request=s.CreateBlockSerializer(),
2025-04-14 23:02:35 +03:00
responses={
2025-04-20 18:06:18 +03:00
c.HTTP_201_CREATED: s.BlockCreatedResponse,
2025-04-14 23:02:35 +03:00
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-block')
def create_block(self, request: Request, pk) -> HttpResponse:
''' Create Block. '''
serializer = s.CreateBlockSerializer(
2025-04-14 23:02:35 +03:00
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']
children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic():
new_block = oss.create_block(**serializer.validated_data['item_data'])
layout['blocks'].append({
'id': new_block.pk,
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height'],
})
oss.update_layout(layout)
if len(children_blocks) > 0:
for block in children_blocks:
block.parent = new_block
m.Block.objects.bulk_update(children_blocks, ['parent'])
if len(children_operations) > 0:
for operation in children_operations:
operation.parent = new_block
m.Operation.objects.bulk_update(children_operations, ['parent'])
return Response(
status=c.HTTP_201_CREATED,
data={
'new_block': s.BlockSerializer(new_block).data,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
2024-07-19 19:28:55 +03:00
@extend_schema(
2025-04-20 18:06:18 +03:00
summary='update block',
2024-07-19 19:28:55 +03:00
tags=['OSS'],
2025-04-20 18:06:18 +03:00
request=s.UpdateBlockSerializer(),
2024-07-19 19:28:55 +03:00
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
2025-04-20 18:06:18 +03:00
@action(detail=True, methods=['patch'], url_path='update-block')
def update_block(self, request: Request, pk) -> HttpResponse:
''' Update Block. '''
serializer = s.UpdateBlockSerializer(
2024-07-19 19:28:55 +03:00
data=request.data,
context={'oss': self.get_object()}
2024-07-19 19:28:55 +03:00
)
serializer.is_valid(raise_exception=True)
2025-04-20 18:06:18 +03:00
block: m.Block = cast(m.Block, serializer.validated_data['target'])
oss = m.OperationSchema(self.get_object())
2024-07-19 19:28:55 +03:00
with transaction.atomic():
2025-04-20 18:06:18 +03:00
if 'layout' in serializer.validated_data:
oss.update_layout(serializer.validated_data['layout'])
block.title = serializer.validated_data['item_data']['title']
block.description = serializer.validated_data['item_data']['description']
block.parent = serializer.validated_data['item_data']['parent']
block.save(update_fields=['title', 'description', 'parent'])
2024-07-19 19:28:55 +03:00
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
2024-07-19 19:28:55 +03:00
)
@extend_schema(
summary='delete block',
tags=['OSS'],
request=s.DeleteBlockSerializer,
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-block')
def delete_block(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Block. '''
serializer = s.DeleteBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
block = cast(m.Block, serializer.validated_data['target'])
layout = serializer.validated_data['layout']
layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk]
with transaction.atomic():
oss.delete_block(block)
oss.update_layout(layout)
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
2025-04-20 18:06:18 +03:00
@extend_schema(
summary='create operation',
tags=['OSS'],
request=s.CreateOperationSerializer(),
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-operation')
def create_operation(self, request: Request, pk) -> HttpResponse:
''' Create Operation. '''
serializer = s.CreateOperationSerializer(
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']
with transaction.atomic():
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout['operations'].append({
'id': new_operation.pk,
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y']
})
oss.update_layout(layout)
schema = new_operation.result
if schema is not None:
connected_operations = \
m.Operation.objects \
.filter(Q(result=schema) & ~Q(pk=new_operation.pk)) \
.only('operation_type', 'oss_id')
for operation in connected_operations:
if operation.operation_type != m.OperationType.INPUT:
raise serializers.ValidationError({
'item_data': msg.operationResultFromAnotherOSS()
})
if operation.oss_id == new_operation.oss_id:
raise serializers.ValidationError({
'item_data': msg.operationInputAlreadyConnected()
})
if new_operation.operation_type == m.OperationType.INPUT and serializer.validated_data['create_schema']:
oss.create_input(new_operation)
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
oss.set_arguments(
target=new_operation.pk,
arguments=serializer.validated_data['arguments']
)
return Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': s.OperationSerializer(new_operation).data,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='update operation',
tags=['OSS'],
request=s.UpdateOperationSerializer(),
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='update-operation')
def update_operation(self, request: Request, pk) -> HttpResponse:
''' Update Operation arguments and parameters. '''
serializer = s.UpdateOperationSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
if 'layout' in serializer.validated_data:
oss.update_layout(serializer.validated_data['layout'])
operation.alias = serializer.validated_data['item_data']['alias']
operation.title = serializer.validated_data['item_data']['title']
operation.description = serializer.validated_data['item_data']['description']
operation.save(update_fields=['alias', 'title', 'description'])
if operation.result is not None:
can_edit = permissions.can_edit_item(request.user, operation.result)
if can_edit or operation.operation_type == m.OperationType.SYNTHESIS:
operation.result.alias = operation.alias
operation.result.title = operation.title
operation.result.description = operation.description
operation.result.save()
if 'arguments' in serializer.validated_data:
oss.set_arguments(operation.pk, serializer.validated_data['arguments'])
if 'substitutions' in serializer.validated_data:
oss.set_substitutions(operation.pk, serializer.validated_data['substitutions'])
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
2025-04-20 18:06:18 +03:00
summary='delete operation',
tags=['OSS'],
2025-04-20 18:06:18 +03:00
request=s.DeleteOperationSerializer,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
2025-04-20 18:06:18 +03:00
@action(detail=True, methods=['patch'], url_path='delete-operation')
def delete_operation(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Operation. '''
serializer = s.DeleteOperationSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
2025-04-20 18:06:18 +03:00
operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result
layout = serializer.validated_data['layout']
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk]
with transaction.atomic():
2025-04-20 18:06:18 +03:00
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
oss.update_layout(layout)
if old_schema is not None:
if serializer.validated_data['delete_schema']:
m.PropagationFacade.before_delete_schema(old_schema)
old_schema.delete()
elif old_schema.is_synced(oss.model):
old_schema.visible = True
old_schema.save(update_fields=['visible'])
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='create input schema for target operation',
tags=['OSS'],
2025-04-20 18:06:18 +03:00
request=s.TargetOperationSerializer(),
responses={
2025-04-20 18:06:18 +03:00
c.HTTP_200_OK: s.SchemaCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='create-input')
2024-08-07 21:54:14 +03:00
def create_input(self, request: Request, pk) -> HttpResponse:
''' Create input RSForm. '''
2025-04-20 18:06:18 +03:00
serializer = s.TargetOperationSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
2025-03-19 13:23:32 +03:00
if len(operation.getQ_arguments()) > 0:
raise serializers.ValidationError({
2025-03-19 13:23:32 +03:00
'target': msg.operationHasArguments(operation.alias)
})
if operation.result is not None:
raise serializers.ValidationError({
'target': msg.operationResultNotEmpty(operation.alias)
})
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
2025-04-06 13:28:00 +03:00
oss.update_layout(serializer.validated_data['layout'])
schema = oss.create_input(operation)
return Response(
status=c.HTTP_200_OK,
data={
'new_schema': LibraryItemSerializer(schema.model).data,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
2024-07-28 21:29:46 +03:00
@extend_schema(
summary='set input schema for target operation',
tags=['OSS'],
request=s.SetOperationInputSerializer(),
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='set-input')
2024-08-07 21:54:14 +03:00
def set_input(self, request: Request, pk) -> HttpResponse:
2024-07-28 21:29:46 +03:00
''' Set input schema for target operation. '''
serializer = s.SetOperationInputSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
2024-08-14 23:36:52 +03:00
target_operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
schema: Optional[LibraryItem] = serializer.validated_data['input']
if schema is not None:
connected_operations = m.Operation.objects.filter(result=schema).only('operation_type', 'oss_id')
for operation in connected_operations:
if operation.operation_type != m.OperationType.INPUT:
raise serializers.ValidationError({
'input': msg.operationResultFromAnotherOSS()
})
if operation != target_operation and operation.oss_id == target_operation.oss_id:
raise serializers.ValidationError({
'input': msg.operationInputAlreadyConnected()
})
2024-07-28 21:29:46 +03:00
oss = m.OperationSchema(self.get_object())
old_schema = target_operation.result
2024-07-28 21:29:46 +03:00
with transaction.atomic():
if old_schema is not None:
if old_schema.is_synced(oss.model):
old_schema.visible = True
old_schema.save(update_fields=['visible'])
2025-04-06 13:28:00 +03:00
oss.update_layout(serializer.validated_data['layout'])
2024-08-14 23:36:52 +03:00
oss.set_input(target_operation.pk, schema)
2024-07-28 21:29:46 +03:00
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
2024-07-29 22:30:24 +03:00
@extend_schema(
summary='execute operation',
tags=['OSS'],
2025-04-20 18:06:18 +03:00
request=s.TargetOperationSerializer(),
2024-07-29 22:30:24 +03:00
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=['post'], url_path='execute-operation')
2024-08-07 21:54:14 +03:00
def execute_operation(self, request: Request, pk) -> HttpResponse:
2024-07-29 22:30:24 +03:00
''' Execute operation. '''
2025-04-20 18:06:18 +03:00
serializer = s.TargetOperationSerializer(
2024-07-29 22:30:24 +03:00
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
if operation.operation_type != m.OperationType.SYNTHESIS:
raise serializers.ValidationError({
'target': msg.operationNotSynthesis(operation.alias)
})
if operation.result is not None:
raise serializers.ValidationError({
'target': msg.operationResultNotEmpty(operation.alias)
})
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
2025-04-06 13:28:00 +03:00
oss.update_layout(serializer.validated_data['layout'])
oss.execute_operation(operation)
2024-07-29 22:30:24 +03:00
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='get predecessor for target constituenta',
tags=['OSS'],
request=CstTargetSerializer(),
responses={
c.HTTP_200_OK: s.ConstituentaReferenceResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=False, methods=['post'], url_path='get-predecessor')
2024-08-07 21:54:14 +03:00
def get_predecessor(self, request: Request) -> HttpResponse:
''' Get predecessor. '''
serializer = CstTargetSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
cst = cast(Constituenta, serializer.validated_data['target'])
inheritance_query = m.Inheritance.objects.filter(child=cst)
while inheritance_query.exists():
inheritance = inheritance_query.first()
if inheritance is None:
break
cst = inheritance.parent
inheritance_query = m.Inheritance.objects.filter(child=cst)
return Response(
status=c.HTTP_200_OK,
data={
'id': cst.pk,
'schema': cst.schema_id
}
)
2024-10-28 14:52:30 +03:00
@extend_schema(
summary='relocate constituents from one schema to another',
tags=['OSS'],
request=s.RelocateConstituentsSerializer(),
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=False, methods=['post'], url_path='relocate-constituents')
def relocate_constituents(self, request: Request) -> Response:
''' Relocate constituents from one schema to another. '''
serializer = s.RelocateConstituentsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
oss = m.OperationSchema(LibraryItem.objects.get(pk=data['oss']))
source = RSForm(LibraryItem.objects.get(pk=data['source']))
destination = RSForm(LibraryItem.objects.get(pk=data['destination']))
with transaction.atomic():
if data['move_down']:
oss.relocate_down(source, destination, data['items'])
m.PropagationFacade.before_delete_cst(source, data['items'])
source.delete_cst(data['items'])
else:
new_items = oss.relocate_up(source, destination, data['items'])
m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk])
return Response(status=c.HTTP_200_OK)