ModelAdmin
Har bir deklarativ parametr — u ekranda nimani o‘zgartiradi va uni noto‘g‘ri belgilasangiz nima yuz beradi.
Mavjud v0.1.0
ModelAdmin bitta modelning qanday taqdim etilishini tavsiflaydi. Bu Django
lug‘ati, faqat bitta muhim farq bilan: siz yozgan har bir nom admin
qurilayotganda haqiqiy model bilan solishtirib tekshiriladi, shuning uchun
xato yozuv — bir vaqtning o‘zida ularning barchasini sanab beruvchi ishga
tushirish xatosi, sahifani kimdir birinchi marta ochganda yuzaga keladigan
500-xato emas.
from fastfort import admin
from app.models import Product
@admin.register(Product, key="catalog.product")
class ProductAdmin(admin.ModelAdmin):
list_display = ("id", "sku", "name", "price", "is_active")
list_filter = ("is_active", "category")
search_fields = ("sku", "name")
ordering = ("-created_at",)
Ro‘yxatga olish
@admin.register(model, *, key=None)
Import vaqtida ishga tushadi va ro‘yxatga olishni buferga yozadi;
include_admin, autodiscover va mount shu buferni bo‘shatadi. Aynan shu
bilvositalik tufayli admin.py faylini ilovangizni import qilmasdan yozish
mumkin — aks holda, mainni import qiladigan models moduli va admin moduli
har qanday loyihada aylanma import hosil qilardi.
key avtomatik chiqarilgan registr kalitini almashtiradi. U URL manzilga
aylanadi: /admin/<key>/. Uni turli paketlardagi ikkita model aks holda bir
xil kalitni olib qolsa ishlating, va umuman, uni har doim bering —
domeningiz mantig‘iga mos kalit (catalog.product) URL manzilda fayl
tuzilishiga mos keladigan kalitdan ko‘ra yaxshiroq o‘qiladi.
Ro‘yxat ko‘rinishi
list_display
Ustunlar, tartib bo‘yicha. Bo‘sh qoldirilsa — asosiy kalit va bir nechta birinchi o‘qiladigan maydonlar, ko‘pi bilan oltita — shunday qilib hech narsa sozlanmagan ro‘yxatga olish ham foydali jadval ko‘rsatadi.
list_display = ("id", "sku", "name", "category", "price", "is_active")
Bog‘lanish ustunlari bog‘langan obyektning __str__ metodi orqali
chiqariladi — aynan shu narsa tashqi kalitni oddiy butun sondan farqli
o‘laroq o‘qiladigan qiladi. Ko‘p-ko‘pga bog‘lanish chiplar sifatida
chiqariladi. Geometriya esa qisqacha xulosa sifatida chiqadi — Polygon · 14 points — buzilgan ma’lumotdek ko‘rinadigan WKB-hex o‘rniga.
list_display_links
Qaysi katakchalar yozuvga havola bo‘lishi. Standart bo‘yicha — birinchi ustun.
list_display_links = ("sku", "name")
ordering
Django uslubida: oldida - bo‘lsa — kamayish tartibi. U bo‘lmasa, asosiy
kalit bo‘yicha eng yangilari birinchi — bu admin uchun foydali standart
qiymat.
ordering = ("-created_at", "name")
Saralab bo‘lmaydigan ustunni ko‘rsatish — ishga tushirish xatosi.
list_filter
Filtrlar paneli. Faqat spetsifikatsiya filtrlash mumkin deb belgilagan narsalar bilan cheklangan — shunday qilib erkin matnli ustun o‘n mingta qiymatdan iborat ochiladigan ro‘yxatga aylanib qolmaydi.
list_filter = ("availability", "is_active", "category", "price", "released_on")
Siz hech qachon boshqaruv elementini o‘zingiz ko‘rsatmaysiz. Uni ustun turi tanlaydi — to‘liq moslikni Filtrlar sahifasida ko‘ring.
Ikki turdagi maydon rad etiladi, va aynan shu rad etishning foydasi bor:
- Erkin matn.
StringyokiTextustuni bo‘yicha filtr jadvaldagi barcha turli qiymatlarni o‘z ichiga olgan ochiladigan ro‘yxatga aylanib qolardi. - Ko‘p-ko‘pga bog‘lanish. Qiymat ko‘p qiymatli bo‘lgani uchun solishtirish uchun yagona qiymat yo‘q.
Ikkalasi ham qidiruv, saralash va ko‘rsatish uchun ochiq qolaveradi.
Bularning birini shu yerda ko‘rsatish — qurilish vaqtidagi
ConfigurationError, qaysi maydon va nima uchun ekanini ko‘rsatadi.
search_fields
Qidiruv maydoni nimani qamrab olishi. Hech qaysi biri ko‘rsatilmasa, qidiruv maydoni umuman chizilmaydi — bu hech narsa topmaydigan, lekin buni sezdirmaydigan maydondan yaxshiroq.
search_fields = ("sku", "name", "description")
Faqat matnga o‘xshash ustunlar mos keladi. inet matnga o‘xshab ko‘rinsa-da,
ataylab chetlab o‘tiladi: PostgreSQL’da bu tur uchun LIKE yo‘q, shuning
uchun unga icontains qo‘llash operator does not exist xatosi bilan
yiqiladi, va 500-xato beradigan qidiruv maydoni ustunni shunchaki o‘tkazib
yuboradigan maydondan yomonroq.
list_per_page
Ushbu model uchun admin.page_size qiymatini almashtiradi. Ro‘yxat,
shuningdek, admin.max_page_size bilan cheklangan sahifa hajmini boshqarish
elementini ham taklif qiladi.
select_related · prefetch_related
Oldindan yuklanadigan bog‘lanishlar. Ularsiz bog‘langan obyekt nomini ko‘rsatuvchi ro‘yxat har bir qator uchun bitta qo‘shimcha so‘rovga tushadi.
select_related = ("category", "supplier") # to-one, joined
prefetch_related = ("tags",) # to-many, second query
list_editable
Formani ochmasdan, to‘g‘ridan-to‘g‘ri ro‘yxatda tahrirlanadigan ustunlar.
list_display = ("id", "name", "stock", "is_active")
list_editable = ("stock", "is_active")
Butun jadval bitta “Saqlash” tugmasi bo‘lgan bitta formaga aylanadi. Har bir
qator o‘zicha yuboradigan boshqaruv elementi har bir katak uchun bitta
so‘rov degani bo‘lardi va JavaScript o‘chirilganda ularning birortasini ham
yuborish imkoni qolmasdi — Django’dagi list_editable ham xuddi shu shaklda
va xuddi shu sababga ko‘ra.
Yo barcha qatorlar, yo hech biri: bitta noto‘g‘ri qiymat butun yuborishni orqaga qaytaradi, chunki yarim saqlangan jadval rad etilganidan yomonroq — ekranda qaysi yarmi yozilgani haqida hech narsa aytilmasdi.
Ustun list_displayda bo‘lishi, yozilishi mumkin bo‘lishi va inline qatori
bilan bir xil tor boshqaruv to‘plamidan foydalanishi shart.
Forma
readonly_fields
Ko‘rsatiladi, lekin hech qachon yozilmaydi. Spetsifikatsiyaning o‘z ruxsat etilganlar ro‘yxati bilan birlashtiriladi, shuning uchun bu faqat toraytiradi, hech qachon kengaytirmaydi.
readonly_fields = ("created_at", "updated_at", "embedding")
password_fields
Parol xeshini saqlovchi ustunlar. Ularning boshqaruv elementi yangi parol va uni tasdiqlashni qabul qiladi, uni Argon2id bilan xeshlaydi va ikkalasi ham bo‘sh qoldirilsa saqlangan qiymatga tegmaydi.
E’lon qilinmagan bo‘lsa ham avtomatik aniqlanadi: adapter parol sifatida
turkumlagan maydon, yoki nomi buni o‘zi aytib turgan nozik ustun — aynan shu
tarzda oddiy hashed_password nomli String siz hech narsa demasdan ham
topib olinadi.
formfield_overrides
Qaysi boshqaruv elementi maydonni chizishi, uning turi tanlagan variantni
almashtirib. Kalit sifatida maydon nomi yoki FieldType ishlatiladi,
shuning uchun bitta ustunning yoki muayyan turdagi barcha ustunlarning
turini qayta belgilash mumkin.
formfield_overrides = {
"brand_colour": "color", # a 7-char string is a colour only because you say so
"photo": "image",
"datasheet": "file",
"description": "richtext",
}
Ikkalasi ham mos kelsa, nom turdan ustun turadi. Nomlar e’lon qilish bosqichida spetsifikatsiya bilan solishtirib tekshiriladi, va noma’lum vidjet nomi — jim-jit faqat o‘qish uchun bo‘lib qoladigan maydon emas, balki qurilish vaqtidagi xato.
field_labels
Alohida maydonlar uchun yorliqlar, adapter ustundan chiqargan qiymatni almashtiradi.
field_labels = {"sku": "Stock code", "metadata_json": "Metadata"}
fieldsets
Usiz barcha maydonlar bitta katakka, spesifikatsiya tartibida tushadi — bu qisqa formaga to‘g‘ri keladi, yigirma besh ustunli modelga esa umuman emas.
fieldsets = (
(None, {"fields": ("name", "sku", "category")}),
("Pricing", {"fields": ("price", "cost"), "description": "Shown to customers."}),
("Logistics", {"fields": ("weight", "dimensions"), "collapsed": True}),
)
None sarlavhasi bo‘limni sarlavhasiz chizadi — nom kerak bo‘lmagan birinchi
guruh uchun. collapsed uni yopiq holda chizadi: bu <details>, shuning
uchun JavaScript’siz ham ochiladi.
Maydonni ikki marta nomlash — ishga tushirish bosqichidagi xato, chunki bitta
nomni yuboradigan ikkita boshquv elementi ikkinchisi jimgina g‘olib chiqishini
anglatadi. Ma’lumotlar bazasi talab qiladigan maydonni tushirib qoldirish ham
shunday: forma chizilar, saqlanar va NOT NULL cheklovida, bazadan tashqarida
hech kimga hech narsa demaydigan nom bilan qulardi. Ixtiyoriy maydonni
tushirib qoldirish mumkin va bu e’tirozga sabab bo‘lmaydi — formani toraytirish
bo‘limlar ro‘yxatining maqsadi.
inlines
Modelning bola yozuvlari, uning o‘z sahifasida tahrirlanadi.
from fastfort import admin
from app.models import Order, OrderLine
class OrderLineInline(admin.TabularInline):
model = OrderLine
fields = ("sku", "quantity", "unit_price")
extra = 1
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
inlines = (OrderLineInline,)
Ota-onaga qaytadigan tashqi kalit, agar bola unga bir marta ishora qilsa,
avtomatik aniqlanadi; ikkitasi bo‘lsa, fk_name uni nomlaydi. U hech qachon
ustun sifatida taklif qilinmaydi — uni bog‘lanishning o‘zi belgilaydi, va har
bir qatordagi ochiladigan ro‘yxat qatorni tasodifan boshqa buyurtmaga
ko‘chirishga taklifdir.
Ota-onaning tranzaksiyasida saqlanadi. Tahlil qilinmagan bola ota-onani ham yozilmagan holda qoldiradi, chunki buyurtmaning yarmi umuman yo‘qidan yomonroq.
extra ta bo‘sh qatorni server chizadi — bola aynan shu yo‘l bilan
JavaScript’siz qo‘shiladi; skript ishlayotganda “Yana qo‘shish” tugmasi qatorni
klonlaydi. can_delete har bir qatorga katakcha qo‘yadi — tugma emas, aynan
katakcha, chunki saqlangan bolani olib tashlash ota-onani saqlashning bir
qismi va serverga borib-kelishdan omon chiqishi kerak.
Jadvalli inline ataylab tor boshqaruv to‘plamini qabul qiladi: matn, sonlar,
pul, sanalar, mantiqiy qiymatlar, sanovlar va “birga” bog‘lanishlar. Xarita,
yuklash kartasi yoki formatlangan matn muharriri o‘zini saqlagan qatordan
baland va u tejagan formadan yomonroq. fieldsda nomlangani ishga tushirish
xatosini beradi; standart holatga qoldirilgani esa shunchaki o‘tkazib
yuboriladi.
Ommaviy amallar
actions
Qatorlar tanlangandan so‘ng taklif etiladi. "delete" o‘rnatilgan va
standart bo‘yicha yoqilgan; qolgan barchasi @admin.action bilan belgilangan
metod nomidir.
actions = ("delete", "activate", "mark_discontinued")
Hech qanday amal taklif qilmaslik uchun actions = () ni belgilang —
qatorlari hech qachon ommaviy o‘chirilmasligi kerak bo‘lgan model aynan shu
tarzda buni bildiradi. Bu har bir qatordagi o‘chirish tugmasiga ta’sir
qilmaydi.
@admin.action(label, *, icon=None, danger=False, confirm=None)
@admin.action(
"Mark discontinued",
icon="trash",
danger=True,
confirm="Mark {count} products discontinued?",
)
async def mark_discontinued(self, adapter, objects):
for product in objects:
await adapter.update(product, {"availability": "discontinued"})
return f"{len(objects)} products discontinued."
Metod ushbu modelning adapterini va tanlangan qatorlarni oladi hamda ko‘rsatiladigan xabarni qaytaradi. U hech qachon commit qilmaydi. So‘rovning unit of work’i muvaffaqiyatli yakunlanganda commit qilinadi va istisno yuz berganda orqaga qaytariladi, shuning uchun qirqinchi qatorda muvaffaqiyatsizlikka uchragan amal ortida hech narsa qoldirmaydi.
Belgi qo‘yilgan, lekin actions ro‘yxatiga kiritilmagan metodga uning
nomini yuborish orqali ham yetib bo‘lmaydi.
bulk_editable
Ommaviy tahrirlash har bir tanlangan qator uchun belgilashi mumkin bo‘lgan maydonlar.
bulk_editable = ("status", "category", "is_active")
Standart holatda bo‘sh, bu esa amalni o‘chiradi. deletedan farqli o‘laroq,
aniq yoqiladi: o‘chirish o‘zi haqida e’lon qiladi va so‘raydi, qirq qatordagi
bitta noto‘g‘ri belgilangan ustun esa keyinroq sezilib qoladigan jimgina
o‘zgarishdir.
Amalni tanlash qaysi maydon va qanday qiymat degan savolli sahifani ochadi, va yozuvni aynan o‘sha sahifa yuboradi — bu o‘chirishni tasdiqlash bilan bir xil shakl va aynan shu narsa hammasini JavaScript’siz ishlashiga imkon beradi.
Ruxsat etilganlar ro‘yxati — bu e’lon, spesifikatsiya emas: formada bemalol
yoziladigan maydon, agar bu yerda nomlanmagan bo‘lsa, baribir rad etiladi. U
FieldSpec.editable va readonly_fields allaqachon hal qilgan narsani faqat
toraytira oladi, hech qachon kengaytira olmaydi.
Eksport va import
exportable · export_fields
Eksport standart bo‘yicha yoqilgan: joriy ko‘rinish qo‘llanilgan filtrlar, qidiruv va saralash bilan CSV, Excel yoki JSON sifatida chiqariladi, shunday qilib fayl u olingan jadvalga mos keladi.
exportable = True # the default
export_fields = ("sku", "name", "price", "stock") # defaults to list_display
Qatorlari fayl shaklida admin panelidan chiqib ketmasligi kerak bo‘lgan model uchun buni o‘chiring.
importable · import_fields
Import standart bo‘yicha o‘chirilgan, va bu assimetriya ataylab qilingan: qatorlarni o‘qish — bu darvoza allaqachon beradigan ruxsat; bitta so‘rovda ularning bir necha mingtasini yozish esa — bu tasodifan kimgadir berib qo‘yishga arzimaydigan boshqa narsa.
importable = True
import_fields = ("sku", "name", "price", "stock")
import_fields yozilishi mumkin bo‘lgan narsani hech qachon kengaytirmaydi
— avval FieldSpec.editable tekshiriladi, shuning uchun bu yerda faqat
o‘qish uchun maydonni ko‘rsatish uni yozish uchun ochiq qilmaydi. Bu
yerdagi maqsad — toraytirish: narxlar ro‘yxati narxlarni yangilashi mumkin,
lekin yetkazib beruvchini hech qachon.
Taqdimot
verbose_name · verbose_name_plural
Yon panel va sahifa sarlavhalari uchun almashtirishlar. Belgilanmasa, model nomidan chiqariladi.
Ular tarjima qilinmaydi, va bu ataylab shunday. Model nomi — bu sizning o‘z sohangiz uchun sizning so‘zingiz, va FastFort’ning uni o‘n bir tilda taxmin qilishga haqqi yo‘q — Django ham xuddi shu sababdan sizning model nomlaringizni tarjima qilmaydi. FastFort faqat o‘zining interfeysini tarjima qiladi: tugmalar, filtrlar, xabarlar.
group_name
Ushbu model joylashadigan yon paneldagi sarlavha. U bo‘lmasa, registr
kalitining nom maydoni qismi ishlatiladi — catalog.product Catalog
ostida guruhlanadi.
icon
fastfort.ui.iconsdan olingan nom, yon paneldagi yozuv yonida chiziladi.
E’lon qilish bosqichida tekshiriladi, shuning uchun xato yozuv — jim-jit
bo‘sh joy emas, balki barcha mavjud ikonkalarni sanab beradigan xato.
icon = "box" # users, shield, key, folder, tag, truck, map-pin, database, …
Hisoblanadigan ustunlar
@admin.display(*, label=None, ordering=None, boolean=False)
@admin.display(label="Margin", ordering="price")
def margin_display(self, obj):
return f"{obj.price - obj.cost:,.0f}"
ordering saralash uchun haqiqiy ustunni ko‘rsatadi, chunki hisoblangan
qiymatning o‘ziniki yo‘q. U bo‘lmasa, sarlavha ma’lumotlar bazasi bajara
olmaydigan saralashni hosil qilish o‘rniga, saralab bo‘lmaydigan holda
chiziladi.
Nimadir noto‘g‘ri ko‘rsatilsa
Yuqoridagi har bir parametr ModelAdmin qurilayotganda model
spetsifikatsiyasi bilan solishtirib tekshiriladi — bu esa ushbu modelning
sahifasiga birinchi marta murojaat qilinganda yuz beradi, mount()
chaqirilganda emas. Muammolar xato chiqarilishidan oldin to‘planadi, shuning
uchun bitta ishga tushirish ularning barchasi haqida xabar beradi:
ConfigurationError: ProductAdmin is misconfigured:
- list_display names 'pirce', which catalog.product has no
- list_filter names 'description'; free-text and multi-valued fields
cannot be offered as a filter
- ordering names 'rank', which is not sortable
- search_fields names 'price', which is not a text field
- icon names 'rocket', which is not one of: bell, book, box, calendar, …
- actions names 'archive', which is missing the @admin.action mark
Hint: Fields available on catalog.product: availability, category, cost,
created_at, description, embedding, id, image, is_active, name, price, …
Tekshiruv har bir model uchun alohida va dangasa (lazy) tarzda bajarilgani sababli, ularning barchasini birdaniga ko‘rish uchun har bir sahifani ochish yoki adminlarni testda o‘zingiz instansiyalash kerak bo‘ladi:
def test_every_admin_is_valid(fort):
for entry in fort.registry:
spec = fort.backend.introspect(entry.model, key=entry.key)
entry.admin(spec) # raises ConfigurationError if anything is wrong
Loyiha test to‘plamida bo‘lishga arziydi: bu deploydan oldin hech kim ochmagan sahifani muvaffaqiyatsiz testga aylantiradi.