【问题标题】:Get a historical version of a model获取模型的历史版本
【发布时间】:2017-07-22 19:54:27
【问题描述】:

我正在使用 Django 1.10,我有两个这样的模型:

class Model1:
    name = models.CharField()

class Model2:
    model1 = models.ForeignKey(Model1, default=get_default_model1)

get_default_model1 函数必须获取Model1 的特定实例(例如,具有特定名称),或者如果它不存在则创建它。问题是这个函数会导入Model1,并且还会在迁移中使用:这意味着当Model1被修改时,这种迁移可能会中断,因为它将导入Model1的新版本而不是它需要的版本当时(有关此问题的更好解释,请参阅here)。

在迁移中执行RunPython 操作时,您可以通过apps 参数访问模型的历史版本,但情况并非如此。

我编写了以下代码来手动创建模型的历史版本:

from django.db import models, connection
from django.db.migrations.loader import MigrationLoader

def get_default_model1():
    loader = MigrationLoader(connection)
    apps = loader.project_state(list(loader.applied_migrations)).apps
    Model1 = apps.get_model('app', 'Model1')
    return Model1.objects.get_or_create(
        name=settings.DEFAULT_NAME,
        defaults={'name': settings.DEFAULT_NAME}
    )[0].pk

但在某些迁移中,它会因django.db.migrations.exceptions.NodeNotFoundError: Node ('default', '0004_auto_20160423_0400') not a valid node 而失败。它似乎与第三方依赖的替换迁移有关。

有没有更好的方法来获取我的模型的历史版本或完成我在这里尝试做的事情?

【问题讨论】:

  • 您能解释一下为什么需要历史模型吗?这样的模型将与数据库不同步,因此可能无法创建外键等
  • 我试图在第二段中解释原因,但可能不够清楚。我需要历史版本,因为该函数是字段的默认值,因此它将为该模型创建一个新的迁移。当该迁移运行时,它需要查看当时与数据库同步的模型版本,如果模型随后遭受更多更改,则可能与最新版本不同。
  • 这个字段不能设置为null吗?这将使生活更轻松
  • 哈,好吧,这会让事情变得更容易,但这个问题的全部目的是禁止该字段的空值。
  • 好吧,这更能说明问题。但是为什么你需要知道模型是什么样的?您当前的代码显示您返回模型实例而不是模型类

标签: django django-models django-migrations


【解决方案1】:

您可以将get_default_model1 函数定义为:

def get_default_model1(Model1):
    default_model = Model1.objects.get(...)

   return lambda: default_model


class Model1:
    name = models.CharField()


class Model2:
    model1 = models.ForeignKey(Model1, default=get_default_model1(Model1))

然后在迁移中使用它:

Model1 = apps.get_model("<your_app>", "Model1")
Model2 = apps.get_model("<your_app>", "Model2")

for model2 in Model2.objects.all():
    if not model2.model1:
        model2.model1 = get_default_model1(Model1)

我知道这看起来不是最漂亮的,尤其是你必须手动设置默认值的部分,但是你将正确的模型注入到函数中,这样你的目标就实现了。

【讨论】:

  • 正如我所解释的,我无权访问 apps 注册表。该函数将从正常的AlterField 操作中调用。
  • 我看不出问题出在哪里。将使用的实际函数是 lambda。所以你不需要修改它,只需创建一个包装器,它会以你想要的方式创建它。
  • 我明白了,但是我从哪里获得 apps 注册表以使其具有我的模型的历史版本?在您的示例中,您只有一个 apps 变量,但您没有展示如何构建它。
  • 好的,我知道是什么问题。我认为最好的解决方案是分三步进行迁移:将默认值设置为None,在数据迁移中填充model1 字段,为您的函数设置默认值。你觉得这可行吗?
  • 但我最终遇到了与现在相同的情况:在第三次迁移中,我将使用直接导入 Model1 的函数,这意味着一旦 Model1 在未来。