Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
223 lines (187 sloc) 19.7 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ajax and Django Views [ brack3t ]</title>
<meta name="description" content="">
<meta name="author" content="Brack3t, aka Kenneth Love and Chris Jones">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href=".././feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="brack3t ATOM Feed">
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le styles -->
<link href="http://fonts.googleapis.com/css?family=Exo:200,300,500,700,900,200italic,300italic,500italic,700italic,900italic" rel="stylesheet">
<link href=".././brack3t-theme/assets/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href=".././brack3t-theme/assets/github.css" rel="stylesheet">
<link href=".././brack3t-theme/assets/bootstrap/css/brack3t.css" rel="stylesheet">
<script>
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-4642386-4"]);
_gaq.push(["_trackPageview"]);
(function() {
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<script type="text/javascript">
var disqus_identifier = "ajax-and-django-views.html";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://brack3t.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</head>
<body>
<div class="container">
<div class="row-fluid">
<div class="span5">
<header id="logo" role="banner">
<h1><a href="/">Brack3t</a></h1>
<h6>Two guys... and Python.</h6>
</header>
</div>
<aside class="span2" id="sidebar" role="complementary">
<nav>
<ul class="unstyled">
<li><a href=".././pages/about-us.html">About</a></li>
<li><a href=".././pages/projects.html">Projects</a></li>
<li><a href=".././archives.html">Archives</a></li>
<li><a href=".././tags.html">Tags</a></li>
</ul>
</nav>
</aside>
</div>
<div class="row-fluid">
<div class="span7" id="main" role="main">
<article>
<header>
<h1><span class="slabtext">Ajax and Django Views</span></h1>
<h6><span class="permalink">Published: <a href=".././ajax-and-django-views.html">03-14-2012</a></span>
<span class="author">by <strong>Chris and Kenneth</strong></span>
<span class="tags">tags: <a href=".././tag/django.html">django</a> <a href=".././tag/ajax.html">ajax</a> <a href=".././tag/jquery.html">jquery</a> </span>
</h6>
</header>
<p>It seems that cleanly and easily doing AJAX views in Django is an area that gives a lot of people trouble. We like to do views as straight HTTP if at all possible, but there are always interactions that would be better served by <em>not</em> having a page load. We're also big fans of <a class="reference external" href="http://tastypieapi.org">django-tastypie</a> but it's a whole other ball of wax on its own, especially if you want to have views that write to the database. So, for the purposes of getting everyone up to speed doing AJAX with Django, we'll ignore Tastypie for now and just stick with ordinary views.</p>
<div class="section" id="django-automatic-csrf">
<h2>Django automatic CSRF</h2>
<p>To start things off, put <a class="reference external" href="https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax">this bit of Javascript</a> from the Django docs into a script that's loaded on all the pages where you'll be needing to perform AJAX views. This allows you to ignore the CSRF token for AJAX views. While this sounds more dangerous, the same-origin policy of AJAX requests should provide a decent amount of protection already. If you're really concerned, don't use this script and fetch a new CSRF token every time you submit a form, basically rebuilding the HTML form after every submission.</p>
</div>
<div class="section" id="ajax-form-field-errors">
<h2>AJAX &amp; Form Field Errors</h2>
<p>Before we get to the Django side, there are a few small scripts that we recycle on every project.</p>
<div class="highlight"><pre><span class="kd">function</span> <span class="nx">apply_form_field_error</span><span class="p">(</span><span class="nx">fieldname</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">input</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#id_&quot;</span> <span class="o">+</span> <span class="nx">fieldname</span><span class="p">),</span>
<span class="nx">container</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#div_id_&quot;</span> <span class="o">+</span> <span class="nx">fieldname</span><span class="p">),</span>
<span class="nx">error_msg</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;&lt;span /&gt;&quot;</span><span class="p">).</span><span class="nx">addClass</span><span class="p">(</span><span class="s2">&quot;help-inline ajax-error&quot;</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="nx">error</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="nx">container</span><span class="p">.</span><span class="nx">addClass</span><span class="p">(</span><span class="s2">&quot;error&quot;</span><span class="p">);</span>
<span class="nx">error_msg</span><span class="p">.</span><span class="nx">insertAfter</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">clear_form_field_errors</span><span class="p">(</span><span class="nx">form</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">&quot;.ajax-error&quot;</span><span class="p">,</span> <span class="nx">$</span><span class="p">(</span><span class="nx">form</span><span class="p">)).</span><span class="nx">remove</span><span class="p">();</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">&quot;.error&quot;</span><span class="p">,</span> <span class="nx">$</span><span class="p">(</span><span class="nx">form</span><span class="p">)).</span><span class="nx">removeClass</span><span class="p">(</span><span class="s2">&quot;error&quot;</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>These two functions are pretty self-explanatory, but I'll go over them anyway. The first, <tt class="docutils literal">apply_form_field_error</tt>, takes a field name and an error, finds the field on the page and sets the error text to the passed-in error. Our selectors here take advantage of how <a class="reference external" href="http://twitter.github.com/bootstrap">Twitter Bootstrap</a> renders forms, so you may need to change the selectors to match your own markup. The second, <tt class="docutils literal">clear_form_field_errors</tt>, removes the above text and classes, basicaly resetting the form. It's important to run this script before every AJAX submission so you don't get doubled-up errors if someone submits invalid data on the same field twice. You could add it to jQuery's <tt class="docutils literal">beforeSend</tt> if you don't want to have to think about it.</p>
<p>There are a couple more useful utility functions that we thought might be useful for some people.</p>
<div class="highlight"><pre><span class="kd">function</span> <span class="nx">django_message</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="nx">level</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">levels</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">warning</span><span class="o">:</span> <span class="s1">&#39;alert&#39;</span><span class="p">,</span>
<span class="nx">error</span><span class="o">:</span> <span class="s1">&#39;error&#39;</span><span class="p">,</span>
<span class="nx">success</span><span class="o">:</span> <span class="s1">&#39;success&#39;</span><span class="p">,</span>
<span class="nx">info</span><span class="o">:</span> <span class="s1">&#39;info&#39;</span>
<span class="p">},</span>
<span class="nx">source</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">&#39;#message_template&#39;</span><span class="p">).</span><span class="nx">html</span><span class="p">(),</span>
<span class="nx">template</span> <span class="o">=</span> <span class="nx">Handlebars</span><span class="p">.</span><span class="nx">compile</span><span class="p">(</span><span class="nx">source</span><span class="p">),</span>
<span class="nx">context</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">&#39;tags&#39;</span><span class="o">:</span> <span class="nx">levels</span><span class="p">[</span><span class="nx">level</span><span class="p">],</span>
<span class="s1">&#39;message&#39;</span><span class="o">:</span> <span class="nx">msg</span>
<span class="p">},</span>
<span class="nx">html</span> <span class="o">=</span> <span class="nx">template</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#message_area&quot;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">html</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">django_block_message</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="nx">level</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">source</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#message_block_template&quot;</span><span class="p">).</span><span class="nx">html</span><span class="p">(),</span>
<span class="nx">template</span> <span class="o">=</span> <span class="nx">Handlebars</span><span class="p">.</span><span class="nx">compile</span><span class="p">(</span><span class="nx">source</span><span class="p">),</span>
<span class="nx">context</span> <span class="o">=</span> <span class="p">{</span><span class="nx">level</span><span class="o">:</span> <span class="nx">level</span><span class="p">,</span> <span class="nx">body</span><span class="o">:</span> <span class="nx">msg</span><span class="p">},</span>
<span class="nx">html</span> <span class="o">=</span> <span class="nx">template</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#message_area&quot;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">html</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>These two scripts mirror Django's <tt class="docutils literal">messages</tt> app's functionality. Both use <a class="reference external" href="http://handlebarsjs.com">Handlebars</a> to render the template, but you could use any Javascript templating library you'd like. Our templates look like so:</p>
<div class="highlight"><pre>{% load verbatim_tag %}
<span class="nt">&lt;script </span><span class="na">id=</span><span class="s">&quot;message_template&quot;</span> <span class="na">type=</span><span class="s">&quot;text/x-handlebars-template&quot;</span><span class="nt">&gt;</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">verbatim</span> <span class="o">%</span><span class="p">}</span>
<span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;alert alert-{{tags}} fade in&quot;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">alert</span><span class="o">=</span><span class="s2">&quot;{{tags}}&quot;</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="nx">a</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;close&quot;</span> <span class="nx">title</span><span class="o">=</span><span class="s2">&quot;Close&quot;</span> <span class="nx">href</span><span class="o">=</span><span class="s2">&quot;#&quot;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">dismiss</span><span class="o">=</span><span class="s2">&quot;alert&quot;</span><span class="o">&gt;&amp;</span><span class="nx">times</span><span class="p">;</span><span class="o">&lt;</span><span class="err">/a&gt;</span>
<span class="p">{{{</span><span class="nx">message</span><span class="p">}}}</span>
<span class="o">&lt;</span><span class="err">/div&gt;</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">endverbatim</span><span class="o">%</span><span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">id=</span><span class="s">&quot;message_block_template&quot;</span> <span class="na">type=</span><span class="s">&quot;text/x-handlebars-template&quot;</span><span class="nt">&gt;</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">verbatim</span> <span class="o">%</span><span class="p">}</span>
<span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;alert alert-block alert-{{level}} fade in&quot;</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="nx">a</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;close&quot;</span> <span class="nx">title</span><span class="o">=</span><span class="s2">&quot;Close&quot;</span> <span class="nx">href</span><span class="o">=</span><span class="s2">&quot;#&quot;</span> <span class="nx">data</span><span class="o">-</span><span class="nx">dismiss</span><span class="o">=</span><span class="s2">&quot;alert&quot;</span><span class="o">&gt;&amp;</span><span class="nx">times</span><span class="p">;</span><span class="o">&lt;</span><span class="err">/a&gt;</span>
<span class="p">{{{</span><span class="nx">body</span><span class="p">}}}</span>
<span class="o">&lt;</span><span class="err">/div&gt;</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">endverbatim</span> <span class="o">%</span><span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
</pre></div>
<p>The <tt class="docutils literal">verbatim</tt> <a class="reference external" href="https://gist.github.com/629508">tag</a> there is from Eric Florenzano and makes including Javascript templates in your Django-parsed HTML really easy. We include these in a base template and provide a spot in the rest of the document to attach them to. Again, this is based largely on Twitter Bootstrap, so your markup will vary.</p>
</div>
<div class="section" id="ajax-views">
<h2>Ajax Views</h2>
<p>So now let's get down to the good stuff. The following view is very generic and only shows the basic concept, but we're sure you'll get the gist of it.</p>
<div class="highlight"><pre><span class="x">from django.http import HttpResponse, HttpResponseBadRequest</span>
<span class="x">from django.utils import simplejson as json</span>
<span class="x">from django.views.generic import UpdateView</span>
<span class="x">from braces.views import LoginRequiredMixin, PermissionRequiredMixin</span>
<span class="x">class PonyAjaxUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):</span>
<span class="x"> form_class = PonyForm</span>
<span class="x"> model = Pony</span>
<span class="x"> permission_required = &quot;ponies.change_pony&quot;</span>
<span class="x"> def form_valid(self, form):</span>
<span class="x"> self.object = form.save()</span>
<span class="x"> if self.request.is_ajax():</span>
<span class="x"> return HttpResponse(json.dumps(&quot;success&quot;),</span>
<span class="x"> mimetype=&quot;application/json&quot;)</span>
<span class="x"> return super(PonyAjaxUpdateView, self).form_valid(form)</span>
<span class="x"> def form_invalid(self, form):</span>
<span class="x"> if self.request.is_ajax():</span>
<span class="x"> return HttpResponseBadRequest(json.dumps(form.errors),</span>
<span class="x"> mimetype=&quot;application/json&quot;)</span>
<span class="x"> return super(PonyAjaxUpdateView, self).form_invalid(form)</span>
</pre></div>
<p>Again, nothing special in the view. We use an <tt class="docutils literal">UpdateView</tt> so we can, technically, still use the view without AJAX. Assuming that the POST data that comes in validates on the form, our <tt class="docutils literal">form_valid</tt> method will fire, which checks to see if the request was made via AJAX and, if so, returns a success string. Quite often we like to return a serialized version of the object that was just created or updated, but that takes some special considerations when it comes to Django model objects. If you don't need the object back, returning a standard <tt class="docutils literal">HttpResponse</tt> or one with a message, like demonstrated above, is enough. If your view creates new objects or deletes old ones, returning proper status codes, like <tt class="docutils literal">201</tt> for <tt class="docutils literal">Created</tt> is a very polite thing to do, especially if you think your view will end up as part of an ad hoc API.</p>
<p>Similarly, above, if the form is invalid, we serialize the form errors (note: not the <tt class="docutils literal">non_form_errors()</tt> errors) and send them back to the view. The script we wrote above, <tt class="docutils literal">apply_form_field_error</tt> can be called in a loop for each error in the list and update your form so the users know what they did wrong.</p>
</div>
</article>
<section>
<header>
<h1>Comments</h1>
</header>
<div id="disqus_thread"></div>
</section>
</div>
</div>
<footer><p>&copy; Brack3t. All rights reserved. <a href=".././feeds/all.atom.xml">ATOM feed</a></p></footer>
</div> <!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src=".././brack3t-theme/assets/jquery-1.7.1.min.js"></script>
<script src=".././brack3t-theme/assets/jquery.slabtext.min.js"></script>
<script src=".././brack3t-theme/assets/jquery.fittext.js"></script>
<script src=".././brack3t-theme/assets/highlight.pack.js"></script>
<script>
$(function() {
$(".slabtext").slabText();
$(".fittext").fitText(0.8);
$(".highlight pre").each(function(i, e) {hljs.highlightBlock(e, " ")});
});
</script>
</body>
</html>
Something went wrong with that request. Please try again.