Skip to Content
GuidesApp Design GuideAdmin Configuration

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 = config

From 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_superuser

From 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.

Last updated on