فصل ۱۲: Class-based Views (CBV) در Django — از مقدماتی تا پیشرفته

""

۱. مقدمه

تا الان ما بیشتر با Function-based Views (FBV) کار کردیم، یعنی ویوهایی که با یک تابع تعریف میشن.
مثل این:

from django.shortcuts import render
from .models import Product

def product_list(request):
    products = Product.objects.all()
    return render(request, 'product_list.html', {'products': products})

این روش ساده و قابل فهمه، مخصوصاً برای پروژه‌های کوچک.
اما وقتی پروژه بزرگ‌تر میشه:

  • کدها تکراری میشه
  • مدیریت حالت‌ها سخت میشه (GET, POST و …)
  • اضافه کردن قابلیت‌ها مثل فرم، لیست و جزییات دردسر پیدا می‌کنه

اینجاست که Class-based Views (CBV) کمکمون می‌کنن.


۲. CBV چیست؟

CBV یعنی به جای یک تابع، از یک کلاس برای تعریف ویو استفاده کنیم.
جنگو چندین کلاس آماده داره که خیلی از کارهای معمول رو برات انجام میدن.

مزایا:

  1. کد تمیزتر — قابلیت وراثت (inheritance) و استفاده مجدد
  2. جداسازی بهتر منطق — متدها برای هر حالت (get(), post())
  3. زمان توسعه کمتر — Generic Views آماده جنگو

۳. ساده‌ترین شکل CBV

یک ویو ساده با CBV:

from django.views import View
from django.http import HttpResponse

class HelloView(View):
    def get(self, request):
        return HttpResponse("سلام از CBV!")

📌 در urls.py:

from django.urls import path
from .views import HelloView

urlpatterns = [
    path('hello/', HelloView.as_view(), name='hello'),
]
  • as_view() یک متد کلاس است که نمونه قابل اجرا از ویو رو می‌سازه.
  • در CBV، هر متد مثل get()، post() و … مسئول یکی از HTTP Methodهاست.

۴. CBV vs FBV — مقایسه عملی

مثلاً یک ویوی ثبت محصول با فرم، در FBV:

def add_product(request):
    if request.method == 'POST':
        form = ProductForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('product_list')
    else:
        form = ProductForm()
    return render(request, 'add_product.html', {'form': form})

همین منطق در CBV:

from django.views import View
from django.shortcuts import render, redirect
from .forms import ProductForm

class AddProductView(View):
    def get(self, request):
        form = ProductForm()
        return render(request, 'add_product.html', {'form': form})

    def post(self, request):
        form = ProductForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('product_list')
        return render(request, 'add_product.html', {'form': form})

۵. Generic Class-based Views — قهرمان‌های آماده جنگو

جنگو CBVهای آماده‌ای داره که این الگوهای پرتکرار رو سریع‌تر می‌کنه:

  • ListView → نمایش لیست اشیا
  • DetailView → نمایش جزئیات یک شیء
  • CreateView → ساخت شیء جدید
  • UpdateView → ویرایش شیء
  • DeleteView → حذف شیء

۶. مثال عملی — لیست محصولات با ListView

در views.py:

from django.views.generic import ListView
from .models import Product

class ProductListView(ListView):
    model = Product
    template_name = 'product_list.html'  # اجباری نیست اگر از اسم پیش‌فرض استفاده کنیم
    context_object_name = 'products'     # نام متغیری که در قالب استفاده می‌کنیم
    paginate_by = 5  # صفحه‌بندی خودکار

📌 در urls.py:

path('products/', ProductListView.as_view(), name='product_list'),

📌 در product_list.html:

<h1>لیست محصولات</h1>
{% for product in products %}
    <h3>{{ product.name }}</h3>
    <p>{{ product.price }} تومان</p>
{% empty %}
    <p>محصولی یافت نشد</p>
{% endfor %}

<div>
    {% if is_paginated %}
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}">قبلی</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 }}">بعدی</a>
        {% endif %}
    {% endif %}
</div>

۷. نمایش جزئیات — DetailView

در views.py:

from django.views.generic import DetailView

class ProductDetailView(DetailView):
    model = Product
    template_name = 'product_detail.html'
    context_object_name = 'product'

📌 در urls.py:

path('products/<int:pk>/', ProductDetailView.as_view(), name='product_detail'),

📌 در product_detail.html:

<h2>{{ product.name }}</h2>
<p>قیمت: {{ product.price }} تومان</p>
{% if product.image %}
    <img src="{{ product.image.url }}" width="200">
{% endif %}
<p>موجودی: {{ product.stock }}</p>

۸. ساخت محصول — CreateView

در views.py:

from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .forms import ProductForm

class ProductCreateView(CreateView):
    model = Product
    form_class = ProductForm
    template_name = 'add_product.html'
    success_url = reverse_lazy('product_list')  # بعد از موفقیت

📌 در urls.py:

path('products/add/', ProductCreateView.as_view(), name='add_product'),

۹. ویرایش محصول — UpdateView

from django.views.generic.edit import UpdateView

class ProductUpdateView(UpdateView):
    model = Product
    form_class = ProductForm
    template_name = 'add_product.html'
    success_url = reverse_lazy('product_list')

۱۰. حذف محصول — DeleteView

from django.views.generic.edit import DeleteView

class ProductDeleteView(DeleteView):
    model = Product
    template_name = 'confirm_delete.html'
    success_url = reverse_lazy('product_list')

📌 در confirm_delete.html:

<h3>آیا مطمئن هستید که می‌خواهید "{{ object.name }}" را حذف کنید؟</h3>
<form method="post">
    {% csrf_token %}
    <button type="submit">بله، حذف شود</button>
    <a href="{% url 'product_list' %}">انصراف</a>
</form>

۱۱. شخصی‌سازی QuerySet در CBV

class ProductListView(ListView):
    model = Product
    template_name = 'product_list.html'
    context_object_name = 'products'

    def get_queryset(self):
        return Product.objects.filter(stock__gt=0).order_by('price')

۱۲. اضافه کردن داده اضافی به Context

class ProductListView(ListView):
    model = Product

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        return context

۱۳. ترکیب CBV با Mixins

Mixin یک کلاس کوچک با یک یا چند رفتار خاصه که میشه به CBV اضافه کرد.

مثال: فقط کاربران لاگین کرده مجاز باشن:

from django.contrib.auth.mixins import LoginRequiredMixin

class ProductCreateView(LoginRequiredMixin, CreateView):
    model = Product
    form_class = ProductForm
    success_url = reverse_lazy('product_list')
    login_url = 'login'

۱۴. وقتی باید از FBV استفاده کرد

  • وقتی منطق ساده و کوتاه داری
  • وقتی نیاز به کنترل کامل روی تمام جزئیات داری
  • وقتی فقط یک متد HTTP رو پشتیبانی می‌کنی

۱۵. نکات حرفه‌ای (Best Practices)

  1. استفاده از Generic Viewها برای کاهش کد
  2. استفاده از Mixins برای اضافه کردن قابلیت‌ها
  3. Override فقط متدهایی که نیاز داری
  4. نامگذاری واضح برای Templateها و Contextها

۱۶. خطاهای رایج

خطاعلتراه‌حل
TemplateDoesNotExistمسیر قالب اشتباهtemplate_name رو درست بده
NoReverseMatchآدرس URL اشتباه در قالباسم درست URL Name رو بده
دسترسی غیرمجازفراموشی استفاده از LoginRequiredMixinاضافه کردن Mixin

۱۷. مثال کامل — مدیریت محصولات با CBV

urls.py:

urlpatterns = [
    path('products/', ProductListView.as_view(), name='product_list'),
    path('products/<int:pk>/', ProductDetailView.as_view(), name='product_detail'),
    path('products/add/', ProductCreateView.as_view(), name='add_product'),
    path('products/<int:pk>/edit/', ProductUpdateView.as_view(), name='edit_product'),
    path('products/<int:pk>/delete/', ProductDeleteView.as_view(), name='delete_product'),
]

حالا ما:

  • لیست محصولات با صفحه‌بندی اتومات
  • نمایش جزئیات
  • افزودن محصول
  • ویرایش محصول
  • حذف محصول

رو با کمتر از نصف کد FBV داریم.


۱۸. جمع‌بندی

در این فصل یاد گرفتیم:

  • CBV چیه و چرا استفاده می‌کنیم
  • پیاده‌سازی ساده با View
  • قدرت Generic Class-based Views
  • استفاده در پروژه فروشگاه آنلاین
  • نکات بهینه‌سازی و امنیتی
محمد وب‌سایت

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *