Skip to content

Instantly share code, notes, and snippets.

@danni
Forked from leifdenby/migration_loaddata.py
Last active November 16, 2023 08:28
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save danni/1b2a0078e998ac080111 to your computer and use it in GitHub Desktop.
Save danni/1b2a0078e998ac080111 to your computer and use it in GitHub Desktop.
Django function for loading fixtures which use the current migration state of the model
import os
import logging
from django.core import serializers
LOGGER = logging.getLogger(__name__)
def load_fixture(app, fixture, ignorenonexistent=True):
"""
A factory to load a named fixture via a data migration.
"""
def inner(apps, schema_editor):
"""
Loads migrations that work at current state of a model, in constrast to
`loaddata` which requires a fixture to have data matching the fields
defined in `models.py`.
Based on https://gist.github.com/leifdenby/4586e350586c014c1c9a
"""
# relative path to fixtures
fixtures_dir = os.path.join(app, 'fixtures')
# monkey patch serializers `apps` so that it uses the models in the
# current migration state
original_apps = serializers.python.apps
try:
serializers.python.apps = apps
objects = None
for extension in ('json', 'yaml', 'xml'):
fixture_path = os.path.join(
fixtures_dir,
'%s.%s' % (fixture, extension))
LOGGER.debug("Trying %s", fixtures_dir)
if os.path.exists(fixture_path):
print("Loading fixtures from %s... " % fixture_path)
with open(fixture_path, 'rb') as file_:
objects = serializers.deserialize(
extension, file_,
ignorenonexistent=ignorenonexistent)
count = 0
for obj in objects:
obj.save()
count += 1
print("Loaded %d objects." % count)
if objects is None:
raise Exception(
"Couldn't find the '%s' fixture for the '%s' app." % (
fixture, app))
finally:
serializers.python.apps = original_apps
return inner
@yardley
Copy link

yardley commented Sep 12, 2020

Thank you for modifying the original script. How should this be run from a migration file?

The objects are inserted but I get an error CommandError: Unknown command: None

This was my migration, perhaps you can advise how you run it?

def forwards_func(apps, schema_editor):
    command = load_fixture("users", "initial_data")
    management.call_command(command(apps, schema_editor), verbosity=0)

class Migration(migrations.Migration):

    dependencies = [
        ("users", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(forwards_func, reverse_func),
    ]

@yardley
Copy link

yardley commented Sep 12, 2020

Solved!

def forwards_func(apps, schema_editor):
    command = load_fixture("users", "initial_data")
    command(apps, schema_editor)

@danni
Copy link
Author

danni commented Sep 12, 2020 via email

@yardley
Copy link

yardley commented Sep 12, 2020

I understand now, thank you.

operations = [
  migrations.RunPython(load_fixture("users", "initial_data"), reverse_func),
]

@newtonsbm
Copy link

Using django-cookiecutter, since apps are inside project folder I needed to

operations = [
  migrations.RunPython(load_fixture("project_folder/name_app", "initial_data"), reverse_func),
]

@kolanos
Copy link

kolanos commented Nov 11, 2022

This doesn't appear to handle auto increment sequences. Django's loaddata command does the following:

        if self.loaded_object_count > 0:
            sequence_sql = connection.ops.sequence_reset_sql(no_style(), self.models)
            if sequence_sql:
                with connection.cursor() as cursor:
                    for line in sequence_sql:
                        cursor.execute(line)

Without something like this anyone who uses this is going to run into primary key conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment