Claude
Skills
Sign in
Back

unfold-admin

Included with Lifetime
$97 forever

Django Unfold admin theme - build, configure, and enhance modern Django admin interfaces with Unfold. Use when working with: (1) Django admin UI customisation or theming, (2) Unfold ModelAdmin, inlines, actions, filters, widgets, or decorators, (3) Admin dashboard components and KPI cards, (4) Sidebar navigation, tabs, or conditional fields, (5) Any mention of 'unfold', 'django-unfold', or 'unfold admin'. Covers the full Unfold feature set: site configuration, actions system, display decorators, filter types, widget overrides, inline variants, dashboard components, datasets, sections, theming, and third-party integrations.

Design

What this skill does


# Django Unfold Admin

Modern Django admin theme with Tailwind CSS, HTMX, and Alpine.js. Replaces Django's default admin with a polished, feature-rich interface.

## Quick Start

### Installation

```python
# settings.py - unfold MUST be before django.contrib.admin
INSTALLED_APPS = [
    "unfold",
    "unfold.contrib.filters",          # advanced filters
    "unfold.contrib.forms",            # array/wysiwyg widgets
    "unfold.contrib.inlines",          # nonrelated inlines
    "unfold.contrib.import_export",    # styled import/export
    "unfold.contrib.guardian",         # django-guardian integration
    "unfold.contrib.simple_history",   # django-simple-history integration
    "unfold.contrib.constance",        # django-constance integration
    "unfold.contrib.location_field",   # django-location-field integration
    # ...
    "django.contrib.admin",
]
```

### Minimal Admin

```python
from unfold.admin import ModelAdmin

@admin.register(MyModel)
class MyModelAdmin(ModelAdmin):
    pass  # inherits Unfold styling
```

### Site Configuration

Replace the default `AdminSite` or configure via `UNFOLD` dict in settings. See [references/configuration.md](references/configuration.md) for the complete settings reference.

```python
UNFOLD = {
    "SITE_TITLE": "My Admin",
    "SITE_HEADER": "My Admin",
    "SITE_SYMBOL": "dashboard",  # Material Symbols icon name
    "SIDEBAR": {
        "show_search": True,
        "navigation": [
            {
                "title": _("Navigation"),
                "items": [
                    {
                        "title": _("Dashboard"),
                        "icon": "dashboard",
                        "link": reverse_lazy("admin:index"),
                    },
                ],
            },
        ],
    },
}
```

## Core Workflow

When building Unfold admin interfaces, follow this sequence:

1. **Configure site** - UNFOLD settings dict (branding, sidebar, theme)
2. **Register models** - Extend `unfold.admin.ModelAdmin`
3. **Enhance display** - `@display` decorator for list columns
4. **Add actions** - `@action` decorator for row/list/detail/submit actions
5. **Configure filters** - Replace default filters with Unfold filter classes
6. **Override widgets** - Apply Unfold widgets via `formfield_overrides`
7. **Set up inlines** - Use Unfold's inline classes with tabs, pagination, sorting
8. **Build dashboard** - `@register_component` + `BaseComponent` for KPI cards

## ModelAdmin Attributes

Unfold extends Django's `ModelAdmin` with these additional attributes:

| Attribute | Type | Purpose |
|-----------|------|---------|
| `list_fullwidth` | bool | Full-width changelist (no sidebar) |
| `list_filter_submit` | bool | Add submit button to filters |
| `list_filter_sheet` | bool | Filters in sliding sheet panel |
| `compressed_fields` | bool | Compact field spacing in forms |
| `warn_unsaved_form` | bool | Warn before leaving unsaved form |
| `ordering_field` | str | Field name for drag-to-reorder |
| `hide_ordering_field` | bool | Hide the ordering field column |
| `list_horizontal_scrollbar_top` | bool | Scrollbar at top of list |
| `list_disable_select_all` | bool | Disable "select all" checkbox |
| `change_form_show_cancel_button` | bool | Show cancel button on form |
| `actions_list` | list | Global changelist actions |
| `actions_row` | list | Per-row actions in changelist |
| `actions_detail` | list | Actions on change form |
| `actions_submit_line` | list | Actions in form submit area |
| `actions_list_hide_default` | bool | Hide default list actions |
| `actions_detail_hide_default` | bool | Hide default detail actions |
| `conditional_fields` | dict | JS expressions for field visibility |
| `change_form_datasets` | list | BaseDataset subclasses for change form |
| `list_sections` | list | TableSection/TemplateSection for list |
| `list_sections_classes` | str | CSS grid classes for sections |
| `readonly_preprocess_fields` | dict | Transform readonly field content |
| `add_fieldsets` | list | Separate fieldsets for add form (like UserAdmin) |

### Template Injection Points

Insert custom HTML before/after changelist or change form:

```python
class MyAdmin(ModelAdmin):
    # Changelist
    list_before_template = "myapp/list_before.html"
    list_after_template = "myapp/list_after.html"
    # Change form (inside <form> tag)
    change_form_before_template = "myapp/form_before.html"
    change_form_after_template = "myapp/form_after.html"
    # Change form (outside <form> tag)
    change_form_outer_before_template = "myapp/outer_before.html"
    change_form_outer_after_template = "myapp/outer_after.html"
```

### Conditional Fields

Show/hide fields based on other field values (Alpine.js expressions):

```python
class MyAdmin(ModelAdmin):
    conditional_fields = {
        "premium_features": "plan == 'PRO'",
        "discount_amount": "has_discount == true",
    }
```

## Actions System

Four action types, each with different signatures. See [references/actions-filters.md](references/actions-filters.md) for complete reference.

```python
from unfold.decorators import action
from unfold.enums import ActionVariant

# List action (no object context)
@action(description=_("Rebuild Index"), icon="sync", variant=ActionVariant.PRIMARY)
def rebuild_index(self, request):
    # process...
    return redirect(request.headers["referer"])

# Row action (receives object_id)
@action(description=_("Approve"), url_path="approve")
def approve_row(self, request, object_id):
    obj = self.model.objects.get(pk=object_id)
    return redirect(request.headers["referer"])

# Detail action (receives object_id, shown on change form)
@action(description=_("Send Email"), permissions=["send_email"])
def send_email(self, request, object_id):
    return redirect(reverse_lazy("admin:myapp_mymodel_change", args=[object_id]))

# Submit line action (receives obj instance, runs on save)
@action(description=_("Save & Publish"))
def save_and_publish(self, request, obj):
    obj.published = True
```

### Action Groups (Dropdown Menus)

```python
actions_list = [
    "primary_action",
    {
        "title": _("More"),
        "variant": ActionVariant.PRIMARY,
        "items": ["secondary_action", "tertiary_action"],
    },
]
```

### Permissions

```python
@action(permissions=["can_export", "auth.view_user"])
def export_data(self, request):
    pass

def has_can_export_permission(self, request):
    return request.user.is_superuser
```

## Display Decorator

Enhance list_display columns. See [references/actions-filters.md](references/actions-filters.md).

```python
from unfold.decorators import display

# Colored status labels
@display(description=_("Status"), ordering="status", label={
    "active": "success",    # green
    "pending": "info",      # blue
    "warning": "warning",   # orange
    "inactive": "danger",   # red
})
def show_status(self, obj):
    return obj.status

# Rich header with avatar
@display(description=_("User"), header=True)
def show_header(self, obj):
    return [
        obj.full_name,           # primary text
        obj.email,               # secondary text
        obj.initials,            # badge text
        {"path": obj.avatar.url, "width": 24, "height": 24, "borderless": True},
    ]

# Interactive dropdown
@display(description=_("Teams"), dropdown=True)
def show_teams(self, obj):
    return {
        "title": f"{obj.teams.count()} teams",
        "items": [{"title": t.name, "link": t.get_admin_url()} for t in obj.teams.all()],
        "striped": True,
        "max_height": 200,
    }

# Boolean checkmark
@display(description=_("Active"), boolean=True)
def is_active(self, obj):
    return obj.is_active
```

## Filters

Unfold provides advanced filter classes. See [references/actions-filters.md](references/actions-filters.md).

```python
from unfold.contrib.filters.admin import (
    TextFilter, RangeNumericFilter, RangeDateFilter, RangeDateTimeFilter,
    SingleNumericFilter, SliderNumericFilter, R
Files: 6
Size: 84.9 KB
Complexity: 58/100
Category: Design

Related in Design