Недавно я переключился с Django 1.6 на 1.7, и начал использовать миграции (я никогда не использовал Юг).
До 1.7 я загружал исходные данные в файл fixture/initial_data.json
, который был загружен командой python manage.py syncdb
(при создании базы данных).
Теперь я начал использовать миграции, и это поведение устарело:
Если приложение использует миграции, автоматическая загрузка светильников не производится. Поскольку для приложений в Django 2.0 потребуется миграция, это поведение считается устаревшим. Если вы хотите загрузить исходные данные для приложения, подумайте об этом в процессе переноса данных. (https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures)
Официальная документация не содержит четкого примера того, как это сделать, поэтому мой вопрос:
Каков наилучший способ импорта таких исходных данных с помощью переноса данных:
mymodel.create(...)
,loaddata
) для загрузки данных из файла привязки JSON.Я предпочитаю второй вариант.
Я не хочу использовать Юг, поскольку Django, похоже, теперь может сделать это изначально.
Обновление: см. Ниже комментарий @GwynBleidD о проблемах, которые может вызвать это решение, и см. Ответ @Rockallite ниже, чтобы узнать о подходе, более устойчивом к будущим изменениям модели.
Предполагая, что у вас есть файл <yourapp>/fixtures/initial_data.json
в <yourapp>/fixtures/initial_data.json
Создайте пустую миграцию:
В Джанго 1.7:
python manage.py makemigrations --empty <yourapp>
В Django 1. 8+ вы можете указать имя:
python manage.py makemigrations --empty <yourapp> --name load_intial_data
Отредактируйте файл миграции <yourapp>/migrations/0002_auto_xxx.py
2.1. Пользовательская реализация, вдохновленная Django ' 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
(согласно предложению @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)
Полезно, если вы хотите использовать пользовательский каталог.
2,3. Простейший: вызов loaddata
с app_label
загрузят светильники из <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
имя файл из всех приложений приборов каталогов (которые вы, вероятно, не хотите).
Запустить его
python manage.py migrate <yourapp>
load_fixture
можно улучшить, просто вызвав call_command('loaddata', fixture_file)
loaddata
), вместо непосредственного вызова loaddata
. Я помню проблемы с loaddata
не работали "как ожидалось" при выполнении полной миграции. python manage.py migrate <yourapp>
будет работать отлично, как и python manage.py loaddata
, но python manage.py migrate
не сможет найти файл python manage.py loaddata
. Может быть, я делал что-то не так.
Вам НЕ следует использовать loaddata
управления loaddata
непосредственно при переносе данных.
# 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 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
который использует самые современные модели для десериализации исторических данных в процессе миграции. Это неправильное поведение.
Например, предполагается, что существует миграция данных, которая использует loaddata
управления loaddata
для загрузки данных из осветителя, и она уже применяется в вашей среде разработки.
Позже вы решаете добавить новое обязательное поле в соответствующую модель, поэтому вы делаете это и делаете новую миграцию для своей обновленной модели (и, возможно, предоставляете одноразовое значение для нового поля, когда ./manage.py makemigrations
запрашивает вас).
Вы запускаете следующую миграцию, и все хорошо.
Наконец, вы закончили разработку приложения Django и развернули его на рабочем сервере. Теперь пришло время запустить все миграции с нуля в производственной среде.
Однако перенос данных не выполняется. Это потому, что десериализованная модель из команды loaddata
, представляющая текущий код, не может быть сохранена с пустыми данными для нового обязательного поля, которое вы добавили. В оригинальном светильнике отсутствуют необходимые данные!
Но даже если вы обновите прибор с необходимыми данными для нового поля, миграция данных все равно не удастся. Когда выполняется миграция данных, следующая миграция, которая добавляет соответствующий столбец в базу данных, еще не применяется. Вы не можете сохранить данные в столбце, который не существует!
Вывод: при переносе данных команда loaddata
вносит потенциальное несоответствие между моделью и базой данных. Вы определенно не должны использовать его непосредственно при переносе данных.
loaddata
использует функцию django.core.serializers.python._get_model
для получения соответствующей модели из осветителя, которая будет возвращать самую последнюю версию модели. Мы должны сделать это, чтобы получить историческую модель.
(Следующий код работает для 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),
]
objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
страдать от той же проблемы, что и loaddata
? Или ignorenonexistent=True
покрывает все возможные проблемы?
ignorenonexistent=True
имеет два эффекта: 1) он игнорирует модели прибора, которых нет в самых последних определениях модели, 2) он игнорирует поля модели прибора которые не в самом актуальном соответствующем определении модели. Ни один из них не справляется с ситуацией нового обязательного поля в модели . Так что, да, я думаю, что он страдает той же проблемой, что и обычные данные loaddata
.
Вдохновленный некоторыми комментариями (а именно n__o) и тем, что у меня много файлов initial_data.*
Распределенных по нескольким приложениям, я решил создать приложение Django, которое облегчит создание этих миграций данных.
Используя django -igration-fixture, вы можете просто запустить следующую команду управления, и она INSTALLED_APPS
все ваши 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 -igration-fixture для инструкций по установке/использованию.
Лучший способ загрузить исходные данные в переносимые приложения - это миграция данных (также рекомендуется в документах). Преимущество заключается в том, что крепление таким образом загружается как во время испытаний, так и при производстве.
@n__o предложила переопределить команду loaddata
в миграции. Однако в моих тестах вызов команды loaddata
также отлично работает. Таким образом, весь процесс:
Создайте файл fixture в <yourapp>/fixtures/initial_data.json
Создайте пустую миграцию:
python manage.py makemigrations --empty <yourapp>
Измените файл миграции /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),
]
loaddata
не подходит для использования при переносе данных, так как она использует django.core.serializers.python.Deserializer
который использует самые современные модели для десериализации исторических данных. Например, если вы добавите новое обязательное поле в модель в более поздней миграции, а затем запустите миграции из новой базы данных, десериализованная модель не удастся сохранить, поскольку новое поле не будет назначено с исторической фикстурой. Даже если вы обновите прибор с новыми полевыми данными, новый столбец еще не будет добавлен в базу данных во время выполнения миграции, поэтому он тоже не удастся.
Чтобы предоставить вашей базе данных некоторые исходные данные, напишите перенос данных. В процессе переноса данных используйте функцию RunPython для загрузки ваших данных.
Не записывайте команду loaddata, так как этот способ устарел.
Ваши миграции данных будут выполняться только один раз. Миграции - упорядоченная последовательность миграций. Когда выполняется миграция 003_xxxx.py, миграции django записывают в базу данных, что это приложение переносится до этого (003), и будут выполняться только следующие миграции.
myModel.create(...)
(или использовать цикл) в функции RunPython?
Представленные выше решения для меня, к сожалению, не работали. Я обнаружил, что каждый раз, когда я меняю свои модели, мне приходится обновлять свои светильники. В идеале я бы вместо этого написал миграцию данных, чтобы модифицировать созданные данные и данные, загруженные с помощью файла, таким же образом.
Чтобы облегчить это я написал краткую функцию, которая будет выглядеть в каталоге fixtures
текущего приложения и загружать прибор. Поместите эту функцию в перемещение в точке истории модели, которая соответствует полям в миграции.
RunPython(load_fixture('badger', 'stoat'))
. gist.github.com/danni/1b2a0078e998ac080111
На Django 2.1 я хотел загрузить некоторые модели (например, названия стран) с исходными данными.
Но я хотел, чтобы это происходило автоматически сразу после выполнения первоначальных миграций.
Поэтому я подумал, что было бы здорово иметь папку sql/
внутри каждого приложения, которое требовало загрузки начальных данных.
Тогда в этой папке sql/
меня будут файлы .sql
с необходимыми DML для загрузки исходных данных в соответствующие модели, например:
INSERT INTO appName_modelName(fieldName)
VALUES
("country 1"),
("country 2"),
("country 3"),
("country 4");
Чтобы быть более наглядным, вот как должно выглядеть приложение, содержащее папку sql/
:
Также я нашел несколько случаев, когда мне нужно было выполнить сценарии sql
в определенном порядке. Поэтому я решил поставить перед именами файлов последовательный номер, как показано на рисунке выше.
Затем мне понадобился способ автоматической загрузки любых SQLs
доступных в любой папке приложения, с помощью команды python manage.py migrate
.
Поэтому я создал другое приложение с именем initial_data_migrations
а затем добавил это приложение в список INSTALLED_APPS
в файле settings.py
. Затем я создал папку migrations
внутри и добавил файл с именем run_sql_scripts.py
(который на самом деле является пользовательской миграцией). Как видно на рисунке ниже:
Я создал run_sql_scripts.py
чтобы он позаботился о запуске всех сценариев sql
доступных в каждом приложении. Затем он запускается, когда кто-то запускает python manage.py migrate
. Эта пользовательская migration
также добавляет вовлеченные приложения в качестве зависимостей, таким образом, она пытается выполнить операторы sql
только после того, как требуемые приложения выполнили свои миграции 0001_initial.py
(мы не хотим пытаться выполнить оператор SQL для несуществующей таблицы).
Вот источник этого скрипта:
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))
]
Я надеюсь, что кто-то найдет это полезным, у меня это сработало! Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать.
ПРИМЕЧАНИЕ. Возможно, это не лучшее решение, так как я только начинаю работать с django, но все же хотел бы поделиться с вами всеми этими практическими рекомендациями, поскольку я не нашел много информации, пока гуглял об этом.
По-моему, светильники немного плохие. Если ваша база данных будет меняться часто, то держать ее в курсе событий скоро будет кошмар. Собственно, это не только мое мнение, в книге "Два совок Джанго" это объяснялось намного лучше.
Вместо этого я напишу файл Python, чтобы обеспечить первоначальную настройку. Если вам нужно что-то еще, я предлагаю вам посмотреть Factory boy.
Если вам нужно перенести некоторые данные, вы должны использовать миграцию данных.
Там также "Записать свои светильники, использовать фабрики моделей" об использовании светильников.