Integração entre Django e ExtJs

Filed Under (Desenvolvimento) by Samir on 25-02-2010

Tagged Under : ,

Recentemente voltei minhas atenções para a biblioteca javascript ExtJs. O projeto/sistema que venho trabalhando desde outubro de 2009 passou por várias mudanças e uma coisa que eu estava achando ruim era justamente a user interface, afinal não sou um designer :P

Conversando com o cliente mostrei a biblioteca, expliquei suas vantagens e tive aprovação para refazer a interface. A primeira dúvida era como integrar a chamadas ajax com o Django, já que o mesmo não tem suporte nativo e como serializar objetos de forma flexível. O Django possui um sistema de serialização para os formatos mais conhecidos, mas a  forma como os objetos são serializados não me agrada, sendo assim comecei a procurar por alternativas.

Existem poucas alternativas de integração entre Python/ExtJs, das alternativas que  testei foram o ExtDirect-Django e Piston.

No final das contas acabei criando uma classe bem simples para serializar objetos de acordo com a minha necessidade, até agora vem funcionando bem e quem quiser melhorar o código fique a vontade :)

Arquivo json.py

# -*- coding:utf-8 -*-

from django.http import HttpResponse
from django.core import serializers
from django.utils import simplejson
from django.utils.encoding import smart_unicode

# ********************************************
def ext_date(d, date_format=1):
    """
    Renderizar Datetime no formato para ExtJs
    """
    if date_format==1:
        return d.strftime("%m/%d/%Y")
    elif date_format==2:
        return d.strftime("%d/%m/%Y")

# ********************************************
class JsonResponse(HttpResponse):
    """
    Renderizar Dicionrario Python para formato JSON
    """
    def __init__(self, params={}):
        HttpResponse.__init__(self, content=simplejson.dumps(params), mimetype='application/json')

# ********************************************
class JsonModelResponse(HttpResponse):
    """
    Renderizar Model para formato JSON
    """
    def __init__(self, queryset, **options):
        excludes = options.get('excludes')
        date_format = options.get('date_format')
        content = {'success': True}
        fields, many_to_many, foreignkeys = {},{},{}

        opts = queryset._meta # Extrai informação do Model

        # colunas
        for c in opts.fields:
            if excludes is not None:
                if c.name in excludes:
                    continue

            if c.__class__.__name__ == 'DateTimeField':
                if date_format is not None:
                    fields.update({c.name: ext_date(getattr(queryset, c.attname), date_format=date_format)})
                else:
                    fields.update({c.name: ext_date(getattr(queryset, c.attname))})
            elif c.__class__.__name__ == 'DecimalField':
                fields.update({c.name: str(getattr(queryset, c.attname))})
            elif c.__class__.__name__ == 'ForeignKey':
                pk = getattr(queryset, c.attname)
                # Usa o metodo smart_unicode para serializer um objeto model
                val = smart_unicode(getattr(queryset, c.name), strings_only=True)
                foreignkeys.update({c.name: [pk,val]})
            else:
                fields.update({c.name: getattr(queryset, c.attname)})

        # relaciomentos
        for c in opts.many_to_many:
            models = c.value_from_object(queryset) # Extrair models da lista
            many_to_many.update({c.name: [[{m.name:getattr(model, m.name)} for m in model._meta.fields] for model in models]})

        # atualiza dict
        content.update({'data': fields, 'foreignkeys': foreignkeys, 'many_to_many': many_to_many})
        # retorna a reposta no formato json
        HttpResponse.__init__(self, content=simplejson.dumps(content), mimetype='application/json')

# ********************************************
class JsonSuccess(HttpResponse):
    """
    Resposta JSON para requests efetuados com sucesso
    """
    def __init__(self, params={}):
        content = {'success':True}
        content.update(params)
        HttpResponse.__init__(self, content=simplejson.dumps(content), mimetype='application/json')

# ********************************************
class JsonFailure(HttpResponse):
    """
    Resposta JSON para requests efetuados com falha
    """
    def __init__(self, params={}):
        content = {'success':False}
        content.update(params)
        HttpResponse.__init__(self, content=simplejson.dumps(content), mimetype='application/json')

A integração fica bem mais simples agora, veja um pequeno exemplo de uma chamada Ajax.

Ext.onReady({
   Ext.Ajax.request({
      method:'GET',
      url:'/usuarios/show/1/'
   });
});

No lado do servidor nossa view faz a utilização do módulo json que criamos acima e devolve para o client no formato Json. Observe que na resposta não quero se seja serializado o campo password na resposta.

from utils import json
from django.contrib.auth.models import User

def show(request,id):
     qs = User.objects.get(pk=id)
     return json.JsonModelResponse(qs, excludes=('password',))

Observando a resposta do servidor pelo Firebug, temos o seguinte:
json

Interessante não? :D

Desenvolvendo Sistemas com Django

Filed Under (Desenvolvimento) by Samir on 03-12-2009

Tagged Under : ,

Essa é uma questão comum que ja vi lista #django-brasil onde muitos desenvolvedores ficam na dúvida para escolher a melhor opção de criar sistemas usando o Django. Fazer tudo no “braço” mesmo criando suas próprias views, htmls, rotas, etc…tudo para ter a melhor flexibilidade possível. Ou como segunda opção usar o próprio admin e aprender como customizar quando necessário.

Antes de iniciar o projeto que ando trabalhando, testei muitas opções de frameworks. Queria algo que pudesse agilizar ao máximo meu trabalho, porque convenhamos, ficar fazendo tela de CRUD alem de ser um trabalho maçante, propenso a erros, é um verdadeiro pé no saco! Nessa época como ainda estava ligado muito no Rails, tentei algumas opções, mas tudo me levava a fazer o CRUD na mão. Voltei novamente a pesquisar opções em Python, nessa época eu já até conhecia bem o admin do Django, mas tinha um certo receio do tipo, “esse negócio vai me deixar na mão”, mas foi puro engano, vou explicar adiante.

Continuando minha pesquisa, conheci o framework “cola” Pylons, na primeira vez fiquei impressionado, extremamente versátil e muito parecido com Rails, mas após alguns testes percebi que não tinha experiência o suficiente para usar essa ferramenta, acho que esse framework é para quem tem um conhecimento avançado em Python e não é meu caso.

Também testei o web2py, outro framework interessante e ágil para projetos pequenos, mas acho que a forma de trabalhar nele é muito estranha, você edita o código no próprio navegador como se fosse seu próprio IDE e assim por diante. Mas o que realmente me incomodou é que ele segue uma linha contrária do Python onde não existe importação de módulos como é de costume. E como tudo no Python é explícito e sem “mágicas”, deixei essa opção de lado e acabei voltando para o Django novamente usando o Admin.

A primeira coisa que recomendo antes de se aventurar a criar um sistema usando o Admin é, saiba bem como usar a Orientação a Objetos do Python e porque isso? No início tive a sensação do Admin ser aquele código “engessado” onde você fica amarrado e sem ter muito o que fazer. Mas com o tempo fui percebendo que você pode sobreescrever qualquer método da biblioteca padrão e customizar da maneira de achar necessário, vamos a um pequeno exemplo.

O sistema que estou criando, cada cliente possui seu sub-dominio e essa informação não pode ser editada como é por padrão, então imagine o seguinte trecho de código.

# models.py
class Cliente(models.Models):
    dominio = models.CharField(max_length=50)
    nome_fantasia = models.CharField(max_length=100)
    plano_acesso = models.ForeignKey('PlanoAcesso')

# forms.py
class ClienteAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ClienteAdminForm, self).__init__(*args, **kwargs)
        instance = getattr(self, 'instance', None)
        if instance and instance.id:
            self.fields['dominio'].widget.attrs['readonly'] = True
            self.fields['dominio'].help_text = 'Acesso: http://%s.dominio.com.br:8090' % instance.dominio

# admin.py
class ClienteAdmin(admin.ModelAdmin):
    form = ClienteAdminForm
    fields = ('dominio','nome_fantasia',...,)
register.admin.site(Cliente, ClienteAdmin)

Observando bem o código, o que fiz ali foi sobreescrever o formulário padrão da classe ClienteAdmin pela classe ClienteAdminForm e no construtor dessa classe eu verifico se o objeto está no modo de edição e travo o campo domínio como “readonly”. Isso é apenas uma pequena amostra do que é possível fazer, o Django é flexível o suficiente de acordo com sua necessidade.

Outra coisa muito interessante é que podemos trabalhar com herança nos templates sem ter a necessidade de modificar o arquivo original do admin, basta criar um novo arquivo com o mesmo nome e herdar do padrão, por exemplo. Na minha lista de clientes, eu quero somar o total do valor dos planos de acessos.

Primeiro mudo o comportamento padrão do método changelist_view:

# admin.py
def changelist_view(self, request, extra_context={}):
   soma = PlanoAcesso.objects.all().aggregate(Sum('valor'))
   try:
      extra_context['soma'] = ("R$ %2.2f" % soma['valor__sum']).replace('.',',')
   except:
      extra_context['soma'] = "R$ 0,00"
   return super(ClienteAdmin, self).changelist_view(request, extra_context)

Para criar o template vamos supor que essa aplicação seja chamada de clientes, dentro dessa pasta crie outra pasta chamada templates e dentro dela outra chamada admin, novamente dentro da pasta admin crie a pasta clientes e dentro de clientes cria a última pasta cliente, achou confuso? :D

Nessa última pasta, crie o arquivo change_list.html com o seguinte conteúdo.

{% extends "admin/change_list.html" %}
{% block result_list %}
    {{ block.super }}
    Total: {{ soma }}
{% endblock result_list %}

Se você observar, temos a listagem original porque no html chamamos a variável {{ block.super }} para herdar do template original e logo abaixo nosso conteúdo customizável, muito legal não?

Para finalizar, acho que ficou claro que podemos usar sim o Admin para criação de sistemas complexos e customizá-lo ao máximo, basta saber usar a ferramenta corretamente e sem gambiarras.

Utilizando MacPorts para desenvolvimento web com Python

Filed Under (Desenvolvimento) by Samir on 09-11-2009

Tagged Under : ,

Semana passada resolvi formatar meu Mac pois estava muito desorganizado minhas instalações de pacotes e tive uma série de dificuldades para instalar módulos no Apache como o mod_wsgi ou mod_python, ambos compilam mas necessitam de uma certa gambiarra no arquivo Makefile para se adequar na arquitetura do Mac, mas pra minha sorte o Apache não reconhecia os módulos de jeito nenhum.

Como iria formatar e reinstalar tudo do zero, resolvi procurar por opções como Homebrew, Fink (me parece desatualizado) e o próprio Macports.

Uma das vantagens que vejo no macports é a centralização da bibliotecas que ficam localizadas somente em um lugar e é fácil instalar ou remover algum pacote.

Antes de começar, o Mac Leopard já vem com uma versão instalada do Python (2.5.1), não precisa remover essa instalação, iremos configurar após a instalação via macports. Sendo assim, vou considerar caso você tenha um Mac :-) , tenha instalado a última versão do MacPorts que é a 1.8.1 no momento.

Instalando Python 2.6:

sudo port install python26

Essa instalação vai levar alguns minutos, pois muitos ports serão instalados. Agora muita atenção após o final da instalação, vai exibir uma mensagem informando que você  precisa instalar o “python_select” para selecionar a versão 2.6 como default do Mac.

Ativando o Python:

sudo port install python_select
sudo python_select python26

Se tudo ocorreu bem, abra o console e digite “python”, o resultado deve ser:

[samirmamude] ~
$ python
Python 2.6.4 (r264:75706, Nov  6 2009, 14:11:23)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Instalando o Django (1.1.1) e o driver para Postgresql (no meu caso):

port install py26-django
port install py26-psycopg2

Instalando o Postgresql 8.4:

sudo port install postgresql84
sudo port install postgresql84-server

Após finalizar a instalação, rode este comando para o servidor inicializar automaticamente.

sudo launchctl load -w /Library/LaunchDaemons/org.macports.postgresql84-server.plist

Por padrão o Mac também possui uma instalação do Apache, mas vamos instalar via macports e ativa-lo em seguida.

sudo port install apache2
sudo launchctl load -w /Library/LaunchDaemons/org.macports.apache2.plist

Agora a instalação que me causou muitos transtornos foi o mod_wsgi, no macports a instalação funciona perfeitamente e o Apache reconhece o módulo sem problemas.

sudo port install mod_wsgi

Veja as instruções na documentação do Apache para ativar esse módulo e logo após reinicie o servidor.

Pra terminar se quiser instale também o MacVim.

sudo port install mac_vim

Uma dica para facilitar quais ports você tem instalado é instalar esse programinha interessante o Porticus.

Isso foi apenas o básico, existem outros ports interessantes que também pode ser instalados como o Memcached, Ngnix e outros, de uma olhada nos ports disponíveis.

É isso ai :-)