Formulários inteligentes no Pylons com WTForms

abr 27 2010

Um dos recursos mais poderosos do Django é o Django Forms, que permitem gerenciar de forma inteligente formulários, seja em relação à obtenção dos dados ou validação dos mesmos. Utilizando os forms do Django, tudo fica mais simples :) Conforme abordado no post onde mostrei a construção de um CRUD em Pylons, vê-se que o Pylons também tem suporte ao uso de formulários de forma inteligente, mas eu ainda acho o método Django de trabalhar mais intuitivo. Acredito que foi pensando nisso que criaram o WTForms…

O WTForms é um framework de nome simpático utilizado para construção e gerenciamento de formulários em Python. Pode ser integrado com qualquer um dos zilhões de frameworks web Python (Django, web2py, Grok, TurboGears, whatever) e é fortemente baseado bastante parecido (errata aqui: o WTForms existe a mais tem que o Django Forms, minhas sinceras desculpas aos criadores do WTForms, em especial ao Thomas Johansson, que me alertou sobre meu erro) com o Django Forms, um esquema de formulários do Django que é super bacana :)

Basicamente, o esquema de funcionamento do WTForms é o seguinte: você define um formulário estendendo a classe wtforms.Form e define os campos do formulário utilizando as classes *Field. Abaixo você pode ver um exemplo de formulário definido no WTForms, retirado do site oficial:

from wtforms import Form, BooleanField, TextField, validators

class RegistrationForm(Form):
username = TextField('Username', [validators.Length(min=4, max=25)])
email = TextField('Email Address', [validators.Length(min=6, max=35)])
accept_rules = BooleanField('I accept the site rules', [validators.Required()])

Para quem conhece Django, isso parece piada, é praticamente a mesma coisa. Como já citei a cima, o WTForms é semelhante ao Django Forms, então tudo é bastante parecido :)

Agora que já fomos devidamente apresentados ao WTForms, podemos começar a integração dele com o Pylons.

Atenção: Se você não conhece o Pylons, é bom parar por aqui e ler um post introdutório sobre Pylons. Após ler este post, você poderá seguir em frente com o Pylons e o WTForms.

Um exemplo que eu gosto sempre de usar é o de criação de um sistema acadêmico (nem sei informar exatamente o porquê =P). Assumindo então que tenhamos o projeto wtforms_pylons_academico criado (com suporte ao Mako e ao SQLAlchemy), vamos definir o model Aluno, o formulário AlunoForm, o controller AlunosController e a action cadastrar_aluno que será utilizada para o cadastro deste aluno.

Vamos então seguir os passos acima, sem muita pressa =P Primeiro, vamos definir nossa classe de modelo, chamada Aluno. Um aluno terá apenas um nome e uma data de nascimento (nada complexo demais, para andarmos rápido =P):

aluno_table = sa.Table('alunos', metadata,
sa.Column('id', sa.Integer, primary_key = True),
sa.Column('nome', sa.String(100), nullable = False),
sa.Column('data_nascimento', sa.Date, nullable = False))

class Aluno(object):
"""docstring for Aluno"""
def __init__(self, nome = None, data_nascimento = None):
super(Aluno, self).__init__()
self.nome = nome
self.data_nascimento = data_nascimento

orm.mapper(Aluno, aluno_table)

Bom, o formulário pode ficar em diversas partes do sistema. Um local interessante para armazená-lo pode ser em um módulo chamado forms dentro do pacote model. O código do formulário fica sendo este, super simples também:

#coding:utf-8

from wtforms import Form, TextField, validators
from wtforms.validators import ValidationError
import wtforms

class AlunoForm(Form):
nome = TextField('Nome', [validators.Required(), validators.Length(min=3, max=200)])
data_nascimento = TextField('Data de nascimento', [validators.Required()])

def validate_data_nascimento(form, field):
import time
try:
time.strptime(field.data, '%Y-%m-%d')
except ValueError:
raise ValidationError('Formato inválido de data')

Veja que utilizamos dois tipos de validators: os built-in do WTForms e também um personalizado, para validar a data. Já definimos o modelo e a classe de formulário, com todas as validações já configuradas. Agora, basta seguir para o próximo passo e criar nosso controller com uma action para o cadastro de aluno. Nesta action, vamos exibir o formulário e receber, em outra action, os dados deste formulário para que eles sejam salvos no banco de dados. Criamos então nosso AlunosController. O código do controller Aluno com a action cadastrar_aluno ficou o seguinte:

class AlunosController(BaseController):

def index(self):
# Return a rendered template
#return render('/alunos.mako')
# or, return a response
return 'Hello World'

def cadastrar_aluno(self):
c.form = AlunoForm()
return render('/novo_aluno.mako')

Assim, basta criar o template novo_aluno.mako dentro do diretório templates com o seguinte código para visualizar o formulário de cadastro. O código do template ficou da seguinte forma:

 

<form action="/alunos/cadastrar" accept-charset="utf-8" method="post">
<div>% if c.form.nome.errors:&nbsp;
<div class="erros">% for error in c.form.nome.errors:
${ error }
% endfor</div>
% endif
${ c.form.nome.label }: ${ c.form.nome }

</div>
<div>% if c.form.data_nascimento.errors:&nbsp;
<div class="erros">% for error in c.form.data_nascimento.errors:
${ error }
% endfor</div>
% endif
${ c.form.data_nascimento.label }: ${ c.form.data_nascimento }

</div>
<input type="submit" value="Continue →" />&nbsp;

</form>

Note que nas linhas onde exibimos o label e o campo (17 e 28), utilizamos dois filtros do Mako: o filtro n remove qualquer filtro padrão, e o filtro unicode converte o objeto em unicode para exibição na tela. O uso destes filtros foi necessário para que as tags HTML label e input não fossem interpretadas como html entities. Uma alternativa a esta abordagem seria configurar no arquivo de configuração config/environment.py para não chamar a função escape nos filtros padrão, na seguinte linha:

input_encoding='utf-8', default_filters=['escape'],

Note a variável default_filters, que posso a lista dos filtros utilizados por padrão. A função escape invoca faz um escape (uau!) em tags HTML e diversos caracteres. Na nova versão do WTForms (0.6) este work-around não é mais necessário. (:

Bom, como vimos em nosso template, estamos fazendo a submissão do formulário para o path /alunos/cadastrar. Isso exige de nós que criemos uma action cadastrar em nosso controller Alunos:

def cadastrar(self):
c.form = AlunoForm(request.params)
if request.method == 'POST' and c.form.validate():
aluno = Aluno()
aluno.nome = c.form.nome.data
time_data_nascimento = time.strptime(c.form.data_nascimento.data, '%Y-%m-%d')
aluno.data_nascimento = date(time_data_nascimento.tm_year, time_data_nascimento.tm_mon, time_data_nascimento.tm_mday)
Session.add(aluno)
Session.commit()
else:
return render('/novo_aluno.mako')
return 'Aluno inserido com sucesso!'

Apenas uma pequena dor de cabeça com a data de nascimento: tivemos que converter a string armazenada no formulário para um objeto do tipo date. Esta não é a melhor forma de fazer isso, mas foi a mais prática aqui. O ideal seria criar um DateField que já trata-se toda esta questão para nós :)

Bom, esse foi um tutorial básico de uso do WTForms. Futuramente, posso mostrar como fazer um refactoring neste mesmo exemplo para utilizar de forma mais inteligente os recursos do WTForms, criando um DateField para já tratar a nossa data.

O código completo do projeto pode ser encontrado no Github: http://github.com/fsouza/integracao-pylons-wtforms.

3 responses so far

  1. [...] This post was mentioned on Twitter by Henrique Pereira, Francisco Souza. Francisco Souza said: Formulários inteligentes no Pylons com WTForms: http://bit.ly/b9ktY2 #blogpost [...]

  2. [...] This post was mentioned on Twitter by Francisco Souza. Francisco Souza said: @prencher I fixed the blogpost ( http://is.gd/bQ5wv ) and pushed changes to github ( http://is.gd/bQ5vP ). Thanks a lot ;) [...]

  3. [...] validators do WTForms, que abordei no blogpost anterior. Assim, recomendo a leitura do blogpost de integração do WTForms com o Pylons para ver um pouco mais dos [...]

Leave a Reply

Spam protection by WP Captcha-Free