Ommaviy amallar

Qatorlar belgilanganda paydo bo‘ladigan panel — buni qanday e’lon qilish, u ishlaydigan tranzaksiya va amallarni umuman ko‘rsatmaslik haqida.

Mavjud v0.1.0

Qatorni belgilang — jadval ustida panel paydo bo‘ladi. delete o‘rnatilgan va sukut bo‘yicha yoqilgan; qolgan barchasi — ModelAdmin dagi metodlar.

@admin.register(Product, key="catalog.product")
class ProductAdmin(admin.ModelAdmin):
    actions = ("delete", "activate", "mark_discontinued")

    @admin.action("Activate", icon="check-circle")
    async def activate(self, adapter, objects):
        for product in objects:
            await adapter.update(product, {"is_active": True})
        return f"{len(objects)} products activated."

Dekorator o‘zida yorliq va ikonkani olib yuradi, shuning uchun ular amalni amalga oshiruvchi koddan chetlashib keta olmaydi — aynan shu narsaga metod nomlarini alohida ro‘yxatda sanab chiqish yo‘l qo‘yardi.

@admin.action(label, *, icon=None, danger=False, confirm=None)

labelTugmada yozilgan matn
iconfastfort.ui.icons dagi nom, e’lon qilish vaqtida tekshiriladi
dangerXavf rangida chiziladi va bajarishdan oldin tasdiq so‘raydi
confirmSavol matni. {count} o‘rniga qiymat qo‘yiladi
@admin.action(
    "Mark discontinued",
    icon="trash",
    danger=True,
    confirm="Mark {count} products discontinued? They will stop appearing in the shop.",
)
async def mark_discontinued(self, adapter, objects):
    for product in objects:
        await adapter.update(
            product, {"availability": Availability.DISCONTINUED, "is_active": False}
        )
    return f"{len(objects)} products discontinued."

Signatura

async def handler(self, adapter, objects) -> str: ...

adapter — shu model uchun mo‘ljallangan, joriy so‘rovning ish birligiga (unit of work) bog‘langan ModelAdapter. objects — belgilangan va allaqachon yuklangan qatorlar. Qaytarilgan qiymat — keyin ko‘rsatiladigan xabar.

U hech qachon commit qilmaydi

Bu joyni yaxshilab tushunib olish kerak. Adapter flush qiladi; so‘rovning UnitOfWorki muvaffaqiyatli yakunlanganda commit qiladi, xatolik yuz bersa — rollback qiladi. Shu sababli qirqinchi qatorda muvaffaqiyatsizlikka uchragan amal dastlabki o‘ttiz to‘qqiztasini ham yozilmagan holda qoldiradi — bu esa ommaviy operatsiyadan deyarli har doim kutilgan natija, alohida saqlashlar siklidan olinadigan natija esa bunday emas.

@admin.action("Recalculate totals")
async def recalculate(self, adapter, objects):
    for invoice in objects:
        # If this raises on invoice 40, invoices 1–39 are rolled back with it.
        await adapter.update(invoice, {"total": compute_total(invoice)})
    return f"{len(objects)} invoices recalculated."

commit()ni o‘zingiz chaqirmang. Bunday qilish so‘rovni ikkita tranzaksiyaga bo‘lib yuboradi va bu kafolatni undan keyingi hamma narsa uchun buzadi.

Hech narsa taklif qilmaslik

actions = ()

Panel hech qachon paydo bo‘lmaydi. Qatorlari ommaviy tarzda o‘chirilishi mumkin bo‘lmagan model buni aynan shunday bildiradi — har bir qatorning o‘chirish tugmasiga bu ta’sir qilmaydi.

@admin.register(ApiToken, key="accounts.token")
class ApiTokenAdmin(admin.ModelAdmin):
    actions = ()

Chaqirish imkoniyati

@admin.action bilan belgilangan, ammo actions ro‘yxatiga kiritilmagan metod nomini yuborish orqali chaqirib bo‘lmaydi. Tekshiruv e’lon qilingan ro‘yxat asosida olib boriladi, getattr asosida emas — shuning uchun nomni actionsdan izohga olib tashlash uni shunchaki yashirish emas, balki butunlay o‘chirib qo‘yadi.

O‘rnatilgan delete amali

"delete" tasdiqlash sahifasida ko‘rsatilgan xuddi shu o‘chirish rejasini bajaradi va yozishdan oldin uni yana bir bor tekshiradi — chunki sahifa bir soatdan beri ochiq turgan bo‘lishi va shu vaqt ichida bog‘lanishlar grafi o‘zgargan bo‘lishi mumkin.

Tanlangan qatorlardan birortasi himoyalangan bo‘lsa, hech narsa o‘chirilmaydi va sahifa qaysi qator ekanini ko‘rsatadi.

Xatolar

Istisno tashlang — va hammasi orqaga qaytariladi, operatorga esa xabar ko‘rsatiladi:

from fastfort import ValidationError

@admin.action("Ship", icon="truck")
async def ship(self, adapter, objects):
    unpaid = [s for s in objects if s.customer and not s.customer.is_active]
    if unpaid:
        raise ValidationError(
            f"{len(unpaid)} shipments belong to inactive customers.",
            hint="Reactivate the customer, or deselect those rows.",
        )
    ...

ValidationError sahifada xabar sifatida ko‘rsatiladi. Boshqa har qanday narsa, ishlab chiqarish muhitida, umumiy xatolikka aylanadi — trassirovka kimningdir ekranida emas, sizning loglaringizda bo‘ladi.