Insights

How to use mixins to process a form in Django

Photo of the author: Vera Mazhuga

Vera Mazhuga

  •  

3 min read.

Django is a quite powerful Python framework, but unfortunately it doesn't offer any solutions to process forms that work with AJAX: to send the data to server and return the list of errors if the data didn't pass the validation.


Processing forms with AJAX has its advantages: using this approach it's not necessary to refresh a page or to render the whole template with a form in it. Let's take a look on how to validate forms using a Django and AJAX.


There are many examples of how to return JSON data from a view, but here we are going to look how to move this logic to a mixin. This way we could write our view without any additional logic. For our example we'll use
Classed Based Views.


Let's look at this in more detail:

  • Firstly, we'll create a class of a simple Django form, that is based on Comment model:

    from django import forms
    from django.db import models
    class Comment(models.Model):
        created_at = models.DateTimeField(
            verbose_name=u'Date',
            auto_now_add=True,
        )
        message = models.TextField(
            verbose_name=u'Message',
        )
    class CommentForm(forms.Form):
        class Meta:
            model = Comment

    Then we'll write a class based view that to create new instances of this model
    Comment:

    from django.views.generic import CreateView 
    class CommentCreateView(JSONFormMixin, CreateView):
        model = Comment
        form_class = CommentForm

    As you can see, this form inherits two classes: CreateView and a mixin JSONFormMixin:

    class JSONMixin(object):
        def render_to_response(self, context, **httpresponse_kwargs):
            return self.get_json_response(
                self.convert_context_to_json(context),
                **httpresponse_kwargs
            )
        def get_json_response(self, content, **httpresponse_kwargs):
            return HttpResponse(
                content,
                content_type='application/json',
                **httpresponse_kwargs
            )
        def convert_context_to_json(self, context):
            u""" This method serialises a Django form and
            returns JSON object with its fields and errors
            """
            form = context.get('form')
            to_json = {}
            options = context.get('options', {})
            to_json.update(options=options)
            to_json.update(success=context.get('success', False))
            fields = {}
            for field_name, field in form.fields.items():
                if isinstance(field, DateField) \
                        and isinstance(form[field_name].value(), datetime.date):
                    fields[field_name] = \
                        unicode(form[field_name].value().strftime('%d.%m.%Y'))
                else:
                    fields[field_name] = \
                        form[field_name].value() \
                        and unicode(form[field_name].value()) \
                        or form[field_name].value()
            to_json.update(fields=fields)
            if form.errors:
                errors = {
                    'non_field_errors': form.non_field_errors(),
                }
                fields = {}
                for field_name, text in form.errors.items():
                    fields[field_name] = text
                errors.update(fields=fields)
                to_json.update(errors=errors)
            return json.dumps(to_json)

    By default, all Django views return the response in text/html format. To change this behaviour, we'll rewrite a
    render_to_response method so it could return JSON objects. Then, we'll have to redefine convert_context_to_json, method in order to say to Django how to process the form object.

    In our case we have to obtain the form
    context.get('form') and to build to_json dictionary from its attributes, that later we'll return to the client in JSON format json.dumps(to_json). The dictionary to_json will hace the following structure:

    {
      fields: {
        message: 'content of message field',
      },
      errors: {
        non_field_errors: 'errors that are not correspond to any particular field',
        fields: {
          message: 'errors related to message field'
        }
      }
      options: { },   // additional data that we want to return to the client
      success: false  // or true if the form is valid
    }

    As we can see, now we have all the data that normally Django
    CreateView returns to a template, but in JSON format.

  • Now we can render our form in a template:

    <form id="add_comment_form" method="post" action="{% url 'comment_create' %}">{% csrf_token %}
        {{ comment_form }}
        <a id="add_comment_submit" class="btn">Send</a>
    </form>

    The next step will be to write a JavaScript function that will catch a the "submit" event of our form, serialise all the fields with their values, sent it to
    CommentCreateView
    view and process the response.

    $('#add_comment_submit').click(function() {
        $.ajax({
            url: $('#add_comment_form').attr('action'),
            type: 'POST',
            data: $('#add_comment_form').serialize(),
            dataType: 'json',
            success: function(response) {
                if(response.success){
                    $('#add_comment_form')[0].reset();
                    $('.form-error').remove();
                    $('#comments').prepend(response.fields.message);
                } else {
                    $('.form-error').remove();
                    for(var error in response.errors.fields) {
                        $('#add_comment_form #id_' + error).before('<div class="form-error">' + response.errors.fields[error] + '</div>');
                    }
                }
            }
        });
    });

    First it will check if the form is valid or not. If it's valid, clear all the fields of the form and delete all the error messages and then add a message to comments list. If the data has errors, if a cycle the algorithm will go through all the form fields and add error messages.


    To simplify the process for managing forms in JavaScript one can use
    jQuery Form plugin.


    As a result we have a mixin that lets us to easily process Django forms and return the results in JSON format, that we can use for all our forms that need to be working with AJAX.

    Learn more by receiving an email once a month.

    Additional Insights

    Mathematical expression evaluator in python

    In this post we will take look at how to parse and execute mathematical expressions in python.

    Author Vera Mazhuga Vera Mazhuga

    Primeros días en AxiaCore

    Hace más o menos un mes, mientras aún trabajaba en mi antigua compañía, decidí asistir a una de las conferencias sobre Django...

    Photo of the author: Pablo Vallejo Pablo Vallejo