orthopedics manic buying avodart rate injection cheap bactrim developmental direct order buspar contamination late buy cephalexin now sexual Death cipro no rx cardiopulmonary clomid without prescription strabismus diphtheria clonidine credentialing
Chronosbox

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

by 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

:, , , ,

29 Comments for this entry

  • Sérgio Berlotto

    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 !

  • Jim

    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'}]

    • Felipe 'chronos' Prenholato

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

  • Alexandre Santos

    Muito bom cara! ajudou muito!

  • ad

    ¿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)
    ?

  • John M

    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?

  • Nick Young

    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.

  • John M

    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’

  • John M

    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

  • John M

    Opps, posted the wrong github link, i think this one is better

    git://github.com/jfmatth/jsonproject.git

  • Nick Young

    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.

  • Newton

    Hi Felipe, I am trying to return two json objects from two queryset in the same view. How do i go about this?

    • Felipe 'chronos' Prenholato

      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:

       
      &gt;&gt;&gt; serialize_to_json([{"a":1,"b":2,"c":3},{"d":4,"e":5,"f":6}])
       u'[{"a": 1, "c": 3, "b": 2}, {"e": 5, "d": 4, "f": 6}]'

      In javascript you use something like this:

       
      for (i in data) {
           obj = data[i]
           // do your things
      }
  • marcus

    De onde voce ta tirando esse forms, que vc ta importando na view??

    from forms import CityForm

  • marcus

    humm…..tendi, desculpa a pergunta ainda sou noob nesse assunto..

  • marcus

    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??

    • Felipe 'chronos' Prenholato

      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:

       
      # faz de conta que a queryset ModelA.objects.select_related('ModelB').values('user__nome','latitude','longitude') retorne os valores abaixo
      queryset = [
      {"user_nome":"João", "latitude":893, "longitude":612, "id":1 },
       
      {"user_nome":"Maria", "latitude":812, "longitude":601, "id":2 },
       
      {"user_nome":"Josefa", "latitude":987, "longitude":234, "id":3 },
       
      {"user_nome":"Paulo", "latitude":123, "longitude":310, "id":4 },
       
      {"user_nome":"Joelma", "latitude":321, "longitude":431, "id":5 },
       
      {"user_nome":"Rafael", "latitude":234, "longitude":083, "id":6 },
       
      {"user_nome":"Carolina", "latitude":943, "longitude":211, "id":7 },
      ]
       
      return JSONResponse(queryset)

      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/

  • marcus

    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’),

  • marcus

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

  • marcus

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

    • Felipe 'chronos' Prenholato

      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&gt 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:

      from aplicativo.models import Cliente
      from jsonresponse import JSONResponse
       
      def grade(request):
      	contacts = Cliente.objects.values() # especificar o valor que vc quer é importante
      	# antes de retorar o JSON faça todo o tratamento que precisar
      	# lembre-se que um objeto json em python é basicmente um dicionário ou uma lista de 
      	# dicionários ou outras listas ou até objetos ...
       
      	# finalmente retorna a lista
      	return JSONResponse(contacts)

      E ai no seu código javascript:

      var grid, store;
      dojo.ready(functon(){
          store = new dojo.data.ItemFileWriteStore({
              url: "/url/da/sua/view/grade/",
              # mais argumentos ... ?
          })
      })

      Tente algo assim que deve funcionar.

  • marcus

    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??

  • Érico

    Bom dia,
    fora do admin rodei normalmente.
    Existe uma soluçõa(link com um how to) para fazer esse tipo de implementação dentro do admin?

    Obrigado e abraços

    • Felipe 'chronos' Prenholato

      Se eu entendi você quer retornar JSON dentro do Admin? É isso? Que eu saiba não.

      Entretanto você pode configurar suas próprias views (vide django-selectable) para retornar JSON e simplesmente usa-las direto do admin.

      Você também pode alterar as views de um ModelAdmin, veja quais são:


      $ grep "def.*_view" lib/python2.7/site-packages/django/contrib/admin/options.py
      def add_view(self, request, form_url='', extra_context=None):
      def change_view(self, request, object_id, form_url='', extra_context=None):
      def changelist_view(self, request, extra_context=None):
      def delete_view(self, request, object_id, extra_context=None):
      def history_view(self, request, object_id, extra_context=None):

1 Trackback or Pingback for this entry

Leave a Reply

StatPress

Visits today: 20 Visits since 6 de abril de 2009: 87280 Visitors now: %visitoronline%