Form con z3c.form
Aggiungiamo z3c.form e z3c.formui come Dipendenze
Prima di poter usare z3c.form, dobbiamo aggiungerlo come dipendenza alla nostra applicazione.
Per aggiungere z3c.form come dipendenza, aprite setup.py nella directory radice della vostra applicazione e aggiungete 'z3c.form', al parametro install_requires (dovrebbe essere intorno alla linea 25). A questo punto dovreste anche aggiungere z3c.formui e z3c.layer come dipendenze, cosa richiesta per renderizzare la form con un layout piacevole.
Successivamente vorrete includere la configurazione zcml per z3c.form e z3c.formui nel file configure.zcml collocato in zcontact/src/configure.zcml. z3c.form fa uso di numerosi altri componenti z3c.* che definiscono nuove direttive zcml. Per usare queste direttive, dovete includere i meta file all'inizio di configure.zcml prima di qualsiasi altro include:
<include package="zope.viewlet" file="meta.zcml" /> <include package="z3c.form" file="meta.zcml" /> <include package="z3c.macro" file="meta.zcml" /> <include package="z3c.template" file="meta.zcml" />
Poi vorrete includere i veri pacchetti z3c.form e z3c.formui alla fine del file configure.zcml:
<include package="z3c.form" /> <include package="z3c.formui" />
Ora tutto quel che dobbiamo fare è rieseguire il processo di buildout in modo che le nuove egg per z3c.form e z3c.formui vengano scaricate e rese disponibili. Eseguite semplicemente:
$ ./bin/buildout -N
L'opzione -N evita di scaricare egg che sono state già scaricate (molto consigliato).
Preparando la vostra Applicazione a Lavorare con z3c.form
Sfortunatamente non siamo ancora pronti ad usare z3c.form. Poichè i pacchetti z3c hanno creato un modo leggermente diverso di costruire applicazioni, ci sono alcuni passi ulteriori da compiere per farli funzionare. Uno di questi passi consiste nel generare i propri layer e skin.
Sfortunatamente molte direttive zcml non vi forzano a dichiarare esplicitamente per quale layer volete registrare la pagina o la vista. Quando non specificate il layer, vengono registrate con un layer di default. Finisce che molti paccheti (incluso Rotterdam) registrano tutte le loro viste sul layer di default, rendendolo sovraccarico di cose che non vogliamo. Per evitare tale sovraffollamento, i pacchetti z3c.* registrano tutte le loro viste a un layer specifico al pacchetto in questione.
Per usare il pacchetto z3c.* propriamente, dovete estendere questi layer con il vostro layer. Ora creeremo un layer che ci permette di usare viste del pacchetto z3c.form.
Creiamo un Layer
Create un nuovo file, src/zcontact/layer.py e aggiungete il seguente codice:
from z3c.form.interfaces import IFormLayer
from z3c.layer.pagelet import IPageletBrowserLayer
class IZContactBrowserLayer(IFormLayer, IPageletBrowserLayer):
"""ZContact browser layer with form support."""
IFormLayer dispone di viste per tutti i widget usati nelle form generate e IPageletBrowserLayer fornisce alcuni errori e altre utili viste utility.
Creiamo una Skin
Per accedere a questo layer da un browser dobbiamo creare una skin. Perciò create un altro file, src/zcontact/skin.py e aggiungete il seguente codice:
import z3c.formui.interfaces
from zcontact import layer
class IZContactBrowserSkin(z3c.formui.interfaces.IDivFormLayer,
layer.IZContactBrowserLayer):
"""The ZContact browser skin using the div-based layout."""
Si noti che la nostra skin verrà ereditata dal layer IDivFormLayer, definito nel pacchetto z3c.formui. Quando le form vengono renderizzate, i campi appariranno in tag <div> invece che in una tabella. Esiste un altro layer per avere un layout basato su una tabella. Grazie all'architettura a componenti, è ugualmente possibile scrivere il layer per il proprio form layout, ma non lo faremo qui.
Ora dobbiamo registrare la skin in zcml con un nuovo file, src/zcontact/skin.zcml. Renderemo la nostra skin accessibile da http://localhost:8080/++skin++ZContact/ che dovrebbe apparire in questo modo:
<configure xmlns="http://namespaces.zope.org/zope">
<interface
interface=".skin.IZContactBrowserSkin"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="ZContact"
/>
</configure>
Non dimenticate di includere questo nuovo file zcml in zcontact/configure.zcml con la linea <include package="zcontact" file="skin.zcml" />.
Creiamo una Form di Generazione
Cominceremo generando un nuovo modulo, zcontact.browser, aggiungendo una directory browser alla directory src/zcontact/ con un file __init__.py vuoto al suo interno. Ora possiamo creare e aprire un nuovo file, zcontact/browser/contact.py dove definiremo tutte le form.
Iniziate aggiungendo il codice seguente al file browser/contact.py:
from z3c.form import form, field
from zcontact import interfaces
class ContactAddForm(form.AddForm):
"""A simple add form for contacts."""
fields = field.Fields(interfaces.IContact)
La classe form.AddForm da cui ereditiamo definisce appunto alcuni metodi per creare e aggiungere il nostro nuovo oggetto, su cui faremo override più tardi, e alcuni pulsanti tipici di una form di generazione.
Di seguito aggiungeremo una pagine che usa questa classe per mostrare la form. Aprite zcontact/browser/configure.zcml e aggiungete quanto segue:
<configure xmlns="http://namespaces.zope.org/browser">
<page
name="addContact.html"
for="zope.app.folder.interfaces.IFolder"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
class=".contact.ContactAddForm"
/>
</configure>
Registriamo la pagina con il nome addContact.html e per l'interfaccia IFolder. Poichè la cartella radice di ogni nuova istanza zope implementa IFolder, dovremmo essere in grado di accedere questa pagina da http://localhost:8080/++skin++ZContact/@@addContact.html.
Non dimenticate di includere il pacchetto browser in zcontact/configure.zcml aggiungendo la linea <include package=".browser" /> in coda al file.
Ora dovremmo essere pronti all'azione, quindi riavviate il server esegendo ./bin/paster serve deploy.ini (or debug.ini if you want) e visitate http://localhost:8080/++skin++ZContact/@@addContact.html. Dovreste visualizzare una semplicissima form come questa:

Completiamo il Form di Generazione
Se avete provato a usare il form di generazione, e avete premuto il pulsante add, dovreste aver ottenuto un errore di NotImplemented. Se avete deciso di eseguire la configurazione di debug.ini invece della deploy.ini con paster, allora avete probabilmente ottenuto una bella schermata come questa:

da questa scermata potete espandere qualsiasi linea nel traceback e inserire codice python per debuggare il problema. La prima volta che l'ho visto sono rimasto molto colpito.
Per risolvere il problema dobbiamo implementare tre metodi per la classe ContactAddForm: create, add e nextURL. Ho deciso di implementarli come segue per farla breve, ma sentitevi liberi di essere creativi:
from z3c.form import form, field
from zcontact import interfaces
from zcontact.contact import Contact
class ContactAddForm(form.AddForm):
"""A simple add form for contacts."""
fields = field.Fields(interfaces.IContact)
def create(self, data):
contact = Contact()
form.applyChanges(self, contact, data)
return contact
def add(self, contact):
self._name = "%s-%s" % (contact.lastName.lower(), contact.firstName.lower())
self.context[self._name] = contact
def nextURL(self):
return '/'
Nel metodo create abbiamo usato la funzione form.applyChanges per impostare i valori degli attributi firstName e lastName del nuovo contatto. I dati passati al metodo create sono un mapping tra i nomi dei campi e i dati inseriti, già convertiti nei tipi di dato python corretti. Per esempio avremmo potuto scrivere contact.firstName = data['firstName'].
Ho impostato il metodo nextURL per restituire una path cablata che dovrebbe portarci indietro alla skin Rotterdam di default dove potrete vedere il nuovo contatto creato nella vista contenuti. Facciamo così perchè non abbiamo ancora scritto le nostre viste contenuti per il nostro layer/skin e dobbiamo tornare indietro a Rotterdam.
Form di Presentazione e Modifica
Le form di presentazione e modifica sono anche più facili di quella di generazione poichè non dovete implementare metodi ulteriori. Iniziamo creando la form di presentazione.
Creiamo la Form di Presentazione
Per creare la form di presentazione ci servirà una nuova classe che erediti da form.Form. Per fare in modo che i widget mostrino del testo piuttosto che input di inserimento dobbiamo impostare la modalità del form a DISPLAY_MODE, che è una costante che può essere importata da z3c.form.interfaces. Aprite zcontact/browser/contact.py e aggiungete il seguente codice:
class ContactDisplayForm(form.Form):
"""A simple display form for contacts."""
fields = field.Fields(interfaces.IContact)
mode = DISPLAY_MODE
e non dimenticate di regitrare la nuova form in configure.zcml con:
<page
name="index.html"
for="..interfaces.IContact"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
class=".contact.ContactDisplayForm"
/>
Ora che abbiamo una form di presentazione, possiamo modificare il metodo nextURL di ContactAddForm per farlo puntare al contatto appena creato. Dovrebbe presentarsi così:
def nextURL(self):
return absoluteURL(self.context[self._name], self.request)
Non dimenticate di includere la linea from zope.traversing.browser.absoluteurl import absoluteURL in cima al file. Se tutto è andato liscio dovreste essere in grado di riavviare il server e aggiungere un nuovo contatto da http://localhost:8080/++skin++ZContact/@@addContact.html per poi essere riportati alla form di presentazione. Sarà qualcosa di simile a questo:

Aggiungiamo pulsanti a una form
Ora aggiungiamo due pulsanti, uno per modificare il contatto e uno per eliminarlo. Iniziamo aggiungendo in dima a contact.py from z3c.form import button. Quando un utente preme su un pulsante, viene efettuato il submit della form alla url specificata dall'attributo action della form. Di default l'azione è impostata all'url della form stessa, quindi quando premiamo sul pulsante di una form, questa viene ricaricata. Quando la form viene processata, controlla quale pulsante sia stato premuto e invoca gli handler appropriati che sono definiti come metodi della classe ContactDisplayForm.
Con z3c.form possiamo definire un pulsante e un handler allo stesso tempo usando un decoratore. Per il pulsande di elimazione vorremo eliminare il contatto e rimandare l'utente alla form di generazione. Aggiungete il seguente codice alla classe ContactDisplayForm:
@button.buttonAndHandler(u'Delete', name='delete')
def handleDelete(self, action):
name = getName(self.context)
parent = getParent(self.context)
del parent[name]
nextURL = absoluteURL(parent, self.request)+'/@@addContact.html'
self.request.response.redirect(nextURL)
Assicuratevi di aggiungere i seguenti import in cima al file:
from z3c.form import form, field, button from z3c.form.interfaces import DISPLAY_MODE from zope.traversing.browser.absoluteurl import absoluteURL from zope.traversing.api import getParent, getName
Ora creiamo un pulsante di modifica. Non abbiamo ancora un form di modifica, perciò il pulsante di edit ci porterà ad una pagina che non esiste. Per ora mettiamoci giusto un segnaposto. Aggiungete quanto segue alla classe ContactDisplayForm:
@button.buttonAndHandler(u'Edit', name="edit")
def handleEdit(self, action):
nextURL = absoluteURL(self.context, self.request) + '/@@editContact.html'
self.request.response.redirect(nextURL)
Ora potete riavviare il server e provare i pulsanti. La vostra form dovrebbe apparire ora in questo modo:

Creiamo una Form di Modifica
Fino a qui dovreste essere quasi dei professionisti per quanto riguarda la questione delle form autogenerate. Il nostro passo finale consiste nel creare una form di modifica, che è la più semplice di tutte. Ecco il codice da aggiungere:
class ContactEditForm(form.EditForm):
"""A simple edit form for contacts."""
fields = field.Fields(interfaces.IContact)
insieme alla configurazione zcml necessaria:
<page
name="editContact.html"
for="..interfaces.IContact"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
class=".contact.ContactEditForm"
/>
Andiamo avanti e proviamo la form di modifica premendo il pulsante Edit dalla form di presentazione. Noterete che disponiamo già di un pulsante apply per confermare le modifiche. Dopo aver operato le modifiche, ci viene presentato un messaggio di stato che indica successo o fallimento. A questo punto vogliamo un modo per poter tornare indietro alla form di generazione, quindi aggiungiamo un pulsante 'Done' alla classe ContactEditForm con il codice:
@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
self.request.response.redirect(absoluteURL(self.context, self.request))
Ma aspettate! non appena creiamo il nostro pulsante, facciamo override sui pulsanti dichiarati nella classe form.EditForm. Per evitarlo dobbiamo estendere la classe form.EditForm mettendo form.extends(form.EditForm) giusto dopo la dichiarazione di classe ContactEditForm. Ora dovreste avere una form di modifica che appare così:

Smussiamo l'Applicazione con una Pagina Iniziale
Prima di saltare nello skinning, aggiungiamo un'ultima pagina per cercare di addolcire la nostra applicazione. Questa pagina sarà la pagina centrale della nostra applicazione e fornirà un link alla form di generazione contatti, e un link per ciascuno dei contatti esistenti. Tutto ciò può essere fatto con un semplice in zcontact/browser/frontpage.pt:
<h3>Welcome to ZContact</h3>
<p>Please tell me what you would like to do:</p>
<ul>
<li><a href="@@addContact.html">Add a Contact</a></li>
<li>Look at contacts:
<ul>
<li tal:repeat="contact context/values">
<a tal:attributes="href contact/@@absolute_url"
tal:content="string:${contact/lastName}, ${contact/firstName}">Last, First</a>
</li>
</ul>
</li>
</ul>
Per farlo comparire come pagina iniziale della nostra applicazione registreremo la pagina in zcml per l'interfaccia IRootFolder. Aggiungete quanto segue a zcontact/browser/configure.zcml:
<page
name="index.html"
for="zope.app.folder.interfaces.IRootFolder"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
template="frontpage.pt"
/>
Riavviate il vostro server e controllate accedendo a http://localhost:8080/++skin++ZContact/ e dovreste avere qualcosa che assomiglia a questo:

Con poche form in posizione e un'applicazione che funziona decentemente, possiamo iniziare a dare un'occhiata agli altri pacchetti z3c.*.