January 31, 2011

Django AJAX Tutorial Part 1 - adding email to newsletter

This is something I really wanted to write about since some time - using ajax with django. The cause is simple - internet is lacking good materials concerning this matter. And I remember having many problems with ajax in the beginning. That's why in the next few days I will show typical usage of ajax with django, like form validation, pagination and simple shoutbox.

Ok - the basics. First thing you need to understand - when talking about ajax here we mean a request which is sent from the browser by javascript, usually asynchronously ( meaning user can do other things while we're processing it). So to keep it simple - ajax is the 'request' we're using in our django views. And instead of reloading the whole page after user clicks some link or submits form we can first process it in the background and then refresh only the needed parts of the page. Sounds pretty facebookish doesn't it ? :) And there's no magic in it, really.

Let's start from the simplest possible example - we will allow user to add email to some newsletter. Here is tho code for model, url and form :

class NewsletterForm(forms.Form):
    email = forms.EmailField(label=_("E-mail address"))

class NewsletterEmails(models.Model):
    email = models.EmailField(_(u"E-mail address"),)

urlpatterns = patterns('',
    url(r'^newsletter_add/$', newsletter_add, name="newsletter_add"),
)

Nothing unexpected here I guess. Simple model, simple form with just one field and a link to our view (remember - even though we're not redirecting user to this link, we still need a way to call our view function processing the email).
Next comes the view function :

from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
from models import NewsletterEmails
from forms import NewsletterForm

#here is some magic. We need to write custom JSON encoder, since sometimes
#serializing lazy strings can cause errors. 
#You need to write it only once and then can reuse in any app

class LazyEncoder(simplejson.JSONEncoder):
    """Encodes django's lazy i18n strings.
    """
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_unicode(obj)
        return obj

#here the view function starts
def newsletter_add(request):

    #as usual when processing forms we check if the method was POST/GET
    if request.method == "POST":

        #we get email address from request variables      
        adr = request.POST['email']
        e = None

        #we process address only if it's longer than 6 characters.
        #this assures us than we won't waste server time on
        #addresses like 'oeui' or 'u@u.u' 
        if len(adr) > 6:
            
            #create form instance and fill it with data. In this case it will only be the email address
            form = NewsletterForm(data=request.POST)
            
            #let django perform the validation
            if form.is_valid():

                #check if the email address is not already in our database
                try:
                    e = NewsletterEmails.objects.get(email = adr)
                    message = _(u"Email already added.")
                    type = "error"
                except NewsletterEmails.DoesNotExist:

                    if DoesNotExist exception was caught we add the address 'safe-mode' using try/catch
                    try:
                        e = NewsletterEmails(email = adr)
                    except DoesNotExist:
                        pass
                    message = _(u"Email added.")
                    type = "success"
                    e.save()
            else:
                message = _(u"Bad address.")
                type = "error"                
        else:
            message = _(u"Too short address.")
            type = "error"

    #we don't need to do this but it's good practice to check the source of request. If it comes from ajax call we build the response.

    if request.is_ajax():
        #let built in simplejson package serialize our data. It means we transform everything to simple strings.
        result = simplejson.dumps({
            "message": message,
            "type": type,
        }, cls=LazyEncoder)

        #return the response
        return HttpResponse(result, mimetype='application/javascript')

Now the form. This is really basic so I won't say anything more about it apart of the fact that we explicitly set the action of our form to address of view processing addition of emails :


and finally the javascript part. I'm using jQuery javascript library here as well as jQuery's form plugin for processing forms. It simply adds method that takes all the values from form's fields and sends it to specified address (in this case it'll be "newsletter_add"). We could do this by ourselves, but that's not what this tutorial is about.



And basically that will be all for this part. As you can see the key to using AJAX with django is that our views must return serialized strings with JSON so that we can process them with javascript.

For the first time I have prepared a working example under this link :

http://samples.fromzerotocodehero.x14.eu/newsletter1/start


as well as downloadable code :

http://ofcjs.x14.eu/ajax1.zip

If you have any questions feel free to ask in comments or on my twitter.

7 comments:

  1. Hey there owca,
    thanks for your crystal clear explanation, and such a useful simple script... well done!

    ReplyDelete
  2. You need to prefix DoesNotExist on line 52 with:

    NewsletterEmails.

    ReplyDelete
  3. Muchas gracias, funciona perfecto. Tuve que hacer algunas modificaciones pero funcion OK ;)

    ReplyDelete
  4. Hi, I´m trying to run the code but doesn't work, when the post sends I get a 403 error.. can you help me please?

    I'm using django 1.3.1

    thanks alot

    ReplyDelete
  5. Firstly, I wish to see syntax highlighting..

    ReplyDelete
  6. thanks for the explanation
    i wrote my code based on yours and it works fine, the only thing is a deprecated alert on the use of simplejson

    ReplyDelete
  7. Hey. Its probably a django noob thing (though I have made a couple of sites in django 1.5), but it is not exactly self explanatory the names of the files you are using, imports, etc. I was hoping to grab the working project but the link is down. Does anyone have this?

    ReplyDelete