فصل ۶: ادمین، احراز هویت و دسترسی

شخصی‌سازی ModelAdmin: list_display، list_filter، search_fields و کارایی

ادمین جنگو؛ پنل اداری رایگان

ادمین جنگو برای کارمندان داخلی ساخته شده، نه مشتریان. با چند خط تنظیم، واحد فروش می‌تواند سفارش‌ها را جست‌وجو، فیلتر و ویرایش کند و شما هفته‌ها زمان ساخت پنل را ذخیره می‌کنید. اما ادمین پیش‌فرض روی جدول‌های بزرگ کند و بی‌استفاده است؛ شخصی‌سازی درست تفاوت را می‌سازد.

# apps/orders/admin.py
from django.contrib import admin
from django.db.models import Count

from apps.core.templatetags.persian import jdate, toman
from .models import Order


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ("tracking_code", "customer", "status", "total_display", "items_count", "created_jalali")
    list_display_links = ("tracking_code",)
    list_filter = ("status", "customer__city", ("created_at", admin.DateFieldListFilter))
    search_fields = ("=tracking_code", "customer__full_name", "^customer__user__mobile")
    list_select_related = ("customer",)
    list_per_page = 50
    readonly_fields = ("tracking_code", "total", "created_at")
    ordering = ("-created_at",)
    show_facets = admin.ShowFacets.ALWAYS          # تعداد کنار هر فیلتر (Django 5.0+)
    fieldsets = (
        ("اطلاعات سفارش", {"fields": ("tracking_code", "customer", "status")}),
        ("مالی", {"fields": ("total", "discount")}),
        ("زمان‌ها", {"fields": ("delivery_date", "created_at"), "classes": ("collapse",)}),
    )

    def get_queryset(self, request):
        return super().get_queryset(request).annotate(_items=Count("items"))

    @admin.display(description="مبلغ", ordering="total")
    def total_display(self, obj):
        return toman(obj.total)

    @admin.display(description="تعداد اقلام", ordering="_items")
    def items_count(self, obj):
        return obj._items

    @admin.display(description="تاریخ ثبت", ordering="created_at")
    def created_jalali(self, obj):
        return jdate(obj.created_at, "%Y/%m/%d %H:%M")

چه چیزی چه می‌کند

گزینهاثر
list_displayستون‌های فهرست؛ فیلد، متد یا تابع
list_filterفیلترهای کناری؛ روی رابطه‌ها هم با __
search_fieldsکادر جست‌وجو؛ پیشوند = تطابق دقیق، ^ شروع‌شدن
list_select_relatedجلوگیری از N+1 برای ستون‌های ForeignKey
readonly_fieldsنمایش بدون امکان ویرایش؛ متدها را هم می‌پذیرد
fieldsetsگروه‌بندی فرم ویرایش؛ collapse برای بخش جمع‌شونده

ستون محاسبه‌شده بدون N+1

اگر در items_count بنویسید obj.items.count()، برای هر ردیف یک کوئری می‌زند: پنجاه ردیف یعنی پنجاه کوئری. با annotate در get_queryset همه در یک کوئری حساب می‌شود و ordering="_items" ستون را قابل مرتب‌سازی هم می‌کند.

نکته‌هایی که کمتر کسی می‌داند

  • search_fields بدون پیشوند از icontains روی همه‌ی فیلدها با OR استفاده می‌کند؛ روی جدول میلیونی کند است. برای کد و موبایل = یا ^ بگذارید تا ایندکس استفاده شود.
  • برای جدول‌های خیلی بزرگ، show_full_result_count = False کوئری COUNT کامل «از ۳٬۲۰۰٬۰۰۰» را حذف می‌کند و صفحه‌ی جست‌وجو چند برابر سریع‌تر می‌شود.
  • date_hierarchy ادمین بر اساس ماه‌های میلادی کار می‌کند؛ برای کاربر ایرانی یک SimpleListFilter سفارشی با بازه‌های «امروز، این هفته، این ماه شمسی» مفیدتر است.
  • list_editable ویرایش درجا در فهرست را ممکن می‌کند، اما هر فیلد list_editable باید در list_display باشد و نمی‌تواند اولین ستون (لینک) باشد.
  • عنوان‌های ادمین را با admin.site.site_header، site_title و index_title فارسی کنید؛ یک خط در admin.py هر اپ یا در urls.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.