B: Fix cyclic block hierarchy

This commit is contained in:
Ivan 2025-07-17 19:17:08 +03:00
parent 854d32aea1
commit 2cb7c44f3a
3 changed files with 46 additions and 9 deletions

View File

@ -123,7 +123,7 @@ class UpdateBlockSerializer(StrictSerializer):
raise serializers.ValidationError({ raise serializers.ValidationError({
'parent': msg.parentNotInOSS() 'parent': msg.parentNotInOSS()
}) })
if parent == attrs['target']: if attrs['target'].pk in _collect_ancestors(parent):
raise serializers.ValidationError({ raise serializers.ValidationError({
'parent': msg.blockCyclicHierarchy() 'parent': msg.blockCyclicHierarchy()
}) })

View File

@ -266,3 +266,36 @@ class TestOssBlocks(EndpointTester):
data['layout'] = self.layout_data data['layout'] = self.layout_data
self.executeOK(data=data) self.executeOK(data=data)
@decl_endpoint('/api/oss/{item}/update-block', method='patch')
def test_update_block_cyclic_parent(self):
self.populateData()
# block1 -> block2
# Try to set block1's parent to block2 (should fail, direct cycle)
data = {
'target': self.block1.pk,
'item_data': {
'title': self.block1.title,
'description': self.block1.description,
'parent': self.block2.pk
},
}
self.executeBadData(data=data, item=self.owned_id)
# Create a deeper hierarchy: block1 -> block2 -> block3
self.block3 = self.owned.create_block(title='3', parent=self.block2)
# Try to set block1's parent to block3 (should fail, indirect cycle)
data['item_data']['parent'] = self.block3.pk
self.executeBadData(data=data, item=self.owned_id)
# Setting block2's parent to block1 (valid, as block1 is not a descendant)
data = {
'target': self.block2.pk,
'item_data': {
'title': self.block2.title,
'description': self.block2.description,
'parent': self.block1.pk
},
}
self.executeOK(data=data, item=self.owned_id)

View File

@ -66,14 +66,18 @@ export function DlgEditBlock() {
<Controller <Controller
name='item_data.parent' name='item_data.parent'
control={control} control={control}
render={({ field }) => ( render={({ field }) => {
const descendantNodeIDs = manager.oss.hierarchy.expandAllOutputs([target.nodeID]);
descendantNodeIDs.push(target.nodeID);
return (
<SelectParent <SelectParent
items={manager.oss.blocks.filter(block => block.id !== target.id)} items={manager.oss.blocks.filter(block => !descendantNodeIDs.includes(block.nodeID))}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null} value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок' placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)} onChange={value => field.onChange(value ? value.id : null)}
/> />
)} );
}}
/> />
<TextArea <TextArea