~/icsd.ir — bash
SYSTEM_ONLINE

🎨 فصل ۴: قالب‌ها (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

myproject/settings.py
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',
            ],
        },
    },
]

📝 متغیرها در قالب

با دو سطح آکولاد { { ... } } متغیرها رو نمایش می‌دیم:

products/list.html
<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>
🔒

CSRF Token چیه؟

یه توکن امنیتی که جلوی حملات 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 }} محصول

🧬 ارث‌بری قالب (مهم!)

بهترین قابلیت قالب‌های جنگو! یک قالب پایه می‌سازی و بقیه قالب‌ها ازش ارث می‌برن.

قالب پایه

templates/base.html
<!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>

قالب فرزند

products/list.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 – استفاده از قطعات

برای استفاده از یک قالب کوچیک داخل قالب دیگه:

templates/partials/product_card.html
<div class="product-card">
    <h3>{{ product.name }}</h3>
    <p>{{ product.price }} تومان</p>
    <a href="{% url 'products:detail' product.id %}">مشاهده</a>
</div>
products/list.html
{% for product in products %}
    {% include 'partials/product_card.html' %}
{% endfor %}

{# با پاس دادن متغیر دیگه #}
{% include 'partials/product_card.html' with product=featured_product %}

📁 static و فایل‌های ثابت

برای استفاده از CSS، JS، تصاویر:

myproject/settings.py
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="لوگو">

🎯 مثال‌های کاربردی

آسان

مثال ۱: کارت محصول ساده

products/card.html
<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

products/list.html
<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>

پیشرفته

مثال ۳: قالب پایه کامل با همه قابلیت‌ها

templates/base.html
{% 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

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.