quarta-feira, 2 de junho de 2010

Porque eu adoro o sistema de views do web2py

Vindo de mim, um elogio ao web2py pode parecer tendencioso, já que sou um dos desenvolvedores do framework. Porém uma coisa que me fascinou, mesmo antes de me tornar desenvolvedor, foi a implementação da camada de apresentação (views, do modelo MVC).

No web2py a camada de apresentação é parecida com código PHP: você utiliza a linguagem que precisar para apresentação do conteúdo (HTML, CSS, XML, JavaScript ou qualquer outra que desejar) e pode embutir código Python no meio. A grande sacada - que trás muita simplicidade e flexibilidade ao desenvolvedor - é exatamente o fato de podemos embutir código Python. Com isso, ganhamos inúmeras vantagens:

  • Você não precisa aprender a utilizar uma linguagem de templates nova: é Python - mesma linguagem em que você programa seus models e controllers!
  • Como o código da view pode ser transformado em um módulo Python, a execução da view fica muito mais rápida, pois o web2py a traduz para código Python puro e então o compila para bytecode;
  • Você não fica limitado a uma linguagem restrita (como são as linguagens criadas para a camada de apresentação) - você pode utilizar todo o poder que Python lhe proporciona sempre!
  • Você pode criar qualquer formato de saída para sua camada de apresentação, o que significa que não necessariamente a saída da sua view precisa ser escrita em alguma linguagem de marcação (HTML, XML etc.). Você pode, por exemplo, ter uma view que gera um arquivo JPEG, um texto em RTF, um documento em PDF, um arquivo em CSV e por aí vai;
  • A camada de apresentação está desacoplada das outras camadas, o que significa que uma única view pode servir a vários controllers ou um único controller pode ter como apresentação várias views diferentes em ocasiões diferentes.

Eu poderia citar vários outros exemplos (inclusive da ótima integração da camada de apresentação com as outras camadas do framework), mas as outras dicas ficarão diluídas nos próximos artigos. Porém, não poderia deixar esse artigo sem código. Como todo bom programador, vamos ao que interessa:

Exemplo 1 - View gerando HTML

Digamos que possuímos um sistema de inscrição em eventos e precisamos listar uma tabela com o nome do participante inscrito e assinalar se ele pagou ou não a inscrição. Dessa forma, possuímos um model (por exemplo, models/db.py) parecido com:

db.define_table('participante',
    Field('nome', 'string', length=250),
    # inclua outros campos aqui
    Field('pagou', 'boolean'),
)

Então precisamos de um controller que seleciona os nomes dos inscritos e o estado do pagamento. Vamos chamar esse controller de controllers/usuarios.py:

def tabela_de_pagamentos():
    inscritos = db().select(db.participante.nome, db.participante.pagou,
                            orderby=db.participante.nome)
    return {'nome_e_pagamento': inscritos}

Com isso, nossa view será executada em um ambiente onde a variável nome_e_pagamento existe e seu conteúdo é o que foi retornado pelo controller acima. Vamos então criar uma view que gera HTML para o controller acima. Coloque no arquivo views/usuarios/tabela_de_pagamentos.html:

{{if len(nome_e_pagamento) == 0:}}
Não temos usuários cadastrados! :-(
{{else:}}
<table>
  <tr>
    <td> Nome </td>
    <td> Pagou? </td>
  </tr>
{{
for usuario in nome_e_pagamento:
    if usuario.pagou:
        pagou = 'Sim'
    else:
        pagou = 'Não'
    pass
}}
  <tr>
    <td> {{=usuario.nome}} </td>
    <td> {{=pagou}} </td>
  </tr>
{{pass
pass}}

Tudo o que está entre {{...}} será executado como código Python. Além disso, o web2py inclui algumas novas palavras-chave para ajudar no processo de criação de views, são elas:

  • {{=expressão_Python}}: executa expressão_Python e imprime o resultado no local;
  • {{extend 'arquivo.html'}}: utiliza a view arquivo.html como base para essa view;
  • {{include}}: indica onde outras views que utilizam essa como base serão adicionadas.

Pode parecer estranho acima o uso da keyword pass. Elas estão lá por conta de uma definição no sistema de apresentação do web2py: por conta de espaços na camada de apresentação fazerem diferença, seria complicado em todos os casos o programador manter a endentação correta do código.
Para resolver esse problema o web2py então gera um novo código baseado em nossa view, ignorando a endentação que utilizamos - ele auto-endenta o código quando encontra if, for etc. Para voltar um nível, como a endentação na view é ignorada, precisamos utilizar a palavra reservada do Python pass. Note que essa palavra reservada faz parte da linguagem e sua função é apenas "passar" (não executa comando algum) - nesse caso ela serve como um sinal para o web2py voltar um nível na endentação.
Note que como a endentação é feita automaticamente, eu não precisaria ter endentado o código entre {{...}}. Porém nesse caso a endentação não atrapalha o código de retorno da minha view e por motivos de organização deixei o código endentado.

Para o exemplo acima, se acessarmos a URL http://localhost:8000/welcome/usuarios/tabela_de_pagamentos (ou http://localhost:8000/welcome/usuarios/tabela_de_pagamentos.html) teríamos como retorno a view acima processada, como nos dois exemplos abaixo, sem e com dados inseridos na tabela:

View do web2py mostrando tabela de pagamentos vazia
View do web2py mostrando tabela de pagamentos cheia

Nota: utilizei/modifiquei a aplicação welcome (que já vem com o web2py) para fazer esses exemplos.

Exemplo 2 - View gerando CSV

Para os mesmos model e controller acima poderíamos ter uma view que gera um arquivo CSV com os dados, em vez de HTML. Vamos então criar o arquivo views/usuarios/tabela_de_pagamentos.csv:

{{
import cStringIO
csv = cStringIO.StringIO()
nome_e_pagamento.export_to_csv_file(csv)
response.write(csv.getvalue())
csv.close()
}}

Não vou entrar em detalhes do código acima, mas para quem estranhou a presença do objeto response fica a explicação: o web2py, para aumentar a produtividade do desenvolvedor, leva a sério o conceito Don't Repeat Yourself e, por isso, ele mesmo importa automaticamente os objetos de sua API que precisamos utilizar (nesse caso, o objeto response).

Feito o código acima, ao entrarmos em http://localhost:8000/welcome/usuarios/tabela_de_pagamentos.csv será feito o download de um arquivo CSV. Abrindo, teremos:

Arquivo CSV gerado pela view do web2py sendo visto no OpenOffice.org

Como disse acima, poderíamos utilizar outras extensões nas views e gerar quaisquer tipos de arquivos que quisermos com código Python. Em uma aplicação que desenvolvi há alguns meses precisei criar views que gerassem PDF (para relatórios sob demanda) e tudo funcionou perfeitamente.
Espero que os exemplos acima tenham explicitado o poder e flexibilidade que esse sistema de apresentação possui. Dúvidas? Entre na lista brasileira de usuários web2py e perguntem-me lá!

8 comentários:

  1. Muito bom. Gosto muito da simplicidade do web2py e o texto está muito leve e didático.

    Luiz Guilherme

    ResponderExcluir
  2. Olá Álvaro, acredito que tenha alguns comentários interessantes a levantar, comentando seus bullets de cima pra baixo:
    1. Não aprender uma nova linguagem é legal, mas a idéia dos frameworks é que um designer não sabe programar, mas se esforçaria um pouquinho pra fazer o código do template, então ele aprende uma linguagem de template simples e descomplicada.
    2. Eu acho interessante que seja mais rápida a execução, mas você já testou lado a lado? Há linguagens de templates com velocidade absurda (comparações http://www.makotemplates.org/).
    3. A limitação vem da idéia de utilizar designers pro template, e realmente um desafio balancear o poder dado a camada de apresentação, pois com muito poder facilmente coisas que deveriam ser feitas na camada de controle é feita na apresentação.
    4. A criação de qualquer formato é garantida pelo Django e diversos outros frameworks, você não é obrigado a utilizar views em HTML.
    5. Não sei se isso é bom o ruim, preciso testar pra ver a vantagem, hehe, essa realmente não tenho o que falar contra.

    Outro detalhe: pode parecer estranho o uso da palavra pass, não só pode, como é estranhissimo. Outro dia o Luciano Ramalho comentou na lista: "Se você quer mesmo embutir código server-side no HTML eu recomendo
    usar PHP. Sem sacanagem."

    Meus dois centavos, espero sinceramente que eles não te ofendam por contrariarem seus argumentos.

    Abraço,

    ResponderExcluir
  3. Adicionando ao que o Cabello comentou, também concordo com os pontos levantados por ele, mas entendo que tais características sejam decisões de design para o framework.

    Usar o pass é bem estranho mesmo, acho que talvez você e o Massimo devem até concordar nisso, mas é um efeito colateral da decisão de adotar o Python (como controlar a indentação?).

    Não acho a questão do desempenho tão relevante assim. Eu não me adequei muito ao estilo web2py de ser, mas o framework é, sem sombra de dúvidas, uma ferramenta sensacional =)

    ResponderExcluir
  4. Álvaro,

    Parabéns pelo artigo !. Acredito que as diferenças e a diversidade de escolhas na questão de frameworks web em python só servem para mostrar todo o poder e o pontencial dessa linguagem que tanto amamos. Bom, como sou suspeito para falar do Web2Py, pois uso ele para fazer os sistemas e sites dos meus clientes, fica dificil elogiar mais ainda...É isso ai !, vamos divulgar essa sensacional alternativa chamada Web2Py.

    Ob: Agora no segundo semestre tu vais ter mais um parceiro nessa divulgação, mas depois eu te passo maiores detalhes.

    Um grande abraço,
    Leandro.

    ResponderExcluir
  5. Vamos às minhas opiniões. :-)
    Danilo, sobre os pontos que você destacou:

    > 1. Não aprender uma nova linguagem é legal, mas a idéia dos frameworks é que um designer não sabe programar, mas se esforçaria um pouquinho pra fazer o código do template, então ele aprende uma linguagem de template simples e descomplicada.

    Concordo. Nesses casos você pode combinar com seu designer de resolver tudo no controller e passar apenas as variáveis para ele imprimir usando "{{=nome_da_variavel}}" ou, no máximo, ter que usar if/elif/else.


    > 2. Eu acho interessante que seja mais rápida a execução, mas você já testou lado a lado? Há linguagens de templates com velocidade absurda (comparações http://www.makotemplates.org/).

    Conheço a mako. Já li comparações sim (nunca fiz) e o web2py saiu melhor. De qualquer forma, o web2py não *interpreta* o código da view: ele gera um código em Python puro que representa aquela view, compila para bytecode e então execute esse código - isso, sem sombra de dúvidas, é mais rápido do que interpretar a view sempre e para ambientes de produção acho necessário.


    > 3. A limitação vem da idéia de utilizar designers pro template, e realmente um desafio balancear o poder dado a camada de apresentação, pois com muito poder facilmente coisas que deveriam ser feitas na camada de controle é feita na apresentação.

    Sobre os designers, já falei acima. O que minha experiência diz é que nem sempre o designer cuida do HTML/CSS etc., então se isso ficar a cargo do programador, é melhor que ele tenha mais poder na view (se o designer for realmente trabalhar na view, é só usar o que comentei acima). Sobre fazer na view coisas que deveriam estar no controller, aí depende do desenvolvedor: somos adultos, certo? É o caso de eu dar a alguém uma faca (que é uma ferramenta); essa pessoa pode utilizar a ferramenta de maneira adequada (auxiliando no corte de objetos etc.) ou não (se cortando). Enfim, não acho que isso seja um ponto negativo, já que o desenvolvedor *precisa* conhecer MVC.

    > 4. A criação de qualquer formato é garantida pelo Django e diversos outros frameworks, você não é obrigado a utilizar views em HTML.

    Mesmo JPEG, PDF etc. (na view)? Sem fazer nenhum processamento com relação a criar o JPEG, PDF etc. no controller?


    > 5. Não sei se isso é bom o ruim, preciso testar pra ver a vantagem, hehe, essa realmente não tenho o que falar contra.

    É bom. :-) Você pode retornar os posts de um blog e exibí-los na index.html de seu site ou no feed.rss - mesmo controller, duas views.

    > Outro detalhe: pode parecer estranho o uso da palavra pass, não só pode, como é estranhissimo. Outro dia o Luciano Ramalho comentou na lista: "Se você quer mesmo embutir código server-side no HTML eu recomendo usar PHP. Sem sacanagem."

    Ok, o uso do pass é realmente estranho, porém, para obter as vantagens acima, acho necessário - já que seria *muito ruim* ter que se preocupar com endentação dentro da view.
    Sobre o que Luciano falou, não concordo, porque o web2py não tem só a camada de view: tem o model, controller e diversas outras ferramentas que auxiliam o desenvolvedor. Nesse caso, caímos novamente na briga "view poderosa versus view sem poder" - e aí entram minhas duas opiniões acima (designer e faca).


    > Meus dois centavos, espero sinceramente que eles não te ofendam por contrariarem seus argumentos.

    Ofender? Claro que não! Estamos aqui para expressar nossas opiniões. ;-)

    Abraços.

    ResponderExcluir
  6. Bem legal o seu post, com alguns exemplos eu já entendi todo o propósito do web2py e como ele ataca os problemas comuns a todos.

    Conheço pessoas que gostam de poder na view e gente que não gosta, bom, oque eu não gosto é de gente que usa view poderosa de forma porca, e não da view em si ser poderosa ;)

    Uma pergunta/sugestão, se o pass é necessário dessa forma, porque não incluir ele automaticamente na compilação da view?

    ResponderExcluir
  7. Arkanjuca,
    o problema é que não existe uma forma de saber onde você vai colocar o "pass". Por exemplo:

    for a in b:
    if a > 10:
    a = 20
    a = 30
    c = 50

    Para esse mesmo código acima, poderíamos ter os dois "pass" (do if e do for) colocados em vários lugares - e a colocação em lugares diferentes altera totalmente a lógica.
    Se o web2py entendesse endentação dentro da view, você não precisaria usar o pass, porém usar endentação dentro da view seria algo bastante ruim de se manter/ler, já que dependendo de qual linguagem você esteja usando na view (HTML, CSS etc.), os espaços podem fazer a diferença.

    ResponderExcluir
  8. Alvaro como vai. Esse post é meio antigo, mas acredito que quase nada mudou desta versão do web2py para 2017.

    Minha dúvida:
    Tenho um layout.html que será extendido por uma view index.html.
    Porém quando faço um include de uma view que extende outro como base, ele não exibe a view incluida.

    Veja o esquema.
    views
    layout.html
    default/
    index.html
    subcomponente.html
    componentes
    base_componente.html
    Index.html extende de layout.html e inclui subcomponente.html que estende de componentes/base_componente.html

    Ele não exibe a view incluída.

    ResponderExcluir