Technology & AI
How to Build a Django-Unfold Admin Dashboard with Custom Models, Filters, Actions, and KPIs

(ROOT / "shop" / "admin.py").write_text('''
from django.contrib import admin, messages
from django.contrib.auth.admin import (UserAdmin as DjangoUserAdmin,
GroupAdmin as DjangoGroupAdmin)
from django.contrib.auth.models import User, Group
from django.shortcuts import redirect
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import (
ChoicesDropdownFilter, RangeNumericFilter, RangeDateFilter,
MultipleChoicesDropdownFilter,
)
from unfold.decorators import display, action
from .models import Category, Customer, Product, Order, OrderItem
admin.site.unregister(User); admin.site.unregister(Group)
@admin.register(User)
class UserAdmin(DjangoUserAdmin, ModelAdmin):
pass
@admin.register(Group)
class GroupAdmin(DjangoGroupAdmin, ModelAdmin):
pass
@admin.register(Category)
class CategoryAdmin(ModelAdmin):
list_display = ("name", "parent", "show_active", "created_at")
list_filter = (("is_active", ChoicesDropdownFilter),)
search_fields = ("name", "slug")
prepopulated_fields = {"slug": ("name",)}
list_filter_submit = True
compressed_fields = True
@display(description=_("Active"), boolean=True)
def show_active(self, obj): return obj.is_active
@admin.register(Customer)
class CustomerAdmin(ModelAdmin):
list_display = ("name","email","show_tier","lifetime_value","joined")
list_filter = (
("tier", MultipleChoicesDropdownFilter),
("lifetime_value", RangeNumericFilter),
("joined", RangeDateFilter),
)
search_fields = ("name","email")
list_filter_submit = True
warn_unsaved_form = True
list_per_page = 25
@display(description=_("Tier"), label={
"bronze":"warning","silver":"info","gold":"success","platinum":"primary"})
def show_tier(self, obj):
return obj.get_tier_display(), obj.tier
class OrderItemInline(TabularInline):
model = OrderItem
extra = 0
fields = ("product", "quantity", "unit_price", "position")
ordering_field = "position"
tab = True
@admin.register(Order)
class OrderAdmin(ModelAdmin):
list_display = ("number","customer_link","show_status","total","created_at")
list_filter = (
("status", ChoicesDropdownFilter),
("total", RangeNumericFilter),
("created_at", RangeDateFilter),
)
search_fields = ("number","customer__name","customer__email")
readonly_fields = ("created_at",)
autocomplete_fields = ("customer",)
inlines = [OrderItemInline]
list_filter_submit = True
fieldsets = (
(_("Order"), {"classes":["tab"], "fields":("number","customer","status","total")}),
(_("Notes"), {"classes":["tab"], "fields":("notes","created_at")}),
)
actions_list = ["mark_paid_bulk"]
actions_row = ["mark_paid_row"]
actions_detail = ["duplicate_order"]
actions_submit_line = ["save_and_ship"]
@display(description=_("Status"), label={
"pending":"warning","paid":"info","shipped":"primary",
"delivered":"success","cancelled":"danger"})
def show_status(self, obj):
return obj.get_status_display(), obj.status
@display(description=_("Customer"))
def customer_link(self, obj):
return format_html('{}',
obj.customer_id, obj.customer.name)
@action(description=_("Mark pending → PAID (all)"), icon="payments")
def mark_paid_bulk(self, request, queryset=None):
n = Order.objects.filter(status="pending").update(status="paid")
self.message_user(request, f"Marked {n} orders as paid.", level=messages.SUCCESS)
@action(description=_("Mark paid"), icon="payments", url_path="mark-paid-row")
def mark_paid_row(self, request, object_id):
Order.objects.filter(pk=object_id).update(status="paid")
self.message_user(request, "Order marked as paid.", level=messages.SUCCESS)
return redirect(request.META.get("HTTP_REFERER","/admin/"))
@action(description=_("Duplicate"), icon="content_copy", url_path="duplicate")
def duplicate_order(self, request, object_id):
o = Order.objects.get(pk=object_id)
o.pk = None; o.number = o.number + "-COPY"; o.status = "pending"; o.save()
self.message_user(request, "Order duplicated.", level=messages.SUCCESS)
return redirect(f"/admin/shop/order/{o.pk}/change/")
@action(description=_("Save & ship"))
def save_and_ship(self, request, obj):
obj.status = "shipped"; obj.save()
self.message_user(request, f"Order {obj.number} shipped.", level=messages.SUCCESS)
@admin.register(Product)
class ProductAdmin(ModelAdmin):
list_display = ("name","sku","category","show_status",
"price_display","stock_badge","featured")
list_editable = ("featured",)
list_filter = (
("status", ChoicesDropdownFilter),
("category", admin.RelatedFieldListFilter),
("price", RangeNumericFilter),
("featured", ChoicesDropdownFilter),
)
search_fields = ("name","sku")
autocomplete_fields = ("category",)
list_filter_submit = True
list_per_page = 20
save_on_top = True
fieldsets = (
(_("Basics"), {"classes":["tab"],
"fields":("name","sku","category","status","featured")}),
(_("Pricing"), {"classes":["tab"],
"fields":("price","has_discount","discount_percent","stock")}),
(_("Content"), {"classes":["tab"], "fields":("description",)}),
)
conditional_fields = {"discount_percent": "has_discount == true"}
@display(description=_("Status"), label={
"draft":"info","active":"success","archived":"warning"})
def show_status(self, obj):
return obj.get_status_display(), obj.status
@display(description=_("Price"))
def price_display(self, obj):
if obj.has_discount and obj.discount_percent:
return format_html(
'${} '
'${}', obj.price, obj.final_price)
return f"${obj.price}"
@display(description=_("Stock"), ordering="stock",
label={"out":"danger","low":"warning","ok":"success"})
def stock_badge(self, obj):
if obj.stock == 0: return "Out of stock", "out"
if obj.stock < 10: return f"Low ({obj.stock})", "low"
return f"{obj.stock} in stock", "ok"
''')
(ROOT / "templates" / "admin" / "index.html").write_text('''{% extends "admin/index.html" %}
{% load i18n %}
{% block content %}
{% for k in kpis %}
{{ k.title }}
{{ k.value }}
{{ k.footer }}
{% endfor %}
{% trans "Top categories" %}
{% for c in top_cats %}- {{ c.name }}{{ c.n }}
{% endfor %}
{% trans "Orders by status" %}
{% for s in by_status %}- {{ s.status }}{{ s.c }}
{% endfor %}
{{ block.super }}
{% endblock %}
''')


