🎯 فصل ۳: ویوها و URLها
ویوها قلب اپلیکیشن جنگو هستن. اینجا منطق برنامهت رو مینویسی.
ویوها قلب اپلیکیشن جنگو هستن. اینجا منطق برنامهت رو مینویسی.
📑 فهرست این فصل
📦 Function-Based Views (FBV)
سادهترین نوع ویو، تابعی هست که یک request میگیره و یک HttpResponse برمیگردونه.
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, JsonResponse
from .models import Product
# سادهترین ویو
def home(request):
return HttpResponse("به فروشگاه ما خوش آمدید!")
# ویو با Template
def product_list(request):
products = Product.objects.filter(is_active=True)
return render(request, 'products/list.html', {
'products': products,
'title': 'لیست محصولات'
})
# ویو با پارامتر
def product_detail(request, product_id):
product = get_object_or_404(Product, id=product_id)
return render(request, 'products/detail.html', {'product': product})
# برگردوندن JSON
def product_json(request, product_id):
product = get_object_or_404(Product, id=product_id)
return JsonResponse({
'id': product.id,
'name': product.name,
'price': str(product.price)
})
اگه آبجکت پیدا بشه، برمیگردونه. اگه پیدا نشه، خودکار صفحه ۴۰۴ نمایش میده. خیلی پرکاربرده!
🏛️ Class-Based Views (CBV)
ویوهای کلاسمحور برای کارهای تکراری بسیار مناسباند. جنگو ویوهای آمادهای داره که کارت رو ساده میکنن.
ویوهای ژنریک پرکاربرد
| ویو | کاربرد |
|---|---|
ListView |
نمایش لیستی از آبجکتها |
DetailView |
نمایش جزئیات یک آبجکت |
CreateView |
ساخت آبجکت جدید با فرم |
UpdateView |
ویرایش آبجکت موجود |
DeleteView |
حذف آبجکت |
TemplateView |
نمایش یک قالب ساده |
مقایسه FBV و CBV
FBV (تابعی)
def product_list(request):
products = Product.objects.all()
return render(request, 'products/list.html', {
'products': products
})
CBV (کلاسی)
from django.views.generic import ListView
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
FBV برای منطق پیچیده و خاص بهتره (راحتتر میخونیش)
CBV برای کارهای استاندارد (CRUD) سریعتره
مثال کامل CBV
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Product
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 10 # صفحهبندی خودکار!
def get_queryset(self):
# فقط محصولات فعال
return Product.objects.filter(is_active=True).order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = 'لیست محصولات'
return context
class ProductDetailView(DetailView):
model = Product
template_name = 'products/detail.html'
context_object_name = 'product'
class ProductCreateView(CreateView):
model = Product
fields = ['name', 'description', 'price', 'stock']
template_name = 'products/form.html'
success_url = reverse_lazy('product_list')
class ProductUpdateView(UpdateView):
model = Product
fields = ['name', 'description', 'price', 'stock']
template_name = 'products/form.html'
success_url = reverse_lazy('product_list')
class ProductDeleteView(DeleteView):
model = Product
template_name = 'products/confirm_delete.html'
success_url = reverse_lazy('product_list')
🛣️ URL Routing
الان وقتشه که این ویوها رو به URLها وصل کنیم.
from django.urls import path
from . import views
app_name = 'products' # برای namespace
urlpatterns = [
# FBV
path('', views.product_list, name='list'),
path('product/<int:product_id>/', views.product_detail, name='detail'),
# CBV - متد as_view() مهمه
path('all/', views.ProductListView.as_view(), name='all'),
path('detail/<int:pk>/', views.ProductDetailView.as_view(), name='detail_cbv'),
path('create/', views.ProductCreateView.as_view(), name='create'),
path('update/<int:pk>/', views.ProductUpdateView.as_view(), name='update'),
path('delete/<int:pk>/', views.ProductDeleteView.as_view(), name='delete'),
]
📌 پارامترهای URL
سه نوع converter پرکاربرد در URLها داریم:
| Converter | پذیرش | مثال |
|---|---|---|
<int:id> |
عدد صحیح | /product/42/ |
<str:name> |
هر متنی بدون اسلش | /category/digital/ |
<slug:slug> |
متن slug | /post/my-first-post/ |
<uuid:uid> |
UUID | /order/abc-123/ |
<path:p> |
مسیر کامل با اسلش | /files/img/logo.png |
urlpatterns = [
path('product/<int:product_id>/', views.product_detail),
path('category/<slug:category_slug>/', views.category_view),
path('search/<str:keyword>/', views.search),
path('archive/<int:year>/<int:month>/', views.archive),
]
def product_detail(request, product_id): # نام پارامتر باید با URL یکی باشه
product = get_object_or_404(Product, id=product_id)
return render(request, 'products/detail.html', {'product': product})
def archive(request, year, month):
products = Product.objects.filter(
created_at__year=year,
created_at__month=month
)
return render(request, 'products/archive.html', {
'products': products,
'year': year,
'month': month
})
Query String (پارامترهای GET)
برای پارامترهای اختیاری مثل ?page=2&sort=price:
def search(request):
# دریافت پارامتر از query string
query = request.GET.get('q', '') # مقدار پیشفرض خالی
sort = request.GET.get('sort', '-created_at')
page = request.GET.get('page', 1)
products = Product.objects.filter(name__icontains=query).order_by(sort)
return render(request, 'products/search.html', {
'products': products,
'query': query
})
# URL درخواست: /search/?q=فرش&sort=price&page=2
🔄 ریدایرکت و Reverse
ریدایرکت کردن
from django.shortcuts import redirect
def add_to_cart(request, product_id):
# ... منطق اضافه کردن به سبد ...
# روش ۱: با نام URL
return redirect('products:list')
# روش ۲: با URL مستقیم
return redirect('/products/')
# روش ۳: با آبجکت (اگه get_absolute_url داشته باشه)
return redirect(product)
Reverse – تولید URL از نام
from django.urls import reverse
# تولید URL از روی نام
url = reverse('products:detail', args=[5])
# نتیجه: '/products/product/5/'
url = reverse('products:archive', kwargs={'year': 1403, 'month': 6})
# نتیجه: '/products/archive/1403/6/'
اگه روزی URLها رو تغییر دادی، نیاز نیست کل کد رو دنبال URLها بگردی! کافیه فقط فایل urls.py رو تغییر بدی.
get_absolute_url در مدل
from django.urls import reverse
class Product(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('products:detail', args=[self.id])
# حالا توی template:
# <a href="{{ product.get_absolute_url }}">{{ product.name }}</a>
📨 روشهای HTTP
وب با چند روش (HTTP Method) کار میکنه:
| Method | کاربرد |
|---|---|
GET |
دریافت اطلاعات (مثل دیدن صفحه) |
POST |
ارسال اطلاعات (مثل ثبت فرم) |
PUT/PATCH |
بهروزرسانی (بیشتر در API) |
DELETE |
حذف (بیشتر در API) |
def contact(request):
if request.method == 'POST':
# کاربر فرم رو ارسال کرده
name = request.POST.get('name')
email = request.POST.get('email')
message = request.POST.get('message')
# ذخیره یا ارسال ایمیل
# ...
return redirect('contact_success')
# درخواست GET - نمایش فرم خالی
return render(request, 'contact.html')
🎯 مثالهای واقعی
مثال ۱: صفحه درباره ما
from django.shortcuts import render
def about(request):
context = {
'company_name': 'ICSD',
'founded_year': 1399,
'team_size': 12,
}
return render(request, 'about.html', context)
مثال ۲: لیست محصولات با فیلتر و جستجو
from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Product, Category
def product_list(request):
products = Product.objects.filter(is_active=True)
# فیلتر بر اساس دسته
category_id = request.GET.get('category')
if category_id:
products = products.filter(category_id=category_id)
# جستجو
query = request.GET.get('q')
if query:
products = products.filter(name__icontains=query)
# مرتبسازی
sort = request.GET.get('sort', '-created_at')
products = products.order_by(sort)
# صفحهبندی - 12 محصول در هر صفحه
paginator = Paginator(products, 12)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'categories': Category.objects.all(),
'current_category': category_id,
'query': query,
}
return render(request, 'products/list.html', context)
مثال ۳: ویو AJAX برای افزودن به سبد خرید
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import login_required
from products.models import Product
from .models import CartItem
@login_required
@require_POST # فقط POST قابل قبوله
def add_to_cart(request):
try:
product_id = request.POST.get('product_id')
quantity = int(request.POST.get('quantity', 1))
product = Product.objects.get(id=product_id)
# اگه موجودی کافی نیست
if product.stock < quantity:
return JsonResponse({
'success': False,
'message': 'موجودی کافی نیست'
}, status=400)
# اضافه یا بهروزرسانی
cart_item, created = CartItem.objects.get_or_create(
user=request.user,
product=product,
defaults={'quantity': quantity}
)
if not created:
cart_item.quantity += quantity
cart_item.save()
# تعداد کل آیتمهای سبد
total_items = CartItem.objects.filter(
user=request.user
).count()
return JsonResponse({
'success': True,
'message': 'محصول به سبد اضافه شد',
'total_items': total_items
})
except Product.DoesNotExist:
return JsonResponse({
'success': False,
'message': 'محصول یافت نشد'
}, status=404)
except Exception as e:
return JsonResponse({
'success': False,
'message': str(e)
}, status=500)
📝 تمرینها
تمرین ۱
یه ویو بساز که عدد صحیح از URL بگیره و ضرب در ۲ رو نمایش بده. مثلاً /double/5/ بهت ۱۰ بده.
تمرین ۲
یه CBV از نوع ListView بساز که محصولات یک دسته خاص رو نمایش بده. URL باید چیزی شبیه /category/digital/ باشه.
تمرین ۳
یه ویو بساز که هم محتوای HTML برمیگردونه (در درخواست عادی) و هم JSON (وقتی پارامتر ?format=json داشته باشه).