فصل ۴: View، URL و Template

View تابعی: request، render، redirect، JsonResponse و get_object_or_404

ساده‌ترین شکل View: یک تابع

View تابعی (FBV) یک HttpRequest می‌گیرد و یک HttpResponse برمی‌گرداند. همین. هر چیز دیگری — قالب، ریدایرکت، JSON — فقط انواع مختلف پاسخ است. FBV صریح و خواناست و برای منطق‌های غیرمعمول اغلب از CBV بهتر است.

آنچه در request دارید

ویژگیمحتوا
request.method"GET"، "POST" و…
request.GET / request.POSTQueryDict پارامترها؛ getlist() برای چندمقداری
request.FILESفایل‌های آپلودی
request.userکاربر فعلی یا AnonymousUser
request.headersهدرها؛ غیرحساس به بزرگی حروف
request.sessionدیکشنری Session
request.META["REMOTE_ADDR"]IP (پشت پراکسی: IP خود پراکسی!)
# apps/orders/views.py
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POST

from apps.catalog.models import Carpet
from .models import Order


@login_required
def order_detail(request, pk):
    order = get_object_or_404(
        Order.objects.select_related("customer").prefetch_related("items__carpet"),
        pk=pk,
        customer__user=request.user,          # جلوگیری از دیدن سفارش دیگران
    )
    return render(request, "orders/detail.html", {"order": order})


@login_required
@require_POST
def cancel_order(request, pk):
    order = get_object_or_404(Order, pk=pk, customer__user=request.user)
    if order.status != Order.Status.PENDING:
        messages.error(request, "فقط سفارش در انتظار پرداخت قابل لغو است.")
    else:
        order.status = Order.Status.CANCELLED
        order.save(update_fields=["status"])
        messages.success(request, f"سفارش {order.tracking_code} لغو شد.")
    return redirect(order)                    # از get_absolute_url استفاده می‌کند


def carpet_price(request, code):
    carpet = get_object_or_404(Carpet, code=code, status="active")
    return JsonResponse({"code": carpet.code, "name": carpet.name, "price": carpet.price},
                        json_dumps_params={"ensure_ascii": False})

الگوی POST/Redirect/GET

بعد از هر POST موفق همیشه redirect کنید، نه render. اگر render کنید و کاربر صفحه را رفرش کند، مرورگر فرم را دوباره می‌فرستد و سفارش دو بار ثبت یا لغو می‌شود. پیام موفقیت را هم با messages به صفحه‌ی بعد منتقل کنید.

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

  • شرط customer__user=request.user در get_object_or_404 جلوی آسیب‌پذیری IDOR را می‌گیرد: کاربری که عدد آدرس را عوض کند به‌جای سفارش دیگران 404 می‌بیند، نه 403 که وجود سفارش را لو بدهد.
  • بدون ensure_ascii=False متن فارسی در JsonResponse به‌صورت ک... می‌رود؛ از نظر فنی درست است اما در دیباگ و لاگ خوانا نیست.
  • JsonResponse به‌طور پیش‌فرض فقط dict می‌پذیرد؛ برای برگرداندن لیست باید safe=False بدهید.
  • ترتیب دکوراتورها مهم است: login_required بیرونی باشد تا کاربر مهمان به صفحه‌ی ورود برود، نه این‌که اول خطای 405 بگیرد.
  • redirect() هم شیء مدل، هم نام url با آرگومان (redirect("orders:detail", pk=5)) و هم آدرس خام را می‌پذیرد؛ برای ریدایرکت دائمی permanent=True بدهید.

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