11 β Class-based views (CBV)¶
β¬ οΈ Oldingi: 10 β Formalar va validatsiya Β· π README Β· Keyingi: 12 β Static, media va to'liq layout β‘οΈ
Bu bobda: shu paytgacha view'larni funksiya (FBV β function-based view) sifatida yozdik. Endi ikkinchi yo'lni β CBV (class-based view) ni o'rganamiz: view klass bo'lib, HTTP metodlari (
get,post) shu klassning metodlari bo'ladi. AvvalView(eng ildizi) bilanas_view()vadispatch()mexanizmini ochamiz, keyin tayyor generic klasslar bo'ylab yuramiz:TemplateView(sof shablon),ListView(ro'yxat + paginatsiya),DetailView(bitta obyekt), vaCreateView/UpdateView/DeleteView(generic editing β forma+saqlash avtomatik). Yo'l-yo'lakay eng ko'p ishlatiladigan moslashtirish nuqtalarini βget_context_data(),get_queryset(),form_valid(),success_url/get_success_url()β va mixin (LoginRequiredMixin) larni ko'ramiz. Oxirida muhim savol: qachon FBV, qachon CBV ishlatish kerak. Hamma kod Django 6.0.6 da haqiqatan ishga tushirib tekshirilgan.
FBV va CBV: bir vazifaning ikki ko'rinishi¶
Avvalgi boblarda view shunaqa edi β oddiy funksiya, request qabul qiladi, HttpResponse qaytaradi:
# views.py β FBV (funksiya)
from django.http import HttpResponse
def salom_fbv(request):
return HttpResponse("Salom, FBV!")
CBV da xuddi shu narsa klass bo'ladi va HTTP metodi β klassning metodi:
# views.py β CBV (klass)
from django.views import View
from django.http import HttpResponse
class SalomView(View):
def get(self, request):
return HttpResponse("Salom, GET!")
def post(self, request):
return HttpResponse("POST qabul qilindi!")
E'tibor bering: FBV da siz if request.method == "GET": ... elif request.method == "POST": ... deb qo'lda ajratardingiz. CBV da bu ajratish avtomatik β GET so'rov get() metodiga, POST so'rov post() metodiga boradi. Bu CBV ning birinchi yutug'i: har bir HTTP fe'li o'z metodiga ega bo'lib, kod toza ajraladi.
URLconf da farq bitta joyda: FBV ni to'g'ridan-to'g'ri uzatasiz, CBV ni esa .as_view() orqali:
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("salom-fbv/", views.salom_fbv, name="salom-fbv"), # funksiyaning o'zi
path("salom/", views.SalomView.as_view(), name="salom"), # klass -> as_view()
]
path() ga "chaqirib bo'ladigan" (callable) narsa kerak. Funksiya o'zi chaqiriladi. Klass esa o'zi chaqirilmaydi β as_view() klassdan chaqirib bo'ladigan funksiya yasab beradi. Buni keyingi bo'limda ochamiz.
Eslatma: bu kitob Python bilishni faraz qiladi (Python qo'llanmasi) β ayniqsa klass, meros (inheritance) va
super()tushunchasi shu bobda asosiy rol o'ynaydi. Agar siz Node.js'dan kelgan bo'lsangiz, CBV β bu marshrutni metodlarga (router.get,router.post) bog'lashning Django'cha, klassga jamlangan ko'rinishi (Node.js solishtirish).
Nega umuman CBV kerak? Meros va takror-yo'qlik¶
Savol tabiiy: FBV ishlayapti-ku, nega yangi narsa? Javob β takrorni yo'qotish. Veb-ilovalarda bir xil naqsh qayta-qayta uchraydi: "ro'yxatni ko'rsat", "bitta obyektni ko'rsat", "forma ko'rsat va saqla". FBV da bu naqshni har safar qaytadan yozasiz. CBV da Django uni tayyor klass qilib bergan β siz faqat farqni (qaysi model, qaysi shablon) aytasiz, qolgani meros orqali keladi.
Quyidagi FBV β bitta maqolani ko'rsatadi. Diqqat bilan qarang, har bir qator "naqsh"ning bir bo'lagi:
# β Yomon emas, lekin har detail sahifada qaytariladigan naqsh
from django.shortcuts import render, get_object_or_404
from .models import Maqola
def maqola_detail_fbv(request, pk):
maqola = get_object_or_404(Maqola, pk=pk) # obyektni top yoki 404
return render(request, "blog/maqola_detail.html", {"maqola": maqola})
Xuddi shu narsa CBV da:
# Generic DetailView shu naqshni o'zida saqlaydi
from django.views.generic import DetailView
from .models import Maqola
class MaqolaDetailView(DetailView):
model = Maqola
template_name = "blog/maqola_detail.html"
context_object_name = "maqola"
Bu yerda hech qanday "top yoki 404", hech qanday "render" yozmadik β DetailView ularni biladi. Biz faqat qaysi model va qaysi shablon ekanini aytdik. Ana shu β CBV ning kuchi: konfiguratsiya orqali, kod yozish orqali emas. Endi har bir generic klassni ochib chiqamiz; lekin avval ularning umumiy poydevorini β View ni β tushunamiz.
View va as_view() / dispatch() mexanizmi¶
Hamma CBV ning ildizi β django.views.View. U eng sodda CBV bo'lib, undan ortig'ini sizdan kutmaydi: siz HTTP metodlariga mos get, post, put, delete metodlarini yozasiz. Mexanizm ikki qadamdan iborat:
as_view()β URLconf da chaqirasiz. U bir marta ishlaydi-da, view klassining instansini yasaydigan vadispatch()ni chaqiradigan funksiya qaytaradi. Yana bir bor:path()ga funksiya kerak,as_view()esa funksiya beradi.dispatch()β har so'rovda ishlaydi. Urequest.methodni o'qib (masalan"GET"), nomi mos keladigan metodni (self.get) topadi va chaqiradi. Agar bunday metod yo'q bo'lsa (masalan klassingizdadeleteyo'q, lekin so'rov DELETE bo'lsa), Django avtomatik 405 Method Not Allowed qaytaradi.
Mana shu mexanizmni o'z ko'zimiz bilan ko'rish uchun, view ichida self orqali so'rovning ma'lumotlariga kiramiz:
# views.py β View ning ichki ishlashini ko'rsatuvchi misol
from django.views import View
from django.http import HttpResponse
class SalomView(View):
def get(self, request):
# self.request, self.args, self.kwargs β dispatch tomonidan biriktiriladi
return HttpResponse("Salom, GET!")
def post(self, request):
return HttpResponse("POST qabul qilindi!")
get() chaqirilishidan oldin dispatch() allaqachon self.request, self.args, self.kwargs ni instansga yozib qo'ygan bo'ladi β shuning uchun har qanday metod ichida self.request mavjud. Bu FBV dagi request argumentining CBV'dagi ko'rinishi, ammo butun klass bo'ylab ulashiladi.
Endi DELETE so'rovi nima bo'lishini sinab ko'ramiz. Test mijozi (Client) orqali β bu Django'ning rasmiy sinov vositasi:
# tests.py β 405 ni isbotlash
from django.test import TestCase
class ViewTest(TestCase):
def test_get_post_405(self):
self.assertEqual(self.client.get("/salom/").content, b"Salom, GET!")
self.assertEqual(self.client.post("/salom/").content, b"POST qabul qilindi!")
# klassda delete() yo'q -> dispatch 405 qaytaradi
self.assertEqual(self.client.delete("/salom/").status_code, 405)
Bu test bizning muhitda PASS bo'ldi. 405 ni siz hech qaerda yozmadingiz β dispatch() "men bunday metodni qo'llab-quvvatlamayman" deb o'zi qaytardi. FBV da buni qo'lda tekshirishingizga to'g'ri kelardi.
dispatch()ni o'zi ham boshqa muhim joy: u har bir so'rovda, har qanday metoddan oldin ishlaydi. Shuning uchun "tekshiruv" (masalan, foydalanuvchi tizimga kirganmi) odatdadispatch()ga qo'yiladi β bu aynan mixin'lar (LoginRequiredMixin) ishlaydigan joy. Buni quyida ko'ramiz.
TemplateView β sof shablon ko'rsatish¶
Ko'p sahifalar shunchaki shablon ko'rsatadi: bosh sahifa, "biz haqimizda", yordam. Buning uchun get yozish ham ortiqcha β TemplateView bor. Faqat template_name ni aytasiz:
# views.py
from django.views.generic import TemplateView
class HaqidaView(TemplateView):
template_name = "blog/haqida.html"
URLconf:
Tamom β sahifa ishlaydi. Lekin shablonga ma'lumot uzatish kerak bo'lsa-chi? Mana shu yerda CBV ning eng muhim moslashtirish nuqtalaridan biri β get_context_data() keladi.
get_context_data() β shablonga ma'lumot uzatish¶
FBV da kontekstni render(request, tpl, {"jami": ...}) ning uchinchi argumenti orqali berardingiz. CBV da kontekst get_context_data() metodidan qaytadi. Uni qayta yozganda doim super() ni chaqirib, tayyor kontekstni olasiz, keyin o'zingiznikini qo'shasiz:
# views.py
from django.views.generic import TemplateView
from .models import Maqola
class BoshView(TemplateView):
template_name = "blog/bosh.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) # ota-klass kontekstini ol
ctx["jami"] = Maqola.objects.count() # o'zingiznikini qo'sh
return ctx
Shablon:
super().get_context_data(**kwargs) ni unutmang β agar uni o'tkazib yuborsangiz, generic klasslar (ListView, DetailView...) o'z obyektlarini kontekstga qo'sha olmay qoladi. Bu CBV'dagi eng ko'p uchraydigan boshlovchi xatosi. Quyidagi test kontekstdagi jami to'g'ri kelishini tasdiqlaydi (bizda 8 ta maqola bor edi):
# tests.py
def test_templateview_context(self):
r = self.client.get("/")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context["jami"], 8) # PASS
ListView β ro'yxat va paginatsiya bepul¶
ListView modeldagi obyektlar ro'yxatini ko'rsatadi. Eng sodda ko'rinishi β faqat model ni aytish:
# views.py
from django.views.generic import ListView
from .models import Maqola
class MaqolaListView(ListView):
model = Maqola
Bu allaqachon ishlaydi, lekin Django ikkita "kelishuv" (convention) ga tayanadi, ularni bilish kerak:
- Shablon nomi: standart bo'yicha
<ilova>/<model>_list.htmlβ bizning holatdablog/maqola_list.html. Boshqa nom kerak bo'lsatemplate_namebilan o'zgartirasiz. - Kontekst nomi: standart bo'yicha shablonda obyektlar
object_listdeb ataladi. Lekinmaqolalardeb chaqirgan tabiiyroq β buning uchuncontext_object_namebor.
To'liqroq, amaliy ko'rinishi:
# views.py
class MaqolaListView(ListView):
model = Maqola
template_name = "blog/maqola_list.html"
context_object_name = "maqolalar" # shablonda {{ maqolalar }}
paginate_by = 5 # har sahifada 5 ta -> paginatsiya yoqiladi
def get_queryset(self):
# standart hammasini oladi; biz faqat nashr etilganlarni, yangidan eskiga
return Maqola.objects.filter(nashr=True).order_by("-yaratilgan")
get_queryset() β qaysi obyektlar ko'rsatiladi¶
get_queryset() β ListView (va DetailView) ning eng muhim moslashtirish nuqtasi. U "qaysi obyektlar"ni belgilaydi. Standart bo'yicha Model.objects.all() qaytaradi; uni qayta yozib, filter, order_by, select_related (N+1 ni yo'qotish β Ilg'or ORM bobiga qarang) qo'shasiz. Yuqorida biz faqat nashr=True bo'lgan maqolalarni, eng yangisidan boshlab ko'rsatdik.
paginate_by = 5 qo'shilishi bilan Django shablonga uchta qo'shimcha narsa beradi: page_obj (joriy sahifa), paginator (umumiy ma'lumot) va is_paginated (bayroq). URL'ga ?page=2 qo'shilsa, ikkinchi sahifa keladi:
<!-- blog/maqola_list.html -->
<h1>Maqolalar</h1>
<ul>
{% for maqola in maqolalar %}
<li><a href="{{ maqola.get_absolute_url }}">{{ maqola.sarlavha }}</a></li>
{% empty %}
<li>Maqola yo'q</li>
{% endfor %}
</ul>
{% if is_paginated %}
<p>Sahifa {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</p>
{% endif %}
Quyidagi test ikki narsani bir vaqtda isbotlaydi: get_queryset filtri ishlaydi (yashirin maqola chiqmaydi), va paginatsiya 7 ta nashr etilgan maqolani 5+2 qilib bo'ladi:
# tests.py β setUp: 7 ta nashr=True, 1 ta nashr=False yaratilgan
def test_listview_queryset_and_pagination(self):
r = self.client.get("/maqolalar/")
self.assertEqual(len(r.context["maqolalar"]), 5) # 1-sahifada 5 ta
self.assertTrue(r.context["is_paginated"])
self.assertEqual(r.context["paginator"].count, 7) # yashirin chiqmadi (8 emas, 7)
r2 = self.client.get("/maqolalar/?page=2")
self.assertEqual(len(r2.context["maqolalar"]), 2) # 2-sahifada qolgan 2 ta
Bu test PASS bo'ldi. E'tibor bering: FBV da paginatsiyani Django'ning Paginator klassi bilan qo'lda qurardingiz; bu yerda bitta paginate_by = 5 yetadi.
DetailView β bitta obyekt¶
DetailView bitta obyektni ko'rsatadi. URL'dan pk (yoki slug) oladi, obyektni topadi, topilmasa 404 qaytaradi:
# views.py
from django.views.generic import DetailView
from .models import Maqola
class MaqolaDetailView(DetailView):
model = Maqola
template_name = "blog/maqola_detail.html"
context_object_name = "maqola" # shablonda {{ maqola }}, yo'qsa {{ object }}
URLconf'da <int:pk> bo'lishi shart β DetailView aynan shu nomdagi parametrni qidiradi:
Shablonda obyekt maqola (yoki context_object_name bermasangiz object) deb keladi:
DetailView ortida get_queryset() turgani uchun, faqat ma'lum obyektlar ko'rinishi kerak bo'lsa (masalan, "faqat nashr etilganlar"), uni shu yerda ham qayta yozasiz: return Maqola.objects.filter(nashr=True). Topilmaslik 404 ga aylanadi:
# tests.py
def test_detailview(self):
m = Maqola.objects.first()
r = self.client.get(f"/maqola/{m.pk}/")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context["maqola"].pk, m.pk)
def test_detailview_404(self):
self.assertEqual(self.client.get("/maqola/99999/").status_code, 404) # PASS
Ikkala test ham PASS. "Mavjud bo'lmasa 404" mantiqini siz yozmadingiz β generic view'ning ichida.
Generic editing: CreateView, UpdateView, DeleteView¶
Bu uchtasi CBV ning eng katta vaqt tejovchisi. Ular forma + saqlash ning butun naqshini o'zida saqlaydi:
- GET so'rovida β formani (modeldan avtomatik yasalgan
ModelFormni, 10-bob) ko'rsatadi. - POST so'rovida β formani tekshiradi; to'g'ri bo'lsa bazaga yozadi va
success_urlga yo'naltiradi; xato bo'lsa formani xatolar bilan qayta ko'rsatadi.
Siz model va fields ni aytasiz (yoki o'z form_class ingizni berasiz), Django qolganini bajaradi.
CreateView β yangi obyekt yaratish¶
# views.py
from django.views.generic import CreateView
from .models import Maqola
class MaqolaCreateView(CreateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"] # formaga kiradigan maydonlar
template_name = "blog/maqola_form.html"
Shablon β oddiy forma (forma {{ form }} deb keladi, 10-bobdagi ModelForm bilan bir xil):
<!-- blog/maqola_form.html -->
<h1>Maqola formasi</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Saqlash</button>
</form>
Saqlangach qayerga ketadi? CreateView (va UpdateView) standart bo'yicha yangi obyektning get_absolute_url() iga yo'naltiradi. Shuning uchun modelda uni yozib qo'yish foydali:
# models.py
from django.db import models
from django.urls import reverse
class Maqola(models.Model):
sarlavha = models.CharField(max_length=200)
matn = models.TextField()
nashr = models.BooleanField(default=False)
yaratilgan = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.sarlavha
def get_absolute_url(self):
# saqlangach shu URL ga yo'naltiriladi
return reverse("maqola-detail", kwargs={"pk": self.pk})
UpdateView β mavjud obyektni tahrirlash¶
UpdateView deyarli CreateView bilan bir xil, faqat URL'dan pk olib mavjud obyektni topadi va formani uning qiymatlari bilan to'ldirib ko'rsatadi:
# views.py
from django.views.generic import UpdateView
class MaqolaUpdateView(UpdateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"]
template_name = "blog/maqola_form.html" # Create bilan bitta shablonni baham ko'radi
DeleteView va success_url¶
DeleteView GET'da tasdiq sahifasini, POST'da o'chirishni bajaradi. O'chirilgach yo'naltirish uchun obyekt endi mavjud emas, shuning uchun get_absolute_url() ishlamaydi β bu yerda success_url kerak. Lekin URL'ni odatdagi reverse() bilan modul yuklanishida hisoblab bo'lmaydi (URLconf hali tayyor emas), shuning uchun reverse_lazy ishlatiladi:
# views.py
from django.views.generic import DeleteView
from django.urls import reverse_lazy
class MaqolaDeleteView(DeleteView):
model = Maqola
template_name = "blog/maqola_confirm_delete.html"
success_url = reverse_lazy("maqola-list") # β reverse() emas β reverse_lazy()
<!-- blog/maqola_confirm_delete.html -->
<h1>O'chirish</h1>
<form method="post">
{% csrf_token %}
<p>"{{ maqola }}" ni o'chirishni tasdiqlaysizmi?</p>
<button type="submit">Ha, o'chir</button>
</form>
Nega
reverse_lazy,reverseemas?success_url = reverse(...)klass e'lon qilinayotgan paytda β ya'ni Django hali barcha URL'larni yuklab bo'lmaganida β ishga tushadi vaNoReverseMatchxatosi beradi.reverse_lazyesa "dangasa": URL'ni faqat haqiqatan kerak bo'lganda (yo'naltirish vaqtida) hisoblaydi. Klass darajasidagi har qanday URL uchun (success_url,login_url) doimreverse_lazyishlating.
Dinamik URL kerak bo'lsa (masalan, har obyekt uchun har xil joy), success_url o'rniga get_success_url() metodini yozasiz β u so'rov vaqtida chaqiriladi, demak reverse() (oddiy) ham bemalol:
# views.py β dinamik yo'naltirish
from django.urls import reverse
class MaqolaUpdateView(UpdateView):
model = Maqola
fields = ["sarlavha", "matn"]
def get_success_url(self):
return reverse("maqola-detail", kwargs={"pk": self.object.pk})
Quyidagi testlar uchala editing view'ni tekshiradi β yaratish get_absolute_urlga, o'chirish success_urlga yo'naltirishini:
# tests.py (foydalanuvchi tizimga kirgan holatda β quyidagi mixin bo'limiga qarang)
def test_create_logged_in(self):
self.client.login(username="ali", password="parol12345")
r2 = self.client.post("/maqola/yangi/", {
"sarlavha": "Yangi", "matn": "kontent", "nashr": True,
})
self.assertEqual(r2.status_code, 302) # yaratildi -> yo'naltirish
m = Maqola.objects.get(sarlavha="Yangi")
self.assertEqual(r2.url, f"/maqola/{m.pk}/") # get_absolute_url ga
def test_delete(self):
self.client.login(username="ali", password="parol12345")
m = Maqola.objects.first()
r = self.client.post(f"/maqola/{m.pk}/ochir/")
self.assertEqual(r.url, "/maqolalar/") # success_url ga
self.assertFalse(Maqola.objects.filter(pk=m.pk).exists())
Ikkalasi ham PASS. To'liq CRUD'ni (Create, Read-list, Read-detail, Update, Delete) biz deyarli faqat e'lonlar bilan qurdik.
form_valid() β saqlashdan oldin aralashish¶
Ko'p hollarda saqlashdan oldin biror narsa qo'shish kerak bo'ladi β eng klassik misol: maqolaning muallifini joriy foydalanuvchiga tenglash. Buni form_valid() da qilasiz. U forma to'g'ri bo'lgach, lekin bazaga yozishdan oldin chaqiriladi:
# views.py β muallifni avtomatik biriktirish (illustrativ, modelda `muallif` maydoni bo'lsa)
class MaqolaCreateView(CreateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"] # muallif formada YO'Q
def form_valid(self, form):
form.instance.muallif = self.request.user # saqlanmagan obyektga qo'shamiz
return super().form_valid(form) # super() saqlaydi + yo'naltiradi
Biz form_valid() mexanizmini soddalashtirilgan misolda haqiqatan tekshirdik: form.instance ni o'zgartirib, super().form_valid() chaqirilgach baza qatori o'zgargan qiymat bilan yozildi va 302 yo'naltirish qaytdi (PASS). Yodda tuting: super().form_valid(form) ni chaqirishni unutmang β aks holda obyekt saqlanmaydi.
Mixin'lar: LoginRequiredMixin va boshqalar¶
Endi dispatch() haqida aytganimizga qaytamiz. Eslang: dispatch() har bir metoddan oldin ishlaydi. Mixin β bu dispatch() (yoki boshqa metod) ga "ulanib", umumiy xatti-harakat qo'shadigan kichik klass. Eng ko'p ishlatiladigani β LoginRequiredMixin: u "agar foydalanuvchi tizimga kirmagan bo'lsa, uni kirish sahifasiga yo'naltir" deydi.
Muhim qoida (Python merosidan): mixin har doim asosiy view'dan oldin yoziladi. Sababi metod-aniqlash tartibi (MRO β method resolution order): chapdagi klass ustun bo'ladi, shuning uchun tekshiruv asosiy view ishlashidan oldin bajariladi.
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
from .models import Maqola
class MaqolaCreateView(LoginRequiredMixin, CreateView): # β
mixin chapda
model = Maqola
fields = ["sarlavha", "matn", "nashr"]
template_name = "blog/maqola_form.html"
login_url = "/kirish/" # kirmaganlar shu yerga yo'naltiriladi
# β XATO tartib: CreateView avval -> LoginRequiredMixin ta'sir qilmaydi
class MaqolaCreateView(CreateView, LoginRequiredMixin):
...
Quyidagi test ikki holatni isbotlaydi β kirmagan foydalanuvchi yo'naltiriladi, kirgani esa kiritiladi:
# tests.py
def test_create_requires_login(self):
r = self.client.get("/maqola/yangi/")
self.assertEqual(r.status_code, 302) # kirmagan -> yo'naltirish
self.assertIn("/kirish/", r.url)
def test_create_logged_in(self):
User.objects.create_user("ali", password="parol12345")
self.client.login(username="ali", password="parol12345")
self.assertEqual(self.client.get("/maqola/yangi/").status_code, 200) # PASS
Ikkalasi ham PASS. Boshqa foydali tayyor mixin'lar (autentifikatsiya bobida chuqurroq ko'ramiz):
PermissionRequiredMixinβ ma'lum ruxsat (permission_required = "blog.add_maqola") talab qiladi.UserPassesTestMixinβtest_func()da o'z shartingizni yozasiz (masalan, "faqat o'z maqolasini tahrirlay olsin").
O'z mixin'ingizni yozish¶
Mixin β oddiy klass. Ko'p view'da takrorlanadigan kontekstni bitta joyga jamlash uchun o'zingiznikini yozishingiz mumkin. Quyidagi mixin har bir sahifaga sarlavha qo'shadi:
# views.py β o'z mixin
from django.views.generic.base import ContextMixin
class SarlavhaMixin(ContextMixin):
sahifa_sarlavha = "Standart"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["sahifa_sarlavha"] = self.sahifa_sarlavha
return ctx
class HaqidaView(SarlavhaMixin, TemplateView):
template_name = "blog/bosh.html"
sahifa_sarlavha = "Biz haqimizda"
Biz buni haqiqatan tekshirdik: HaqidaView().get_context_data() kontekstida sahifa_sarlavha == "Biz haqimizda" chiqdi, va MRO tartibi HaqidaView -> SarlavhaMixin -> TemplateView -> ... -> ContextMixin bo'ldi (PASS). Bu yerda super().get_context_data() zanjiri MRO bo'ylab har bir klassni navbat bilan chaqirgani uchun ishlaydi β super() ni unutmaslikning yana bir sababi.
Qachon FBV, qachon CBV?¶
Bu eng amaliy savol va javob "har doim CBV" emas. Quyidagi qoidalar amaliyotda yaxshi ishlaydi:
CBV oling, agar:
- Klassik CRUD naqshini bajaryapsiz β ro'yxat, detail, yaratish/tahrirlash/o'chirish. Bu yerda
ListView,DetailView,CreateViewva h.k. juda ko'p kod tejaydi. - Bir nechta view'da bir xil xatti-harakat kerak (login talabi, umumiy kontekst) β mixin orqali bir marta yozasiz.
- Loyiha o'sib boryapti va izchillik (har detail sahifa bir xil ko'rinsin) muhim.
FBV oling, agar:
- View'da g'ayrioddiy, "naqshga tushmaydigan" mantiq bor β bir nechta model bilan ishlash, murakkab shart, tashqi API chaqiruvi. CBV ni majburlab moslashtirgandan ko'ra, oddiy funksiya tushunarliroq.
- View juda oddiy va bir martalik (masalan, bitta JSON qaytaradigan endpoint).
- Jamoangiz yoki siz hali CBV ichki oqimi (qaysi metod nimani chaqiradi) bilan tanish emassiz β o'qib bo'lmaydigan "sehrli" CBV dan ochiq FBV afzal.
Oltin qoida: takrorlanuvchi, standart ish β CBV; noyob, murakkab mantiq β FBV. Ikkalasini bir loyihada aralash ishlatish mutlaqo normal va keng tarqalgan. Django ham, jamoa ham buni qoralamaydi.
Bitta foydali maslahat: CBV ichida qaysi metodni qayta yozishni bilmasangiz, Classy Class-Based Views (ccbv.co.uk) saytida har bir generic view'ning butun metod ro'yxati va MRO si ochib berilgan β eng yaxshi ma'lumotnoma.
Mashqlar¶
Oson¶
Viewdan meros olib,get()daHttpResponse("Vaqt: ...")qaytaradiganVaqtViewyozing va URL'ga ulang.as_view()ni qaerda chaqirasiz?TemplateViewyordamida "Bog'lanish" (aloqa.html) sahifasini ko'rsatuvchi view yozing. Hech qandayget()yozmang.BoshView(TemplateView) ningget_context_data()ida kontekstganom = "Mening blogim"qo'shing va shablonda ko'rsating.ListViewuchun shablon nomi va kontekst nomining standart qiymatlari qanaqa?Maqolamodeli uchun aniq yozib bering.MaqolaDetailView(DetailView) ning URL'i<int:pk>o'rniga<int:id>bo'lsa nima bo'ladi? Nega?DeleteViewda negasuccess_url = reverse(...)xato,reverse_lazy(...)to'g'ri? Bir jumlada tushuntiring.
O'rta¶
MaqolaListView(ListView) daget_queryset()ni shunday yozingki, faqatnashr=Truebo'lgan va sarlavhasida URL'dan kelgan?q=so'zi bor maqolalar chiqsin (self.request.GET).MaqolaListViewgapaginate_by = 3qo'shing va shablonda "oldingi / keyingi" havolalarinipage_obj.has_previous/has_nextorqali ko'rsating.MaqolaCreateView(CreateView) gaform_valid()yozib, saqlashdan oldinform.instance.nashr = Falseqiling (har yangi maqola qoralama bo'lib yaratilsin).MaqolaUpdateViewdasuccess_urlo'rnigaget_success_url()yozing β tahrirlangach o'sha maqolaning detail sahifasiga qaytsin.LoginRequiredMixinniMaqolaUpdateViewvaMaqolaDeleteViewga qo'shing. Mixin klasslar ro'yxatida qayerda turishi kerak va nega?get_context_data()dasuper()ni chaqirmasangizListViewda nima yo'qoladi? Sinab ko'ring va tushuntiring.
Qiyin¶
SarlavhaMixin(ContextMixin) yozing: u kontekstgasahifa_sarlavhaqo'shsin. UniListViewvaDetailViewga qo'llang, har birida boshqa sarlavha bering. MRO tartibi qanaqa bo'ladi?UserPassesTestMixinyordamidaMaqolaUpdateViewni shunday cheklang: faqat staff (request.user.is_staff) foydalanuvchi tahrirlay olsin, boshqasi 403 olsin. (Modelda muallif maydoni yo'q deb faraz qiling.)- Bitta
MaqolaActionView(View)yozing:get()da tasdiq sahifasini,post()da maqolaninashr=Trueqilib belgilab, ro'yxatga yo'naltiruvchi (qo'ldaredirect) β ya'ni generic editing'siz, sofViewbilan. Bu CBV ichidadispatchning ishini ko'rsatadi.
Yechimlar
1.
# views.py
from django.views import View
from django.http import HttpResponse
from django.utils import timezone
class VaqtView(View):
def get(self, request):
return HttpResponse(f"Vaqt: {timezone.now():%H:%M:%S}")
# urls.py β as_view() URLconf da chaqiriladi (path ga callable kerak)
path("vaqt/", VaqtView.as_view(), name="vaqt"),
2.
# views.py
from django.views.generic import TemplateView
class AloqaView(TemplateView):
template_name = "blog/aloqa.html"
TemplateView GET'ni o'zi qayta ishlaydi β get() yozish shart emas.
3.
class BoshView(TemplateView):
template_name = "blog/bosh.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["nom"] = "Mening blogim"
return ctx
4. Shablon: blog/maqola_list.html (<ilova>/<model kichik harf>_list.html). Kontekst nomi: object_list (yana maqola_list ham mavjud bo'ladi). context_object_name = "maqolalar" bilan o'zgartirasiz.
5. DetailView standart bo'yicha URL'dan pk yoki slug nomli parametrni qidiradi. <int:id> bo'lsa, id nomi mos kelmaydi va AttributeError/konfiguratsiya xatosi chiqadi. Yechim: <int:pk> ishlating, yoki pk_url_kwarg = "id" deb view'ga ayting.
6. success_url = reverse(...) klass yuklanayotgan paytda β URL'lar hali ro'yxatdan o'tmaganida β ishga tushadi va NoReverseMatch beradi; reverse_lazy(...) esa URL'ni faqat haqiqatan kerak bo'lganda (yo'naltirishda) hisoblaydi.
7.
class MaqolaListView(ListView):
model = Maqola
context_object_name = "maqolalar"
def get_queryset(self):
qs = Maqola.objects.filter(nashr=True)
q = self.request.GET.get("q")
if q:
qs = qs.filter(sarlavha__icontains=q)
return qs.order_by("-yaratilgan")
8.
{% if is_paginated %}
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Oldingi</a>
{% endif %}
<span>{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Keyingi</a>
{% endif %}
{% endif %}
9.
class MaqolaCreateView(CreateView):
model = Maqola
fields = ["sarlavha", "matn"] # nashr formada yo'q
template_name = "blog/maqola_form.html"
def form_valid(self, form):
form.instance.nashr = False # qoralama
return super().form_valid(form)
10.
from django.urls import reverse
class MaqolaUpdateView(UpdateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"]
template_name = "blog/maqola_form.html"
def get_success_url(self):
return reverse("maqola-detail", kwargs={"pk": self.object.pk})
get_success_url() so'rov vaqtida chaqilgani uchun bu yerda oddiy reverse() bemalol ishlaydi.
11.
from django.contrib.auth.mixins import LoginRequiredMixin
class MaqolaUpdateView(LoginRequiredMixin, UpdateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"]
template_name = "blog/maqola_form.html"
class MaqolaDeleteView(LoginRequiredMixin, DeleteView):
model = Maqola
success_url = reverse_lazy("maqola-list")
Mixin eng chapda (asosiy view'dan oldin) turishi kerak. MRO tufayli chapdagi klass ustun bo'ladi, shuning uchun LoginRequiredMixin.dispatch() tekshiruvi asosiy view ishlamasdan oldin bajariladi. Teskari tartibda mixin ta'sir qilmaydi.
12. super().get_context_data() ni chaqirmasangiz, ListView o'zining object_list/paginator/page_obj/is_paginated (va context_object_name orqali ataladigan ro'yxat) kontekstini kontekstga qo'sha olmaydi β shablonda obyektlar ro'yxati yo'qoladi, {% for %} bo'sh ishlaydi. Sinov: get_context_data da faqat return {"x": 1} qaytarsangiz, shablon ro'yxatni topa olmaydi.
13.
from django.views.generic.base import ContextMixin
class SarlavhaMixin(ContextMixin):
sahifa_sarlavha = "Standart"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["sahifa_sarlavha"] = self.sahifa_sarlavha
return ctx
class MaqolaListView(SarlavhaMixin, ListView):
model = Maqola
context_object_name = "maqolalar"
sahifa_sarlavha = "Barcha maqolalar"
class MaqolaDetailView(SarlavhaMixin, DetailView):
model = Maqola
context_object_name = "maqola"
sahifa_sarlavha = "Maqola tafsiloti"
MRO (masalan ListView uchun): MaqolaListView -> SarlavhaMixin -> ListView -> ... -> ContextMixin -> object. super().get_context_data() zanjiri shu tartibda har bir klassni chaqirgani uchun mixin ham, ListView ham o'z hissasini qo'shadi.
14.
from django.contrib.auth.mixins import UserPassesTestMixin
class MaqolaUpdateView(UserPassesTestMixin, UpdateView):
model = Maqola
fields = ["sarlavha", "matn", "nashr"]
template_name = "blog/maqola_form.html"
raise_exception = True # test o'tmasa 403 (yo'naltirish o'rniga)
def test_func(self):
return self.request.user.is_staff
raise_exception = True bo'lmasa, test o'tmagan foydalanuvchi login_url ga yo'naltiriladi; True bilan to'g'ridan-to'g'ri 403 oladi.
15.
from django.views import View
from django.shortcuts import render, get_object_or_404, redirect
class MaqolaActionView(View):
def get(self, request, pk):
maqola = get_object_or_404(Maqola, pk=pk)
return render(request, "blog/nashr_tasdiq.html", {"maqola": maqola})
def post(self, request, pk):
maqola = get_object_or_404(Maqola, pk=pk)
maqola.nashr = True
maqola.save()
return redirect("maqola-list")
Bu yerda dispatch() GET'ni get() ga, POST'ni post() ga o'zi yo'naltiradi β siz hech qanday if request.method yozmaysiz. Aynan shu CBV ning View darajasidagi asosiy foydasi.
β¬ οΈ Oldingi: 10 β Formalar va validatsiya Β· π README Β· Keyingi: 12 β Static, media va to'liq layout β‘οΈ