Chapter 7: Formulários e validadores
Formulários e validadores
Existem quatro maneiras distintas de construir formulários no web2py:
FORM
fornece uma implementação de baixo nível em termos de ajudantes HTML. Um objetoFORM
pode ser serializado em HTML e está ciente dos campos que ele contém. Um objetoFORM
sabe como validar valores de formulário enviados.SQLFORM
fornece uma API de alto nível para criar formulários de criação, atualização e exclusão a partir de uma tabela de banco de dados existente.SQLFORM.factory
é uma camada de abstração no topoSQLFORM
para aproveitar os recursos de geração de formulários, mesmo que não haja nenhum banco de dados presente. Ele gera um formulário muito semelhante aoSQLFORM
a partir da descrição de uma tabela, mas sem a necessidade de criar a tabela de banco de dados.- Métodos
CRUD
. Estes são funcionalmente equivalentes ao SQLFORM e são baseados no SQLFORM, mas fornecem uma notação mais compacta;CRUD
é agora preterido em favor deSQLFORM.grid()
eSQLFORM.smartgrid()
.
Todas essas formas são autoconscientes e, se a entrada não passar na validação, elas podem se modificar e adicionar mensagens de erro. Os formulários podem ser consultados para as variáveis validadas e para mensagens de erro que foram geradas por validação.
Código HTML arbitrário pode ser inserido ou extraído do formulário usando auxiliares.
FORM
e SQLFORM
são ajudantes e eles podem ser manipulados de forma semelhante como o DIV
. Por exemplo, você pode definir um estilo de formulário:
form = SQLFORM(..)
form['_style']='border:1px solid black'
FORM
form accepts formname
Considere como um exemplo teste aplicação com o seguinte controlador "default.py":
def display_form():
form = FORM('Your name:', INPUT(_name='name'), INPUT(_type='submit'))
return dict(form=form)
e a vista "default/display_form.html" associada:
{{extend 'layout.html'}}
<h2>Input form</h2>
<form enctype="multipart/form-data"
action="{{=URL()}}" method="post">
Your name:
<input name="name" />
<input type="submit" />
</form>
<h2>Submitted variables</h2>
{{=BEAUTIFY(request.vars)}}
Este é um formulário HTML regular que pede o nome do usuário. Quando você preencher o formulário e clicar no botão enviar, o formulário será enviado automaticamente e a variável request.vars.name
juntamente com o valor fornecido é exibido na parte inferior.
Você pode gerar o mesmo formulário usando ajudantes. Isso pode ser feito na exibição ou na ação. Como o web2py processou o formulário na ação, é melhor definir o formulário na própria ação.
Aqui está o novo controlador:
def display_form():
form=FORM('Your name:', INPUT(_name='name'), INPUT(_type='submit'))
return dict(form=form)
e a vista "default/display_form.html" associada:
{{extend 'layout.html'}}
<h2>Input form</h2>
{{=form}}
<h2>Submitted variables</h2>
{{=BEAUTIFY(request.vars)}}
O código até agora é equivalente ao código anterior, mas o formulário é gerado pela instrução {{=form}}
que serializa o FORM
objeto.
Agora, adicionamos um nível de complexidade adicionando validação e processamento de formulários.
Altere o controlador da seguinte forma:
def display_form():
form=FORM('Your name:',
INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if form.accepts(request, session):
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
else:
response.flash = 'please fill the form'
return dict(form=form)
e a vista "default/display_form.html" associada:
{{extend 'layout.html'}}
<h2>Input form</h2>
{{=form}}
<h2>Submitted variables</h2>
{{=BEAUTIFY(request.vars)}}
<h2>Accepted variables</h2>
{{=BEAUTIFY(form.vars)}}
<h2>Errors in form</h2>
{{=BEAUTIFY(form.errors)}}
Notar que:
- Na ação, adicionamos o
requires=IS_NOT_EMPTY()
validador para o campo de entrada "nome". - Na ação, adicionamos uma chamada para
form.accepts(..)
- Na visão, estamos imprimindo
form.vars
eform.errors
bem como a forma erequest.vars
.
Todo o trabalho é feito pelo método accepts
do objeto form
. Filtra o request.vars
de acordo com os requisitos declarados (expressos por validadores). accepts
armazena essas variáveis que passam validação em form.vars
. Se um valor de campo não atender a um requisito, o validador com falha retornará um erro e o erro será armazenado em form.errors
. Ambos form.vars
e form.errors
estamos gluon.storage.Storage
objetos semelhantes a request.vars
. O primeiro contém os valores que passaram na validação, por exemplo:
form.vars.name = "Max"
Este último contém os erros, por exemplo:
form.errors.name = "Cannot be empty!"
A assinatura completa do método accepts
é o seguinte:
form.accepts(vars, session=None, formname='default',
keepvalues=False, onvalidation=None,
dbio=True, hideerror=False):
O significado dos parâmetros opcionais é explicado nas próximas subseções.
O primeiro argumento pode ser request.vars
ou request.get_vars
ou request.post_vars
ou simplesmente request
. Este último é equivalente a aceitar como entrada request.post_vars
.
o accepts
função retorna True
se o formulário for aceito e False
de outra forma. Um formulário não é aceito se tiver erros ou quando não tiver sido enviado (por exemplo, na primeira vez em que for exibido).
Aqui está como esta página aparece na primeira vez que é exibida:
Aqui está como parece a submissão inválida:
Aqui está como parece uma submissão válida:
os métodos process
e validate
Um atalho para
form.accepts(request.post_vars, session, ...)
é
form.process(...).accepted
este último não precisa do request
e session
argumentos (embora você possa especificá-los opcionalmente). também difere de accepts
porque retorna o próprio formulário. Internamente process
chamadas aceita e passa seus argumentos para ele. O valor retornado por aceita é armazenado em form.accepted
.
A função do processo leva algum argumento extra que accepts
não leva:
message_onsuccess
onsuccess
: se for igual a "flash" (padrão) e o formulário for aceito, o flash será exibidomessage_onsuccess
message_onfailure
onfailure
: se igual a 'flash' (padrão) e o formulário falha na validação, ele irá piscar o código acimamessage_onfailure
next
indica onde redirecionar o usuário após o formulário ser aceito.onsuccess
eonfailure
pode ser funções comolambda form: do_something(form)
.
form.validate(...)
é um atalho para
form.process(..., dbio=False).accepted
Campos condicionais
Há momentos em que você deseja que um campo apareça apenas se uma condição for atendida. Por exemplo, considere o seguinte modelo:
db.define_table('purchase', Field('have_coupon', 'boolean'), Field('coupon_code'))
Você deseja exibir o campo coupon_code
se e somente se o have_coupon
campo está marcado. Isso pode ser feito em JavaScript. O web2py pode ajudá-lo, gerando esse JavaScript para você. Você só precisa declarar que o campo é condicional a uma expressão usando o campo show_if
atributo:
def index():
db.purchase.coupon_code.show_if = (db.purchase.have_coupon==True)
form = SQLFORM(db.purchase).process()
return dict(form = form)
O valor de show_if
é uma consulta e usa a mesma sintaxe DAL que você usa para consultas de banco de dados. A diferença é que essa consulta não é enviada ao banco de dados, mas é convertida em JavaScript e enviada ao navegador, onde é executada quando o usuário edita o formulário.
Campos ocultos
Quando o objeto de formulário acima é serializado por {{=form}}
e por causa da chamada anterior para o método accepts
, agora parece com isso:
<form enctype="multipart/form-data" action="" method="post">
your name:
<input name="name" />
<input type="submit" />
<input value="783531473471" type="hidden" name="_formkey" />
<input value="default" type="hidden" name="_formname" />
</form>
Observe a presença de dois campos ocultos: "_formkey" e "_formname". Sua presença é desencadeada pela chamada para accepts
e eles desempenham dois papéis diferentes e importantes:
- O campo oculto chamado "_formkey" é um token único que o web2py usa para impedir o envio duplo de formulários. O valor dessa chave é gerado quando o formulário é serializado e armazenado no
session
. Quando o formulário é enviado, esse valor deve corresponder ou entãoaccepts
retornaFalse
sem erros, como se o formulário não tivesse sido enviado. Isso ocorre porque o web2py não pode determinar se o formulário foi enviado corretamente. - O campo oculto chamado "_formname" é gerado por web2py como um nome para o formulário, mas o nome pode ser substituído. Este campo é necessário para permitir páginas que contenham e processem vários formulários. O web2py distingue os diferentes formulários enviados pelos seus nomes.
- Campos ocultos opcionais especificados como
FORM(.., hidden=dict(...))
.
O papel desses campos ocultos e seu uso em formulários personalizados e páginas com vários formulários é discutido em mais detalhes posteriormente no capítulo.
Se o formulário acima for enviado com um campo "nome" vazio, o formulário não passará pela validação. Quando o formulário é serializado novamente, aparece como:
<form enctype="multipart/form-data" action="" method="post">
your name:
<input value="" name="name" />
<div class="error">cannot be empty!</div>
<input type="submit" />
<input value="783531473471" type="hidden" name="_formkey" />
<input value="default" type="hidden" name="_formname" />
</form>
Observe a presença de um DIV de classe "erro" no formulário serializado. O web2py insere essa mensagem de erro no formulário para notificar o visitante sobre o campo que não passou na validação. o método accepts
, após o envio, determina que o formulário é enviado, verifica se o campo "nome" está vazio e se é necessário e, eventualmente, insere a mensagem de erro do validador no formulário.
Espera-se que a visualização "layout.html" base manipule DIVs de classe "error". O layout padrão usa efeitos do jQuery para fazer com que os erros apareçam e deslize para baixo com um fundo vermelho. Veja o Capítulo 11 para mais detalhes.
keepvalues
O argumento opcional keepvalues
informa ao web2py o que fazer quando um formulário é aceito e não há redirecionamento, então o mesmo formulário é exibido novamente. Por padrão, o formulário está desmarcado. E se keepvalues
está configurado para True
, o formulário é pré-preenchido com os valores inseridos anteriormente. Isso é útil quando você tem um formulário que deve ser usado repetidamente para inserir vários registros semelhantes. Se o dbio
argumento está definido para False
, o web2py não executará nenhuma inserção/atualização de banco de dados depois de aceitar o formulário. E se hideerror
está configurado para True
e o formulário contém erros, eles não serão exibidos quando o formulário for renderizado (cabe a você exibi-los de form.errors
de alguma forma. o onvalidation
argumento é explicado abaixo.
onvalidation
o onvalidation
argumento pode ser None
ou pode ser uma função que toma a forma e não retorna nada. Tal função seria chamada e passada no formulário, imediatamente após a validação (se a validação for aprovada) e antes de qualquer outra coisa acontecer. Essa função tem várias finalidades: por exemplo, executar verificações adicionais no formulário e, eventualmente, adicionar erros ao formulário ou calcular os valores de alguns campos com base nos valores de outros campos ou disparar alguma ação (como enviar um email ) antes de um registro ser criado/atualizado.
Aqui está um exemplo:
db.define_table('numbers',
Field('a', 'integer'),
Field('b', 'integer'),
Field('c', 'integer', readable=False, writable=False))
def my_form_processing(form):
c = form.vars.a * form.vars.b
if c < 0:
form.errors.b = 'a*b cannot be negative'
else:
form.vars.c = c
def insert_numbers():
form = SQLFORM(db.numbers)
if form.process(onvalidation=my_form_processing).accepted:
session.flash = 'record inserted'
redirect(URL())
return dict(form=form)
Detectar alteração de registro
Ao preencher um formulário para editar um registro, há uma pequena probabilidade de que outro usuário possa estar editando o mesmo registro simultaneamente. Então, quando salvamos o registro, queremos verificar possíveis conflitos. Isto pode ser feito:
db.define_table('dog', Field('name'))
def edit_dog():
dog = db.dog(request.args(0)) or redirect(URL('error'))
form=SQLFORM(db.dog, dog)
form.process(detect_record_change=True)
if form.record_changed:
# do something
elif form.accepted:
# do something else
else:
# do nothing
return dict(form=form)
record_changed
funciona apenas com um SQLFORM e não com um FORM.
Formulários e redirecionamento
A maneira mais comum de usar formulários é através de auto-envio, para que as variáveis de campo submetidas sejam processadas pela mesma ação que gerou o formulário. Depois que o formulário for aceito, é incomum exibir a página atual novamente (algo que estamos fazendo aqui apenas para manter as coisas simples). É mais comum redirecionar o visitante para uma página "seguinte".
Aqui está o novo controlador de exemplo:
def display_form():
form = FORM('Your name:',
INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if form.process().accepted:
session.flash = 'form accepted'
redirect(URL('next'))
elif form.errors:
response.flash = 'form has errors'
else:
response.flash = 'please fill the form'
return dict(form=form)
def next():
return dict()
Para definir um flash na próxima página, em vez da página atual, você deve usar session.flash
ao invés de response.flash
. O web2py move o primeiro para o último após o redirecionamento. Note que usando session.flash
exige que você não session.forget()
.
Múltiplos formulários por página
O conteúdo desta seção se aplica a ambos FORM
e SQLFORM
objetos. É possível ter vários formulários por página, mas você deve permitir que o web2py os distinga. Se estes são derivados por SQLFORM
a partir de tabelas diferentes, o web2py fornece nomes diferentes automaticamente; caso contrário, você precisará fornecer explicitamente nomes de formulários diferentes. Aqui está um exemplo:
def two_forms():
form1 = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
form2 = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if form1.process(formname='form_one').accepted:
response.flash = 'form one accepted'
if form2.process(formname='form_two').accepted:
response.flash = 'form two accepted'
return dict(form1=form1, form2=form2)
e aqui está a saída que produz:
Quando o visitante envia um formulário vazio1, somente o form1 exibe um erro; se o visitante enviar um formulário vazio2, somente o formulário2 exibirá uma mensagem de erro.
Compartilhando formulários
O conteúdo desta seção se aplica a ambos FORM
e SQLFORM
objetos. O que discutimos aqui é possível, mas não recomendado, já que é sempre uma boa prática ter formulários que se auto-submetam. Às vezes, porém, você não tem escolha, porque a ação que envia o formulário e a ação que o recebe pertencem a diferentes aplicativos.
É possível gerar um formulário que seja submetido a uma ação diferente. Isso é feito especificando a URL da ação de processamento nos atributos do FORM
ou SQLFORM
objeto. Por exemplo:
form = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'), _action=URL('page_two'))
def page_one():
return dict(form=form)
def page_two():
if form.process(session=None, formname=None).accepted:
response.flash = 'form accepted'
else:
response.flash = 'there was an error in the form'
return dict()
Observe que, já que tanto "page_one" quanto "page_two" usam o mesmo form
, definimos isso apenas uma vez colocando-o fora de todas as ações, para não nos repetirmos. A parte comum do código no início de um controlador é executada toda vez antes de dar o controle à ação chamada.
Desde "page_one" não chama process
(nem accepts
), o formulário não tem nome nem chave, então você deve passar session=None
E definir formname=None
dentro process
, ou o formulário não será validado quando "page_two" o receber.
Adicionando botões aos FORMs
Normalmente, um formulário fornece um único botão de envio. É comum querer adicionar um botão "voltar" que, em vez de enviar o formulário, direciona o visitante para uma página diferente.
Isso pode ser feito com o método add_button
:
form.add_button('Back', URL('other_page'))
Você pode adicionar mais de um botão para formar. Os argumentos de add_button
são o valor do botão (seu texto) e o URL para onde redirecionar. (Veja também o argumento de botões do SQLFORM, que fornece uma abordagem mais poderosa)
Mais sobre manipulação de formulários
Como discutido no capítulo Views, um FORM é um auxiliar HTML. Os auxiliares podem ser manipulados como listas do Python e como dicionários, o que permite a criação e modificação em tempo de execução.
SQLFORM
Agora passamos para o próximo nível, fornecendo o aplicativo com um arquivo de modelo:
db = DAL('sqlite://storage.sqlite')
db.define_table('person', Field('name', requires=IS_NOT_EMPTY()))
Modifique o controlador da seguinte maneira:
def display_form():
form = SQLFORM(db.person)
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
else:
response.flash = 'please fill out the form'
return dict(form=form)
A visualização não precisa ser alterada.
No novo controlador, você não precisa construir um FORM
, desde o SQLFORM
construtor construiu um da tabela db.person
definido no modelo. Esse novo formulário, quando serializado, aparece como:
<form enctype="multipart/form-data" action="" method="post">
<table>
<tr id="person_name__row">
<td><label id="person_name__label"
for="person_name">Your name: </label></td>
<td><input type="text" class="string"
name="name" value="" id="person_name" /></td>
<td></td>
</tr>
<tr id="submit_record__row">
<td></td>
<td><input value="Submit" type="submit" /></td>
<td></td>
</tr>
</table>
<input value="9038845529" type="hidden" name="_formkey" />
<input value="person" type="hidden" name="_formname" />
</form>
O formulário gerado automaticamente é mais complexo que o formulário anterior de baixo nível. Primeiro de tudo, contém uma tabela de linhas e cada linha tem três colunas. A primeira coluna contém os rótulos de campo (conforme determinado db.person
), a segunda coluna contém os campos de entrada (e, eventualmente, mensagens de erro), e a terceira coluna é opcional e, portanto, vazia (pode ser preenchida com os campos no campo SQLFORM
construtor).
Todas as tags no formulário têm nomes derivados da tabela e do nome do campo. Isso permite a fácil personalização do formulário usando CSS e JavaScript. Essa capacidade é discutida em mais detalhes no Capítulo 11.
Mais importante é que agora o método accepts
faz muito mais trabalho para você. Como no caso anterior, ele realiza a validação da entrada, mas além disso, se a entrada passar pela validação, ela também executa uma inserção de banco de dados do novo registro e armazena na entrada. form.vars.id
o "id" único do novo registro.
Um objeto SQLFORM
também lida automaticamente com campos "upload" salvando arquivos enviados na pasta "uploads" (depois de tê-los renomeados com segurança para evitar conflitos e evitar ataques de passagem de diretórios) e armazena seus nomes (seus novos nomes) no campo apropriado no banco de dados . Após o processamento do formulário, o novo nome de arquivo estará disponível em form.vars.fieldname
(isto é, substitui o cgi.FieldStorage
objeto em request.vars.fieldname
), para que você possa facilmente referenciar o novo nome logo após o upload.
Aviso: o tamanho do campo padrão é de 512 caracteres. Se o sistema de arquivos não suportar nomes de arquivos por muito tempo, ele poderá gerar nomes que causarão um erro quando for feita uma tentativa de criá-los. Isso pode ser resolvido configurando Field(..., length=...)
para avaliar o valor. Observe também que isso pode truncar a codificação do nome do arquivo original e pode ser impossível recuperá-lo após o download ou o arquivo carregado.
Um SQLFORM
exibe valores "booleanos" com caixas de seleção, valores "text" com textareas, valores obrigatórios em um conjunto definido ou um banco de dados com drop-downs, e campos "upload" com links que permitem aos usuários fazer o download dos arquivos enviados. Ele oculta campos de "blob", uma vez que eles devem ser tratados de maneira diferente, como discutido mais adiante.
Por exemplo, considere o seguinte modelo:
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()),
Field('married', 'boolean'),
Field('gender', requires=IS_IN_SET(['Male', 'Female', 'Other'])),
Field('profile', 'text'),
Field('image', 'upload'))
Nesse caso, SQLFORM(db.person)
gera o formulário mostrado abaixo:
o SQLFORM
O construtor permite várias personalizações, como exibir apenas um subconjunto dos campos, alterar os rótulos, adicionar valores à terceira coluna opcional ou criar formulários UPDATE e DELETE, em oposição a formulários INSERT como o atual. SQLFORM
é o maior objeto de economia de tempo no web2py.
A classe SQLFORM
é definido em "gluon/sqlhtml.py". Pode ser facilmente estendido, ignorando seu método xml
, o método que serializa os objetos, para alterar sua saída.
SQLFORM
construtor é o seguinte:SQLFORM(table, record=None,
deletable=False, linkto=None,
upload=None, fields=None, labels=None,
col3={}, submit_button='Submit',
delete_label='Check to delete:',
showid=True, readonly=False,
comments=True, keepopts=[],
ignore_rw=False, record_id=None,
formstyle='table3cols',
buttons=['submit'], separator=': ',
**attributes)
- O segundo argumento opcional transforma o formulário INSERT em um formulário UPDATE para o registro especificado (consulte a próxima subseção). showiddelete_labelid_labelsubmit_button
- E se
deletable
está configurado paraTrue
, o formulário UPDATE exibe uma caixa de seleção "Marcar para excluir". O valor do rótulo para este campo é definido por meio dodelete_label
argumento. submit_button
define o valor do botão de envio.id_label
define o rótulo do registro "id"- O "id" do registro não é mostrado se
showid
está configurado paraFalse
. fields
é uma lista opcional de nomes de campos que você deseja exibir. Se uma lista for fornecida, apenas os campos da lista serão exibidos. Por exemplo:fields = ['name']
labels
é um dicionário de rótulos de campo. A chave do dicionário é um nome de campo e o valor correspondente é o que é exibido como seu rótulo. Se um rótulo não for fornecido, web2py deriva o rótulo do nome do campo (ele capitaliza o nome do campo e substitui os sublinhados por espaços). Por exemplo:labels = {'name':'Your Full Name:'}
col3
é um dicionário de valores para a terceira coluna. Por exemplo:col3 = {'name':A('what is this?', _href='http://www.google.com/search?q=define:name')}
linkto
eupload
são URLs opcionais para controladores definidos pelo usuário que permitem que o formulário lide com campos de referência. Isso é discutido em mais detalhes posteriormente na seção.readonly
. Se definido como True, exibe o formulário como somente leituracomments
. Se definido como False, não exibe os comentários col3ignore_rw
. Normalmente, para um formulário de criação/atualização, apenas os campos marcados como writable = True são mostrados e, para formulários readonly, apenas os campos marcados como readable = True são mostrados. Configuraçãoignore_rw=True
faz com que essas restrições sejam ignoradas e todos os campos sejam exibidos. Isso é usado principalmente na interface appadmin para exibir todos os campos de cada tabela, substituindo o que o modelo indica.- formstyle
formstyle
determina o estilo a ser usado ao serializar o formulário em html. Em um aplicativo moderno baseado no aplicativo welcome scaffolding, o formstyle padrão é definido em db.py usando o arquivo private/appconfig.ini do aplicativo; o padrão é atualmente bootstrap3_inline. Outras opções são "bootstrap3_stacked", "bootstrap2", "table3cols", "table2cols" (uma linha para rótulo e comentário, e uma linha para entrada), "ul" (faz uma lista não ordenada de campos de entrada), "divs" ( representa o formulário usando css amigável divs, para personalização arbitrária), "bootstrap" que usa o bootstrap 2.3 form class "form-horizontal".formstyle
também pode ser uma função que gera tudo dentro da tag FORM. Você passa para a sua função de construtor de formulário dois argumentos, forma e campos. Dicas podem ser encontradas no arquivo de código-fonte sqlhtml.py (procure por funções denominadas formstyle_) - buttons
buttons
é uma lista deINPUT
s ouTAG.button
s (embora tecnicamente poderia ser qualquer combinação de ajudantes) que será adicionado a um DIV onde o botão de envio iria.
Por exemplo, adicionando um botão de retorno baseado em URL (para um formulário de várias páginas) e um botão de envio renomeado:
buttons = [TAG.button('Back', _type="button", _onClick = "parent.location='%s' " % URL(...),
TAG.button('Next', _type="submit")]
ou um botão com links para outra página:
buttons = [..., A("Go to another page", _class='btn', _href=URL("default", "anotherpage"))]
- separator
separator
define a string que separa os rótulos de formulário dos campos de entrada do formulário. - Opcional
attributes
são argumentos começando com sublinhado que você quer passar para oFORM
tag que processa o objetoSQLFORM
. Exemplos são:_action = '.' _method = 'POST'
Existe um especial hidden
atributo. Quando um dicionário é passado como hidden
, seus itens são convertidos em campos INPUT "ocultos" (veja o exemplo para FORM
ajudante no Capítulo 5).
form = SQLFORM(..., hidden=...)
faz com que os campos ocultos sejam passados com o envio, nem mais nem menos. form.accepts(...)
não é destinado a ler os campos ocultos recebidos e movê-los para form.vars. O motivo é segurança. Campos ocultos podem ser adulterados. Portanto, você precisa mover explicitamente os campos ocultos da solicitação para o formulário:
form = SQLFORM(..., hidden=dict(a='b'))
form.vars.a = request.vars.a
o método process
O SQLFORM usa o método de processo (assim como os formulários).
Se você quiser usar keepvalues com um SQLFORM, você passa um argumento para o método process:
if form.process(keepvalues=True).accepted:
SQLFORM
e insert
/ update
/ delete
SQLFORM
cria um novo registro db quando o formulário é aceito. Assumindo
form=SQLFORM(db.test)
myform.vars.id
.Se você passar um registro como o segundo argumento opcional para o SQLFORM
construtor, o formulário se torna um formulário UPDATE para esse registro. Isso significa que quando o formulário é enviado, o registro existente é atualizado e nenhum novo registro é inserido. Se você definir o argumento deletable=True
, o formulário UPDATE exibe uma caixa de seleção "marque para excluir". Se marcado, o registro é excluído.
Se um formulário for enviado e a caixa de seleção de exclusão estiver marcada, o atributo
form.deleted
está configurado paraTrue
.
Você pode modificar o controlador do exemplo anterior para que, quando passarmos um argumento inteiro adicional no caminho da URL, como em:
/test/default/display_form/2
e se houver um registro com o id correspondente, o SQLFORM
gera um formulário UPDATE/DELETE para o registro:
def display_form():
record = db.person(request.args(0)) or redirect(URL('index'))
form = SQLFORM(db.person, record)
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
A linha 2 encontra o registro e a linha 3 faz um formulário UPDATE/DELETE. A linha 4 faz todo o processamento do formulário correspondente.
Um formulário de atualização é muito semelhante a um formulário de criação, exceto que ele é pré-preenchido com o registro atual e visualiza imagens. Por padrão
deletable = True
o que significa que o formulário de atualização exibirá uma opção "excluir registro".
Edit forms também contém um campo INPUT oculto com name="id"
que é usado para identificar o registro. Esse id também é armazenado no lado do servidor para segurança adicional e, se o visitante interferir no valor desse campo, o UPDATE não é executado e o web2py gera um SyntaxError, "o usuário está mexendo com o formulário".
Quando um campo é marcado com writable=False
, o campo não é mostrado em formulários de criação e é mostrado somente leitura em formulários de atualização. Se um campo estiver marcado como writable=False
e readable=False
, então o campo não é mostrado, nem mesmo em formulários de atualização.
Formulários criados com
form = SQLFORM(..., ignore_rw=True)
ignore o readable
e writable
atributos e sempre mostrar todos os campos. Formulários em appadmin
ignorá-los por padrão.
Formulários criados com
form = SQLFORM(table, record_id, readonly=True)
mostre sempre todos os campos no modo readonly, e eles não podem ser aceitos.
Marcando um campo com writable=False
impede que o campo seja parte do formulário e faz com que o processamento do formulário desconsidere o valor de request.vars.field
ao processar o formulário. No entanto, se você atribuir um valor a form.vars.field
, esse valor will fará parte da inserção ou atualização quando o formulário for processado. Isso permite que você altere o valor dos campos que, por algum motivo, você não deseja incluir em um formulário.
SQLFORM
em HTML
Há momentos em que você quer usar SQLFORM
para se beneficiar de sua geração e processamento de formulários, mas você precisa de um nível de personalização do formulário em HTML que não pode ser alcançado com os parâmetros do objeto SQLFORM
, então você tem que projetar o formulário usando HTML.
Agora, edite o controlador anterior e adicione uma nova ação:
def display_manual_form():
form = SQLFORM(db.person)
if form.process(session=None, formname='test').accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
else:
response.flash = 'please fill the form'
# Note: no form instance is passed to the view
return dict()
e insira o formulário na exibição "padrão/display_manual_form.html" associada:
{{extend 'layout.html'}}
<form action="#" enctype="multipart/form-data" method="post">
<ul>
<li>Your name is <input name="name" /></li>
</ul>
<input type="submit" />
<input type="hidden" name="_formname" value="test" />
</form>
Observe que a ação não retorna o formulário porque não precisa passá-lo para a exibição. A visualização contém um formulário criado manualmente em HTML. O formulário contém um campo oculto "_formname" que deve ser o mesmo formname
especificado como um argumento de accepts
na ação. O web2py usa o nome do formulário caso haja vários formulários na mesma página, para determinar qual deles foi enviado. Se a página contiver um único formulário, você pode definir formname=None
e omitir o campo oculto na vista.
form.accepts
vai olhar para dentro response.vars
para dados que correspondem a campos na tabela do banco de dados db.person
. Esses campos são declarados no HTML no formato
<input name="field_name_goes_here" />
Note que no exemplo dado, as variáveis do formulário serão passadas na URL como argumentos. Se isso não for desejado, o POST
protocolo terá que ser especificado. Além disso, se os campos de upload forem especificados, o formulário deverá ser configurado para permitir isso. Aqui, as duas opções são mostradas:
<form enctype="multipart/form-data" method="post">
SQLFORM
e uploads
Campos do tipo "upload" são especiais. Eles são renderizados como campos INPUT de type="file"
. A menos que especificado de outra forma, o arquivo enviado é transmitido usando um buffer e armazenado na pasta "uploads" do aplicativo usando um novo nome seguro, atribuído automaticamente. O nome desse arquivo é salvo no campo de uploads de tipo.
Como exemplo, considere o seguinte modelo:
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()),
Field('image', 'upload'))
Você pode usar a mesma ação do controlador "display_form" mostrada acima.
Quando você insere um novo registro, o formulário permite procurar um arquivo. Escolha, por exemplo, uma imagem jpg. O arquivo é carregado e armazenado como:
applications/test/uploads/person.image.XXXXX.jpg
"XXXXXX" é um identificador aleatório para o arquivo atribuído pelo web2py.
Observe que, por padrão, o nome do arquivo original de um arquivo enviado é b16coded e usado para criar o novo nome para o arquivo. Esse nome é recuperado pela ação "download" padrão e usado para definir o cabeçalho de disposição do conteúdo para o nome do arquivo original.
Apenas sua extensão é preservada. Esse é um requisito de segurança, pois o nome do arquivo pode conter caracteres especiais que podem permitir que um visitante realize ataques de travessia de diretório ou outras operações mal-intencionadas.
O novo nome de arquivo também é armazenado form.vars.image
.
Ao editar o registro usando um formulário UPDATE, seria interessante exibir um link para o arquivo carregado existente, e o web2py fornece uma maneira de fazer isso.
Se você passar um URL para o SQLFORM
construtor através do argumento de upload, web2py usa a ação nesse URL para baixar o arquivo. Considere as seguintes ações:
def display_form():
record = db.person(request.args(0))
form = SQLFORM(db.person, record, deletable=True,
upload=URL('download'))
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
def download():
return response.download(request, db)
Agora, insira um novo registro no URL:
http://127.0.0.1:8000/test/default/display_form
Carregar uma imagem, enviar o formulário e, em seguida, editar o registro recém-criado visitando:
http://127.0.0.1:8000/test/default/display_form/3
(aqui assumimos que o último registro tem id = 3). O formulário exibirá uma pré-visualização da imagem, conforme mostrado abaixo:
Este formulário, quando serializado, gera o seguinte HTML:
<td><label id="person_image__label" for="person_image">Image: </label></td>
<td><div><input type="file" id="person_image" class="upload" name="image"
/>[<a href="/test/default/download/person.image.0246683463831.jpg">file</a>|
<input type="checkbox" name="image__delete" />delete]</div></td><td></td></tr>
<tr id="delete_record__row"><td><label id="delete_record__label" for="delete_record"
>Check to delete:</label></td><td><input type="checkbox" id="delete_record"
class="delete" name="delete_this_record" /></td>
que contém um link para permitir o download do arquivo enviado e uma caixa de seleção para remover o arquivo do registro do banco de dados, armazenando NULL no campo "imagem".
Por que este mecanismo é exposto? Por que você precisa escrever a função de download? Porque você pode querer impor algum mecanismo de autorização na função de download. Veja o Capítulo 9 para um exemplo.
Normalmente, os arquivos enviados são armazenados em "app/uploads", mas você pode especificar um local alternativo:
Field('image', 'upload', uploadfolder='...')
Na maioria dos sistemas operacionais, o acesso ao sistema de arquivos pode se tornar lento quando há muitos arquivos na mesma pasta. Se você planeja fazer o upload de mais de 1000 arquivos, peça ao web2py para organizar os uploads nas subpastas:
Field('image', 'upload', uploadseparate=True)
Armazenando o nome do arquivo original
O web2py armazena automaticamente o nome do arquivo original dentro do novo nome de arquivo UUID e o recupera quando o arquivo é baixado. No download, o nome do arquivo original é armazenado no cabeçalho de disposição de conteúdo da resposta HTTP. Tudo isso é feito de forma transparente, sem a necessidade de programação.
Ocasionalmente, você pode querer armazenar o nome do arquivo original em um campo do banco de dados. Neste caso, você precisa modificar o modelo e adicionar um campo para armazená-lo em:
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()),
Field('image_filename'),
Field('image', 'upload'))
Então você precisa modificar o controlador para lidar com isso:
def display_form():
record = db.person(request.args(0)) or redirect(URL('index'))
url = URL('download')
form = SQLFORM(db.person, record, deletable=True,
upload=url, fields=['name', 'image'])
if request.vars.image!=None:
form.vars.image_filename = request.vars.image.filename
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
Observe que o SQLFORM
não exibe o campo "image_filename". A ação "display_form" move o nome do arquivo do request.vars.image
no form.vars.image_filename
, para que seja processado por accepts
e armazenados no banco de dados. A função de download, antes de exibir o arquivo, verifica no banco de dados o nome do arquivo original e o utiliza no cabeçalho de disposição de conteúdo.
autodelete
o SQLFORM
, ao excluir um registro, não exclui o (s) arquivo (s) físico (s) carregado (s) pelo registro. A razão é que o web2py não sabe se o mesmo arquivo é usado/vinculado por outras tabelas ou usado para outra finalidade. Se você souber que é seguro excluir o arquivo real quando o registro correspondente for excluído, faça o seguinte:
db.define_table('image',
Field('name', requires=IS_NOT_EMPTY()),
Field('source', 'upload', autodelete=True))
o autodelete
atributo é False
por padrão. Quando definido para True
é garante que o arquivo seja excluído quando o registro for excluído.
Links para registros de referência
Agora, considere o caso de duas tabelas ligadas por um campo de referência. Por exemplo:
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()))
db.define_table('dog',
Field('owner', 'reference person'),
Field('name', requires=IS_NOT_EMPTY()))
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s')
Uma pessoa tem cachorros, e cada cachorro pertence a um dono, que é uma pessoa. O dono do cão é obrigado a referir-se a um db.person.id
de '%(name)s'
.
Vamos usar o appadmin interface para esta aplicação para adicionar um poucas pessoas e seus cachorros.
Ao editar uma pessoa existente, o appadmin O formulário UPDATE mostra um link para uma página que lista os cães que pertencem à pessoa. Esse comportamento pode ser replicado usando o linkto
argumento do SQLFORM
. linkto
tem que apontar para o URL de uma nova ação que recebe uma string de consulta do SQLFORM
e lista os registros correspondentes. Aqui está um exemplo:
def display_form():
record = db.person(request.args(0)) or redirect(URL('index'))
url = URL('download')
link = URL('list_records', args='db')
form = SQLFORM(db.person, record, deletable=True,
upload=url, linkto=link)
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
Aqui está a página:
Existe um link chamado "dog.owner". O nome deste link pode ser alterado através do labels
argumento do SQLFORM
, por exemplo:
labels = {'dog.owner':"This person's dogs"}
Se você clicar no link, você será direcionado para:
/test/default/list_records/dog?query=db.dog.owner%3D%3D5
"list_records" é a ação especificada, com request.args(0)
definido para o nome da tabela de referência e request.vars.query
definido para a string de consulta SQL. A string de consulta no URL contém o valor "dog.owner = 5" apropriadamente codificado por url (o web2py decodifica isso automaticamente quando o URL é analisado).
Você pode facilmente implementar uma ação "list_records" muito geral da seguinte maneira:
def list_records():
import re
REGEX = re.compile('^(\w+).(\w+).(\w+)\=\=(\d+)$')
match = REGEX.match(request.vars.query)
if not match:
redirect(URL('error'))
table, field, id = match.group(2), match.group(3), match.group(4)
records = db(db[table][field]==id).select()
return dict(records=records)
com a vista "default/list_records.html" associada:
{{extend 'layout.html'}}
{{=records}}
Quando um conjunto de registros é retornado por um select e serializado em uma view, ele é primeiro convertido em um objeto SQLTABLE (não o mesmo que uma Table) e depois serializado em uma tabela HTML, onde cada campo corresponde a uma coluna da tabela.
Pré-preenchendo o formulário
Sempre é possível pré-preencher um formulário usando a sintaxe:
form.vars.name = 'fieldvalue'
Declarações como esta acima devem ser inseridas após a declaração do formulário e antes que o formulário seja aceito, independentemente de o campo ("nome" no exemplo) ser explicitamente visualizado no formulário.
Adicionando elementos de formulário extras para SQLFORM
Às vezes, você pode adicionar um elemento extra ao formulário depois de criá-lo. Por exemplo, você pode adicionar uma caixa de seleção que confirma que o usuário concorda com os termos e condições do seu site:
form = SQLFORM(db.yourtable)
my_extra_element = TR(LABEL('I agree to the terms and conditions'), INPUT(_name='agree', value=True, _type='checkbox'))
form[0].insert(-1, my_extra_element)
A variável my_extra_element
deve ser adaptado ao estilo de formulário. Neste exemplo, o padrão formstyle='table3cols'
foi assumido.
Após a submissão, form.vars.agree
conterá o status da caixa de seleção, que pode então ser usada em um onvalidation
função, por exemplo.
SQLFORM
sem banco de dados IO
Há momentos em que você deseja gerar um formulário a partir de uma tabela de banco de dados usando SQLFORM
e você quer validar um formulário submetido de acordo, mas você não quer nenhum INSERT/UPDATE/DELETE automático no banco de dados. Esse é o caso, por exemplo, quando um dos campos precisa ser calculado a partir do valor de outros campos de entrada. Esse também é o caso quando você precisa realizar validação adicional nos dados inseridos que não podem ser obtidos através de validadores padrão.
Isso pode ser feito facilmente quebrando:
form = SQLFORM(db.person)
if form.process().accepted:
response.flash = 'record inserted'
para dentro:
form = SQLFORM(db.person)
if form.validate():
### deal with uploads explicitly
form.vars.id = db.person.insert(**dict(form.vars))
response.flash = 'record inserted'
O mesmo pode ser feito para os formulários UPDATE/DELETE, quebrando:
form = SQLFORM(db.person, record)
if form.process().accepted:
response.flash = 'record updated'
para dentro:
form = SQLFORM(db.person, record)
if form.validate():
if form.deleted:
db(db.person.id==record.id).delete()
else:
form.record.update_record(**dict(form.vars))
response.flash = 'record updated'
No caso de uma tabela que inclui um campo do tipo "upload" ("nome do campo"), ambos process(dbio=False)
e validate()
lidar com o armazenamento do arquivo enviado como se process(dbio=True)
, o comportamento padrão.
O nome atribuído pelo web2py ao arquivo enviado pode ser encontrado em:
form.vars.fieldname
Outros tipos de formulários
SQLFORM.factory
Há casos em que você deseja gerar formulários 'como se' tivesse uma tabela de banco de dados, mas não deseja a tabela de banco de dados. Você simplesmente quer aproveitar o SQLFORM
capacidade de gerar um formulário CSS agradável e talvez realizar upload e renomeação de arquivos.
Isso pode ser feito através de um form_factory
. Aqui está um exemplo onde você gera o formulário, executa a validação, carrega um arquivo e armazena tudo no session
:
def form_from_factory():
form = SQLFORM.factory(
Field('your_name', requires=IS_NOT_EMPTY()),
Field('your_image', 'upload'))
if form.process().accepted:
response.flash = 'form accepted'
session.your_name = form.vars.your_name
session.your_image = form.vars.your_image
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
O objeto Field no construtor SQLFORM.factory () é totalmente documentado no DAL chapter . Uma técnica de construção em tempo de execução para SQLFORM.factory () é
fields = []
fields.append(Field(...))
form=SQLFORM.factory(*fields)
Aqui está a visualização "default/form_from_factory.html":
{{extend 'layout.html'}}
{{=form}}
Você precisa usar um sublinhado em vez de um espaço para rótulos de campo ou passar explicitamente um dicionário de labels
para form_factory
, como você faria para um SQLFORM
. Por padrão SQLFORM.factory
gera o formulário usando atributos "id" html gerados como se o formulário fosse gerado a partir de uma tabela chamada "no_table". Para alterar este nome da tabela fictícia, use o table_name
atributo para a fábrica:
form = SQLFORM.factory(..., table_name='other_dummy_name')
Alterando a table_name
é necessário se você precisar colocar dois formulários gerados em fábrica na mesma tabela e quiser evitar conflitos de CSS.
Fazendo upload de arquivos com o SQLFORM.factory
Um formulário para várias tabelas
Muitas vezes acontece que você tem duas tabelas (por exemplo, 'cliente' e 'endereço' que estão ligados por uma referência e você quer criar um formulário único que permita inserir informações sobre um cliente e seu endereço padrão. Aqui está como:
modelo:
db.define_table('client',
Field('name'))
db.define_table('address',
Field('client', 'reference client',
writable=False, readable=False),
Field('street'), Field('city'))
controlador:
def register():
form=SQLFORM.factory(db.client, db.address)
if form.process().accepted:
id = db.client.insert(**db.client._filter_fields(form.vars))
form.vars.client=id
id = db.address.insert(**db.address._filter_fields(form.vars))
response.flash='Thanks for filling the form'
return dict(form=form)
Observe o SQLFORM.factory (faz uma forma usando campos públicos de ambas as tabelas e herda seus validadores também). No formulário aceita isso faz duas inserções, alguns dados em uma tabela e alguns dados no outro.
Isso só funciona quando as tabelas não possuem nomes de campo em comum.
Formulários de Confirmação
Muitas vezes você precisa de um formulário com uma opção de confirmação. O formulário deve ser aceito se a escolha for aceita e nenhuma outra. O formulário pode ter opções adicionais que ligam outras páginas da web. O web2py fornece uma maneira simples de fazer isso:
form = FORM.confirm('Are you sure?')
if form.accepted: do_what_needs_to_be_done()
Observe que o formulário de confirmação não precisa e não deve chamar .accepts
ou .process
porque isso é feito internamente. Você pode adicionar botões com links para o formulário de confirmação na forma de um dicionário de {'value':'link'}
:
form = FORM.confirm('Are you sure?', {'Back':URL('other_page')})
if form.accepted: do_what_needs_to_be_done()
Formulário para editar um dicionário
Imagine um sistema que armazena opções de configurações em um dicionário,
config = dict(color='black', language='English')
e você precisa de um formulário para permitir que o visitante modifique este dicionário. Isso pode ser feito com:
form = SQLFORM.dictform(config)
if form.process().accepted: config.update(form.vars)
O formulário exibirá um campo INPUT para cada item no dicionário. Ele usará as chaves do dicionário como nomes e rótulos INPUT e valores atuais para inferir os tipos (string, int, double, data, datetime, booleano).
Isso funciona muito bem, mas deixa a você a lógica de tornar persistente o dicionário de configuração. Por exemplo, você pode querer armazenar o config
em uma sessão.
session.config or dict(color='black', language='English')
form = SQLFORM.dictform(session.config)
if form.process().accepted:
session.config.update(form.vars)
CRUD
A API Create/Read/Update/Delete (CRUD) é uma interface experimental no topo do SQLFORM. Está agora obsoleto em favor de SQLFORM.grid() and SQLFORM.smartgrid() , mas é descrito aqui porque alguns aplicativos foram criados com ele.
O CRUD cria um SQLFORM, mas simplifica a codificação porque incorpora a criação do formulário, o processamento do formulário, a notificação e o redirecionamento, tudo em uma única função. A primeira coisa a notar é que o CRUD difere das outras APIs web2py que usamos até agora porque ainda não está exposto. Deve ser importado. Ele também deve estar vinculado a um banco de dados específico. Por exemplo:
from gluon.tools import Crud
crud = Crud(db)
o objeto crud
definido acima fornece a seguinte API:
crud.tables()
retorna uma lista de tabelas definidas no banco de dados.crud.create(db.tablename)
retorna um formulário de criação para a tabela tablename.crud.read(db.tablename, id)
Retorna um formulário readonly para tablename e record id.crud.update(db.tablename, id)
retorna um formulário de atualização para o nome da tabela e o ID do registro.crud.delete(db.tablename, id)
exclui o registro.crud.select(db.tablename, query)
retorna uma lista de registros selecionados da tabela.crud.search(db.tablename)
retorna uma tupla (formulário, registros) em que form é um formulário de pesquisa e registros é uma lista de registros com base no formulário de pesquisa enviado.crud()
retorna um dos itens acima com base norequest.args()
.
Por exemplo, a seguinte ação:
def data(): return dict(form=crud())
expor os seguintes URLs:
http://.../[app]/[controller]/data/tables
http://.../[app]/[controller]/data/create/[tablename]
http://.../[app]/[controller]/data/read/[tablename]/[id]
http://.../[app]/[controller]/data/update/[tablename]/[id]
http://.../[app]/[controller]/data/delete/[tablename]/[id]
http://.../[app]/[controller]/data/select/[tablename]
http://.../[app]/[controller]/data/search/[tablename]
No entanto, a seguinte ação:
def create_tablename():
return dict(form=crud.create(db.tablename))
só iria expor o método de criação
http://.../[app]/[controller]/create_tablename
Enquanto a seguinte ação:
def update_tablename():
return dict(form=crud.update(db.tablename, request.args(0)))
só iria expor o método de atualização
http://.../[app]/[controller]/update_tablename/[id]
e assim por diante.
O comportamento do CRUD pode ser personalizado de duas maneiras: definindo alguns atributos do objeto crud
ou passando parâmetros extras para cada um dos seus métodos.
Configurações
Aqui está uma lista completa dos atributos CRUD atuais, seus valores padrão e significado:
Para impor a autenticação em todos os formulários crud:
crud.settings.auth = auth
O uso é explicado no capítulo 9.
Para especificar o controlador que define o data
função que retorna o objeto crud
crud.settings.controller = 'default'
Para especificar o URL para redirecionar depois de um registro de "criação" bem-sucedido:
crud.settings.create_next = URL('index')
Para especificar o URL para redirecionar depois de um registro de "atualização" bem-sucedido:
crud.settings.update_next = URL('index')
Para especificar o URL para redirecionar após um registro de "exclusão" bem-sucedido:
crud.settings.delete_next = URL('index')
Para especificar o URL a ser usado para vincular arquivos enviados:
crud.settings.download_url = URL('download')
Para especificar funções extras a serem executadas após procedimentos de validação padrão para crud.create
formulários:
crud.settings.create_onvalidation = StorageList()
StorageList
é o mesmo que um objeto Storage
, ambos são definidos no arquivo "gluon/storage.py", mas o padrão é []
ao contrário de None
. Permite a seguinte sintaxe:
crud.settings.create_onvalidation.mytablename.append(lambda form:....)
Para especificar funções extras a serem executadas após procedimentos de validação padrão para crud.update
formulários:
crud.settings.update_onvalidation = StorageList()
To specify extra functions to be executed after completion of crud.create
formulários:
crud.settings.create_onaccept = StorageList()
To specify extra functions to be executed after completion of crud.update
formulários:
crud.settings.update_onaccept = StorageList()
To specify extra functions to be executed after completion of crud.update
se o registro for excluído:
crud.settings.update_ondelete = StorageList()
To specify extra functions to be executed after completion of crud.delete
:
crud.settings.delete_onaccept = StorageList()
Para determinar se os formulários de "atualização" devem ter um botão "excluir":
crud.settings.update_deletable = True
Para determinar se os formulários de "atualização" devem mostrar o ID do registro editado:
crud.settings.showid = False
Para determinar se os formulários devem manter os valores inseridos anteriormente ou redefinir para o padrão após o envio bem-sucedido:
crud.settings.keepvalues = False
O Crud sempre detecta se um registro que está sendo editado foi modificado por um terceiro no tempo entre o momento em que o formulário é exibido e o momento em que ele é enviado. Esse comportamento é equivalente a
form.process(detect_record_change=True)
e é definido em:
crud.settings.detect_record_change = True
e pode ser alterado/desativado, definindo a variável para False
.
Você pode alterar o estilo de formulário
crud.settings.formstyle = 'table3cols' or 'table2cols' or 'divs' or 'ul'
Você pode definir o separador em todas as formas cruas:
crud.settings.label_separator = ':'
CAPTCHA
Você pode adicionar captcha a formulários, usando a mesma convenção explicada para auth, com:
crud.settings.create_captcha = None
crud.settings.update_captcha = None
crud.settings.captcha = None
Mensagens
Aqui está uma lista de mensagens personalizáveis:
crud.messages.submit_button = 'Submit'
define o texto do botão "enviar" para os formulários de criação e atualização.
crud.messages.delete_label = 'Check to delete:'
define o rótulo do botão "delete" em formulários "update".
crud.messages.record_created = 'Record Created'
define a mensagem flash na criação de registros bem-sucedidos.
crud.messages.record_updated = 'Record Updated'
define a mensagem flash na atualização de registro bem-sucedida.
crud.messages.record_deleted = 'Record Deleted'
define a mensagem flash na exclusão de registro bem-sucedida.
crud.messages.update_log = 'Record %(id)s updated'
define a mensagem de log na atualização de registro bem-sucedida.
crud.messages.create_log = 'Record %(id)s created'
define a mensagem de log na criação de registro bem-sucedida.
crud.messages.read_log = 'Record %(id)s read'
define a mensagem de log no acesso de leitura de registro bem-sucedido.
crud.messages.delete_log = 'Record %(id)s deleted'
define a mensagem de log na exclusão do registro bem-sucedido.
Notar que
crud.messages
pertence à turmagluon.storage.Message
que é semelhante agluon.storage.Storage
mas traduz automaticamente seus valores, sem necessidade deT
operador.
As mensagens de log são usadas se, e somente se, o CRUD estiver conectado ao Auth, conforme discutido no Capítulo 9. Os eventos são registrados na tabela Auth "auth_events".
Métodos
O comportamento dos métodos CRUD também pode ser personalizado em uma base por chamada. Aqui estão suas assinaturas:
crud.tables()
crud.create(table, next, onvalidation, onaccept, log, message)
crud.read(table, record)
crud.update(table, record, next, onvalidation, onaccept, ondelete, log, message, deletable)
crud.delete(table, record_id, next, message)
crud.select(table, query, fields, orderby, limitby, headers, **attr)
crud.search(table, query, queries, query_labels, fields, field_labels, zero, showall, chkall)
table
é uma tabela DAL ou um nome de tabela no qual o método deve atuar.record
erecord_id
são o id do registro em que o método deve atuar.next
é o URL para redirecionar após o sucesso. Se o URL contiver a substring "[id]", este será substituído pelo id do registro atualmente criado/atualizado (useURL(..., url_encode=False)
para evitar que os colchetes sejam escapados).onvalidation
tem a mesma função que o SQLFORM (..., onvalidation)onaccept
é uma função a ser chamada depois que o envio do formulário é aceito e executado, mas antes do redirecionamento.log
é a mensagem de log. Mensagens de log no CRUD ver variáveis noform.vars
dicionário como "% (id) s".message
é a mensagem flash após a aceitação do formulário.ondelete
é chamado no lugar deonaccept
quando um registro é excluído por meio de um formulário de "atualização".deletable
determina se o formulário de "atualização" deve ter uma opção de exclusão.query
é a consulta a ser usada para selecionar registros.fields
é uma lista de campos a serem selecionados.orderby
determina a ordem na qual os registros devem ser selecionados DAL chapter ).limitby
determina o intervalo de registros selecionados que devem ser exibidos (consulte o Capítulo 6).headers
é um dicionário com os nomes do cabeçalho da tabela.queries
uma lista como['equals', 'not equal', 'contains']
contendo os métodos permitidos no formulário de busca.query_labels
um dicionário comoquery_labels=dict(equals='Equals')
dando nomes aos métodos de pesquisa.fields
uma lista de campos a serem listados no widget de pesquisa.field_labels
um dicionário mapeando nomes de campo em rótulos.zero
O padrão "escolher um" é usado como opção padrão para o menu suspenso no widget de pesquisa.showall
defina como Verdadeiro se quiser que as linhas sejam retornadas conforme a consulta na primeira chamada (adicionada após 1.98.2).chkall
configure-o para True para ativar todas as caixas de seleção no formulário de pesquisa (adicionado após 1.98.2).**attr
adicionalcrud.select
argumentos de palavras-chave a serem passados para oSQLTABLE
construtor (ver DAL chapter ).
Aqui está um exemplo de uso em uma única função do controlador:
## assuming db.define_table('person', Field('name'))
def people():
form = crud.create(db.person, next=URL('index'),
message=T("record created"))
persons = crud.select(db.person, fields=['name'],
headers={'person.name': 'Name'})
return dict(form=form, persons=persons)
Aqui está outra função de controlador muito genérica que permite pesquisar, criar e editar quaisquer registros de qualquer tabela onde o nome da tabela é passado request.args (0):
def manage():
table=db[request.args(0)]
form = crud.update(table, request.args(1))
table.id.represent = lambda id, row: A('edit:', id, _href=URL(args=(request.args(0), id)))
search, rows = crud.search(table)
return dict(form=form, search=search, rows=rows)
Observe a linha table.id.represent=...
que informa ao web2py para alterar a representação do campo id e exibir um link em vez da própria página e passa o id como request.args (1), que transforma a página de criação em uma página de atualização.
Versão de registro
O SQLFORM e o CRUD fornecem um utilitário para os registros do banco de dados de versão:
Se você tem uma tabela (db.mytable) que precisa de um histórico de revisão completo, basta fazer:
form = SQLFORM(db.mytable, myrecord).process(onsuccess=auth.archive)
form = crud.update(db.mytable, myrecord, onaccept=auth.archive)
auth.archive
define uma nova tabela chamada db.mytable_archive (o nome é derivado do nome da tabela a que se refere) e, na atualização, armazena uma cópia do registro (como era antes da atualização) na tabela de archive criada, incluindo uma referência ao registro atual.
Porque o registro é realmente atualizado (somente seu estado anterior é arquivado), referências nunca são quebradas.
Tudo isso é feito sob o capô. Se você deseja acessar a tabela de arquivos, você deve defini-la em um modelo:
db.define_table('mytable_archive',
Field('current_record', 'reference mytable'),
db.mytable)
Observe a tabela se estende db.mytable
(incluindo todos os seus campos) e adiciona uma referência ao current_record
.
auth.archive
não registra o registro de data e hora a menos que sua tabela original tenha campos de registro de data e hora, por exemplo:
db.define_table('mytable',
Field('created_on', 'datetime',
default=request.now, update=request.now, writable=False),
Field('created_by', 'reference auth_user',
default=auth.user_id, update=auth.user_id, writable=False),
Não há nada especial sobre esses campos e você pode dar a eles qualquer nome que desejar. Eles são preenchidos antes que o registro seja arquivado e arquivado em cada cópia do registro. O nome da tabela de arquivos e/ou o nome do campo de referência podem ser alterados assim:
db.define_table('myhistory',
Field('parent_record', 'reference mytable'),
db.mytable)
## ...
form = SQLFORM(db.mytable, myrecord)
form.process(onsuccess = lambda form:auth.archive(form,
archive_table=db.myhistory,
current_record='parent_record'))
Formulários personalizados
Se um formulário for criado com o SQLFORM, o SQLFORM.factory ou o CRUD, haverá várias maneiras de incorporá-lo em uma visualização, permitindo vários graus de personalização. Considere, por exemplo, o seguinte modelo:
db.define_table('image',
Field('name', requires=IS_NOT_EMPTY()),
Field('source', 'upload'))
e ação de upload
def upload_image():
return dict(form=SQLFORM(db.image).process())
A maneira mais simples de incorporar o formulário na exibição para upload_image
é
{{=form}}
Isso resulta em um layout de tabela padrão. Se você deseja usar um layout diferente, você pode dividir o formulário em componentes
{{=form.custom.begin}}
Name: <div>{{=form.custom.widget.name}}</div>
File: <div>{{=form.custom.widget.source}}</div>
{{=form.custom.submit}}
{{=form.custom.end}}
Onde form.custom.widget[fieldname]
Obtém serializado no widget adequado para o campo. Se o formulário for enviado e contiver erros, eles serão anexados abaixo dos widgets, como de costume.
O formulário de amostra acima é mostrado na imagem abaixo.
Um resultado semelhante poderia ter sido obtido sem usar um formulário personalizado:
SQLFORM(..., formstyle='table2cols')
ou no caso de formulários CRUD com o seguinte parâmetro:
crud.settings.formstyle='table2cols'
Outro possível formstyle
s são "table3cols" (o padrão), "divs" e "ul".
Se você não deseja usar os widgets serializados por web2py, você pode substituí-los por HTML. Existem algumas variáveis que serão úteis para isso:
form.custom.label[fieldname]
contém o rótulo para o campo.form.custom.comment[fieldname]
contém o comentário para o campo.form.custom.dspval[fieldname]
tipo de formulário e representação de exibição dependente do tipo de campo do campo.form.custom.inpval[fieldname]
valores dependentes do tipo de formulário e tipo de campo a serem usados no código de campo.
Se você formou deletable=True
você também deve inserir
{{=form.custom.delete}}
para exibir a caixa de seleção de exclusão.
É importante seguir as convenções descritas abaixo.
Convenções CSS
Tags em formulários gerados pelo SQLFORM, SQLFORM.factory e CRUD seguem uma convenção de nomenclatura CSS estrita que pode ser usada para personalizar ainda mais os formulários.
Dada uma tabela "mytable" e um campo "myfield" do tipo "string", ela é renderizada por padrão por um
SQLFORM.widgets.string.widget
que se parece com isso:
<input type="text" name="myfield" id="mytable_myfield"
class="string" />
Notar que:
- a classe da tag INPUT é a mesma do tipo do campo. Isso é muito importante para o código jQuery em "web2py_ajax.html" funcionar. Ele garante que você só pode ter números em campos "integer" e "double", e que os campos "time", "date" e "datetime" exibem o calendário popup/datepicker.
- id é o nome da classe mais o nome do campo, unidos por um sublinhado. Isso permite que você se refira exclusivamente ao campo, por exemplo,
jQuery('#mytable_myfield')
e manipular a folha de estilo do campo ou vincular ações associadas aos eventos de campo (foco, desfoque, chave, etc.). - o nome é, como você esperaria, o nome do campo.
Ocultar erros
Ocasionalmente, você pode querer desabilitar o posicionamento automático de erros e exibir mensagens de erro do formulário em algum outro lugar que não o padrão. Isso pode ser feito facilmente.
- No caso de FORM ou SQLFORM, passe
hideerror=True
aoaccepts
método. - No caso do CRUD, defina
crud.settings.hideerror=True
Você também pode querer modificar as visualizações para exibir o erro (já que elas não são mais exibidas automaticamente).
Aqui está um exemplo em que os erros são exibidos acima do formulário e não no formulário.
{{if form.errors:}}
Your submitted form contains the following errors:
<ul>
{{for fieldname in form.errors:}}
<li>{{=fieldname}} error: {{=form.errors[fieldname]}}</li>
{{pass}}
</ul>
{{form.errors.clear()}}
{{pass}}
{{=form}}
Os erros serão exibidos como na imagem mostrada abaixo.
Esse mecanismo também funciona para formulários personalizados.
Validadores
Validadores são classes usadas para validar campos de entrada (incluindo formulários gerados a partir de tabelas de banco de dados). Com os formulários avançados derivados do SQLFORM, os validadores criam widgets, como menus suspensos e pesquisas de outras tabelas.
Aqui está um exemplo de usar um validador com um FORM
:
INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
Aqui está um exemplo de como requerer um validador para um campo de tabela:
db.define_table('person', Field('name'))
db.person.name.requires = IS_NOT_EMPTY()
Os validadores são sempre atribuídos usando o requires
atributo de um campo. Um campo pode ter um único validador ou vários validadores. Vários validadores fazem parte de uma lista:
db.person.name.requires = [IS_NOT_EMPTY(),
IS_NOT_IN_DB(db, 'person.name')]
Normalmente os validadores são chamados automaticamente pela função accepts
e process
de um FORM
ou outro objeto auxiliar HTML que contenha um formulário. Eles são chamados na ordem em que estão listados.
Também é possível chamar validadores explicitamente para um campo:
db.person.name.validate(value)
que retorna uma tupla (value, error)
e error
é None
se não o valor valida.
Os validadores integrados têm construtores que aceitam um argumento opcional:
IS_NOT_EMPTY(error_message='cannot be empty')
error_message
permite que você substitua a mensagem de erro padrão de qualquer validador.
Aqui está um exemplo de um validador em uma tabela de banco de dados:
db.person.name.requires = IS_NOT_EMPTY(error_message='fill this!')
onde usamos o operador de tradução T
para permitir a internacionalização. Observe que as mensagens de erro padrão não são traduzidas.
Lembre-se de que os únicos validadores que podem ser usados com list:
campos tipo são:
IS_IN_DB(..., multiple=True)
IS_IN_SET(..., multiple=True)
IS_NOT_EMPTY()
IS_LIST_OF(...)
Este último pode ser usado para aplicar qualquer validador aos itens individuais da lista. multiple=(1, 1000)
requer uma seleção de entre 1 e 1000 itens. Isso impõe a seleção de pelo menos uma escolha.
Validadores de formato de texto
IS_ALPHANUMERIC
Este validador verifica se um valor de campo contém apenas caracteres nos intervalos a-z, A-Z ou 0-9.
requires = IS_ALPHANUMERIC(error_message='must be alphanumeric!')
IS_LOWER
Este validador nunca retorna um erro. Apenas converte o valor para minúsculas.
requires = IS_LOWER()
IS_UPPER
Este validador nunca retorna um erro. Converte o valor para maiúscula.
requires = IS_UPPER()
IS_EMAIL
Ele verifica se o valor do campo se parece com um endereço de e-mail. Não tenta enviar e-mail para confirmar.
requires = IS_EMAIL(error_message='invalid email!')
IS_MATCH
Este validador combina o valor com uma expressão regular e retorna um erro se não corresponder. Aqui está um exemplo de uso para validar um código postal dos EUA:
requires = IS_MATCH('^\d{5}(-\d{4})?$',
error_message='not a zip code')
Aqui está um exemplo de uso para validar um endereço IPv4 (nota: o validador IS_IPV4 é mais apropriado para este propósito):
requires = IS_MATCH('^\d{1,3}(.\d{1,3}){3}$',
error_message='not an IP address')
Aqui está um exemplo de uso para validar um número de telefone dos EUA:
requires = IS_MATCH('^1?((-)\d{3}-?|\(\d{3}\))\d{3}-?\d{4}$',
error_message='not a phone number')
Para mais informações sobre expressões regulares do Python, consulte a documentação oficial do Python.
IS_MATCH
leva um argumento opcional strict
qual padrão é False
. Quando definido para True
ele só corresponde ao começo da string:
>>> IS_MATCH('ab', strict=False)('abc')
('abc', None)
>>> IS_MATCH('ab', strict=True)('abc')
('abc', 'Invalid expression')
IS_MATCH
leva um outro argumento opcional search
qual padrão é False
. Quando definido para True
, usa o método regex search
em vez de método match
para validar a string.
IS_MATCH('...', extract=True)
filtros e extrair apenas a primeira correspondência substring em vez do valor original.
IS_LENGTH
Verifica se o comprimento do campo se ajusta entre os limites especificados. Trabalho para entradas de texto e arquivo.
Seus argumentos são:
- maxsize: o comprimento/tamanho máximo permitido (tem o padrão = 255)
- minsize: o comprimento/tamanho mínimo permitido
Exemplos: Verifique se a cadeia de texto é menor que 33 caracteres:
INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
Verifique se a string de senha tem mais de 5 caracteres:
INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
Verifique se o arquivo enviado tem tamanho entre 1 KB e 1 MB:
INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
Para todos os tipos de campo, exceto arquivos, ele verifica o tamanho do valor. No caso de arquivos, o valor é um cookie.FieldStorage
, portanto, valida o comprimento dos dados no arquivo, que é o comportamento que se pode esperar intuitivamente.
IS_URL
Rejeita uma string de URL, se qualquer uma das seguintes condições for verdadeira:
- A string está vazia ou não tem
- A string usa caracteres que não são permitidos em um URL
- A string quebra qualquer uma das regras sintáticas de HTTP
- O esquema de URL especificado (se um for especificado) não é 'http' ou 'https'
- O domínio de nível superior (se um nome de host for especificado) não existe
(Estas regras são baseadas no RFC 2616 [RFC2616] )
Esta função apenas verifica a sintaxe da URL. Não verifica se o URL aponta para um documento real, por exemplo, ou que de outra forma faz sentido semântico. Esta função é automaticamente precedida 'http: //' na frente de um URL no caso de um URL abreviado (por exemplo, 'google.ca').
Se o parâmetro mode = 'generic' for usado, o comportamento desta função será alterado. Em seguida, ele rejeita uma string de URL, se qualquer uma das seguintes condições for verdadeira:
- A string está vazia ou não tem
- A string usa caracteres que não são permitidos em um URL
- O esquema de URL especificado (se um for especificado) não é válido
(Essas regras são baseadas no RFC 2396 [RFC2396] )
A lista de esquemas permitidos é personalizável com o parâmetro allowed_schemes. Se você excluir Nenhum da Na lista, os URLs abreviados (sem um esquema como "http") serão rejeitados.
O esquema prefixado padrão é personalizável com o parâmetro prepend_scheme. Se você definir prepend_scheme para Nenhum, em seguida, o prefácio será desativado. URLs que exigem prepending para análise ainda serão aceitos, mas o valor de retorno não será modificado.
IS_URL é compatível com o padrão de nome de domínio internacionalizado (IDN) especificado no RFC 3490 [RFC3490] ). Como resultado, as URLs podem ser sequências regulares ou unicode. Se o componente de domínio da URL (por exemplo, google.ca) contiver letras não-US-ASCII, o domínio será ser convertido em Punycode (definido no RFC 3492 [RFC3492] ). IS_URL vai um pouco além dos padrões e permite que caracteres não-US-ASCII estejam presentes no caminho e consultar componentes do URL também. Esses caracteres não-US-ASCII serão codificados. Por exemplo, o espaço será codificado como '% 20'. O caractere unicode com o código hexadecimal 0x4e86 vai se tornar '% 4e% 86'.
Exemplos:
requires = IS_URL())
requires = IS_URL(mode='generic')
requires = IS_URL(allowed_schemes=['https'])
requires = IS_URL(prepend_scheme='https')
requires = IS_URL(mode='generic',
allowed_schemes=['ftps', 'https'],
prepend_scheme='https')
IS_SLUG
requires = IS_SLUG(maxlen=80, check=False, error_message='must be slug')
E se check
está configurado para True
ele verifica se o valor validado é um slug (permitindo apenas caracteres alfanuméricos e traços não repetidos).
E se check
está configurado para False
(padrão) ele converte o valor de entrada em um slug.
Validadores de data e hora
IS_TIME
Este validador verifica se um valor de campo contém um horário válido no formato especificado.
requires = IS_TIME(error_message='must be HH:MM:SS!')
IS_DATE
Este validador verifica se um valor de campo contém uma data válida no formato especificado. É recomendável especificar o formato usando o operador de conversão para suportar diferentes formatos em diferentes localidades.
requires = IS_DATE(format=T('%Y-%m-%d'),
error_message='must be YYYY-MM-DD!')
Para obter a descrição completa em% directives, consulte o validador IS_DATETIME.
Para obter a descrição completa em% directives, consulte o validador IS_DATETIME.
IS_DATETIME
Este validador verifica se um valor de campo contém uma data e hora válida no formato especificado. É recomendável especificar o formato usando o operador de conversão para suportar diferentes formatos em diferentes localidades.
requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'),
error_message='must be YYYY-MM-DD HH:MM:SS!')
Os seguintes símbolos podem ser usados para a string de formato (isso mostra o símbolo e uma string de exemplo):
%Y '1963'
%y '63'
%d '28'
%m '08'
%b 'Aug'
%b 'August'
%H '14'
%I '02'
%p 'PM'
%M '30'
%S '59'
IS_DATE_IN_RANGE
Funciona muito parecido com o validador anterior, mas permite especificar um intervalo:
requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'),
minimum=datetime.date(2008, 1, 1),
maximum=datetime.date(2009, 12, 31),
error_message='must be YYYY-MM-DD!')
IS_DATETIME_IN_RANGE
Funciona muito parecido com o validador anterior, mas permite especificar um intervalo:
requires = IS_DATETIME_IN_RANGE(format=T('%Y-%m-%d %H:%M:%S'),
minimum=datetime.datetime(2008, 1, 1, 10, 30),
maximum=datetime.datetime(2009, 12, 31, 11, 45),
error_message='must be YYYY-MM-DD HH:MM::SS!')
Para obter a descrição completa em% directives, consulte o validador IS_DATETIME.
Validadores de intervalo, conjunto e igualdade
IS_EQUAL_TO
Verifica se o valor validado é igual a um determinado valor (que pode ser uma variável):
requires = IS_EQUAL_TO(request.vars.password,
error_message='passwords do not match')
IS_NOT_EMPTY
Este validador verifica se o conteúdo do valor do campo não é uma string vazia.
requires = IS_NOT_EMPTY(error_message='cannot be empty!')
IS_NULL_OR
Depreciado, um apelido para IS_EMPTY_OR
Descrito abaixo.
IS_EMPTY_OR
Às vezes, você precisa permitir valores vazios em um campo junto com outros requisitos. Por exemplo, um campo pode ser uma data, mas também pode estar vazio. o IS_EMPTY_OR
validator permite isso:
requires = IS_EMPTY_OR(IS_DATE())
IS_EXPR
Seu primeiro argumento é uma string contendo uma expressão lógica em termos de um valor variável. Valida um valor de campo se a expressão for avaliada como True
. Por exemplo:
requires = IS_EXPR('int(value)%3==0',
error_message='not divisible by 3')
Deve-se primeiro verificar se o valor é um inteiro, para que uma exceção não ocorra.
requires = [IS_INT_IN_RANGE(0, 100), IS_EXPR('value%3==0')]
IS_DECIMAL_IN_RANGE
INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot="."))
Ele converte a entrada em um Decimal do Python ou gera um erro se o decimal não está dentro do intervalo inclusivo especificado. A comparação é feita com aritmética decimal do Python.
Os limites mínimo e máximo podem ser Nenhum, significando nenhum limite inferior ou superior, respectivamente.
o dot
O argumento é opcional e permite internacionalizar o símbolo usado para separar os decimais.
IS_FLOAT_IN_RANGE
Verifica se o valor do campo é um número de ponto flutuante dentro de um intervalo definido, 0 <= value <= 100
no exemplo a seguir:
requires = IS_FLOAT_IN_RANGE(0, 100, dot=".",
error_message='too small or too large!')
o dot
O argumento é opcional e permite internacionalizar o símbolo usado para separar os decimais.
IS_INT_IN_RANGE
Verifica se o valor do campo é um número inteiro dentro de um intervalo definido, 0 <= value < 100
no exemplo a seguir:
requires = IS_INT_IN_RANGE(0, 100,
error_message='too small or too large!')
IS_IN_SET
No SQLFORM (e nas grades), este validador irá definir automaticamente o campo de formulário para um campo de opção (ou seja, com um menu suspenso).
IS_IN_SET
verifica se os valores do campo estão em um conjunto:
requires = IS_IN_SET(['a', 'b', 'c'], zero=T('choose one'),
error_message='must be a or b or c')
O argumento zero é opcional e determina o texto da opção selecionada por padrão, uma opção que não é aceita pelo IS_IN_SET
validador em si. Se você não quiser uma opção "escolha uma", defina zero=None
.
Os elementos do conjunto podem ser combinados com um validador numérico, desde que IS_IN_SET seja o primeiro da lista. Fazer isso forçará a conversão pelo validador para o tipo numérico. Então, IS_IN_SET pode ser seguido por IS_INT_IN_RANGE
(que converte o valor para int) ou IS_FLOAT_IN_RANGE
(que converte o valor em float). Por exemplo:
requires = [ IS_IN_SET([2, 3, 5, 7], IS_INT_IN_RANGE(0, 8),
error_message='must be prime and less than 10')]
# Validação da caixa de seleção
Para forçar uma caixa de seleção de formulário preenchida (como uma aceitação de termos e condições), use isto:
requires=IS_IN_SET(['on'])
# Dicionários e tuplas com IS_IN_SET
Você também pode usar um dicionário ou uma lista de tuplas para tornar a lista suspensa mais descritiva:
Dictionary example:
requires = IS_IN_SET({'A':'Apple', 'B':'Banana', 'C':'Cherry'}, zero=None)
List of tuples example:
requires = IS_IN_SET([('A', 'Apple'), ('B', 'Banana'), ('C', 'Cherry')])
IS_IN_SET
e marcação
o IS_IN_SET
validador tem um atributo opcional multiple=False
. Se definido como True, vários valores podem ser armazenados em um campo. O campo deve ser do tipo list:integer
ou list:string
. multiple
as referências são tratadas automaticamente em formulários de criação e atualização, mas são transparentes para o DAL. Nós sugerimos fortemente o uso do plugin multiselect do jQuery para renderizar múltiplos campos.
Note que quando
multiple=True
,IS_IN_SET
vai aceitarzero
ou mais valores, isto é, aceitará o campo quando nada tiver sido selecionado.multiple
também pode ser uma tupla do formulário(a, b)
Ondea
eb
são o número mínimo e (exclusivo) máximo de itens que podem ser selecionados respectivamente.
Validadores de complexidade e segurança
IS_STRONG
Reforça os requisitos de complexidade em um campo (geralmente um campo de senha)
Exemplo:
requires = IS_STRONG(min=10, special=2, upper=2)
Onde
- min é o comprimento mínimo do valor
- especial é o número mínimo de caracteres especiais necessários, os caracteres especiais são alguns dos seguintes
!@#$%^&*(){}[]-+
- upper é o número mínimo de caracteres maiúsculos
CRYPT
Este também é um filtro. Ele executa um hash seguro na entrada e é usado para impedir que senhas sejam passadas para o banco de dados.
requires = CRYPT()
Por padrão, o CRYPT usa 1000 iterações do algoritmo pbkdf2 combinadas com o SHA512 para produzir um hash de 20 bytes. Versões mais antigas de web2py usavam "md5" ou HMAC + SHA512 dependendo se uma chave era especificada ou não.
Se uma chave for especificada, o CRYPT usa o algoritmo HMAC. A chave pode conter um prefixo que determina o algoritmo a ser usado com o HMAC, por exemplo, SHA512:
requires = CRYPT(key='sha512:thisisthekey')
Essa é a sintaxe recomendada. A chave deve ser uma cadeia exclusiva associada ao banco de dados usado. A chave nunca pode ser alterada. Se você perder a chave, os valores anteriormente em hash se tornam inúteis.
Por padrão, o CRYPT usa sal aleatório, de forma que cada resultado é diferente. Para usar um valor salt constante, especifique seu valor:
requires = CRYPT(salt='mysaltvalue')
Ou, para não usar sal:
requires = CRYPT(salt=False)
O validador CRYPT hashes sua entrada, e isso torna um pouco especial. Se você precisar validar um campo de senha antes que ele esteja com hash, você pode usar CRYPT em uma lista de validadores, mas deve certificar-se de que seja o último da lista, para que seja chamado last. Por exemplo:
requires = [IS_STRONG(), CRYPT(key='sha512:thisisthekey')]
CRYPT
também leva um min_length
argumento, cujo padrão é zero.
O hash resultante assume a forma alg$salt$hash
, Onde alg
é o algoritmo de hash usado, salt
é a cadeia de sal (que pode estar vazia) e hash
é a saída do algoritmo. Consequentemente, o hash é autoidentificador, permitindo, por exemplo, que o algoritmo seja alterado sem invalidar os hashes anteriores. A chave, no entanto, deve permanecer a mesma.
Validadores de tipo especial
IS_LIST_OF
Isto não é propriamente um validador. Seu uso pretendido é permitir validações de campos que retornam vários valores. Ele é usado nos casos raros em que um formulário contém vários campos com o mesmo nome ou uma caixa de seleção múltipla. Seu único argumento é outro validador, e tudo o que ele faz é aplicar o outro validador a cada elemento da lista. Por exemplo, a expressão a seguir verifica se cada item em uma lista é um número inteiro no intervalo de 0 a 10:
requires = IS_LIST_OF(IS_INT_IN_RANGE(0, 10))
Nunca retorna um erro e não contém uma mensagem de erro. O validador interno controla a geração de erros.
IS_IMAGE
Este validador verifica se um arquivo enviado através da entrada de arquivo foi salvo em um dos formatos de imagem selecionados e possui dimensões (largura e altura) dentro de determinados limites.
Não verifica o tamanho máximo do arquivo (use IS_LENGTH para isso). Devolve uma falha de validação se nenhum dado foi carregado. Suporta os formatos de arquivo BMP, GIF, JPEG, PNG e não requer a biblioteca de imagens Python.
Peças de código retiradas da ref. [source1]
Leva os seguintes argumentos:
- extensions: iterável contendo extensões de arquivo de imagem permitidas em minúsculas
- maxsize: iterável contendo largura e altura máximas da imagem
- minsize: iterável contendo largura e altura mínimas da imagem
Use (-1, -1) como minsize para ignorar a verificação do tamanho da imagem.
Aqui estão alguns exemplos:
- Verifique se o arquivo enviado está em qualquer um dos formatos de imagem suportados:
requires = IS_IMAGE()
- Verifique se o arquivo carregado é JPEG ou PNG:
requires = IS_IMAGE(extensions=('jpeg', 'png'))
- Verifique se o arquivo enviado é PNG com tamanho máximo de 200 x 200 pixels:
requires = IS_IMAGE(extensions=('png'), maxsize=(200, 200))
- Nota: ao exibir um formulário de edição para uma tabela incluindo
requires = IS_IMAGE()
, umadelete
caixa de seleção não aparecerá porque para excluir o arquivo causaria a validação falhar. Para exibir odelete
checkbox use esta validação:requires = IS_EMPTY_OR(IS_IMAGE())
IS_UPLOAD_FILENAME
Este validador verifica se o nome e a extensão de um arquivo carregado por meio da entrada de arquivo correspondem aos critérios fornecidos.
Não garante o tipo de arquivo de forma alguma. Retorna falha de validação se nenhum dado foi carregado.
Seus argumentos são:
- filename: nome do arquivo (antes do ponto) regex.
- extensão: extensão (após ponto) regex.
- lastdot: qual ponto deve ser usado como um separador de nome de arquivo/extensão:
True
indica o último ponto (por exemplo, "file.tar.gz" será quebrado em "file.tar" + "gz") enquantoFalse
significa primeiro ponto (por exemplo, "file.tar.gz" será dividido em "arquivo" + "tar.gz"). - caso: 0 significa manter o caso; 1 significa transformar a string em minúscula (padrão); 2 significa transformar a string em maiúscula.
Se não houver nenhum ponto presente, as verificações de extensão serão feitas contra um verificações de string e nome de arquivo serão feitas contra todo o valor.
Exemplos:
Verifique se o arquivo tem uma extensão pdf (não diferencia maiúsculas de minúsculas):
requires = IS_UPLOAD_FILENAME(extension='pdf')
Verifique se o arquivo tem uma extensão tar.gz e nome começando com backup:
requires = IS_UPLOAD_FILENAME(filename='backup.*', extension='tar.gz', lastdot=False)
Verifique se o arquivo não tem extensão e nome corresponde a README (sensível a maiúsculas e minúsculas):
requires = IS_UPLOAD_FILENAME(filename='^README$', extension='^$', case=0)
IS_IPV4
Este validador verifica se o valor de um campo é um endereço IP versão 4 na forma decimal. lata ser definido para forçar endereços de um determinado intervalo.
Regex IPv4 retirado da ref. [regexlib] Seus argumentos são:
minip
menor endereço permitido; aceita: strpor exemplo, 192.168.0.1; iterável de números, por exemplo, [192, 168, 0, 1]; int, por exemplo, 3232235521maxip
maior endereço permitido; o mesmo que acima
Todos os três valores de exemplo são iguais, já que os endereços são convertidos para inteiros para verificação de inclusão com a seguinte função:
number = 16777216 * IP[0] + 65536 * IP[1] + 256 * IP[2] + IP[3]
Exemplos:
Verifique o endereço IPv4 válido:
requires = IS_IPV4()
Verifique o endereço IPv4 válido da rede privada:
requires = IS_IPV4(minip='192.168.0.1', maxip='192.168.255.255')
Outros validadores
CLEANUP
Este é um filtro. Isso nunca falha. Apenas remove todos os caracteres cujos códigos ASCII decimais não estão na lista [10, 13, 32-127].
requires = CLEANUP()
Validadores de banco de dados
IS_NOT_IN_DB
#Sinopse:
IS_NOT_IN_DB(db|set, 'table.field')
Considere o seguinte exemplo:
db.define_table('person', Field('name'))
db.person.name.requires = IS_NOT_IN_DB(db, 'person.name')
Isso exige que, quando você insere uma nova pessoa, o nome dela ainda não esteja no banco de dados, db
, no campo person.name
.
Um conjunto pode ser usado em vez de db
.
Como com todos os outros validadores, esse requisito é aplicado no nível de processamento do formulário, não no nível do banco de dados. Isso significa que há uma pequena probabilidade de que, se dois visitantes tentarem inserir simultaneamente registros com a mesma pessoa.nome, isso resulta em uma condição de corrida e os dois registros são aceitos. Por isso, é mais seguro informar também ao banco de dados que esse campo deve ter um valor único:
db.define_table('person', Field('name', unique=True))
db.person.name.requires = IS_NOT_IN_DB(db, 'person.name')
Agora, se ocorrer uma condição de corrida, o banco de dados gerará um OperationalError e uma das duas inserções será rejeitada.
O primeiro argumento de IS_NOT_IN_DB
pode ser uma conexão de banco de dados ou um conjunto. Neste último caso, você verificaria apenas o conjunto definido pelo Conjunto.
Uma lista completa de argumentos para IS_NOT_IN_DB()
é o seguinte:
IS_NOT_IN_DB(dbset, field, error_message='value already in database or empty', allowed_override=[],
ignore_common_filters=True)
O código a seguir, por exemplo, não permite o registro de duas pessoas com o mesmo nome dentro de 10 dias uma da outra:
import datetime
now = datetime.datetime.today()
db.define_table('person',
Field('name'),
Field('registration_stamp', 'datetime', default=now))
recent = db(db.person.registration_stamp>now-datetime.timedelta(10))
db.person.name.requires = IS_NOT_IN_DB(recent, 'person.name')
IS_IN_DB
#Sinopse:
IS_IN_DB(db|set, 'table.value_field', '%(representing_field)s', zero='choose one')
onde o terceiro e quarto argumentos são opcionais.
multiple=
também é possível se o tipo de campo for uma lista. O padrão é falso. Pode ser definido como True ou como uma tupla (min, max) para restringir o número de valores selecionados. assim multiple=(1, 10)
aplica pelo menos uma e no máximo dez seleções.
Outros argumentos opcionais são discutidos abaixo.
#Exemplo
Considere as seguintes tabelas e requisitos:
db.define_table('person', Field('name', unique=True))
db.define_table('dog', Field('name'), Field('owner', db.person)
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
zero=T('choose one'))
*or using a Set*
db.person.name.requires = IS_IN_DB(db(db.person.id > 10), 'person.id', '%(name)s')
É aplicado ao nível dos formulários INSERT/UPDATE/DELETE do cão. Este exemplo requer que um dog.owner
ser um id válido no campo person.id
no banco de dados db
. Devido a este validador, o dog.owner
campo é representado como uma lista suspensa. O terceiro argumento do validador é uma string que descreve os elementos na lista suspensa. No exemplo, você quer ver a pessoa %(name)s
em vez da pessoa %(id)s
. %(...)s
é substituído pelo valor do campo entre parênteses para cada registro.
o zero
opção funciona muito bem para o IS_IN_SET
validador.
O primeiro argumento do validador pode ser uma conexão de banco de dados ou um DAL Set, como em IS_NOT_IN_DB
. Isso pode ser útil, por exemplo, quando se deseja limitar os registros na lista suspensa. Neste exemplo, usamos IS_IN_DB
em um controlador para limitar os registros dinamicamente cada vez que o controlador é chamado:
def index():
(...)
query = (db.table.field == 'xyz') #in practice 'xyz' would be a variable
db.table.field.requires=IS_IN_DB(db(query), ....)
form=SQLFORM(...)
if form.process().accepted: ...
(...)
Se você deseja que o campo seja validado, mas não deseja uma lista suspensa, deverá colocar o validador em uma lista.
db.dog.owner.requires = [IS_IN_DB(db, 'person.id', '%(name)s')]
Ocasionalmente, você deseja a lista suspensa (para não usar a sintaxe da lista acima), mas deseja usar validadores adicionais. Para este efeito, o IS_IN_DB
validador leva um argumento extra _and
que pode apontar para uma lista de outros validadores aplicados se o valor validado passar IS_IN_DB
validação. Por exemplo, para validar todos os proprietários de cães em db que não estão em um subconjunto:
subset=db(db.person.id > 100)
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
_and=IS_NOT_IN_DB(subset, 'person.id'))
IS_IN_DB
tem um booleano distinct
argumento que padrão para False
. Quando definido para True
evita valores repetidos no menu suspenso.
IS_IN_DB
também leva um cache
argumento que funciona como o cache
argumento de select.
IS_IN_DB
e marcação
o IS_IN_DB
validador tem um atributo opcional multiple=False
. Se definido para True
Vários valores podem ser armazenados em um campo. Este campo deve ser do tipo list:reference
como discutido no Capítulo 6. Um exemplo explícito de marcação é discutido lá. multiple
as referências são tratadas automaticamente em formulários de criação e atualização, mas são transparentes para o DAL. Nós sugerimos fortemente o uso do plugin multiselect do jQuery para renderizar múltiplos campos.
Validadores Customizados
Todos os validadores seguem o protótipo abaixo:
class sample_validator:
def __init__(self, *a, error_message='error'):
self.a = a
self.e = error_message
def __call__(self, value):
if validate(value):
return (parsed(value), None)
return (value, self.e)
def formatter(self, value):
return format(value)
ou seja, quando chamado para validar um valor, um validador retorna uma tupla (x, y)
. E se y
é None
, então o valor passou pela validação e x
contém um valor analisado. Por exemplo, se o validador exigir que o valor seja um inteiro, x
é convertido para int(value)
. Se o valor não passou na validação, então x
contém o valor de entrada e y
contém uma mensagem de erro que explica a validação com falha. Essa mensagem de erro é usada para relatar o erro em formulários que não são validados.
O validador também pode conter método formatter
. Deve executar a conversão oposta à que o __call__
faz. Por exemplo, considere o código-fonte para IS_DATE
:
class IS_DATE(object):
def __init__(self, format='%Y-%m-%d', error_message='must be YYYY-MM-DD!'):
self.format = format
self.error_message = error_message
def __call__(self, value):
try:
y, m, d, hh, mm, ss, t0, t1, t2 = time.strptime(value, str(self.format))
value = datetime.date(y, m, d)
return (value, None)
except:
return (value, self.error_message)
def formatter(self, value):
return value.strftime(str(self.format))
Em caso de sucesso, o método __call__
lê uma string de data do formulário e a converte em um objeto datetime.date usando a string de formato especificada no construtor. o objeto formatter
pega um objeto datetime.date e o converte em uma representação de string usando o mesmo formato. o formatter
é chamado automaticamente em formulários, mas você também pode chamá-lo explicitamente para converter objetos em sua representação apropriada. Por exemplo:
>>> db = DAL()
>>> db.define_table('atable',
Field('birth', 'date', requires=IS_DATE('%m/%d/%Y')))
>>> id = db.atable.insert(birth=datetime.date(2008, 1, 1))
>>> row = db.atable[id]
>>> print db.atable.formatter(row.birth)
01/01/2008
Vários validadores
Normalmente, quando vários validadores são necessários (e armazenados em uma lista), eles são executados em ordem e a saída de um é passada como entrada para a próxima. A corrente se rompe quando um dos validadores falha.
Por outro lado, quando ligamos para o método formatter
de um campo, os formatadores dos validadores associados também são encadeados, mas em ordem inversa.
Observe que, como alternativa aos validadores personalizados, você também pode usar o
onvalidate
argumento deform.accepts(...)
,form.process(...)
eform.validate(...)
.
Como alternativa ao comportamento encadeado descrito acima, a
>>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('[email protected]')
('[email protected]', None)
>>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco')
('abco', None)
>>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co')
('@ab.co', 'enter only letters, numbers, and underscore')
>>> ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co')
('@ab.co', 'enter a valid email address')
Validadores com dependências
Geralmente, os validadores são definidos de uma vez por todas nos modelos.
Ocasionalmente, você precisa validar um campo e o validador depende do valor de outro campo. Isso pode ser feito de várias maneiras. Isso pode ser feito no modelo ou no controlador.
Por exemplo, aqui está uma página que gera um formulário de registro que solicita nome de usuário e senha duas vezes. Nenhum dos campos pode estar vazio e as duas senhas devem corresponder:
def index():
form = SQLFORM.factory(
Field('username', requires=IS_NOT_EMPTY()),
Field('password', requires=IS_NOT_EMPTY()),
Field('password_again',
requires=IS_EQUAL_TO(request.vars.password)))
if form.process().accepted:
pass # or take some action
return dict(form=form)
O mesmo mecanismo pode ser aplicado aos objetos FORM e SQLFORM.
Widgets
Aqui está uma lista de widgets web2py disponíveis:
SQLFORM.widgets.string.widget
SQLFORM.widgets.text.widget
SQLFORM.widgets.password.widget
SQLFORM.widgets.integer.widget
SQLFORM.widgets.double.widget
SQLFORM.widgets.time.widget
SQLFORM.widgets.date.widget
SQLFORM.widgets.datetime.widget
SQLFORM.widgets.upload.widget
SQLFORM.widgets.boolean.widget
SQLFORM.widgets.options.widget
SQLFORM.widgets.multiple.widget
SQLFORM.widgets.radio.widget
SQLFORM.widgets.checkboxes.widget
SQLFORM.widgets.autocomplete
Os dez primeiros deles são os padrões para os tipos de campo correspondentes. O widget "opções" é usado quando um campo requer é IS_IN_SET
ou IS_IN_DB
com multiple=False
(comportamento padrão). O widget "múltiplo" é usado quando o campo requer IS_IN_SET
ou IS_IN_DB
com multiple=True
. Os widgets "radio" e "checkboxes" nunca são usados por padrão, mas podem ser definidos manualmente. O widget de preenchimento automático é especial e discutido em sua própria seção.
Por exemplo, para ter um campo "string" representado por uma textarea:
Field('comment', 'string', widget=SQLFORM.widgets.text.widget)
Widgets também podem ser atribuídos a campos "a posteriori":
db.mytable.myfield.widget = SQLFORM.widgets.string.widget
Às vezes, os widgets aceitam argumentos adicionais e é necessário especificar seus valores. Neste caso, pode-se usar lambda
db.mytable.myfield.widget = lambda field, value: SQLFORM.widgets.string.widget(field, value, _style='color:blue')
Widgets são fábricas auxiliares e seus dois primeiros argumentos são sempre field
e value
. Os outros argumentos podem incluir atributos auxiliares normais, como _style
, _class
Alguns widgets também recebem argumentos especiais. Em particular SQLFORM.widgets.radio
e SQLFORM.widgets.checkboxes
dê uma style
argumento (não confundir com _style
) que pode ser definido como "table", "ul" ou "divs" para corresponder ao formstyle
do formulário de contenção.
Você pode criar novos widgets ou estender widgets existentes.
SQLFORM.widgets[type]
é uma aula e SQLFORM.widgets[type].widget
é uma função de membro estático da classe correspondente. Cada função de widget usa dois argumentos: o objeto de campo e o valor atual desse campo. Ele retorna uma representação do widget. Como exemplo, o widget de string pode ser recodificado da seguinte forma:
def my_string_widget(field, value):
return INPUT(_name=field.name,
_id="%s_%s" % (field._tablename, field.name),
_class=field.type,
_value=value,
requires=field.requires)
Field('comment', 'string', widget=my_string_widget)
Os valores id e class devem seguir a convenção descrita posteriormente neste capítulo. Um widget pode conter seus próprios validadores, mas é uma boa prática associar os validadores ao atributo "requer" do campo e fazer com que o widget os obtenha a partir daí.
Widget de preenchimento automático
Há dois usos possíveis para o widget de preenchimento automático: para autocompletar um campo que recebe um valor de uma lista ou para preencher automaticamente um campo de referência (em que a cadeia a ser autocompleted é uma representação da referência que é implementada como um id).
O primeiro caso é fácil:
db.define_table('category', Field('name'))
db.define_table('product', Field('name'), Field('category'))
db.product.category.widget = SQLFORM.widgets.autocomplete(
request, db.category.name, limitby=(0, 10), min_length=2)
Onde limitby
instrui o widget a exibir no máximo 10 sugestões no momento e min_length
instrui o widget a executar um retorno de chamada do Ajax para buscar sugestões somente depois que o usuário tiver digitado pelo menos dois caracteres na caixa de pesquisa.
O segundo caso é mais complexo:
db.define_table('category', Field('name'))
db.define_table('product', Field('name'), Field('category'))
db.product.category.widget = SQLFORM.widgets.autocomplete(
request, db.category.name, id_field=db.category.id)
Neste caso, o valor de id_field
diz ao widget que, mesmo que o valor a ser preenchido automaticamente seja um db.category.name
, o valor a ser armazenado é o correspondente db.category.id
. Um parâmetro opcional é orderby
que instrui o widget sobre como classificar as sugestões (alfabética por padrão).
Este widget funciona via Ajax. Onde está o retorno de chamada do Ajax? Alguma mágica está acontecendo neste widget. O retorno de chamada é um método do próprio objeto widget. Como isso é exposto? No web2py, qualquer parte do código pode gerar uma resposta, levantando uma exceção HTTP. Esse widget explora essa possibilidade da seguinte maneira: o widget envia a chamada do Ajax para a mesma URL que gerou o widget em primeiro lugar e coloca um token especial no request.vars. Se o widget for instanciado novamente, ele localizará o token e disparará uma exceção HTTP que responde à solicitação. Tudo isso é feito sob o capô e escondido para o desenvolvedor.
SQLFORM.grid
e SQLFORM.smartgrid
Atenção: o grid e o smartgrid eram experimentais antes do web2py versão 2.0 e eram vulneráveis ao vazamento de informações. A grade e a smartgrid não são mais experimentais, mas ainda não estamos prometendo compatibilidade retroativa da camada de apresentação da grade, apenas de suas APIs.
Estes são dois objetos de alto nível que criam controles CRUD complexos. Eles fornecem paginação, a capacidade de navegar, pesquisar, classificar, criar, atualizar e excluir registros de um único objeto.
Como os objetos HTML da web2py se baseiam nos objetos subjacentes e mais simples, as grades criam SQLFORMs para visualizar, editar e criar suas linhas. Muitos dos argumentos para as grades são passados para este SQLFORM. Isso significa que a documentação do SQLFORM (e FORM) é relevante. Por exemplo, a grade leva um onvalidation
ligue de volta. A lógica de processamento da grade finalmente passa para o método process () subjacente do FORM, o que significa que você deve consultar a documentação do onvalidation
para FORMs.
À medida que a grade passa por estados diferentes, como editar uma linha, uma nova solicitação é gerada. request.args tem informações sobre em qual estado a grade está.
SQLFORM.grid
O mais simples dos dois é SQLFORM.grid
. Aqui está um exemplo de uso:
@auth.requires_login()
def manage_users():
grid = SQLFORM.grid(db.auth_user)
return locals()
que produz a seguinte página:
O primeiro argumento de SQLFORM.grid
pode ser uma tabela ou uma consulta. O objeto de grade fornecerá acesso aos registros correspondentes à consulta.
Antes de mergulharmos na longa lista de argumentos do objeto de grade, precisamos entender como ele funciona. O objeto olha request.args
para decidir o que fazer (navegar, pesquisar, criar, atualizar, excluir, etc.). Cada botão criado pelo objeto vincula a mesma função ( manage_users
no caso acima), mas passa diferente request.args
.
login necessário por padrão para atualizações de dados
Por padrão, todas as URLs geradas pela grade são assinadas e verificadas digitalmente. Isso significa que não é possível executar determinadas ações (criar, atualizar, excluir) sem estar logado. Essas restrições podem ser relaxadas:
def manage_users():
grid = SQLFORM.grid(db.auth_user, user_signature=False)
return locals()
mas nós não recomendamos isso.
Várias grades por função do controlador
Devido à forma como a rede funciona, só é possível ter uma grade por função de controlador, a menos que elas sejam incorporadas como componentes via
LOAD
. Para fazer com que a grade de pesquisa padrão funcione em mais de uma grade LOADed, use umformname
para cada um.
Usando requests.args com segurança
Como a função controladora que contém a grade pode manipular os argumentos da URL (conhecidos no web2py como response.args e response.vars), a grade precisa saber quais argumentos devem ser manipulados pela grade e quais não. Aqui está um exemplo de código que permite gerenciar qualquer tabela:
@auth.requires_login()
def manage():
table = request.args(0)
if not table in db.tables(): redirect(URL('error'))
grid = SQLFORM.grid(db[table], args=request.args[:1])
return locals()
a args
argumento do grid
especifica qual request.args
deve ser repassado e ignorado pelo grid
. No nosso caso request.args[:1]
é o nome da tabela que queremos gerenciar e é tratada pelo manage
função em si, não pelo grid
. Assim, args=request.args[:1]
informa à grade para preservar o primeiro argumento de URL em quaisquer links que ele gera, anexando quaisquer argumentos específicos da grade após esse primeiro argumento.
Assinatura SQLFORM.grid
A assinatura completa da grade é a seguinte:
SQLFORM.grid(
query,
fields=None,
field_id=None,
left=None,
headers={},
orderby=None,
groupby=None,
searchable=True,
sortable=True,
paginate=20,
deletable=True,
editable=True,
details=True,
selectable=None,
create=True,
csv=True,
links=None,
links_in_grid=True,
upload='<default>',
args=[],
user_signature=True,
maxtextlengths={},
maxtextlength=20,
onvalidation=None,
oncreate=None,
onupdate=None,
ondelete=None,
sorter_icons=(XML('↑'), XML('↓')),
ui = 'web2py',
showbuttontext=True,
_class="web2py_grid",
formname='web2py_grid',
search_widget='default',
ignore_rw = False,
formstyle = 'table3cols',
exportclasses = None,
formargs={},
createargs={},
editargs={},
viewargs={},
buttons_placement = 'right',
links_placement = 'right'
)
fields
é uma lista de campos a serem buscados no banco de dados. Ele também é usado para determinar quais campos serão mostrados na exibição de grade. No entanto, ele não controla o que é exibido no formulário separado usado para editar linhas. Para isso, use o atributo legível e gravável dos campos do banco de dados. Por exemplo, em uma grade editável, suprima a atualização de um campo como este: antes de criar o SQLFORM.grid, definadb.my_table.a_field.writable = False db.my_table.a_field.readable = False
field_id
deve ser o campo da tabela a ser usado como ID, por exemplodb.mytable.id
. Isso é útil quando a consulta de grade é uma junção de várias tabelas. Qualquer botão de ação na grade (adicionar registro, visualizar, editar, excluir) funcionará em db.mytable.left
é uma expressão de junção esquerda opcional usada para construir...select(left=...)
.headers
é um dicionário que mapeia 'tablename.fieldname' para o rótulo do cabeçalho correspondente, por ex.{'auth_user.email' : 'Email Address'}
orderby
é usado como ordem padrão para as linhas. Vejo DAL chapter (vários campos são possíveis).groupby
é usado para agrupar o conjunto. Use a mesma sintaxe que você estava passando em um simplesselect(groupby=...)
.searchable
,sortable
,deletable
,editable
,details
,create
determine se é possível pesquisar, classificar, excluir, editar, visualizar detalhes e criar novos registros, respectivamente.selectable
pode ser usado para chamar uma função personalizada em vários registros (uma caixa de seleção será inserida para cada linha).
selectable = lambda ids : redirect(URL('default', 'mapping_multiple', vars=dict(id=ids)))
selectable = [('button label1', lambda...), ('button label2', lambda ...)]
paginate
define o número máximo de linhas por página.csv
se definido como true, permite baixar a grade em vários formatos (mais sobre isso depois).links
é usado para exibir novas colunas que podem ser links para outras páginas. olinks
argumento deve ser uma lista dedict(header='name', body=lambda row: A(...))
Ondeheader
é o cabeçalho da nova coluna ebody
é uma função que recebe uma linha e retorna um valor. No exemplo, o valor é umA(...)
ajudante.links_in_grid
se definido como Falso, os links serão exibidos apenas na página "detalhes" e "editar" (portanto, não na grade principal)upload
o mesmo que o do SQLFORM. O web2py usa a ação nesse URL para baixar o arquivomaxtextlength
define o comprimento máximo do texto a ser exibido para cada valor de campo, na visualização de grade. Esse valor pode ser sobrescrito para cada campo usandomaxtextlengths
, um dicionário de 'tablename.fieldname': length p.{'auth_user.email' : 50}
onvalidation
,oncreate
,onupdate
eondelete
são funções de retorno de chamada. Todos excetoondelete
pega um objeto form como entrada, ondelete pega a tabela e o id do registro
Como o formulário de edição/criação é um SQLFORM que estende o FORM, esses retornos de chamada são essencialmente usados da mesma forma como documentado nas seções para FORM e SQLFORM.
Aqui está o código do esqueleto:
def myonvalidation(form):
print "In onvalidation callback"
print form.vars
form.errors= True #this prevents the submission from completing
#...or to add messages to specific elements on the form
form.errors.first_name = "Do not name your child after prominent deities"
form.errors.last_name = "Last names must start with a letter"
response.flash = "I don't like your submission"
def myoncreate(form):
print 'create!'
print form.vars
def myonupdate(form):
print 'update!'
print form.vars
def myondelete(table, id):
print 'delete!'
print table, id
onupdate e oncreate são os mesmos callbacks disponíveis para SQLFORM.process ()
sorter_icons
é uma lista de duas sequências (ou ajudantes) que serão usadas para representar as opções de classificação para cima e para baixo para cada campo.ui
pode ser definido igual a 'web2py' e irá gerar nomes de classe amigáveis para web2py, pode ser definido igual ajquery-ui
e irá gerar nomes de classe amigável de jquery UI, mas também pode ser seu próprio conjunto de nomes de classes para os vários componentes de grade:
ui = dict(
widget='',
header='',
content='',
default='',
cornerall='',
cornertop='',
cornerbottom='',
button='button',
buttontext='buttontext button',
buttonadd='icon plus',
buttonback='icon leftarrow',
buttonexport='icon downarrow',
buttondelete='icon trash',
buttonedit='icon pen',
buttontable='icon rightarrow',
buttonview='icon magnifier')
search_widget
permite substituir o widget de pesquisa padrão e nos referimos ao código fonte em "gluon/sqlhtml.py" para detalhes.showbuttontext
permite botões sem texto (haverá efetivamente apenas ícones)_class
é a classe do contêiner de grade.exportclasses
pega um dicionário de tuplas: por padrão é definido como
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'),
csv=(ExporterCSV, 'CSV'),
xml=(ExporterXML, 'XML'),
html=(ExporterHTML, 'HTML'),
tsv_with_hidden_cols=(ExporterTSV, 'TSV (Excel compatible, hidden cols)'),
tsv=(ExporterTSV, 'TSV (Excel compatible)'))
ExporterCSV, ExporterXML, ExporterHTML e ExporterTSV são todos definidos em gluon/sqlhtml.py. Dê uma olhada naqueles para criar seu próprio exportador. Se você passar um dict como dict(xml=False, html=False)
você desativará os formatos de exportação xml e html.
formargs
é passado para todos os objetos SQLFORM usados pela grade, enquantocreateargs
,editargs
eviewargs
são passados apenas para o específico criar, editar e detalhes SQLFORMsformname
,ignore_rw
eformstyle
são passados para os objetos SQLFORM usados pela grade para formulários de criação/atualização.buttons_placement
elinks_placement
ambos usam um parâmetro ('right', 'left', 'both') que afetará onde na linha os botões (ou os links) serão colocados
deletable
,editable
edetails
são geralmente valores booleanos, mas podem ser funções que pegam o objeto de linha e decidem se devem ou não exibir o botão correspondente.
Campos virtuais em SQLFORM.grid e smartgrid
Nas versões do web2py após o 2.6, os campos virtuais são mostrados em grades como campos normais: mostrados ao lado de todos os outros campos por padrão, ou incluindo-os no campo. fields
argumento. No entanto, os campos virtuais não são classificáveis.
Em versões mais antigas do web2py, mostrar campos virtuais em uma grade requer o uso de links
argumento. Isso ainda é suportado para versões mais recentes. Se a tabela db.t1 tiver um campo chamado t1.vfield que é baseado nos valores de t1.field1 e t1.field2, faça o seguinte:
grid = SQLFORM.grid(db.t1, ..., fields = [t1.field1, t1.field2, ...],
links = [dict(header='Virtual Field 1', body=lambda row:row.vfield), ...] )
Em todos os casos, porque t1.vfield depende de t1.field1 e t1.field2, esses campos devem estar presentes na linha. No exemplo acima, isso é garantido incluindo t1.field1 e t1.field2 no argumento fields. Como alternativa, mostrar todos os campos também funcionará. Você pode suprimir um campo da exibição definindo o atributo legível como False.
Observe que ao definir o campo virtual, a função lambda deve qualificar os campos com o nome do banco de dados, mas no argumento de links, isso não é necessário. Então, para o exemplo acima, o campo virtual pode ser definido como:
db.define_table('t1', Field('field1', 'string'),
Field('field2', 'string'),
Field.Virtual('virtual1', lambda row: row.t1.field1 + row.t1.field2),
...)
SQLFORM.smartgrid
UMA SQLFORM.smartgrid
parece muito com um grid
, na verdade, ele contém uma grade, mas ela é projetada para tomar como entrada não uma consulta, mas apenas uma tabela e navegar na referida tabela e nas tabelas de referência selecionadas.
Por exemplo, considere a seguinte estrutura de tabela:
db.define_table('parent', Field('name'))
db.define_table('child', Field('name'), Field('parent', 'reference parent'))
Com o SQLFORM.grid, você pode listar todos os pais:
SQLFORM.grid(db.parent)
todas as crianças:
SQLFORM.grid(db.child)
e todos os pais e filhos em uma tabela:
SQLFORM.grid(db.parent, left=db.child.on(db.child.parent==db.parent.id))
Com o SQLFORM.smartgrid, você pode colocar todos os dados em um objeto que gera ambas as tabelas:
@auth.requires_login()
def manage():
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
return locals()
que se parece com isso:
Observe os links extras "filhos". Pode-se criar o extra links
usando um regular grid
mas eles apontariam para uma ação diferente. Com um smartgrid
eles são criados automaticamente e manipulados pelo mesmo objeto.
Observe também que, quando se clica no link "children" para um determinado pai, você só obtém a lista de filhos para esse pai (e isso é óbvio), mas também percebe que se um agora tentasse adicionar um novo filho, o valor pai do O novo filho é automaticamente definido para o pai selecionado (exibido nos breadcrumbs associados ao objeto). O valor deste campo pode ser sobregravado. Podemos evitar isso fazendo-o somente leitura:
@auth.requires_login()
def manage():
db.child.parent.writable = False
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
return locals()
Se o linked_tables
argumento não é especificado todas as tabelas de referência são vinculadas automaticamente. De qualquer forma, para evitar a exposição acidental de dados, recomendamos listar explicitamente as tabelas que devem ser vinculadas.
O código a seguir cria uma interface de gerenciamento muito poderosa para todas as tabelas no sistema:
@auth.requires_membership('managers')
def manage():
table = request.args(0) or 'auth_user'
if not table in db.tables(): redirect(URL('error'))
grid = SQLFORM.smartgrid(db[table], args=request.args[:1])
return locals()
assinatura smartgrid
o smartgrid
leva os mesmos argumentos como um grid
e mais alguns com algumas ressalvas:
- O primeiro argumento é uma tabela, não uma consulta
- Existe um argumento extra
constraints
que é um dicionário de 'tablename': consulta que pode ser usada para restringir ainda mais o acesso aos registros exibidos na grade 'tablename'. - Existe um argumento extra
linked_tables
que é uma lista de nomes de tabelas que devem ser acessíveis através do smartgrid. divider
permite especificar um caractere para usar no navegador de breadcrumb,breadcrumbs_class
aplicará a classe ao elemento breadcrumb- Todos os argumentos, mas a tabela,
args
,linked_tables
euser_signatures
podem ser dicionários, conforme explicado abaixo.
Considere a grade anterior:
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
Permite que se acesse tanto um db.parent
e um db.child
. Além dos controles de navegação, para cada tabela, uma smarttable não é nada além de uma grade. Isso significa que, nesse caso, um smartgrid pode criar uma grade para pai e uma grade para filho. Podemos querer passar diferentes conjuntos de parâmetros para essas grades. Por exemplo, diferentes conjuntos de searchable
parâmetros.
Enquanto para uma grade nós passamos um booleano:
grid = SQLFORM.grid(db.parent, searchable=True)
Para um smartgrid nós passamos um dicionário de booleans:
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'],
searchable= dict(parent=True, child=False))
Dessa forma, tornamos os pais pesquisáveis, mas os filhos de cada pai não são pesquisáveis (não deve haver muitos que precisem do widget de pesquisa).
controle de acesso à rede e smartgrid
grid
e smartgrid
não utilizam automaticamente o controle de acesso, como crud mas você pode integrá-lo com auth
usando verificação de permissão explícita:
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_membership('managers'),
deletable = auth.has_membership('managers'))
ou
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_permission('edit', 'auth_user'),
deletable = auth.has_permission('delete', 'auth_user'))
smartgrid plurais
o smartgrid
é o único objeto em web2py que exibe o nome da tabela e precisa do singular e do plural. Por exemplo, um dos pais pode ter um "Filho" ou muitos "Filhos". Portanto, um objeto de tabela precisa conhecer seus próprios nomes no singular e no plural. O web2py normalmente os adivinha, mas você pode defini-los explicitamente:
db.define_table('child', ..., singular="Child", plural="Children")
ou com:
db.define_table('child', ...)
db.child._singular = "Child"
db.child._plural = "Children"
Eles também devem ser internacionalizados usando o T
operador.
Os valores plural e singular são então usados por smartgrid
para fornecer nomes corretos para cabeçalhos e links.