Zope 3 con i pacchetti comunitari (z3c.*)
Note: Return to tutorial view.
Introduzione
Negli ultimi mesi, non molto dopo che Philipp von Weitershausen avesse pubblicato la seconda versione di Web Component Development with Zope 3, Zope 3 ha subito una serie massiva di cambiamenti e ristrutturazioni.
L'applicazione un tempo nota come Zope 3 è stata spezzettata in centinaia di pezzi, dando vita a "Zope 3 il framework". Ogni componente di Zope è divenuto un pacchetto separato ditribuito via egg con alberi di dipendenze molto complessi.
Un altro grosso cambiamento per Zope negli ultimi mesi è stato la creazione di numerosi pacchetti mantenuti dalla comunità, che non sono considerati "zope core": i pacchetti z3c.*. Scritti principalmente da hacker Zope 2 ribelli alla ricerca di rendere Zope ancora più potente, i pacchetti z3c.* hanno rapidamente preso il centro della scena.
Sfortunatamente per il resto di noi, coloro che sapevano usare i pacchetti z3c.* erano troppo indaffarati, essendo incredibilmente produttivi e di successo, per poter scrivere nuovi libri, tutorial, o documentazione semplice da seguire.
Questo documento cerca di ridurre le preoccupazioni di coloro che ancora fanno le cose alla vecchia maniera fornendo una modesta introduzione a z3c.*.
Chi (Non) Dovrebbe Leggere Questo Tutorial
Questo tutorial è stato scritto principalmente per chi ha già un po' di dimestichezza con Zope 3.
Si assume che il lettore abbia un certo livello di comprensione dei paradigmi di base di Zope: l'architettura a componenti, configurazione ZCML, page template, e ovviamente Python.
Fortunatamente sono già disponibili degli ottimi libri che coprono tali argomenti. Se non lo avete ancora fatto, vi consiglio caldamente di dare uno sguardo al libro di Philipp von Weitershausen, Web Component Development with Zope 3, e di usarlo come supplemento a questo tutorial. Anche Baiju ; ha scritto un ottimo libro che mette a fuoco pro e contro dell'architettura a componenti disponibile online: http://www.muthukadan.net/docs/zca.html
Informazioni sul Documento
Autore
Paul Carduner
Versione
Revisione BZR
Progetto LP
Copyright
Feed Aggiornamenti
Traduzioni
Partiamo
- Nota
- si assume che l'ambiente di lavoro sia Ubuntu Gutsy o simile.
Preparariamo un Ambiente Virtuale
Prima di poter iniziare a lavorare sulla nostra applicazione, è sempre una buona idea preparare un ambiente python che sia isolato dal python di sistema nella vostra distribuzione linux (nel mio caso, Ubuntu Gutsy).
Creando un ambiente python "virtuale", possiamo facilmente evitare molti problemi associati al python di sistema, come pacchetti rotti, dipendenze conflitto, etc.
Un ambiente virtuale python può essere facilmente creato usando lo script virtualenv.py disponibile da http://svn.colorstudy.com/virtualenv/trunk/virtualenv.py. Per la documentazione relativa a quello che fa lo script, date un'occhiata alla pagina pypi http://pypi.python.org/pypi/virtualenv.
Scaricate questo script nella vostra directory home e create un nuovo ambiente chiamato sandbox:
$ wget http://svn.colorstudy.com/virtualenv/trunk/virtualenv.py ~/ $ python ~/virtualenv.py sandbox
In alternativa potete creare questo ambiente dovunque preferite, e chiamarlo come volete.
Questa cartella ora ha una directory bin con un nuovo eseguibile python e uno nuovo script easy_install. Dispone anche di uno script bash per collocare la vostra shell nel nuovo ambiente, in modo che non dovrete scrivere l'intero path per lanciare il python dell'ambiente. Lanciatelo così:
$ cd sandbox $ source bin/activate
Dopo di che potrete verificare che sta funzionando:
(sandbox)$ which python /home/pcardune/sandbox/bin/python
Da questo punto in poi, dovunque vedrete un comando shell da lanciare, si assume che abbiate l'ambiente virtuale attivato.
Uso di zopeproject
Risulta conveniente iniziare con uno scheletro di applicazione di base in modo da non doversi preoccupare di scrivere tutte le configuarazioni di base da zero. Fortunatamente Philipp von Weitershausen ha scritto un ottimo strumento chiamato zopeproject per farlo.
zopeproject può essere installato usando easy_install. Scrivete semplicemente:
$ easy_install zopeproject
Sucessivamente creiamo un nuovo progetto chiamato zcontact. zopeproject crea tutta la configurazione di base di cui avrete bisogno per essere in grado di avviare immediatamente un'applicazione zope vuota e loggarsi con un account di amministrazione. Vi sarà chiesto uno username e una password iniziali per l'account di amministrazione (che può essere cambiato in qualsiasi momento) insieme a una collocazione dove installare i componenti necessari:
$ zopeproject zcontact Enter user (Name of an initial administrator user): manager Enter passwd (Password for the initial administrator user): zcontact Enter eggs_dir (Location where zc.buildout will look for and place packages) ['/home/pcardune/buildout-eggs']: eggs
Questa fase potrebbe richiedere parecchi minuti poichè molto codice deve essere scaricato e in parte compilato. Mentre aspettate, vi consiglio di dare un'occhiata alla documentazione di zopeproject per averne un'idea migliore di cosa stia facendo.
Avviamo il Server
Finito questo, potrete saltare a bordo immediatamente e avviare la vostra applicazione nascente che sarà in esecuzione alla porta 8080 di default:
$ cd zcontact $ ./bin/paster serve deploy.ini Starting server in PID 23818. serving on http://127.0.0.1:8080
Dovreste aver ottenuto una schermata zope vuota che assomiglia a quella sotto:

Il file deploy.ini dato al comando ./bin/paster specifica le opzioni per eseguire il server, come l'impostazione delle porte. zopeproject genera anche un file debug.ini che include un filtro WSGI per la gestione degli errori. Se mai incapperete in errori, potrete usare le configurazioni nel debug.ini per ispezionare il codice, mentre è in esecuzione, direttamente nel vostro browser web!
Sistemiamo la Sicurezza
Per Tenere Semplici le Cose (Stupido) in questo tutorial, modificheremo i permessi generati da zopeproject in modo da non dovercene preoccupare dopo. Aggiungete le seguenti linee al file site.zcml nella directory root della vostra applicazione:
<role id="zope.Anonymous" title="Everybody" /> <grantAll role="zope.Anonymous" />
Questo offre agli utenti anonimi accesso completo senza restrizioni.
Creiamo una Semplice Interfaccia e Implementazione
Prima di poter saltare nel mondo dei pacchetti z3c.*, dovremo disporre di alcuni oggetti con cui voler giocare. Poichè si suppone che ZContact sia un gestore di contatti, iniziamo con un oggetto contact. Per Farla Semplice (Stupido), avremo solo due campi, un first name e un last name.
- Nota
- Tutto il codice sorgente si trova nella cartella zcontact/src/zcontact in modo che zcontact/src/zcontact/interfaces.py possa essere importato con un import zcontact.interfaces
L'interfaccia IContact
Apriamo interfaces.py e aggiungiamo quanto segue:
import zope.interface
import zope.schema
class IContact(zope.interface.Interface):
"""A simple contact."""
firstName = zope.schema.TextLine(
title=u"First Name",
required=True)
lastName = zope.schema.TextLine(
title=u"Last Name",
required=True)
Implementazione di Contact
Ora costruiamo la nostra veloce implementazione. Aprite contact.py e aggiungete quando segue:
import zope.interface from zope.schema.fieldproperty import FieldProperty import interfaces class Contact(object): """See ``zcontact.interfaces.IContact``.""" zope.interface.implements(interfaces.IContact) firstName = FieldProperty(interfaces.IContact['firstName']) lastName = FieldProperty(interfaces.IContact['lastName'])
Registrazione della Sicurezza per la classe Contact
Dobbiamo anche registrare la classe Contact in zcml e impostare i permessi per accedere agli attributi. Aggiungete semplicemente quanto segue al file src/zcontact/configure.zcml:
<class class="zcontact.contact.Contact">
<require
interface=".interfaces.IContact"
permission="zope.View" />
<require
set_schema=".interfaces.IContact"
permission="zope.ManageContent" />
</class>
Fatto questo, possiamo saltare nelle viste che usano componenti z3c.*.
Attenzione!
Non dimenticate, nel mondo reale scrivereste prima degli unit test per questo codice, anche solo per assicurarsi che la classe Contact veramente implementa l'interfaccia IContact.
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.*.
Skinning con z3c.layout, z3c.pagelet, e z3c.template
Dipendenze
Prima le cose importanti, dobbiamo includere la configurazione per le nuove dipendenze che avremo con lo skinning. Aprite src/zcontact/configure.zcml e aggiungete <include package="z3c.pagelet" file="meta.zcml" /> verso la cima con il resto dei meta include. Qusto ci darà la nuova direttiva zcml z3c:pagelet. In fondo potrete quindi aggiungere due ulteriori include:
<include package="z3c.pagelet" /> <include package="zope.contentprovider" />
Creiamo un Layout
Ora andiamo alla parte interessante. Possiamo creare quello che viene chiamato un layout, che è simile a una macro, ma molto più semplice. Invece di avere una macro complicata che ogni pagina deve usare (con use-macro e fill-slot), possiamo semplicemente creare un page template con un tag particolare impostato a parte per il contenuti delle pagine reali. Possiamo usare layout diversi per skin differenti, e perfino per interfacce degli oggetti differenti. Pronti! Aprite il file src/zcontact/layout.pt e fatelo assomigliare a qualcosa di simile a questo (sentitevi liberi di essere creativi):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>ZContact</title>
<style type="text/css">
<!--
* {
margin: 0;
padding: 0;
}
body {
padding: 1em;
}
#content{
border: 1px solid #999;
padding: 1em;
background: #eee;
}
-->
</style>
</head>
<body>
<h1>ZContact</h1>
<!-- THE MAGIC HAPPENS IN THE LINE BELOW -->
<div id="content"
tal:content="structure provider:pagelet">Page Content</div>
<i>ZContact Tutorial Application</i>
</body>
</html>
Notate in particolare la linea verso il fondo che dice:
<!-- THE MAGIC HAPPENS IN THE LINE BELOW -->
<div id="content"
tal:content="structure provider:pagelet">Page Content</div>
Come potete immaginare, qui avviene la magia. La parte provider:pagelet dice a zope di cercare un content provider chiamato pagelet, che attraverso z3c.pagelet finisce per essere il contenuto per la pagina che vogliamo ottenere, che sia un form o un altro page template. Quindi è piuttosto potente, ma ora dobbiamo registrare questo layout per la nostra skin in zcml. Aprite zcontact/skin.zcml e aggiungete la seguente direttiva, piuttosto auto esplicativa:
<z3c:layout
for="*"
layer="zcontact.layer.IZContactBrowserLayer"
template="layout.pt"
/>
Inoltre, non dimenticate di aggiungere il namespace xml z3c al tag configure con xmlns:z3c="http://namespaces.zope.org/z3c".
Usiamo i Layout con Pagelet (e Template)
Ora che abbiamo il nostro layout, dobbiamo leggermente modificare le nostre pagine esistenti in modo da fargli usare effettivamente il nuovo meccanismo di layout. Il primo passo consiste nell'aprire zcontact/browser/configure.zcml e aggiungere il namespace xml z3c al tag configure: xmlns:z3c="http://namespaces.zope.org/z3c". Ora, dovunque stat usando un tag page, sostituite il tag page con z3c:pagelet senza cambiare altro. Siamo al 90% del nostro lavoro. L'unico avvertimento con le pagelet che le rende veramente diverse dalle normali pagine è che non hanno nulla a che pare con i page template. La direttiva pagelet non accetta un attributo template e richiede un attributo class. Per la maggior parte delle nostre pagine, costituite solo da form auto-generate, questo non è un problema. Tuttavia per la pagina iniziale vogliamo il nostro template.
Iniziamo sostituendo l'ultima direttiva per la pagina iniziale con quanto segue:
<z3c:pagelet
name="index.html"
for="zope.app.folder.interfaces.IRootFolder"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
class=".contact.FrontPage"
/>
Notate che ho eliminato l'attributo template e aggiunto l'attributo class per la nostra classe FrontPage ancora da scrivere. Scriviamo questa classe in modo da completare questa nostra conversione. Aprite zcontact/browser/contact.py e in fondo aggiungete:
class FrontPage(BrowserPagelet):
"""Pagelet for the front page."""
mettendo in cima al file from z3c.pagelet.browser import BrowserPagelet.
A questo punto dovreste pensare "che è successo al nostro page template e questa classe sembra inutile". Malgrado non abbia nessuna reale sostanza, questa classe fa abbastanza poichè eredita da BrowserPagelet. La classe BrowserPagelet fornisce uno speciale metodo __call__ che usa un adattatore per agganciare il.. template.
Potreste dire "un adattatore per agagnciare il template? Significa che posiamo registrare template diversi per la stessa page(let)?" la risposta è un sonoro sì. Possiamo specificare il template per la pagelet nella sua classe (usando template = ViewPageTemplateFile('frontpage.pt')), o possiamo registrare un template da usare per una specifica skin tramite zcml. Lo faremo più tardi a fini dimostrativi.
tornate a zcontact/browser/configure.zcml e aggiungete la seguente direttiva proprio sotto la pagelet della pagina iniziale:
<z3c:template
template="frontpage.pt"
for=".contact.FrontPage"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Usiamo i Layout con dei Form
C'è ancora un punto rimasto prima che la nostra applicazione possa essere considerata bella e pronta. Il paccheto z3c.form è stato architettato in modo tale da poter essere usato senza tutta questa nuova questione di layout e pagelet (buona cosa). Fortunatamente il pacchetto z3c.formui fornisce supporto ai layout con il suo modulo form.
Per convertire le nostre form esistenti a usare il meccanismo del layout dobbiamo solo manipolare i nostri import in modo da usare from z3c.formui import form al posto di from z3c.form import form. Gli import che sono in cima a zcontact/browser/contact.py dovebbero ora apparire così:
from z3c.form import field, button from z3c.form.interfaces import DISPLAY_MODE from z3c.formui import form from z3c.pagelet.browser import BrowserPagelet from zope.traversing.browser.absoluteurl import absoluteURL from zope.traversing.api import getParent, getName from zcontact import interfaces from zcontact.contact import Contact
Questo conclude la nostra conversione di skin, quindi dovreste essere in grado di riavviare il web server e saltare a bordo di una bella skin (a meno che non abbiate risolutamente copiato il mio CSS). Ecco delle screenshot delle nostre nuove pagine:


Menu di Navigazione con z3c.menu e Viewlet
z3c.menu è un pacchetto molto minimale che fornisce alcune classi di aiuto utili a generare menu.
La vera potenza proviene da un pacchetto zope core, zope.viewlet. Questa è l'idea: invece di lavorare con i vecchi menu zope, piuttosto poco flessibili, vogliamo utilizzare dei viewlet per definire più facilmente come e quando mostrare voci di menu. Il nostro menu di navigazione sarà un viewlet manager, e ogni link nel menu sarà un viewlet. Nel caso non abbiate mai lavorato con i viewlet, proverò a spiegarvi di che si tratta.
Impostiamo un Viewlet Manager
Cos'è un viewlet manager? In un certo senso un viewlet manager vuole rappresentare una certa regione di una pagina web dove si vuol contenere un qualsiasi numero di pezzi di contenuto generati dinamicamente.
Un semplice esempio è la regione presente nella maggior parte dei blog in cui viene mostrata un'immagine del blogger, una breve descrizione del blog, la lista dei post recenti, un piccolo calendario e a volte un gruppo di tag. In termini Zope potremmo considerare ciascuno di questi pezzi di contenuto come un viewlet, tutti racolti insieme in un viewlet manager.
Arrivando al punto, creiamo un viewlet manager. Iniziate aprendo src/zcontact/skin.py e aggiungendo il seguente codice:
from zope.viewlet.interfaces import IViewletManager
from zope.viewlet.manager import WeightOrderedViewletManager
class INavigationMenu(IViewletManager):
"""Navigation Menu Viewlet Manager."""
class NavigationMenu(WeightOrderedViewletManager):
zope.interface.implements(INavigationMenu)
Quando generate delle viewlet, vengono registrate ad uno specifico viewlet manager in base ad una interfaccia. Così iniziamo creando la nostra interfaccia chiamata INavigationMenu proprio per il menu di navigazione, che eredita da IViewletManager (linee 4-5). Poi creiamo un'implementazione dell'interfaccia INavigationMenu (linee 7-8). La classe WeightOrderedViewletManager può ordinare i viewlet secondo un dato peso, che potrebbe tornare utile per un menu di navigazione.
Quindi vogliamo registrare la nostra istanza di ViewletManager in zcml, in modo da poter essere renderizzata da un page template. Aprite src/zcontact/skin.zcml e aggiungete la seguente registrazione:
<browser:viewletManager
name="INavigationMenu"
provides="zcontact.skin.INavigationMenu"
class="zcontact.skin.NavigationMenu"
layer="zcontact.layer.IZContactBrowserLayer"
permission="zope.Public"
/>
Dovrete aggiungere xmlns:browser="http://namespaces.zope.org/browser" al tag di configure per ottenere il namespace browser.
Ora disponiamo di un viewlet manager che rappresenta la regione dove finiranno i nostri link di navigazione. Ma dobbiamo ancora renderizzare il viewlet manager nella nostra skin modificando src/zcontact/layout.pt. Potete mettere il seguente frammento di HTML nel template in qualsiasi punto in cui volete far apparire il menu. Io ho scelto di metterlo proprio sotto l'intestazione "ZContact":
<div tal:content="structure provider:INavigationMenu">Navigation Menu</div>
Aggiungiamo i Viewlet
Ora che abbiamo impostato il nostro viewlet manager per il menu di navigation dobbiamo aggiungere dei viewlet per le voci dei menu. Tutto ciò che dobbiamo fare per i viewlet è predisporre alcune registrazioni zcml nello stesso punto in cui abbiamo registrato i pagelet a cui le nostre voci di menu punteranno. Quindi apriamo zcontact/browser/configure.zcml e aggiungiamo le seguenti due configurazioni zcml:
<viewlet
name="Add Contact"
viewURL="@@addContact.html"
for="*"
manager="zcontact.skin.INavigationMenu"
class="z3c.menu.simple.menu.GlobalMenuItem"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
weight="2"
/>
<viewlet
name="Contact List"
viewURL="@@index.html"
for="*"
manager="zcontact.skin.INavigationMenu"
class="z3c.menu.simple.menu.GlobalMenuItem"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
weight="1"
/>
L'attributo name specifica cosa dirà il link e il viewURL specifica l'url relativo dalla radice del sito alla vista in questione. Fatto ciò impostiamo l'interfaccia INavigationMenu che abbiamo creato nella precedente sezione come manager. Infine vediamo il nostro primo pezzetto di z3c.menu usando la classe GlobalMenuItem per il viewlet. Questa classe calcola quella che dovrebbe essere l'url di base e le attacca in coda l'attributo viewURL.
Fatto ciò potete riavviare il vostro server e dare un'occhiata ad un menù di navigazione lucido di nuovo che funziona perfettamente su ogni pagina della nostra applicazione.
Come sempre, ecco uno screenshot:

z3c.zrtresource e viewlet CSS
Un importante obiettivo da raggiungere nello sviluppare applicazioni web consiste nel permettere ai designer che non conoscono nulla di programmazione di modificare gli aspetti puramente visivi dell'applicazione. Questo include i fogli di stile. I grafici tipicamente sviluppano in un ambiente locale senza mai eseguire codice, quindi dobbiamo gestire in qualche modo la disparità che c'è tra l'accedere a risorse come i fogli di stile dal file system locale e da un'applicazione web in esecuzione.
I page template ci aiutano già molto; aprite layout.pt direttamente in un browser e vedrete un'esposizione molto semplice di come appare l'applicazione anche se non è in esecuzione. z3c.zrtresource ci aiuterà a farlo con i fogli di stile e altre risorse.
Usiamo z3c.zrtresource
Dipendenze
Il primo passo nell'uso di qualsiasi nuovo pacchetto z3c.* consiste nell'aggiungerlo come dipendenza alla nostra applicazione. Aprite setup.py, aggiungete "z3c.zrtresource" al parametro install_requires e rilanciate ./bin/buildout. z3c.zrtresource definisce una nuova direttiva zcml che vedremo fra un attimo, perciò dobbiamo prima includere il file meta.zcml del pacchetto nel nostro src/zcontact/configure.zcml con la linea:
<include package="z3c.zrtresource" file="meta.zcml" />
Usiamo la Direttiva zrt-resource
Ora possiamo andare avanti e rimuovere il CSS dal file layout.pt e incollarlo in un nuovo file src/zcontact/style.css. Al posto dei tag style in layout.pt aggiungiamo un tag link come questo:
<link rel="stylesheet" type="text/css" href="style.css" />
Questo è solo un segnaposto che permette al foglio di stile di essere acceduto dal file system. In un attimo aggiungeremo del codice per ottenere dinamicamente la path del foglio di stile.
Se avviate il server ora e visitate http://localhost:8080/++skin++ZContact/ lo stile risulterà rotto. Questo perchè http://localhost:8080/++skin++ZContact/style.css non corrisponde ad una risorsa esistente. Prima dobbiamo aggiungere il foglio di stile come una risorsa in zope. Aprite skin.zcml e aggiungete la direttiva zrt-resource in fondo:
<browser:zrt-resource
name="style.css"
file="style.css"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Ora possiamo riavviare il server e dovreste essere in grado di accedere al foglio di stile tramite http://localhost:8080/++skin++ZContact/++resource++style.css
Ora possiamo cambiare il link del nostro foglio di stile in layout.pt come di seguito:
<link rel="stylesheet" type="text/css" href="style.css"
tal:attributes="href string:++resource++style.css"/>
Questo dovrebbe soddisfare i bisogno tutti i grafici sul progetto, ma c'è qualcosa di più che potreste fare con le risorse zrt.
Sostituzione di Stringhe con zrt-resource
Fino a qui non abbiamo fatto nulla con le risorse zrt che non potreste fare con la normale direttiva resource che fa parte di zope core. Ma se andate alla pagina pypi di z3c.zrtresource troverete documentate alcune funzionalità di gestione template molto allettanti. Come esempio di ciò che si può fare (non che vogliate farlo necessariamente) andremo avanti disponendo del colore di sfondo per la nostra pagina generato dinamicamente dalla request.
Il nostro obiettivo consiste nell'ottenere dall'url http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow una pagina con uno sfondo LightGoldenRodYellow. Per prima cosa dobbiamo passare i dati della request al foglio di stile stesso modificando il link al foglio di stile in layout.pt come segue:
<link rel="stylesheet" type="text/css" href="style.css"
tal:attributes="href string:++resource++style.css?color=${request/color|string:white}"/>
In questo modo quando renderizzeremo la pagina il foglio di stile punterà a ++resource++style.css?color=LightGoldenRodYellow. Ora possiamo modificare style.css aggiungendo un commento in cima e modificando il body come segue:
/* zrt-replace: "requestcolor" tal"request/color|string:white" */
body {
padding: 1em;
background: requestcolor;
}
Ora provate ad accedere http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow e dovreste vedere un magnifico sfondo color "light golden rod yellow". Accedendo al codice tal, non ci sono limiti a quello che potreste fare in questo modo. Date unn'occhiata alla documentazione e agli esempi di z3c.zrtresource.
Viewlet per gli stili CSS
Abbiamo visto un esempio di come i viewlet siano utili con i menu di navigazione, ma ci sono molti altri modi di rendere la nostra applicazione più gestibile utilizzando i viewlet.
Una delle cose che vogliamo è un modo per includere il giusto CSS su ogni pagina includendo all'occorrenza CSS personalizzato per pagine molto specifiche.
Inoltre vogliamo facilmente includere fogli di stile CSS definiti in altri pacchetti, ad esempio z3c.form senza dover copiare file.
Ecco i viewlet, che permettono a pezzi di html generati dinamicamente di apparire su qualsiasi pagina secondo una grande quantità di parametri, inclusi il contesto, skin/layer, e qualsiasi altra regola vi sia gradita. Un paradigma comune in questi giorni è quello di registrare tutti i fogli di stile CSS all'interno di viewlet che possono essere gestiti con zcml. Proviamo ad approfondire tale modalità.
Creiamo il Viewlet Manager
Per prima cosa creiamo un nuovo viewlet manager per raccogliere i nostri viewlet CSS. Lo faremo nello stesso modo che abbiamo usato per il viewlet manager del menu di navigazione. Aprite src/zcontact/skin.py e aggiungete queste due linee:
class ICSS(IViewletManager):
"""CSS viewlet manager."""
Ora vogliamo registrare questo nuovo viewlet manager nel file src/zcontact/skin.zcml:
<browser:viewletManager
name="ICSS"
provides="zcontact.skin.ICSS"
class="zope.viewlet.manager.WeightOrderedViewletManager"
layer="zcontact.layer.IZContactBrowserLayer"
permission="zope.Public"
/>
Con un nuovo viewlet manager chiamato ICSS possiamo modificare il template principale del nostro layout per sostituire il tag link con un tal block che mostra i viewlet CSS rilevanti. Aprite src/zcontact/layout.pt e fate in modo che il tag link assomigli a questo:
<tal:block tal:content="structure provider:ICSS"> <link rel="stylesheet" type="text/css" href="style.css"/> </tal:block>
Una cosa da notare qui è che ancora lasciamo in giro il tag link originale. Quando la pagina viene generata in zope sarà sostituito dai nostri viewlet CSS, ma quando un grafico apre direttamente il file layout.pt, il foglio di stile sarà ancora caricato, anche se non dinamicamente.
Ora siamo pronti per aggiungere i viewlet dei CSS. Proprio come abbiamo fatto per i viewlet z3c.menu, esiste già una classe viewlet appositamente creata per i viewlet dei CSS, che li renderizza con tag di tipo link. Creiamo una tale viewlet aggiungendo due linee a src/zcontact/skin.py:
from zope.viewlet.viewlet import CSSViewlet
ZContactCSSViewlet = CSSViewlet('style.css')
Il parametro che passiamo alla classe CSSViewlet indica il nome della risorsa css non del file stesso. Poichè generalmente diamo lo stesso nome alla risorsa e al file si potrebbe generare confusione. Infine dobbiamo registrare il viewlet con dello zcml in src/zcontact/skin.zcml:
<browser:viewlet
name="style.css"
for="*"
manager="zcontact.skin.ICSS"
class="zcontact.skin.ZContactCSSViewlet"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Note sui Layout Personalizzati
Normalmente a questo punto vremmo finito il nostro lavoro e potreste riavviare il server e vedere il foglio di stile ancora caricato. Sfortunatamente c'è una nota da fare derivante da un precedente passo del tutorial relativa all'uso di un layer che non eredita dal layer di default.
Quando la classe CSSViewlet renderizza la viewlet in html, l'url usato per accedere alla risorsa (style.css) non è /++resource++style.css ma piuttosto il più semplice /@@/style.css. Sfortunatamente @@ è in verità una vista registrata sul layer di default e perciò non esiste nel nostro IZContactBrowserLayer, che significa che la risorsa non sarà accessibile dall'interno della nostra skin con quella url.
Esistono alcune soluzioni a questo problema, ma la più semplice è di registrare nuovamente la vista @@ nella nostra skin. La registrazione zcml per questa vista dovrebbe essere quella che segue (in src/zcontact/skin.zcml):
<browser:page
name=""
for="zope.app.component.interfaces.ISite"
class="zope.app.publisher.browser.resources.Resources"
permission="zope.Public"
allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Nel caso siate curiosi la vista originale viene definita in zope/app/publisher/browser/configure.zcml.
Con questo ultimo ritocco potete riavviare il server per vedere esattamente la stessa cosa che avevate prima, ma ora costruita nel modo molto più potente e lessibile del viewlet.
Personalizziamo le Viewlet
- Nota
- La prossima sezione non è per i deboli di cuore.
Cosa ne dite di tutte le cose meravigliose che abbiamo fatto con z3c.zrtresource? Ora che il viewlet renderizza il tag link non includerà la variabile colore della request per impostare il colore di sfondo. Per tornare ad aggiungere questa funzionalità dovremo personalizzare il viewlet CSS con la nostra classe viewlet.
Prima vi mostrerò il codice, poi la spiegazione. In src/zcontact/skin.py ho aggiunto il seguente codice:
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.viewlet.viewlet import CSSResourceViewletBase
from zope.viewlet.viewlet import ViewletBase
class RequestCSSResourceViewlet(CSSResourceViewletBase, ViewletBase):
index = ViewPageTemplateFile(os.path.join(os.path.dirname(zope.viewlet.viewlet.__file__), 'css_viewlet.pt'))
requestKey = ''
default = ''
path = ''
def getURL(self):
self._path = self.path
baseURL = super(RequestCSSResourceViewlet, self).getURL()
fullURL = baseURL + '?%s=%s' % (self.requestKey,
self.request.get(self.requestKey, self.default))
return fullURL
Mentre leggete il codice precedente, suggerisco caldamente di dare uno sguardo al codice delle classi di base CSSResourceViewletBase e ViewletBase. Ci sono tre parti delle classi di base su cui stiamo facendo override, l'attributo index che punta ad un page template per renderizzare il tag link, l'attributo path che restituisce il nome dela risorsa che stiamo cercando, e il metodo getURL che effettivamente renderizza l'url che vogliamo.
Per il page template di index vogliamo riusare il template esistente dal pacchetto zope.viewlet.viewlet chiamato css_viewlet.pt. Il metodo getURL esegue fa esattamente la stessa cosa del tag tal che avevamo in precedenza: concatena i parametri della request nell'url.
Abbiamo anche definito questa classe in modo generico in modo che possa essere usata per parametri diversi dal solo color. Una bella cosa dei viewlet che vedremo in un attimo è che possiamo specificare qualsiasi attributo di classe mediante zcml, semplicemente aggiungendo attributi ulteriori dello stesso nome al tag browser:viewlet. In questo caso passeremo tre attributi aggiuntivi: requestKey, default, e path. Ora aprite src/zcontact/skin.zcml e modificate il tag viewlet di style.css come segue:
<browser:viewlet
name="style.css"
for="*"
manager="zcontact.skin.ICSS"
class="zcontact.skin.RequestCSSResourceViewlet"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
requestKey="color"
default="white"
path="style.css"
/>
Come potete vedere abbiamo solo cambiato l'attributo class e aggiunto gli altri attributi di cui abbiamo bisogno per la viewlet. Questo conclude il nostro primo approccio alla personalizzazione delle viewlet CSS e dovreste ora essere in grado di riavviare il server e visitare http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow dove dovreste vedere uno sfondo color Light Golden Rod Yellow!