Versão curta
Você NÃO deve usar o loaddata
comando de gerenciamento diretamente em uma migração de dados.
# 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),
]
Versão longa
loaddata
utiliza o django.core.serializers.python.Deserializer
que usa os modelos mais atualizados para desserializar dados históricos em uma migração. Esse é um comportamento incorreto.
Por exemplo, suponha que haja uma migração de dados que utiliza o loaddata
comando de gerenciamento para carregar dados de um aparelho, e já está aplicado em seu ambiente de desenvolvimento.
Posteriormente, você decide adicionar um novo campo obrigatório ao modelo correspondente, então você o faz e faz uma nova migração em relação ao seu modelo atualizado (e possivelmente fornece um valor único para o novo campo quando ./manage.py makemigrations
solicitado).
Você executa a próxima migração e está tudo bem.
Finalmente, você concluiu o desenvolvimento de seu aplicativo Django e o implementou no servidor de produção. Agora é a hora de você executar todas as migrações do zero no ambiente de produção.
No entanto, a migração de dados falha . Isso ocorre porque o modelo desserializado do loaddata
comando, que representa o código atual, não pode ser salvo com dados vazios para o novo campo obrigatório adicionado. O acessório original não possui os dados necessários para isso!
Mas mesmo se você atualizar o aparelho com os dados necessários para o novo campo, a migração de dados ainda falhará . Quando a migração de dados está em execução, a próxima migração que adiciona a coluna correspondente ao banco de dados, ainda não é aplicada. Você não pode salvar dados em uma coluna que não existe!
Conclusão: em uma migração de dados, oloaddata
comando introduz uma possível inconsistência entre o modelo e o banco de dados. Definitivamente, você NÃO deveusá-lo diretamente em uma migração de dados.
A solução
loaddata
comando depende da django.core.serializers.python._get_model
função para obter o modelo correspondente de um acessório, que retornará a versão mais atualizada de um modelo. Precisamos fazer um patch de macacos para obter o modelo histórico.
(O código a seguir funciona para 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. However, here it
# has a different context, specifically, the apps variable.
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),
]