Người dùng vừa bấm Lưu, form lưu xong, trình duyệt redirect sang trang danh sách — làm sao báo cho họ biết đã thành công? Django có sẵn messages framework: chỉ cần một dòng messages.success(request, “Đã lưu thành công.”) trong view, thông báo sẽ tự lưu vào session hoặc cookie, hiển thị đúng một lần ở request tiếp theo rồi tự biến mất. Không cần tự quản lý session hay nhét cờ vào URL.

Vấn đề

Pattern chuẩn sau khi xử lý form thành công là Post/Redirect/Get: view xử lý POST rồi redirect, tránh gửi lại form khi người dùng F5. Nhưng redirect tạo ra một request hoàn toàn mới — mọi biến bạn gán trong view cũ đều biến mất, nên không còn cách nào “truyền” thông báo sang trang đích bằng biến thông thường.

Nhiều người chữa cháy bằng cách nhét cờ vào URL, kiểu redirect(‘/?saved=1’), rồi kiểm tra request.GET trong template. Cách này để lại URL xấu, người dùng có thể bookmark nhầm link kèm cờ, và mỗi loại thông báo lại cần thêm một tham số mới. Cách khác là tự ghi request.session[‘notice’] rồi xóa tay sau khi hiển thị — quên xóa thì thông báo hiện mãi, xóa sớm thì chưa kịp hiện.

def create_post(request):
form = PostForm(request.POST)
if form.is_valid():
form.save()
request.session["notice"] = "Đã lưu bài viết thành công."
return redirect("post-list")
return render(request, "blog/post_form.html", {"form": form})
{# base.html — phải nhớ xóa tay, quên là hiện mãi #}
{% if request.session.notice %}
<p>{{ request.session.notice }}</p>
{% endif %}

Giải pháp

Messages framework sinh ra cho bài toán này và đã bật sẵn trong project tạo bằng django-admin startproject: django.contrib.messages trong INSTALLED_APPS, MessageMiddleware trong MIDDLEWARE, và context processor django.contrib.messages.context_processors.messages để template nào cũng thấy biến messages. Bạn không cần cài thêm gì.

Trong view, sau khi lưu form xong, gọi một dòng rồi redirect như bình thường:

from django.contrib import messages
from django.shortcuts import redirect, render
def create_post(request):
form = PostForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Đã lưu bài viết thành công.")
return redirect("post-list")
return render(request, "blog/post_form.html", {"form": form})

Mỗi mức độ có hàm riêng: debug, info, success, warning, error. Trong base template, duyệt biến messages một lần duy nhất — vòng lặp này cũng “tiêu thụ” thông báo, nên nó chỉ hiện đúng một request:

{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}

Muốn đổi class CSS theo mức độ — ví dụ map error sang danger của Bootstrap — khai báo MESSAGE_TAGS trong settings. Cần class riêng cho từng thông báo thì truyền extra_tags. Viết code dùng chung cho project chưa chắc đã bật messages thì thêm fail_silently=True để tránh lỗi.

# settings.py
from django.contrib.messages import constants as message_constants
MESSAGE_TAGS = {message_constants.ERROR: "danger"}
messages.add_message(
request, messages.INFO, "Bài viết sẽ hiển thị sau 5 phút.",
extra_tags="delayed", fail_silently=True,
)

Mặc định Django dùng FallbackStorage: thử cookie trước, thông báo quá lớn thì chuyển sang session. Muốn ép chỉ dùng session, đặt MESSAGE_STORAGE trong settings.

# settings.py
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"

Áp dụng vào dự án của bạn

Đặt block hiển thị messages vào base.html để mọi trang đều có thông báo, khỏi lặp lại ở từng template. Quy ước mức độ ngay từ đầu: success cho tạo/sửa/xóa thành công, error khi form lỗi hoặc thao tác thất bại, warning trước hành động nguy hiểm như xóa dữ liệu, info cho ghi chú không khẩn. Khi form lỗi, gọi messages.error trong nhánh else của form.is_valid() rồi render lại form — không redirect, vì lúc này chưa cần Post/Redirect/Get.

Bài viết liên quan

Câu hỏi thường gặp

Thông báo hiển thị mấy lần?

Đúng một lần. Vòng lặp {% for message in messages %} trong template sẽ tiêu thụ toàn bộ thông báo, nên sang request sau chúng tự biến mất.

Project cũ không có messages thì phải làm gì?

Thêm django.contrib.messages vào INSTALLED_APPS, MessageMiddleware vào MIDDLEWARE (sau SessionMiddleware và AuthenticationMiddleware), và context processor vào TEMPLATES. Ba dòng này đã có sẵn trong mọi project startproject.

Dùng messages với API JSON được không?

Không nên — messages sinh ra cho trang render bằng template. Với API, hãy trả thông báo trong JSON response để client tự hiển thị.

Làm sao để thông báo tồn tại qua nhiều request?

Mặc định thì không — messages chỉ sống một request. Nếu thật sự cần, hãy tự lưu vào session hoặc database, nhưng đó là dấu hiệu bạn đang cần một hệ thống notification riêng, không phải flash message nữa.

💬 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