JSONResponse – Trabalhando com JSON em Django, o jeito fácil.

14 Apr

There are dozen of articles and snippets about how work with JSON
and Django, however I saw that most are a bit vague and solution involves use of simplejson to serialize and HttpResponse to send response from the view. There are some details that normally aren’t covered and now I show a easy way and a functional example of how work with Django, JSON and forms.

:: Common problems ::

The most common problems that I see when working with JSON in Django are:

      1. Isn’t always simple to parse arguments
      2. The simplejson have problems with some type of objects.
      3. Why not use a single call to JSONResponse instead of simplejson + HttpRespone :)?

:: The solution ::

      1. Create a JSON serializer that handle problematic objects to default simplejson.dumps
      2. Create one JSONResponse, based on HttpResponse with our new serializer
      3. Easily parse arguments that are sent to get JSON

The JSONResponse uses the JSON serializer and return a response with a JSON object. The argument that are sent to JSONResponse
is the object to be serialized.

With that let’s create a Django APP named jsonui ;). This app have 2 models, city and state, 2 views, the main and one that returns json, a simple form and files response.py and utils.py with methods that we use to generate the JSON to response.

:: The JSON serializer ::

The serializer live on utils, splited in a JSONEncoder used by simplejson (he work on ‘problematic’ objects) and a serializer that is a wrapper to simplejson:

Arquivo: jsonui/utils.py

# -*- coding: utf-8 -*-

from django.utils import simplejson
from django.utils.encoding import force_unicode
from django.db.models.base import ModelBase

class LazyJSONEncoder(simplejson.JSONEncoder):
     """ a JSONEncoder subclass that handle querysets and models objects. Add¬
    your code about how to handle your type of object here to use when dumping
    json """¬
    def default(self,o):
        # this handles querysets and other iterable types
        try:
            iterable = iter(o)
        except TypeError:
            pass
        else:
            return list(iterable)

        # this handlers Models
        try:
            isinstance(o.__class__,ModelBase)
        except Exception:
            pass
        else:
            return force_unicode(o)

        return super(LazyJSONEncoder,self).default(obj)

def serialize_to_json(obj,*args,**kwargs):
    """ A wrapper for simplejson.dumps with defaults as:

    ensure_ascii=False
    cls=LazyJSONEncoder

    All arguments can be added via kwargs
    """

    kwargs['ensure_ascii'] = kwargs.get('ensure_ascii',False)
    kwargs['cls'] = kwargs.get('cls',LazyJSONEncoder)


    return simplejson.dumps(obj,*args,**kwargs)

:: The JSONResponse ::

The JSONResponse live at response.py, basically, it receive any object (list, dict, QuerySet) and returns a response object with json serialized:

Arquivo: jsonui/response.py

# -*- coding: utf-8 -*-

from utils import serialize_to_json
from django.http import HttpResponseForbidden, HttpResponse

class JSONResponse(HttpResponse):
    """ JSON response class """
    def __init__(self,content='',json_opts={},mimetype="application/json",*args,**kwargs):
        """
        This returns a object that we send as json content using 
        utils.serialize_to_json, that is a wrapper to simplejson.dumps
        method using a custom class to handle models and querysets. Put your
        options to serialize_to_json in json_opts, other options are used by
        response.
        """
        if content:
            content = serialize_to_json(content,**json_opts)
        else:
            content = serialize_to_json([],**json_opts)
        super(JSONResponse,self).__init__(content,mimetype,*args,**kwargs)

:: Putting to work in view. ::

Without further ado, let’s see how it works. We have two views:

  • mainview: call the form and render a template with javascript code used to execute the POST and render JSON sent by json_get_city view as options of select city.
  • json_get_city: return a queryset of cities jsonified.

Arquivo: urls.py @ 17

(r'^$','jsonui.views.mainview'),
(r'^ajax/city/','jsonui.views.json_get_city'),

Arquivo: jsonui/views.py

# -*- coding: utf-8 -*-

from django.shortcuts import render_to_response
from django.template import RequestContext

from models import City,State
from forms import CityForm

from utils import qdct_as_kwargs
from response import JSONResponse

def mainview(request):

    return render_to_response('base.html',{'form': CityForm() },
        context_instance=RequestContext(request))

def json_get_city(request):

    if not request.method == "POST":
        # return all cities if any filter was send
        return JSONResponse(City.objects.order_by('name'))

    # get cities with request.POST as filter arguments
    cities = City.objects.filter(**qdct_as_kwargs(request.POST)).order_by('name')
    
    #return JSONResponse with id and name
    return JSONResponse(cities.values('id','name'))

The qdct_as_kwargs method is responsible by return a dict that is sent to filter method of any model from request.POST or request.GET. That’s a nice magic very useful to views like this one.

So, as soon as url /ajax/city/ receive the POST, we consult model filter using qdct_as_kwargs and JSONResponse returns a JSON ready to use in javascript.

Arquivo: jsonui/utils.py @ 44

def qdct_as_kwargs(qdct):
kwargs={}
for k,v in qdct.items():
    kwargs[str(k)]=v
return kwargs

The javascript responsible to send POST live in template base.html used by mainview. That’s relevant snippet:

Arquivo: templates/base.html @ 10

$(function(){
    $("#id_state").change(function(e){
        $.post(
            // url to post
            "/ajax/city/",
            // args
            {state__id:$(this).val()},
            // response callback
            function(data,text,xhrobject){
                // uncomment if you wanna to see objects on firebug console
                //console.log(data,text,xhrobject)
                $("#id_city").children().remove()
                    .append("")
                for (i in data) {
                    i = data[i]
                    $("#id_city").append(
                        ""
                    )
                }
            })
    })
})

This code execute a POST to /ajax/city/ and on receive a response fill a select with cities received as json.

:: Finishing::

Creating similar javascript code and using the qdct_as_kwargs and JSONResponse methods, like in view json_get_city you can easily create a lot of views or maybe a more generic view that get data from any model. Just let the imagination run ;).

You can download entire project in these links: jsonproject.tbz2 (MD5 Hash) (mirror, mirror MD5)

I built using Django 1.2 SVN, but runs in any version >= 1.0.

Have fun, contribute, say about 😉

See ya!

VN:F [1.9.22_1171]
Rating: 5.0/5 (1 vote cast)
JSONResponse - Trabalhando com JSON em Django, o jeito fácil., 5.0 out of 5 based on 1 rating

5 Responses to “JSONResponse – Trabalhando com JSON em Django, o jeito fácil.”

  1. Gilson Leite June 24, 2014 at %I:%M %p #

    Bom dia Felipe tudo bem?
    Baixei o arquivo jsonproject.tbz2 mas não esta carregando as cidades vc sabe porque?

    Obrigado.

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
  2. Alexandre Santos November 6, 2010 at %I:%M %p #

    Muito bom cara! ajudou muito!

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
  3. Jim September 28, 2010 at %I:%M %p #

    does this work for returning a data structure containing a queryset and a dict? for example to return this as json data : [ cities, {‘status’: 1, ‘msg’: ‘Success’}]

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    • Felipe 'chronos' Prenholato September 28, 2010 at %I:%M %p #

      Yes, it can work in many ways :).

      in line

      return JSONResponse(cities.values('id','name'))
      

      the part cities.values(‘id’,’name’) returns a list of dicts like [{‘id’:1,’city’:’New York’},…].

      but you can do something like that:

      return JSONResponse([
          cities.values('id','name'),
          {
              'status':1,
              'msg':'Sucess'
          }
      ])
      

      And you should receive your json with any additional data that you want 🙂

      VA:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
  4. Sérgio Berlotto May 6, 2010 at %I:%M %p #

    Cara, muito bom isto !
    Eu acabei juntando todas as soluções de json em meu projeto e criando uma solução meio “frankenstein” hahehehe
    Vou testar esta sua solução no meu projeto …
    Valeu !

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)

Leave a Reply