Niedawno przestawiłem się z Django 1.6 na 1.7 i zacząłem używać migracji (nigdy nie używałem South).
Przed 1.7 ładowałem dane początkowe za pomocą fixture/initial_data.json
pliku, który był ładowany python manage.py syncdb
poleceniem (podczas tworzenia bazy danych).
Teraz zacząłem używać migracji i to zachowanie jest przestarzałe:
Jeśli aplikacja korzysta z migracji, nie ma automatycznego ładowania urządzeń. Ponieważ migracje będą wymagane dla aplikacji w Django 2.0, to zachowanie jest uważane za przestarzałe. Jeśli chcesz załadować początkowe dane aplikacji, rozważ zrobienie tego w ramach migracji danych. ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )
Oficjalna dokumentacja nie posiada wyraźny przykład o tym, jak to zrobić, więc moje pytanie brzmi:
Jaki jest najlepszy sposób importowania takich początkowych danych przy użyciu migracji danych:
- Napisz kod w Pythonie z wieloma wywołaniami
mymodel.create(...)
, - Użyj lub napisz funkcję Django ( jak wywołanie
loaddata
), aby załadować dane z pliku ustawień JSON.
Wolę drugą opcję.
Nie chcę używać South, ponieważ wydaje się, że Django jest teraz w stanie to zrobić natywnie.
Odpowiedzi:
Aktualizacja : Zobacz komentarz @ GwynBleidD poniżej, aby poznać problemy, które może spowodować to rozwiązanie, i zobacz odpowiedź @ Rockallite poniżej, aby uzyskać podejście, które jest bardziej trwałe na przyszłe zmiany modelu.
Zakładając, że masz plik urządzenia w formacie
<yourapp>/fixtures/initial_data.json
Utwórz pustą migrację:
W Django 1.7:
W Django 1.8+ możesz podać nazwę:
Edytuj plik migracji
<yourapp>/migrations/0002_auto_xxx.py
2.1. Niestandardowa implementacja, inspirowana Django '
loaddata
(wstępna odpowiedź):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. Prostsze rozwiązanie dla
load_fixture
(zgodnie z sugestią @ juliocesar):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)
Przydatne, jeśli chcesz użyć katalogu niestandardowego.
2.3. Najprostszy: wywołanie
loaddata
zapp_label
opraw obciążenie będzie od<yourapp>
„sfixtures
reż automatycznie:from django.core.management import call_command fixture = 'initial_data' def load_fixture(apps, schema_editor): call_command('loaddata', fixture, app_label='yourapp')
Jeśli nie określisz
app_label
, loaddata spróbuje załadowaćfixture
nazwę pliku ze wszystkich katalogów urządzeń aplikacji (których prawdopodobnie nie chcesz).Uruchom
źródło
loaddata('loaddata', fixture_filename, app_label='<yourapp>')
również przejdzie bezpośrednio do katalogu urządzeń aplikacji (stąd nie ma potrzeby tworzenia pełnej ścieżki urządzenia)models.py
plików, które mogą mieć dodatkowe pola lub inne zmiany. Jeśli jakieś zmiany zostały wprowadzone po utworzeniu migracji, to się nie powiedzie (więc nie możemy nawet utworzyć migracji schematu po tej migracji). Aby to naprawić, możemy ręcznie zmienić rejestr aplikacji, nad którymi pracuje serializator, na rejestr dostarczony do funkcji migracji dla pierwszego parametru. Rejestr do ścieżki znajduje się pod adresemdjango.core.serializers.python.apps
.app registry
, bez zmiany zmiennej globalnej (co może powodować problemy w hipotetycznej przyszłości z równoległymi migracjami baz danych).Krótka wersja
NIE należy używać
loaddata
polecenia zarządzania bezpośrednio podczas migracji danych.# 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), ]
Długa wersja
loaddata
wykorzystujedjango.core.serializers.python.Deserializer
najbardziej aktualne modele do deserializacji danych historycznych podczas migracji. To nieprawidłowe zachowanie.Załóżmy na przykład, że istnieje migracja danych, która wykorzystuje
loaddata
polecenie zarządzania do ładowania danych z urządzenia i jest już zastosowana w twoim środowisku programistycznym.Później decydujesz się dodać nowe wymagane pole do odpowiedniego modelu, więc robisz to i wykonujesz nową migrację do zaktualizowanego modelu (i ewentualnie
./manage.py makemigrations
podajesz jednorazową wartość do nowego pola, gdy pojawi się monit).Przeprowadzasz następną migrację i wszystko jest w porządku.
Wreszcie, skończyłeś programować swoją aplikację Django i wdrożyć ją na serwerze produkcyjnym. Teraz pora na uruchomienie całej migracji od podstaw na środowisku produkcyjnym.
Jednak migracja danych kończy się niepowodzeniem . Dzieje się tak, ponieważ zdeserializowanego modelu z
loaddata
polecenia, który reprezentuje bieżący kod, nie można zapisać z pustymi danymi dla nowego dodanego wymaganego pola. W oryginalnym urządzeniu brakuje niezbędnych do tego danych!Ale nawet jeśli zaktualizujesz urządzenie o wymagane dane dla nowego pola, migracja danych nadal się nie powiedzie . Gdy migracja danych jest uruchomiona, kolejna migracja, która dodaje odpowiednią kolumnę do bazy danych, nie jest jeszcze stosowana. Nie możesz zapisać danych w kolumnie, która nie istnieje!
Wniosek: w migracji danych
loaddata
komenda wprowadza potencjalną niespójność między modelem a bazą danych. Zdecydowanie NIE powinieneśużywać go bezpośrednio podczas migracji danych.Rozwiązanie
loaddata
polecenie opiera się nadjango.core.serializers.python._get_model
funkcji, która pobiera odpowiedni model z urządzenia, co zwróci najbardziej aktualną wersję modelu. Musimy go małpować, aby uzyskał model historyczny.(Poniższy kod działa dla 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), ]
źródło
objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
ten sam problem, coloaddata
? Czyignorenonexistent=True
obejmuje wszystkie możliwe problemy?ignorenonexistent=True
argument ma dwa efekty: 1) ignoruje modele urządzenia, które nie znajdują się w najbardziej aktualnych definicjach modelu, 2) ignoruje pola modelu urządzenia, które nie są w najbardziej aktualnej definicji odpowiedniego modelu. Żaden z nich nie radzi sobie z sytuacją nowego wymaganego pola w modelu . Więc tak, myślę, że cierpi na ten sam problem, co zwykłyloaddata
.natural_key()
, czego ta metoda nie wydaje się obsługiwać - po prostu zastąpiłem wartość natural_key rzeczywistym identyfikatorem modelu, do którego się odwołuje.Zainspirowany niektórymi komentarzami (a mianowicie n__o) i faktem, że mam dużo
initial_data.*
plików rozrzuconych po wielu aplikacjach, zdecydowałem się stworzyć aplikację Django, która ułatwiłaby tworzenie tych migracji danych.Korzystanie django-migracja-Uchwyt można po prostu uruchom następujące polecenie zarządzania i będzie wyszukiwanie wszystkich
INSTALLED_APPS
doinitial_data.*
plików i włączyć je do migracji danych../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.
Zobacz django-migracja-fixture, aby uzyskać instrukcje dotyczące instalacji / użytkowania.
źródło
Aby dać swojej bazie danych trochę danych początkowych, napisz migrację danych. Podczas migracji danych użyj funkcji RunPython, aby załadować dane.
Nie pisz żadnej komendy loaddata, ponieważ ta metoda jest przestarzała.
Twoje migracje danych zostaną uruchomione tylko raz. Migracje są uporządkowaną sekwencją migracji. Po uruchomieniu migracji 003_xxxx.py django migrations zapisuje w bazie danych, że ta aplikacja jest migrowana aż do tej (003) i uruchomi tylko poniższe migracje.
źródło
myModel.create(...)
zachęcasz mnie do powtarzania wywołań (lub używania pętli) w funkcji RunPython?Przedstawione powyżej rozwiązania niestety nie zadziałały. Odkryłem, że za każdym razem, gdy zmieniam modele, muszę aktualizować swoje urządzenia. Idealnie byłoby zamiast tego pisać migracje danych, aby podobnie modyfikować utworzone dane i dane ładowane przez urządzenia.
Aby to ułatwić , napisałem szybką funkcję, która zajrzy do
fixtures
katalogu bieżącej aplikacji i załaduje urządzenie. Umieść tę funkcję w migracji w punkcie historii modelu, który jest zgodny z polami w migracji.źródło
RunPython(load_fixture('badger', 'stoat'))
. gist.github.com/danni/1b2a0078e998ac080111Moim zdaniem urządzenia są trochę kiepskie. Jeśli baza danych zmienia się często, aktualizowanie ich wkrótce stanie się koszmarem. Właściwie to nie tylko moja opinia, w książce „Two Scoops of Django” jest to wyjaśnione znacznie lepiej.
Zamiast tego napiszę plik w Pythonie, aby zapewnić początkową konfigurację. Jeśli potrzebujesz czegoś więcej, proponuję zajrzeć do Factory boy .
Jeśli musisz migrować niektóre dane, powinieneś użyć migracji danych .
Jest też „Burn Your Fixtures, Use Model Factories” o używaniu urządzeń.
źródło
W Django 2.1 chciałem załadować niektóre modele (na przykład nazwy krajów) z danymi początkowymi.
Ale chciałem, żeby stało się to automatycznie zaraz po wykonaniu początkowych migracji.
Pomyślałem więc, że byłoby wspaniale mieć
sql/
folder w każdej aplikacji, który wymagałby załadowania danych początkowych.Następnie w tym
sql/
folderze miałbym.sql
pliki z wymaganymi plikami DML, aby załadować dane początkowe do odpowiednich modeli, na przykład:INSERT INTO appName_modelName(fieldName) VALUES ("country 1"), ("country 2"), ("country 3"), ("country 4");
Aby być bardziej opisowym, tak wyglądałaby aplikacja zawierająca
sql/
folder:Znalazłem również przypadki, w których potrzebowałem
sql
skryptów do wykonania w określonej kolejności. Postanowiłem więc poprzedzić nazwy plików kolejnym numerem, jak widać na powyższym obrazku.Potem potrzebowałem sposobu, aby automatycznie załadować wszystkie
SQLs
dostępne w dowolnym folderze aplikacji, wykonującpython manage.py migrate
.Utworzyłem więc inną aplikację o nazwie,
initial_data_migrations
a następnie dodałem tę aplikację do listyINSTALLED_APPS
wsettings.py
pliku. Następnie utworzyłemmigrations
folder w środku i dodałem plik o nazwierun_sql_scripts.py
( co w rzeczywistości jest niestandardową migracją ). Jak widać na poniższym obrazku:Stworzyłem
run_sql_scripts.py
tak, aby zadbał o uruchomienie wszystkichsql
skryptów dostępnych w ramach każdej aplikacji. Ten jest następnie odpalany, gdy ktoś biegniepython manage.py migrate
. Ten niestandardowymigration
dodaje również zaangażowane aplikacje jako zależności, w ten sposób próbuje uruchomićsql
instrukcje dopiero po wykonaniu0001_initial.py
migracji przez wymagane aplikacje (nie chcemy próbować uruchamiać instrukcji SQL na nieistniejącej tabeli).Oto źródło tego skryptu:
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)) ]
Mam nadzieję, że ktoś uzna to za pomocne, działało dobrze dla mnie !. Jeśli masz jakieś pytania, daj mi znać.
UWAGA: To może nie być najlepsze rozwiązanie, ponieważ dopiero zaczynam pracę z django, jednak nadal chciałem się z wami podzielić tym "Jak to zrobić", ponieważ nie znalazłem zbyt wielu informacji podczas wyszukiwania go w Google.
źródło