django-dev-unfold

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Unfold Admin Development

Unfold Admin 开发

Django Unfold admin patterns with modern UI and HTMX customization.
基于Django Unfold的Admin开发模式,包含现代UI与HTMX自定义功能。

Core Principles

核心原则

  1. One admin = one file - Each ModelAdmin in its own file
  2. Unfold base classes - Extend UnfoldAdmin classes
  3. HTMX for dynamics - Use HTMX for interactive features
  4. Dashboard first - Custom dashboards for data visualization
  5. Tailwind styling - Consistent Tailwind CSS classes
  1. 一个Admin对应一个文件 - 每个ModelAdmin单独存放在一个文件中
  2. Unfold基类 - 继承UnfoldAdmin类
  3. 用HTMX实现动态效果 - 使用HTMX开发交互式功能
  4. 仪表盘优先 - 自定义仪表盘用于数据可视化
  5. Tailwind样式 - 使用统一的Tailwind CSS类

Installation

安装

bash
pip install django-unfold
bash
pip install django-unfold

Configuration

配置

python
undefined
python
undefined

config/settings.py (with Dynaconf)

config/settings.py(使用Dynaconf)

INSTALLED_APPS = [ "unfold", "unfold.contrib.filters", "unfold.contrib.forms", "unfold.contrib.inlines", "unfold.contrib.import_export", # Optional "django.contrib.admin", # ... other apps ]
INSTALLED_APPS = [ "unfold", "unfold.contrib.filters", "unfold.contrib.forms", "unfold.contrib.inlines", "unfold.contrib.import_export", # 可选 "django.contrib.admin", # ... 其他应用 ]

Unfold configuration

Unfold 配置

UNFOLD = { "SITE_TITLE": "My Admin", "SITE_HEADER": "My Admin", "SITE_URL": "/", "SITE_SYMBOL": "speed", # Material Symbols icon "SHOW_HISTORY": True, "SHOW_VIEW_ON_SITE": True, "ENVIRONMENT": "config.settings.environment_callback", "COLORS": { "primary": { "50": "250 245 255", "100": "243 232 255", "200": "233 213 255", "300": "216 180 254", "400": "192 132 252", "500": "168 85 247", "600": "147 51 234", "700": "126 34 206", "800": "107 33 168", "900": "88 28 135", "950": "59 7 100", }, }, "SIDEBAR": { "show_search": True, "show_all_applications": True, "navigation": [ { "title": "Navigation", "items": [ { "title": "Dashboard", "icon": "dashboard", "link": reverse_lazy("admin:index"), }, { "title": "Users", "icon": "people", "link": reverse_lazy("admin:users_user_changelist"), }, ], }, ], }, }
def environment_callback(request): """Show environment badge in admin.""" from config.settings import settings env = settings.current_env if env == "production": return ["Production", "danger"] elif env == "staging": return ["Staging", "warning"] return ["Development", "info"]
undefined
UNFOLD = { "SITE_TITLE": "My Admin", "SITE_HEADER": "My Admin", "SITE_URL": "/", "SITE_SYMBOL": "speed", # Material Symbols 图标 "SHOW_HISTORY": True, "SHOW_VIEW_ON_SITE": True, "ENVIRONMENT": "config.settings.environment_callback", "COLORS": { "primary": { "50": "250 245 255", "100": "243 232 255", "200": "233 213 255", "300": "216 180 254", "400": "192 132 252", "500": "168 85 247", "600": "147 51 234", "700": "126 34 206", "800": "107 33 168", "900": "88 28 135", "950": "59 7 100", }, }, "SIDEBAR": { "show_search": True, "show_all_applications": True, "navigation": [ { "title": "导航", "items": [ { "title": "仪表盘", "icon": "dashboard", "link": reverse_lazy("admin:index"), }, { "title": "用户", "icon": "people", "link": reverse_lazy("admin:users_user_changelist"), }, ], }, ], }, }
def environment_callback(request): """在Admin中显示环境标识。""" from config.settings import settings env = settings.current_env if env == "production": return ["Production", "danger"] elif env == "staging": return ["Staging", "warning"] return ["Development", "info"]
undefined

Admin Structure

Admin 结构

myapp/
└── admin/
    ├── __init__.py           # Register all admins
    ├── base.py               # Base admin classes
    ├── user.py               # UserAdmin
    ├── product.py            # ProductAdmin
    └── order.py              # OrderAdmin
myapp/
└── admin/
    ├── __init__.py           # 注册所有Admin
    ├── base.py               # 基础Admin类
    ├── user.py               # UserAdmin
    ├── product.py            # ProductAdmin
    └── order.py              # OrderAdmin

Base Admin Classes

基础Admin类

In
admin/base.py
:
python
from django.contrib import admin
from unfold.admin import ModelAdmin


class BaseModelAdmin(ModelAdmin):
    """Base admin with common configuration."""

    list_per_page = 25
    show_full_result_count = False

    # Unfold features
    compressed_fields = True
    warn_unsaved_form = True

    def get_queryset(self, request):
        """Exclude soft-deleted by default."""
        qs = super().get_queryset(request)
        if hasattr(self.model, "deleted_at"):
            qs = qs.filter(deleted_at__isnull=True)
        return qs


class ReadOnlyModelAdmin(BaseModelAdmin):
    """Admin for read-only models."""

    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 False
admin/base.py
中:
python
from django.contrib import admin
from unfold.admin import ModelAdmin


class BaseModelAdmin(ModelAdmin):
    """包含通用配置的基础Admin类。"""

    list_per_page = 25
    show_full_result_count = False

    # Unfold 功能
    compressed_fields = True
    warn_unsaved_form = True

    def get_queryset(self, request):
        """默认排除软删除的数据。"""
        qs = super().get_queryset(request)
        if hasattr(self.model, "deleted_at"):
            qs = qs.filter(deleted_at__isnull=True)
        return qs


class ReadOnlyModelAdmin(BaseModelAdmin):
    """用于只读模型的Admin类。"""

    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 False

ModelAdmin Template

ModelAdmin 模板

Each admin in its own file (
admin/user.py
):
python
from django.contrib import admin
from django.utils.html import format_html
from unfold.admin import ModelAdmin
from unfold.decorators import display

from ..models import User
from .base import BaseModelAdmin


@admin.register(User)
class UserAdmin(BaseModelAdmin):
    list_display = ["email", "name", "display_status", "created_at"]
    list_filter = ["is_active", "created_at"]
    search_fields = ["email", "name"]
    ordering = ["-created_at"]

    readonly_fields = ["id", "created_at", "updated_at"]

    fieldsets = [
        (None, {
            "fields": ["id", "email", "name"],
        }),
        ("Status", {
            "fields": ["is_active"],
            "classes": ["collapse"],
        }),
        ("Timestamps", {
            "fields": ["created_at", "updated_at"],
            "classes": ["collapse"],
        }),
    ]

    @display(
        description="Status",
        label={
            True: "success",
            False: "danger",
        },
    )
    def display_status(self, obj):
        return obj.is_active
每个Admin单独存放在一个文件中(如
admin/user.py
):
python
from django.contrib import admin
from django.utils.html import format_html
from unfold.admin import ModelAdmin
from unfold.decorators import display

from ..models import User
from .base import BaseModelAdmin


@admin.register(User)
class UserAdmin(BaseModelAdmin):
    list_display = ["email", "name", "display_status", "created_at"]
    list_filter = ["is_active", "created_at"]
    search_fields = ["email", "name"]
    ordering = ["-created_at"]

    readonly_fields = ["id", "created_at", "updated_at"]

    fieldsets = [
        (None, {
            "fields": ["id", "email", "name"],
        }),
        ("状态", {
            "fields": ["is_active"],
            "classes": ["collapse"],
        }),
        ("时间戳", {
            "fields": ["created_at", "updated_at"],
            "classes": ["collapse"],
        }),
    ]

    @display(
        description="状态",
        label={
            True: "success",
            False: "danger",
        },
    )
    def display_status(self, obj):
        return obj.is_active

Admin Init

Admin 注册初始化

Register all admins in
admin/__init__.py
:
python
from .user import UserAdmin
from .product import ProductAdmin
from .order import OrderAdmin

__all__ = [
    "UserAdmin",
    "ProductAdmin",
    "OrderAdmin",
]
admin/__init__.py
中注册所有Admin:
python
from .user import UserAdmin
from .product import ProductAdmin
from .order import OrderAdmin

__all__ = [
    "UserAdmin",
    "ProductAdmin",
    "OrderAdmin",
]

Display Decorators

显示装饰器

Unfold provides display decorators for styled output:
python
from unfold.decorators import display


@display(
    description="Status",
    label={
        "active": "success",
        "pending": "warning",
        "cancelled": "danger",
    },
)
def display_status(self, obj):
    return obj.status


@display(
    description="Amount",
    ordering="total_amount",
)
def display_amount(self, obj):
    return f"${obj.total_amount:,.2f}"


@display(
    description="Actions",
    header=True,  # Show in header row
)
def display_actions(self, obj):
    return format_html(
        '<a href="{}" class="btn btn-sm btn-primary">View</a>',
        obj.get_absolute_url(),
    )
Unfold提供用于样式化输出的显示装饰器:
python
from unfold.decorators import display


@display(
    description="状态",
    label={
        "active": "success",
        "pending": "warning",
        "cancelled": "danger",
    },
)
def display_status(self, obj):
    return obj.status


@display(
    description="金额",
    ordering="total_amount",
)
def display_amount(self, obj):
    return f"${obj.total_amount:,.2f}"


@display(
    description="操作",
    header=True,  # 在表头行显示
)
def display_actions(self, obj):
    return format_html(
        '<a href="{}" class="btn btn-sm btn-primary">查看</a>',
        obj.get_absolute_url(),
    )

Filters

过滤器

Unfold filter types:
python
from unfold.contrib.filters.admin import (
    RangeDateFilter,
    RangeDateTimeFilter,
    SingleNumericFilter,
    RangeNumericFilter,
    SliderNumericFilter,
    DropdownFilter,
    ChoicesDropdownFilter,
    RelatedDropdownFilter,
    AutocompleteSelectFilter,
)


@admin.register(Order)
class OrderAdmin(BaseModelAdmin):
    list_filter = [
        ("created_at", RangeDateFilter),
        ("total_amount", RangeNumericFilter),
        ("status", ChoicesDropdownFilter),
        ("user", RelatedDropdownFilter),
    ]
Unfold支持的过滤器类型:
python
from unfold.contrib.filters.admin import (
    RangeDateFilter,
    RangeDateTimeFilter,
    SingleNumericFilter,
    RangeNumericFilter,
    SliderNumericFilter,
    DropdownFilter,
    ChoicesDropdownFilter,
    RelatedDropdownFilter,
    AutocompleteSelectFilter,
)


@admin.register(Order)
class OrderAdmin(BaseModelAdmin):
    list_filter = [
        ("created_at", RangeDateFilter),
        ("total_amount", RangeNumericFilter),
        ("status", ChoicesDropdownFilter),
        ("user", RelatedDropdownFilter),
    ]

Inline Admin

内联Admin

python
from unfold.admin import TabularInline, StackedInline


class OrderItemInline(TabularInline):
    model = OrderItem
    extra = 0
    readonly_fields = ["subtotal"]

    def subtotal(self, obj):
        return obj.quantity * obj.unit_price


@admin.register(Order)
class OrderAdmin(BaseModelAdmin):
    inlines = [OrderItemInline]
python
from unfold.admin import TabularInline, StackedInline


class OrderItemInline(TabularInline):
    model = OrderItem
    extra = 0
    readonly_fields = ["subtotal"]

    def subtotal(self, obj):
        return obj.quantity * obj.unit_price


@admin.register(Order)
class OrderAdmin(BaseModelAdmin):
    inlines = [OrderItemInline]

HTMX Actions

HTMX 操作

See
references/customization.md
for HTMX patterns including:
  • Dynamic field updates
  • Inline actions
  • Modal dialogs
  • Live search
详见
references/customization.md
中的HTMX模式,包括:
  • 动态字段更新
  • 内联操作
  • 模态对话框
  • 实时搜索

Dashboard

仪表盘

Custom dashboard in
admin/dashboard.py
. See
references/customization.md
.
admin/dashboard.py
中自定义仪表盘。详见
references/customization.md

Additional Resources

其他资源

Reference Files

参考文件

  • references/customization.md
    - HTMX patterns, dashboard setup, custom widgets
  • references/customization.md
    - HTMX模式、仪表盘设置、自定义组件

Related Skills

相关技能

  • django-dev - Core Django patterns
  • django-dev-test - Testing admin functionality
  • django-dev - 核心Django开发模式
  • django-dev-test - Admin功能测试