The seeders app is an independent Django application responsible for populating the database with sample data for development and testing. It provides a flexible, extensible seeding system with support for multi-tenancy, idempotent operations, and selective model seeding.
Seed the entire database with all sample data:
python manage.py seed_database --clear --create-default
This will:
seeders/wagtail_locale_seeder.py; staff can add more in Wagtail admin)platform_super, platform_staff) with no TenantMembership (Wagtail / platform-only; emails under @system.local)owner, coordinator, manager, tenant_viewer) with is_staff=False and roles on the Default tenant (SPA + tenant JWT; emails under @org.seed.local)After adding or removing locales in Wagtail admin (Settings → Locales), refresh the Next.js locale list and rebuild:
python manage.py sync_frontend_locales
cd frontend && yarn build
Configuration: set environment variables on the container (not flags on the gunicorn command, not values inside the_inventory.settings.*). Examples:
| Platform | Where |
|---|---|
| Render | Web Service → Environment → add variables |
| Docker | docker run -e AUTO_SEED_DATABASE=true … or Compose environment: / env_file: |
When they run: entrypoint.sh reads them once at container start, after migrate and before gunicorn. They are ignored if you only run python manage.py runserver locally (use manage.py seed_database yourself in that case).
Truthiness: 1, true, yes, or on (case-insensitive).
| Variable | Effect |
|---|---|
AUTO_SEED_DATABASE |
When truthy, runs seed_database after migrations |
SEED_TENANT |
Optional slug → --tenant=… (if unset, entrypoint uses --create-default) |
SEED_MODELS |
Optional → --models=… (comma-separated) |
SEED_CLEAR |
When truthy → --clear (wipes seeded inventory tables first; destructive) |
SEED_QUIET |
When truthy → --quiet |
See .env.example (section AUTO-SEED) for the full notes.
seeders/
├── management/
│ └── commands/
│ ├── seed_database.py # Django management command
│ └── sync_frontend_locales.py # Export locales → frontend/src/i18n/locales-config.json
├── tests/ # Comprehensive test suite (72 tests)
│ ├── test_base_seeder.py
│ ├── test_seed_command.py
│ ├── test_seeder_manager.py
│ └── test_seeder_tenant.py
├── apps.py # Django app configuration
├── base.py # BaseSeeder abstract class
├── seeder_manager.py # SeederManager orchestration
├── tenant_seeder.py # TenantSeeder
├── platform_user_seeder.py # PlatformUserSeeder (Wagtail operators, no membership)
├── user_seeder.py # UserSeeder (tenant members, is_staff=False)
├── category_seeder.py # CategorySeeder
├── product_seeder.py # ProductSeeder
├── stock_location_seeder.py # StockLocationSeeder
├── stock_record_seeder.py # StockRecordSeeder
├── stock_movement_seeder.py # StockMovementSeeder
├── low_stock_seeder.py # LowStockSeeder
├── wagtail_locale_seeder.py # Default Wagtail Locale bootstrap
└── README.md # This file
Seeders run in dependency order to respect foreign key relationships:
ensure_default_wagtail_locales() (global, not tenant-scoped) runs first inside SeederManager.seed()is_staff / is_superuser) without tenant membershipsis_staff=False) with TenantMembership and rolesAbstract base class providing common functionality:
transaction.atomic()Orchestrates all seeders in the correct dependency order:
Locale rows, then refreshes Django/Wagtail language settingsEach seeder is responsible for one or more related models and implements idempotent operations.
Seed all models into the Default tenant:
python manage.py seed_database --create-default
Clear existing data and reseed:
python manage.py seed_database --clear --create-default
Seed into a specific tenant:
python manage.py seed_database --tenant=acme-corp
Seed only certain models:
python manage.py seed_database --models=categories,products
Available models: users, categories, products, locations, records, movements
Suppress verbose output:
python manage.py seed_database --quiet --create-default
All seeders are idempotent — they can be safely re-run without creating duplicates:
get_or_create() to retrieve existing tenantget_or_create() by SKUget_or_create() by product + locationAll seeders support multi-tenancy:
execute(tenant=tenant_instance)from seeders import SeederManager
from tenants.models import Tenant
from tenants.context import set_current_tenant, clear_current_tenant
# Get or create tenant
tenant = Tenant.objects.get_or_create(
slug="acme-corp",
defaults={"name": "ACME Corporation", "is_active": True}
)[0]
# Set tenant context
set_current_tenant(tenant)
try:
# Seed data
manager = SeederManager(verbose=True, clear_data=False)
manager.seed(tenant=tenant)
finally:
# Clear tenant context
clear_current_tenant()
To create a custom seeder:
BaseSeederseed() methodself.create_with_tenant() or self.add_root_with_tenant() for model creationSeederManager in the correct dependency orderfrom seeders.base import BaseSeeder
from inventory.models import Product
class CustomSeeder(BaseSeeder):
"""Custom seeder for specific models."""
def seed(self):
"""Seed custom data."""
self.log("Creating custom products...")
# Check if already exists
if Product.objects.filter(sku="CUSTOM-001").exists():
self.log("Custom product already exists, skipping")
return
# Create product
product = self.create_with_tenant(
Product,
sku="CUSTOM-001",
name="Custom Product",
description="A custom product"
)
self.log(f"Created product: {product.name}")
Run all seeder tests:
python manage.py test seeders.tests
Run specific test class:
python manage.py test seeders.tests.test_seeder_tenant.TenantSeederTestCase
Run with verbose output:
python manage.py test seeders.tests --verbosity=2
The seeders app includes 72 comprehensive tests covering:
Platform (no tenant membership — use Wagtail):
platform_super123 — superuser + staff (platform.superoperator@system.local)platform_staff123 — staff only (platform.operator@system.local)Tenant (memberships on seeded tenant — use SPA / tenant JWT):
owner123 — TenantRole.OWNER (default membership; owner@org.seed.local)coordinator123 — TenantRole.COORDINATOR (coordinator@org.seed.local)manager123 — TenantRole.MANAGER (manager@org.seed.local)tenant_viewer123 — TenantRole.VIEWER (viewer@org.seed.local)See TEST_USERS.md for full tables and troubleshooting.
transaction.atomic() for consistencyget_or_create() to avoid duplicate creation--quiet flagUse --create-default flag:
python manage.py seed_database --create-default
Create the tenant first via admin or use --create-default for the Default tenant.
All seeders are idempotent. If duplicates appear, check that:
get_or_create() or existence checksThe seeders app is registered in INSTALLED_APPS in the_inventory/settings/base.py:
INSTALLED_APPS = [
# ...
"seeders",
# ...
]
This enables: