Form wizard¶
O Django vem acompanhado uma aplicação opicional “form wizard” que divide
formulários em várias páginas Web. Ele mantém o
estado num hash HTML de campos <input type="hidden">, e os dados não são
processados no lado do servidor até que a última parte do formulário seja
submetida.
Você pode querer usar isto com formulários muito compridos que seriam desagradáveis de se mostrar numa única página. A primeira página pode pedir ao usuário informações centrais, a segunda coisas menos importantes, e assim por diante.
o termo “wizard” (em português “assistente”) neste contexto, é explicado na Wikipedia.
Como ele funciona¶
Aqui temos o fluxo de funcionamento básico de como um usuário poderia usar um wizard:
- O usuário visita a primeira página do wizard, preenche o formulário e o submete.
- O servidor valida os dados. Se eles forem inválidos, o formulário é
mostrado novamente, com mensagens de erro. Se for válido, o servidor
calcula um hash seguro dos dados e apresenta ao usuário o próximo
formulário, poupando os dados validados e o hash em campos
<input type="hidden">. - Repete o passo 1 e 2, para todos os formulários subsequentes do wizard.
- Um vez que o usuário tenha submetido todos os formulários e todos os dados estejam validados, o wizard processa os dados – salvando-os no banco de dados, enviando um e-mail, ou seja lá o que for que a aplicação precise fazer.
Uso¶
Esta aplicação manipula tantos mecanismos para você quanto possível. Geralmente, você só tem que fazer estas coisas:
- Definir um número de classes
django.formseForm– uma por página do wizard. - Criar uma classe
FormWizardque especifica o que fazer uma vez que todos os seus formulários foram submetidos e validados. Isso também permite você sobrescrever alguns comportamentos do wizard. - Criar alguns templates que renderizam formulários. Você pode definir um único template genérico para manipular todos os formulários, ou definir um template específico para cada formulário.
- Apontar seu URLconf para sua classe
FormWizard.
Definindo classes Form¶
o primeiro passo para criar um “form wizard” é criar as classes
Form. Estes deveriam ser padronizados com as
classes django.forms e Form, explanados na
documentação do forms.
Estas classes podem estar em qualquer lugar de sua base de código, mas por
convenção são postas num arquivo chamado forms.py dentro da sua
aplicação.
Por exemplo, vamos escrever um wizard “contact form”, onde a primeira página
coleta o endereço de email do remetente e o assunto, e a segunda página coleta
a mensagem em si. Aqui tem um exemplo de como o forms.py pode ficar:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Limitação importante: Pelo wizard usar campos HTML hidden para armazenar
dados entre as páginas, você não pode incluir um
FileField em qualquer outra página, a não ser na
última.
Criando uma classe FormWizard¶
O próximo passo é criar uma classe
FormWizard, que deveria ser uma
subclasse do django.contrib.formtools.wizard.FormWizard.
Assim como suas classes Form, esta classe
FormWizard pode estar em qualquer
lugar da sua base de código, mas por convenção é colocado no forms.py.
O único requerimento desta subclasse é que ela implementa um método
done(), que especifica o que
deve acontecer quando todos os formulários foram submetidos e validados. A
este método é passado dois argumentos:
request– um objetoHttpRequestform_list– uma lista de classesdjango.formsForm
Neste exemplo simplista, ao invés de executar qualquer operação de banco de dados, o método simplesmente renderiza um template com os dados validados:
from django.shortcuts import render_to_response
from django.contrib.formtools.wizard import FormWizard
class ContactWizard(FormWizard):
def done(self, request, form_list):
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Note que este método será chamado via POST, então ele realmente deveria ser
um bom cidadão Web e redirecionar depois de processar os dados. Aqui tem outro
exemplo:
from django.http import HttpResponseRedirect
from django.contrib.formtools.wizard import FormWizard
class ContactWizard(FormWizard):
def done(self, request, form_list):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
Veja a seção Métodos avançados do FormWizard abaixo para aprender mais sobre
os hooks do FormWizard.
Criando templates para formulários¶
Agora, precisamos criar um template que renderize os forumlários do wizard. Por
padrão, todo formulário usa um template chamado forms/wizard.html. (Você
pode mudar este nome sobrescrevendo o método
get_template(), que está documentado
abaixo. Este hook também permite você usar um template diferente para cada
formulário.)
Este template aguarda o seguinte contexto:
step_field– O nome do campo hidden contento o passo.step0– O passo atual (partindo de zero).step– O passo atual (partindo de um).step_count– O total de passos.form– A instância doFormpara o passo atual (vazio ou com erros).previous_fields– Uma string representando todos os campos de dados anteriores, mais os hashes de formulários completados, todos em campos hidden de formulário. Note que você precisará executar isto através do template filtersafe(), para previnir o auto escape, por ele é um HTML puro.
Ele também será transmitido a quaisquer objetos no extra_context, que é
um dicionário que você pode especificar contendo valores extra a serem
adicionados ao contexto.
Você pode especificá-lo de duas formas:
- Sete o atributo
extra_contextna sua subclasseFormWizardpara um dicionário. - Passe
extra_contextcomo parâmetros extra no URLconf.
Aqui tem um exemplo de template completo:
{% extends "base.html" %}
{% block content %}
<p>Step {{ step }} of {{ step_count }}</p>
<form action="." method="post">
<table>
{{ form }}
</table>
<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
{{ previous_fields|safe }}
<input type="submit">
</form>
{% endblock %}
Note que previous_fields, step_field e step0 são todos obrigatórios
para o wizard funcionar perfeitamente.
Ligando o wizard num URLconf¶
Finalmente, dê ao seu novo objeto
FormWizard uma URL no urls.py. O
wizard recebe uma lista de seus objetos form como argumentos:
from django.conf.urls.defaults import *
from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard
urlpatterns = patterns('',
(r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
)
Métodos avançados do FormWizard¶
-
class
FormWizard¶ Além do método
done(), oFormWizardoferece alguns métodos avançados que permitem você customizar o funcionamento do seu wizard.Alguns destes métodos recebem um argumento
step, que é um contador, partindo de zero, representando o passo corrente do wizard. (E.g., o primeiro form é0e o segundo form é1.)
-
FormWizard.prefix_for_step()¶ Dado um passo, retorna um prefixo de
Formpara uso. Por padrão, este simplesmente usa o passo em si. Para mais, veja a documentação do prefixo de formulário. Implementação padrão:def prefix_for_step(self, step): return str(step)
-
FormWizard.render_hash_failure()¶ Renderiza um template se a verificação do hash falhar. É raro você precisar sobrescrever este método.
Implementação padrão:
def render_hash_failure(self, request, step): return self.render(self.get_form(step), request, step, context={'wizard_error': 'Desculpe-nos, mas seu formulário expirou. Por favor continue preenchendo o formulário desta página.'})
-
FormWizard.security_hash()¶ Calcula o hash de segurança para um dado objeto request e instância de
Form.Por padrão, ele usa um hash MD5 dos dados do formulário e sua configuração
SECRET_KEY. É raro alguém precisar sobrescrever isso.Exemplo:
def security_hash(self, request, form): return my_hash_function(request, form)
-
FormWizard.parse_params()¶ Um hook para salvar o estado do objeto request e
args/kwargsque foram capiturados da URL pelo seu URLconf.Por padrão, isso não faz nada.
Exemplo:
def parse_params(self, request, *args, **kwargs): self.my_state = args[0]
-
FormWizard.get_template()¶ Retorna o nome do template que deveria ser usado para um certo passo.
Por padrão, ele retorna
'forms/wizard.html', indiferente do passo.Exemplo:
def get_template(self, step): return 'myapp/wizard_%s.html' % step
Se o
get_template()retorna uma lista de strings, então o wizard usará a função do sistema de templateselect_template(), explanada na documentação de template. Isto significa que o sistema usará o primeiro template que existir no sistema de arquivo. Por exemplo:def get_template(self, step): return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html']
-
FormWizard.render_template()¶ Renderiza o template para o dado passo, retornando um objeto
HttpResponse.Sobrescreva este método se você deseja adicionar um contexto customizado, retornar um tipo MIME diferente, etc. Se você somente precisa sobrescrever o nome do template, use o método
get_template().O template será renderizado com o contexto documentado na seção acima “criando templates para formulários”.
-
FormWizard.process_step()¶ Um hook para modificar o estado interno do wizard, dado uma totalidade de objetos
Formvalidados. O Form está garantido a ter dados limpos e válidos.Este método não deve modificar qualquer dado. Ao invés disso, ele pode querer setar um
self.extra_contextou dinamicamente alterar oself.form_list, baseado nos formulários submetidos anteriormente.Note que este método é chamado toda vez que uma página é renderizada para todos os passos submetidos.
A assinatura da função:
def process_step(self, request, form, step): # ...