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

ادمین پیشرفته: inlines، actions، autocomplete و ادمین فارسی

از فهرست ساده تا ابزار کار روزانه

کارمند فروش باید اقلام سفارش را همان‌جا ببیند، ده سفارش را با یک کلیک «ارسال‌شده» کند و بین دو هزار طرح فرش با تایپ چند حرف انتخاب کند. سه ابزار این‌ها را ممکن می‌کنند: inline، action و autocomplete.

from django.contrib import admin, messages
from django.db import transaction

from apps.catalog.models import Carpet
from .models import Order, OrderItem


@admin.register(Carpet)
class CarpetAdmin(admin.ModelAdmin):
    search_fields = ("code", "name")          # برای autocomplete الزامی است
    list_display = ("code", "name", "density", "price", "stock")


class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 0
    autocomplete_fields = ("carpet",)
    readonly_fields = ("unit_price",)


@admin.action(description="علامت‌گذاری به‌عنوان ارسال‌شده", permissions=["change"])
def mark_shipped(modeladmin, request, queryset):
    with transaction.atomic():
        n = queryset.filter(status="paid").update(status="shipped")
    skipped = queryset.count() - n
    modeladmin.message_user(request, f"{n} سفارش ارسال‌شده شد.", messages.SUCCESS)
    if skipped:
        modeladmin.message_user(request, f"{skipped} سفارش پرداخت‌نشده نادیده گرفته شد.", messages.WARNING)


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    inlines = [OrderItemInline]
    actions = [mark_shipped]
    autocomplete_fields = ("customer",)

    def get_readonly_fields(self, request, obj=None):
        if obj and obj.status in ("shipped", "cancelled"):
            return [f.name for f in self.model._meta.fields]     # سفارش بسته قفل است
        return ("tracking_code", "total")

    def has_delete_permission(self, request, obj=None):
        return request.user.is_superuser

    def save_related(self, request, form, formsets, change):
        super().save_related(request, form, formsets, change)
        form.instance.recalculate_total()                        # بعد از ذخیره‌ی اقلام

ادمین فارسی و راست‌به‌چپ

با LANGUAGE_CODE = "fa" ادمین خودکار راست‌به‌چپ و فارسی می‌شود؛ جنگو ترجمه‌ی فارسی رسمی دارد. دو چیز باقی می‌ماند: فونت (با override کردن قالب admin/base_site.html و افزودن یک فایل CSS محلی با فونت Vazirmatn) و تاریخ شمسی (با ستون‌های نمایشی مثل درس قبل یا بسته‌هایی مثل django-jalali). اگر ظاهر مدرن‌تر می‌خواهید، تم‌هایی مثل django-unfold ادمین را بازطراحی می‌کنند و از RTL پشتیبانی دارند؛ همین سایت آموزشی هم با آن ساخته شده است.

{# templates/admin/base_site.html #}
{% extends "admin/base_site.html" %}
{% load static %}
{% block extrastyle %}{{ block.super }}
<link rel="stylesheet" href="{% static 'admin/fa.css' %}">
{% endblock %}

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

  • autocomplete_fields فقط وقتی کار می‌کند که ModelAdmin مدل مقصد search_fields داشته باشد؛ وگرنه در check خطای admin.E040 می‌گیرید.
  • action با queryset.update() نه save() مدل را صدا می‌زند نه سیگنال‌ها را؛ اگر ارسال به پیامک یا لاگ تغییرات لازم است، روی اشیا حلقه بزنید یا کار را صریحاً انجام دهید.
  • admin.site.disable_action("delete_selected") حذف گروهی را در کل ادمین غیرفعال می‌کند؛ یکی از رایج‌ترین منشأهای فاجعه‌ی «همه‌ی سفارش‌ها پاک شد».
  • ادمین با extends "admin/base_site.html" در قالبی با همان نام کار می‌کند چون جنگو قالب‌های پروژه را قبل از قالب‌های اپ admin پیدا می‌کند؛ اپ‌های شما باید در DIRS یا قبل از admin در INSTALLED_APPS باشند.
  • formfield_for_foreignkey اجازه می‌دهد گزینه‌های یک ForeignKey را بر اساس کاربر محدود کنید؛ مثلاً هر نماینده‌ی فروش فقط مشتریان شهر خودش را ببیند.

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