การปรับแต่ง Django Admin 2026: Actions, Filters และคำถามสัมภาษณ์งาน

เรียนรู้การปรับแต่ง Django admin ด้วย custom actions, list filters, fieldsets และ inlines สำหรับ Django 6 พร้อมคำถามสัมภาษณ์งานที่พบบ่อย

การปรับแต่ง Django Admin 2026: Actions, Filters และคำถามสัมภาษณ์งาน

การปรับแต่ง Django admin เปลี่ยนอินเทอร์เฟซ admin ที่มีมาให้จากเครื่องมือ scaffolding ธรรมดาให้กลายเป็น back office ที่ออกแบบมาเฉพาะสำหรับแอปพลิเคชัน production Django 6 มาพร้อมฟีเจอร์ admin ที่ได้รับการปรับปรุง รวมถึง facet counts สำหรับ filters, form เปลี่ยนรหัสผ่านที่ปรับแต่งได้ และไอคอน Font Awesome ทำให้ admin ที่กำหนดค่าอย่างดีเป็นเครื่องมือที่ใช้งานได้อย่างสะดวกสบาย

ประเด็นสำคัญ

ModelAdmin คือคลาสหลักสำหรับการปรับแต่ง admin ทั้งหมด ควรเชี่ยวชาญ list_display, list_filter, search_fields, fieldsets และ custom actions เพื่อจัดการกับ 90% ของความต้องการในการกำหนดค่า admin

พื้นฐาน ModelAdmin: list_display และ search_fields

list_display ควบคุมว่าคอลัมน์ใดจะแสดงบนหน้า change list นอกจากชื่อ field แบบธรรมดาแล้ว ยังรับ callable ที่ถูก decorate ด้วย @admin.display() สำหรับค่าที่คำนวณ search_fields เปิดใช้งานกล่องค้นหาที่กรอง record โดยใช้ icontains เป็นค่าเริ่มต้น

python
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Article


class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "author_name", "status", "publish_date", "is_recent"]
    search_fields = ["title", "author__email", "content"]
    list_per_page = 50
    list_select_related = ["author"]  # Avoid N+1 queries

    @admin.display(description="Author", ordering="author__last_name")
    def author_name(self, obj):
        return f"{obj.author.first_name} {obj.author.last_name}"

    @admin.display(description="Recent?", boolean=True)
    def is_recent(self, obj):
        from django.utils import timezone
        return obj.publish_date >= timezone.now() - timezone.timedelta(days=7)


admin.site.register(Article, ArticleAdmin)

Decorator @admin.display() แทนที่รูปแบบเก่าของการตั้ง short_description เป็น attribute ของฟังก์ชัน พารามิเตอร์ boolean=True แสดงผลเป็นไอคอนเครื่องหมายถูกหรือ X แทนข้อความ True/False การใช้ list_select_related ป้องกัน query N+1 เมื่อเข้าถึง field ของ foreign key ใน list_display

Custom list_filter ด้วย SimpleListFilter

list_filter เปิดใช้งาน filter ที่ sidebar บนหน้า change list ชื่อ field ทำงานได้สำหรับการกรองพื้นฐาน แต่ SimpleListFilter จัดการกับ logic การกรองที่ซับซ้อนซึ่งครอบคลุมหลาย field หรือต้องการ query เฉพาะ

python
# admin.py
from datetime import date, timedelta
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Article


class PublishDateFilter(admin.SimpleListFilter):
    title = _("publish period")
    parameter_name = "published"  # URL parameter: ?published=this_week

    def lookups(self, request, model_admin):
        # Returns (value, label) tuples for the filter sidebar
        return [
            ("this_week", _("This week")),
            ("this_month", _("This month")),
            ("this_year", _("This year")),
            ("unpublished", _("Not published")),
        ]

    def queryset(self, request, queryset):
        # Filter the queryset based on self.value()
        today = date.today()
        if self.value() == "this_week":
            return queryset.filter(publish_date__gte=today - timedelta(days=7))
        if self.value() == "this_month":
            return queryset.filter(
                publish_date__year=today.year, publish_date__month=today.month
            )
        if self.value() == "this_year":
            return queryset.filter(publish_date__year=today.year)
        if self.value() == "unpublished":
            return queryset.filter(publish_date__isnull=True)
        return queryset


class ArticleAdmin(admin.ModelAdmin):
    list_filter = [
        "status",
        PublishDateFilter,
        ("author", admin.RelatedOnlyFieldListFilter),  # Show only authors with articles
    ]

RelatedOnlyFieldListFilter จำกัด dropdown ของ author ให้แสดงเฉพาะ author ที่มีบทความจริงๆ แทนที่จะแสดงรายการ user ทั้งหมดใน database สิ่งนี้ปรับปรุงการใช้งานเมื่อ model ที่เกี่ยวข้องมี record จำนวนมาก

Django 6 Facets: จำนวน Filter ใน Sidebar

Django 6 แนะนำ facet counts ที่แสดงจำนวน object ที่ตรงกันข้างตัวเลือก filter แต่ละตัว Attribute show_facets ควบคุมพฤติกรรมนี้

python
# admin.py
from django.contrib import admin
from django.contrib.admin import ShowFacets
from .models import Article


class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "status", "category"]
    list_filter = ["status", "category", "author"]
    show_facets = ShowFacets.ALWAYS  # Always display counts

    # Alternative values:
    # ShowFacets.ALLOW - Show counts when ?_facets=1 in URL
    # ShowFacets.NEVER - Never show counts (for large datasets)

Facet counts รัน query COUNT เพิ่มเติมสำหรับตัวเลือก filter แต่ละตัว บนตารางที่มีหลายล้านแถว ให้ตั้ง show_facets = ShowFacets.NEVER เพื่อหลีกเลี่ยงการลดประสิทธิภาพ จำนวนจะอัปเดตแบบไดนามิกเมื่อมีการใช้ filter แสดงว่ามี record กี่รายการที่ตรงกับชุด filter ปัจจุบัน

Custom Admin Actions สำหรับการดำเนินการแบบกลุ่ม

Admin actions ดำเนินการแบบกลุ่มบน object ที่เลือก Decorator @admin.action กำหนด permissions และคำอธิบายที่แสดงใน dropdown

python
# admin.py
from django.contrib import admin
from django.contrib import messages
from django.utils.translation import ngettext
from .models import Article


@admin.action(description="Publish selected articles", permissions=["change"])
def publish_articles(modeladmin, request, queryset):
    # Exclude already published articles
    unpublished = queryset.exclude(status="published")
    updated = unpublished.update(status="published")

    modeladmin.message_user(
        request,
        ngettext(
            "%d article was published.",
            "%d articles were published.",
            updated,
        )
        % updated,
        messages.SUCCESS,
    )


@admin.action(description="Export selected as JSON")
def export_as_json(modeladmin, request, queryset):
    from django.http import HttpResponse
    from django.core import serializers

    response = HttpResponse(content_type="application/json")
    response["Content-Disposition"] = 'attachment; filename="articles.json"'
    serializers.serialize("json", queryset, stream=response)
    return response  # Returning HttpResponse triggers file download


class ArticleAdmin(admin.ModelAdmin):
    actions = [publish_articles, export_as_json]

    def get_actions(self, request):
        # Conditionally remove actions based on user permissions
        actions = super().get_actions(request)
        if not request.user.has_perm("articles.delete_article"):
            del actions["delete_selected"]
        return actions

การ return HttpResponse จาก action จะเริ่มการดาวน์โหลดไฟล์หรือ redirect ไปยังหน้ายืนยัน พารามิเตอร์ permissions จำกัด action เฉพาะผู้ใช้ที่มี permissions ของ model ที่กำหนด Override get_actions() เพื่อแสดงหรือซ่อน actions แบบไดนามิกตาม request ปัจจุบัน

พร้อมที่จะพิชิตการสัมภาษณ์ Django แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Fieldsets และ Inline Models สำหรับฟอร์มที่มีระเบียบ

fieldsets จัดระเบียบฟอร์ม add และ change เป็น section ที่ย่อได้ TabularInline และ StackedInline ฝัง model ที่เกี่ยวข้องโดยตรงในฟอร์มหลัก ลดการนำทางระหว่างหน้า

python
# admin.py
from django.contrib import admin
from .models import Article, ArticleImage, ArticleComment


class ArticleImageInline(admin.TabularInline):
    model = ArticleImage
    extra = 1  # Number of empty forms to display
    max_num = 5  # Maximum images per article
    fields = ["image", "caption", "order"]


class ArticleCommentInline(admin.StackedInline):
    model = ArticleComment
    extra = 0
    readonly_fields = ["created_at", "author"]
    can_delete = True


class ArticleAdmin(admin.ModelAdmin):
    fieldsets = [
        (
            None,
            {
                "fields": ["title", "slug", "author"],
            },
        ),
        (
            "Content",
            {
                "fields": ["content", "excerpt"],
                "classes": ["wide"],  # Full-width fields
            },
        ),
        (
            "Publishing",
            {
                "fields": ["status", "publish_date", "category", "tags"],
                "classes": ["collapse"],  # Collapsed by default
            },
        ),
        (
            "SEO",
            {
                "fields": ["meta_title", "meta_description"],
                "classes": ["collapse"],
                "description": "Search engine optimization fields",
            },
        ),
    ]
    inlines = [ArticleImageInline, ArticleCommentInline]
    prepopulated_fields = {"slug": ["title"]}  # Auto-generate slug from title
    autocomplete_fields = ["author", "category"]  # AJAX-powered select
    filter_horizontal = ["tags"]  # Widget for M2M selection


admin.site.register(Article, ArticleAdmin)

TabularInline แสดง object ที่เกี่ยวข้องในรูปแบบตารางกะทัดรัด เหมาะสำหรับ model ง่ายๆ ที่มี field น้อย StackedInline แสดง object ที่เกี่ยวข้องแต่ละตัวเป็นบล็อกฟอร์มแยก ดีกว่าสำหรับ model ที่มีหลาย field ตัวเลือก autocomplete_fields แทนที่ dropdown เริ่มต้นด้วย widget ค้นหา AJAX จำเป็นเมื่อ model ที่เกี่ยวข้องมี record หลายพันรายการ

การปรับแต่ง Admin Site

การสร้าง subclass ของ AdminSite อนุญาตให้มีอินเทอร์เฟซ admin หลายตัวที่มี branding, permissions หรือการลงทะเบียน model ที่แตกต่างกัน Django 6 เพิ่มการปรับแต่ง password_change_form สำหรับนโยบายรหัสผ่านที่เข้มงวดขึ้น

python
# admin.py
from django.contrib import admin
from django.contrib.admin import AdminSite
from .models import Article, Category


class ContentAdminSite(AdminSite):
    site_header = "Content Management"
    site_title = "CMS"
    index_title = "Dashboard"

    def has_permission(self, request):
        # Restrict to staff in the 'editors' group
        return (
            request.user.is_active
            and request.user.is_staff
            and request.user.groups.filter(name="editors").exists()
        )


content_admin = ContentAdminSite(name="content_admin")
content_admin.register(Article, ArticleAdmin)
content_admin.register(Category)

# In urls.py:
# path('content-admin/', content_admin.urls),

แต่ละ instance ของ AdminSite มี namespace URL ของตัวเอง หน้าเข้าสู่ระบบ และชุดของ model ที่ลงทะเบียน รูปแบบนี้เหมาะสำหรับแอปพลิเคชันที่มี role ผู้ใช้ที่แตกต่างกัน ซึ่ง editor ไม่ควรเห็นการจัดการผู้ใช้หรือการตั้งค่าระบบ

คำถามสัมภาษณ์: การปรับแต่ง Django Admin

คำถามเหล่านี้มักปรากฏในการสัมภาษณ์เชิงเทคนิคของ Django การเข้าใจกลไกพื้นฐาน ไม่ใช่แค่ syntax แยกความแตกต่างระหว่างผู้สมัครระดับกลางกับ engineer อาวุโส

ถาม: จะเพิ่มคอลัมน์ที่คำนวณให้ list_display ที่ไม่สามารถเรียงลำดับได้อย่างไร?

Decorator @admin.display() รับพารามิเตอร์ ordering ที่ระบุว่า field database ใดที่ใช้สำหรับการเรียงลำดับ หากไม่มี header ของคอลัมน์จะไม่สามารถคลิกได้ ค่าที่คำนวณซึ่งรวมหลาย record หรือเรียกบริการภายนอกไม่สามารถเรียงลำดับได้เพราะไม่มีคอลัมน์ database พื้นฐานสำหรับ order

ถาม: ผลกระทบต่อประสิทธิภาพของ facet counts คืออะไร และเมื่อใดควรปิดใช้งาน?

Facets รัน query COUNT หนึ่งตัวต่อตัวเลือก filter คูณด้วยจำนวน filter ที่ใช้งานอยู่ บนตารางที่มี 10 ล้านแถวและ 5 ตัวเลือก filter แต่ละตัวบน 3 field นั่นคือ 15 query COUNT เพิ่มเติมต่อการโหลดหน้าหนึ่งครั้ง ตั้ง show_facets = ShowFacets.NEVER เมื่อ filter กำหนดเป้าหมายตารางที่มีมากกว่า 100,000 แถวโดยไม่มี index ที่เหมาะสม

ถาม: admin actions แตกต่างจาก custom admin views อย่างไร?

Actions ทำงานบน queryset ของ object ที่เลือกและรวมเข้ากับ checkbox การเลือกของ change list Custom admin views เป็นหน้าแบบ standalone ที่เข้าถึงผ่าน URL เฉพาะ เหมาะสำหรับ reports, imports หรือ dashboards ที่ไม่ทำงานบน record ที่เลือก Actions ไม่ควรดำเนินการที่ใช้เวลานานเพราะจะบล็อก request

ถาม: ทำไมต้องใช้ RelatedOnlyFieldListFilter แทน filter เริ่มต้น?

RelatedFieldListFilter เริ่มต้น query object ทั้งหมดในตารางที่เกี่ยวข้อง เมื่อ ForeignKey ชี้ไปยัง model User ที่มี 100,000 record dropdown ของ filter โหลดตัวเลือกทั้ง 100,000 ทำให้เบราว์เซอร์ค้าง RelatedOnlyFieldListFilter query เฉพาะค่าที่มีอยู่ใน record ของ model ปัจจุบัน ซึ่งโดยปกติเป็นชุดที่เล็กกว่ามาก

ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยงใน Production

หลายรูปแบบที่ทำงานได้ใน development ทำให้เกิดปัญหาเมื่อ scale

python
# admin.py
from django.contrib import admin
from .models import Order


class OrderAdmin(admin.ModelAdmin):
    # Mistake: Accessing related fields without list_select_related
    # This causes N+1 queries, one per row in the list view
    list_display = ["id", "customer_email", "total"]

    # Fix: Prefetch related objects
    list_select_related = ["customer"]

    def customer_email(self, obj):
        return obj.customer.email

    # Mistake: search_fields on unindexed large text columns
    # search_fields = ["notes"]  # Slow on large tables

    # Fix: Limit search to indexed fields
    search_fields = ["id", "customer__email"]

    # Mistake: Expensive computation in list_display
    # def order_total(self, obj):
    #     return sum(item.price * item.qty for item in obj.items.all())  # N+1

    # Fix: Use annotation in get_queryset
    def get_queryset(self, request):
        from django.db.models import Sum, F

        qs = super().get_queryset(request)
        return qs.annotate(computed_total=Sum(F("items__price") * F("items__qty")))

    @admin.display(description="Total", ordering="computed_total")
    def total(self, obj):
        return obj.computed_total

การ override get_queryset() เพื่อเพิ่ม annotations รักษาค่าที่คำนวณไว้ใน query เดียวแทนที่จะเข้าถึง database หนึ่งครั้งต่อแถว ใช้ Django Debug Toolbar เพื่อ profile admin และจับ query N+1 ก่อนที่จะถึง production

ธีม Admin จากบุคคลที่สาม: Django Unfold

Django Unfold เป็นธีม admin ที่ได้รับการดูแลอย่างแข็งขันที่สุดสำหรับ Django 6 สร้างด้วย Tailwind CSS มันขยายแทนที่จะแทนที่ admin มาตรฐาน ดังนั้นการกำหนดค่า ModelAdmin ที่มีอยู่จะยังคงทำงานต่อไป

python
# settings.py
INSTALLED_APPS = [
    "unfold",  # Must come before django.contrib.admin
    "unfold.contrib.filters",  # Advanced filter widgets
    "unfold.contrib.forms",  # Enhanced form widgets
    "django.contrib.admin",
    # ...
]

Unfold เพิ่ม dark mode, layouts ที่ตอบสนอง, การนำทาง sidebar และประเภท filter ขั้นสูงเช่น RangeNumericFilter และ RangeDateFilter การรวม HTMX และ Alpine.js ให้การโต้ตอบโดยไม่มี overhead ของ framework SPA เต็มรูปแบบ สำหรับทีมที่ต้องการ admin ที่ขัดเกลาโดยไม่ต้องสร้าง dashboard เอง Unfold ประหยัดเวลาหลายสัปดาห์ของงาน frontend

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

สิ่งที่ควรจำเกี่ยวกับการปรับแต่ง Django Admin

  • list_display ด้วย @admin.display() ควบคุมคอลัมน์; เพิ่ม ordering สำหรับค่าที่คำนวณที่สามารถเรียงลำดับได้
  • SimpleListFilter จัดการ logic filter เฉพาะที่ครอบคลุมหลาย field หรือต้องการช่วงวันที่
  • Django 6 facets แสดงจำนวน filter; ปิดด้วย ShowFacets.NEVER บนตารางขนาดใหญ่เพื่อหลีกเลี่ยง overhead ของ query COUNT
  • Admin actions ด้วย @admin.action(permissions=[...]) จัดการการดำเนินการแบบกลุ่ม; return HttpResponse สำหรับการส่งออกไฟล์
  • ใช้ list_select_related และ queryset ที่มี annotations ใน get_queryset() เพื่อป้องกัน query N+1 ใน list views
  • RelatedOnlyFieldListFilter ป้องกันเบราว์เซอร์ค้างเมื่อกรองตาม ForeignKey ไปยังตารางขนาดใหญ่
  • ฝึกฝนรูปแบบเหล่านี้ด้วย คำถามสัมภาษณ์ Django ที่กำหนดเป้าหมายการกำหนดค่า admin โดยเฉพาะ
ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Django เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 13 กันยายน 2569

แชร์

บทความที่เกี่ยวข้อง