فصل ۴: View، URL و Template

View کلاسی و generic viewها: ListView، DetailView، CreateView و mixinها

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

فهرست با صفحه‌بندی، صفحه‌ی جزئیات، فرم ساخت و ویرایش، تأیید حذف: این پنج الگو در هر پروژه ده‌ها بار تکرار می‌شوند. Generic viewهای جنگو این‌ها را آماده دارند و شما فقط تفاوت‌ها را بازنویسی می‌کنید.

Viewکارقالب پیش‌فرض
ListViewفهرست + صفحه‌بندیorders/order_list.html
DetailViewیک شیء با pk یا slugorders/order_detail.html
CreateViewفرم ساختorders/order_form.html
UpdateViewفرم ویرایشorders/order_form.html
DeleteViewتأیید و حذف با POSTorders/order_confirm_delete.html
TemplateView / RedirectViewصفحه‌ی ثابت / ریدایرکت—
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic import CreateView, DetailView, ListView

from .forms import OrderForm
from .models import Order


class OrderListView(LoginRequiredMixin, ListView):
    template_name = "orders/list.html"
    context_object_name = "orders"
    paginate_by = 20
    paginate_orphans = 3                     # صفحه‌ی آخرِ ۲ آیتمی ساخته نشود

    def get_queryset(self):
        qs = Order.objects.for_user(self.request.user).select_related("customer")
        if q := self.request.GET.get("q", "").strip():
            qs = qs.filter(tracking_code__icontains=q)
        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["q"] = self.request.GET.get("q", "")
        return ctx


class OrderDetailView(LoginRequiredMixin, DetailView):
    template_name = "orders/detail.html"

    def get_queryset(self):
        return Order.objects.for_user(self.request.user).with_details()


class OrderCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
    form_class = OrderForm
    template_name = "orders/form.html"
    def get_success_message(self, cleaned_data):
        # tracking_code فیلد فرم نیست؛ پس از self.object می‌خوانیم
        return f"سفارش {self.object.tracking_code} با موفقیت ثبت شد."

    def form_valid(self, form):
        form.instance.customer = self.request.user.customer
        return super().form_valid(form)     # ذخیره + redirect به get_absolute_url

صفحه‌بندی در قالب

{% if page_obj.has_next %}
  <a href="?{% querystring page=page_obj.next_page_number %}">صفحه‌ی بعد</a>
{% endif %}
<span>صفحه‌ی {{ page_obj.number }} از {{ page_obj.paginator.num_pages }}</span>

تگ {% querystring %} (Django 5.1 به بعد) پارامترهای فعلی مثل q را حفظ می‌کند و فقط page را عوض می‌کند؛ قبلاً باید دستی می‌نوشتید و جست‌وجو با رفتن به صفحه‌ی ۲ گم می‌شد.

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

  • mixinهای دسترسی (LoginRequiredMixin، PermissionRequiredMixin) باید سمت چپ کلاس پایه باشند؛ به خاطر MRO پایتون، اگر راست باشند ممکن است View قبل از بررسی دسترسی اجرا شود.
  • فیلتر امنیتی را در get_queryset بگذارید نه get_object؛ این‌طور DetailView، UpdateView و DeleteView همه خودکار 404 می‌دهند.
  • اگر success_url تعریف نکنید، CreateView و UpdateView به get_absolute_url شیء ریدایرکت می‌کنند؛ نبود هر دو خطای ImproperlyConfigured می‌دهد.
  • در urls می‌توانید ویژگی‌ها را بدون زیرکلاس عوض کنید: TemplateView.as_view(template_name="pages/about.html").
  • DeleteView از Django 4.0 از FormMixin استفاده می‌کند؛ منطق قبل از حذف را در form_valid بنویسید، نه در delete() که دیگر برای POST صدا زده نمی‌شود.

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