用Django 1.7加载初始数据和数据迁移

我最近从Django 1.6切换到1.7,我开始使用迁移(我从来没有使用南)。

在1.7之前,我曾经用一个fixture/initial_data.json文件加载了初始数据,该文件使用python manage.py syncdb命令加载(创build数据库时)。

现在,我开始使用迁移,并且此行为已被弃用:

如果应用程序使用迁移,则不会自动加载灯具。 由于Django 2.0中的应用程序需要进行迁移,因此此行为被视为弃用。 如果您想加载应用程序的初始数据,请考虑在数据迁移中进行。 ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )

官方文档没有清楚的例子,所以我的问题是:

使用数据迁移导入此类初始数据的最佳方法是:

  1. 通过多次调用mymodel.create(...)来编写Python代码,
  2. 使用或编写Django函数( 如调用loaddata )从JSON fixture文件加载数据。

我更喜欢第二个选项。

我不想使用南方,因为现在Django似乎可以自己做到。

更新 :有关此解决scheme可能导致的问题,请参阅下面的@ GwynBleidD的评论,并参阅下面的@ rockalite的答案,这种方法对未来的模型更改更持久。


假设你在<yourapp>/fixtures/initial_data.json有一个夹具文件

  1. 创build您的空迁移:

    在Django 1.7中:

     python manage.py makemigrations --empty <yourapp> 

    在Django 1.8+中,您可以提供一个名称:

     python manage.py makemigrations --empty <yourapp> --name load_intial_data 
  2. 编辑您的迁移文件<yourapp>/migrations/0002_auto_xxx.py

    2.1。 自定义实现,受loaddata启发(初始答案):

     import os from sys import path from django.core import serializers fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) fixture_filename = 'initial_data.json' def load_fixture(apps, schema_editor): fixture_file = os.path.join(fixture_dir, fixture_filename) fixture = open(fixture_file, 'rb') objects = serializers.deserialize('json', fixture, ignorenonexistent=True) for obj in objects: obj.save() fixture.close() def unload_fixture(apps, schema_editor): "Brutally deleting all entries for this model..." MyModel = apps.get_model("yourapp", "ModelName") MyModel.objects.all().delete() class Migration(migrations.Migration): dependencies = [ ('yourapp', '0001_initial'), ] operations = [ migrations.RunPython(load_fixture, reverse_code=unload_fixture), ] 

    2.2。 一个更简单的load_fixture解决scheme(per @ juliocesar的build议):

     from django.core.management import call_command fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) fixture_filename = 'initial_data.json' def load_fixture(apps, schema_editor): fixture_file = os.path.join(fixture_dir, fixture_filename) call_command('loaddata', fixture_file) 

    如果你想使用自定义目录很有用。

    2.3。 最简单:使用app_label调用loaddata会自动从<yourapp>fixtures dir中加载灯具:

     from django.core.management import call_command fixture = 'initial_data' def load_fixture(apps, schema_editor): call_command('loaddata', fixture, app_label='yourapp') 

    如果你不指定app_label ,loaddata会尝试从所有应用程序的fixture目录(你可能不需要)加载fixture文件名。

  3. 运行

     python manage.py migrate <yourapp> 

简洁版本

您不应该直接在数据迁移中使用loaddata management命令。

 # Bad example for a data migration from django.db import migrations from django.core.management import call_command def load_fixture(apps, schema_editor): # No, it's wrong. DON'T DO THIS! call_command('loaddata', 'your_data.json', app_label='yourapp') class Migration(migrations.Migration): dependencies = [ # Dependencies to other migrations ] operations = [ migrations.RunPython(load_fixture), ] 

长版

loaddata利用了django.core.serializers.python.Deserializer ,它使用最新的模型来反序列化迁移中的历史数据。 这是不正确的行为。

例如,假设有一个利用loaddatapipe理命令从fixture中加载数据的数据迁移,并且已经应用​​到您的开发环境中。

之后,您决定为相应的模型添加一个新的必填字段,因此您可以执行此操作,并根据更新后的模型进行新的迁移(并可能会在./manage.py makemigrations提示您时向新字段提供一次性值)。

你运行下一个迁移,一切都很好。

最后,你完成了开发你的Django应用程序,并将其部署到生产服务器上。 现在是您在生产环境中从头开始运行整个迁移的时候了。

但是, 数据迁移失败 。 这是因为来自loaddata命令的代表当前代码的反序列化模型无法使用您添加的新的必填字段的空数据保存。 原来的夹具缺乏必要的数据!

但即使您用新字段的所需数据更新了夹具, 数据迁移仍然失败 。 当数据迁移正在运行时, 下一次将相应列添加到数据库的迁移尚未应用。 您不能将数据保存到不存在的列!

结论:在数据迁移中, loaddata命令引入模型和数据库之间的潜在不一致性。 您绝对应该直接在数据迁移中使用它。

解决scheme

loaddata命令依赖于django.core.serializers.python._get_model函数从fixture中获取相应的模型,这将返回模型的最新版本。 我们需要对其进行修补,使其成为历史模型。

(以下代码适用于Django 1.8.x)

 # Good example for a data migration from django.db import migrations from django.core.serializers import base, python from django.core.management import call_command def load_fixture(apps, schema_editor): # Save the old _get_model() function old_get_model = python._get_model # Define new _get_model() function here, which utilizes the apps argument to # get the historical version of a model. This piece of code is directly stolen # from django.core.serializers.python._get_model, unchanged. def _get_model(model_identifier): try: return apps.get_model(model_identifier) except (LookupError, TypeError): raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier) # Replace the _get_model() function on the module, so loaddata can utilize it. python._get_model = _get_model try: # Call loaddata command call_command('loaddata', 'your_data.json', app_label='yourapp') finally: # Restore old _get_model() function python._get_model = old_get_model class Migration(migrations.Migration): dependencies = [ # Dependencies to other migrations ] operations = [ migrations.RunPython(load_fixture), ] 

在迁移的应用程序中加载初始数据的最佳方式是通过数据迁移(也在文档中build议)。 优点是夹具在testing和生产过程中均被装载。

@n__build议在迁移中重新实现loaddata命令。 然而,在我的testing中,直接调用loaddata命令也可以正常工作。 整个过程是这样的:

  1. <yourapp>/fixtures/initial_data.json创build一个夹具文件

  2. 创build您的空迁移:

     python manage.py makemigrations --empty <yourapp> 
  3. 编辑您的迁移文件/migrations/0002_auto_xxx.py

     from django.db import migrations from django.core.management import call_command def loadfixture(apps, schema_editor): call_command('loaddata', 'initial_data.json') class Migration(migrations.Migration): dependencies = [ ('<yourapp>', '0001_initial'), ] operations = [ migrations.RunPython(loadfixture), ] 

受到一些评论(也就是n_o's)的启发,并且我已经将很多initial_data.*文件分散在多个应用程序中,所以我决定创build一个Django应用程序来帮助创build这些数据迁移。

使用django-migration-fixture,你可以简单的运行下面的pipe理命令,它将search所有INSTALLED_APPS中的initial_data.*文件,并将它们转换为数据迁移。

 ./manage.py create_initial_data_fixtures Migrations for 'eggs': 0002_auto_20150107_0817.py: Migrations for 'sausage': Ignoring 'initial_data.yaml' - migration already exists. Migrations for 'foo': Ignoring 'initial_data.yaml' - not migrated. 

有关安装/使用说明,请参阅django-migration-fixture 。

为了给你的数据库一些初始数据,写一个数据迁移。 在数据迁移中,使用RunPython函数来加载数据。

不要使用这种方式写入任何loaddata命令。

您的数据迁移将只运行一次。 迁移是一个有序的迁移序列。 运行003_xxxx.py迁移时,django迁移会在数据库中写入此应用程序已迁移至此(003),并仅运行以下迁移。

上述解决scheme不幸的是我不工作。 我发现每当我改变我的模型,我必须更新我的灯具。 理想情况下,我会写数据迁移,以类似地修改创build的数据和夹具加载的数据。

为了方便起见, 我写了一个快速的函数 ,它将查看当前应用程序的fixtures目录并加载一个fixture。 将此函数放入与迁移中的字段相匹配的模型历史logging中的迁移。

在我看来,赛程有点糟糕。 如果你的数据库频繁变化,保持最新状态,很快就会变成一场噩梦。 实际上,这不仅仅是我的观点,在“Django的两勺”一书中有更好的解释。

相反,我会写一个Python文件来提供初始设置。 如果你需要更多的东西,我会build议你看看工厂的男孩 。

如果你需要迁移一些数据,你应该使用数据迁移 。

还有关于使用灯具的“烧你的灯具,使用型号工厂” 。