Data Creation Scripts

August 20, 2026 · View on GitHub

Overview

The cms.test_data app provides management commands for seeding and cleaning up test data during local development. It uses factory-boy and Faker to generate reproducible CMS content, and Pydantic to validate configuration.

The app is not intended for production environments. It may be used in non-production test environments, for example during load testing.

It is controlled by the CMS_TEST_DATA_ENABLED environment variable and is enabled locally by default in dev and test settings. The environment variable can be used to enable it for load testing in a test environment.

Supported Models

The following models can be created by the test data scripts:

ModelAppNotes
CustomImagecms.imagesWagtail images created in the root collection.
Datasetcms.datasetsEach dataset is given a unique edition and version.
Topiccms.taxonomyTaxonomy topics, created under the root topic.
TopicPagecms.topicsWagtail pages linked to a topic, with optional dataset links and "explore more" blocks.

Prerequisites

Ensure the app is enabled. In local development this is the default; otherwise, set the environment variable:

CMS_TEST_DATA_ENABLED=true

The CMS_TEST_DATA_PREFIX setting (defined in cms/settings/base.py, defaults to Z-RANDOM) controls the prefix string added to all generated records. This prefix is how the delete command identifies test data.

Usage

All commands are available as Makefile targets or can be run directly via manage.py.

Viewing the Default Configuration

To see the default configuration (how many of each model will be created):

make test-data-show-default-config

To view the full JSON schema (useful when writing a custom config file):

make test-data-show-default-config SCHEMA=1

Creating Test Data

make test-data-create

You will be prompted to confirm before data is created.

VariableDefaultDescription
SEED4Integer seed for deterministic output. The same seed and config always produce the same data.
CONFIGBuilt-in defaultsPath to a custom JSON configuration file.
NOINPUTSet to 1 to skip the confirmation prompt.

Examples:

# Use a specific seed
make test-data-create SEED=42

# Use a custom config file
make test-data-create CONFIG=path/to/config.json

# Skip the confirmation prompt (useful in scripts / CI)
make test-data-create NOINPUT=1

Creation order: images → datasets → topics. Topics reference previously created images and datasets, so they are created last.

All records are created inside a single database transaction. If anything fails, all changes are rolled back.

Deleting Test Data

To preview what would be deleted (dry run):

make test-data-delete-dry-run

To delete all previously created test data:

make test-data-delete

To skip the confirmation prompt:

make test-data-delete NOINPUT=1

The delete command scans every model's CharField and TextField columns for values starting with CMS_TEST_DATA_PREFIX. Only records created by create_test_data will be matched. Cascading deletions and field updates (e.g. SET_NULL) are displayed before confirmation.

Configuration

Configuration is validated with Pydantic. The default configuration is:

{
    "images": { "count": 1 },
    "datasets": { "count": 1 },
    "topics": {
        "count": 3,
        "published_probability": 0.5,
        "revisions": 1,
        "datasets": 1,
        "dataset_manual_links": 0,
        "explore_more": 1
    }
}

Configuration Reference

images / datasets

FieldTypeDefaultDescription
countinteger or {"min": N, "max": M}1Number of records to create. A range produces a random count per run (deterministic for a given seed).

topics

FieldTypeDefaultDescription
countinteger or {"min": N, "max": M}3Number of topic pages to create.
published_probabilityfloat (0–1)0.5Probability that each topic page will be published.
revisionsinteger or {"min": N, "max": M}1Number of revisions to create per topic page.
datasetsinteger or {"min": 0, "max": M}1Number of existing datasets to link to each topic page via dataset lookup. Must not exceed datasets.count.
dataset_manual_linksinteger (≥ 0)0Number of manually-entered dataset links (title + URL) to add to each topic page.
explore_moreinteger or {"min": 0, "max": M}1Number of "explore more" blocks per topic page. Even-indexed blocks are internal links; odd-indexed are external links.

Validation Rules

  • count must be a positive integer or a range where min < max.
  • topics.datasets (highest possible value) must not exceed datasets.count (lowest possible value), since each dataset can only be linked once.
  • The sum of topics.datasets and topics.dataset_manual_links must not exceed the maximum items per section (defined by MAX_ITEMS_PER_SECTION in the topics app).

How It Works

Prefixed Records

Every generated record has its title (or equivalent text field) prefixed with the value of CMS_TEST_DATA_PREFIX. This makes test data easy to identify and is the mechanism the delete command uses to find records to remove.

Deterministic Seeding

The --seed option seeds both factory-boy's random generator and a dedicated Faker instance. Given the same seed and configuration, the commands produce identical data. The random state is saved and restored after the command completes, so running the command does not affect other test randomness.

Signal Disconnection

During creation and deletion, certain signal receivers are temporarily disconnected to avoid unnecessary work (e.g. search indexing and post-publish side-effects). They are automatically reconnected when the operation completes, even if an error occurs.

Signals disconnected during creation

The search_publisher_receivers are disconnected:

SignalReceiverSender
page_publishedrun_post_publish_actions_handler
page_unpublishedon_page_unpublished
page_slug_changedon_page_slug_changed
post_page_moveon_page_moved
post_deleteon_page_deletedPage

Signals disconnected during deletion

The index_receivers (for each model being deleted) and the search_publisher_receivers are disconnected:

SignalReceiverSender
post_savepost_save_signal_handlereach collected model
post_saveupdate_reference_index_on_saveeach collected model
page_publishedrun_post_publish_actions_handler
page_unpublishedon_page_unpublished
page_slug_changedon_page_slug_changed
post_page_moveon_page_moved
post_deleteon_page_deletedPage

Tree Repair

The Wagtail topic tree (Topic, a treebeard MP_Node) can become inconsistent after bulk inserts or deletes. The commands call Topic.fix_tree() before creating topics, and after all deletion to ensure the tree remains valid.

This may be able to change after PR 758 merges.

Todos

Currently, this supports several core models, but there are plans to expand this to cover:

  • All page types
  • Bundles
  • Page workflows (draft, review, publish)
  • Snippets
  • Locales: converted aliases to supported languages e.g. welsh. Only english is currently supported.

Other future changes include:

  • Refactoring to avoid need to call fix_tree()
  • Configurability of signal disconnection