← ~/logs LOG-011

>Is It Time for a Django Admin Rewrite?

Django-Admin-Deux: a ground-up admin rewrite using generic class-based views, a plugin system, and factory-generated views.

Emma Delescolle’s talk at DjangoCon Europe 2026 on Django-Admin-Deux, a ground-up admin rewrite.

Why

Django’s admin is 20 years old. It’s a framework inside a framework — its own patterns, its own view system, its own template hierarchy. Third-party packages collide because there’s no plugin architecture. Class-based views, dataclasses, type hints, Django’s generic views — none of these existed when the admin was written. It doesn’t use them.

How Admin-Deux works

Factory-generated views. At startup, a view factory dynamically creates view classes using type(), composing base views + plugin mixins. The result is standard Django class-based views.

Actions as recipes. Every operation (list, create, update, delete) is an “Action” — a dataclass describing what view to generate. Custom actions (export PDF, send notification) work the same way as CRUD.

Plugin-first design. Built on djp (which uses pluggy from pytest). Plugins auto-register on pip install. Even core CRUD is implemented as a plugin.

Side-by-side migration. Runs on a separate URL (/djadmin/) alongside stock admin. Migrate one model at a time.

Using it

Registration is near-identical to stock admin. Create djadmin.py in your app:

from djadmin import ModelAdmin, register, Column
from djadmin.dataclasses import Filter

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        "name",
        Column("sku", label="SKU Code", classes="font-mono"),
        Column("category", filter=True, order=True),
        Column("price", order=True, filter=Filter(lookup_expr=["gte", "lte"])),
    ]

Mix plain strings and Column objects. filter=True gives exact-match filtering; Filter(...) for range lookups.

Form layouts with Layout, Fieldset, Row, and Field:

from djadmin import Layout, Field, Fieldset, Row

@register(Author)
class AuthorAdmin(ModelAdmin):
    layout = Layout(
        Fieldset("Personal Information",
            Row(
                Field("first_name", css_classes=["flex-1", "pr-2"]),
                Field("last_name", css_classes=["flex-1", "pl-2"]),
            ),
            Field("birth_date", label="Date of Birth"),
        ),
        Fieldset("Biography",
            Field("bio", widget="textarea", attrs={"rows": 8}),
        ),
    )

The plugin system

Plugins use djp/pluggy hooks. Auto-register on install when using djadmin_apps():

from djadmin.plugins import hookimpl

@hookimpl
def djadmin_provides_features():
    return ["search", "filter"]

@hookimpl
def djadmin_get_action_view_mixins(action):
    from djadmin.plugins.core.actions import ListAction
    from .mixins import SearchMixin
    return {ListAction: [SearchMixin]}

Available plugins: core (CRUD, search), djadmin-filters (filtering, ordering), djadmin-formset (form rendering, inlines), djadmin-classy-doc (auto-generated view docs), djadmin-rest2 (REST API views).

Key takeaways

  • Admin-deux uses Django’s own tools — generic views, forms, querysets. If you know Django, you know how to extend it
  • Plugin-first means no more collisions between third-party packages
  • Migration is gradual — run both admins side by side
  • This is alpha software (0.1.6) but the architecture is solid and ready for feedback
  • Dark mode is built in

Slides | Experiment code