python - 如何组织两个相关模型的迁移并为新创建的对象的 id 自动设置默认字段值?
问题描述
假设有一个生产数据库,里面有一些数据。我需要在下一个棘手的情况下迁移。
有一个模型(已经在数据库中),比如说Model
,它有其他模型的外键。
class ModelA: ...
class ModelX: ...
class Model:
a = models.ForeignKey(ModelA, default = A)
x = models.ForeignKey(ModelX, default = X)
我们需要再创建一个模型ModelY
来Model
参考。并且在创建时Model
,一个对象应该有一些与某个ModelY
对象相关的默认值,这显然还没有,但是我们应该在迁移过程中创建它。
class ModelY: ...
class Model:
y = models.ForeignKey (ModelY, default = ??????)
所以迁移顺序应该是:
- 创建
ModelY
表 - 在该表中创建一个默认对象,将其 id 放在某处
y
在表中创建一个新字段Model
,默认值取自上一段
当然,我想自动化所有这些。因此,为了避免手动应用一次迁移,然后创建一些对象,然后记下它的 id,然后将此 id 用作新字段的默认值,然后才使用此新字段应用另一次迁移。
而且我也想一步完成,所以在旧模型中定义ModelY
一个新字段y
,生成迁移,以某种方式修复它,然后立即应用并使其工作。
这种情况有什么最佳实践吗?特别是,在哪里存储这个新创建的对象的 id?同一个数据库中的一些专用表?
解决方案
您将无法在单个迁移文件中执行此操作,但是您可以创建多个迁移文件来实现此目的。我会尽力帮助你,虽然我不完全确定这是你想要的,它应该教你一两件事关于 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。
推荐阅读
- python - 是否有根据类型注释检查覆盖函数返回类型的python linter?
- batch-file - 批处理命令检查当前使用的系统语言,(不是默认语言)
- javascript - Firestore 认为我离线,即使我不在
- gps - FMB120 在 localhost 上截获来自设备的数据
- node.js - Passport序列化用户使用问题,怎么办?
- arrays - 如何从数组中获取数据?
- java - 按自定义顺序对字符串元素数组进行排序
- c++ - 如何在 C++ 中返回具有原子成员的结构
- cordova - 如何显示离线网站视图 Cordova?
- php - 图像未显示 - Symfony 树枝