Kurzfassung
Sie sollten den Verwaltungsbefehl NICHTloaddata
direkt in einer Datenmigration verwenden.
# 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),
]
Lange Version
loaddata
nutzt , django.core.serializers.python.Deserializer
die die meisten up-to-date verwendet Modelle , historische Daten in einer Migration deserialisieren. Das ist falsches Verhalten.
Angenommen, es gibt eine Datenmigration, bei der der loaddata
Verwaltungsbefehl zum Laden von Daten von einem Fixture verwendet wird und die bereits auf Ihre Entwicklungsumgebung angewendet wird.
Später beschließen Sie, dem entsprechenden Modell ein neues erforderliches Feld hinzuzufügen. Führen Sie dies aus und führen Sie eine neue Migration für Ihr aktualisiertes Modell durch (und geben Sie dem neuen Feld möglicherweise einen einmaligen Wert, wenn ./manage.py makemigrations
Sie dazu aufgefordert werden ).
Sie führen die nächste Migration aus und alles ist gut.
Schließlich sind Sie mit der Entwicklung Ihrer Django-Anwendung fertig und stellen sie auf dem Produktionsserver bereit. Jetzt ist es Zeit für Sie, die gesamten Migrationen in der Produktionsumgebung von Grund auf neu auszuführen.
Die Datenmigration schlägt jedoch fehl . Dies liegt daran, dass das deserialisierte Modell aus dem loaddata
Befehl, der den aktuellen Code darstellt, nicht mit leeren Daten für das neue erforderliche Feld gespeichert werden kann, das Sie hinzugefügt haben. Dem Originalgerät fehlen die notwendigen Daten dafür!
Aber selbst wenn Sie das Gerät mit den erforderlichen Daten für das neue Feld aktualisieren, schlägt die Datenmigration immer noch fehl . Wenn die Datenmigration ausgeführt wird, wird die nächste Migration, bei der die entsprechende Spalte zur Datenbank hinzugefügt wird, noch nicht angewendet. Sie können keine Daten in einer nicht vorhandenen Spalte speichern!
Schlussfolgerung: Bei einer Datenmigration führt derloaddata
Befehl zu potenziellen Inkonsistenzen zwischen dem Modell und der Datenbank. Sie sollten es definitiv NICHT direkt in einer Datenmigration verwenden.
Die Lösung
loaddata
Der Befehl basiert auf der django.core.serializers.python._get_model
Funktion, um das entsprechende Modell von einem Gerät abzurufen, das die aktuellste Version eines Modells zurückgibt. Wir müssen es mit Affen patchen, damit es das historische Modell erhält.
(Der folgende Code funktioniert für 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),
]