一旦我在删除受保护的孩子时遇到了这个问题 (django-mptt==0.9.1)。
经过一番搜索,我发现了下一个问题:
问题发生在 MPTTodel 的 delete() 方法中。
首先它尝试更新树结构,然后删除对象。所以如果我们有保护关系,我们的树可能会搞砸。
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)
我已经使用事务解决了这个问题。比如:
from django.db import transaction
def delete(self, *args, **kwargs):
with transaction.atomic():
super().delete(*args, **kwargs)
在 shell 中测试(django 3.1.7 和 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())
- 无交易:
>>> 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)>]>
- 交易内
>>> 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)>]>