从model.save()中删除关联的模型实例不起作用

时间:2018-04-02 05:27:43

标签: python django postgresql django-models

我遇到的问题有点难以解释,所以请耐心等待。首先,以下是相关版本,以防万一: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文档,但我没有看到任何可以帮助我诊断问题的内容。

有谁知道这里发生了什么?任何帮助是极大的赞赏。如果我能澄清其中的任何内容,请告诉我。

1 个答案:

答案 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)返回时为真。)

感谢大家的帮助!我希望这最终可以帮助别人。