I recently got a form working via jQuery and Django. This was not easy for me to do and I thought I'd record my finding here.
The form submits via jQuery and the "form" plugin. Please visit jQuery's home page to find all those links.
This code handles: * urls.py -- passing both normal and 'Ajax' urls to a view.
-
views.py -- Handling both kinds of requests so that both normal and ajax submits will work.
-
The HTML template with the script for submitting and some bling.
Error handling
I like to stay DRY so the idea of checking the form for errors in javascript and checking it in Django irks me. I decided to leave that up to Django, so the form submits and gets validated on the server. The error messages are sent back to the browser and then displayed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | Mixed code -- Sorry about that.
urls.py
===
#Direct to template: For non-ajax form.
(r'afterform/$', 'django.views.generic.simple.direct_to_template', {"template": 'contact/afterform.html',"extra_content":{"error":False} }),
#Handle contact form. Using a named url: Note use in template below.
#This url handles contact/ and contact/xhr : The 'xhr' is a flag to tell the
#view that this is an ajax POST. I can't recall what it stood for :)
url(r'contact/(?P<xhr>.*)$', 'scents.contact.views.contactForm',name='contactform'),
views.py
===
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django import forms
from django.template import RequestContext
from django.utils import simplejson
# This describes our "form" - on the server side.
class ContactForm( forms.Form ):
subject = forms.CharField(label = "Subject",max_length=80)
text = forms.CharField(label = "Your query",widget=forms.Textarea)
class Media:
# Plug in the javascript we will need:
js = ("/media/js/jquery/jquery-1.2.6.pack.js", "/media/js/jquery/plugins/form/jquery.form.js")
# The main view:
def contactForm( request, xhr="WTF?" ): #The xhr default is being ignored. Weird.
# Is this a POST?
if request.method == "POST":
form = ContactForm(request.POST)
#Check if the <xhr> var had something passed to it.
if xhr=="xhr":
# Yup, this is an Ajax request.
# Validate the form:
clean = form.is_valid()
# Make some dicts to get passed back to the browser
rdict = {'bad':'false'}
if not clean:
rdict.update({'bad':'true'})
d={}
# This was painful, but I can't find a better way to extract the error messages:
for e in form.errors.iteritems():
d.update({e[0]:unicode(e[1])}) # e[0] is the id, unicode(e[1]) is the error HTML.
# Bung all that into the dict
rdict.update({'errs': d })
# Make a json whatsit to send back.
json = simplejson.dumps(rdict, ensure_ascii=False)
# And send it off.
return HttpResponse( json, mimetype='application/javascript')
# It's a normal submit - non ajax.
else:
if form.is_valid():
# Move on to an okay page:
return HttpResponseRedirect("/scents/afterform/")
else:
# It's not post so make a new form
form = ContactForm()#error_class=DivErrorList)
# Get it rollin:
return render_to_response(
'contact/contact.html',
{
"form":form,
},
context_instance=RequestContext(request)
)
contact.html (The template)
===
{% extends "base.html" %}
{% block J%}{{form.media}}{% endblock %}
{% block CSS %}forms.css{%endblock%}
{% block JQUERY %}
// prepare the form when the DOM is ready
$(document).ready(function() {
// prepare Options Object
var options = {
url: '{% url contactform "xhr"%}', // Here we pass the xhr flag
dataType: 'json',
success: processJson, //What to call after a reply from Django
beforeSubmit: beforeForm
};
// bind form using ajaxForm
$('#tf').ajaxForm(options); //My form id is 'tf'
});
function beforeForm() {
$('#bt').attr("disabled","disabled"); //Disable the submit button - can't click twice
$('.errorlist').remove(); //Get rid of any old error uls
$('#emsg').fadeOut('slow'); //Get rid of the main error message
return true; //Might not need this...
}
#Do stuff with server reply:
function processJson(data) {
// This is the first time I have touched jQuery, so I'm not sure about my
// approach. It does work, but perhaps not in the best way.
//Do we have any data at all?
if (data) {
// Build a var. NOTE: Make sure your id name (this is a div) is NOT THE SAME
// as any var in javascript -- ie has a fit and barfs errors.
e_msg = "We received your form, thank you.";
// Did we set the 'bad' flag in Django?
// Use eval() to turn the stuff in the data object into actual vars.
if (eval(data.bad)) {
e_msg = "Please check your form.";
errors = eval(data.errs); //Again with the eval :)
// This is very nice: Go thru the errors, build an id name and
// then access that tag and add the HTML from the Django error
$.each(errors,function(fieldname,errmsg)
{
id = "#id_" + fieldname;
$(id).parent().before( errmsg ); //I want the error above the <p> holding the field
});
// re-enable the submit button, coz user has to fix stuff.
$('#bt').attr("disabled","");
}
//Show the message
$('#emsg').text( e_msg ).fadeIn("slow");
} else {
//DON'T PANIC :D
$('#emsg').text("Ajax error : no data received. ").fadeIn("slow");
}
}
{% endblock %}
{% block CONTENT %}
<div id="emsg"> </div>
<form action="{% url contactform None %}" method="post" id="tf">
<fieldset>
{{ form.as_p }}
<input class="input" type="submit" value="Send mail" id="bt"/>
</fieldset>
</form>
{% endblock %}
Conclusion
===
Right, that's it. I hope. If I didn't make any horrible mistakes in writing this small tut then you should be able to reproduce the effect.
HTH
\d
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 4 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
When I try this on IE8, it prompts me to download the AJAX response!
Works on FF, Chrome etc - anything I could be doing wrong?
#
The mime type is incorrect for JSON responses. It should not be application/javascript, but application/json.
Also, the use of eval for JSON parsing is a Very Bad Thing. Use the JSON Javascript library at https://github.com/douglascrockford/JSON-js
I don't think you need the painful method of getting the error message but can probably use this (untested): if not clean: rdict.update({'bad':'true'}) rdict.update({'errs': dict(form.errors.iteritems()) })
(I don't think you need unicode for this. If you do, then use a list comprehension).
#
This snippet does not submit the form without ajax if there are no errors, correct me if I am wrong.
#
"btaylordesign" is right about xhr flag, since you can get the type of the request from HttpRequestHeader, request.is_ajax() does what you need with xhr flag. So the url regex for capturing the "xhr" parameter is also not necessary. For newbies like me, this snippet is pointing out a nice way to deal with default form errors while submitting the form via ajax. The basic idea is returning a json object to the client side then check if any errors exist. If there is not your view should create the object then return a json object with 'bad':false which states your form is submitted successfully and related objects are created on server side, so it is kind of a success message for you to evaluate on client side. You can either show errors if 'bad':true or do whatever you need to do on a successful callback if 'bad':false.
#
Please login first before commenting.