Ant*_*kov 5 python django django-migrations
假设有一个生产数据库,其中有一些数据。在下一个棘手的情况下,我需要迁移。
有一个模型(已经存在于db中),例如Model,它具有其他模型的外键。
class ModelA: ...
class ModelX: ...
class Model:
a = models.ForeignKey(ModelA, default = A)
x = models.ForeignKey(ModelX, default = X)
Run Code Online (Sandbox Code Playgroud)
我们需要再创建一个模型ModelY以供Model参考。创建Model对象时,对象应具有与某个ModelY对象相关的默认值,该默认值显然尚不可用,但我们应在迁移期间创建它。
class ModelY: ...
class Model:
y = models.ForeignKey (ModelY, default = ??????)
Run Code Online (Sandbox Code Playgroud)
因此,迁移顺序应为:
ModelY表格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)
Run Code Online (Sandbox Code Playgroud)
您会注意到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
)
]
Run Code Online (Sandbox Code Playgroud)
不要直接将您的模型导入到此迁移中!模型需要通过该apps.get_model("my_app", "my_model")方法返回,以便获得在此迁移时间点的模型。如果将来您添加更多字段并运行此迁移,您的模型字段可能与数据库列不匹配(因为模型来自未来,有点......),并且您可能会收到一些关于数据库中缺少列的错误,并且这样的。还要警惕在迁移中在模型/管理器上使用自定义方法,因为您无法从这个代理模型访问它们,通常我可能会将一些代码复制到迁移中,因此它始终运行相同。
现在我们可以返回并修改Model模型以确保y它不为 null 并且它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)
Run Code Online (Sandbox Code Playgroud)
现在您应该python manage.py makemigrations再次运行以创建另一个架构迁移。
您不应该混合模式迁移和数据迁移,因为迁移包含在事务中的方式可能会导致数据库错误,这些错误会抱怨尝试在事务中创建/更改表和执行 INSERT 查询。
最后你可以运行python manage.py migrate它,它应该创建一个默认的 ModelY 对象,将它添加到你模型的 ForeignKey,并删除它null以使其像默认的 ForeignKey。
我留下之前的答案只是为了显示对想法的搜索。最后,我找到了全自动解决方案,因此不再需要手动编辑 django 生成的迁移,但代价是猴子修补,就像平常一样。
这个想法是为ForeignKey的默认提供可调用的,它创建引用模型的默认实例(如果不存在)。但问题是,这个可调用对象不仅可以在最终的 Django 项目阶段调用,还可以在旧项目阶段的迁移过程中调用,因此可以在模型仍然存在的早期阶段为已删除的模型调用它。
RunPython 操作中的标准解决方案是使用迁移状态中的应用程序注册表,但此功能不可用于我们的可调用,因为此注册表作为 RunPython 的参数提供,并且在全局范围内不可用。但为了支持迁移应用和回滚的所有场景,我们需要检测我们是否正在迁移,并访问适当的应用程序注册表。
唯一的解决方案是猴子修补 AddField 和 RemoveField 操作,以将迁移应用程序注册表保留在全局变量中(如果我们正在进行迁移)。
migration_apps = None
def set_migration_apps(apps):
global migration_apps
migration_apps = apps
def get_or_create_default(model_name, app_name):
M = (migration_apps or django.apps.apps).get_model(app_name, model_name)
try:
return M.objects.get(isDefault=True).id
except M.DoesNotExist as e:
o = M.objects.create(isDefault=True)
print '{}.{} default object not found, creating default object : OK'.format(model_name, app_name)
return o
def monkey_patch_fields_operations():
def patch(klass):
old_database_forwards = klass.database_forwards
def database_forwards(self, app_label, schema_editor, from_state, to_state):
set_migration_apps(to_state.apps)
old_database_forwards(self, app_label, schema_editor, from_state, to_state)
klass.database_forwards = database_forwards
old_database_backwards = klass.database_backwards
def database_backwards(self, app_label, schema_editor, from_state, to_state):
set_migration_apps(to_state.apps)
old_database_backwards(self, app_label, schema_editor, from_state, to_state)
klass.database_backwards = database_backwards
patch(django.db.migrations.AddField)
patch(django.db.migrations.RemoveField)
Run Code Online (Sandbox Code Playgroud)
其余的,包括具有数据完整性检查的 Defaultable 模型,位于GitHub 存储库中
| 归档时间: |
|
| 查看次数: |
364 次 |
| 最近记录: |