قالب فارسی بدون تکرار کد
در هر سایت ایرانی سه نیاز نمایشی همیشگی داریم: تاریخ شمسی، ارقام فارسی و مبلغ به تومان با جداکنندهی هزارگان. جای این منطق نه در مدل است نه در View؛ جایش فیلتر قالب است. فیلترها و تگهای سفارشی در پوشهی templatetags یک اپ قرار میگیرند (با __init__.py) و نام فایل، نام کتابخانه برای {% load %} است.
# apps/core/templatetags/persian.py
import datetime
import jdatetime
from django import template
from django.utils import timezone
register = template.Library()
FA_DIGITS = str.maketrans("0123456789", "۰۱۲۳۴۵۶۷۸۹")
MONTHS = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور",
"مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"]
@register.filter
def fa_digits(value):
return str(value).translate(FA_DIGITS)
@register.filter
def jdate(value, fmt="%Y/%m/%d"):
if not value:
return ""
if isinstance(value, datetime.datetime):
if timezone.is_aware(value):
value = timezone.localtime(value) # UTC به وقت تهران
j = jdatetime.datetime.fromgregorian(datetime=value)
else:
j = jdatetime.date.fromgregorian(date=value)
return j.strftime(fmt).translate(FA_DIGITS)
@register.filter
def jdate_long(value):
if not value:
return ""
if isinstance(value, datetime.datetime):
value = timezone.localtime(value).date()
j = jdatetime.date.fromgregorian(date=value)
return f"{j.day} {MONTHS[j.month - 1]} {j.year}".translate(FA_DIGITS)
@register.filter
def toman(rial):
try:
return f"{int(rial) // 10:,}".replace(",", "٬").translate(FA_DIGITS) + " تومان"
except (TypeError, ValueError):
return ""
@register.simple_tag(takes_context=True)
def active(context, url_name):
match = context["request"].resolver_match
return "active" if match and match.view_name == url_name else ""
{% load persian %}
<p>تاریخ ثبت: {{ order.created_at|jdate:"%Y/%m/%d %H:%M" }}</p>
<p>تحویل: {{ order.delivery_date|jdate_long }}</p> {# ۱۵ بهمن ۱۴۰۴ #}
<p>مبلغ: {{ order.total|toman }}</p> {# ۱۲٬۵۰۰٬۰۰۰ تومان #}
<a class="{% active 'orders:list' %}" href="{% url 'orders:list' %}">سفارشها</a>
inclusion_tag: یک تکهی قالب قابلاستفادهی مجدد
@register.inclusion_tag("orders/_status_badge.html")
def status_badge(order):
colors = {"pending": "warning", "paid": "info", "shipped": "success", "cancelled": "secondary"}
return {"label": order.get_status_display(), "color": colors.get(order.status, "light")}
نکتههایی که کمتر کسی میداند
- بعد از ساختن پوشهی templatetags باید runserver را از نو اجرا کنید؛ کتابخانههای قالب فقط هنگام شروع ثبت میشوند و تا آن موقع خطای «is not a registered tag library» میگیرید.
- اگر کتابخانهای را در همهی قالبها لازم دارید، آن را در
TEMPLATES["OPTIONS"]["builtins"] = ["apps.core.templatetags.persian"]بگذارید تا دیگر{% load %}لازم نباشد. - تبدیل تاریخ aware به شمسی بدون
timezone.localtimeزمان UTC را نشان میدهد؛ سفارش ساعت ۲ بامداد تهران در روز «قبل» نمایش داده میشود. - جداکنندهی هزارگان فارسی کاراکتر «٬» (U+066C) است نه ویرگول لاتین؛ در متن راستبهچپ ویرگول لاتین گاهی جای عدد را بههم میریزد.
- فیلترها را برای ورودی نامعتبر مقاوم بنویسید (مثل try در toman)؛ خطای یک فیلتر کل صفحه را با 500 از کار میاندازد.