【问题标题】:Django Many-to-many change in model save method does not work模型保存方法中的Django多对多更改不起作用
【发布时间】:2020-01-22 17:48:36
【问题描述】:

概念

首先,我有一个非常复杂的模型......这个想法是建造一艘星际飞船作为模型。所以每艘船都有一个类型(ship_type),它基于这个船型的蓝图。因此,当您创建 Ship 时,您必须决定模型应该使用哪个 ship_type(外键)。

因为我想改变一艘船(例如:购买另一个软件)而不是蓝图本身,所以每艘船在数据库中都有与蓝图船相同的字段(都继承自 ShipField )。当有人设置ship_type或更改它时,我希望Django去蓝图获取所有信息并覆盖船的信息。所以我尝试在 save 方法中完成这个行为。

函数和错误搜索

我写的函数总是在self.ship_type有变化的时候触发,目前为止还不错。而且所有“正常”字段也都发生了变化,只有多对多字段不起作用。

我投入其中并感到困惑。让我们假设我们的船在self.software 中没有条目,但新的ship_type 有3。如果我在保存(1.)之前打印出self.software,我得到了一个空查询集。当我在 super.save (2.) 之后执行此操作时,我得到了一个包含三个元素的查询集。所以它似乎一切正常。但是如果我在管理菜单中查看飞船,飞船根本没有软件。

结论

所以我的结论是,在保存方法之后的某个地方(可能在 post_save 事件中),软件又被删除了......此时我需要一些帮助。

想法

我希望你们明白我在这里想要做什么。我不是数据库专家,可以想象有更好的方法来实现这一点,所以我愿意接受彻底的改变。

模型(简化):

class ShipFields(models.Model):
    body = models.ForeignKey(to=Body, verbose_name="Body", on_delete=models.SET_NULL,
                         null=True, blank=False, default=None)
    software = models.ManyToManyField(Software, default=None, blank=True)
    ...

class ShipBlueprints(ShipFields, models.Model):
    class Meta:
        ordering = ['name']
        verbose_name = "Ship Blueprint"
        verbose_name_plural = "Ship Blueprints"

class Ship(ShipFields, models.Model):
    name = models.CharField(max_length=256, unique=True)
    ship_type = models.ForeignKey(to=ShipBlueprints, on_delete=models.SET_NULL, null=True)
    ...
    __original_ship_type = None

    def __init__(self, *args, **kwargs):
        super(Ship, self).__init__(*args, **kwargs)
        self.__original_ship_type = self.ship_type

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        # check if the ship type is changed
        if self.ship_type != self.__original_ship_type:
            ...
            # copy all fields from the related ShipBlueprints to the fields of Ship
            # 1. 
            print(self.software.all())
            self.body = self.ship_type.body
            self.software.set(self.ship_type.software.all())
            # or
            # for soft in self.ship_type.software.all():
            #     self.software.add(soft)
            # 2. 
            print(self.software.all())
            ...
        super(Ship, self).save(force_insert, force_update, *args, **kwargs)
        # 2. 
        print(self.software.all())
        print(Ship.objects.get(name=self.name).software.all())
        self.__original_ship_type = self.ship_type

我想我缩小了问题的范围。当我通过管理员更改 ship_type 时,many_to_many_fields 不会更新。 但是当我通过 django shell 更改 ship_type 时,它​​可以完美运行!

当我在视图中使用表单时,它也可以工作。所以我的代码工作得很好,但管理页面在某种程度上是问题......对我来说没关系,但它看起来像管理员保存方法中的错误,也许我会报告这个。

谢谢大家的意见。

【问题讨论】:

  • 我认为问题可能是您在添加软件 set() 后没有调用save()。所以我认为可能发生的是 self.software 正在内存中更新,而不是在数据库中。这就是为什么你在打印而不是在 django 管理员上看到它的原因。尝试在 Django 文档中搜索 .add() 方法:docs.djangoproject.com/en/3.0/topics/db/examples/many_to_many 如果对您有帮助,请告诉我,以便我将其作为答案。
  • 首先感谢您的提示。我之前尝试过添加:[selft.software.add(soft) in selft.ship_type.software.all()] 但最后还是一样。save() 所以你想说我必须先保存软件然后再添加它?我什么时候必须拯救他们?并且在软件模型中的数据库中肯定存在过
  • 我现在尝试了这个:self.software.save()(在set() 之后)但我得到一个错误:'ManyRelatedManager' 对象没有属性'save'

标签: django django-models many-to-many


【解决方案1】:

嗯,我相信这是因为 __original_ship_type。它也需要是 ShipBlueprints 的外键。 __original_ship_type 不会在每行内保存任何内容,因为它不是数据库属性/列。 __original_ship_type 只是数据库记录的模型 的一部分。

这实际上是一种选择。另一种选择是使用 Django 信号,特别是 pre_save。当您使用 pre_save 时,您将获得新实例,并使用此新实例的 ID 对旧实例进行数据库查询。之后,您像这样保存对象。 pre_save 信号并不一定意味着您必须在其中保存一些东西。这只是一个调用函数的信号。

【讨论】:

  • 我用 pre_save 信号测试了它同样的错误。只是为了保证我删除了 __original_ship_type 并再次尝试(现在它总是覆盖),但它没有帮助。
猜你喜欢
  • 2014-01-30
  • 2017-11-03
  • 2021-11-11
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多