Skip to content

Developer Experience

Milestone 4 delivers essential tools for building and testing with django-admin-deux. These tools improve development velocity, debugging, and code quality throughout the development lifecycle.

Overview

Developer Experience (DX) features make working with django-admin-deux faster and more enjoyable:

  • Introspection: Understand your admin configuration at a glance
  • Testing: Comprehensive CRUD testing with minimal boilerplate
  • Configuration: Zero-config plugin setup with automatic dependency resolution
  • Debugging: Clear visibility into how your admin is composed

Tools & Features

1. Admin Introspection (djadmin_inspect)

Management command for understanding your admin configuration

# Inspect all registered admins
python manage.py djadmin_inspect

# Inspect specific model
python manage.py djadmin_inspect --model myapp.MyModel

# JSON output for automation
python manage.py djadmin_inspect --format json

What it shows: - All registered actions (general, bulk, record) - View composition (base class + mixins) - Feature availability (which plugins provide what) - Form configuration - Template resolution order

When to use: - Debugging why a feature isn't working - Understanding plugin contributions - Finding URL names - Verifying form configuration

→ Full Command Reference

2. CRUD Testing (BaseCRUDTestCase)

Base test class for automatic CRUD operation testing

from djadmin.testing import BaseCRUDTestCase
from myapp.factories import MyModelFactory
from myapp.models import MyModel


class TestMyModelAdmin(BaseCRUDTestCase):
    model = MyModel
    model_factory_class = MyModelFactory

    # That's it! Automatically tests all CRUD operations

What it tests: - List view (GET) - Create view (GET + POST) - Update view (GET + POST) - Delete view (POST)

Key features: - Action introspection (works with any admin configuration) - Plugin-based test methods (extensible by plugins) - Factory integration (FactoryBoy) - 80%+ boilerplate reduction

→ Full Testing Guide

3. Plugin-Driven Apps (djadmin_apps())

Zero-configuration INSTALLED_APPS setup

# settings.py
from djadmin import djadmin_apps

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    # ... your apps
] + djadmin_apps()  # Automatically adds djadmin + all plugins

What it does: - Discovers third-party plugins via Python entrypoints - Registers built-in plugins (core, theme) - Resolves ordering using First, Before, After, Position modifiers - Removes duplicates automatically

Benefits: - Single function call in settings - Plugins declare dependencies in code (not docs) - Automatic ordering resolution - No configuration required

→ Full Setup Guide

Quick Start

Installation

# Install django-admin-deux with dev dependencies
pip install -e ".[dev]"

# Or with uv
uv pip install -e ".[dev]"

Setup

# Run setup script (installs dependencies + pre-commit hooks)
./setup_dev.sh

# Or manually:
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pre-commit install

First Steps

  1. Inspect your admin configuration:

    python manage.py djadmin_inspect
    

  2. Set up INSTALLED_APPS:

    # settings.py
    from djadmin import djadmin_apps
    INSTALLED_APPS += djadmin_apps()
    

  3. Write tests with BaseCRUDTestCase:

    from djadmin.testing import BaseCRUDTestCase
    
    class TestMyAdmin(BaseCRUDTestCase):
        model = MyModel
        model_factory_class = MyModelFactory
    

Common Workflows

Debugging Feature Issues

When a feature (like search or filters) isn't working:

# 1. Check what features are requested
python manage.py djadmin_inspect --model myapp.MyModel

# 2. Look at REQUESTED FEATURES section
# - Feature not listed? → Configure it in ModelAdmin
# - Shows "NOT PROVIDED"? → Install the plugin

# 3. Check plugin contributions
# Look at "Mixins" section to see what plugins are active

Adding a New Plugin

When installing a third-party plugin:

# 1. Install the plugin
pip install djadmin-my-plugin

# 2. That's it! djadmin_apps() discovers it automatically
# No INSTALLED_APPS changes needed

# 3. Verify plugin is active
python manage.py djadmin_inspect --format json | grep plugin_name

Writing Comprehensive Tests

When testing a ModelAdmin:

# 1. Create basic test class
class TestMyAdmin(BaseCRUDTestCase):
    model = MyModel
    model_factory_class = MyModelFactory

# 2. Customize for specific tests
    to_update_fields = {'name': 'Updated'}

    def assert_create_successful(self, response, data):
        # Custom assertions after create
        assert MyModel.objects.filter(name=data['name']).exists()

# 3. Run tests
pytest tests/test_my_admin.py -v

Architecture

Plugin-Based Testing

Tests methods come from plugins via the djadmin_get_test_methods() hook:

# Core plugin provides default test methods
@hookimpl
def djadmin_get_test_methods():
    return {
        ListActionMixin: {'_test_list': test_list_method},
        CreateViewActionMixin: {'_test_create_get': test_create_get_method},
        # ... more test methods
    }

# Other plugins can override with Replace()
@hookimpl
def djadmin_get_test_methods():
    from djadmin.plugins.modifiers import Replace
    return {
        CreateViewActionMixin: {
            Replace('_test_create_post', custom_create_post_test)
        }
    }

Benefits: - Plugins control how their features are tested - Core provides sensible defaults - No if/else logic in test infrastructure

Plugin Discovery

Third-party plugins declare themselves via entrypoints:

# Third-party plugin's pyproject.toml
[project.entry-points.djadmin]
my_plugin = "my_plugin.djadmin_hooks"

Built-in plugins (core, theme) are explicitly registered in djadmin_apps().

Discovery process: 1. djadmin_apps() is called from settings.py (before Django starts) 2. Built-in plugins registered explicitly 3. Third-party plugins discovered via pm.load_setuptools_entrypoints('djadmin') 4. All plugins call djadmin_get_required_apps() hook 5. Apps ordered using First, Before, After, Position modifiers

Ordering Resolution

Apps are categorized into buckets for ordering:

first = []      # Absolute first (themes)
before = []     # Before core apps
default = []    # Normal apps (includes djadmin core)
after = []      # After core apps
position = []   # Relative positioning

# Final order
result = first + before + default + after
# Then insert Position apps relative to others

Testing Strategy

Unit vs Integration Tests

Unit tests (test tool itself): - tests/test_management_commands.py - djadmin_inspect tests - tests/test_crud_testing.py - BaseCRUDTestCase tests - tests/test_plugin_apps.py - djadmin_apps() tests

Integration tests (using webshop): - examples/webshop/tests/test_admin_site.py - Real admin testing - examples/webshop/tests/test_model_admin.py - Real configuration

Test Coverage

Current coverage: - djadmin_inspect.py: 81% - djadmin/testing.py: 98% - djadmin/apps.py: 78% - djadmin/plugins/modifiers.py: 100%

Target: 90%+ across all DX tools

Best Practices

Using djadmin_inspect

# DO: Use for debugging
python manage.py djadmin_inspect --model myapp.MyModel

# DO: Export for documentation
python manage.py djadmin_inspect > docs/admin_config.txt

# DO: Use JSON for automation
python manage.py djadmin_inspect --format json | jq '.features'

# DON'T: Use in production code (development/debugging only)

Using BaseCRUDTestCase

# DO: Only specify what you're testing
class TestProductAdmin(BaseCRUDTestCase):
    model = Product
    model_factory_class = ProductFactory
    to_update_fields = {'name': 'Updated'}  # Only testing name

# DO: Use customization hooks
    def assert_create_successful(self, response, data):
        assert Product.objects.filter(name=data['name']).exists()

# DON'T: Override _test_* methods directly
# Instead, use plugin hooks to customize test behavior

Using djadmin_apps()

# DO: Call at end of INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    # ... your apps
] + djadmin_apps()

# DO: Let plugins declare dependencies
# (In your plugin's djadmin_hooks.py)
@hookimpl
def djadmin_get_required_apps():
    return [Before('my_plugin'), 'dependency']

# DON'T: Manually add plugin apps to INSTALLED_APPS
# Let djadmin_apps() handle it

Performance

djadmin_inspect

  • Typical admin: <1 second
  • Large admin (20+ actions): ~2 seconds
  • JSON output: Same performance, but easier to parse

Optimization: Results are generated fresh each run (no caching). This is intentional for accuracy during development.

BaseCRUDTestCase

  • Per-admin overhead: ~100ms
  • Per-test overhead: ~50ms
  • With FormCollection: ~100ms per POST test (JSON serialization)

Optimization: Tests run in parallel with pytest-xdist. Use pytest -n auto for best performance.

djadmin_apps()

  • Discovery: <50ms (loads entrypoints)
  • Ordering: <50ms (topological sort)
  • Total: <100ms (runs once at Django startup)

Optimization: Results are not cached - function runs once during settings import, which is fine.

Troubleshooting

djadmin_inspect Issues

"No admins found" - Check ModelAdmin is registered: site.register(Model, Admin) - Verify model path: app_label.ModelName - Ensure djadmin.py is imported

AttributeError/KeyError - This is a bug - please report with traceback - Workaround: Use --format json to get partial results

BaseCRUDTestCase Issues

422 status code on POST - Expected with FormCollection (hierarchical JSON required) - Plugin should override _test_create_post with custom test method - See CRUD Testing Guide

DynamicURLConf pattern required - Use when registering/unregistering same model multiple times - See CRUD Testing Guide

djadmin_apps() Issues

ImproperlyConfigured: Could not position app - Circular dependency or impossible constraint - Check Position() modifiers in plugin hooks - Use First, Before, After instead when possible

Plugin not discovered - Verify entrypoint in pyproject.toml: [project.entry-points.djadmin] - Check plugin module has djadmin_hooks.py - Ensure plugin is installed: pip list | grep djadmin

Extending DX Tools

Custom Test Methods

To provide custom test methods for your plugin:

# your_plugin/djadmin_hooks.py
from djadmin.plugins import hookimpl
from djadmin.plugins.modifiers import Replace

@hookimpl
def djadmin_get_test_methods():
    def custom_create_post(test_case, action):
        # Custom test logic for your plugin's forms
        pass

    return {
        CreateViewActionMixin: {
            Replace('_test_create_post', custom_create_post)
        }
    }

Custom Ordering Modifiers

For complex app ordering:

# your_plugin/djadmin_hooks.py
from djadmin.plugins import hookimpl
from djadmin.plugins.modifiers import Position, Before

@hookimpl
def djadmin_get_required_apps():
    return [
        Position('your_app', after='some_app'),  # Relative positioning
        Before('theme_app'),  # Before bucket
        'dependency_app',  # No constraint
    ]

Custom Introspection Output

Add custom sections to djadmin_inspect output:

# Not yet supported - future enhancement
# Would use djadmin_add_introspection_data() hook

Future Enhancements

Planned for Milestone 6 (Quality & Polish):

  • Scaffolding CLI: Generate ModelAdmin boilerplate
  • View Caching: Cache generated view classes
  • Profiling Tools: Performance profiling utilities
  • Enhanced djadmin_inspect: Better tree view, more filtering options
  • Test Utilities: More helper functions for common test patterns

See Milestone 6 in PRD for details.

Contributing

When contributing to DX tools:

  1. Add tests for all new functionality (target 90%+ coverage)
  2. Update documentation with examples
  3. Consider plugin extensibility - can plugins customize this?
  4. Performance matters - DX tools run frequently during development

See Also