Personal tools
You are here: Home Aiuti ed Info Manuale di Plone 2 6. Introduzione al Plone Templating avanzato e allo Scripting
To change the maximal image width select one of the following:

6. Introduzione al Plone Templating avanzato e allo Scripting

Document Actions

Capitolo 6: Introduzione a modellazione e scripting avanzato in Plone

Nel capitolo precedente abbiamo trattato come funziona il sistema dei modelli di pagina Zope Page Template. Per comprendere i modelli di pagina, nel Capitolo 5 abbiamo trattato anche la gerarchia degli oggetti, l'acquisizione, e il Template Attribute Language Espression Syntax (TALES). Usando il codice del capitolo precedente, siamo ora in grado di generare pagine web dinamiche. In quel capitolo abbiamo mostrato anche un modello di pagina di esempio che mette insieme il codice, trattato la costruzione dei fondamenti del sistema di modellazione di Plone e fornite le informazioni chiave necessarie per usarlo.

Ora è tempo di muoversi all'interno delle caratteristiche più avanzate dei modelli di pagina e più genericamente della modellazione in Plone. Presentiamo, prima, il Macro Expansion Template Attribute Language (METAL) ed lo spazio dei nomi I18N per l'internazionalizzazione. Come lo spazio dei nomi TAL, anche questi offrono funzionalità allo sviluppatore del sito. Per coloro che muoiono dalla voglia di sapere precisamente come sia assemblata una pagina Plone, la sezione Guardare dentro Plone usando METAL offre molte risposte.

Fino ad ora abbiamo mostrato come sia possibile usare semplici espressioni Python nei modelli di pagina. Ovviamente, talvolta un'espressione Python di una-riga non è sufficiente. Così nella sezione Scrivere codice Python per Plone, mostreremo come portare Python al successivo livello e come aumentare la potenza del nostro codice Python.

Infine, tratteremo un esempio comune, mostrando come mettere insieme un modulo in Plone. Questo esempio utilizza i concetti imparati nei capitoli precedenti, integrandoli tutti assieme mostrando precisamente come Plone si occupa dei moduli.

Capire la modellazione avanzata in Plone

Uno degli aspetti belli dei modelli di pagina è che funzioni diverse sono chiaramente separate in spazi di nomi diversi. Nel capitolo precedente, abbiamo visto lo spazio dei nomi TAL. Quello non è l'unico spazio dei nomi disponibili nei modelli di pagina ; altri due spazi dei nomi sono fondamentali in Plone.

Il primo è METAL. Come il nome, abbastanza lungo, suggerisce, è simile a TAL in quanto è un linguaggio di attributi e lo si inserisce negli attributi degli elementi. Comunque, il suo scopo primario è far si che sia possibile riusare pezzi di codice provenienti da altri modelli di pagina, reso possibile con l'utilizzo degli slot e delle macro.

Il secondo è I18N, che permette di tradurre il contenuto dei modelli di pagina. Questo è usato in Plone per localizzare l'interfaccia in più di 30 lingue e per molti utenti è una delle caratteristiche fondamentali di Plone. Come vedremo, la possibilità di localizzare testo interessa tutti gli utenti, anche quelli che costruiscono un sito monolingua. Inizieremo con METAL.

Guardare dentro Plone usando METAL

Finora abbiamo visto come usare TAL per creare dinamicamente parti di pagine. Questo in realtà non permette di fare modellazione molto complessa. Non c'è un vero meccanismo per posizionare un'intestazione standard in alto a ciascuna pagina, se non usando un etichetta TAL. METAL è un metodo per consentire un preprocessing dei modelli e di offrire alcune funzioni più potenti di TAL. Ogni funzione METAL inizia con il prefisso metal:.

metal:define-macro

Il comando metal:define-macro permette di definire un elemento da citare in un altro modello. Il nome della parte referenziata è il nome della macro. Ciò che segue è un esempio che definisce boxA come una parte che si desidera usare altrove:

<div metal:define-macro="boxA">
    ...
</div>

Quell'elemento div è ora una macro che può essere citata in altri modelli. La macro si riferisce solamente alla parte della pagina citata dall'elemento che, in questo caso, è il marcatore div. È quindi piuttosto comune inserire svariate di queste definizioni di macro in una singola pagina, rispettando il formato Hypertext Markup Language (HTML), come nel seguente esempio:

<html xmlns:tal="http://xml.zope.org/namespaces/tal"
    xmlns:metal="http://xml.zope.org/namespaces/metal"
    i18n:domain="plone">
    <body>
        <div metal:define-macro="boxA">
            ...
        </div>
        <div metal:define-macro="boxB">
            ...
        </div>
    </body>
</html>

Essendo uno dei obiettivi primari dei modelli di pagine, questa pagina contiene codice HTML valido che può essere modificato da un designer. Quando la macro è chiamata, il codice HTML esterno al marcatore div verrà scartato.

metal:use-macro

Il comando metal:use-macro usa una macro che è stata definita con define-macro. Quando un modello definisce una macro usando il comando define-macro, essa è accessibile agli altri modelli attraverso una proprietà macros. Per esempio, se si vuole estrarre la macro portlet del modello portlet_login, si può fare come segue:

<div metal:use-macro="context/portlet_login/macros/portlet">
    Qui verrà inserito lo slot informativo
</div>

Questo recupererà la macro e ne inserirà il risultato al proprio posto. Come mostrato, il comando use-macro prende un'espressione path che punta al modello e poi alla macro specificata nel modello stesso.

Esempio: Usare le macro use-macro e define-macro

Come esempio, quello che segue è un modello chiamato time_template. Questo modello mostra la data e l'ora corrente sul server Plone. Questa è una funzione veramente utile e che per comodità di riutilizzo può essere inserita in una macro. Questo è il modello di pagina dell'esempio che contiene la define-macro:

<html>
    <body>
        <div metal:define-macro="time">
            <div tal:content="context/ZopeTime">
                 Data e ora
            </div>
        </div>
    </body>
</html>

Se il nostro modello è stato chiamato time_template, allora possiamo citare questa macro in un altro modello. Possiamo ora referenziare questa macro in diversi modelli. Questa è un modello di esempio:

<html>
    <body>
    <div metal:use-macro="context/time_template/macros/time">
      Se c'è un messaggio qui viene inserita la macro.
    </div>
    </body>
</html>

Quando questo modello è restituito, l'HTML prodotto da Plone avrà un come questo:

<html>
    <body>
    <div>
       <div>2004/04/15 17:18:18.312 GMT-7</div>
    </div>
    </body>
</html>
metal:define-slot

Uno slot è una sezione di una macro che l'autore di un modello si aspetta venga sostituita da un altro modello. Si può pensare come ad un buco nel nostro modello di pagina dove, ci aspettiamo, qualche altra cosa lo riempia. Tutti i comandi define-slot devono essere contenuti all'interno di una define-macro. Per esempio:

<div metal:define-macro="master">
  <div metal:define-slot="main">
  ...
  </div>
</div>
metal:fill-slot

Questo riempie uno slot che è stato definito con il comando define-slot. Una macro fill-slot deve essere definita con il comando use-macro. Quando la parte define-macro è chiamata, la macro cercherà di riempire tutti gli slot definiti con gli appropriati fill-slot. Ecco un esempio di fill-slot:

<div metal:use-macro="master">
  <div metal:fill-slot="main">
    Qui verrà inserito lo slot main
  </div>
</div>
Esempio: usare macro e slot

Ritornando all'esempio precedente, ora lo miglioreremo un pochino. Se vogliamo mettere un messaggio personalizzato che preceda l'ora, allora aggiungeremo uno slot all'inizio del time_template, dentro la define-macro. Lo slot è chiamato time ed è il seguente:

<html>
    <body>
        <div metal:define-macro="time">
            <div metal:define-slot="msg">Time slot</div>
            <div tal:content="context/ZopeTime">
                 Data e ora
            </div>
        </div>
    </body>
</html>

Ora, nel modello di pagina chiamante, possiamo richiamare la fill-slot:

<html>
    <body>
    <div metal:use-macro="context/time_template/macros/time">
      <div metal:fill-slot="msg">L'istante è:</div>
      Se c'è un messaggio qui viene inserita la macro.
    </div>
    </body>
</html>

Il risultato finale è vedere la time-slot rimpiazzata come segue:

<html>
    <body>
    <div>
       <div>L'istante è:</div>
       <div>2004/04/15 17:18:18.312 GMT-7</div>
    </div>
    </body>
</html>
Come Plone usa macro e slot

Le macro e gli slot sono simili dal momento che entrambi estraggono contenuti da altri modelli ed inseriscono contenuti, ma lo fanno in maniera differente. La differenza è evidente nel modo in cui vengono usati. Le macro sono elementi di un modello che vengono chiamate esplicitamente, invece gli slot sono buchi in un modello che aspettano che di essere riempiti da altri modelli. Per esempio, nel caso dei portlet Plone come il calendario, la navigazione e così via sono macro che vengono esplicitamente chiamate.

Infatti, se nella Zope Management Interface (ZMI) guardiamo il file cliccando su portal_skin, poi su plone_template ed infine cliccando main_template, vedremo che l'intera pagina consiste di macro e di slot. A questo punto, probabilmente c'è un po' di confusione, ma quando questo viene chiamato, esegue una serie di macro e mette insieme tutto. Ciò permette all'utente di modificare facilmente qualsiasi parte di un sito Plone sovrascrivendo tale macro, come vedremo nel prossimo capitolo. Per esempio:

...
<div metal:use-macro="here/global_siteactions/macros/site_actions">
    Site-wide actions (Contact, Sitemap, Help, Style Switcher etc)
</div>
 
<div metal:use-macro="here/global_searchbox/macros/quick_search">
    The quicksearch box, normally placed at the top right
</div>
...

Continuando la lettura di main_template, si incontrano alcune definizioni di slot. Brevemente ricorderemo come viene realizzata una pagina di Plone. Quando un oggetto è mostrato, viene visualizzato un modello di quella vista per quel contenuto. Quando si vede un'immagine, è mostrato il modello image_view, e quel modello controlla come l'immagine viene mostrata. Per far questo, il modello riempe lo slot main. Se guardiamo nel modello image_view, vedremo che contiene il seguente codice:

<div metal:fill-slot="main">
...
</div>

Se ritorniamo di nuovo al main_template, vedremo che contiene una definizione define-slot per lo slot main:

<metal:bodytext metal:define-slot="main" tal:content="nothing">
    Page body text
</metal:bodytext>

Ciascun tipo di contenuto possiede un diverso modello, e ciascun modello definisce come usare differentemente lo slot main. Cosicchè, ciascun tipo di contenuto assume un aspetto personale e particolare definito da un modello. Solo un elemento è scomparso dall'equazione. In qualche modo quando viene chiamato image_view, il modello sa di dover usare main_template. Nel modello image_view, viene usata la macro proveniente da main_template. Questo è definito nel seguente HTML:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"
    lang="en-US"
    metal:use-macro="here/main_template/macros/master"
    i18n:domain="plone">

In questo caso, il main_template ha lo slot main riempito dallo slot definito come main nel modello che viene reso. Ciò che segue è la cronologia di come viene costruita la pagina quando si guarda un'immagine:

  1. image_view
  2. main_template
  3. image_view
  4. main_template
  5. define-slot="main"
  6. fill-slot
  7. image_view
  8. fill-slot
  9. main_template
  10. main_template

Questo permette a Plone di essere flessibile in termini di come ciascuna pagina è definita. Per esempio, main_template definisce più di uno slot; c'è anche un slot per inserire codice Cascading Style Sheets (CSS):

<metal:cssslot fill-slot="css_slot">
    <metal:cssslot define-slot="css_slot" />
</metal:cssslot>

Se una vista ha bisogno di un proprio insieme di istruzioni CSS, potremo definire questo slot nella vista, e verrà riempito quando reso. Alcune delle macro in main_template definiscono a loro volta degli slot che vengono riempiti dallo stesso main_template in modo tale che se necessario possano essere ridefiniti. Comunque, questa è una tecnica avanzata che potrà essere usata solo una volta che ci saremo impadroniti dei fondamentali.

Introduzione all'internazionalizzazione

Plone sta sforzandosi di mantenere sempre un gran numero di traduzioni di alta qualità. Il fatto che Plone offra un'interfaccia utente accessibile in più di 30 lingue è un punto fondamentale per la sua commercializzazione. Questo vuol dire anche che I18N [1] è una caratteristica basilare dei modelli. Il namespace I18N è uno spazio dei nomi come TAL o METAL che possiedono specifiche etichette.

Questa sezione mostra ciò di cui gli utenti hanno bisogno di conoscere circa i modelli. In un modello potete aggiungere un marcatore i18n ad un elemento per permettere la traduzione di un attributo o del suo contenuto. Esistono sei etichette: attributes, data, domain, source, target e translate. La configurazione di base consiste nell'avvolgere il brano di testo che desiderate tradurre ed aggiungere gli attributi i18n adatti. Per esempio, se vogliamo tradurre il seguente:

<i>Del testo</i>

esso diverrebbe come:

<i i18n:translate="etichetta_del_testo">Del testo</i>

Ogni localizzazione offre una traduzione di Del testo, e lo strumento di traduzione individua la stringa tradotta e la mostra all'utente. Quando si effettua la traduzione, ogni stringa per essere tradotta deve avere un unico ID che la contraddistingue. Per esempio, una stringa come Ricerca potrebbe avere un ID etichetta_oggetto_ricerca. Questo ID permette alla stringa di essere identificata unicamente e la traduzione di essere riutilizzata.

i18n:translate

Questo traduce i contenuti di un elemento, con un ID opzionale passato come un'etichetta. Per esempio, il seguente creerà una stringa con ID stringa_titolo:

<h1 i18n:translate="stringa_titolo">Questo è un titolo</h1>

Questo esempio mostra un testo statico che non subisce variazioni. Comunque, in alcune situazioni il pezzo di testo potrebbe essere preso da un database o da un oggetto e potrebbe essere dinamico. Lasciando l'etichetta translate vuota, l'ID della stringa viene calcolato a partire dal valore del campo. Nell'esempio seguente, se il titolo ritornato dall'espressione path here/title fosse Alice nel Paese delle meraviglie, allora questo titolo verrà passato allo strumento di traduzione. Se non esiste alcuna traduzione, verrà restituito il valore originale:

<h1
    tal:content="here/title"
    i18n:translate="">
      Questo è un titolo.
</h1>

Il comando di traduzione è probabilmente uno dei marcatori i18n più comunemente usati, e lo vedremo in tutti i modelli di Plone. Non solo consente di tradurre parti statiche del nostro sito, come etichette di modulo, messaggi di aiuto, e descrizioni, ma anche le parti più dinamiche del sito che possono cambiare spesso, come i titoli delle pagine.

i18n:domain

Questo setta il dominio per la traduzione. Per prevenire conflitti, ogni sito potrebbe avere multipli domini o gruppi di traduzioni; per esempio, potrebbe esserci un dominio per Plone ed uno per la nostra applicazione personalizzata. Plone usa il dominio plone che è il dominio di default di Plone:

<body i18n:domain="plone">

Non si tratta di un comando che useremo spesso; comunque, se stiamo scrivendo un'applicazione personalizzata, potremmo trovare utile avere un dominio per evitare conflitti con altri dominii.

i18n:source

Questo setta il linguaggio sorgente del testo da tradurre. Non è usato in Plone:

<p i18n:source="en" i18n:translate="">Del testo</p>
i18n:name

Questo fornisce una via per preservare elementi in un grande blocco di testo così che il blocco di testo possa essere riordinato. In molte lingue, non vengono solamente cambiate le parole ma anche l'ordine. Se dobbiamo tradurre un intero paragrafo o una frase che contiene piccoli pezzetti che non dovrebbero essere tradotti, allora possono essere passati attraverso:

<p i18n:translate="messaggio_libro">
    The
    <span
       tal:omit-tag=""
       tal:content="book/color"
       i18n:name="age">Blue</span>
    Book
</p>

Questo produrrà il seguente messaggio:

The {color} Book

Se il linguaggio tradotto costringesse ad un diverso ordine, le parole possono venire spostate e comunque si otterrebbe il contenuto dinamico posizionato nel posto giusto. In italiano, questo avrebbe bisogno di essere tradotto in questo modo:

Il Libro {color}
i18n:target

Questo imposta la lingua di destinazione della traduzione. Non è usato in Plone.

i18n:attributes

Questo permette la traduzione di attributi all'interno di un elemento, piuttosto che il contenuto. Per esempio, un marcatore image ha l'attributo alt che mostra una visualizzazione alternativa dell'immagine:

<img
    href="/qualcheimmagine.jpg"
    alt="Un certo testo"
    i18n:attributes="alt alternate_image_label" />

Attributi multipli devono essere separati con un punto e virgola, nello stesso modo di tal:attributes.

i18n:data

Questo offre un modo di tradurre qualsiasi cosa oltre che le stringhe. Un esempio è un oggetto DateTime. Un'etichetta i18n:data richiede una corrispondente etichetta i18n:translate così che sia disponibile un ID di messaggio valido. Per esempio:

<span i18n:data="here/currentTime"
    i18n:translate="timefmt"
    i18n:name="time">2:32 pm</span>...  beep!
Translation Service

Ora che abbiamo trattato i marcatori, tratteremo del meccanismo per eseguire la traduzione. Di suo Plone dispone di un meccanismo I18N. Questo permette di internazionalizzare l'interfaccia utente così che messaggi, schede, e moduli possono essere tutti tradotti. A questo livello non trattiamo il reale contenuto che gli utenti aggiungono. Se si aggiunge un documento in inglese e si vede la pagina richiedendola in francese, troveremo il documento inglese con il testo francese di contorno (vedere Figura 6-1).

../pb2_en/img/3294f0601.png

Figura 6-1. Plone.org in francese

Plone legge le intestazioni HTTP che un browser spedisce al client richiedendo una determinata lingua. Se il nostro browser è in inglese, allora non vedremo granché.

Per cambiare le impostazioni della lingua in Internet Explorer, si faccia come segue:

Nota

manca qualche cosa ....

Una volta fatto questo, scegliamo i nostri siti Plone favoriti e visitiamoli col browser.

Le traduzioni di Plone sono effettuate attraverso uno strumento chiamato Placeless Translation Service (PTS).

Possiamo localizzare lo strumento PTS nel pannello di controllo di Zope; in fondo alla pagina vedremo un opzione per il Placeless Translation Service. Clicchiamolo, ed apriamo tutte le traduzioni che esistono. Queste traduzioni sono lette direttamente dal File System; clicchiamo una traduzione per vedere le informazioni sul linguaggio, come il traduttore, la codifica, ed il percorso del file. Tutti i file in realtà sono conservati nella directory i18n della directory CMFPlone.

Le traduzioni sono eseguite usando due file, un file .po ed un file .mo [2]. Per esempio, plone-de.po contiene le traduzioni per il tedesco (de è il suffisso per il tedesco). Il file .mo è la versione "compilata" del file .po e sono usati da Plone per un miglior rendimento. Non abbiamo mai bisogno di guardare nel file .mo, così che potremo proprio ignorarlo.

Il .po è il file che può essere modificato per una traduzione. Se apriamo questo file in un editor di testo, vedremo una serie di linee che iniziano con il testo msgid o msgstr. Al di sopra di msgid v'è attualmente il codice dove si presenta il comando i18n, così che si possa risalire a quale pezzo della pagina si sta traducendo. Per esempio:

#: from plone_forms/content_status_history.pt
#.   <input attributes="tabindex tabindex/next;" value="Apply"
class="context" name="workflow_action_submit" type="submit" />
#.
#: from plone_forms/personalize_form.pt
#.   <input attributes="tabindex tabindex/next;" tabindex=""
value="Apply" class="context" type="submit" />
#.
msgid "Apply"
msgstr "Anwenden"

Nelle due parti del precedente modello di pagina, la parola Apply, sarà tradotta in Anwenden per gli utenti tedeschi. Quello che viene tradotto è determinato dai marcatori i18n che sono stati inseriti nei modelli di pagine, come precedentemente visto. Se vogliamo cambiare questa traduzione o aggiungere la nostra personale variazione, allora dobbiamo solamente cambiare il file .po. Se nessun msgstr viene trovato, allora viene restituita la traduzione inglese di default. Una volta che fatto questo cambiamento, riavviamo Plone. Quando accade ciò, Plone ricompilerà questi file nella versione .mo e la nostra traduzione sarà aggiornata.

Per Plone, l'opzione predefinita è sempre quella di usare il file di traduzione inglese se non viene fornito nessun linguaggio ovvero nessuna traduzione sia disponibile. Infatti, il file plone-en.po è vuoto, in modo che nessuna traduzione sia disponibile. Perciò, Plone fa la ricaduta finale (fallback), non effettua traduzione, e mostra il testo nel modello di pagina. Il testo in tutti i modelli di pagine è in inglese visto che la maggior parte degli sviluppatori parlano inglese. Il vantaggio di ciò è che non v'è traduzione in inglese.

Perciò, si può fare una nuova traduzione copiando il file plone.pot in un nuovo file col nome plone-xx.po. Il valore di xx deve essere uguale al codice del paese della nostra traduzione. Possiamo trovare un elenco di codici di linguaggio presso http://www.unicode.org/onlinedat/languages.html. Una volta avviata la traduzione, aggiustiamo i valori all'inizio del file, incluso il codice di linguaggio e iniziamo a tradurre. Se abbiamo realizzato un nuovo file di linguaggio, allora il team di Plone I18N sarà felice di accettarlo e aiuterà a completarlo. La mailing list del team di Plone è presso http://sourceforge.net/mailarchive/forum.php?forum_id=11647.

Tradurre il contenuto che le persone via via aggiungono è davvero un compito complicato: si sta lavorando ad una soluzione, ma non c'è ancora nulla di definitivo. L'approccio migliore è al momento usare due prodotti, PloneLanguageTool e i18nLayer ; entrambi possono essere trovati nel progetto Collective di SourceForge. Comunque, entrambi sono prodotti per sviluppatori molto esperti per comprenderli pienamente ed integrarli; speriamo che qualche cosa come questi vi sia nella prossima versione del libro.

[1]La sigla I18N è la contrazione del termine inglese internationalization, prendendo le due lettere agli estremi e saltandone appunto 18.
[2]In realtà, le versioni recenti di PTS sono in grado di utilizzare direttamente i file .po e non c'è più motivo di doverli compilare nel formato .mo. [N.d.T.]

Esempio: Visualizzare informazioni su più utenti

Nel capitolo 5 abbiamo usato semplice comandi TAL per mostrare più in dettaglio le informazioni di un utente. Quel modello ha alcuni limiti; uno è che mostra solamente un utente per volta. Abbiamo visto come con un semplice tal:repeat sia possibile ripetere dei contenuti, ma ora useremo una macro per realizzare questa pagina in forma più modulare.

Cambieremo il modello di pagina user_info così da elencare ogni membro del sito. Invece di cercare un username che venga passato alla richiesta, verrà usata la funzione listMembers che restituisce un elenco dei membri del sito:

<div metal:fill-slot="main">
  <tal:block
   tal:define="
   getPortrait nocall: here/portal_membership/getPersonalPortrait;
   getFolder nocall: here/portal_membership/getHomeFolder
   ">
   <table>
     <tr tal:repeat="userObj here/portal_membership/listMembers">
         <metal:block
          metal:use-macro="here/user_section/macros/userSection" />
     </tr>
   </table>
 </tal:block>
</div>

Noteremo che ora il codice di user_info è molto più breve. L'utente ritornato da listMembers è passato in tal:repeat. Per ciascun utente, vi sarà una riga di tabella e poi una macro per mostrare informazioni all'utente. In quella riga di tabella, la variabile userObj, definita localmente, ora contiene le informazioni dell'utente. Di certo, ora avremo necessità di creare una macro chiamata userSection in un modello di pagina: nell'esempio la macro è contenuta nel modello user_section. Questo modello contiene tutto il codice che era tra i marcatori row nella tabella. Di nuovo, possiamo trovare un listato completo per questo modello di pagina nell'Appendice B:

<div metal:define-macro="userSection"
     tal:define="userName userObj/getUserName">
     ...

L'unico vero cambiamento è che la use-macro nel modello main deve essere rimossa da una nuova macro definita in modo che questa macro possa essere definita. Poiché l'username non viene più passato esplicitamente, abbiamo bisogno di ottenere l'username dell'oggetto utente usando il metodo getUserName. Per esaminare la pagina risultante, visitiamo l'indirizzo http://sitoplone/user_info, e dovremmo vedere un elenco di utenti.

La pagina ora è di facile uso, mostrando diversi utenti contemporaneamente. Il codice è più modulare, avendo posizionato le informazioni dell'utente in una macro separata che può essere modificata indipendentemente. Questa pagina ancora non è perfetta ma sarà migliorata nei capitoli successivi.

Esempio: Creare un nuovo Portlet con gli annunci di Google

Nel capitolo 4 abbiamo affermato come sia facile modificare i portlet in un sito Plone; aggiungere un nostro portlet personale non è molto più difficile. Per scrivere un nostro slot personale, dovremo creare un nuovo modello di pagina con una macro all'interno. Poi un'espressione TALES che punta alla macro verrà aggiunta all'elenco dei portlet, visualizzando il portlet nella pagina.

Il modello di base per un portlet è il seguente:

<div metal:define-macro="portlet">
    <div class="portlet">
      <!-- Enter code here -->
    </div>
</div>

Tutto quello di cui abbiamo bisogno di fare è inserire del codice appropriato nel portlet. Google nel 2003 ha reso disponibile un sistema di annunci testuali per poterli posizionare nel proprio sito. Gli annunci sono basati su quello che Google ritiene essere interessante per il nostro sito, basandosi sui risultati delle ricerche effettuate sul sito stesso. Il sistema di Google è disponibile presso http://www.google.com/adsense. Per visualizzare gli annunci (ed essere pagati per questo), dobbiamo registrarci sul sito web di Google, dove ci verrà chiesto di scegliere i colori e lo stile. Siccome metteremo questo in uno slot, raccomandiamo la dimensione "skyscraper". Fare una copia dello JavaScript che il sito fornisce.

Dopo di che, dovremo creare un portlet:

portal_skins/custom

googleAds

googleBox

<!-- Digitiamo il codice qui -->

Il risultato finale dovrebbe essere qualche cosa come il listato 6-1; comunque, la nostra versione dovrebbe avere un valore valido per google_ad_client, piuttosto che yourUniqueValue. Questo valore informa Google quale sito ordina questo annuncio e chi dovrà essere pagato. Abbastanza curiosamente, non inserendo un ID valido in quella posizione, Google mostrerà felicemente gli annunci ma non ci pagherà!

Listato 6-1. Visualizzatore degli annunci da Google

<div metal:define-macro="portlet">
    <div class="portlet">
<script type="text/javascript"><!--
google_ad_client = "yourUniqueValue";
google_ad_width = 120;
google_ad_height = 600;
google_ad_format = "120x600_as";
//--></script>
<script type="text/javascript"
  src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
 
   </div>
</div>

Quindi, per includere questo sul nostro sito, come mostrato nel capitolo 4, aggiungiamo il portlet seguente al nostro elenco di portlet:

here/googleAds/macros/portlet

Scrivere codice Python per Plone

In Plone esistono almeno quattro livelli differenti per creare logica. Il livello più semplice di uso di Python in Plone sono le espressioni Python TALES che abbiamo trattato nel capitolo precedente. Un'espressione Python, comunque, permette di inserire solamente una riga di codice; spesso vorremo fare qualche cosa di più complesso.

Anche più comune è il problema di non voler assolutamente mischiare la logica all'interno del modello. Posizionare logica nel nostro modello è in generale una cattiva abitudine; ogni volta che si può spostare qualche cosa che non sia esplicitamente la logica di presentazione fuori dal modello, ci si risparmia un bel mal di testa. Separare logica e presentazione permette di far lavorare facilmente persone diverse su diverse parti del progetto, e migliora il riuso del codice. Gli altri livelli di aggiungere scripting in Plone avvengono più o meno nel seguente ordine:

Template attribute expressions
Questo offre espressioni ed un modo di inserire piccoli frammenti di logica o dei semplici percorsi in vari posti.
Script (Python) objects
Questi sono semplici script che sono eseguiti in Plone in ambiente ristretto.
External method objects
Questi sono moduli più complicati che non vengono eseguiti in ambiente ristretto.
Python products Questo è il modo fondamentale con cui sono scritti
internamente CMF e Plone; questo offre accesso a qualsiasi cosa in Plone. I prodotti Python sono un oggetto avanzato e sono trattati nel capitolo 14.

Dopo un'espressione, il livello successivo di complessità è un oggetto Script (Python). Questo oggetto permette diverse linee di codice Python, e può essere chiamato da un'espressione. Quando un oggetto Script (Python) viene eseguito, si incorre in un piccolo sovraccarico extra per il fatto che Plone deve attivare quell'oggetto. Comunque, questo sovraccarico è minimo perché v'è un compromesso fra chiarezza, separazione e rendimento. Il nostro consiglio è di mettere, se possibile, molta logica all'interno di Python e di tenere semplici e puliti i modelli di pagina. È facile reinserirlo, in un secondo tempo, se vi sono problemi di rendimento, ma almeno capiremo quello che sta accadendo.

Uso degli oggetti Script (Python)

Un oggetto Script (Python) è probabilmente ciò che tradizionalmente si pensa in Plone come ad uno script. È un frammento Python che si può scrivere e quindi chiamare da altri modelli o direttamente attraverso il web. Plone possiede in realtà moltissimi script per effettuare varie funzioni fondamentali. Uno Script (Python) è, in termini di potenza, una via di mezzo fra un'espressione ed un External Method.

Per aggiungere un oggetto Script (Python), andiamo nella ZMI, selezioniamo Script (Python) nel menu di scelta, e clicchiamo Add, come dimostrato in figura 6-2.

../pb2_en/img/3294f0602.png

Figura 6-2. Aggiungere un oggetto Script (Python)

Diamo allo script un ID come test_py e poi clicchiamo Add and Edit. Questo aprirà la pagina di modifica dell'oggetto Script (Python) come mostrato in figura 6-3.

../pb2_en/img/3294f0603.png

Figura 6-3. Modificare un oggetto Script (Python)

Possiamo modificare direttamente lo script attraverso il web. Se facciamo un errore di sintassi, verrà evidenziato dopo aver cliccato Save Changes, come mostrato in figura 6-4.

../pb2_en/img/3294f0604.png

Figura 6-4. Un deliberato errore di indentazione nell'oggetto Script (Python)

Se non vi sono errori nel nostro Script (Python), possiamo cliccare la scheda Test per vedere quello che sarà l'output. In questo caso, l'esempio è piuttosto noioso; stampa il testo seguente:

This is the Script (Python) "test_py" in
http://gloin:8080/Plone/portal_skins/custom

Uno script inoltre ha le seguenti opzioni:

Title
Il modulo di modifica ha un opzione Title che fornisce un titolo allo script. Questo verrà mostrato nella ZMI, così sarà più facile ricordare ciò che fa.
Parameter List Questo è un lista di parametri che lo script prende,
come variableA o variableB=none. Questo, infatti, è l'elenco standard di parametri che ci si aspetta in una funzione standard Python. Alcuni parametri, comunque, sono sempre definiti in questo oggetto; possiamo vederli cliccando la scheda Bindings. In questa scheda, già vediamo un elenco delle variabili contenute dall'oggetto che ora dovrebbero avere nomi familiari.

In seguito vi sono le variabili associate allo script accessibili da un oggetto Script (Python):

context
Questo è l'oggetto dal quale viene chiamato lo script.
container
Questo è l'oggetto contenitore dello script.
script
Questo è l'oggetto Script (Python) stesso; l'equivalente in Zope Page Templates è template.
namespace
Questo è per quando questo script viene chiamato dal Document Template Markup Language (DTML); che è un qualche cosa che non succede in Plone.
traverse_subpath
Questo è il percorso Uniform Resource Locator (l'URL) dopo il nome dello script che è una caratteristica avanzata.

Ora mostreremo un semplice esempio che unisce questi argomenti nel sistema dei modelli di pagina di Zope, usando l'esempio di un'espressione Python, fornita nel capitolo precedente, che somma due numeri. Come abbiamo visto, si può costruire un modello di pagina come segue:

<p>1 + 2 = <em tal:content="python: 1 + 2" /></p>

Il seguente è l'equivalente che utilizza uno oggetto script (Python). Cambiiamo lo script test_py alla linea seguente:

return 1+2

Come si è visto all'inizio del capitolo precedente, richiamiamo un oggetto fornendo il suo percorso in un'espressione. Quindi, in un modello di pagina, ora possiamo fare qualcosa del tipo:

<p>1 + 2 = <em tal:content="here/test_py" /></p>

L'oggetto test_py è acquisito nell'espressione path e chiamato e quindi ritorna indietro al modello Python e lo stampa. Ora abbiamo richiamato uno script dal nostro modello! Questo è evidentemente un esempio piuttosto semplice, ma a nostro giudizio vi sono una grande quantità di cose che possiamo fare con uno oggetto Script (Python) che giustamente non è possibile fare in un modello di pagina.

In un oggetto Script (Python), si possono specificare titolo, parametri, ed impostare i binding usando la notazione ## all'inizio dello script. Quando salviamo uno script con questo testo in cima, Plone rimuoverà questa linea e cambierà gli appropriati valori nell'oggetto. Questa sintassi viene molto utilizzata, in questo libro, negli oggetti Script (Python) per essere sicuri che si abbia il titolo corretto ed i parametri. Quindi, possiamo riscrivere lo script precedente come segue:

##title=Returns 1+2
##parameters=
return 1+2
Script in Plone

Scrivere codice in Plone è un soggetto piuttosto complicato perché non appena siamo capaci di scrivere script in Plone, vorremmo prendere in considerazione l'Application Programming Interface (API) di tutti gli oggetti e degli strumenti che possiamo voler usare. Spiegare le API va oltre lo scopo di questo libro; mostreremo invece come fare semplici compiti usando gli oggetti Script (Python). Una volta che avremo preso confidenza con loro, descriveremo ulteriori specifiche funzioni API.

I modelli di pagina possono accedere in modo semplice ai dizionari e alle liste Python. Ma spesso non si hanno dati in una di queste forme convenienti, così si ha bisogno di saltare all'interno con un oggetto Script (Python), formattare esattamente i dati, e poi passarli di nuovo al modello di pagina.

Il formato più conveniente dei dati è un lista di dizionari che permette di combinare in una funzione la potenza di un tal:repeat e di una path expression. Come esempio, vediamo una funzione che prende un lista di oggetti. Ciascun oggetto è in effetti un oggetto in una cartella. Per ognuno di questi oggetti, dobbiamo controllare se l'oggetto è stato aggiornato negli ultimi cinque giorni. Il listato 6-2 mostra un piccolo utile portlet che abbiamo messo insieme per un sito che cerca di localizzare questo tipo di informazioni e di evidenziarli con precisione.

Listato 6-2. Ritornare oggetti più vecchi di cinque giorni

##title=recentlyChanged
##parameters=objects
from DateTime import DateTime
 
now = DateTime()
difference = 5 # as in 5 days
result = []
 
for object in objects:
  diff = now - object.bobobase_modification_time()
  if diff < difference:
    dct = {"object":object,"diff":int(diff)}
    result.append(dct)
 
return result

In questo oggetto Script (Python) abbiamo presentato un paio di concetti nuovi. Prima, importiamo il modulo DateTime di Zope usando la funzione import. Il modulo DateTime, trattato nell'Appendice C, è un modulo che fornisce accesso alle date. È abbastanza semplice, ma se creiamo un nuovo oggetto DateTime senza parametri, allora troveremo la data e l'ora corrente; questa è la variabile now. Quando si sottraggono due oggetti DateTime, viene restituito il numero di giorni. Si può confrontare questa differenza con quella richiesta dall'utente e, se è più grande, aggiungerlo alla lista dei risultati. Il risultato è un lista di oggetti dizionario, come mostrato nel listato 6-3.

Listato 6-3. Il Risultato del listato 6-2

[
  {
      'diff': 1,
      'object': <PloneFolder instance at 02C0C110>
  },
  {
      'diff': 4,
      'object': <PloneFolder instance at 02FE3321>
  },
  ...

Così ora che abbiamo il risultato nell'ordine corretto, abbiamo bisogno di un modello di pagina che passi la lista di oggetti e processi il risultato. Un esempio di ciò è il seguente:

<ul>
  <li tal:repeat="updated python: context.updateScript(context.contentValues())">

Questo modello ha una chiamata tal:repeat in cima che richiama lo script (in questo caso, chiamato updateScript). A questa funzione viene passato un valore, una lista di contentValues, dal contesto corrente. Precedentemente si è chiamato l'oggetto Script (Python) usando un'espressione path; si può fare questo con context/updateScript. Comunque, non si possono passare parametri attraverso lo script che è stato chiamato con questa sintassi, così deve essere invece utilizzata una espressione Python, che è python: context.updateScript(). La funzione contentValues ritorna la lista di ogni oggetto contenuto in una cartella. Successivamente, guardiamo al codice per ogni iterazione:

  <a href="#"
     tal:attributes="href updated/object/absolute_url"
     tal:content="updated/object/title_or_id">
     The title of the item</a>
  <em tal:content="updated/diff" /> days ago
  </li>
</ul>

Come mostrato, possiamo iterare attraverso questa lista di valori, e possiamo usare allora espressioni path per accedere prima al valore ripetuto (update), poi all'oggetto (object), ed infine ad un metodo di questo oggetto (title_or_id). Questo è un esempio di utilizzo di una logica complicata processata e passata ad un oggetto Script (Python).

Python in ambiente limitato

Diverse volte abbiamo menzionato che gli oggetti Script (Python) e le espressioni Python TAL vengono tutti eseguiti in restricted Python. Quello del Python limitato è un ambiente che ha alcune funzioni rimosse. Queste funzioni possono essere potenzialmente pericolose in un ambiente web come è Plone. La ragione originale è che si possono avere utenti non fidati (ma autenticati) che scrivono codice Python sul nostro sito. Se apriamo un account in uno dei diversi host web liberi per Zope, troveremo che si possa fare questo. Comunque, se abbiamo dato a persone il diritto di farlo, non vogliamo che loro abbiano accesso a certe cose come il File System.

In Python limitato, alcune funzioni comuni di Python sono state rimosse per ragioni di sicurezza, dir ed open non sono disponibili. Questo vuol dire che, gli oggetti Script (Python), non possono essere introspettivi, e che l'accesso al File System è limitato. Sono disponibili all'utente alcuni moduli Python. La maggior parte di loro sono solo per sviluppatori esperti; per ulteriori informazioni, si veda la documentazione attinente o il codice del modulo:

string
Questo è il modulo Python per le stringhe (http://Python.org/doc/current/lib/module-string.html).
random
Questo è il modulo Python casuale (http://Python.org/doc/current/lib/module-random.html).
whrandom
Questo è il modulo Python whrandom. Si dovrebbe usare quasi sempre random (http://Python.org/doc/current/lib/module-whrandom.html).
math
Questo è il modulo Python relativo alle funzioni matematiche (http://Python.org/doc/current/lib/module-math.html).
DateTime
Questo è il modulo proprio di Zope per le date.
sequence
Questo è un modulo Zope per ordinare facilmente delle sequenze.
ZTUtils
Questo è un modulo Zope che offre varie utilità.
AccessControl
Questo dà accesso al modulo Access di Zope.
Products.PythonScripts.standard
Questo dà accesso alle funzioni standard di processo delle stringhe DTML come html_quote, thousands_commas e così via.

Se vogliamo importare un modulo che non è nella lista precedente, possiamo trovare allora eccellenti istruzioni nel modulo PythonScript. Lo troveremo presso Zope/lib/Python/Products/PythonScripts/module_access_examples.py. Comunque, un procedimento più semplice è disponibile usando un External Method.

Uso degli oggetti external Method

Un External Method è un modulo Python scritto nel File System e che può accedere a Plone. Siccome è scritto nel File System, non viene eseguito in modalità lim itata di Python, e perciò è conforme alle impostazioni di sicurezza standard di Plone.

Questo vuol dire che si può scrivere uno script che fa qualsiasi cosa si voglia e lo si chiama poi da un modello di pagina. Compiti comuni includono l'apertura e la chiusura dei file, accedere ad altri processi o a file eseguibili, ed eseguire compiti che Plone o Zope non possono effettuare con semplicità in qualche altro modo. Per ovvie ragioni, quando stiamo scrivendo uno script che può fare questo, abbiamo bisogno di essere sicuri che non stiamo facendo qualcosa di pericoloso, come leggere file di password del nostro server o cancellare un file che non si voglia cancellare.

Per aggiungere un External Method, andiamo nel File System della nostra istanza home e cerchiamo la directory Extensions. In questa directory, aggiungiamo un nuovo file Script (Python); per esempio, come mostrato in Figura 6-5 abbiamo aggiunto test.py nella cartella del nostro computer Windows.

../pb2_en/img/3294f0605.png

Figura 6-5. Un nuovo External Method, test.py

Ora possiamo aprire test.py e modificare il suo contenuto, scrivendo qualunque codice Python si voglia. L'unico accorgimento è che dobbiamo avere una funzione di entrata che richieda almeno un argomento, self. Questo argomento è l'oggetto dell'External Method di Plone che aggiungeremo fra breve. Ciò che segue è un esempio di funzione di ingresso che legge il file README.txt fuori dalla stessa directory Extensions e lo restituisca all'utente (dovremo cambiare il percorso per puntare al nostro file):

def readFile(self):
   fh = open(r'c:\Program Files\Plone\Data\Extensions\README.txt', 'rb')
   data = fh.read()
   return data

Ora che abbiamo fatto questo, abbiamo bisogno di mappare un External Method a questo script. Questo è un oggetto Zope, così ritorniamo alla ZMI, clicchiamo portal_skin e poi clicchiamo custom. Finalmente, selezioniamo External Method dal box a cascata Add New Items. Quando si aggiunge un External Method, si ha bisogno di dare il nome del modulo (senza il .py) e la funzione di ingresso, così che in questo caso avrà un modulo come in Figura 6-6.

../pb2_en/img/3294f0606.png

Figura 6-6. L'External Method aggiunto di recente

Dopo avere cliccato Save Changes, si può premere la scheda Test per vedere quello che succede quando viene eseguito. In questo caso, dovremmo trovare una linea o due di testo. Siccome abbiamo il modulo Plone External Method, si può accedere ad esso da un modello di pagina nello stesso modo come con qualsiasi altro oggetto. Una path expression a here/test_external farebbe ciò in questo caso. Per esempio:

<h1>README.txt</h1>
<p tal:content="here/test_external" />

La vera forza è che si può passare codice da un ambiente non limitato di Python senza alcuna restrizione e da là a qualsiasi funzione che si vuole, senza doversi preoccupare della sicurezza. Anche se questo può sembrare come una funzione leggera, i metodi esterni non sono molto usati in Plone perché la logica più complessa è usualmente posta all'interno di un oggetto Product mentre la logica semplice è invece posta in un oggetto Script (Python). Se ci si trova ad usare molto oggetti External Method, si prenda in considerazione uno degli strumenti discussi nel Capitolo 12.

Consigli utili

Poiché i modelli di pagine sono validi Extensible Markup Language (XML) e possono essere usati indipendentemente da Zope o da Plone, abbiamo molti utili script per ripulire il codice nei modelli di pagine e per effettuare controlli di sintassi. Questi sono strumenti supplementari e controlli; Zope attualmente compie tutti i controlli necessari quando carica un modello di pagina. Per un progetto come Plone, può essere utile eseguire controlli automatici sul nostro codice o possiamo verificarlo localmente prima di inviare le modifiche.

Per eseguire questi controlli, avremo bisogno di poter abilitare questi strumenti a modificare localmente e di avere Python istallato sul nostro computer. Per ulteriori informazioni sull'External Editor, un metodo per modificare in locale codice remoto, si veda Capitolo 10.

Introduzione allo spazio dei nomi XML

I modelli di pagine usano lo spazio dei nomi XML per generare codice. I programmatori possono usare le regole del namespaces XML per rendersi la vita più facile. All'inizio di un modello di pagina, si può vedere una dichiarazione dello spazio dei nomi nel marcatore iniziale:

<html xmlns="http://www.w3.org/1999/xhtml"...

Questo pone il namespace predefinito a Extensible HTML (XHTML). Per qualsiasi elemento in esso contenuto, se nessuno spazio di nomi viene definito, viene usato di base tale spazio dei nomi. Per esempio, riconosciamo che l'elemento successivo è XHTML perché non ha prefisso:

<body>

Normalmente per gli elementi e gli attributi TAL e METAL, abbiamo aggiunto il prefisso tal: e METAL: per definire il namespace. Il codice seguente è qualche cosa di cui ora dovreste avere familiarità:

<span tal:omit-tag="" tal:content="python: 1+2" />

Questo restituirà 3. Comunque, ciò che segue è un'alternativa:

<tal:number content="python: 1+2" />

Usando il prefisso tal:, abbiamo definito lo spazio dei nomi predefinito per questo elemento intero come tal. Se nessun altro prefisso è dato, viene usato lo spazio dei nomi tal. Nell'esempio, usando il marcatore span, lo spazio dei nomi predefinito è XHTML, così dobbiamo specificatamente definire il prefisso tal: quando si usa la scheda Content.

Si noti che il nome di un elemento è descrittivo e non può essere nessuna cosa già definita dal nomespace tal (per esempio, conten*t o *replace). Perché tal:number non è un valido elemento XHTML, il marcatore attuale non verrà visualizzato, ma il contenuto verrà marcato con un inutile omit-tag. Questa tecnica è molto usata in Plone per realizzare codice che sia più piccolo, più facile da correggere, e più semantico.

Presentare codice pulito

HTML Tidy è un eccellente strumento per esaminare e ripulire il codice HTML che può eseguire alcuni compiti utili. Esistono versioni di HTML Tidy per molti sistemi operativi; possiamo scaricarlo da http://tidy.sourceforge.net. Per gli utenti Windows, trovato l'adatto download per la nostra versione Windows, decomprimiamo il file tidy.zip e collochiamo il file tidy.exe nel nostro PATH (di default la nostra cartella Windows, come C:\WINNT).

HTML Tidy può mostrare se vi sia qualche errore XHTML nel nostro modello di pagina. Per questi scopi, una opzione del comando (flag) può fare la differenza: -xml. Questo richiede ad HTML Tidy di processare il file come XML e riportare qualsiasi errore XML. Dato l'esempio di modello "bad" mostrato nel listato 6-4, possiamo vedere alcuni errori. Non solo è codice non indentato, ma si sono omesso elementi di chiusura e abbiamo annidamenti invalidi.

Listato 6-4. Un esempio di modello di pagina "bad": bad_template.pt

<!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">
<head>
<title></title>
</head>
<body>
<p>
<div>
This is bad HTML,
XHTML or XML...<a tal:contents="string: someUrl"></a>
</p>
<img>
Further it isnt indented!
</body>
</html>

Se eseguiamo il Listato 6-4 tramite HTML Tidy, vedremo gli errori nel modello ed otterremo un codice gradevolmente indentato, come mostra il Listato 6-5.

Listato 6-5. L'output prodotto da HTML Tidy

$ tidy -q -i bad_template.pt
line 11 column 1 - Warning: <img> element not empty or not closed
line 10 column 1 - Warning: missing </div>
line 10 column 39 - Warning: <a> proprietary attribute "tal:contents"
line 11 column 1 - Warning: <img> lacks "alt" attribute
line 11 column 1 - Warning: <img> lacks "src" attribute
line 9 column 1 - Warning: trimming empty <p>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta name="generator" content=
  "HTML Tidy for Linux/x86 (vers 1st August 2003), see www.w3.org" />
 
  <title></title>
</head>
 
<body>
  <div>
    This is bad HTML, XHTML or XML...<a tal:contents=
    "string: someUrl"></a> <img />Further it isnt indented!
  </div>
</body>
</html>

Le lagnanze su attributi di proprietà riservati possono essere un po' noiose. Per controllare che il nostro modello di pagina sia XML valido, aggiungiamo l'opzione - xml. L'output è meno verboso e indica solo i marcatori mancanti:

$ tidy -q -xml bad_template.pt
line 15 column 1 - Error: unexpected </body> in <img>
line 16 column 1 - Error: unexpected </html> in <img>

Eseguire i controlli sulla sintassi

Quando modifichiamo un modello di pagina nella ZMI, Zope compie un controllo di sintassi sul documento per cose come i marcatori non validi. Se un marcatore è invalido, un errore sarà mostrato sul modello mentre viene compilato attraverso il web. Se, come noi (e come mostrato nel Capitolo 7), scriviamo la maggior parte dei nostri modelli di pagina nel File System, allora è veramente utile avere un controllo di sintassi semplice per un modello di pagina. Il Listato 6-6 è uno Script (Python) che risiede nel nostro File System ed viene eseguito indipendentemente da Zope.

Per eseguirlo, dobbiamo avere un interprete Python, ed il modulo Python PageTemplate deve essere importabile. Per rendere importabile PageTemplate nel nostro interprete Python dobbiamo aggiungere la il path della cartella Products della nostra installazione Zope ai percorsi Python path. Abbiamo diversi modi di fare ciò (trattato in Appendice B).

Listato 6-6. Controlli di Errore sul modello di pagina

#!/usr/bin/python
from Products.PageTemplates.PageTemplate import PageTemplate
import sys
 
def test(file):
    raw_data = open(file, 'r').read()
    pt = PageTemplate()
    pt.write(raw_data)
    if pt._v_errors:
        print "*** Error in:", file
        for error in pt._v_errors[1:]:
            print error
 
if __name__=='__main__':
    if len(sys.argv) < 2:
        print "python check.py file [files...]"
        sys.exit(1)
    else:
        for arg in sys.argv[1:]:
            test(arg)

Per ciascun file passato allo script, la ZMI compila il modello di pagina e vediamo se c'è qualche errore TAL. Prendendo il file bad_ template.pt dal listato 6-4, troveremo un errore:

$ python zpt.py /tmp/bad_template.pt
*** Error in: /tmp/bad_template.pt
TAL.TALDefs.TALError: bad TAL attribute: 'contents', at line 10, column 39

In questo caso, abbiamo evidenziato una non corretta sintassi di tal:content come tal:contents. Questo errore è qualche cosa che HTML Tidy non evidenzia. Sfortunatamente, il processo si ferma al primo errore di sintassi. Se vi sono diversi errori, solamente il primo è evidenziato, ricordiamoci allora che dobbiamo ricontrollare più volte la sintassi.

Usare i moduli

I moduli sono una parte integrale di qualsiasi sito, e quasi ognuno ha bisogno di creare un metodo per realizzare e modificare moduli nel proprio sito Plone. Con l'ambiente dei moduli Plone, si può cambiare la validazione che hanno i moduli di processo, da dove prendono l'utente e così via. Questo ambiente non è specificamente progettata solo per moduli autonomi che compiano semplici lavori, come richiedere una password, effettuare il login e così via. L'ambiente si applica anche a tutti i tipi di contenuto, con l'obiettivo di modificarli, cosa che tratteremo in un secondo tempo in questo libro nel Capitolo 11 e Capitolo 13.

Tutti i moduli di base hanno almeno due componenti che abbiamo già visto: un oggetto Page Template per mostrare il modulo all'utente, ed uno oggetto Script (Python) per analizzare i risultati e eseguire l'azione sui risultati.

La struttura di controllo del modulo presenta alcuni nuovi tipi di oggetto che sono equivalenti ai tipi che abbiamo visto in questo capitolo. Questi sono l'oggetto Controller Page Template, l'oggetto Controller Script (Python), ed l'oggetto Controller Validator. Questi nuovi oggetti hanno i loro equivalenti oggetti, come mostrato in Tabella 6-1. Questi nuovi oggetti possiedono ulteriori proprietà ed agiscono in modo lievemente diverso dagli oggetti equivalenti.

Tabella 6-1. Nuovi tipi di oggetto che il Controller fornisce

Tipi di oggetti Oggetti Zope equivalenti
Controller Fileystem Page Template Page Template
Controller Python Script Python Script
Controller Validator Python Script

Per aggiungere uno di questi oggetti usando la ZMI, andiamo al box a cascata, e selezioniamo il nome.

L'ambiente del modulo di controllo crea una sequenza di eventi per un modulo che un utente può successivamente definire. Ciò che segue è la sequenza di eventi che accadono quando si esegue un modulo:

Attenzione!

manca qualcosa nel file cap6.rst -- | Ugo

Quando questa sequenza di eventi accade, un oggetto state viene passato e contiene informazioni sullo stato dell'oggetto, il successo di qualunque validazione e qualsiasi messaggio sia stato restituito.

Nelle sezioni seguenti rivedremo questi passi per mostrare come un modulo può essere validato e poi mostreremo un esempio completo nella sezione Un esempio di posta elettronica: spedire una e-mail all'amministratore

Creare un modulo campione ed associare uno script

L'inizio di questo processo è un modulo. Sebbene questo sia in realtà un oggetto Controller Page Template, è scritto usando codice TAL standard. Per aggiungerne uno, selezioniamo Controller Page Template dall'ormai famigliare box a cascata e diamogli un ID test_cpt.

Un modulo Plone è un pezzo di codice davvero lungo se vogliamo utilizzare tutte le opzioni disponibili. Questo pezzo di codice è riprodotto completamente nell'Appendice B ed è il codice usato nell'esempio successivo:

<form method="post"
      tal:define="errors options/state/getErrors"
      tal:attributes="action template/id;">
    ...
    <input type="hidden" name="form.submitted" value="1" />
</form>

Osservando questo codice, dovreste notare che per poter funzionare nell'ambiente, esistono alcune piccole differenze fra questo e quello che si può considerare un modulo standard. Primo, il modulo è impostato per poter inserire se stesso; questo non è opzionale. Secondo, esiste una speciale variabile nascosta; form.submitted.

L'oggetto Controller Page Template controlla la variabile request per il valore form.submitted per vedere se il modulo sia già stato proposto (submit). Se, invece, vi è stato solo l'accesso, - per esempio attraverso un link – questo non è opzionale. All'inizio del modulo, viene impostata la variabile errors. Il dizionario errors viene creato dall'oggetto state che è stato passato al modello. L'oggetto state è un oggetto comune, in questo sistema, a tutte i modelli e script.

Creare i validatori

Una volta che l'utente ha cliccato il pulsante Submit sul nostro modulo, i dati saranno inviati ai validatori per essere validati. La validazione è opzionale. I dati non hanno bisogno di essere validati, ma chiaramente qualsiasi applicazione dovrebbe fare ciò. La scheda Validator per un oggetto Controller Page Template ci da un collegamento alla possibile validazione.

Uno script di validazione è identico ad un normale oggetto Script (Python) con una variabile aggiuntiva, state. La variabile state è necessaria per poter passare i risultati della validazione. Il Listato 6-7 mostra un semplice script di validazione per controllare se è stato fornito un numero.

Listato 6-7. Convalidare che sia stato fornito un numero

##title=A validation script to check we have a number
##parameters=
num = context.REQUEST.get('num', None)
try:
    int(num)
except ValueError:
    state.setError("num", "Not a number", new_status="failure")
except TypeError:
    state.setError("num", "No number given.", new_status="failure")
if state.getErrors():
    state.set(portal_status_message="Please correct the errors.")
return state

Questo oggetto state contiene informazioni basilari su ciò che è successo durante il processo di validazione. L'oggetto state conserva gli errori per ciascun campo, lo stato, e qualsiasi altro valore. Per esempio, se il numero dato non può essere restituito come numero intero, imposta lo stato a failure e ritorna un messaggio d'errore per il campo usando il metodo setError. In un secondo momento questo messaggio d'errore verrà mostrato per il campo. Alla fine dello script, qualsiasi errore restituito finora è recuperato attraverso il metodo getErrors.

Per aggiungere lo script precedente, clicchiamo portal_skin, clicchiamo custom, e scegliamo il Controller Validator dal box a cascata. Diamogli l'ID test_validator. Ora possiamo ritornare alla scheda Validation del nostro oggetto Controller Page Template e possiamo aggiungere un puntatore a questo script di validazione, come mostrato in Figura 6-7.

../pb2_en/img/3294f0607.png

Figura 6-7. Aggiungere il test_Validator all'oggetto Controller Page Template

Abbiamo un paio di opzioni per la validazione. Nell'esempio li abbiamo ignorati visto che non sono attinenti, ma ciò che segue è la lista delle opzioni:

contextType
Questo è il tipo dell'oggetto context, all'occorenza, questo modello viene eseguito dentro. Questa è una scorciatoia al tipo di contenuto dell'oggetto context. Se vogliamo eseguire solo questa validazione su un collegamento, allora possiamo mettere questo valore a Link.
button
Questo è il pulsante, che, all'occorenza, viene cliccato per sottoporre il modulo. Possiamo avere diversi pulsanti su un modulo (per esempio, un pulsante Submit ed uno Cancel). Ciascuno di questi pulsanti può poi mappare una diversa azione; cliccando Cancel si può andare in un posto, e cliccando Submit andare in un altro.
validators
Questo è un lista di validatori separati da una virgola, che sono gli oggetti Controller Validator che il modello acquisirà. Nell'esempio precedente, usiamo l'ID per il validatore: test_validator.

Nota

Quando si scrivono script di validazione, usiamo l'oggetto Controller Validator invece di unoggetto Script (Python). Gli oggetti Controller Validator sono proprio ordinari oggetti Script (Python) con un'addizionale scheda ZMI Actions.

Specificare azioni

Le Actions sono le azioni finali dopo che i validatori sono stati eseguiti, e dipendono dallo stato che viene restituito dalla validazione. La scheda Actions per un oggetto Controller Page Template mostra tutte le azioni per il modello di pagina in questione. Possiamo specificare azioni con lo stesso genere delle opzioni di specializzazione come descritto precedentemente attraverso un modulo web, come mostrato in Figura 6-8.

../pb2_en/img/3294f0608.png

Figura 6-8. Aggiungere un'azione

Abbiamo le seguenti quattro opzioni per l'attuale azione risultante:

redirect_to
Questo reindirizza all'URL specificata nell'argomento (un'espressione TALES). L'URL può essere assoluta o relativa.

redirect_to_action

Questo reindirizza all'azione specificata nell'argomento (un'espressione TALES) per il corrente oggetto contenuto (per esempio, string:view). In questa fase non abbiamo trattato ancora azioni, ma qualsiasi oggetto contenuto ha azioni come view ed edit. Il Capitolo 11 tratta le azioni per un oggetto.
traverse_to
Questo traversa l'URL specificata nell'argomento (un'espressione TALES). L'URL può essere assoluta o può essere relativa.
traverse_to_action
Questo traversa l'azione specificata nell'argomento (un'espressione TALES) per il corrente oggetto contenuto (per esempio, string:view).

Un esempio di ciò è che se il riempimento del modulo avviene con successo, attraversiamo un oggetto Controller Python Script che abbiamo scritto il quale processa il risultato del modulo. Se la pagina da un errore, torniamo ad attraversare il modello e visualizzamo l'errore.

La differenza tra un reindirizzamento ed un attraversamento è che il reindirizzamento è una redirezione dell'HTTP spedito al browser dell'utente. Il browser lo processa e manda quindi l'utente alla pagina successiva. Quindi, le'azioni di reindirizzamento perdono tutti i valori passati dalle richieste originali. Se abbiamo bisogno di esaminare i contenuti del modulo originale, questo non è l'approccio migliore. Invece, raccomandiamo di usare le opzioni di attraversamento. Il risultato è lo stesso; solo che la scelta di attraversare esegue tutto sul server. Così facendo preserva le variabili request e permette di esaminarle con gli script.

Un esempio di posta elettronica: spedire una e-mail all'amministratore

Vedremo ora un vero esempio ed utilizzeremo il resto di questo capitolo per costruirlo. Un'esigenza comune è un modulo personale che spedisce un e-mail al Webmaster. Costruiremo questo tipo di modulo nelle sezioni seguenti. Lo script completo, il modello di pagina, ed il codice associato sono disponibili nell'Appendice B. Se non vogliamo realmente digitare tutto questo, possiamo vedere questo esempio on-line sul sito Web del libro; ed inoltre possiamo scaricarlo come file compresso dal sito Web del libro Plone (http://plone-book.agmweb.ca) e dal sito web Apress (http://www.apress.com), così possiamo installarlo da soli e possiamo provarlo. Questo esempio ha solo due campi nel modulo: la e-mail della persona che sottopone il modulo ed i commenti di questa persona. In questo modulo viene richiesta la e-mail della persona , così potremo rispondere ai suoi commenti.

Costruire il modulo

Il modulo è la parte più grande e più complicata di questa procedura, soprattutto perché v'è tanto lavoro da fare per supportare la gestione degli errori. Questo modulo è un oggetto Controller Page Template chiamato feedbackForm. Per assicurarsi che sia contenuto nel modello principale, inizieremo il modulo in un metodo standard:

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xml:lang="en-US"
    lang="en-US"
    i18n:domain="plone"
    metal:use-macro="here/main_template/macros/master">
  <body>
    <div metal:fill-slot="main"
         tal:define="errors options/state/getErrors;">

Un'aggiunta opzionale qui è errors options/state/getErrors che posiziona qualsiasi cosa e tutti gli errori all'interno della variabile locale per usi futuri.

A causa del requisito del modulo di poter far riferimento indietro a se stesso, viene impostata questa azione in TAL, con l'espressione template/id. Questo percorso estrae l'ID del modello e lo inserisce nell'azione, così che questo percorso funzionerà sempre, anche se rinominiamo il modello. Notiamo che stiamo aggiungendo anche marcatori i18n per essere sicuri che questo modulo possa essere localizzato:

<form method="post"
      tal:attributes="action template/id;">
 
<legend i18n:translate="legend_feedback_form">
    Website Feedback
</legend>

Ciò che segue è l'inizio della riga dell'indirizzo e-mail. Definiamo qui una variabile chiamata error_email_address che posizionerà una stringa di errore se v'è una stringa appropriata nel dizionario errors. Quel valore di errore sarà generato dal validatore qualora dovesse generarsi un errore là:

<div class="field"
     tal:attributes="class python:test(error_email_address,
                                       'field error', 'field')">
     tal:define="error_email_address errors/email_address|nothing;">

La seguente è l'etichetta per il campo indirizzo e-mail. In questa etichetta includeremo un div per il testo di aiuto. L'elemento span diventa ora il familiare punto rosso vicino all'etichetta così che l'utente sappia che è richiesto:

<label i18n:translate="label_email_address">Your email address</label>
<span class="fieldRequired" title="Required">(Required)</span>
<div class="formHelp"
     i18n:translate="label_email_address_help">
     Enter your email address.
</div>

Successivamente aggiungeremo l'elemento attuale:

    <div tal:condition="error_email_address">
        <tal:block i18n:translate=""
                   content="error_email_address">Error
        </tal:block>
    </div>
    <input type="text" name="email_address"
           tal:attributes="tabindex tabindex/next;
                           value request/email_address|nothing" />
</div>

All'inizio di questo blocco, esamineremo per vedere se c'è un errore. Se c'è, la classe per l'elemento cambiato sarà la classe field error; questa classe mostrerà una bel riquadro arancio attorno al campo. Successivamente, se è accaduto un errore in questa zona (come abbiamo già sperimentato), sarà visualizzato il corrispondente messaggio. Finalmente, mostrerà l'elemento del modulo, e se v'è un valore per email_address già nella richiesta, popolerà l'elemento del modulo con quel valore.

Il tabindex è un utile strumento di Plone. Contiene un numero sequenziale che viene incrementato per ogni elemento, e ogni volta imposta un nuovo valore HTML tabindex per ciascun elemento nel modulo. Questa è una bella caratteristica per l'interfaccia utente; vuole dire che ciascun elemento del modulo può essere salvato senza doversi preoccupare di ricordare il numero tabindex perché questo viene generato automaticamente.

Questo è un grosso lavoro per un elemento, ma è soprattutto codice pronto all'uso (boilerplate); possiamo facilmente copiarlo o possiamo cambiarlo. Possiamo trovare il resto del modulo nell'Appendice B.

Creare un validatore

Nell'esempio abbiamo solamente un elemento richiesto (la e-mail), così è un semplice pezzo di Python chiamato validEmail.vpy ad eseguire il lavoro. I contenuti di questo script sono i seguenti:

email = context.REQUEST.get('email_address', None)
if not email:
    state.setError('email_address', 'Email is required',
                    new_status='failure')
if state.getErrors():
    state.set(portal_status_message='Please correct the errors.')
return state

Se nessun indirizzo e-mail può essere trovato, questo script aggiunge un errore al dizionario degli errori con la chiave email_address ed un messaggio. Questa chiave è usata nel modello di pagina per vedere se è stato generato un errore in quel particolare campo.

Eseguire lo script

Questo esempio ha un semplice script e-mail che restituisce i valori (che sono già convalidati) e realizza un indirizzo e-mail da questi. Questo è un oggetto Controller Python Script; proprio come un oggetto Script (Python) standard con una variabile aggiuntiva state, e, come un Controller Page Template, possiamo fornirgli azioni per quando esso viene invocato:

mhost = context.MailHost
emailAddress = context.REQUEST.get('email_address')
administratorEmailAddress = context.email_from_address
comments = context.REQUEST.get('comments')
 
# the message format, %s will be filled in from data
message = """
From: %s
To: %s
Subject: Website Feedback
 
%s
URL: %s """
 
# format the message
message = message % (
    emailAddress,
    administratorEmailAddress,
    comments,
    context.absolute_url())
 
mhost.send(message)

Ora abbiamo visto una semplice script per spedire posta elettronica. Questa è un comune script che vedremo spesso. Fondamentalmente, gli oggetti MailHost in Plone prendono una e-mail come una stringa, affinché sia conforme alla specifica Request for Comment (RFC) per la e-mail che ha gli indirizzi From e To.

In questa e-mail, prende l'indirizzo dell'amministratore che abbiamo specificato nella configurazione del portale e spedisce la posta elettronica a quella persona. L'unica parte addizionale di questo script è aggiungere L'impostazione dello stato. Questo prepara un messaggio che fornisce alcuni feedback all'utente:

screenMsg = "Comments sent, thank you."
state.setKwargs( {'portal_status_message':screenMsg} )
return state
Assemblare le tre parti

Al momento, comunque viene richiesta la e-mail della persona , così potremo rispondere ai suoi commenti.

Costruire il modulo

System Message: INFO/1 (<string>, line 1934); backlink

Duplicate implicit target name: "costruire il modulo".

Il modulo è la parte più grande e più complicata di questa procedura, soprattutto perché v'è tanto lavoro da fare per supportare la gestione degli errori. Questo modulo è un oggetto Controller Page Template chiamato feedbackForm. Per assicurarsi che sia contenuto nel modello principale, inizieremo il modulo in un metodo standard:

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xml:lang="en-US"
    lang="en-US"
    i18n:domain="plone"
    metal:use-macro="here/main_template/macros/master">
  <body>
    <div metal:fill-slot="main"
         tal:define="errors options/state/getErrors;">

Un'aggiunta opzionale qui è errors options/state/getErrors che posiziona qualsiasi cosa e tutti gli errori all'interno della variabile locale per usi futuri.

A causa del requisito del modulo di poter far riferimento indietro a se stesso, viene impostata questa azione in TAL, con l'espressione template/id. Questo percorso estrae l'ID del modello e lo inserisce nell'azione, così che questo percorso funzionerà sempre, anche se rinominiamo il modello. Notiamo che stiamo aggiungendo anche marcatori i18n per essere sicuri che questo modulo possa essere localizzato:

<form method="post"
      tal:attributes="action template/id;">
 
<legend i18n:translate="legend_feedback_form">
    Website Feedback
</legend>

Ciò che segue è l'inizio della riga dell'indirizzo e-mail. Definiamo qui una variabile chiamata error_email_address che posizionerà una stringa di errore se v'è una stringa appropriata nel dizionario errors. Quel valore di errore sarà generato dal validatore qualora dovesse generarsi un errore là:

<div class="field"
     tal:attributes="class python:test(error_email_address,
                                       'field error', 'field')">
     tal:define="error_email_address errors/email_address|nothing;">

La seguente è l'etichetta per il campo indirizzo e-mail. In questa etichetta includeremo un div per il testo di aiuto. L'elemento span diventa ora il familiare punto rosso vicino all'etichetta così che l'utente sappia che è richiesto:

<label i18n:translate="label_email_address">Your email address</label>
<span class="fieldRequired" title="Required">(Required)</span>
<div class="formHelp"
     i18n:translate="label_email_address_help">
     Enter your email address.
</div>

Successivamente aggiungeremo l'elemento attuale:

    <div tal:condition="error_email_address">
        <tal:block i18n:translate=""
                   content="error_email_address">Error
        </tal:block>
    </div>
    <input type="text" name="email_address"
           tal:attributes="tabindex tabindex/next;
                           value request/email_address|nothing" />
</div>

All'inizio di questo blocco, esamineremo per vedere se c'è un errore. Se c'è, la classe per l'elemento cambiato sarà la classe field error; questa classe mostrerà una bel riquadro arancio attorno al campo. Successivamente, se è accaduto un errore in questa zona (come abbiamo già sperimentato), sarà visualizzato il corrispondente messaggio. Finalmente, mostrerà l'elemento del modulo, e se v'è un valore per email_address già nella richiesta, popolerà l'elemento del modulo con quel valore.

Il tabindex è un utile strumento di Plone. Contiene un numero sequenziale che viene incrementato per ogni elemento, e ogni volta imposta un nuovo valore HTML tabindex per ciascun elemento nel modulo. Questa è una bella caratteristica per l'interfaccia utente; vuole dire che ciascun elemento del modulo può essere salvato senza doversi preoccupare di ricordare il numero tabindex perché questo viene generato automaticamente.

Questo è un grosso lavoro per un elemento, ma è soprattutto codice pronto all'uso (boilerplate); possiamo facilmente copiarlo o possiamo cambiarlo. Possiamo trovare il resto del modulo nell'Appendice B.

Creare un validatore

System Message: INFO/1 (<string>, line 2038); backlink

Duplicate implicit target name: "creare un validatore".

Nell'esempio abbiamo solamente un elemento richiesto (la e-mail), così è un semplice pezzo di Python chiamato validEmail.vpy ad eseguire il lavoro. I contenuti di questo script sono i seguenti:

email = context.REQUEST.get('email_address', None)
if not email:
    state.setError('email_address', 'Email is required',
                    new_status='failure')
if state.getErrors():
    state.set(portal_status_message='Please correct the errors.')
return state

Se nessun indirizzo e-mail può essere trovato, questo script aggiunge un errore al dizionario degli errori con la chiave email_address ed un messaggio. Questa chiave è usata nel modello di pagina per vedere se è stato generato un errore in quel particolare campo.

Eseguire lo script

System Message: INFO/1 (<string>, line 2060); backlink

Duplicate implicit target name: "eseguire lo script".

Questo esempio ha un semplice script e-mail che restituisce i valori (che sono già convalidati) e realizza un indirizzo e-mail da questi. Questo è un oggetto Controller Python Script; proprio come un oggetto Script (Python) standard con una variabile aggiuntiva state, e, come un Controller Page Template, possiamo fornirgli azioni per quando esso viene invocato:

mhost = context.MailHost
emailAddress = context.REQUEST.get('email_address')
administratorEmailAddress = context.email_from_address
comments = context.REQUEST.get('comments')
 
# the message format, %s will be filled in from data
message = """
From: %s
To: %s
Subject: Website Feedback
 
%s
URL: %s """
 
# format the message
message = message % (
    emailAddress,
    administratorEmailAddress,
    comments,
    context.absolute_url())
 
mhost.send(message)

Ora abbiamo visto una semplice script per spedire posta elettronica. Questa è un comune script che vedremo spesso. Fondamentalmente, gli oggetti MailHost in Plone prendono una e-mail come una stringa, affinché sia conforme alla specifica Request for Comment (RFC) per la e-mail che ha gli indirizzi From e To.

In questa e-mail, prende l'indirizzo dell'amministratore che abbiamo specificato nella configurazione del portale e spedisce la posta elettronica a quella persona. L'unica parte addizionale di questo script è aggiungere L'impostazione dello stato. Questo prepara un messaggio che fornisce alcuni feedback all'utente:

screenMsg = "Comments sent, thank you."
state.setKwargs( {'portal_status_message':screenMsg} )
return state
Assemblare le tre parti

System Message: INFO/1 (<string>, line 2113); backlink

Duplicate implicit target name: "assemblare le tre parti".

Al momento, comunque esistono tre entità separate: un modulo, un validatore ed uno script d'azione. Ora hanno bisogno di essere assemblati per formare una concatenazione, per questo dobbiamo ritornare all'oggetto Controller Template. Clicchiamo la scheda Validator, e digitiamo un nuovo validatore che punti allo script validEmail. Aggiungiamo inoltre un'azione di successo per quando il processo abbia correttamente traversato lo script sendEmail. Sullo script sendEmail, ora possiamo aggiungere un altro traversamento di ritorno a feedbackForm così che dopo che viene eseguita correttamente sendEmail, l'utente sarà rispedito alla pagina originale.

Nota

Uno script di validazione e-mail molto più completo appare in Plone chiamando validate_emailaddr che controlla che la posta elettronica sia nel formato corretto. Se vogliamo usare invece questo script, possiamo puntare il validatore ad esso.

Questo è fatto! Ora dovremmo essere capaci di esaminare il modulo sul sito web del libro. Per renderlo più facile, abbiamo fatto una scheda FeedBack, che punta al modello feedbackForm e da qui ora possiamo fornire un feedBack su questo libro!


Andy McKay: The Definitive Guide to Plone. Apress 2004
This online version was generated using the 'PloneBook' product from docs.neuroinf.de/products.
It was last updated by
lallo on 2005-04-09 07:08 from the cvs source using
svn export http://docit.bice.dyndns.org/Plone/PloneBook2/it LibroPlone.

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: