JSONResponse – Trabalhando com JSON em Django, o jeito fácil.
by Felipe 'chronos' Prenholato on Domingo, 30 Outubro/2011, under Apps e extensões, Django, python
Nota: Olá meus caros leitores, apesar do ChronosBox ser um ótimo Blog, eu estou focando meus esforços em um blog conjunto com meu amigo Handrus Nogueira, portanto não deixem de visitar o Dev With Passion! Todos os posts do ChronosBox estarão no Dev With Passion, exceto por alguns comentários novos. Abraços!
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!
** UPDATES
- Se você está ganhando erros 403, cheque permissões na view ajax e o CSRF (dica por Nick Young)
- Se você precisa de dados adicionais na resposta JSON veja a ajuda que dei ao Jim
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 !
terça, 28 em setembro/2010 on 20:06
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'}]
terça, 28 em setembro/2010 on 20:26
Yes, it can work in many ways
.
in line
the part cities.values(‘id’,'name’) returns a list of dicts like [{'id':1,'city':'New York'},...].
but you can do something like that:
And you should receive your json with any additional data that you want
sábado, 06 em novembro/2010 on 14:00
Muito bom cara! ajudou muito!
quinta, 05 em maio/2011 on 12:54
¿que debo hacer con el MD45?.
ya hice mi proyecto y en la url le pongo http://localhost:8000/ajax/city pero me genera un .part lo habro como texto plano, y no sale nada.
¿Qué hago con esto (r’^$’,mainview)
?
domingo, 22 em maio/2011 on 02:53
MD5 files is just to check integrity of package, you can check on windows (http://www.softpedia.com/get/System/File-Management/MD5-Check.shtml) or linux (https://help.ubuntu.com/community/HowToMD5SUM) (or yet other OS).
About your error, the ajax url should be handled via javascript, so you can use the data.
Normally if opened in browser, a JSON file will be downloaded, you can use some plugins to see JSON instead download it:
Firefox: https://addons.mozilla.org/pt-br/firefox/addon/jsonview/
Chrome: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc
And, to finish, (r’^$’,mainview) indicates that your url pointin to / will call mainview. Doc about that is present at Django basics: http://docs.djangoproject.com/en/dev/intro/tutorial03/?from=olddocs#design-your-urls
quinta, 12 em maio/2011 on 08:34
Muito legal!!!
Vlw
quinta, 30 em junho/2011 on 12:51
Thank you for this, its simple enough to get me going, however..
I tried your example and can’t seem to get the ajax part to work.
I’m running manage.py runserver, and when I pull down a city, I get a 403 2332 error and the States combo is no populated.
Thoughts?
quinta, 30 em junho/2011 on 14:14
Hello John. The 403 code is for access denied / forbidden. Something is breaking you ajax when validate access.
quinta, 30 em junho/2011 on 21:17
John, I received the same 403 error that you did. The CSRF protection is causing the 403. In views.py add the @csrf_exempt tag to json_get_city and the 403 will go away.
Felipe, thanks for posting this. I was looking all around for a tutorial on how to easily add JSON Responses to a site.
quinta, 30 em junho/2011 on 22:06
Thx for share! I’ll update the post with one link to this comment
sexta, 01 em julho/2011 on 16:58
Nick, thanks for the reply, after talking with my friend who had the same issue, we came to the same conclusion and i was able to fix with both the middleware removal and the crsf_exempt stuff.
Now I’m trying to add the jQuery stuff to post the crsf cookie on the post, i’ve seen lots of posts on this.
This post is awesome to get me going with Ajax on django, thanks again Felipe’
sexta, 01 em julho/2011 on 20:12
I’ve added this to my github account, and you’re all welcome to pull it down. It’s been ‘fixed’ for the CSRF stuff.
It would be great to get some updates to it from everyone on the list. I’m not sure how that works on github, but if u know, tell me and i’ll accept any pushes.
John
https://github.com/jfmatth/jsonproject
sexta, 01 em julho/2011 on 20:16
Opps, posted the wrong github link, i think this one is better
git://github.com/jfmatth/jsonproject.git
segunda, 18 em julho/2011 on 21:49
John, I don’t see why having the JSON call be CSRF exempt is a problem. The JSON call is retrieving information from the database, but not updating it. The CSRF is required only when there is an update being made to the server.
If you are going to update the database, a POST should be submitted to the server, in which case the submit button on the CSRF protected form would suffice.
Let me know if there is something that I am missing here.
terça, 01 em novembro/2011 on 08:08
Hi Felipe, I am trying to return two json objects from two queryset in the same view. How do i go about this?
sexta, 04 em novembro/2011 on 10:03
You can put objects in a list, ex:
I have {“a”:1,”b”:2,”c”:3} and {“d”:4,”e”:5,”f”:6} and can return [{"a":1,"b":2,"c":3},{"d":4,"e":5,"f":6}]. Se how it appears on on terminal with serialize_to_json:
In javascript you use something like this:
quinta, 03 em novembro/2011 on 17:20
De onde voce ta tirando esse forms, que vc ta importando na view??
from forms import CityForm
sexta, 04 em novembro/2011 on 10:05
Esse CityForm é um ModelForm, fica em jsonui.forms. É só um exemplo
sexta, 04 em novembro/2011 on 18:53
humm…..tendi, desculpa a pergunta ainda sou noob nesse assunto..
sexta, 04 em novembro/2011 on 18:58
Eu implementei dois models A e B e tenho que fazer com que uma grid carregue pela URL os dados em .json
{“grade” : [
{"nome":"João", "latitude":893, "longitude":612, "id":1 },
{"nome":"Maria", "latitude":812, "longitude":601, "id":2 },
{"nome":"Josefa", "latitude":987, "longitude":234, "id":3 },
{"nome":"Paulo", "latitude":123, "longitude":310, "id":4 },
{"nome":"Joelma", "latitude":321, "longitude":431, "id":5 },
{"nome":"Rafael", "latitude":234, "longitude":083, "id":6 },
{"nome":"Carolina", "latitude":943, "longitude":211, "id":7 },
]};
eu nao estou conseguindo carregar a extensao .json ele tem que ler e carregar a grid pelo dojo url =”grade”
EU tentei implementar esse metodo seu de serialização, mas como ainda sou noob estou me perdendo, teria alguma dica??
domingo, 06 em novembro/2011 on 20:31
O meu JSONResponse retorna como tipo JSON, se eu não me engano o dojo vai precisar só do que vc atribuiu a ‘grade’, ou você pode ter uma url qualquer que aponte para a view que retorne o JSON e na view:
Com isso no seu javascript você só precisa pegar os dados retornados da sua url e mandar pro dojo. Vi algo parecido aqui: http://www.enterprisedojo.com/2011/01/31/a-simple-dojo-datagrid-example-or-so-close-yet-wide-right/
segunda, 07 em novembro/2011 on 18:02
Cara, faz muito sentido isso, mas onde que eu devo colocar o arquivo dados.json para ele ser chamado e carregar a extensao??
eu Reajustei o direotiro de arquivos estaticos
STATICFILES_DIRS = (
‘/home/marcus/Templates/static/’,
)
e na grid esta assim:
var grid, store;
dojo.ready(function(){
store = new dojo.data.ItemFileWriteStore({
url: “carregar”//essa url que passa para a view carregar os dados
a urls.py do projeto esta assim:
urlpatterns = patterns(”,
url(r’^grade/’, include(‘Aplicativo.urls’)),
url(r’^admin/’, include(admin.site.urls)),
)
a urls.py do aplicativo esta assim:
(r’^$’, ‘index’),
#(r’^(?P\d+)/$’, ‘detail’),
#(r’^(?P\d+)/results/$’, ‘results’),
#(r’^(?P\d+)/vote/$’, ‘vote’),
#(r’^static/(.*)$’ ,’static.serve’,{‘document_root’:'settings.STATIC_ROOT’}),#arquivo estatico
(r’^(?P\d+)/grade/$’, ‘grade’),
segunda, 07 em novembro/2011 on 18:04
A view do aplicativo esta dessa forma:
#serializador
def grade(request):
json_serializer = serializers.get_serializer(“json”)()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
all_contacts = list(iter(Cliente.objects.values()))
json_contacts = simplejson.dumps(all_contacts)
return HttpResponse(json_contacts, mimetype=”application/json”)
segunda, 07 em novembro/2011 on 18:07
O models esta disposto dessa forma:
from django.db import models
import datetime
class A(models.Model):
nome = models.CharField(max_length=200)
data = models.DateTimeField()
def Data(self):
return self.data.date() == datetime.date.today()
class B(models.Model):
a = models.ForeignKey(A)
nome = models.CharField(max_length=200)
latitude = models.IntegerField()
longitude = models.IntegerField()
data = models.DateTimeField()
def Nome(self):
return self.nome
def Latitude(self):
return self.latitude
def Longitude(self):
return self.longitude
def Data(self):
return self.data.date() == datetime.date.today()
segunda, 07 em novembro/2011 on 23:44
Marcus, ficou meio confuso todo esse código colado, eu recomendo a você usar o http://gist.github.com daqui para frente
. Você também pode usar <pre lang=’python’>…</pre> aqui no blog, variando a linguagem usada claro
. Fica mais fácil de lermos.
Eu imagino que com esse arquivo dados.json você deve estar falando da view que retorna o JSON.
Nesse caso a sua view seria a view grade, só que bem mais simples, veja:
E ai no seu código javascript:
Tente algo assim que deve funcionar.
quarta, 09 em novembro/2011 on 17:37
Entao, em primeiro lugar obrigado pela resposta rapida, eu dei uma treinada aqui e implementei alguns templates novos no novo projeto. meus models sao respectivcamente Empresa e Pessoa. estou usando o banco sqlite3 para inserção dos dados o admin funciona perfeitamente, as views apresentam a arquitetura CRUD, elas inserem, listam.blabla blá, eu adaptei um arquivo estatico que puxa o template do dojo que segue esse caminho projeto/aplicativo/static/empresa/dojo/grid_map.html
A serialização para json deve ser feita quando eu estou incluindo os dados dentro do banco correto?..e como eu consigo recuperar esses dados? eu aponto a url para a view e faço o q???
minha view de inserção esta simples
Como eu faço para recuperar esses dados??