mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
516 lines
18 KiB
Python
516 lines
18 KiB
Python
''' Endpoints for RSForm. '''
|
|
import json
|
|
from typing import Union, cast
|
|
|
|
import pyconcept
|
|
from django.db import transaction
|
|
from django.http import HttpResponse
|
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
|
from rest_framework import generics
|
|
from rest_framework import status as c
|
|
from rest_framework import views, viewsets
|
|
from rest_framework.decorators import action, api_view
|
|
from rest_framework.request import Request
|
|
from rest_framework.response import Response
|
|
|
|
from shared import messages as msg
|
|
from shared import permissions
|
|
|
|
from .. import models as m
|
|
from .. import serializers as s
|
|
from .. import utils
|
|
|
|
|
|
@extend_schema(tags=['RSForm'])
|
|
@extend_schema_view()
|
|
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
|
''' Endpoint: RSForm operations. '''
|
|
queryset = m.RSForm.objects.all()
|
|
serializer_class = s.LibraryItemSerializer
|
|
|
|
def _get_schema(self) -> m.RSForm:
|
|
return cast(m.RSForm, self.get_object())
|
|
|
|
def get_permissions(self):
|
|
''' Determine permission class. '''
|
|
if self.action in [
|
|
'load_trs',
|
|
'reset_aliases',
|
|
'cst_create',
|
|
'cst_delete_multiple',
|
|
'cst_rename',
|
|
'cst_substitute'
|
|
]:
|
|
permission_list = [permissions.ItemEditor]
|
|
elif self.action in [
|
|
'contents',
|
|
'details',
|
|
'export_trs',
|
|
'resolve',
|
|
'check'
|
|
]:
|
|
permission_list = [permissions.ItemAnyone]
|
|
else:
|
|
permission_list = [permissions.Anyone]
|
|
return [permission() for permission in permission_list]
|
|
|
|
@extend_schema(
|
|
summary='create constituenta',
|
|
tags=['Constituenta'],
|
|
request=s.CstCreateSerializer,
|
|
responses={
|
|
c.HTTP_201_CREATED: s.NewCstResponse,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['post'], url_path='cst-create')
|
|
def cst_create(self, request: Request, pk):
|
|
''' Create new constituenta. '''
|
|
schema = self._get_schema()
|
|
serializer = s.CstCreateSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
data = serializer.validated_data
|
|
if 'insert_after' in data:
|
|
try:
|
|
insert_after = m.Constituenta.objects.get(pk=data['insert_after'])
|
|
except m.LibraryItem.DoesNotExist:
|
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
|
else:
|
|
insert_after = None
|
|
new_cst = schema.create_cst(data, insert_after)
|
|
|
|
schema.refresh_from_db()
|
|
response = Response(
|
|
status=c.HTTP_201_CREATED,
|
|
data={
|
|
'new_cst': s.CstSerializer(new_cst).data,
|
|
'schema': s.RSFormParseSerializer(schema).data
|
|
}
|
|
)
|
|
response['Location'] = new_cst.get_absolute_url()
|
|
return response
|
|
|
|
@extend_schema(
|
|
summary='produce the structure of a given constituenta',
|
|
tags=['RSForm'],
|
|
request=s.CstTargetSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.NewMultiCstResponse,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='cst-produce-structure')
|
|
def produce_structure(self, request: Request, pk):
|
|
''' Produce a term for every element of the target constituenta typification. '''
|
|
schema = self._get_schema()
|
|
|
|
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
|
|
serializer.is_valid(raise_exception=True)
|
|
cst = cast(m.Constituenta, serializer.validated_data['target'])
|
|
|
|
schema_details = s.RSFormParseSerializer(schema).data['items']
|
|
cst_parse = next(item for item in schema_details if item['id'] == cst.id)['parse']
|
|
if not cst_parse['typification']:
|
|
return Response(
|
|
status=c.HTTP_400_BAD_REQUEST,
|
|
data={f'{cst.id}': msg.constituentaNoStructure()}
|
|
)
|
|
|
|
result = schema.produce_structure(cst, cst_parse)
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data={
|
|
'cst_list': result,
|
|
'schema': s.RSFormParseSerializer(schema).data
|
|
}
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='rename constituenta',
|
|
tags=['Constituenta'],
|
|
request=s.CstRenameSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.NewCstResponse,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
|
def cst_rename(self, request: Request, pk):
|
|
''' Rename constituenta possibly changing type. '''
|
|
schema = self._get_schema()
|
|
serializer = s.CstRenameSerializer(data=request.data, context={'schema': schema})
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
cst = cast(m.Constituenta, serializer.validated_data['target'])
|
|
old_alias = cst.alias
|
|
|
|
cst.alias = serializer.validated_data['alias']
|
|
cst.cst_type = serializer.validated_data['cst_type']
|
|
|
|
with transaction.atomic():
|
|
cst.save()
|
|
schema.apply_mapping(mapping={old_alias: cst.alias}, change_aliases=False)
|
|
schema.refresh_from_db()
|
|
cst.refresh_from_db()
|
|
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data={
|
|
'new_cst': s.CstSerializer(cst).data,
|
|
'schema': s.RSFormParseSerializer(schema).data
|
|
}
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='substitute constituenta',
|
|
tags=['RSForm'],
|
|
request=s.CstSubstituteSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='cst-substitute')
|
|
def cst_substitute(self, request: Request, pk):
|
|
''' Substitute occurrences of constituenta with another one. '''
|
|
schema = self._get_schema()
|
|
serializer = s.CstSubstituteSerializer(
|
|
data=request.data,
|
|
context={'schema': schema}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
with transaction.atomic():
|
|
for substitution in serializer.validated_data['substitutions']:
|
|
original = cast(m.Constituenta, substitution['original'])
|
|
replacement = cast(m.Constituenta, substitution['substitution'])
|
|
schema.substitute(original, replacement, substitution['transfer_term'])
|
|
schema.refresh_from_db()
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(schema).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='delete constituents',
|
|
tags=['RSForm'],
|
|
request=s.CstListSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='cst-delete-multiple')
|
|
def cst_delete_multiple(self, request: Request, pk):
|
|
''' Endpoint: Delete multiple constituents. '''
|
|
schema = self._get_schema()
|
|
serializer = s.CstListSerializer(
|
|
data=request.data,
|
|
context={'schema': schema}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
schema.delete_cst(serializer.validated_data['items'])
|
|
schema.refresh_from_db()
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(schema).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='move constituenta',
|
|
tags=['RSForm'],
|
|
request=s.CstMoveSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
|
def cst_moveto(self, request: Request, pk):
|
|
''' Endpoint: Move multiple constituents. '''
|
|
schema = self._get_schema()
|
|
serializer = s.CstMoveSerializer(
|
|
data=request.data,
|
|
context={'schema': schema}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
schema.move_cst(
|
|
listCst=serializer.validated_data['items'],
|
|
target=serializer.validated_data['move_to']
|
|
)
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(schema).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='reset aliases, update expressions and references',
|
|
tags=['RSForm'],
|
|
request=None,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
|
def reset_aliases(self, request: Request, pk):
|
|
''' Endpoint: Recreate all aliases based on order. '''
|
|
schema = self._get_schema()
|
|
schema.reset_aliases()
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(schema).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='restore order based on types and term graph',
|
|
tags=['RSForm'],
|
|
request=None,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='restore-order')
|
|
def restore_order(self, request: Request, pk):
|
|
''' Endpoint: Restore order based on types and term graph. '''
|
|
schema = self._get_schema()
|
|
schema.restore_order()
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(schema).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='load data from TRS file',
|
|
tags=['RSForm'],
|
|
request=s.RSFormUploadSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['patch'], url_path='load-trs')
|
|
def load_trs(self, request: Request, pk):
|
|
''' Endpoint: Load data from file and replace current schema. '''
|
|
input_serializer = s.RSFormUploadSerializer(data=request.data)
|
|
input_serializer.is_valid(raise_exception=True)
|
|
schema = self._get_schema()
|
|
load_metadata = input_serializer.validated_data['load_metadata']
|
|
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
|
data['id'] = schema.pk
|
|
|
|
serializer = s.RSFormTRSSerializer(
|
|
data=data,
|
|
context={'load_meta': load_metadata}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
result = serializer.save()
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.RSFormParseSerializer(result).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='get all constituents data from DB',
|
|
tags=['RSForm'],
|
|
request=None,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormSerializer,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['get'], url_path='contents')
|
|
def contents(self, request: Request, pk):
|
|
''' Endpoint: View schema db contents (including constituents). '''
|
|
schema = s.RSFormSerializer(self.get_object())
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=schema.data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='get all constituents data and parses',
|
|
tags=['RSForm'],
|
|
request=None,
|
|
responses={
|
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['get'], url_path='details')
|
|
def details(self, request: Request, pk):
|
|
''' Endpoint: Detailed schema view including statuses and parse. '''
|
|
serializer = s.RSFormParseSerializer(self._get_schema())
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=serializer.data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='check RSLang expression',
|
|
tags=['RSForm', 'FormalLanguage'],
|
|
request=s.ExpressionSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.ExpressionParseSerializer,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
},
|
|
)
|
|
@action(detail=True, methods=['post'], url_path='check')
|
|
def check(self, request: Request, pk):
|
|
''' Endpoint: Check RSLang expression against schema context. '''
|
|
serializer = s.ExpressionSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
expression = serializer.validated_data['expression']
|
|
schema = s.PyConceptAdapter(self._get_schema())
|
|
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=json.loads(result)
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='resolve text with references',
|
|
tags=['RSForm', 'NaturalLanguage'],
|
|
request=s.TextSerializer,
|
|
responses={
|
|
c.HTTP_200_OK: s.ResolverSerializer,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['post'], url_path='resolve')
|
|
def resolve(self, request: Request, pk):
|
|
''' Endpoint: Resolve references in text against schema terms context. '''
|
|
serializer = s.TextSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
text = serializer.validated_data['text']
|
|
resolver = self._get_schema().resolver()
|
|
resolver.resolve(text)
|
|
return Response(
|
|
status=c.HTTP_200_OK,
|
|
data=s.ResolverSerializer(resolver).data
|
|
)
|
|
|
|
@extend_schema(
|
|
summary='export as TRS file',
|
|
tags=['RSForm'],
|
|
request=None,
|
|
responses={
|
|
(c.HTTP_200_OK, 'application/zip'): bytes,
|
|
c.HTTP_404_NOT_FOUND: None
|
|
}
|
|
)
|
|
@action(detail=True, methods=['get'], url_path='export-trs')
|
|
def export_trs(self, request: Request, pk):
|
|
''' Endpoint: Download Exteor compatible file. '''
|
|
data = s.RSFormTRSSerializer(self._get_schema()).data
|
|
file = utils.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
|
filename = utils.filename_for_schema(self._get_schema().alias)
|
|
response = HttpResponse(file, content_type='application/zip')
|
|
response['Content-Disposition'] = f'attachment; filename={filename}'
|
|
return response
|
|
|
|
|
|
class TrsImportView(views.APIView):
|
|
''' Endpoint: Upload RS form in Exteor format. '''
|
|
serializer_class = s.FileSerializer
|
|
permission_classes = [permissions.GlobalUser]
|
|
|
|
@extend_schema(
|
|
summary='import TRS file into RSForm',
|
|
tags=['RSForm'],
|
|
request=s.FileSerializer,
|
|
responses={
|
|
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
|
c.HTTP_403_FORBIDDEN: None
|
|
}
|
|
)
|
|
def post(self, request: Request):
|
|
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
|
owner = cast(m.User, self.request.user)
|
|
_prepare_rsform_data(data, request, owner)
|
|
serializer = s.RSFormTRSSerializer(
|
|
data=data,
|
|
context={'load_meta': True}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
schema = serializer.save()
|
|
result = s.LibraryItemSerializer(schema)
|
|
return Response(
|
|
status=c.HTTP_201_CREATED,
|
|
data=result.data
|
|
)
|
|
|
|
|
|
@extend_schema(
|
|
summary='create new RSForm empty or from file',
|
|
tags=['RSForm'],
|
|
request=s.LibraryItemSerializer,
|
|
responses={
|
|
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
|
c.HTTP_400_BAD_REQUEST: None,
|
|
c.HTTP_403_FORBIDDEN: None
|
|
}
|
|
)
|
|
@api_view(['POST'])
|
|
def create_rsform(request: Request):
|
|
''' Endpoint: Create RSForm from user input and/or trs file. '''
|
|
owner = cast(m.User, request.user) if not request.user.is_anonymous else None
|
|
if 'file' not in request.FILES:
|
|
return Response(
|
|
status=c.HTTP_400_BAD_REQUEST,
|
|
data={'file': msg.missingFile()}
|
|
)
|
|
else:
|
|
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
|
_prepare_rsform_data(data, request, owner)
|
|
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
|
serializer_rsform.is_valid(raise_exception=True)
|
|
schema = serializer_rsform.save()
|
|
result = s.LibraryItemSerializer(schema)
|
|
return Response(
|
|
status=c.HTTP_201_CREATED,
|
|
data=result.data
|
|
)
|
|
|
|
|
|
def _prepare_rsform_data(data: dict, request: Request, owner: Union[m.User, None]):
|
|
data['owner'] = owner
|
|
if 'title' in request.data and request.data['title'] != '':
|
|
data['title'] = request.data['title']
|
|
if data['title'] == '':
|
|
data['title'] = 'Без названия ' + request.FILES['file'].fileName
|
|
if 'alias' in request.data and request.data['alias'] != '':
|
|
data['alias'] = request.data['alias']
|
|
if 'comment' in request.data and request.data['comment'] != '':
|
|
data['comment'] = request.data['comment']
|
|
|
|
visible = True
|
|
if 'visible' in request.data:
|
|
visible = request.data['visible'] == 'true'
|
|
data['visible'] = visible
|
|
|
|
read_only = False
|
|
if 'read_only' in request.data:
|
|
read_only = request.data['read_only'] == 'true'
|
|
data['read_only'] = read_only
|
|
|
|
data['access_policy'] = request.data.get('access_policy', m.AccessPolicy.PUBLIC)
|
|
data['location'] = request.data.get('location', m.LocationHead.USER)
|