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

Posted

技术标签:

【中文标题】使用查询数据库的默认字段函数迁移时出现 Django 错误【英文标题】:Django error when migrating with default field function that queries database 【发布时间】:2016-06-30 07:29:53 【问题描述】:

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

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

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

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

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

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

其他位:

我使用的是 Django 1.8 在我的实际应用中,Preferences 类实际上是使用 django-solo 的 db 单例。

UPDATE:明确操作顺序

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

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

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

【问题讨论】:

您是否尝试过编辑dependencies 以便首先运行Preferences 迁移? 我没试过。编辑以前创建的迁移的依赖项以依赖于新的依赖项感觉很奇怪(然后每次我向Preferences 添加新迁移时都必须手动管理依赖项。)不过,这可能是另一种解决方法。 我对两次迁移之间运行的代码感到困惑。运行测试时,它应该通过在执行任何测试代码之前运行所有迁移来重建数据库。您是否有正在为现有对象设置新首选项的数据迁移? 更新问题试图澄清 【参考方案1】:

您似乎遇到了文档here 中讨论的问题:

因为不可能序列化任意 Python 代码,所以这些历史模型不会有您定义的任何自定义方法。

这通常出现在创建模型实例的数据迁移的上下文中。如果您在迁移期间没有创建任何实例,那么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"]

【讨论】:

是的!因此,由于 django-solo,实际答案涉及更多,但这让我完成了 90% 的工作,谢谢。为了完整起见,django-solo 会在数据库中创建记录(如果它还没有),所以如果记录丢失,我必须使用字段默认值来处理这种情况。

以上是关于使用查询数据库的默认字段函数迁移时出现 Django 错误的主要内容,如果未能解决你的问题,请参考以下文章

Django 2.0.7 - 进行重命名字段迁移时出现语法错误

使用 Laravel 迁移创建外键时出现 MySQL 错误

使用Laravel迁移创建外键时出现MySQL错误

从models.py迁移我的数据时出现问题

CodeFirst迁移时出现的中文乱码问题

使用 psycopg2 在 python 中执行查询时出现“ProgrammingError:在或附近出现语法错误”