假设有一个生产数据库,其中有一些数据。在下一个棘手的情况下,我需要迁移。
有一个模型(已经存在于db中),例如public void parseJSONData(String response) {
try {
JSONObject obj = new JSONObject(response);
JSONArray dataArray = obj.getJSONArray("data");
for (int i = 0; i < dataArray.length(); i++) {
JSONObject dataobj = dataArray.getJSONObject(i);
haberModel1.setTitle(dataobj.getString("header"));
haberModel1.setUrl(dataobj.getString("picture"));
haberModel1.setId(dataobj.getString("id"));
haberModelArrayList.add(haberModel1);
setupRecycler();
}
} catch (JSONException e) {
Log.d(""+TAG,e.printStackTrace() );
}
}
,它具有其他模型的外键。
private void setupRecycler() {
lManager = new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(lManager);
rvAdapter = new RvAdapter(this, haberModelArrayList);
recyclerView.setAdapter(rvAdapter);
isLoading = false;
}
我们需要再创建一个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,然后将此ID用作新字段的默认值,然后再对该新字段进行另一次迁移,就可以了。
我也想一步一步做完所有事情,因此在旧模型中同时定义y
和新字段Model
,生成迁移,以某种方式解决,然后应用于一次,使其工作。
对于这种情况,是否有最佳做法?特别是在哪里存储此新创建的对象的ID?同一数据库中有一些专用表?
答案 0 :(得分:3)
您将无法在单个迁移文件中执行此操作,但是您将能够创建多个迁移文件来实现此目的。尽管我不确定您要的是什么,但我会尽全力帮助您,它应该教给您关于Django迁移的一两件事。
我将在此处引用两种类型的迁移,一种是模式迁移,这些是您在更改模型后通常生成的迁移文件。另一个是数据迁移,需要使用--empty
命令的makemigrations
选项创建这些文件,例如python manage.py makemigrations my_app --empty
,用于移动数据,在需要更改为非null的空列上设置数据,等等。
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。
答案 1 :(得分:0)
最后,我来到了以下解决方案。
首先,我接受通过isDefault
属性标识默认对象的想法,并编写了一些抽象模型来处理该默认对象,以尽可能保持数据完整性(代码在文章底部)。
我在接受的解决方案中不太喜欢的是,数据迁移与架构迁移混合在一起。很容易丢失它们,即在挤压过程中。当我确定所有生产数据库和备份数据库都与代码一致时,有时我也完全删除了迁移,因此我可以生成单个初始迁移并对其进行伪造。将数据迁移与模式迁移保持在一起会破坏此工作流程。
因此,我决定将所有数据迁移都保留在migrations
软件包之外的单个文件中。因此,我在应用程序包中创建了data.py
,并将所有数据迁移都放在单个函数migratedata
中,请记住,可以在早期阶段调用此函数,而此时某些模型可能仍然不存在,因此我们需要捕获LookupError
个应用注册表访问的异常。比起数据迁移中的每个RunPython
操作,我都使用此功能。
所以工作流看起来像这样(我们假设Model
和ModelX
已经存在):
1)创建ModelY
:
class ModelY(Defaultable):
y_name = models.CharField(max_length=255, default='ModelY')
2)生成迁移:
manage.py makemigration
3)在data.py
中添加数据迁移(在我的情况下,将模型名称添加到defaultable
列表中):
# data.py in myapp
def migratedata(apps, schema_editor):
defaultables = ['ModelX', 'ModelY']
for m in defaultables:
try:
M = apps.get_model('myapp', m)
if not M.objects.filter(isDefault=True).exists():
M.objects.create(isDefault=True)
except LookupError as e:
print '[{} : ignoring]'.format(e)
# owner model, should be after defaults to support squashed migrations over empty database scenario
Model = apps.get_model('myapp', 'Model')
if not Model.objects.all().exists():
Model.objects.create()
4)通过添加操作RunPython
编辑迁移:
from myapp.data import migratedata
class Migration(migrations.Migration):
...
operations = [
migrations.CreateModel(name='ModelY', ...),
migrations.RunPython(migratedata, reverse_code=migratedata),
]
5)将ForeignKey(ModelY)
添加到Model
:
class Model(models.Model):
# SET_DEFAULT ensures that there will be no integrity issues, but make sure default object exists
y = models.ForeignKey(ModelY, default=ModelY.default, on_delete=models.SET_DEFAULT)
6)再次生成迁移:
manage.py makemigration
7)迁移:
manage.py migrate
8)完成!
整个链可以应用于空数据库,它将创建最终模式并用初始数据填充它。
当我们确定数据库与代码同步时,我们可以轻松地删除长迁移链,生成单个初始迁移链,向其添加RunPython(migratedata, ...)
,然后使用--fake-initial
进行迁移(删除{ {1}}表之前)。
嗯,如此简单的任务是如此棘手的解决方案!
最后有django_migrations
个模型源代码:
Defaultable
答案 2 :(得分:0)
我留下了先前的答案只是为了表达对思想的搜索。最终,我建立了全自动解决方案,因此不再需要手动编辑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)
其余部分(包括带有数据完整性检查的Defaultable模型)在GitHub repository
中