Migration Guide: Legacy to Extensions
This guide helps you migrate from the old monolithic DjangoConfig to the new modular extensions architecture.
Who Should Read This?
If your DjangoConfig has extension-specific fields like payments, leads, support, etc., you should migrate to the new architecture.
Why Migrate?
Benefits of Extensions Architecture
- Simpler Configuration - Your
DjangoConfigbecomes 70% smaller - Better Separation - Extension config lives with the extension code
- Easier Maintenance - Update extensions independently
- Type Safety - Full Pydantic validation for all settings
- Reusability - Share extensions across projects
- Auto-Discovery - No manual
INSTALLED_APPSmanagement
Before vs After
Before (Legacy - Monolithic):
# api/config.py - 500+ lines ❌
class MyProjectConfig(DjangoConfig):
# Payments
payments: PaymentsConfig = PaymentsConfig(
crypto_api_key="...",
crypto_webhook_secret="...",
enabled=True
)
# Leads
leads: LeadsConfig = LeadsConfig(
telegram_enabled=True,
email_enabled=True
)
# Support
support: SupportConfig = SupportConfig(
ticket_auto_assign=True,
sla_hours=24
)
# Manual app registration
project_apps: list[str] = [
"extensions.apps.payments",
"extensions.apps.leads",
"extensions.apps.support",
]After (Modern - Modular):
# api/config.py - 50 lines ✅
class MyProjectConfig(DjangoConfig):
project_name: str = "My SaaS"
databases: Dict[str, DatabaseConfig] = {...}
# Your custom apps only
project_apps: list[str] = ["core", "apps.profiles"]
# Extensions auto-discovered! ✨# extensions/apps/payments/__cfg__.py - Self-contained ✅
settings = PaymentsSettings(
crypto_api_key=env.payments.api_key,
crypto_webhook_secret=env.payments.secret,
)Prerequisites
Before starting migration:
- Backup your project (git commit or full backup)
- Django-CFG 2.0+ installed
- Extensions directory exists:
extensions/apps/ - Test environment ready for validation
Migration Steps
Step 1: Identify Extensions in Your Config
Find all extension-specific fields in your DjangoConfig:
# api/config.py
class MyProjectConfig(DjangoConfig):
# ❌ Extension fields (need to migrate)
payments: Optional[PaymentsConfig] = PaymentsConfig(...)
leads: Optional[LeadsConfig] = LeadsConfig(...)
support: Optional[SupportConfig] = SupportConfig(...)
# ✅ Core fields (keep in DjangoConfig)
project_name: str = "My Project"
databases: Dict[str, DatabaseConfig] = {...}
email: Optional[EmailConfig] = EmailConfig(...)Common extension fields to migrate:
payments→ Payment processingleads→ Lead managementsupport→ Support ticketsnewsletter→ Email campaignsknowbase→ Knowledge basebackup→ Database backups
Step 2: Create Extension Config Files
For each extension, create a __cfg__.py file:
Example: Migrate Payments Extension
Before (in api/config.py):
class MyProjectConfig(DjangoConfig):
payments: PaymentsConfig = PaymentsConfig(
crypto_api_key="your-api-key",
crypto_webhook_secret="your-secret",
enabled=True
)After (create extensions/apps/payments/cfg.py):
# extensions/apps/payments/__cfg__.py
from django_cfg.extensions.configs.apps import BaseExtensionSettings
class PaymentsSettings(BaseExtensionSettings):
# Move settings here
crypto_api_key: str = env.payments_api_keys.crypto_api_key
crypto_webhook_secret: str = env.payments_api_keys.crypto_webhook_secret
# This line enables the extension!
settings = PaymentsSettings()Step 3: Move Environment Variables
Update your .env file to match new structure:
Before:
# Old flat structure
CRYPTO_API_KEY=your-key
CRYPTO_WEBHOOK_SECRET=your-secretAfter:
# New nested structure (double underscores)
PAYMENTS_API_KEYS__CRYPTO_API_KEY=your-key
PAYMENTS_API_KEYS__CRYPTO_WEBHOOK_SECRET=your-secretNested ENV Variables
Use __ (double underscore) for nesting:
PAYMENTS_API_KEYS__CRYPTO_API_KEY→payments_api_keys.crypto_api_key
Step 4: Remove from DjangoConfig
Remove the extension field from your main config:
# api/config.py
class MyProjectConfig(DjangoConfig):
# ❌ REMOVE THIS
# payments: PaymentsConfig = PaymentsConfig(...)
# ✅ Keep core config only
project_name: str = "My SaaS"
databases: Dict[str, DatabaseConfig] = {...}Step 5: Update INSTALLED_APPS
Remove manual extension registration:
Before:
class MyProjectConfig(DjangoConfig):
project_apps: list[str] = [
"extensions.apps.payments", # ❌ Remove
"extensions.apps.leads", # ❌ Remove
"core", # ✅ Keep your apps
"apps.profiles", # ✅ Keep your apps
]After:
class MyProjectConfig(DjangoConfig):
project_apps: list[str] = [
"core", # ✅ Your apps only
"apps.profiles", # ✅ Your apps only
]
# Extensions auto-added! ✨Step 6: Test Extension Discovery
Verify the extension was discovered:
# Start Django
python manage.py runserver
# Check in shell
python manage.py shell
>>> from extensions.apps.payments.__cfg__ import settings
>>> settings.crypto_api_key
'your-api-key'
>>> settings.admin_enabled
TrueStep 7: Run Migrations
Extensions use the cfg_ prefix for app labels:
# Run migrations for extension
python manage.py migrate cfg_payments
# Or migrate all
python manage.py migrateStep 8: Verify URLs
Extensions auto-register URLs at /cfg/{prefix}/:
# Test extension endpoints
curl http://localhost:8000/cfg/payments/api/
curl http://localhost:8000/cfg/leads/api/
curl http://localhost:8000/cfg/support/api/Complete Migration Example
Before: Monolithic Config
# api/config.py (OLD - 300 lines)
from django_cfg import DjangoConfig
from django_cfg import DatabaseConfig
class MyProjectConfig(DjangoConfig):
project_name: str = "My SaaS Platform"
secret_key: str = env.secret_key
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(url=env.database.url)
}
# Extension configs (to be migrated)
payments: PaymentsConfig = PaymentsConfig(
crypto_api_key=env.crypto_api_key,
crypto_webhook_secret=env.crypto_webhook_secret,
enabled=True
)
leads: LeadsConfig = LeadsConfig(
telegram_enabled=True,
email_enabled=True,
telegram_bot_token=env.telegram_bot_token,
notification_email=env.leads_email,
)
support: SupportConfig = SupportConfig(
ticket_auto_assign=True,
sla_hours=24,
sla_tracking_enabled=True,
)
# Manual app registration
project_apps: list[str] = [
"extensions.apps.payments",
"extensions.apps.leads",
"extensions.apps.support",
"core",
"apps.profiles",
]After: Modular Config
# api/config.py (NEW - 50 lines)
from django_cfg import DjangoConfig
from django_cfg import DatabaseConfig
class MyProjectConfig(DjangoConfig):
project_name: str = "My SaaS Platform"
secret_key: str = env.secret_key
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(url=env.database.url)
}
# Your custom apps only
project_apps: list[str] = [
"core",
"apps.profiles",
]
# Extensions auto-discovered! ✨# extensions/apps/payments/__cfg__.py (NEW)
from django_cfg.extensions.configs.apps import BaseExtensionSettings
class PaymentsSettings(BaseExtensionSettings):
crypto_api_key: str = env.payments_api_keys.crypto_api_key
crypto_webhook_secret: str = env.payments_api_keys.crypto_webhook_secret
settings = PaymentsSettings()# extensions/apps/leads/__cfg__.py (NEW)
from django_cfg.extensions.configs.apps import BaseExtensionSettings
class LeadsSettings(BaseExtensionSettings):
telegram_enabled: bool = True
email_enabled: bool = True
telegram_bot_token: str = env.telegram.bot_token
notification_email: str = env.leads.notification_email
settings = LeadsSettings()# extensions/apps/support/__cfg__.py (NEW)
from django_cfg.extensions.configs.apps import BaseExtensionSettings
class SupportSettings(BaseExtensionSettings):
ticket_auto_assign: bool = True
sla_hours: int = 24
sla_tracking_enabled: bool = True
settings = SupportSettings()Migration Checklist
Use this checklist for each extension you migrate:
Per Extension:
- Create
extensions/apps/{name}/__cfg__.py - Copy settings from old config to new
__cfg__.py - Add
settings = {Extension}Settings()line - Update
.envwith new nested variable names - Remove extension field from
DjangoConfig - Remove from
project_appslist (if present) - Test extension discovery:
python manage.py shell - Run migrations:
python manage.py migrate cfg_{name} - Test URLs:
curl http://localhost:8000/cfg/{name}/api/ - Verify admin navigation appears
- Test extension functionality
Overall:
- All extensions migrated
-
DjangoConfigonly has core fields -
project_appsonly has your custom apps - All
.envvariables updated - All migrations run successfully
- All tests passing
- No import errors or warnings
Troubleshooting Migration
Extension Not Discovered
Problem: Extension doesn’t appear after creating __cfg__.py
Solutions:
-
Check
settingsvariable:# Must have this line! settings = PaymentsSettings() -
Restart Django:
# Extensions load at startup python manage.py runserver -
Check for Python errors:
python manage.py shell >>> from extensions.apps.payments.__cfg__ import settings # Fix any import errors
Environment Variables Not Loading
Problem: env.payments.api_key raises error
Solutions:
-
Check nesting syntax:
# ✅ Correct PAYMENTS_API_KEYS__CRYPTO_API_KEY=key # ❌ Wrong PAYMENTS.API_KEYS.CRYPTO_API_KEY=key -
Verify .env location:
# Must be in project root or environment/ ls -la .env -
Restart Django after .env changes
Migration Conflicts
Problem: Database migrations fail
Solutions:
-
Use new app label:
# Extensions use cfg_ prefix python manage.py migrate cfg_payments -
Check for model conflicts:
python manage.py showmigrations # Look for duplicate migrations -
Fake initial if needed:
# If tables already exist python manage.py migrate cfg_payments --fake-initial
Settings Not Applied
Problem: Extension is enabled but settings don’t work
Solutions:
-
Verify settings loaded:
from extensions.apps.payments.__cfg__ import settings print(settings.crypto_api_key) # Should show key -
Check base class:
# Must inherit from Base{Extension}Settings class PaymentsSettings(BaseExtensionSettings): # ✅ Correct -
Clear Python cache:
find . -type d -name __pycache__ -exec rm -r {} +
Rollback Procedure
If migration causes issues, you can rollback:
Quick Rollback
-
Comment out
settingsline:# extensions/apps/payments/__cfg__.py # settings = PaymentsSettings() # Disabled -
Restore old config temporarily:
# api/config.py payments: PaymentsConfig = PaymentsConfig(...) # Temporary -
Restart Django
Full Rollback
# Restore from git
git checkout HEAD -- api/config.py
git checkout HEAD -- extensions/apps/*/\_\_cfg\_\_.py
# Restore .env
git checkout HEAD -- .env
# Restart
python manage.py runserverBest Practices
1. Migrate One Extension at a Time
✅ Good approach:
- Migrate
payments→ test → commit - Migrate
leads→ test → commit - Migrate
support→ test → commit
❌ Risky approach:
- Migrate all extensions at once
- Hope everything works
2. Test After Each Migration
# After migrating each extension
python manage.py test
python manage.py runserver
# Manual testing in browser
git commit -m "Migrated {extension} to new architecture"3. Keep Old Config Commented
# api/config.py
class MyProjectConfig(DjangoConfig):
# Migrated to extensions/apps/payments/__cfg__.py
# payments: PaymentsConfig = PaymentsConfig(...)4. Document Custom Settings
# extensions/apps/support/__cfg__.py
class SupportSettings(BaseExtensionSettings):
# Custom: We use 48-hour SLA (not default 24)
sla_hours: int = 48
# Custom: Auto-assign disabled per team request
ticket_auto_assign: bool = FalseFAQ
Q: Do I have to migrate all extensions at once?
A: No! Migrate one at a time. You can have some extensions in old config and some in new __cfg__.py during transition.
Q: Will this break my production site?
A: If done correctly, no. Test thoroughly in development/staging first. The migration is backwards compatible during transition.
Q: Can I keep some config in DjangoConfig?
A: Yes! Core infrastructure config (email, database, cache) stays in DjangoConfig. Only extension-specific config moves to __cfg__.py.
Q: What about custom extensions I built?
A: Same process - create __cfg__.py with your settings class inheriting from BaseExtensionSettings.
Q: How long does migration take?
A: Typically:
- 1 extension: 15-30 minutes
- 3-5 extensions: 1-2 hours
- 10+ extensions: Half day
Next Steps
Migration Support Need help with migration? Join our Discord community or check the troubleshooting section above!