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 messagesfrom django.shortcuts import redirect, renderdef 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.pyfrom django.contrib.messages import constants as message_constantsMESSAGE_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.pyMESSAGE_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
- Flask: hiển thị thông báo bằng flash() sau redirect trong 5 phút
- Django: viết form nhanh gấp đôi bằng ModelForm — khỏi map tay từng field
- Django: dùng ListView và DetailView thay hàm view lặp lại
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
- Django documentation — The messages framework: https://docs.djangoproject.com/en/6.0/ref/contrib/messages/
- Django documentation — MessageMiddleware: https://docs.djangoproject.com/en/6.0/ref/middleware/#django.contrib.messages.middleware.MessageMiddleware
