Admin Configuration
Django-CFG uses a declarative admin system based on PydanticAdmin and AdminConfig. No more scattered list_display, fieldsets, and method definitions — everything is configured in one place.
For a full reference on the admin system including all field types and configuration options, see the Django Admin module docs.
Basic Setup
# admin/my_admin.py
from django.contrib import admin
from django_cfg.modules.django_admin import AdminConfig, BadgeField, DateTimeField
from django_cfg.modules.django_admin.base import PydanticAdmin
from ..models import APIKey
config = AdminConfig(
model=APIKey,
list_display=["name", "prefix", "status_badge", "last_used_display", "created_at"],
list_filter=["is_active", "key_type"],
search_fields=["name", "prefix"],
ordering=["-created_at"],
display_fields=[
BadgeField(
name="status_badge",
label_map={"active": "success", "expired": "danger", "revoked": "warning"},
),
DateTimeField(name="last_used_display", show_relative=True),
],
)
@admin.register(APIKey)
class APIKeyAdmin(PydanticAdmin):
config = configFrom api_keys (stockapis) — the entire admin configuration in one AdminConfig object.
Display Fields
Special field types for rich admin display:
display_fields=[
# Status badge with color mapping
BadgeField(
name="type_badge",
label_map={
"command": "info",
"machine": "success",
"auth": "warning",
"settings": "secondary",
},
),
# Relative timestamps ("2 hours ago")
DateTimeField(name="created_display", show_relative=True),
# User display with avatar
UserField(name="user_display"),
# Truncated text
TextField(name="details_preview", truncate=100),
]Computed Fields
For dynamic display that requires logic:
@admin.register(Symbol)
class SymbolAdmin(PydanticAdmin):
config = config
@computed_field("Status")
def status_display(self, obj):
if obj.is_tradeable:
return self.html.badge("Active", "success")
return self.html.badge("Inactive", "danger")
@computed_field("Data Coverage")
def data_coverage(self, obj):
flags = []
if obj.has_klines:
flags.append("Klines")
if obj.has_trades:
flags.append("Trades")
return self.html.text(", ".join(flags) if flags else "None")From symbols (stockapis) — @computed_field methods use self.html helpers.
Fieldsets
Group fields into sections:
from django_cfg.modules.django_admin import FieldsetConfig
config = AdminConfig(
model=Activity,
fieldsets=[
FieldsetConfig(
title="Activity Info",
fields=["type", "action", "target"],
),
FieldsetConfig(
title="Context",
fields=["details", "ip_address", "user_agent"],
collapsed=True,
),
FieldsetConfig(
title="Relations",
fields=["workspace", "user"],
),
],
)Actions
Custom admin actions with confirmation and bulk support:
from django_cfg.modules.django_admin import ActionConfig
config = AdminConfig(
model=APIKey,
actions=[
ActionConfig(
name="revoke_keys",
label="Revoke selected keys",
icon="block",
variant="danger",
),
ActionConfig(
name="activate_keys",
label="Activate selected keys",
icon="check_circle",
variant="success",
),
],
)
@admin.register(APIKey)
class APIKeyAdmin(PydanticAdmin):
config = config
def revoke_keys(self, request, queryset):
count = queryset.update(is_active=False)
self.message_user(request, f"Revoked {count} keys.")
def activate_keys(self, request, queryset):
count = queryset.update(is_active=True)
self.message_user(request, f"Activated {count} keys.")Read-Only Admin
For audit logs and activity records — view-only, no create/edit:
config = AdminConfig(
model=Activity,
list_display=["type_badge", "action", "target", "user_display", "created_display"],
select_related=["workspace", "user"],
readonly_fields="__all__",
)
@admin.register(Activity)
class ActivityAdmin(PydanticAdmin):
config = config
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return request.user.is_superuserFrom activity (cmdop) — audit logs are immutable, only superusers can delete.
Query Optimization
Use select_related in AdminConfig to prevent N+1 queries in list views:
config = AdminConfig(
model=Activity,
select_related=["workspace", "user"], # Joined in SQL
list_display=["workspace_name", "user_email", "type", "action"],
)Inline Admin
For parent-child relationships displayed inline:
class CoreMemoryBlockInline(admin.TabularInline):
model = CoreMemoryBlock
extra = 0
readonly_fields = ["block_type", "content", "version"]
@admin.register(Agent)
class AgentAdmin(PydanticAdmin):
config = agent_config
inlines = [CoreMemoryBlockInline]From agents (stockapis-trader) — memory blocks displayed inline within the agent detail view.
File Organization
admin/
├── __init__.py # Re-exports
├── my_model_admin.py # AdminConfig + PydanticAdmin
└── actions.py # Shared action handlers (optional)For simple apps, a single admin.py is fine. Split into a directory when you have multiple models with complex admin configs.