我遇到的问题有点难以解释,所以请耐心等待。首先,以下是相关版本,以防万一:Django 2.0.3,Python 3.6.4,PostgreSQL 10.3。
基本上,我有一些我的Django模型的结构:
class Capability(models.Model):
relationships = models.ManyToManyField('self',
symmetrical=False,
through='CapabilityRelationship',
blank=True)
name = models.CharField(max_length=255)
class CapabilityRelationship(models.Model):
from_capability = models.ForeignKey(Capability,
on_delete=models.CASCADE,
related_name='from_set',
blank=True,
null=True)
to_capability = models.ForeignKey(Capability,
on_delete=models.CASCADE,
related_name='to_set')
number = models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
def _get_complete_numbers(self):
# generate the complete numbers using depth-first search
def save(self, *args, **kwargs):
# Get the complete numbers before saving. If we're not able to generate the complete numbers, something is
# wrong, an error will be raised, and we don't want to save.
complete_numbers = self._get_complete_numbers()
super().save(*args, **kwargs) # Call the "real" save() method.
# Delete all old complete numbers.
self.capabilityrelationshipcompletenumber_set.all().delete()
# Save the new complete numbers.
for complete_number in complete_numbers:
CapabilityRelationshipCompleteNumber.objects.create(capability_relationship=self,
complete_number=complete_number)
class CapabilityRelationshipCompleteNumber(models.Model):
capability_relationship = models.ForeignKey(CapabilityRelationship, on_delete=models.CASCADE)
complete_number = models.CharField(max_length=255, unique=True)
为了用单词描述这些模型,我有一个Capability
模型,它与自身有多对多的关系(在CapabilityRelationship
中捕获)。在实践中,这将是一棵树"其中每个节点可以有多个子节点和多个父节点(即,它是有向无环图)。最后,每个关系实例可以有多个"完整的数字" (在CapabilityRelationshipCompleteNumber
中捕获。)
背后的想法"数字"和#34;完整的数字"本质上是杜威十进制。完整数量的1.2.3.4将具有4个级别的Capability
个对象,其中1是最顶层(即根节点),4是叶。因为我上面列出的结构是DAG而不是实际上是树,所以节点实际上可以有多个这样的结构"完成"数字,因为从任何给定节点到其根节点可以有多条路径。
如果此说明没有,请告诉我,我可以在Paint中嘲笑。
我覆盖了CapabilityRelationship.save()
方法,因为每次关系发生变化时我都需要重新计算完整的数字,因为number
可能已经改变了。所以我想做的只是计算新的完整数字,删除所有旧的完整数字,然后保存新的数字。
我遇到的问题是我根本无法删除旧的完整数字,这让我感到困惑。我想知道是否有一些关于压倒CapabilityRelationship.save()
但我根本没有得到的东西。例如:
def save(self, *args, **kwargs):
complete_numbers = self._get_complete_numbers()
print('complete numbers: {}'.format(complete_numbers))
super().save(*args, **kwargs) # Call the "real" save() method.
print('before delete: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
self.capabilityrelationshipcompletenumber_set.all().delete()
print('after delete: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
for complete_number in complete_numbers:
CapabilityRelationshipCompleteNumber.objects.create(capability_relationship=self,
complete_number=complete_number)
print('after save: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
如果我访问管理站点并将叶节点的number
设置为1,保存它,然后将其修改为2,我得到此输出:
complete numbers: {'1.2.1.1', '2.2.1.1', '1.3.1.1', '1.1.1.1'}
before delete: <QuerySet []>
after delete: <QuerySet []>
after save: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>]>
complete numbers: {'1.1.1.2', '1.2.1.2', '2.2.1.2', '1.3.1.2'}
before delete: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>]>
after delete: <QuerySet []>
after save: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.2>, <CapabilityRelationshipCompleteNumber: 1.2.1.2>, <CapabilityRelationshipCompleteNumber: 1.3.1.2>, <CapabilityRelationshipCompleteNumber: 2.2.1.2>]>
现在,所有这些看起来都很棒,但是当我访问管理网站以获取所有完整数字的列表时,我看到到目前为止计算的所有8个完整数字,而不是更改后的4个当前正确的数字number
到2.如果我打开一个Python shell并列出完整的数字,我看到目前为止所有这些都已创建:
> ./manage.py shell
Python 3.6.4 (default, Dec 19 2017, 15:24:51)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from src.apps.api.models.capability import *
>>> CapabilityRelationshipCompleteNumber.objects.all()
<QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.1.1.2>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.2>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.2>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.2>]>
如果我使用psql直接查看数据库,我会看到同样的事情。
显然,无论出于何种原因,删除调用实际上并未发生。我已尝试CapabilityRelationshipCompleteNumber.objects.filter(capability_relationship=self).delete()
,CapabilityRelationshipCompleteNumber.objects.all().delete()
,并使用DELETE FROM api_capabilityrelationshipcompletenumber;
发布原始SQL connection.cursor()
。似乎什么都没有用。我不明白发生了什么。我已经阅读了有关删除QuerySet并覆盖save()
的Django文档,但我没有看到任何可以帮助我诊断问题的内容。
有谁知道这里发生了什么?任何帮助是极大的赞赏。如果我能澄清其中的任何内容,请告诉我。
答案 0 :(得分:2)
好吧,我经过调试并整天在网上挖掘后想出来了。据我所知,问题似乎是Django如何保存ManyToMany关系。正如https://stackoverflow.com/a/1925784/1269634所讨论的那样:
当您通过管理表单保存模型时,它不是原子事务。首先保存主对象(以确保它具有PK),然后M2M 清除 ,并将新值设置为表单中的任何内容。因此,如果您在主要对象的save()中,则您处于M2M尚未更新的机会之窗。实际上,如果您尝试对M2M执行某些操作,则clear()将会消除此更改。我大约一年前碰到了这个。
虽然这篇文章的历史可以追溯到2009年,但今天显然仍然存在Django 2.0的烦恼。读完之后,我相信这就是造成这种奇怪的原因。不幸的是,据我所知,这在Django文档的任何地方都没有记录,这就是为什么花了这么长时间来追踪和修复。
这个问题的解决方案变得相对简单。我有效地将CapabilityRelationship.save()
重命名为CapabilityRelationship.update_complete_numbers()
。然后,我修改了Capability
管理员实施以覆盖ModelAdmin.save_related()
。管理员实现现在看起来像这样:
@admin.register(Capability)
class CapabilityAdmin(nested_admin.NestedModelAdmin):
list_display = ['name']
search_fields = ['name']
inlines = [CapabilityRelationshipInlineAdmin]
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
for capability_relationship in form.instance.to_set.all():
capability_relationship.update_complete_numbers()
这个有效!神奇的是,在模型保存完全完成后更新完整数字(当super().save_related(request, form, formsets, change)
返回时为真。)
感谢大家的帮助!我希望这最终可以帮助别人。