16 de novembro de 2010

Fixtures no Web2py

Uma das coisas boas dos frameworks modernos é trabalhar com fixtures.

Se você ainda não sabe, fixtures são a massa de dados inicial do seu banco de dados. Aqueles registros que você inclui só pra testar suas consultas, lembra? Pois é; são fixtures. E dá um trabalho danado incluir novamente toda vez que você precisa mudar alguma coisa no seu banco de dados, né? As fixtures estão aí pra ajudar exatamente nisso.

O esquema é o seguinte: você define um conjunto de dados inicial. Daí, toda vez que as tabelas são criadas, os dados são inseridos automaticamente, pra você não ter que popular tudo na mão novamente.

Isso te dá liberdade (e segurança) de apagar tudo e recomeçar sem sujeira nas tabelas. A qualquer momento você pode esvaziar ou recriar tudo. Sua massa de teste estará lá novamente. É como se fosse um "quick restore" dos seus dados.

Posso dizer que é uma mão na roda, mas é preciso tomar alguns cuidados para não se perder.

Eu usei fixtures pela primeira vez no Django e demorou um pouco até cair a ficha sobre como isso funciona no web2py. De tão fácil que é, às vezes a gente nem percebe.

Você pode trabalhar com fixtures no Web2py de 3 formas:
a) Incluindo as linhas via script que é executado automaticamente;
b) Importando de um arquivo CSV;
c) Mesclando as duas formas anteriores.

Os scripts do diretório my_app/models são executados em ordem alfabética sempre que sua aplicação é requisitada. Ou seja, a cada request. Portanto, ali é um bom lugar para colocar scripts que fazem alguma configuração ou preparação para sua aplicação rodar. Bem, as fixtures fazem exatamente isso, certo? Elas preparam a massa de dados de sua aplicação. Portanto, podemos iniciar as fixtures chamando-as a partir do script my_app/models/z_fixtures.py, por exemplo.

Nesse script, você pode incluir os dados iniciais se suas tabelas estiverem vazias, fazendo o seguinte teste:
if db(db.produtos.id>0).count() == 0:
    db.produtos.insert(codigo=1234, nome='martelo')
    db.produtos.insert(codigo=1235, nome='parafuso')
    db.commit()

Fazendo isso para todas as tabelas, você terá seus dados recarregados sempre que esvaziá-las.
A vantagem desse método é que você tem total domínio sobre a forma de inclusão dos dados. A desvantagem é que escreve muito código.

A outra forma de trabalhar com fixtures, é usando o appadmin (my_app/appadmin) para inserir os dados e usar a opção "exportar para csv", que tem na view de listagem dos dados.

A vantagem desse método é que você garante a inclusão de dados que passaram pelos validadores definidos e aproveita a exportação para csv que já vem com o Web2py.
Uma desvantagem é que você vai ter que importar os dados na mão sempre que suas tabelas forem zeradas. A outra é que, dependendo da mexida nas tabelas, suas fixtures podem não carregar por diferença de layout.

Mas, será que existe uma maneira de aproveitar e unir as duas formas abordadas? Sim, claro. O Web2py é flexível e praticamente tudo que é possível fazer pelo appadmin, também é possível fazer via programação.

Então, sugiro você usar o appadmin para inserir os dados, exportá-los para csv pelo próprio appadmin e o método db.minha_tabela.import_from_csv_file() no script my_app/models/z_fixtures.py para importar os dados, se a tabela estiver vazia.

Aproveite e veja os métodos para importar/exportar um banco de dados inteiro. Isso pode te ajudar bastante.

Experimente. Use fixtures e você verá como sua vida fica mais fácil e produtiva.

8 de novembro de 2010

request.now ou datetime.now?

O Web2py tem a variável request.now que você pode usar no lugar de datetime.datetime.now() sem precisar importar nada. Essa variável guarda o horário que a requisição foi executada.

Eu estou fazendo uma aplicação com uma parte rodando via cron e me deparei com uma situação inusitada: todos os registros alterados no processo da cron tinham o mesmo horário de modificação. Isso porque eu usei o update=request.now na definição do campo que guarda a hora da última modificação do registro.

A situação é que, no shell (cron), a requisição ocorre apenas uma vez, no momento que ele é iniciado. Já na web, a requisição ocorre toda vez que uma URL é solicitada pelo browser. Como no shell a característica é executar processos em background, não existe a URL sendo solicitada. Na web, a cada clique de link ou de botão, uma nova requisição é realizada.

Então, o request.now, na verdade, contém o horário que a requisição iniciou.

Para resolver meu problema, eu passei a usar o update=datetime.datetime.now(). Conferindo os resultados depois dessa modificação, me deparei com o mesmo problema: todos os registros alterados pelo processo da cron com o mesmo horário de modificação.

Recorri à lista mundial do web2py e aprendi que esses valores para default e update são avaliados apenas no momento que os models são executados, quando associados a conteúdos fixos ou variáveis.

Para resolver o problema, temos que usar lambda assim: update=lambda:datetime.datetime.now(). Dessa forma, como temos a chamada de uma função (lambda no nosso caso), toda vez que um registro for modificado, esse conteúdo do update será reavaliado.

O mesmo vale para a propriedade default.

Portanto, use os parâmetros default e update com lambda, para que atualizem o conteúdo no momento de inserção/alteração do registro.

Segue um exemplo:

from datetime import datetime
db.define_table('ficha',
    Field('nome', 'string'),
    Field('quando', 'datetime', default=lambda:datetime.now())