F: Add function for predecessor tracing
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Frontend CI / build (22.x) (push) Has been cancelled

Update oss.py
This commit is contained in:
Ivan 2024-08-01 21:16:26 +03:00
parent e29f7409c1
commit 27ba4a5da8
12 changed files with 173 additions and 81 deletions

View File

@ -10,4 +10,4 @@ from .data_access import (
OperationUpdateSerializer,
SetOperationInputSerializer
)
from .responses import NewOperationResponse, NewSchemaResponse
from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse

View File

@ -16,3 +16,9 @@ class NewSchemaResponse(serializers.Serializer):
''' Serializer: Create RSForm for input operation response. '''
new_schema = LibraryItemSerializer()
oss = OperationSchemaSerializer()
class ConstituentaReferenceResponse(serializers.Serializer):
''' Serializer: Constituenta reference. '''
id = serializers.IntegerField()
schema = serializers.IntegerField()

View File

@ -12,6 +12,8 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer
from apps.rsform.models import Constituenta
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
from shared import permissions
@ -43,6 +45,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
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]
@ -306,3 +310,34 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
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')
def get_predecessor(self, request: Request):
''' Get predecessor. '''
# TODO: add tests for this method
serializer = CstTargetSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
cst = cast(Constituenta, serializer.validated_data['target'])
inheritance = m.Inheritance.objects.filter(child=cst)
while inheritance.exists():
cst = cast(m.Inheritance, inheritance.first()).parent
inheritance = m.Inheritance.objects.filter(child=cst)
return Response(
status=c.HTTP_200_OK,
data={
'id': cst.pk,
'schema': cst.schema_id
}
)

View File

@ -231,17 +231,13 @@ class CstTargetSerializer(serializers.Serializer):
target = PKField(many=False, queryset=Constituenta.objects.all())
def validate(self, attrs):
if 'schema' in self.context:
schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target'])
if schema and cst.schema_id != schema.pk:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
})
if cst.cst_type not in [CstType.FUNCTION, CstType.STRUCTURED, CstType.TERM]:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNoStructure()
})
self.instance = cst
return attrs

View File

@ -147,6 +147,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True)
cst = cast(m.Constituenta, serializer.validated_data['target'])
if cst.cst_type not in [m.CstType.FUNCTION, m.CstType.STRUCTURED, m.CstType.TERM]:
raise ValidationError({
f'{cst.pk}': msg.constituentaNoStructure()
})
schema_details = s.RSFormParseSerializer(schema).data['items']
cst_parse = next(item for item in schema_details if item['id'] == cst.pk)['parse']

View File

@ -12,6 +12,7 @@ import {
IPositionsData,
ITargetOperation
} from '@/models/oss';
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
@ -73,3 +74,10 @@ export function postExecuteOperation(oss: string, request: FrontExchange<ITarget
request: request
});
}
export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) {
AxiosPost({
endpoint: `/api/oss/get-predecessor`,
request: request
});
}

View File

@ -64,7 +64,7 @@ export { BiDiamond as IconTemplates } from 'react-icons/bi';
export { GiHoneycomb as IconOSS } from 'react-icons/gi';
export { LuBaby as IconChild } from 'react-icons/lu';
export { RiParentLine as IconParent } from 'react-icons/ri';
export { TbOld as IconPredecessor } from 'react-icons/tb';
export { BiSpa as IconPredecessor } from 'react-icons/bi';
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
export { LuArchive as IconArchive } from 'react-icons/lu';
export { LuDatabase as IconDatabase } from 'react-icons/lu';

View File

@ -13,6 +13,7 @@ import {
postCreateVersion,
postSubscribe
} from '@/backend/library';
import { postFindPredecessor } from '@/backend/oss';
import {
getTRSFile,
patchDeleteConstituenta,
@ -37,6 +38,7 @@ import {
ConstituentaID,
IConstituentaList,
IConstituentaMeta,
IConstituentaReference,
ICstCreateData,
ICstMovetoData,
ICstRenameData,
@ -89,6 +91,7 @@ interface IRSFormContext {
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void;
cstDelete: (data: IConstituentaList, callback?: () => void) => void;
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void;
findPredecessor: (data: ITargetCst, callback: (reference: IConstituentaReference) => void) => void;
versionCreate: (data: IVersionData, callback?: (version: VersionID) => void) => void;
versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void;
@ -526,6 +529,17 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
[itemID, library, setSchema]
);
const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => {
setProcessingError(undefined);
postFindPredecessor({
data: data,
showError: true,
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: callback
});
}, []);
const versionUpdate = useCallback(
(target: number, data: IVersionData, callback?: () => void) => {
setProcessingError(undefined);
@ -638,6 +652,8 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
cstSubstitute,
cstDelete,
cstMoveTo,
findPredecessor,
versionCreate,
versionUpdate,
versionDelete,

View File

@ -119,6 +119,11 @@ export interface IConstituenta extends IConstituentaData {
children_alias: string[];
}
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference extends Pick<IConstituentaMeta, 'id' | 'schema'> {}
/**
* Represents Constituenta list.
*/

View File

@ -80,21 +80,15 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
return (
<div className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
{controller.isContentEditable ? (
<ToolbarConstituenta
activeCst={activeCst}
disabled={disabled}
modified={isModified}
showList={showList}
onMoveUp={controller.moveUp}
onMoveDown={controller.moveDown}
onSubmit={initiateSubmit}
onReset={() => setToggleReset(prev => !prev)}
onDelete={controller.promptDeleteCst}
onClone={controller.cloneCst}
onCreate={() => controller.createCst(activeCst?.cst_type, false)}
onToggleList={() => setShowList(prev => !prev)}
/>
) : null}
<div
tabIndex={-1}
className={clsx(

View File

@ -8,6 +8,7 @@ import {
IconMoveDown,
IconMoveUp,
IconNewItem,
IconPredecessor,
IconReset,
IconSave
} from '@/components/Icons';
@ -16,39 +17,31 @@ import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous';
import { IConstituenta } from '@/models/rsform';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext';
interface ToolbarConstituentaProps {
activeCst?: IConstituenta;
disabled: boolean;
modified: boolean;
showList: boolean;
onSubmit: () => void;
onReset: () => void;
onMoveUp: () => void;
onMoveDown: () => void;
onDelete: () => void;
onClone: () => void;
onCreate: () => void;
onToggleList: () => void;
}
function ToolbarConstituenta({
activeCst,
disabled,
modified,
showList,
onSubmit,
onReset,
onMoveUp,
onMoveDown,
onDelete,
onClone,
onCreate,
onToggleList
}: ToolbarConstituentaProps) {
const controller = useRSEdit();
@ -61,6 +54,15 @@ function ToolbarConstituenta({
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
/>
) : null}
{activeCst && activeCst.is_inherited ? (
<MiniButton
title='Перейти к исходной конституенте в ОСС'
onClick={() => controller.viewPredecessor(activeCst.id)}
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
/>
) : null}
{controller.isContentEditable ? (
<>
<MiniButton
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
icon={<IconSave size='1.25rem' className='icon-primary' />}
@ -77,37 +79,45 @@ function ToolbarConstituenta({
title='Создать конституенту после данной'
icon={<IconNewItem size={'1.25rem'} className='icon-green' />}
disabled={disabled}
onClick={onCreate}
onClick={() => controller.createCst(activeCst?.cst_type, false)}
/>
<MiniButton
titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')}
icon={<IconClone size='1.25rem' className='icon-green' />}
disabled={disabled || modified}
onClick={onClone}
onClick={controller.cloneCst}
/>
<MiniButton
title='Удалить редактируемую конституенту'
disabled={disabled || !controller.canDeleteSelected}
onClick={onDelete}
onClick={controller.promptDeleteCst}
icon={<IconDestroy size='1.25rem' className='icon-red' />}
/>
</>
) : null}
<MiniButton
title='Отображение списка конституент'
icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />}
onClick={onToggleList}
/>
{controller.isContentEditable ? (
<>
<MiniButton
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
disabled={disabled || modified}
onClick={onMoveUp}
onClick={controller.moveUp}
/>
<MiniButton
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
disabled={disabled || modified}
onClick={onMoveDown}
onClick={controller.moveDown}
/>
</>
) : null}
<BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} className={PARAMETER.TOOLTIP_WIDTH} />
</Overlay>
);

View File

@ -53,6 +53,8 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils';
import { RSTabID } from './RSTabs';
export interface IRSEditContext {
schema?: IRSForm;
selected: ConstituentaID[];
@ -80,6 +82,7 @@ export interface IRSEditContext {
viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
viewVersion: (version?: VersionID, newTab?: boolean) => void;
viewPredecessor: (target: ConstituentaID) => void;
createVersion: () => void;
restoreVersion: () => void;
promptEditVersions: () => void;
@ -202,6 +205,20 @@ export const RSEditState = ({
[router, model]
);
const viewPredecessor = useCallback(
(target: ConstituentaID) =>
model.findPredecessor({ target: target }, reference =>
router.push(
urls.schema_props({
id: reference.schema,
active: reference.id,
tab: RSTabID.CST_EDIT
})
)
),
[router, model]
);
const viewOSS = useCallback(
(target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab),
[router]
@ -620,6 +637,7 @@ export const RSEditState = ({
viewOSS,
viewVersion,
viewPredecessor,
createVersion,
restoreVersion,
promptEditVersions: () => setShowEditVersions(true),