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

14 Abr

Existem por ai dezenas de artigos e snippets sobre como trabalhar com JSON
em Django, entretanto vi que a maioria é um pouco vaga e a solução envolve serializar com simplejson e mandar para o HttpResponse. Existem alguns detalhes que normalmente não se cobrem e eu apresento a maneira fácil de trabalhar e um exemplo funcional de JSON em um formulário.

:: Problemas comuns ::

Os problemas mais comuns que vejo ao trabalhar com JSON em qualquer lugar e não só em Django são:

      1. Nem sempre é simples parsear os argumentos enviados
      2. O simplejson da problemas com alguns tipos de objetos
      3. Porque não termos um JSONResponse em vez de somente o HttpResponse :)?

:: A solução ::

      1. Criar um JSON serializer que manipula objetos que dão problema ao simplejson.dumps
      2. Criar a JSONResponse, baseada na HttpResponse e no nosso novo serializer
      3. Achar um meio fácil de parsear os argumentos enviados para obter o JSON

A JSONResponse usa o JSON serializer e retorna uma response com os parâmetros enviados pela view. Os parâmetros são os objetos a serem serializados.

Definido isso vamos criar uma APP Django chamada jsonui :). Ela tera 2 models, cidade e estado, 2 views, a main e a que retorna o json, um form simples e os arquivos response.py e utils.py com os métodos que usaremos para gerar o JSON bonitinho.

:: O JSON serializer ::

O serializer reside no utils, ele é divido em um JSONEncoder usado pelo simplejson que é responsável por interpretar os objetos que dariam problema, e no serializer propriamente dito que nada mais é um wrapper pro 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 how handle your type of object here to use when when dump 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)

:: A JSONResponse ::

A JSONResponse reside no response, basicamente ela recebe a lista, dicionário ou qualquer objeto que você envia para o JSONResponse e retorna serializado:

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)

:: Botando pra funcionar na view. ::

Sem mais rodeios, vamos ver como funciona. Nós temos duas views:

  • mainview: chama o form e renderiza um template com o código javascript necessário para executar o POST e renderizar o JSON que obtemos como resposta.
  • json_get_city: responsável por retornar as cidades de acordo com o estado enviado.

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'))

O método qdct_as_kwargs é responsável por retornar um dicionário que é passado parao método filter de um objeto a partir do request.POST ou request.GET. É uma magica legal que agiliza e muito a criação de views como essa.

Assim que a url /ajax/city/ recebe o POST ela obtem as cidades usando o qdct_as_kwargs e retorna a JSONResponse, no javascript a resposta é um JSON pronto para usar.

Arquivo: jsonui/utils.py @ 44

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

O javascript responsável por mandar o POST esta no template base.html que é renderizado pela mainview. Este é o trecho relevante:

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(
                        ""
                    )
                }
            })
    })
})

Este código executa um POST para /ajax/city/ e na volta preenche o select de cidades com o objeto retornado.

:: Finalizando ::

Reproduzindo javascripts similares e usando o qdct_as_kwargs e o JSONResponse como usado na view json_get_city você pode facílmente criar várias views ou até uma view mais genérica que pegue de qualquer modelo. Basta deixar a imaginação fluir ;).

Você pode baixar o projeto completo aqui: jsonproject.tbz2 (MD5 Hash) (mirror, mirror MD5)

Eu construi ele usando Django 1.2, mas roda em qualquer versão acima da 1.0.

Divirta-se, contribua, comente 😉

Abraços!

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 junho 24, 2014 at %H:%M 12Tue, 24 Jun 2014 12:02:46 +000046. #

    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 novembro 6, 2010 at %H:%M 02Sat, 06 Nov 2010 14:00:49 +000049. #

    Muito bom cara! ajudou muito!

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
  3. Jim setembro 28, 2010 at %H:%M 08Tue, 28 Sep 2010 20:06:10 +000010. #

    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 setembro 28, 2010 at %H:%M 08Tue, 28 Sep 2010 20:26:10 +000010. #

      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 maio 6, 2010 at %H:%M 03Thu, 06 May 2010 15:22:33 +000033. #

    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