Skip to main content

Django Admin Module

Django-CFG includes a revolutionary Pydantic-based admin system that transforms how you build Django admin interfaces. Say goodbye to verbose admin classes and hello to clean, declarative, type-safe configurations.

Overview

The Django Admin module provides:

  • Declarative Configuration - Define admin interfaces using Pydantic models
  • Type Safety - Full Pydantic 2.x validation and IDE autocomplete
  • Specialized Field Types - Pre-built field configs for common patterns (badges, currency, dates, users)
  • Automatic Display Methods - Auto-generated display methods with widgets
  • HTML Builder Utilities - self.html methods for custom displays
  • Computed Field Decorators - @computed_field for type-safe custom logic
  • Modern UI - Seamless Unfold integration with Material Design icons
  • Query Optimization - Built-in select_related/prefetch_related support
  • Zero Boilerplate - Minimal code, maximum functionality

Philosophy

Before: Traditional Django Admin

from django.contrib import admin

@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
list_display = ['id', 'user', 'amount', 'status', 'created_at']
list_filter = ['status', 'created_at']
search_fields = ['user__username', 'user__email']

# Lots of manual configuration...

After: Django-CFG Declarative Admin

from django.contrib import admin
from django_cfg.modules.django_admin import (
AdminConfig, BadgeField, CurrencyField, DateTimeField, UserField,
)
from django_cfg.modules.django_admin.base import PydanticAdmin

# ✅ Clean, declarative configuration
payment_config = AdminConfig(
model=Payment,
select_related=["user"], # Auto-applied to all queries

list_display=["internal_payment_id", "user", "amount_usd", "status", "created_at"],

# Auto-generates display methods with widgets
display_fields=[
BadgeField(
name="internal_payment_id",
variant="info",
icon=Icons.RECEIPT
),
UserField(name="user", header=True), # User with avatar
CurrencyField(name="amount_usd", currency="USD", precision=2),
BadgeField(
name="status",
label_map={
"pending": "warning",
"completed": "success",
"failed": "danger",
}
),
DateTimeField(name="created_at", ordering="created_at"),
],

list_filter=["status", "created_at"],
search_fields=["internal_payment_id", "user__username"],
)

@admin.register(Payment)
class PaymentAdmin(PydanticAdmin):
"""Type-safe, auto-configured admin with zero boilerplate."""
config = payment_config

Key Features

1. Type-Safe Configuration

All configuration is validated at runtime using Pydantic 2.x:

config = AdminConfig(
model=MyModel,
list_display=["name", "status"],
display_fields=[
BadgeField(
name="status",
variant="primary", # ✅ IDE autocomplete + validation
icon=Icons.CHECK_CIRCLE # ✅ 2234+ icons with autocomplete
)
]
)

2. Specialized Field Types

Pre-built field configurations for common patterns - these automatically generate display methods:

display_fields=[
# User with avatar
UserField(name="user", header=True),

# Formatted currency
CurrencyField(name="price", currency="USD", precision=2),

# Status badge with conditional colors
BadgeField(
name="status",
label_map={"active": "success", "inactive": "secondary"}
),

# Datetime with relative time
DateTimeField(name="created_at", ordering="created_at"),
]

3. HTML Builder Utilities

For custom display methods, use self.html utilities:

@admin.register(Payment)
class PaymentAdmin(PydanticAdmin):
config = payment_config

def payment_details_display(self, obj):
"""Custom readonly field using self.html."""
details = []

# Badge with icon
details.append(self.html.badge(
f"Confirmations: {obj.confirmations_count}",
variant="info",
icon=Icons.CHECK_CIRCLE
))

# Inline items with separator
details.append(self.html.inline([
self.html.span("Amount:", "font-semibold"),
self.html.span(f"${obj.amount:.2f}", "")
], separator=" "))

# Icon with text
details.append(self.html.icon_text(Icons.RECEIPT, obj.internal_payment_id))

return "<br>".join(details)

payment_details_display.short_description = "Payment Details"

Available self.html Methods:

self.html.badge(text, variant="primary", icon=None)
# Colored badge with optional Material icon

self.html.span(text, css_class="")
# Wrapped text with CSS classes

self.html.inline(items, separator=" | ")
# Join items horizontally

self.html.icon(icon_name, size="xs")
# Material icon only

self.html.icon_text(icon_or_text, text, icon_size="xs", color=None)
# Icon + text combination with optional color

self.html.colored_text(text, color=None)
# Colored text without icon

self.html.link(url, text, css_class="", target="")
# Clickable link

self.html.empty(text="—")
# Empty placeholder

4. Computed Field Decorator

For custom display logic in list views, use @computed_field:

from django_cfg.modules.django_admin import computed_field, Icons

@admin.register(User)
class UserAdmin(PydanticAdmin):
config = user_config

@computed_field("Status", ordering="is_active")
def status_display(self, obj):
"""Enhanced status with icons."""
if obj.is_superuser:
return self.html.badge("Superuser", variant="danger", icon=Icons.ADMIN_PANEL_SETTINGS)
elif obj.is_staff:
return self.html.badge("Staff", variant="warning", icon=Icons.SETTINGS)
elif obj.is_active:
return self.html.badge("Active", variant="success", icon=Icons.CHECK_CIRCLE)
else:
return self.html.badge("Inactive", variant="secondary", icon=Icons.CANCEL)

@computed_field("Full Name")
def full_name(self, obj):
"""Full name with badge."""
name = obj.get_full_name()
if not name:
return self.html.badge("No name", variant="secondary", icon=Icons.PERSON)
return self.html.badge(name, variant="primary", icon=Icons.PERSON)

5. Annotated Fields

For computed values from database aggregations:

from django.db.models import Count

config = AdminConfig(
model=User,
# Define annotations
annotations={
'transaction_count': Count('transactions'),
},
)

@admin.register(User)
class UserAdmin(PydanticAdmin):
config = config

@annotated_field("Transactions", annotation_name="transaction_count")
def transaction_display(self, obj):
"""Display transaction count from annotation."""
count = getattr(obj, 'transaction_count', 0)
if count == 0:
return self.html.empty()
return self.html.badge(f"{count} transactions", variant="info", icon=Icons.RECEIPT)

6. Material Design Icons

2234+ Material icons with full IDE autocomplete:

from django_cfg.modules.django_admin import Icons

Icons.CHECK_CIRCLE # ✓
Icons.CANCEL # ✗
Icons.RECEIPT # Receipt
Icons.EMAIL # Email
Icons.PERSON # Person
Icons.ADMIN_PANEL_SETTINGS # Admin
Icons.SHOPPING_CART # Cart
Icons.CURRENCY_BITCOIN # Bitcoin
Icons.BUSINESS # Business
# ... 2234+ more icons

7. Query Optimization

Built-in database optimization - applied automatically:

config = AdminConfig(
model=Order,

# Auto-applied to all queries
select_related=["user", "product"],
prefetch_related=["items"],

# Aggregate annotations
annotations={
'total_items': Count('items'),
'total_amount': Sum('items__price'),
},
)

Architecture

┌─────────────────────────────────────────┐
│ PydanticAdmin │
│ (Base class with config processing) │
└────────────────┬────────────────────────┘

├──> AdminConfig (Pydantic model)
│ ├── list_display
│ ├── display_fields []
│ ├── fieldsets []
│ ├── actions []
│ └── optimizations

├──> Field Configs
│ ├── BadgeField → auto-generates display method
│ ├── CurrencyField → auto-generates display method
│ ├── DateTimeField → auto-generates display method
│ └── UserField → auto-generates display method

├──> Custom Display Methods
│ ├── @computed_field → for list display
│ ├── @annotated_field → for aggregations
│ └── self.html → for readonly fields

└──> Widget Registry
├── badge → render_badge()
├── currency → render_currency()
└── datetime → render_datetime()

Integration with Unfold

The admin module is designed to work seamlessly with Django Unfold:

from django_cfg.modules.django_admin.base import PydanticAdmin

# PydanticAdmin inherits from:
# UnfoldImportExportModelAdmin
# └─ ImportExportModelAdmin # Import/Export functionality
# └─ UnfoldModelAdmin # Modern Unfold UI
# └─ Django ModelAdmin
Always Unfold-Ready

Every admin class using PydanticAdmin automatically gets:

  • ✅ Modern Unfold UI
  • ✅ Material Design icons
  • ✅ Import/Export support (optional)
  • ✅ Responsive layout

Benefits

For Developers

  1. Less Code - 60-80% reduction in admin boilerplate
  2. Type Safety - Catch errors at configuration time, not runtime
  3. Consistency - Standardized patterns across all admins
  4. Maintainability - Declarative configs are easier to read and modify
  5. IDE Support - Full autocomplete for all config options
  6. Rich Utilities - self.html builder and Display utilities for complex formatting

For Teams

  1. Faster Onboarding - New developers understand declarative configs faster
  2. Code Reviews - Less code to review, more focus on logic
  3. Standards - Built-in best practices and patterns
  4. Documentation - Self-documenting configuration

For Projects

  1. Performance - Built-in query optimization
  2. UI/UX - Consistent, modern interface
  3. Extensibility - Easy to add custom fields and actions
  4. Future-Proof - Type-safe configurations evolve with your schema

Complete Example

from django.contrib import admin
from django_cfg.modules.django_admin import (
AdminConfig, BadgeField, CurrencyField, DateTimeField,
FieldsetConfig, Icons, UserField, computed_field,
)
from django_cfg.modules.django_admin.base import PydanticAdmin

# Declarative config
payment_config = AdminConfig(
model=Payment,

# Performance
select_related=["user", "currency"],

# List display
list_display=["internal_payment_id", "user", "amount_usd", "status", "created_at"],

# Auto-generated display methods
display_fields=[
BadgeField(name="internal_payment_id", variant="info", icon=Icons.RECEIPT),
UserField(name="user", header=True),
CurrencyField(name="amount_usd", currency="USD", precision=2),
BadgeField(
name="status",
label_map={
"pending": "warning",
"completed": "success",
"failed": "danger",
}
),
DateTimeField(name="created_at", ordering="created_at"),
],

# Fieldsets
fieldsets=[
FieldsetConfig(title="Basic", fields=["id", "internal_payment_id", "user"]),
FieldsetConfig(title="Payment", fields=["amount_usd", "currency", "status"]),
FieldsetConfig(title="Details", fields=["payment_details_display"], collapsed=True),
],

readonly_fields=["payment_details_display"],
list_filter=["status", "created_at"],
search_fields=["internal_payment_id", "user__username"],
)

@admin.register(Payment)
class PaymentAdmin(PydanticAdmin):
config = payment_config

# Custom readonly field using self.html
def payment_details_display(self, obj):
"""Detailed payment info using self.html utilities."""
details = []

details.append(self.html.inline([
self.html.span("Internal ID:", "font-semibold"),
self.html.span(obj.internal_payment_id, "")
], separator=" "))

if obj.transaction_hash:
details.append(self.html.inline([
self.html.span("Transaction:", "font-semibold"),
self.html.link(obj.get_explorer_link(), obj.transaction_hash[:16] + "...", target="_blank")
], separator=" "))

if obj.confirmations_count > 0:
details.append(self.html.badge(
f"{obj.confirmations_count} confirmations",
variant="info",
icon=Icons.CHECK_CIRCLE
))

return "<br>".join(details)

payment_details_display.short_description = "Payment Details"

Next Steps

Key Concepts
  • Declarative fields (BadgeField, CurrencyField, etc.) auto-generate display methods
  • @computed_field decorator for custom list display logic
  • self.html utilities for custom readonly fields
  • Display utilities (UserDisplay, MoneyDisplay, DateTimeDisplay) for advanced formatting