Você está aqui: Página Inicial Documentação Utilizando z3c.form

Utilizando z3c.form

por Ramiro B. da Luz última modificação 13/11/2011 09:56
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.

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.z3cformplone.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 PythonSetuptools 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()