So first we need to edit the iommi.py file
from django.utils.html import escape
from django.utils.text import format_lazy
from django.utils.translation import gettext, gettext_lazy
from django.contrib import messages
class Form(iommi.Form):
class Meta:
# messages on save containing model verbose name and str(object)
# we use extra_evaluated, so they can be changed per Table if needed
extra_evaluated__message_created = lambda form, **kwargs: format_lazy(
"{model_verbose_name} <em>{{object_name}}</em> {text}",
model_verbose_name=form.model._meta.verbose_name.capitalize(),
text=gettext_lazy("created"),
)
extra_evaluated__message_saved = lambda form, **kwargs: format_lazy(
"{text1} {model_verbose_name} <em>{{object_name}}</em> {text2}",
model_verbose_name=form.model._meta.verbose_name,
text1=gettext_lazy("Changes of"),
text2=gettext_lazy("saved"),
)
@staticmethod
def extra__on_save(request, form, **kwargs):
if getattr(form.extra_evaluated, "disable_messages", False):
return
if form.extra.is_create:
msg = form.extra_evaluated.message_created
else:
msg = form.extra_evaluated.message_saved
messages.success(request, msg.format(object_name=escape(str(form.instance))))
@staticmethod
def post_validation(request, form, **kwargs):
if getattr(form.extra_evaluated, "disable_messages", False):
return
# message on invalid
if not form.is_valid():
messages.error(request, gettext("Changes not saved, invalid or missing values"))
Add a template filter to templatetags/bootstrap5.py
from django.contrib import messages
from django import template
register = template.Library()
@register.filter
def bs5_alert_type(message):
if message.level == messages.DEBUG:
return "primary"
elif message.level == messages.ERROR:
return "danger"
return messages.DEFAULT_TAGS[message.level]
Then create a static/js/messages.js with
function bs5_alert_type(type) {
if(type === "debug")
return "primary";
else if(type === "error")
return "danger";
return type
}
function bs5_alert_icon(type) {
let icon = null;
if(type === "debug" || type === "primary")
icon = "bi-shield";
else if(type === "info")
icon = "bi-shield";
else if(type === "success")
icon = "bi-shield-check";
else if(type === "warning")
icon = "bi-shield-exclamation";
else if(type === "error" || type === "danger")
icon = "bi-shield-x";
if(icon)
return '<i class="bi ' + icon + ' fs-2hx text-' + bs5_alert_type(type) + ' me-4"></i>';
return "";
}
// pop-up messages on an event
document.addEventListener("message.show", function(event) {
let messages_block = document.querySelector("#messages");
if(!messages_block) {
messages_block = document.createElement('div');
messages_block.id = "messages";
document.body.append(messages_block);
}
let type = bs5_alert_type(event.detail.type);
let message_element = document.createElement('div');
message_element.classList.add("row");
message_element.classList.add("one-message");
let message_element_content = "<div class=\"col col-md-auto\">";
message_element_content += " <div class=\"alert alert-" + type + " d-flex align-items-center p-5 mb-5 alert-dismissible fade show\" role=\"alert\">";
message_element_content += bs5_alert_icon(type);
message_element_content += " <div class=\"d-flex flex-column me-8\"><span>" + event.detail.message + "</span></div>";
message_element_content += " <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button>";
message_element_content += " </div>";
message_element_content += "</div>";
message_element.innerHTML = message_element_content;
messages_block.append(message_element);
if(type === "success") {
autoHideSuccessMessage(message_element);
}
});
function showMessage(message, type, element) {
if(!element) {
element = document;
}
element.dispatchEvent(
new CustomEvent("message.show", {
bubbles: true,
detail: {type: type, message: message},
})
);
}
// message close button
IommiBase.addLiveEventListener(
"click",
"#messages .one-message .btn-close",
function (event) {
let msg_block = this.closest(".one-message");
let messages_block = msg_block.closest("#messages");
msg_block.remove();
if(!messages_block.querySelectorAll('.one-message').length) {
messages_block.remove();
}
}
);
// success messages auto-close after 5s
function autoHideSuccessMessage(message_element) {
setTimeout(function() {
message_element.querySelector(".btn-close").dispatchEvent(new Event('click', { 'bubbles': true }));
}, 5000);
}
To your main.css add
#messages {
width: 100%;
position: fixed;
bottom: 0;
left: 0;
z-index: 900;
padding: 0 1rem;
}
@media (min-width: 768px) {
#messages {
width: auto;
bottom: 1rem;
left: 2.25rem;
margin-right: 2.25rem;
padding: 0;
}
}
Then add to your base template before </body>
{# load bootstrap5 at the beggining of the template #}
<script src="{% static 'js/messages.js' %}"></script>
<script>
{% for message in messages %}
showMessage("{{ message|safe }}", "{{ message|bs5_alert_type }}");
{% endfor %}
</script>
{{ message|safe }}
can be potentially dangerous, it's so I can use html inside messages (e.g.<em>{{object_name}}</em>
), but then I also do msg.format(object_name=escape(str(form.instance)))
.
And now you have nice colored popup messages with icons. Success messages disapear after 5s, errors stay until closed manually.