Once I had this problem with protected children deleting (django-mptt==0.9.1).
After some searching I found out the next issue:
problem occurs inside delete() method of the MPTTodel.
First it tries to update the tree structure, then delete the object. So if we have protected relation, our tree could be messed up.
tree_width = (self._mpttfield('right') -
self._mpttfield('left') + 1)
target_right = self._mpttfield('right')
tree_id = self._mpttfield('tree_id')
self._tree_manager._close_gap(tree_width, target_right, tree_id)
parent = cached_field_value(self, self._mptt_meta.parent_attr)
if parent:
right_shift = -self.get_descendant_count() - 2
self._tree_manager._post_insert_update_cached_parent_right(parent, right_shift)
return super(MPTTModel, self).delete(*args, **kwargs)
I've solved this issue using transactions. Something like:
from django.db import transaction
def delete(self, *args, **kwargs):
with transaction.atomic():
super().delete(*args, **kwargs)
test in shell (django 3.1.7 and django-mptt 0.12.0)
from mptt.models import MPTTModel, TreeForeignKey
from django.db import models, transaction
class Test(MPTTModel):
name = models.TextField()
parent = TreeForeignKey(
'self',
on_delete=models.PROTECT,
null=True,
blank=True,
related_name='children'
)
from testing.models import Test
a = Test.objects.create(name='a')
b = Test.objects.create(name='b', parent=a)
c = Test.objects.create(name='c', parent=b)
print(a.get_descendants())
print(c.get_ancestors())
b.delete()
# refresh from db
a = Test.objects.get(pk=a.pk)
c = Test.objects.get(pk=c.pk)
print(a.get_descendants())
print(c.get_ancestors())
- Without transaction:
>>> print(a.get_descendants())
<TreeQuerySet [<Test: Test object (20)>, <Test: Test object (21)>]>
>>> print(c.get_ancestors())
<TreeQuerySet [<Test: Test object (19)>, <Test: Test object (20)>]>
>>>
>>> b.delete()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/mptt/models.py", line 1138, in delete
return super().delete(*args, **kwargs)
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/django/db/models/base.py", line 946, in delete
collector.collect([self], keep_parents=keep_parents)
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/django/db/models/deletion.py", line 302, in collect
raise ProtectedError(
django.db.models.deletion.ProtectedError: ("Cannot delete some instances of model 'Test' because they are referenced through protected foreign keys: 'Test.parent'.", {<Test: Test object (21)>})
>>>
>>> # refresh from db
>>> a = Test.objects.get(pk=a.pk)
>>> c = Test.objects.get(pk=c.pk)
>>>
>>> print(a.get_descendants())
<TreeQuerySet []>
>>> print(c.get_ancestors())
<TreeQuerySet [<Test: Test object (20)>]>
- Within transaction
>>> print(a.get_descendants())
<TreeQuerySet [<Test: Test object (23)>, <Test: Test object (24)>]>
>>> print(c.get_ancestors())
<TreeQuerySet [<Test: Test object (22)>, <Test: Test object (23)>]>
>>>
>>> b.delete()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/vladimir/Desktop/test/someproject/testing/models.py", line 16, in delete
super().delete(*args, **kwargs)
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/mptt/models.py", line 1138, in delete
return super().delete(*args, **kwargs)
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/django/db/models/base.py", line 946, in delete
collector.collect([self], keep_parents=keep_parents)
File "/home/vladimir/Desktop/test/.venv/lib/python3.8/site-packages/django/db/models/deletion.py", line 302, in collect
raise ProtectedError(
django.db.models.deletion.ProtectedError: ("Cannot delete some instances of model 'Test' because they are referenced through protected foreign keys: 'Test.parent'.", {<Test: Test object (24)>})
>>>
>>> # refresh from db
>>> a = Test.objects.get(pk=a.pk)
>>> c = Test.objects.get(pk=c.pk)
>>>
>>> print(a.get_descendants())
<TreeQuerySet [<Test: Test object (23)>, <Test: Test object (24)>]>
>>> print(c.get_ancestors())
<TreeQuerySet [<Test: Test object (22)>, <Test: Test object (23)>]>