首页 > 解决方案 > 如何组织两个相关模型的迁移并为新创建的对象的 id 自动设置默认字段值?

问题描述

假设有一个生产数据库,里面有一些数据。我需要在下一个棘手的情况下迁移。

有一个模型(已经在数据库中),比如说Model,它有其他模型的外键。

class ModelA: ...
class ModelX: ...

class Model:
  a = models.ForeignKey(ModelA, default = A)
  x = models.ForeignKey(ModelX, default = X)

我们需要再创建一个模型ModelYModel参考。并且在创建时Model,一个对象应该有一些与某个ModelY对象相关的默认值,这显然还没有,但是我们应该在迁移过程中创建它。

class ModelY: ...
class Model:
  y = models.ForeignKey (ModelY, default = ??????)

所以迁移顺序应该是:

当然,我想自动化所有这些。因此,为了避免手动应用一次迁移,然后创建一些对象,然后记下它的 id,然后将此 id 用作新字段的默认值,然后才使用此新字段应用另一次迁移。

而且我也想一步完成,所以在旧模型中定义ModelY一个新字段y,生成迁移,以某种方式修复它,然后立即应用并使其工作。

这种情况有什么最佳实践吗?特别是,在哪里存储这个新创建的对象的 id?同一个数据库中的一些专用表?

标签: pythondjangodjango-migrations

解决方案


您将无法在单个迁移文件中执行此操作,但是您可以创建多个迁移文件来实现此目的。我会尽力帮助你,虽然我不完全确定这是你想要的,它应该教你一两件事关于 Django 迁移。

我将在这里提到两种类型的迁移,一种是模式迁移,这些是您通常在更改模型后生成的迁移文件。另一个是数据迁移,这些需要使用命令的--empty选项来创建makemigrations,例如python manage.py makemigrations my_app --empty,用于移动数据,在需要更改为非空的空列上设置数据等。

class ModelY(models.Model):
    # Fields ...
    is_default = models.BooleanField(default=False, help_text="Will be specified true by the data migration")

class Model(models.Model):
    # Fields ...
    y = models.ForeignKey(ModelY, null=True, default=None)

您会注意到y接受 null,我们可以稍后更改它,现在您可以运行python manage.py makemigrations以生成模式迁移。

要生成您的第一个数据迁移,请运行命令python manage.py makemigrations <app_name> --empty。您将在迁移文件夹中看到一个空的迁移文件。您应该添加两种方法,一种用于创建您的默认ModelY实例并将其分配给您现有的Model实例,另一种将作为存根方法,以便 Django 稍后允许您在需要时反转您的迁移。

from __future__ import unicode_literals

from django.db import migrations


def migrate_model_y(apps, schema_editor):
    """Create a default ModelY instance, and apply this to all our existing models"""
    ModelY = apps.get_model("my_app", "ModelY")
    default_model_y = ModelY.objects.create(something="something", is_default=True)

    Model = apps.get_model("my_app", "Model")
    models = Model.objects.all()
    for model in models:
        model.y = default_model_y
        model.save()


def reverse_migrate_model_y(apps, schema_editor):
    """This is necessary to reverse migrations later, if we need to"""
    return


class Migration(migrations.Migration):

    dependencies = [("my_app", "0100_auto_1092839172498")]

    operations = [
        migrations.RunPython(
            migrate_model_y, reverse_code=reverse_migrate_model_y
        )
    ]

不要直接将模型导入此迁移!模型需要通过该apps.get_model("my_app", "my_model")方法返回,以便获得在此迁移时间点的模型。如果将来您添加更多字段并运行此迁移,您的模型字段可能与数据库列不匹配(因为模型来自未来,有点......),您可能会收到一些关于数据库中缺少列的错误,并且这样的。还要警惕在迁移中在模型/管理器上使用自定义方法,因为您无法从此代理模型访问它们,通常我可能会将一些代码复制到迁移中,因此它始终运行相同。

现在我们可以返回并修改Model模型以确保y它不为空,并且它ModelY在将来会选择默认实例:

def get_default_model_y():
    default_model_y = ModelY.objects.filter(is_default=True).first()
    assert default_model_y is not None, "There is no default ModelY to populate with!!!"
    return default_model_y.pk  # We must return the primary key used by the relation, not the instance

class Model(models.Model):
    # Fields ...
    y = models.ForeignKey(ModelY, default=get_default_model_y)

现在您应该python manage.py makemigrations再次运行以创建另一个模式迁移。

您不应该混合模式迁移和数据迁移,因为迁移被包装在事务中的方式可能会导致数据库错误,这些错误会抱怨尝试在事务中创建/更改表和执行 INSERT 查询。

最后你可以运行python manage.py migrate它,它应该创建一个默认的 ModelY 对象,将它添加到你的模型的 ForeignKey 中,然后删除它null以使它像一个默认的 ForeignKey。


推荐阅读