Nota: questa è la vista stampabile contenente tutte le pagine della guida in una sola pagina. Se si preferisce c'è disponibile la versione suddivisa in più pagine.
Come sviluppare dei prodotti in Zope 3 utilizzando le FormLib.
Componenti Richiesti
Gli esempi mostrati necessitano dei seguenti componenti:
- Python >= 2.4.3
- Zope >= 2.9.3
- Plone >= 2.5
- Five >= 1.4
Gli esempi descritti in questo tutorial possono funzionare con
versioni differenti, ma l'autore non ha eseguito test e la strada da
percorrere potrebbe essere molto lunga.
Qualche Annotazione Prima di Iniziare
Si inizia a lavorare partendo da un prodotto dal nome
"ploneexample.formlib". Il codice completo per l'esempio può essere
navigato usando il browser svn o scaricato usando Subversion attraverso il repository svn.
La prima cosa che uno sviluppatore Plone nota di insolito è il modo
di chiamare il prodotto. Tradizionalmente i prodotti Plone usano la
notazione CamelCase
per i nomi in modo che non contengano punti (per esempio PloneFormLib).
Ma in quanto uno degli obiettivi prefissati per questo tutorial, per un
migliore esercizio verrà utilizzata la nuova notazione quando si
proverà ad essere pythonic il più possibile usando le lettere minuscole
per i nomi dei prodotti.
L'Importanza di Pythonic
La maggior parte di voi potrebbe chiedersi, "ma perchè bisogna preoccuparsi di essere pythonic?" La ragione più evidente di mantenere lo stile pythonic è rendere le cose così semplici da essere comprese da chi ha già familiarità con Python.
Quando si sviluppano prodotti standard Zope 2 e vengono collocati nella directory Products dell'istanza Zope 2, Zope magicamente li inserisce nel namespace dei Products (per esempio il path del pacchetto CMFPlone attualmente diventa Products.CMPFlone). Inserire i prodotti nel PYTHONPATH come ogni altro package e renderlo riusabile in generale è una Buona Cosa TM.
Ancora, rendendo i prodotti in stile pythonic, è possibile usare dei tool generici di Python quali easy_install e setuptools (entrambi degli ottimi package di gestione dei componenti) per lavorare con questi prodotti.
Per la creazione iniziale del prodotto verrà usato un piccolo componente del progetto Python Paste.
Per il resto di questo tutorial si presume che abbiato installato i prodotti setuptools e easy_install scritti da Phillip J.
Eby. Così come il componente paster insieme al package dei template ZopeSkel. Per le istruzioni su come configurare paster e ZopeSkel, si guardi l'eccellente articolo su ZopeSkel di Daniel Nouri.
Quindi, si parte:
$ paster create -t plone ploneexample.formlib
Selected and implied templates:
ZopeSkel#plone_core A Plone Core project
Variables:
package: ploneexampleformlib
project: ploneexample.formlib
Creating template plone_core
Enter namespace_package (Namespace package) ['plone']: ploneexample
Enter package (The package contained namespace package (like i18n)) ['']: formlib
Enter pythonproducts (Are you making a productsless Zope 2 Product?) [False]: True
Enter version (Version) ['0.1']:
Enter description (One-line description of the package) ['']: A Plone product for demonstrating zope.formlib usage
Enter long_description (Multi-line description (in reST)) ['']:
Enter author (Author name) ['Plone Foundation']: Rocky Burt
Enter author_email (Author email) ['plone-developers@lists.sourceforge.net']: rocky@serverzen.com
Enter keywords (Space-separated keywords/tags) ['']:
Enter url (URL of homepage) ['http://svn.plone.org/svn/plone/plone.i18n']: http://dev.plone.org/collective/browser/examples/ploneexample.formlib
Enter license_name (License name) ['GPL']:
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]:
Recursing into +namespace_package+
Creating ./ploneexample.formlib/ploneexample/
Recursing into +package+
Creating ./ploneexample.formlib/ploneexample/formlib/
Copying HISTORY.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/HISTORY.txt
Copying LICENSE.GPL to ./ploneexample.formlib/ploneexample/formlib/LICENSE.GPL
Copying LICENSE.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/LICENSE.txt
Copying README.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/README.txt
Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/formlib/__init__.py
Copying configure.zcml to ./ploneexample.formlib/ploneexample/formlib/configure.zcml
Copying version.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/version.txt
Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/__init__.py
Copying setup.cfg to ./ploneexample.formlib/setup.cfg
Copying setup.py_tmpl to ./ploneexample.formlib/setup.py
Running /usr/bin/python2.4 setup.py egg_info
Iniziamo a sviluppare
Dopo questa operazione si dovrebbe ora avere una directory chiamata ploneexample.formlib nella directory corrente. Per le finalità di sviluppo verrà usato setuptools per configurare il progetto ploneexample.formlib nel PYTHONPATH in modo da poterlo rendere disponibile per Zope. Spostarsi nella directory del progetto ploneexample.formlib ed eseguire il seguente comando (ricordando di eseguirlo come sudo se si è in un ambiente UNIX e non si hanno i permessi sufficienti per scrivere nella directory site-packages di python):
$ python2.4 setup.py develop
Ci sarà un output simile al seguente:
[snip output...]
Installed /home/rocky/Documents/developing/projects/ploneexample.formlib
Processing dependencies for ploneexample.formlib==0.1dev
Inseriamo il Nuovo Prodotto in Zope 2
Ora che si ha a disposizione un prodotto per Zope 2 pienamente
eseguibile, è possibile procedere con la sua configurazione all'interno
dell'istanza Zope 2. Finchè Zope e CMF/Plone non verranno aggiornati
(Zope 2.10 possiede già le modifiche necessarie), è necessario
utilizzare il prodotto pythonproducts per abilitare i prodotti che risiedono all'esterno della directory Products dell'istanza Zope.
Scaricate ed installate pythonproducts seguendo le istruzioni che
trovate nel prodotto. Per chi avesse fretta, l'installazione di
pythonproducts consiste nel download del sorgente, la sua estrazione in
una dir temporanea, e l'esecuzione del comando:
$ python2.4 setup.py install --home $INSTANCE_HOME
Ora è necessario attivare il package ploneexample.formlib quale prodotto Zope 2. Per fare ciò bisogna spostarsi nella directory etc/package-includes dell'istanza Zope 2 e creare un nuovo file chiamato ploneexample.formlib-configure.zcml il cui contenuto deve essere:
<include package="ploneexample.formlib" />
E' possibile ora provare l'instanza Zope 2 per avere la conferma che il nuovo prodotto ploneexample.formlib sia
disponibile. Si può fare ciò eseguendo l'istanza Zope normalmente e
verificare nella sezione Prodotti nel Pannello di Controllo della ZMI
se il prodotto è correttamente installato. Dovrebbe essere visibile una
cosa del tipo:
ploneexample.formlib (Installed product ploneexample.formlib)
Costruzione di una semplice form di ricerca per i contenuti Plone.
zope.formlib è un package progettato per facilitare lo
sviluppo di form web nelle proprie applicazioni Zope. Nella sua più
semplice espressione, è possibile confrontare cosa genera formlib
con la controparte auto-generata da Archetype che permette la
visualizzazione di un contenuto (base_view) e la sua modifica
(base_edit). In pratica, i componenti basati su formlib sono
dei normali componenti di visualizzazione Zope con alcune classi base
per generare automaticamente risultati basati sullo schema e altre
informazioni di configurazione.
Grazie alla sua inclusione in Zope 2.9.3, zope.formlib è ora
distribuito con le release di Zope 2. Ovviamente, Five >= 1.4 è
richiesto per eseguire correttamente questo package Zope 3.
Definire la Prima Form
Per gli scopi di questo tutorial, verrà costruita una
semplice form di ricerca per i contenuti Plone. Questa form è simile a quella
che si ottiene dalla Ricerca Avanzata di Plone, ma molto più semplice.
Il codice sorgente di questi esempi sono disponibili nel browser svn e nel repository svn .
La Classe Form
Si incomincia creando un nuovo file, browser.py, che deve risiedere in ploneexample.formlib/ploneexample/formlib/. Il file browser.py comprende tutto ciò che è necessario per lavorare correttamente. Aggiungiamo gli import necessari:
from zope import interface, schema
from zope.formlib import form
from Products.CMFCore import utils as cmfutils
from Products.Five.browser import pagetemplatefile
from Products.Five.formlib import formbase
Ora si passa alla costruzione dell'interfaccia Zope 3:
class ISearch(interface.Interface):
text = schema.TextLine(title=u'Search Text',
description=u'The text to search for',
required=False)
description = schema.TextLine(title=u'Description',
required=False)
Lo scopo dell'interfaccia in questo caso non è quello di descrivere
un particolare content object, ma è quello di definire i campi
che formlib userà. Più avanti si scoprirà come le
tradizionali interfacce usate per descrivere le attuali classi di
contenuti possono essere usate in combinazione con formlib per autogenerare propriamente i form di aggiunta e modifca dei contenuti.
E ora ecco la classe della form stessa. Si partirà dalla prima parte della definizione della classe.
class SearchForm(formbase.PageForm):
form_fields = form.FormFields(ISearch)
result_template = pagetemplatefile.ZopeTwoPageTemplateFile('search-results.pt')
Viene usata la classe PageForm come superclasse per ereditare le
funzionalità dalla formlib stessa. Come comportamento predefinito,
PageForm sa come generare tutta la porzione di HTML necessaria per la
visualizzazione della form. Ma per fare ciò, formlib necessita di
sapere di quali campi abbiamo bisogno. E' possibile impostare ciò
utilizzando l'attributo form_field. FormField è una classe di
appoggio di tipo formlib che genera i campi appropriati da un qualsiasi
schema Zope 3 (in questo caso lo schema di interfaccia che è stata
appena definita).
L'attributo result_template definisce una nuova pagina modello che viene usata per mostrare tutti i risultati della ricerca.
Ora viene definita una action per il form:
@form.action("search")
def action_search(self, action, data):
catalog = cmfutils.getToolByName(self.context, 'portal_catalog')
kwargs = {}
if data['text']:
kwargs['SearchableText'] = data['text']
if data['description']:
kwargs['description'] = data['description']
self.search_results = catalog(**kwargs)
self.search_results_count = len(self.search_results)
return self.result_template()
Questo è dove il vero lavoro prende posto. Una action formlib è
generalmente un gestore che viene invocato quando si invia un form
HTML. In questo caso è stata creata una nuova action chiamata search,
che viene poi usata quando un utente clicca sul pulsante di ricerca. La
classe basata su formlib automaticamente comprende come rendere
indipendente il pulsante search dal form HTML. Il particolare gestore della action torna come risultato il result template.
Il Result Page Template
Al fine di mostrare i risultati della form di ricerca è necessario
configurare un semplice modello di pagina. Questo modello è stato
chiamato search-results.pt
. La maggior parte del template non è di grande interesse, ma per gli
scopi di questo tutorial viene mostrata la porzione relativa alla
stampa dei risultati.
<tal:block tal:repeat="single view/search_results">
<div class="single-result">
<h4>
<span tal:replace="repeat/single/number"></span>.
<a tal:content="single/Title" tal:attributes="href single/getURL" href=""></a>
</h4>
<p tal:content="single/Description"></p>
</div>
</tal:block>
Siccome la precedente classe basata su formlib era una vista
regolare, essa prende forma all'interno del page template. E ora è
possibile assegnare semplici attributi alla vista che si possono quindi
cogliere all'interno del template.
Collegare il Tutto Attraverso ZCML
Ora che è stata definita la classe del form e la pagina dei
risultati, per andare avanti è necessario inserire il tutto all'interno
di Zope. E' possibile far ciò attraverso il file configure.zcml
E' necessario, quindi, aggiungere il seguente frammento di ZCML:
<browser:page
name="search.html"
for="Products.CMFPlone.Portal.PloneSite"
class=".browser.SearchForm"
permission="zope.Public"
/>
I lettori attenti noteranno il particolare nome del tag per configurare il componente della vista, browser:page.
Questo tag XML attualmente utilizza un prefisso tipico di XML che è
necessario che sia definito. Normalmente questo tag è contenuto
all'interno del tag configure come questo:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
Controllare più volte il file configure.zcml se ci sono dubbi sulla sua configurazione.
Inoltre, poichè formlib è basato interamente sulle viste di Zope 3,
viene utilizzato lo stesso metodo nella ZCML. Per chi non fosse
familiare con le viste di Zope 3, questi frammenti vogliono significare
che la vista search.html sarà disponibile direttamente
attraverso il sito Plone, così l'indirizzo dovrebbe essere qualcosa
tipo: http://localhost:8080/plonesite/search.html
Search Form

Search Results

In breve, il Primo Esempio di zope.formlib
L'esempio qui proposto mostra il più semplice form che può essere
creato con formlib e come integrarlo in una semplice action. Dovrebbe
essere ovvio da questo esempio come è possibile utilizzare formlib per
rimpiazzare semplicemente la logica basata su CMFFormController. Di
certo formlib può essere utilizzato per tante altre cose molto più
avanzate come offire funzionalità di sub-form oppure form autogenerate
di aggiunta e modifica per classi di tipo contenuto.
La cosa maggiormente in evidenza è che zope.formlib è pronto per
essere utilizzato in Plone già da oggi. E considerato che formlib è
così facile da utilizzare, consigliamo a tutti gli sviluppatori
di applicazioni Plone di dargli uno sguardo.
Schema Tradizionale Archetypes
Attualmente tutta la comunità Plone utilizza generalmente il framework Archetypes
per sviluppare i propri tipi di contenuto. Questo framework ha
introdotto il suo concetto di schema, di widget e di campi che molti
sviluppatori hanno trovato soddisfacente. Il punto è che da lì a poco
(forse anche in contemporanea) la comunità Zope 3 stava già sviluppando
i propri costrutti degli stessi concetti. Il risultato è stato zope.interface, zope.schema e zope.app.form. Infine è arrivata zope.formlib che dovrebbe aiutare a unire tutti i precedenti in una unica interfaccia utente.
Zope 3 Schema
Uno schema Zope 3 funziona in modo molto simile ad uno schema di
Archetypes se lo si osserva da una prospettiva concettuale. Entrambi
condividono i prinicipi basilari della definizione dei campi di un
tipo. Una differenza eclatante è che un campo dello schema di Zope 3
non ha una associazione diretta (all'interno dello schema stesso) con
nessuna logica di interfaccia. Cioè, esso non conosce quale widget
verrà utilizzato per mostrare il suo contenuto in un form web. In
questo modo vengono trattati separatamente il contenuto e l'interfaccia
utente. Il contenuto, dopo tutto, non dovrebbe mai aver bisogno di
conoscere o preoccuparsi di ciò che avviene nell'interfaccia. Si deve
preoccupare solo dei dati.
L'Esempio
Affinchè i concetti possano essere chiari, i precedenti codici di
esempio verranno smembrati in più comuni moduli python. Questo vuol
dire che bisognerà migrare l'interfaccia di ISearch dal modulo browser al nuovo modulo interfaces.
Uno Schema
In Zope 3 gli schema sono definiti costruendo interfacce Zope 3,
molto simili a quella creata nella seconda parte di questo tutorial.
Una cosa su cui non è stata fatta chiarezza è che
in effetti è stato creato uno schema di rappresentazione dei campi di
ricerca.
Si inizia creando una nuova interfaccia nel modulo interfaces chiamato IExampleContent. Questa interfaccia avrà vari campi e dovrebbe presentarsi più o meno così:
class IExampleContent(interface.Interface):
title = schema.TextLine(title=u'Title',
required=True)
description = schema.Text(title=u'Description',
required=False)
funny = schema.Bool(title=u'Am I Funny?',
default=False,
required=True)
really_funny = schema.Bool(title=u'Am I Really and Truly Funny?',
default=False,
required=True)
Si può dare uno sguardo al risultante modulo interfaces per osservare cosa questo file debba includere e come si debba presentare.
Come menzionato poc'anzi, l'esempio dimostra che nello schema stesso
non è disponibile alcuna proprietà di interfaccia utente. Tutto ciò che
è descritto riguardo lo schema e i suoi campi è ciò che è richiesto per
definire il tipo di dati che esso rappresenta.
Viste Formlib
Ora che è disponibile lo schema che descrive quali tipologie di
campi si hanno, è possibile usare lo stesso approccio utilizzato per costruire le viste per il contenuto.
Lo schema che è stato definito contiene una buona quantità di campi di tipo Bool.
Il widget predefinito per questa tipologia di campi mostra le opzioni
Vero/Falso. Per gli scopi di questo esempio, si ha la necessità di
mostrare Si/No invece che Vero/Falso e si dovrà modificare un apposito
widget.
def YesNoWidget(field, request, true=_('yes'), false=_('no')):
vocabulary = schemavocab.SimpleVocabulary.fromItems(((true, True),
(false, False)))
return form_browser.RadioWidget(field, vocabulary, request)
Quello che è stato creato qui è un tipo di costrutto che fornisce il
widget con le caratteristiche richieste, basato su un widget esistente:
il RadioWidget. RadioWidget fa riferimento ad un
vocabolario che ha come voci "yes" e "no". Quindi sarà possibile
definire i campi del form basati sul nuovo schema.
example_content_fields = form.FormFields(interfaces.IExampleContent)
example_content_fields['really_funny'].custom_widget = YesNoWidget
Come nell'esempio fatto nella seconda parte del tutorial, i campi del
form vengono generati usando form.FormFields(). Questa classe utilizza
un'interfaccia come argomento e genera i campi del form (l'associazione
al campo dello schema nell'interfaccia utente) che verranno usati per
le viste. La seconda riga configura il widget personalizzato.
Ora non resta che definire la vista predefinita e la form di modifica:
class ExampleContentView(formbase.DisplayForm):
form_fields = example_content_fields
def __init__(self, *args, **kwargs):
formbase.DisplayForm.__init__(self, *args, **kwargs)
# a hack to make the content tab work
self.template.getId = lambda: 'index.html'
class ExampleContentEditForm(formbase.EditForm):
form_fields = example_content_fields
def __init__(self, *args, **kwargs):
formbase.EditForm.__init__(self, *args, **kwargs)
# a hack to make the content tab work
self.template.getId = lambda: 'edit.html'
E' stata definita ExampleContentView come estensione di formbase.DisplayForm
e che fa in modo che formlib possa comprendere che questa è la vista
normale e così tutti i widget dovrebbero essere mostrati in modalità view. La form di modifica, ExampleContentEditForm è stata definita per estendere formbase.EditForm
così che formlib sia in grado di mostrare i widget in modalità di
modifica. Ma questa non è la sola cosa che formlib conosce con il form
di modifica. Come impostazione predefinita, formbase.EditForm definisce una unica action apply per questo form. Quando questo form è inviato attraverso la action apply, formlib sa che deve effettivamente aggiornare l'oggetto corrente con i valori immessi. Bisogna notare che un'azione apply andata a buon fine invocherà un IObjectModifiedEvent per salvare i dati immessi.
Certamente una volta che queste viste sono state definite,
necessitano di essere registrate come componente di Zope 3. Questa
operazione si compie attraverso configure.zcml
<browser:page
name="index.html"
for=".interfaces.IExampleContent"
class=".browser.ExampleContentView"
permission="zope2.View"
/>
<browser:page
name="edit.html"
for=".interfaces.IExampleContent"
class=".browser.ExampleContentEditForm"
permission="cmf.ModifyPortalContent"
/>
Questo frammento di zcml mostra che la vista predefinita ha il nome index.html, mentre la form di modifica edit.html.
E' possibile dare uno sguardo al modulo browser.py e al configure.zcml per osservarne i risultati.
Tipi
Ora che lo schema e le viste sono state tutte definite per questo
tipo, è tempo di costruire l'effettiva classe del tipo. Per una
migliore esercitazione, si definisce questa classe nel modulo content.
class FormlibExampleContent(atapi.BaseContent):
interface.implements(interfaces.IExampleContent)
title = fieldproperty.FieldProperty(interfaces.IExampleContent['title'])
description = fieldproperty.FieldProperty(interfaces.IExampleContent['description'])
funny = fieldproperty.FieldProperty(interfaces.IExampleContent['funny'])
really_funny = fieldproperty.FieldProperty(interfaces.IExampleContent['really_funny'])
La prima riga dopo la dichiarazione della classe fa in modo che essa implementi l'interfaccia IExampleContent che è stata definita in precedenza nel modulo interfaces (uno schema è quindi un'interfaccia con i campi nel zope.schema). Le rimanenti righe configurano le proprietà python per ognuno dei campi richiesti. Per esempio, la linea funny descrive un attributo funny che è modellato dopo il campo funny dello schema in IExampleContent.
Questo assicura che la validazione di base è impostata sul tipo stesso. Se si prova a richiamare direttamente il tipo myobj con myobj.funny = 'foo' si può osservare che si solleva un'eccezione perchè 'foo' non è un valore di tipo Bool. Il tipo Bool si aspetta vero o falso.
Questo è quanto per la classe stessa. Si può osservare il modulo content.py per vederne i risultati.
Catalogazione dei Tipi
Un aspetto che è stato trascurato è che poichè non si stanno più
usando i form auto-generati da Archetypes l'oggetto non può essere
catalogato. Nell'universo Zope 3 queste cose dovrebbero essere compiute
usando gli eventi. Poichè fomlib richiama un IObjectModifiedEvent quando avviene un salvataggio corretto, tutto ciò che è necessario fare è definire un gestore per questo evento.
def catalog_content(obj, event):
obj.reindexObject()
Il gestore stesso è estremamente semplice: esso prende come argomenti l'effettivo oggetto e l'evento (in questo caso l'istanza IObjectModifiedEvent). Sicomme si è in possesso dell'oggetto, a questo punto non resta che invocare la reindexObject().
Ovviamente è necessario aggangiarlo con l'architettura di Zope 3 e quindi bisogna andare a toccare il file configure.zcml.
<subscriber
for=".interfaces.IExampleContent
zope.app.event.interfaces.IObjectModifiedEvent"
handler=".content.catalog_content"
/>
In questo modo è possibile gestire ogni IObjectModifiedEvent che è stato invocato con un'istanza di tipo IExampleContent come oggetto. Nel caso in questione, FormlibExampleContent implementa l'interfaccia IExampleContent, quindi il gestore che è stato appena creato verrà chiamato quando l'oggetto è stato modificato.
L'ultima cosa che resta da fare è registrare il tipo con CMF/GenericSetup.
Installazione del Tipo con GenericSetup
Siccome la versione di Plone utilizzata per questo tutorial è la 2.5, è possibile utilizzare il tool GenericSetup per configurare il nuovo tipo nel sito Plone. Maggiori informazioni su GenericSetup e su Plone si possono trovare nell'eccellente tutorial di Rob Miller Understanding and Using GenericSetup in
Plone.
I passi basilari per configurare il Tipo attraverso GenericSetup sono:
- Creare una directory profilo con la struttura profiles/default/types all'interno della directory formlib.
- Costruire un nuovo file types.xml nella directory default.
- Creare un file chiamato FormlibExampleContent.xml nella directory types.
- Registrare un nuovo profilo che usa questi file attraverso GenericSetup nel file __init__.py.
L'effettiva costruzione di questi file va oltre gli scopi di questo tutoria, ma essi possono essere trovati nella directory base formlib.
Bisogna ricordarsi che per attivare questi Tipi in Plone 2.5 bisogna andare nel tool portal_setup, selezionare ploneexample.formlib sample content come profilo attivo del sito e quindi eseguire tutte le operazioni di importazione.
Selezionare il Profilo Attivo

Passi di Esportazione

Conclusioni
Lo schema Zope 3 è arrivato così in Plone. E grazie alla zope.formlib
sono state auto-generato le viste e i form da usare. Il widget e le
impostazioni dei campi da scegliere in Zope 3 sono minori che nel mondo
Archetypes, ma in Zope 3 lo sta rapidamente raggiungendo.
Il principale pezzo mancante (dalla prospettiva della formlib) è
l'utilizzo di una form di aggiunta auto-generata. Mentre è possibile
svilupparli, non è facile inserirli in Plone poichè in Plone è
necessario creare prima il contenuto per poi visualizzare i form.
Aggiunta di un Nuovo Oggetto ExampleContent

Form di Modifica

Vista Predefinita
