Yawd website

aug30

Using class-based views to process a django form through ajax

Since Django 1.3 class-based views have been introduced to allow for code reusability. Class-based views have been criticized for their poor documentation (count me in) and their amazing ability to grow our code size instead of reducing it! The latter appears to be true only if we are not careful with sub-classing. On the other hand class-based views finally deliver django what has been missing: a consistent way of coding all your models, forms and views.

Unfortunately, django's generic class-based views do not provide a mechanism for implementing JSON views. Ajax requests are the standard way of doing things these days and I feel that django needs some work towards this direction. However, the official documentation offers the implementation of a simple JSONResponseMixin that we will use as a base for our own view. What we try to achieve here is create a view that will process a form, return the errors when the form is not valid or save the form and return a success message otherwise. This is a fairly common scenario and extremely useful when a full page reload is not acceptable (e.g. a newsletter subscription form).

To start with our code, we first define a simple class that will serve as a response object:

class SimpleResponse(object):
    """
    The simplest form of our ajax response
    """
    def __init__(self, error=False, message=None):
        self.error = error
        self.message = message
        self.data = {}
    
    def setError(self, message):
        self.error = True
        self.message = message
        
    def setSuccess(self, message):
        self.error = False
        self.message = message
    
    def setData(self,data={}):
        self.data = data.copy()
    
    def json(self):
        return {'error': self.error,
                'message': self.message,
                'data':self.data
                } 

With SimpleResponse we are able to set a message that the calling javascript will display on success or failure. The 'data' property will be used to hold possible errors generated by the form validation. Now we can combine the FormMixin, View and JSONResponseMixin classed to create our own view. The FormMixin eases the form creation and process while the View is the "master-class view" that enables class-based view functionality. Once again, the JSONResponseMixin can be found here.

class JSONFormView(FormMixin, View, JSONResponseMixin):
    success_message = ''

    def form_valid(self, form, request):
        form.save()
        return self.success(_(self.success_message))
    
    def form_invalid(self, form):
        response = SimpleResponse()
        response.setError(_("Validation failed."))
        response.setData(form.errors)
        return self.render_to_response(response.json())
    
    def success(self, message):
        response = SimpleResponse()
        response.setSuccess(message)
        return self.render_to_response(response.json()) 
    
    def get(self, request, *args, **kwargs):
        raise Http404
    
    def post(self, request, *args, **kwargs):
        form = self.get_form(self.get_form_class())
        
        if form.is_valid():
            return self.form_valid(form, request)
        else:
            return self.form_invalid(form)

The above class just checks the form for validity and calls the appropriate method (form_valid / form_invalid). A normal use case should be the following:

in views.py
class MyNewsletterExampleView(JSONFormView):
    form_class = MyNewsletterForm
    success_message="Thank you for subscribing to our newsletter!"

in urls.py

urlpatterns = patterns('',
    (r'^ajax/newsletter-example/$', MyNewsletterExampleView.as_view()),
  )

Of course you can override the form_valid method if you need a different processing than simply saving the form. Finally, a calling JQuery code might look like this:

In your form-rendering template
<script type="text/javascript">
( function (a) {
    a(document).ready( function() {
        a('#newsletter-form-button').click( function() {
              a.post('/ajax/newsletter-example/',data, function(data) {
		    a('#dialogue_message').html(data.message);
	      });
        });
    });
})(jQuery);
</script>

The above javascript is pretty basic. You can always use the 'error' property of the response to check if the form contained errors. You can also iterate over the 'data' property, get each field's own error and display it inside an appropriate div next to that certain field.

Field-specific errors with the JSONFormView class-based view Field-specific errors with the JSONFormView class-based view

That's all! Please feel free to comment if you have any ideas you'd like to share! :)

Comments powered by Disqus