Chronosbox

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

by Felipe 'chronos' Prenholato on Quarta, 14 Abril/2010, under Apps e extensões, Django, python

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# -*- 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- 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

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

Arquivo: jsonui/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- 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

44
45
46
47
48
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

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$(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("<option selected=\"selected\" value=\"\">------</option>")
                for (i in data) {
                    i = data[i]
                    $("#id_city").append(
                        "<option value=\""+i.id+"\">"+i.name+"</option>"
                    )
                }
            })
    })
})

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!

:, , , ,

1 Comment for this entry

Leave a Reply

StatPress

Visits today: 4 Visits since 6 de abril de 2009: 15860 Visitors now: %visitoronline%