🎨 فصل ۴: قالبها (Templates)
اینجا میخوای با زبان قالب جنگو HTML پویا بسازی - با حلقه، شرط و فیلتر!
اینجا میخوای با زبان قالب جنگو HTML پویا بسازی – با حلقه، شرط و فیلتر!
📑 فهرست این فصل
📐 اصول قالبها
قالبهای جنگو فایلهای HTML معمولی هستن که سینتکس خاصی برای متغیرها و منطق دارن.
محل قرارگیری قالبها
دو روش برای سازماندهی قالبها وجود داره:
myproject/
├── products/
│ ├── templates/
│ │ └── products/ # یک پوشه به اسم اپ
│ │ ├── list.html
│ │ └── detail.html
│ └── views.py
├── templates/ # قالبهای مشترک
│ ├── base.html
│ └── partials/
│ ├── navbar.html
│ └── footer.html
└── settings.py
تنظیمات Templates
import os
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # ← این مهمه
'APP_DIRS': True, # ← قالبهای هر اپ رو هم میخونه
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
📝 متغیرها در قالب
با دو سطح آکولاد { { ... } } متغیرها رو نمایش میدیم:
<h1>{{ title }}</h1>
<p>سلام {{ user.username }}!</p>
<p>قیمت محصول: {{ product.price }} تومان</p>
<!-- دسترسی به متد -->
<p>مجموع: {{ order.get_total }}</p>
<!-- اندیس لیست/دیکشنری -->
<p>{{ items.0 }}</p>
<p>{{ user_dict.name }}</p>
در قالب جنگو از . برای دسترسی به attribute، متد، اندیس لیست و کلید دیکشنری استفاده میشه. خود جنگو میفهمه کدوم!
🏷️ تگهای قالب
تگها با { % ... % } نوشته میشن:
۱. شرط (if/elif/else)
{% if user.is_authenticated %}
<p>خوش آمدی {{ user.username }}!</p>
{% else %}
<a href="/login/">ورود</a>
{% endif %}
{% if product.stock > 0 %}
<span class="text-success">موجود</span>
{% elif product.stock == 0 %}
<span class="text-warning">ناموجود</span>
{% else %}
<span class="text-danger">لغو شده</span>
{% endif %}
{% if user.is_staff and product.is_featured %}
<span>مدیر - محصول ویژه</span>
{% endif %}
۲. حلقه (for)
<ul>
{% for product in products %}
<li>
{{ forloop.counter }}. {{ product.name }} - {{ product.price }}
</li>
{% empty %}
<li>هیچ محصولی یافت نشد!</li>
{% endfor %}
</ul>
متغیرهای مفید داخل حلقه:
| متغیر | کاربرد |
|---|---|
forloop.counter |
شماره از ۱ شروع |
forloop.counter0 |
شماره از ۰ |
forloop.first |
اولین آیتم؟ (True/False) |
forloop.last |
آخرین آیتم؟ |
forloop.revcounter |
شماره معکوس |
۳. URL
<!-- URL ساده -->
<a href="{% url 'home' %}">خانه</a>
<!-- URL با namespace -->
<a href="{% url 'products:list' %}">محصولات</a>
<!-- URL با پارامتر -->
<a href="{% url 'products:detail' product.id %}">
{{ product.name }}
</a>
<!-- یا با kwargs -->
<a href="{% url 'archive' year=1403 month=6 %}">بایگانی</a>
۴. CSRF Token (مهم برای فرمها!)
<form method="POST">
{% csrf_token %}
<input type="text" name="title">
<button type="submit">ارسال</button>
</form>
یه توکن امنیتی که جلوی حملات Cross-Site Request Forgery رو میگیره. هر فرم POST باید این رو داشته باشه، وگرنه جنگو خطای ۴۰۳ میده!
۵. comment
{# این یک کامنت یکخطی است #}
{% comment %}
این یک کامنت چندخطی
که توسط جنگو نادیده گرفته میشه
{% endcomment %}
🔧 فیلترها
فیلترها دادهها رو قبل از نمایش تغییر میدن. با | اعمال میشن:
| فیلتر | کاربرد | مثال |
|---|---|---|
upper |
حروف بزرگ | { { name|upper } } |
lower |
حروف کوچک | { { name|lower } } |
title |
اول هر کلمه بزرگ | { { name|title } } |
length |
طول | { { items|length } } |
default |
مقدار پیشفرض | { { name|default:"ناشناس" } } |
truncatechars |
کوتاه کردن متن | { { desc|truncatechars:50 } } |
truncatewords |
کوتاه کردن کلمات | { { desc|truncatewords:20 } } |
date |
فرمت تاریخ | { { obj.created|date:"Y/m/d" } } |
time |
فرمت زمان | { { obj.created|time:"H:i" } } |
floatformat |
فرمت اعداد اعشاری | { { price|floatformat:2 } } |
add |
جمع | { { num|add:5 } } |
safe |
HTML امن (بدون escape) | { { content|safe } } |
linebreaks |
تبدیل n به <br> | { { text|linebreaks } } |
pluralize |
جمع/مفرد | { { count } } آیتم{ { count|pluralize } } |
slice |
برش لیست | { { items|slice:":5" } } |
join |
پیوستن لیست | { { tags|join:", " } } |
زنجیرهای کردن فیلترها
{# میتونی چند فیلتر رو پشت سر هم استفاده کنی #}
{{ name|lower|truncatechars:20|default:"بدون نام" }}
{# نمایش تاریخ فارسی #}
{{ post.created_at|date:"Y/m/d - H:i" }}
{# تعداد محصولات با کلمهبندی #}
{{ products|length }} محصول
🧬 ارثبری قالب (مهم!)
بهترین قابلیت قالبهای جنگو! یک قالب پایه میسازی و بقیه قالبها ازش ارث میبرن.
قالب پایه
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<title>{% block title %}سایت من{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
{% include 'partials/navbar.html' %}
</header>
<main class="container">
{% block content %}
<!-- محتوای پیشفرض -->
{% endblock %}
</main>
<footer>
{% include 'partials/footer.html' %}
</footer>
<script src="{% static 'js/script.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
قالب فرزند
{% extends 'base.html' %}
{% load static %}
{% block title %}لیست محصولات | سایت من{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/products.css' %}">
{% endblock %}
{% block content %}
<h1>محصولات</h1>
<div class="row">
{% for product in products %}
<div class="col-md-4">
<h3>{{ product.name }}</h3>
<p>{{ product.price }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'js/products.js' %}"></script>
{% endblock %}
{ % extends % } باید اولین خط فایل باشه (قبل از هر چیز دیگهای حتی فاصله!)
🧩 include – استفاده از قطعات
برای استفاده از یک قالب کوچیک داخل قالب دیگه:
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.price }} تومان</p>
<a href="{% url 'products:detail' product.id %}">مشاهده</a>
</div>
{% for product in products %}
{% include 'partials/product_card.html' %}
{% endfor %}
{# با پاس دادن متغیر دیگه #}
{% include 'partials/product_card.html' with product=featured_product %}
📁 static و فایلهای ثابت
برای استفاده از CSS، JS، تصاویر:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'static', # برای فایلهای مشترک
]
# مسیر گردآوری برای production
STATIC_ROOT = BASE_DIR / 'staticfiles'
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<script src="{% static 'js/main.js' %}"></script>
<img src="{% static 'images/logo.png' %}" alt="لوگو">
🎯 مثالهای کاربردی
مثال ۱: کارت محصول ساده
<div class="card">
{% if product.image %}
<img src="{{ product.image.url }}" alt="{{ product.name }}">
{% else %}
<img src="{% static 'images/no-image.png' %}" alt="بدون تصویر">
{% endif %}
<div class="card-body">
<h5>{{ product.name }}</h5>
<p>{{ product.description|truncatewords:20 }}</p>
<div class="price">
{{ product.price|floatformat:0 }} تومان
</div>
{% if product.stock > 0 %}
<span class="badge bg-success">موجود</span>
{% else %}
<span class="badge bg-danger">ناموجود</span>
{% endif %}
<a href="{% url 'products:detail' product.id %}" class="btn btn-primary">
مشاهده
</a>
</div>
</div>
مثال ۲: صفحهبندی Pagination
<nav aria-label="صفحهبندی">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
« قبلی
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if num == page_obj.number %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
بعدی »
</a>
</li>
{% endif %}
</ul>
</nav>
مثال ۳: قالب پایه کامل با همه قابلیتها
{% load static %}
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}سایت من{% endblock %}</title>
<meta name="description" content="{% block description %}توضیحات پیشفرض{% endblock %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.rtl.min.css' %}">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
{# پیامهای جنگو #}
{% if messages %}
<div class="messages container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% include 'partials/navbar.html' %}
<main>
{% block breadcrumb %}{% endblock %}
{% block content %}{% endblock %}
</main>
{% include 'partials/footer.html' %}
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
📝 تمرینها
تمرین ۱
یه قالب بساز که لیست ۱۰ شهر رو با شماره و حرف بزرگ نمایش بده. (راهنمایی: forloop.counter و فیلتر upper)
تمرین ۲
یه قالب پایه بساز که شامل navbar، فوتر و block content باشه. سپس دو قالب فرزند ازش ارث ببرن.
تمرین ۳
یه فیلتر سفارشی بنویس که عدد رو با جداکننده هزارگان فارسی نمایش بده (۱,۲۳۴,۵۶۷ تومان). راهنما: در پوشه templatetags