• bitcoinBitcoin(BTC)$77,431.000.81%
  • ethereumEthereum(ETH)$2,249.821.33%
  • tetherTether(USDT)$1.000.05%
  • binancecoinBNB(BNB)$702.770.94%
  • rippleXRP(XRP)$1.250.89%
  • usd-coinUSDC(USDC)$1.000.04%
  • solanaSolana(SOL)$95.600.82%
  • tronTRON(TRX)$0.317467-4.91%
  • Figure HelocFigure Heloc(FIGR_HELOC)$1.03-0.22%
  • zcashZcash(ZEC)$1,288.084.93%
  • HyperliquidHyperliquid(HYPE)$86.76-4.44%
  • dogecoinDogecoin(DOGE)$0.0752880.02%
  • moneroMonero(XMR)$592.621.16%
  • USDSUSDS(USDS)$1.000.01%
  • chainlinkChainlink(LINK)$11.555.22%
  • whitebitWhiteBIT Coin(WBT)$74.350.86%
  • leo-tokenLEO Token(LEO)$9.37-0.42%
  • cardanoCardano(ADA)$0.1921740.72%
  • stellarStellar(XLM)$0.1693211.90%
  • bitcoin-cashBitcoin Cash(BCH)$288.811.19%
  • nearNEAR Protocol(NEAR)$4.423.65%
  • daiDai(DAI)$1.000.03%
  • Ethena USDeEthena USDe(USDE)$1.000.03%
  • RainRain(RAIN)$0.006128-5.49%
  • uniswapUniswap(UNI)$6.971.95%
  • litecoinLitecoin(LTC)$54.98-0.35%
  • USD1USD1(USD1)$1.000.04%
  • CantonCanton(CC)$0.0961560.23%
  • avalanche-2Avalanche(AVAX)$8.483.03%
  • suiSui(SUI)$0.891.45%
  • the-open-networkGram (prev. Toncoin)(GRAM)$1.192.29%
  • hedera-hashgraphHedera(HBAR)$0.0717721.02%
  • tether-goldTether Gold(XAUT)$3,875.29-0.57%
  • Global DollarGlobal Dollar(USDG)$1.000.02%
  • quant-networkQuant(QNT)$200.163.70%
  • paypal-usdPayPal USD(PYUSD)$1.000.01%
  • shiba-inuShiba Inu(SHIB)$0.000005-0.38%
  • BittensorBittensor(TAO)$234.301.27%
  • crypto-com-chainCronos(CRO)$0.0514830.61%
  • EthenaEthena(ENA)$0.1901122.27%
  • Ripple USDRipple USD(RLUSD)$1.00-0.01%
  • Ondo US Dollar YieldOndo US Dollar Yield(USDY)$1.15-0.10%
  • Circle USYCCircle USYC(USYC)$1.140.00%
  • okbOKB(OKB)$110.640.26%
  • MemeCoreMemeCore(M)$0.92-5.63%
  • pepePepe(PEPE)$0.000005-2.97%
  • aaveAave(AAVE)$135.088.86%
  • BlackRock USD Institutional Digital Liquidity FundBlackRock USD Institutional Digital Liquidity Fund(BUIDL)$1.000.00%
  • OndoOndo(ONDO)$0.4087724.93%
  • Pump.funPump.fun(PUMP)$0.0041453.57%
TradePoint.io
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop
No Result
View All Result
TradePoint.io
No Result
View All Result

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

May 15, 2026
in AI & Technology
Reading Time: 4 mins read
A A
How to Build a Django-Unfold Admin Dashboard with Custom Models, Filters, Actions, and KPIs
ShareShareShareShareShare
(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 }}

YOU MAY ALSO LIKE

Here’s How Much The iPhone Pro Cost In 2019 Vs. 2026

Google Joins OpenAI And Anthropic In Promising New Scraping Controls

{{ 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 %} ''')

Credit: Source link

ShareTweetSendSharePin

Related Posts

Here’s How Much The iPhone Pro Cost In 2019 Vs. 2026
AI & Technology

Here’s How Much The iPhone Pro Cost In 2019 Vs. 2026

October 31, 2026
Google Joins OpenAI And Anthropic In Promising New Scraping Controls
AI & Technology

Google Joins OpenAI And Anthropic In Promising New Scraping Controls

October 31, 2026
Vercel Labs Open-Sources agent-desktop for Agent Computer Use
AI & Technology

Vercel Labs Open-Sources agent-desktop for Agent Computer Use

October 31, 2026
Apple Brings First Apple Intelligence Features to China in iOS 27.3 – Unite.AI
AI & Technology

Apple Brings First Apple Intelligence Features to China in iOS 27.3 – Unite.AI

October 30, 2026
Next Post
White House Correspondents’ Dinner wrapped early, but Trump hoped to ‘let the show go on’

White House Correspondents' Dinner wrapped early, but Trump hoped to 'let the show go on'

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Search

No Result
View All Result
Her Kid Won’t Pay Her Back

Her Kid Won’t Pay Her Back

October 27, 2026
Epoch AI Reports Open-Weight GLM 5.3 Exploits Firefox Patch Gap – Unite.AI

Epoch AI Reports Open-Weight GLM 5.3 Exploits Firefox Patch Gap – Unite.AI

October 29, 2026
Pennsylvania’s measles outbreak shows no sign of slowing

Pennsylvania’s measles outbreak shows no sign of slowing

October 31, 2026

About

Learn more

Our Services

Legal

Privacy Policy

Terms of Use

Bloggers

Learn more

Article Links

Contact

Advertise

Ask us anything

©2020- TradePoint.io - All rights reserved!

Tradepoint.io, being just a publishing and technology platform, is not a registered broker-dealer or investment adviser. So we do not provide investment advice. Rather, brokerage services are provided to clients of Tradepoint.io by independent SEC-registered broker-dealers and members of FINRA/SIPC. Every form of investing carries some risk and past performance is not a guarantee of future results. “Tradepoint.io“, “Instant Investing” and “My Trading Tools” are registered trademarks of Apperbuild, LLC.

This website is operated by Apperbuild, LLC. We have no link to any brokerage firm and we do not provide investment advice. Every information and resource we provide is solely for the education of our readers. © 2020 Apperbuild, LLC. All rights reserved.

No Result
View All Result
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop

© 2023 - TradePoint.io - All Rights Reserved!