Utilizando z3c.form
Utilizando formulários z3c.form no Plone
O z3c.form é um framework avançado para construção e utilização de formulários e widgets dentro do Zope e do Plone. Ele provê um caminho fácil e flexível para exibir formulários e manipular suas etapas de criação, validação bem como possíveis ações subsequentes.
Introdução
O que é o z3cform e por que eu devo considerar usá-lo.
O que é plone.app.z3cform?
plone.app.z3cform é um pacote que fornece widgets e outros utilitários para fazer formulários dentro do Plone. Ele também contém templates para seu irmão mais velho, o pacote plone.z3cform , que por sua vez é uma camada bem fina encapsulando o z3c.form.
Formulários que você faz com plone.app.z3cform são essencialmente os mesmos que os formulários de Zope 3 puro construídos com z3c.form. Sem cláusulas secretas nem hacks especiais para Zope 2 ou Plone (coisas como self.context.aq_inner).
O pacote z3c.form é distribuído com uma documentação excelente.
z3c.form vs. Archetypes
Formulários de Archetypes são difíceis de usar independentemente de tipos de conteúdo. Isso levou a variados hacks no passado, onde tipos de conteúdo de Archetypes foram erroneamente utilizados como ferramentas, formulários de pesquisa etc.
z3c.form vs. formlib
zope.formlib é por vezes inflexível ou coberta por hacks, devido à sua falta de adaptabilidade e de um conjunto sólido de widgets.
O que irei aprender neste tutorial?
Este tutorial mostrará como criar um formulário de comentários simples com z3c.form, que será então extendido e exibido dentro de um viewlet.
Maiores informações
Você pode encontrar maiores informações nas páginas do PyPi para os pacotes plone.z3cform e plone.app.z3cform.
O novo Plone Developers Manual também traz documentação para o z3c.form.
O código-fonte deste exemplo está disponível no collective.
Além disso, o pacote z3c.form foi usado extensivamente no produto plone.app.discussion. No código-fonte desse pacote, você encontrará toneladas de exemplos de formulários z3c.form.
Criando um buildout para o desenvolvimento z3c.form
Algumas coisas que você precisa antes de nós iniciarmos.
Antes de podermos criar um formulário z3c.form dentro do Plone, precisamos criar um buildout Plone com as dependências de pacotes necessárias. Certifique-se de que você tem o Python, Setuptools e ZopeSkel instalados. Instruções podem ser encontradas no tutorial sobre buildout. Certifique-se também de ter a versão mais recente do ZopeSkel instalada. Você pode atualizar seu ZopeSkel com:$ easy_install-2.4 -U ZopeSkel
Crie um ambiente buildout
Inicialmente criamos um novo buildout Plone com o auxílio do script paster:
$ paster create -t plone3_buildout z3cformtutorial-buildout
Apenas aperte <enter> para todas as perguntas para escolher os valores padrão. Atente somente à pergunta da senha do Zope, a única que você deverá preencher individualmente.
Selected and implied templates: ZopeSkel#plone3_buildout A buildout for Plone 3 projects Variables: egg: z3cformtutorial_buildout package: z3cformtutorialbuildout project: z3cformtutorial-buildout Enter plone_version (Which Plone version to install) ['3.3.1']: Enter zope2_install (Path to Zope 2 installation; leave blank to fetch one) ['']: Enter plone_products_install (Path to directory containing Plone products; leave blank to fetch one) ['']: Enter zope_user (Zope root admin user) ['admin']: Enter zope_password (Zope root admin password) ['']: admin Enter http_port (HTTP port) [8080]: Enter debug_mode (Should debug mode be "on" or "off"?) ['off']: Enter verbose_security (Should verbose security be "on" or "off"?) ['off']: Creating template plone3_buildout Creating directory ./z3cformtutorial-buildout Copying README.txt to ./z3cformtutorial-buildout/README.txt Copying bootstrap.py to ./z3cformtutorial-buildout/bootstrap.py Copying buildout.cfg_tmpl to ./z3cformtutorial-buildout/buildout.cfg Recursing into products Creating ./z3cformtutorial-buildout/products/ Copying README.txt to ./z3cformtutorial-buildout/products/README.txt Recursing into src Creating ./z3cformtutorial-buildout/src/ Copying README.txt to ./z3cformtutorial-buildout/src/README.txt Recursing into var Creating ./z3cformtutorial-buildout/var/ Copying README.txt to ./z3cformtutorial-buildout/var/README.txt ----------------------------------------------------------- Generation finished You probably want to run python bootstrap.py and then edit buildout.cfg before running bin/buildout -v See README.txt for details -----------------------------------------------------------
Ajuste as versões de pacote necessárias para trabalhar com o z3c.form
Para fazer o z3c.form funcionar no Plone, precisamos instalar certos pacotes com um conjunto específico de versões. Para facilitar as coisas, podemos extender nosso buildout com o Known Good Set (KGS) do plone.autoform. Simplesmente acrescente "http://good-py.appspot.com/release/plone.autoform/1.0b2" à linha extends do seu buildout.
buildout.cfg
extends =
http://good-py.appspot.com/release/plone.autoform/1.0b2
...
Para o Plone 4, não precisamos de um KGS. Basta ajustar a versão do pacote zope.schema no buildout.cfg:
[versions] zope.schema = 3.6.0
Execute o buildout
Após ajustar as versões, podemos rodar o script de buildout:
$ cd z3cformtutorial-buildout $ python bootstrap $ ./bin/buildout
Agora estamos prontos para criar um pacote Python contendo o formulário que iremos criar no próximo passo.
Criando um pacote Python para o formulário z3c.form
Agora criaremos um novo pacote Python contendo um formulário simples.
Para criar um novo pacote Python, vá até o diretório src/ do seu ambiente buildout e permita que o paster faça o trabalho para você. Informe "example" como namespace e "z3cformtutorial" como nome do pacote.
$ cd src $ paster create -t plone example.z3cformtutorial
A saída se parecerá com isto:
Selected and implied templates:
ZopeSkel#basic_namespace A project with a namespace package
ZopeSkel#plone A Plone project
Variables:
egg: example.z3cformtutorial
package: examplez3cformtutorial
project: example.z3cformtutorial
Enter namespace_package (Namespace package (like plone)) ['plone']: example
Enter package (The package contained namespace package (like example)) ['example']: z3cformtutorial
Enter zope2product (Are you creating a Zope 2 Product?) [False]:
Enter version (Version) ['1.0']:
Enter description (One-line description of the package) ['']:
Enter long_description (Multi-line description (in reST)) ['']:
Enter author (Author name) ['Plone Foundation']:
Enter author_email (Author email) ['plone-developers@lists.sourceforge.net']:
Enter keywords (Space-separated keywords/tags) ['']:
Enter url (URL of homepage) ['http://svn.plone.org/svn/plone/plone.example']:
Enter license_name (License name) ['GPL']:
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]:
Creating template basic_namespace
Creating directory ./example.z3cformtutorial
Recursing into +namespace_package+
Creating ./example.z3cformtutorial/example/
Recursing into +package+
Creating ./example.z3cformtutorial/example/z3cformtutorial/
Copying __init__.py_tmpl to ./example.z3cformtutorial/example/z3cformtutorial/__init__.py
Copying __init__.py_tmpl to ./example.z3cformtutorial/example/__init__.py
Copying README.txt_tmpl to ./example.z3cformtutorial/README.txt
Recursing into docs
Creating ./example.z3cformtutorial/docs/
Copying HISTORY.txt_tmpl to ./example.z3cformtutorial/docs/HISTORY.txt
Copying setup.cfg to ./example.z3cformtutorial/setup.cfg
Copying setup.py_tmpl to ./example.z3cformtutorial/setup.py
Creating template plone
Recursing into +namespace_package+
Recursing into +package+
./example.z3cformtutorial/example/z3cformtutorial/__init__.py already exists (same content)
Copying configure.zcml_tmpl to ./example.z3cformtutorial/example/z3cformtutorial/configure.zcml
Copying tests.py_tmpl to ./example.z3cformtutorial/example/z3cformtutorial/tests.py
Recursing into docs
Copying INSTALL.txt_tmpl to ./example.z3cformtutorial/docs/INSTALL.txt
Copying LICENSE.GPL to ./example.z3cformtutorial/docs/LICENSE.GPL
Copying LICENSE.txt_tmpl to ./example.z3cformtutorial/docs/LICENSE.txt
Replace 1022 bytes with 1272 bytes (0/32 lines changed; 8 lines added)
Copying setup.py_tmpl to ./example.z3cformtutorial/setup.py
------------------------------------------------------------------------------
The project you just created has local commands. These can be used from within
the product.
usage: paster COMMAND
Commands:
addcontent Adds plone content types to your project
For more information: paster help COMMAND
------------------------------------------------------------------------------
Running /usr/bin/python2.4 setup.py egg_info
Adicione as dependências do z3c.form ao pacote
Agora adicionaremos o pacote plone.app.z3cform como dependência ao seu recém criado pacote Python. Por sua vez, o pacote plone.z3cform será automaticamente baixado como dependência de plone.app.z3cform:
src/example.z3cformtutorial/setup.py
...
install_requires=[
'setuptools',
# -*- Extra requirements: -*-
'plone.app.z3cform',
],
...
Depois disso, adicionamos nosso pacote à configuração de nosso buildout:
buildout.cfg
[buildout]
...
eggs =
example.z3cformtutorial
...
develop =
src/example.z3cformtutorial
...
[instance]
...
zcml =
example.z3cformtutorial
E então re-executamos o buildout para baixar as dependências:
$ ./bin/buildout
Agora estamos prontos para de fato criarmos nosso primeiro formulário.
Crie um formulário z3cform simples
Crie um formulários simples para comentários.
Primeiramente definimos um schema com três campos: título, autor e campo de texto para os comentários:
from zope import interface, schema
class IComment(interface.Interface):
title = schema.TextLine(title=u"Title")
author = schema.TextLine(title=u"Author",
required=False)
text = schema.TextLine(title=u"Text")
O formulário de comentários usa a definição do schema para modelar e mais tarde renderizar o formulário. Nesse momento também definimos uma label que será exibida acima do formulário:
from z3c.form import form, field
CommentForm(form.Form):
fields = field.Fields(IComment)
ignoreContext = True # don't use context to get widget data
label = "Add a comment"
Em seguida, acrescentamos o botão de envio na forma de um método decorado que irá manipular a informação recebida. Extraímos os dados do request e retornamos ao formulário se houver erros de validação, caso contrário prosseguimos:
from z3c.form import button
@button.buttonAndHandler(u'Post comment')
@button.buttonAndHandler(u'Post comment')
def handleApply(self, action):
data, errors = self.extractData()
if errors:
return
if data.has_key('text'):
print data['text'] # ... or do stuff
Como último passo, precisamos encapsular o formulário em uma página Plone padrão:
from plone.z3cform.layout import wrap_form wrap_form(CommentForm)
Colocando todos esses passos juntos, nosso arquivo comment.py deve se parecer com isto:
from zope import interface, schema
from z3c.form import form, field, button
from plone.z3cform.layout import wrap_form
class IComment(interface.Interface):
title = schema.TextLine(title=u"Title")
author = schema.TextLine(title=u"Author", required=False)
text = schema.TextLine(title=u"Text")
class CommentForm(form.Form):
fields = field.Fields(IComment)
ignoreContext = True # don't use context to get widget data
label = u"Add a comment"
@button.buttonAndHandler(u'Post comment')
def handleApply(self, action):
data, errors = self.extractData()
if data.has_key('title') and data.has_key('text'):
print data['title'] # ... or do stuff
CommentView = wrap_form(CommentForm)
Para conhecer em detalhes o schema do z3c.form, veja http://docs.zope.org/z3c.form/browser/README.html.
A única coisa que falta fazer para usar nosso formulário é registrá-lo no configure.zcml de nosso pacote:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="example.z3cformtutorial">
<!-- Include z3c.form as dependency -->
<include package="plone.app.z3cform" />
<!-- Register the comment form -->
<browser:page
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
name="comment_form"
class=".comment.CommentView"
permission="zope2.View"
/>
</configure>
Inicie a instância Zope:
$ ./bin/instance fg
Vá até a ZMI e crie uma instância Plone com o nome 'test' e com o perfil de Generic Setup "Plone z3c.form support". Então acesse a seguinte URL:
http://localhost:8080/test/comment_form
Exibindo seu formulário z3c.form dentro de um Viewlet
Agora iremos exibir o formulário de comentários dentro de um Viewlet.
A fim de exibir o formulário de comentários dentro de um Viewlet, primeiramente criamos um novo arquivo chamado commentviewlet.py contendo um Viewlet padrão com um page template associado e um título:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import ViewletBase
class CommentViewlet(ViewletBase):
index = ViewPageTemplateFile('commentviewlet.pt')
label = 'Add Comment'
Para exibir o formulário de contatos dentro do Viewlet, temos que atualizar o request definindo um método update da seguinte forma:
def update(self):
super(CommentViewlet, self).update()
z2.switch_on(self, request_layer=IFormLayer)
self.form = CommentForm(aq_inner(self.context), self.request)
self.form.update()
Considerando os imports necessários, nosso arquivo commentviewlet.py deve se parecer com:
from Acquisition import aq_inner
from z3c.form.interfaces import IFormLayer
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import ViewletBase
from plone.z3cform import z2
from example.z3cformtutorial.comment import CommentForm
class CommentViewlet(ViewletBase):
index = ViewPageTemplateFile('commentviewlet.pt')
label = 'Add Comment'
def update(self):
super(CommentViewlet, self).update()
z2.switch_on(self, request_layer=IFormLayer)
self.form = CommentForm(aq_inner(self.context), self.request)
self.form.update()
Em seguida, criamos um novo page template chamado commentviewlet.pt para exibir nosso formulário chamando o método render do formulário:
<h2 tal:content="view/label">View Title</h2> <div id="layout-contents"> <div tal:replace="structure view/form/render" /> </div>
A única coisa que falta fazer é registrar o novo Viewlet no seu configure.zcml:
<browser:viewlet
name="comment_viewlet"
for="Products.CMFCore.interfaces.IContentish"
manager="plone.app.layout.viewlets.interfaces.IBelowContent"
class=".commentviewlet.CommentViewlet"
permission="zope2.View"
/>
Reinicie sua instância Zope:
$ ./bin/instance restart
e acesse a seguinte URL para ver o novo viewlet com formulário de comentários em ação:
http://localhost:8080/test
OBS: plone.z3cform >= 0.6.0
Se você está usando o pacote plone.z3cform >= 0.6.0, o formulário de comentários precisa fornecer a interface IWrappedForm, senão o Plone levantará uma exceção de "maximum recursion error". Adicione o código em negrito fazer o formulário funcionar em todas as versões do plone.z3cform:
from Acquisition import aq_inner
from zope.interface import alsoProvides
from z3c.form.interfaces import IFormLayer
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import ViewletBase
from plone.z3cform import z2
from example.z3cformtutorial.comment import CommentForm
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
try:
from plone.z3cform.interfaces import IWrappedForm
HAS_WRAPPED_FORM = True
except ImportError:
HAS_WRAPPED_FORM = False
class CommentViewlet(ViewletBase):
index = ViewPageTemplateFile('commentviewlet.pt')
label = 'Add Comment'
def update(self):
super(CommentViewlet, self).update()
z2.switch_on(self, request_layer=IFormLayer)
self.form = CommentForm(aq_inner(self.context), self.request)
if HAS_WRAPPED_FORM:
alsoProvides(self.form, IWrappedForm)
self.form.update()
