使用查询数据库的默认字段函数迁移时出现 Django 错误

Django error when migrating with default field function that queries database

我正在尝试创建一个可调用的默认字段,如下所示:

from datetime import date, timedelta
from django.db import models

class Preferences(models.Model):
    expiration_days = models.IntegerField(default=30)
    ... other db settings here ...

class ImportantBusinessObject(models.Model):
    expiration_date = models.DateField(default=get_default_expiration_date)

def get_default_expiration_date():
    expiration_days = Preferences.objects.first().expiration_days
    return date.today() + timedelta(days=expiration_days)

想法是在数据库中存储了一组全局首选项,这会影响其他模型对象的默认值。

在将另一个字段添加到 Preferences class 之前,一切都很好。然后,在构建数据库时(例如 运行 测试时),添加 expiration_date 字段的迁移最终会在新迁移添加另一个首选项字段之前执行。在调用 get_default_expiration_date() 期间,Preferences 对象是最新版本,但数据库尚未获得最新迁移,我收到 column preferences.new_preference does not exist 错误。

解决这个问题的最佳方法是什么?

现在我正在测试期间禁用迁移作为解决方法,但我想知道是否有其他方法。

我已经尝试从 get_default_expiration_date() 中的应用程序注册表中获取模型,但它仍然是最新的,而不是您在 migrations.RunPython 函数中提供的版本。

我也试过将获取 Preferences 对象的行包装在 try/except 块中,但数据库最终处于回滚状态,我无法继续。

其他位:

更新:阐明操作顺序

下面是正在发生的事情的时间表:

  1. preferences/migrations/initial_0001.py:初始偏好,包括expiration_days
  2. business/migrations/initial_0001.py:初始业务对象,依赖于preferences 0001
  3. preferences/migrations/add_pref_0002.py: 添加一个新字段到 Preferences

一旦我在步骤 #3 中定义了迁移,事情就开始中断了,因为当 运行 从步骤 #2 添加迁移时,调用 get_default_expiration_date 会引发错误。从下面关于更新依赖项的评论线程中,我的观点是,为了做到这一点,每次我添加一个新的首选项迁移时,我需要更新先前定义的 business/migrations/initial_0001.py (以及任何其他行为相似的依赖项)的依赖项).希望这能稍微澄清一下情况。

您似乎 运行 关注文档 here 中讨论的问题:

Because it’s impossible to serialize arbitrary Python code, these historical models will not have any custom methods that you have defined.

这通常出现在创建模型实例的数据迁移的上下文中。如果您在迁移期间没有创建任何实例,那么 get_default_expiration_date() 将永远不会被执行,您也不会有问题。 (我不确定你的情况是怎么回事,也许与 django-solo 或你正在使用的其他包有关?)

我认为在这种情况下最简单的解决方案是确保您的默认函数仅引用它们需要的字段,这样它们就不会生成对可能尚不存在的 SQL 列的引用。当您像现在一样获取整个对象时,它会尝试获取所有字段,并查看当前(而非历史)模型定义以查看这些字段是什么。

如果您使用 values()(或 only()),生成的 SQL 将仅引用您指定的字段:

def get_default_expiration_date():
    expiration_days = Preferences.objects.values("expiration_days")[0]["expiration_days"]