webentwicklung-frage-antwort-db.com.de

Anfangsdaten mit Django 1.7 und Datenmigrationen laden

Ich habe vor kurzem von Django 1.6 auf 1.7 gewechselt und begann mit Migrationen (ich habe nie Süd verwendet).

Vor 1.7 habe ich Anfangsdaten mit einer fixture/initial_data.json-Datei geladen, die mit dem python manage.py syncdb-Befehl geladen wurde (beim Erstellen der Datenbank).

Jetzt habe ich mit Migrationen begonnen und dieses Verhalten ist veraltet:

Wenn eine Anwendung Migrationen verwendet, werden die Scheinwerfer nicht automatisch geladen. Da für Anwendungen in Django 2.0 Migrationen erforderlich sind, wird dieses Verhalten als veraltet angesehen. Wenn Sie Ausgangsdaten für eine App laden möchten, sollten Sie dies bei einer Datenmigration tun. ( https://docs.djangoproject.com/de/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )

Die offizielle Dokumentation hat kein klares Beispiel, wie es geht, daher lautet meine Frage:

Was ist der beste Weg, um solche ursprünglichen Daten mithilfe von Datenmigrationen zu importieren:

  1. Schreiben Sie Python-Code mit mehreren Aufrufen an mymodel.create(...).
  2. Verwenden oder schreiben Sie eine Django-Funktion ( wie loaddata ), um Daten aus einer JSON-Fixture-Datei zu laden.

Ich bevorzuge die zweite Option.

Ich möchte South nicht verwenden, da Django jetzt in der Lage ist, dies nativ zu tun.

88
Mickaël

Update: Informationen zu den Problemen, die diese Lösung verursachen kann, finden Sie im Kommentar von @ GwynBleidD und in der Antwort von @ Rockallite weiter unten.


Vorausgesetzt, Sie haben eine Fixture-Datei in <yourapp>/fixtures/initial_data.json

  1. Erstellen Sie Ihre leere Migration:

    In Django 1.7:

    python manage.py makemigrations --empty <yourapp>
    

    In Django 1.8+ können Sie einen Namen angeben:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. Bearbeiten Sie Ihre Migrationsdatei <yourapp>/migrations/0002_auto_xxx.py

    2.1. Benutzerdefinierte Implementierung, inspiriert von Django 'loaddata (erste Antwort):

    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. Eine einfachere Lösung für load_fixture (per @ juliocesars Vorschlag):

    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) 
    

    Nützlich, wenn Sie ein benutzerdefiniertes Verzeichnis verwenden möchten.

    2.3. Simplest: Wenn Sie loaddata mit app_label aufrufen, werden die Scheinwerfer automatisch aus dem <yourapp>-Verzeichnis fixtures geladen: 

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

    Wenn Sie nicht app_label angeben, versucht loaddata, fixture dateiname aus all apps Verzeichnisse zu laden. 

  3. Starte es

    python manage.py migrate <yourapp>
    
76
n__o

Kurze Version

Sie sollten NICHT den Verwaltungsbefehl loaddata direkt bei 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 verwendet Django.core.serializers.python.Deserializer, das die aktuellsten Modelle zur Deserialisierung historischer Daten in einer Migration verwendet. Das ist falsches Verhalten.

Angenommen, es gibt eine Datenmigration, die den loaddata-Verwaltungsbefehl zum Laden von Daten aus einem Fixture verwendet und bereits in Ihrer Entwicklungsumgebung angewendet wird.

Später entscheiden Sie sich, ein neues required -Feld zum entsprechenden Modell hinzuzufügen, so dass Sie dies tun und eine neue Migration für Ihr aktualisiertes Modell durchführen (und möglicherweise einen einmaligen Wert für das neue Feld angeben, wenn ./manage.py makemigrations Sie dazu auffordert) .

Sie führen die nächste Migration durch und alles ist gut.

Schließlich ist die Entwicklung Ihrer Django-Anwendung abgeschlossen, und Sie stellen sie auf dem Produktionsserver bereit. Nun ist es Zeit für Sie, die gesamten Migrationen in der Produktionsumgebung von Grund auf durchzuführen.

Die Datenmigration schlägt fehl. Das deserialisierte Modell aus dem Befehl loaddata, das den aktuellen Code darstellt, kann nicht mit leeren Daten für das neue Feld required gespeichert werden, das Sie hinzugefügt haben. Dem ursprünglichen Gerät fehlen dazu die notwendigen Daten!

Auch wenn Sie das Gerät mit den erforderlichen Daten für das neue Feld aktualisieren, ist die Datenmigration schlägt immer noch fehl. Während der Datenmigration wird die Migration next, die die entsprechende Spalte zur Datenbank hinzufügt, noch nicht angewendet. Sie können keine Daten in einer Spalte speichern, die nicht vorhanden ist!

Conclusion: Bei einer Datenmigration führt der Befehl loaddata zu einer möglichen Inkonsistenz zwischen dem Modell und der Datenbank. Sie sollten es auf jeden Fall NICHT direkt bei einer Datenmigration verwenden.

Die Lösung

Der Befehl loaddata basiert auf der Funktion Django.core.serializers.python._get_model, um das entsprechende Modell von einem Fixture abzurufen, das die aktuellste Version eines Modells zurückgibt. Wir müssen ihn mit einem Affen-Patch versehen, damit er 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),
    ]
37
Rockallite

Inspiriert von einigen Kommentaren (nämlich n__o's) und der Tatsache, dass ich viele initial_data.*-Dateien auf mehrere Apps verteilt habe, habe ich mich entschlossen, eine Django-App zu erstellen, die die Erstellung dieser Datenmigrationen erleichtern würde.

Mit Django-migration-fixture können Sie einfach den folgenden Verwaltungsbefehl ausführen, der alle Ihre INSTALLED_APPS-Dateien nach initial_data.*-Dateien durchsucht und diese in Datenmigrationen verwandelt.

./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.

Siehe Django-Migration-Fixture für Anweisungen zur Installation/Verwendung.

5
alexhayes

Das Laden der ursprünglichen Daten in migrierten Apps erfolgt am besten durch Datenmigrationen (wie in den Dokumenten empfohlen). Der Vorteil ist, dass die Vorrichtung sowohl während der Tests als auch in der Produktion geladen wird.

@n__o hat vorgeschlagen, den Befehl loaddata in der Migration neu zu implementieren. In meinen Tests funktioniert der direkte Aufruf von loaddata auch gut. Der ganze Prozess ist also:

  1. Erstellen Sie eine Fixture-Datei in <yourapp>/fixtures/initial_data.json

  2. Erstellen Sie Ihre leere Migration:

    python manage.py makemigrations --empty <yourapp>
    
  3. Bearbeiten Sie Ihre Migrationsdatei /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),
        ]
    
4
Pratyush

Um Ihrer Datenbank einige Anfangsdaten zu geben, schreiben Sie eine Datenmigration. Verwenden Sie bei der Datenmigration die Funktion RunPython , um Ihre Daten zu laden.

Schreiben Sie keine loaddata-Befehle, da diese Methode veraltet ist.

Ihre Datenmigrationen werden nur einmal ausgeführt. Die Migrationen sind eine geordnete Abfolge von Migrationen. Wenn die 003_xxxx.py-Migrationen ausgeführt werden, schreibt Django-Migrationen in die Datenbank, dass diese App bis zu diesem Zeitpunkt (003) migriert wird und nur die folgenden Migrationen ausführt.

2
aRkadeFR

Die oben dargestellten Lösungen haben für mich leider nicht funktioniert. Ich habe festgestellt, dass ich jedes Mal, wenn ich meine Modelle wechsle, meine Geräte aktualisieren muss. Idealerweise würde ich stattdessen Datenmigrationen schreiben, um die erstellten Daten und die vom Fixture geladenen Daten auf ähnliche Weise zu ändern.

Um dies zu erleichtern habe ich eine schnelle Funktion geschrieben, die im fixtures-Verzeichnis der aktuellen App nach einem Fixture sucht. Setzen Sie diese Funktion in eine Migration an der Stelle der Modellhistorie, die den Feldern in der Migration entspricht.

1
leifdenby

Auf Django 2.1 wollte ich einige Modelle (wie zum Beispiel Ländernamen) mit Anfangsdaten laden.

Ich wollte jedoch, dass dies unmittelbar nach der Durchführung der ursprünglichen Migrationen automatisch geschieht.

Also dachte ich, dass es toll wäre, einen sql/-Ordner in jeder Anwendung zu haben, der das Laden der Anfangsdaten erfordert.

Innerhalb dieses sql/-Ordners hätte ich .sql-Dateien mit den erforderlichen DMLs, um die Ausgangsdaten in die entsprechenden Modelle zu laden, zum Beispiel:

INSERT INTO appName_modelName(fieldName)
VALUES
    ("country 1"),
    ("country 2"),
    ("country 3"),
    ("country 4");

Zur Veranschaulichung würde eine App, die einen sql/-Ordner enthält, folgendermaßen aussehen:  enter image description here

Ich habe auch Fälle gefunden, in denen ich sql-Skripts in einer bestimmten Reihenfolge ausführen musste. Also entschied ich mich, den Dateinamen mit einer fortlaufenden Nummer voranzustellen, wie im Bild oben zu sehen.

Dann brauchte ich eine Möglichkeit, alle SQLs, die in einem beliebigen Anwendungsordner verfügbar sind, automatisch zu laden, indem python manage.py migrate ausgeführt wird.

Also habe ich eine andere Anwendung mit dem Namen initial_data_migrations erstellt und diese App zur Liste von INSTALLED_APPS in der settings.py-Datei hinzugefügt. Dann erstellte ich einen migrations-Ordner und fügte eine Datei mit dem Namen run_sql_scripts.py hinzu (die tatsächlich eine benutzerdefinierte Migration) ist. Wie im Bild unten zu sehen:

 enter image description here

Ich habe run_sql_scripts.py erstellt, damit alle in der jeweiligen Anwendung verfügbaren sql-Skripts ausgeführt werden. Dieser wird dann ausgelöst, wenn jemand python manage.py migrate ausführt. Diese benutzerdefinierte migration fügt die betroffenen Anwendungen auch als Abhängigkeiten hinzu. Auf diese Weise wird versucht, die sql-Anweisungen nur auszuführen, nachdem die erforderlichen Anwendungen ihre 0001_initial.py-Migrationen ausgeführt haben. (Wir möchten nicht versuchen, eine SQL-Anweisung gegen eine nicht vorhandene Tabelle auszuführen.).

Hier ist die Quelle dieses Skripts:

import os
import itertools

from Django.db import migrations
from YourDjangoProjectName.settings import BASE_DIR, INSTALLED_APPS

SQL_FOLDER = "/sql/"

APP_SQL_FOLDERS = [
    (os.path.join(BASE_DIR, app + SQL_FOLDER), app) for app in INSTALLED_APPS
    if os.path.isdir(os.path.join(BASE_DIR, app + SQL_FOLDER))
]

SQL_FILES = [
    sorted([path + file for file in os.listdir(path) if file.lower().endswith('.sql')])
    for path, app in APP_SQL_FOLDERS
]


def load_file(path):
    with open(path, 'r') as f:
        return f.read()


class Migration(migrations.Migration):

    dependencies = [
        (app, '__first__') for path, app in APP_SQL_FOLDERS
    ]

    operations = [
        migrations.RunSQL(load_file(f)) for f in list(itertools.chain.from_iterable(SQL_FILES))
    ]

Ich hoffe, jemand findet das hilfreich, es hat gut für mich funktioniert! Wenn Sie Fragen haben, lassen Sie es mich wissen.

HINWEIS: Dies ist möglicherweise nicht die beste Lösung, da ich gerade erst mit Django angefangen habe. Ich wollte dieses "How-to" jedoch immer mit Ihnen allen teilen, da ich beim Googeln nicht viel darüber erfahren habe .

Meiner Meinung nach sind die Spiele etwas schlecht. Wenn sich Ihre Datenbank häufig ändert, wird es bald zu einem Albtraum, wenn Sie sie auf dem neuesten Stand halten. Eigentlich ist es nicht nur meine Meinung, in dem Buch "Two Scoops of Django" wird es viel besser erklärt.

Stattdessen schreibe ich eine Python-Datei, um die ersten Einstellungen vorzunehmen. Wenn Sie etwas mehr brauchen, schlage ich vor, Sie schauen sich Factory Boy an.

Wenn Sie einige Daten migrieren müssen, sollten Sie data migrations verwenden.

Es gibt auch "Verbrennen Sie Ihre Geräte, verwenden Sie Modellfabriken" über die Verwendung von Geräten.

0
Griffosx