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!
quinta, 06 em maio/2010 on 15:22
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 !