[Fixed]-Django โ€“ How to show messages under ajax function

16๐Ÿ‘

โœ…

These are the tools/methods that helped me to solve the problem. First, I have a helper utility method called render_to_json:

# `data` is a python dictionary
def render_to_json(request, data):
    return HttpResponse(
        json.dumps(data, ensure_ascii=False),
        mimetype=request.is_ajax() and "application/json" or "text/html"
    )

I have a messages.html template to render the necessary html for the popup message(s):

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

When create a message in response to an AJAX request, I use Djangoโ€™s render_to_string to package the message(s) into a string that gets stored in a data dictionary, which then uses my render_to_json to return an appropriate response:

def my_custom_view(request)
    # ...  your view code
    data = {
        'msg': render_to_string('messages.html', {}, RequestContext(request)),
    }
    return render_to_json(request, data)

Then, in my jQuery $.post(...) callback function, I check to see if the response object has a msg attribute, and then insert the contents of response.msg into the DOM where I want it needs to be, with jQuery transitions if desired. My base.html template contains the <ul> container for the messages:

<ul id="popup-messages-content">
    {% include 'messages.html' %}
</ul>

Note that the above includes the messages.html for the case when you want to display messages on an actual page load (non-AJAX request) โ€“ it is blank if there are no messages, but the <ul> is still available to push AJAX-received messages into.

The last piece is the Javascript function (requires jQuery) I use in any $.post(...) callbacks to show the messages:

function showPopupMessage(content) {
    var elMessages = $('#popup-messages-content');
    if (elMessages.length && content) {
        elMessages.html(content);
    }
}
๐Ÿ‘คJames Addison

11๐Ÿ‘

You just create your messages as always and before sending the response you put them into a list of dicts:

django_messages = []

for message in messages.get_messages(request):
    django_messages.append({
        "level": message.level,
        "message": message.message,
        "extra_tags": message.tags,
})

Then you add any data and your messages and serialize it, e.g.:

data = {}
data['success'] = success
data['messages'] = django_messages

return HttpResponse(simplejson.dumps(data), content_type="application/json")

Finally at your ajax:

success: function(data){
    success = data.success;
    update_messages(data.messages);
    if (success){
        ...                                                                             
    }
},

And the update_messages function:

function update_messages(messages){
    $("#div_messages").html("");
    $.each(messages, function (i, m) {
        $("#div_messages").append("<div class='alert alert-"+m.level+"''>"+m.message+"</div>");
    }); 
}

It works perfectly and I found it very easily to implement.

Edit: There was a suggestion to change the last piece of code in order to use a mapping for the levels. This does not apply to version this answer was written for, since level was actually a string. In version 4.0 it is now a number, see docs here. This would be the new snippet. Thanks for the suggestion @codingfactory1:

function update_messages(messages){
    const levels = {20: 'info', 25: 'success', 30: 'warning', 40: 'error'}
    $("#div_messages").html("");
    $.each(messages, function (i, m) {
        $("#div_messages").append("<div class='alert alert-"+levels[m.level]+"''>"+m.message+"</div>");
    });
    
}
๐Ÿ‘คsteven2308

5๐Ÿ‘

Hereโ€™s a simple idea.

Add a placeholder for your messages in layout.html, this allows appending new messages in javascript:

<div id="messages">
{% for message in messages %}
    <div id="notice" align="center">
        {{ message }}
    </div>
{% endfor %}
</div>

Instead of:

{% for message in messages %}
    <div id="notice" align="center">
        {{ message }}
    </div>
{% endfor %}

In add.html, add another one like:

{% if messages %}
<ul class="hidden-messages" style="display:none">
    {% for message in messages %}
        <div id="notice" align="center">
            {{ message }}
        </div>
    {% endfor %}
</ul>
{% endif %}

And ajaxForm would look like:

$('#your_form_id').ajaxForm({
    success: function(responseText) {
        var newMessages = $(responseText).find('.hidden-messages').html();
        $('#messages').append(newMessages);
    },
});
๐Ÿ‘คjpic

1๐Ÿ‘

in template

<div id="ajax_message">
    {% if messages %}
        {% for message in messages %}
            <div class="alert alert-{{ message.tags }}  fade show">
                {{ message|safe }}
            </div>
        {% endfor %}
    {% endif %}
</div>

after ajax_send start function

$("#ajax_message").load("this_url #ajax_message");

or

function alert_message() {
    var pathname = window.location.pathname;
    $("#ajax_message").load(pathname+" #ajax_message");
}
๐Ÿ‘คSergey K

0๐Ÿ‘

How to do this when using django-bootstrap5 lib:

Make an AJAX get request to a messages view that simply returns a custom messages.html template.

Okay, I reviewed some of the work done so far on this problem, and I came up with a neat solution that is probably a duplicate of the above, however, my post is distinct from those in that I am specifically solving this problem for coders using django-bootstrap5. So if that is you, then read further and this may solve your problem.

Here <DefaultApp> is the default app that some IDEโ€™s create for a Django project. My current project is called AbstractSpacecraft and it has a subfolder called AbstractSpacecraft also, which contains settings.py and which I refer to as the default app.

<DjangoProject>/<DefaultApp>/javascript_tools.js:

window.messages_tag_id = "#django-messages-div";
window.messages_url = null;

function load_html_from_url(url, elem) {
    $.get(url, function(data, status) {
        elem.html(data);
    });   
}

function display_django_messages() {
    messagesDiv = $(window.messages_tag_id);
    messagesDiv.empty();
    load_html_from_url(window.messages_url, messagesDiv);
}

function post_string_to_url(data, url)
{
    $.ajax({
        type: 'POST',
        url: url,
        data: data,
        success: function(data, status, xhr) {         // Function( Anything data, String textStatus, jqXHR jqXHR )
            if ('msg' in data) {
                const msg = data['msg'];
                console.log(msg);
                display_django_messages();
            }
        },
        error : function(xhr, errmsg, err) {
            // Provide a bit more info about the error to the console:
            if (errmsg) {
                console.log('ERROR: ' + errmsg);
                display_django_messages();
            }
            console.log(xhr.status + ": " + xhr.responseText);             
        }
    });
}

function csrf_safe_method(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

function setup_ajax_csrf_token(csrf_token) { 
    // BUGFIX.  This took hours to get to work!  
    // And remember the csrf_token line at the top of template
    window.csrf_token = csrf_token;
    
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!csrf_safe_method(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
            }
        }
    });
}

`

<DjangoProject>/templates/<AppName>/your_template.html (only relevant parts):

<script>
    $(document).ready(function()
    {
        // Remember to call this!
        setup_ajax_csrf_token("{{ csrf_token }}");   

        let ui = window.ui;
            
        $("#diagram-name-form").submit((event) => {
            event.preventDefault();
            const data = {
                "diagram name" : $("#diagram-name-input").val(),
                "diagram id" : "{{ diagram_id }}",
            };
            const set_name_url = "{% url 'set_model_string' Model='Diagram' field='name'%}";
            
            post_string_to_url(data, set_name_url, "{% url 'messages' %}");
        });
    });        
    
</script>

 .....

<div class="form-outline">
    <form class="row row-cols-lg-auto g-3 align-items-center" method="post"
        id="diagram-name-form">
        {% csrf_token %}
    
        <input type="text" id="diagram-name-input" class="form-control" placeholder="Diagram name?" />
    </form>
</div>

<DjangoProject>/templates/<DefaultApp>/messages.html:

{% load django_bootstrap5 %}

{% autoescape off %}{% bootstrap_messages %}{% endautoescape %} 

<DjangoProject/<DefaultApp>/views.py:

from django.shortcuts import render
from django.views.generic.base import TemplateView, View

class MessagesView(View):
    template_name = 'AbstractSpacecraft/messages.html'

    def get(self, request, *args, **kwargs):
        return render(request, self.template_name)

<DjangoProject>/<DefaultApp>/base.html:

{% block bootstrap5_content %}
  {% block as_navbar %}  <!-- as = AbstractSpacecraft -->
   ....
  {% endblock %}
  
  <div class="container-fluid">
      <div class="row">
          <div class="col">
              <div class="row" id="django-messages-div">
                {% autoescape off %}{% bootstrap_messages %}{% endautoescape %}              
              </div>              
              {% block content %}{% endblock %}                
          </div>
      </div>
  </div>
{% endblock %}
....

Basically, after we receive the OK from the ajax POST request, we do an ajax GET request, it might work now that everything is working, but I was not able to get messagesDiv.load(url) to work, via viewing the Network tab in Chrome DevTools and under the traffic analyzer you should see a POST request followed by a GET request when everything is working.

The ajax GET simply gets an messages.html template that we then fill with the freshly known about messages. We make sure to clear the messageDiv first though so the messages canโ€™t stack up on the gui.

๐Ÿ‘คDaniel Donnelly

Leave a comment