Populating a Django Project With Test Data

As a Django developer, I usually want to remove all of my database's tables in order to start fresh and see how my application behaves when running for the first time. This is even more tempting when using SQLite, where it's just a matter of removing a single file. But if you do that regularly as well, you will find yourself creating test data (an admin user, a catalogue of products, a blog post) over and over. As a workaround, I have found that the most useful thing is to create a testdata application that populates my database when it runs for the first time (or, more precisely, when migrations run for the first time.)

To do so, start by creating a new application by running the following command in your project's directory (where manage.py is):

python manage.py startapp testdata

This will create a testdata directory with the following structure:

testdata/
    migrations/
    __init__.py
    admin.py
    apps.py
    models.py
    tests.py
    views.py

We are only going to use this application's migrations, so you can safely delete admin.py, models.py, tests.py and views.py. Do not remove apps.py!

Before anything else, we need to install the application in our project. Go to your settings.py and modify INSTALLED_APPS:

INSTALLED_APPS = [
    # ...remaining apps...
    "testdata.apps.TestdataConfig",
]

Now that the application is installed, we can create a new data migration by doing:

python manage.py makemigrations testdata --empty

By default, this will create a migration file called 0001_initial.py inside testdata/migrations/.

You might use a data migration to populate your database as you want, but make sure it complies with what Django expects of a migration. Here is an example data migration that will create an admin user and two regular users on startup that you can use as a template for your own purposes:

from django.db import migrations
def create_test_data(apps, schema_editor):
    User = apps.get_model("auth", "User")
    # Create a superuser.
    if not User.objects.filter(username="admin").exists():
        User.objects.create_superuser(
            username="admin", email="admin@localhost", password="admin"
        )
    # Create two regular users.
    if not User.objects.filter(username="user1").exists():
        User.objects.create_user(
            username="user1", email="user1@localhost", password="user1"
        )
    if not User.objects.filter(username="user2").exists():
        User.objects.create_user(
            username="user2", email="user2@localhost", password="user2"
        )
class Migration(migrations.Migration):
    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]
    operations = [
        migrations.RunPython(create_test_data),
    ]

Also, ensure your migration has the right dependencies. Dependencies in migrations determine the order in which Django runs them. In the example, since we are loading the User model from the auth application (that's the label for django.contrib.auth), we need to specify the last migration of auth (which in Django 5.1 is django/contrib/auth/migrations/0012_alter_user_first_name_max_length.py) as a dependency of our migration to make sure the User model is ready when we want to use it. In other words, we want to make sure auth migrations run before our own migrations.

If your create_test_data() function will make use of other models from applications installed in your project, make sure to include those applications' latest migrations in your dependencies list.

Once the migration is ready, run the following command to execute it:

python manage.py migrate

If you want to start fresh, just delete your database, run migrate again and your test data will be there.

As a final note, you might want to ensure your testdata application does not run in production! How to achieve that depends on how you have configured your settings for different environments. In its simplest case, you might just do in your settings.py:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

if DEBUG:
    INSTALLED_APPS.append("testdata.apps.TestdataConfig")

And remember to use DEBUG = False whenever running in production.