The solution for this is contained in this question and the answer by Terry. Plus some other info about handling the CSRF protection in Django.
The short answer is to override the regular form submit with event.preventDefault()
in your own onsubmit
function for each form. Then do the POST asynchronously with JQuery. This avoids the server having to return an HttpResponse with status 204, or anything really, beyond an empty JsonResponse.
There are several forms on my web page so I don't have a single function assigned to each button, like in the above linked answer, but several that change individual button behaviour. Also for Django there's the added consideration of including the CSRF token in the POST request.
So, for example, some html with two buttons;
<form id="form-a" onsubmit="submit_form_a(event)">{% csrf_token %}
<input type="hidden" name="button-a" value="some_value_for_a">
<button type="submit" class="w3-button">Button A</button>
</form>
<form id="form-z" onsubmit="submit_form_z(event)">{% csrf_token %}
<input type="hidden" name="button-z" value="some_value_for_z">
<button type="submit" class="w3-button">Button Z</button>
</form>
Then Javascript, where the first form submit callback, for example, disables the button;
function submit_form_a(e){
e.preventDefault();
var url = "/main/a"
var formData = $(e.target).serialize();
var btn = $(e.target).find("button");
btn.prop("disabled", true);
myPost(url, formData);
}
And the second submit callback that does some animation, using the excellent W3.CSS;
function submit_form_z(e){
e.preventDefault();
var url = "/main/z"
var formData = $(e.target).serialize();
var btn = $(e.target).find("button");
btn.toggleClass("w3-animate-fading").toggleClass("w3-text-red")
myPost(url, formData);
}
Both of those scripts call a common script mypost
, which sets up the XMLHttpResponse header with the CSRF token, then POSTs it;
// Make sure any AJAX POST requests are not going off-site. Prevents
// leaking the CSRF token.
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); }
function myPost(url, data) {
var csrftoken = $("[name=csrfmiddlewaretoken]").val();
// Set the header on the AJAX POST request with the CSRF token
$.ajaxSetup({beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}});
$.post(url, data);
}
Back at the Django views.py
side of things, look at the <input>
data from the form and do what you need to do. I only just discovered on iOS the buttons themselves don't get included in the POST form data, like they seem to with other platforms, making hidden text input elements necessary;
if request.is_ajax() and request.method == 'POST':
if 'button-a' in request.POST:
# do something for A
elif 'button-z' in request.POST:
# do something for Z
return JsonResponse({})
And voila! Buttons can be clicked, background stuff happens and the page doesn't move. You could return something other than an empty JSON response and handle it back in the form callbacks, such as re-enabling the button, but I don't need that. See earlier linked answer for details there.