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.
Aqui temos o fluxo de funcionamento básico de como um usuário poderia usar um wizard:
Esta aplicação manipula tantos mecanismos para você quanto possível. Geralmente, você só tem que fazer estas coisas:
o primeiro passo para criar um “form wizard” é criar as classes Form. Estes devel ser classses django.forms.Form padrão conforme explicado 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.
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:
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.
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:
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:
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.
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])),
)
Além do método done(), o FormWizard oferece 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 é 0 e o segundo form é 1.)
Dado um passo, retorna um prefixo de Form para 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)
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.'})
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)
Um hook para salvar o estado do objeto request e args / kwargs que 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]
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 template select_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']
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".
Um hook para modificar o estado interno do wizard, dado uma totalidade de objetos Form validados. 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_context ou dinamicamente alterar o self.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):
# ...
Initial data for a wizard's Form objects can be provided using the optional initial keyword argument. This argument should be a dictionary mapping a step to a dictionary containing the initial data for that step. The dictionary of initial data will be passed along to the constructor of the step's Form:
>>> from testapp.forms import ContactForm1, ContactForm2, ContactWizard
>>> initial = {
... 0: {'subject': 'Hello', 'sender': 'user@example.com'},
... 1: {'message': 'Hi there!'}
... }
>>> wiz = ContactWizard([ContactForm1, ContactForm2], initial=initial)
>>> form1 = wiz.get_form(0)
>>> form2 = wiz.get_form(1)
>>> form1.initial
{'sender': 'user@example.com', 'subject': 'Hello'}
>>> form2.initial
{'message': 'Hi there!'}
Dec 26, 2011