Django không có filter sẵn để format tiền Việt, nhưng bạn không cần format trong view hay model. Tạo một template filter tùy chỉnh bằng @register.filter, nạp bằng {% load %} rồi dùng ngay trong template với cú pháp {{ price|vnd }}. Cách này áp dụng khi cùng một kiểu định dạng lặp lại ở nhiều template và bạn muốn giữ logic hiển thị tách khỏi logic nghiệp vụ.
Vấn đề
Trong dự án bán hàng, giá tiền xuất hiện ở trang danh sách, trang chi tiết, giỏ hàng và email xác nhận. Nếu mỗi view tự format giá bằng f-string rồi truyền chuỗi đã format xuống template, bạn sẽ lặp lại cùng một đoạn code ở nhiều nơi. Khi đổi quy ước hiển thị, bạn phải sửa từng view một và rất dễ sót chỗ.
Một lỗi phổ biến khác là format ngay trong model, chẳng hạn thêm method formatted_price(). Cách này trộn logic hiển thị vào tầng dữ liệu, khiến model phình ra và khó tái sử dụng.
Giải pháp
Django cho phép định nghĩa filter riêng trong package templatetags của app. Tạo file shop/templatetags/shop_tags.py, nhớ tạo cả file __init__.py trong thư mục templatetags, nếu không Django sẽ không tìm thấy module:
from django import templateregister = template.Library()@register.filterdef vnd(value): try: return f"{int(value):,} đ".replace(",", ".") except (TypeError, ValueError): return value@register.filterdef shorten(value, length=50): text = str(value) return text if len(text) <= length else text[:length].rstrip() + "…"
Trong template, nạp thư viện rồi dùng filter như filter có sẵn của Django:
{% load shop_tags %}<p>Giá: {{ product.price|vnd }}</p><p>{{ product.description|shorten:60 }}</p>
Filter nhận giá trị ở phía trái dấu | làm đối số đầu tiên, tham số sau dấu hai chấm là đối số thứ hai. Nhờ đó shorten:60 chỉ là cách gọi hàm shorten(value, 60) trong template. Khi giá trị không phải số, filter vnd trả lại nguyên vẹn thay vì làm trang lỗi nên trang không bị lỗi 500 khi dữ liệu thiếu.
Áp dụng vào dự án của bạn
Bắt đầu với đúng một filter mà dự án đang lặp lại nhiều nhất, thường là format tiền hoặc ngày giờ, rồi mới mở rộng dần. Đặt tên filter ngắn gọn, mô tả đúng việc nó làm, và viết thêm một test render template để khóa hành vi định dạng lại. Khi nhóm đã quen, gom các filter dùng chung vào một app tiện ích riêng để mọi dự án đều dùng lại được mà không phải copy file.
Bài viết liên quan
- Django: tái sử dụng logic query bằng custom Manager — hết copy-paste filter() khắp dự án
- Django signals: tự động tạo Profile cho user mới bằng post_save trong 5 phút
- Django: tạo lệnh quản lý tùy chỉnh — seed dữ liệu và chạy tác vụ nền trong 5 phút
Câu hỏi thường gặp
Filter khác gì template tag?
Filter biến đổi một giá trị và luôn viết sau dấu |, ví dụ {{ price|vnd }}. Tag thực hiện một hành động phức tạp hơn, như lặp, rẽ nhánh hoặc render cả một đoạn template, và không bị giới hạn ở một giá trị đầu vào.
Quên file __init__.py trong templatetags thì sao?
Django sẽ không nhận module filter và template báo lỗi Invalid template library specified. Luôn tạo __init__.py trong cả thư mục app và thư mục templatetags, rồi khởi động lại server phát triển sau khi thêm file mới.
Filter có dùng được với giá trị None không?
Được, nhưng filter phải tự xử lý. Trong ví dụ trên, vnd bắt TypeError và ValueError rồi trả lại giá trị gốc, nên trang không bị lỗi 500 khi dữ liệu thiếu.
Có nên viết mọi logic hiển thị bằng filter không?
Không. Filter phù hợp với biến đổi nhỏ, không có tác dụng phụ và không truy vấn database. Logic nặng, cần nhiều dữ liệu hoặc có tác dụng phụ nên nằm ở view, còn filter chỉ lo phần trình bày cuối cùng.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Tài liệu Django: Custom template tags and filters — https://docs.djangoproject.com/en/6.1/howto/custom-template-tags/
