Bookmark and Share
Document Actions

Customizzazione per sviluppatori

Note: Return to tutorial view.

Questo tutorial offre una panoramica sulla personalizzazione di differenti aspetti di Plone 3, come skin layers, viste, viewlets, portlet renderer.

Introduzione

Di cosa stiamo parlando?

Plone ha una radicata cultura per quanto riguarda la customizzazione. Stiamo parlando del fatto che quasi ogni aspetto di Plone può essere riscritto e personalizzato, senza toccare l'originale codice sorgente. Questo tutorial prenderà in esame  i modi principali in cui è possibile per gli sviluppatori customizzare componenti visuali di Plone.

Gli amministratori di un sito hanno la possibilità di personalizzare diversi templates direttamente attraverso il web, utilizzando o lo strumento portal_skins (per i tradizionali skin layers del CMF), o lo strumento portal_view_customizations (per le viste, i viewlets e i portlets di Zope 3). La differenza tra i due strumenti verrà spiegata più avanti. La customizzazione attraverso il web è adatta a piccoli interventi sulla grafica o esperimenti superficiali, ma non è la via giusta per sviluppare un progetto. Questo tutorial si concentra su una customizzazione filesystem-based, ovvero come se uno sviluppatore dovesse lavorare su un prodotto aggiuntivo separato.

Illustreremo i principali tipi di customizzazione utilizzando un pacchetto chiamato example.customization (documento in inglese). Puoi scaricarlo e installarlo in un ambiente Python locale o in un buildout. Il buildout tutorial ti illustrerà come installare un prodotto egg-based. Non dimenticare di installare uno slug ZCML se vuoi esaminare il prodotto.

Se vuoi semplicemente dare un'occhiata all'ultima versione del codice sorgente, puoi cercare in the Collective Subversion repository (in inglese).

Customizzazione degli skin layers

Alla vecchia maniera

Tradizionalmente, la customizzazione in Zope si effettuava tramite acquisizione. Per esempio, uno sviluppatore poteva posizionare una page template in una cartella e acquisirla per generare un oggetto in una sottocartella /foo/bar. Se lo sviluppatore desiderava un rendering differente per l'oggetto ma solo in /foo/bar/baz, poteva creare un template con lo stesso nome (id) in quella cartella, che avrebbe avuto precedenza.

Dato che l'idea di Plone (e del CMF) è proprio quella di dare la libertà agli utenti di creare le loro proprie gerarchie di contenuti, includere templates e codice all'interno della struttura del sito diventerebbe una fonte di confusione e di disordine. E' per questo motivo che gli sviluppatori hanno inventato lo strumento portal_skins. Se visualizzi questo strumento nella ZMI troverai una o più skins nel tab Properties, in particolare Plone Default. Una skin, (che tendenzialmente chiameremo tema, per evitare confusione) è semplicemente una lista ordinata di skin layers. Gli skin layers elencate nel tab Properties corrispondono a cartelle contenute in portal_skins. Quando Zope cerca una risorsa, (logo.jpg, per esempio) sfoglierà la cartella corrente (current folder), e successivamente la parent folders, fino alla root del sito. Se la risorsa non viene trovata, il CMF la cercherà negli skin layers del tema corrente, partendo dalla cima dell'elenco e scendendo fino a quando non trova ciò che cerca.

Notiamo subito il fatto che custom è il primo termine della lista. E' così che funziona la customizzazione attraverso il web. Copia un elemento in questa cartella, e la nuova copia avrà la precedenza su tutti gli altri. Notiamo anche che tutte le altre cartelle sono in sola lettura, perché non sono cartelle dello ZODB: sono Filesystem Directory Views, cartelle che riflettono i files contenuti in una particolare cartella nel filesystem.

Perciò, per creare un prodotto riscrivendo alcune risorse in uno skin layer che lavora con Plone bisogna:

  • Registrare una nuova filesystem directory view
  • Inserirla nella lista degli skin layers per la skin corrente, che normalmente si trova appena sotto la cartella custom
  • Copiare la risorsa pertinente nella nuova directory dello skin layer, mantenendo lo stesso nome
  • Personalizzare la copia appena creata

Nota Bene: Alcune risorse avranno un file .metadata associato. In questo caso devi copiare anche il file .metadata per customizzare la risorsa.

Tradizionalmente, tutte le risorse di Plone - immagini, fogli di stile, file Javascript, page templates e script Python come form handlers - erano contenute negli skin layers. Tuttavia, come puoi immaginare, tutto ciò diventava un po' "ingombrante" man mano che Plone cresceva, dato che il meccanismo degli skin layers presuppone che ogni risorsa abbia un nome unico (id). Ancora, la maggior parte delle principali visualizzazioni, immagini e fogli di stile che sono parte di Plone sono contenute in skin layers che hanno nomi prefissati e che iniziano con plone_, come plone_images, o plone_templates. Nel filesystem, li troverai all'interno di CMFPlone/skins. Puoi navigare tra le sottocartelle di queste cartelle per trovare le diverse risorse.

In generale, se una risorsa è menzionata nel codice di Plone (per esempio da un url, un'azione in portal_actions o un alias in portal_types) e non è prefissata con @@ (come in @@view) oppure ++resource++ (come ++resource++stylesheet.css), allora è probabile che si trovi in uno skin layer.

 

Registrare e installare un nuovo skin layer filesystem-based

Nel prodotto example.costumization, abbiamo una subdirectory skins/, con una singola directory skin layer, example_customization. Per essere in grado di registrarla come una filesystem directory view, dobbiamo comunicare al CMF la directory dei top-level skins. Possiamo farlo nel file __init__.py del pacchetto: 

from Products.CMFCore.DirectoryView import registerDirectory

GLOBALS = globals()
registerDirectory('skins', GLOBALS)

def initialize(context):
    """Intializer called when used as a Zope 2 product."""

Perché funzioni, dobbiamo inoltre assicurarci che il pacchetto sia un prodotto Zope 2. Se è nel magico namespace Products.* (ad esempio un tipico prodotto che si trova nella directory Products in un'istanza Zope), tutto ciò accadrà automaticamente. Se stiamo usando un prodotto egg-based in un namespace differente, dobbiamo aggiungerlo nel configure.zcml del pacchetto:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    xmlns:five="http://namespaces.zope.org/five"
    i18n_domain="example.customization">

   <five:registerPackage package="." initialize=".initialize" />

   ...

</configure>

Ora dobbiamo creare la directory view e installarla per la skin corrente quando il prodotto viene installato in un sito Plone. Possiamo farlo usando GenericSetup. Prima di tutto, dobbiamo registrare un nuovo profilo di estensione in modo che il prodotto sia installabile. Agiamo in configure.zcml, come segue:

  <genericsetup:registerProfile
      name="default"
      title="Example customizations"
      directory="profiles/default"
      description='Install various customizations from the example.customization package'
      provides="Products.GenericSetup.interfaces.EXTENSION"
      />

Poi usiamo l'import handler skins.xml per configurare lo strumento portal_skins:

<?xml version="1.0"?>
<!-- This file holds the setup configuration for the portal_skins tool -->

<object name="portal_skins">

 <object name="example_customization"
    meta_type="Filesystem Directory View"
    directory="example.customization:skins/example_customization"/>

 <skin-path name="*">
  <layer name="example_customization"
     insert-after="custom"/>
 </skin-path>

</object>

Possiamo registrare quante directory views vogliamo.

Per fare un breve esempio, posizionando un logo vivace in skins/example_customizations/logo.jpg, riscrivendo logo.jpg da CMFPlone/skins/plone_images, quando il prodotto sarà installato, vedremo il nuovo logo al posto di quello di default. Potrebbe essere necessario ricaricare la pagina.

Layer di tipo browser

Customizzazione in stile Zope 3

Quando Zope 3 fu progettato, molti elementi importati dal CMF vi sono stati incorporati e la tecnologia è stata raffinata. L'idea di un unico, globale namespace in cui la customizzazione era possibile solo attraverso l'id è stata soppiantata dalla nozione di risorse identificate da una nome, che vengono registrate per un tipo di contesto (così che la vista @@view richiamata su una pagina appare in modo differente rispetto alla vista con lo stesso nome richiamata su una cartella, per esempio), e possibilmente registrate per un layer di tipo browser. Un layer di tipo browser Zope 3 è simile in quanto a scopo a uno skin layer del CMF, ma è implementato in modo diverso.

Un layer di tipo browser non è altro che un'interfaccia marcatore applicata alla request mediante attraversamento. Una risorsa di tipo browser Zope 3 è flessibile e adattabile a seconda del contesto e della request, e quando una request è marcata con una particolare interfaccia, il Component Architecture può trovare un adapter specifico in una visualizzazione registrata in quel layer particolare. Se tutto ciò ti sembra non abbia molto senso, non ti preoccupare. Ciò che ti serve sapere è:

  • Definiamo un'interfaccia marcatore (ovvero una semplice classe, senza corpo, derivata da zope.interface.Interface) che rappresenta il layer di tipo browser.
  • Ci assicuriamo che l'interfaccia sia applicabile al request automaticamente, in modo che attivi il layer browser nel nostro sito.
  • Registriamo (usando ZCML) le risorse di tipo browser, le visualizzazioni, i viewlets e i portlets per questo nuovo layer, lasciando che si sovrascrivano a quelle di default (che sono implicitamente registrate per il layer di tipo browser di default).

In Plone ci sono due modi principali con cui attivare un'interfaccia layer di tipo browser personalizzata. Il primo è usare i meccanismo di plone.theme. Questo pacchetto, distribuito con Plone di default, ci permette di collegare un layer di tipo browser con un tema particolare (una skin) in portal_skins (da non confondere con uno skin layer). Quando il tema viene attivato in portal_skin, il layer è abilitato. Ciò è utile per prodotti che installano un intero nuovo tema in Plone. E' invece meno utile per prodotti generici, dato che viene attivato solo un plone.theme alla volta. L'importante è che l'installazione del layer sia cumulabile, in modo da poter installare una qualsiasi quantità di prodotti, ognuno con il suo proprio layer.

Per far ciò, bisogna utilizzare il pacchetto plone.browserlayer. Non fa parte di Plone 3.0 (ma le sue funzionalità faranno parte di Plone 3.1). Perciò, bisogna installarlo assieme al resto.

Dato che example.customization è un prodotto egg-based, possiamo ottenere plone.browserlayer semplicemente richiedendolo in setup.py. Ciò è possibile modificando la definizione install_requires in quel file:

install_requires=[
'setuptools',
'plone.browserlayer',
],

In questo modo, il buildout (o easy_install) scaricherà e installerà plone.browserlayer from PyPI.

Dobbiamo anche installare plone.browserlayer come prodotto. In assenza del supporto per le dipendenze di GenericSetup (pianificato per Plone 3.1), possiamo farlo usando un file Extensions/Install.py all'interno del nostro prodotto, con il seguente codice:

import transaction
from Products.CMFCore.utils import getToolByName

PRODUCT_DEPENDENCIES = ('plone.browserlayer',)

EXTENSION_PROFILES = ('example.customization:default',)

def install(self, reinstall=False):
portal_quickinstaller = getToolByName(self, 'portal_quickinstaller')
portal_setup = getToolByName(self, 'portal_setup')

for product in PRODUCT_DEPENDENCIES:
if reinstall and portal_quickinstaller.isProductInstalled(product):
portal_quickinstaller.reinstallProducts([product])
transaction.savepoint()
elif not portal_quickinstaller.isProductInstalled(product):
portal_quickinstaller.installProduct(product)
transaction.savepoint()

for extension_id in EXTENSION_PROFILES:
portal_setup.runAllImportStepsFromProfile('profile-%s' % extension_id, purge_old=False)
product_name = extension_id.split(':')[0]
portal_quickinstaller.notifyInstalled(product_name)
transaction.savepoint()

In questo modo, plone.browserlayer ("Local browser layer support") sarà installato quando viene installato example.customization.

Risorse di tipo browser Zope 3

Come personalizzare immagini e fogli di stile registrati come browser layer Zope 3

Zope 3 permette di registrare risorse di tipo browser, specialmente immagini e fogli di stile, con un namespace specifico. Per esempio, se registri una risorsa immagine con il nome wibble.gif, la risorsa di tipo browser avrà come URL http://yoursite.com/++resource++wibble.gif. Questo serve per far uscire la risorsa dal piatto namespace globale. Questi oggetti non hanno molta utilità da soli. Normalmente vengono installate in un registro come portal_css, portal_javascripts, o portal_kss, o usate in action o altri collegamenti.

Come tutti gli altri componenti browser Zope 3, le risorse sono registrate con una direttiva ZCML nel namespace browser che contiene, tra gli altri, un attributo layer. Il layer dovrebbe essere un'interfaccia.

Per fare un esempio, prendiamo il pacchetto plone.app.iterate. Nel suo file browser/configure.zcml, troverai la seguente definizione:

<browser:resource
        name="checkout.png"
        image="checkout.png"
        />

Questo definisce una risorsa, ++resource++checkout.png, che è usata nell'azione di check-out quando Iterate viene installato. Se volessimo cambiare quest'immagine piuttosto piacevole, in una colorata di un orrendo rosa, potremmo personalizzarla per il layer IExampleCustomization con un'immagine personalizzata chiamata ugly_checkout.png nella nostra directory browser/; per far ciò dovremo aggiungere ciò che segue nel browser/configure.zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    i18n_domain="example.customization">

    ...

    <browser:resource
        name="checkout.png"
        image="ugly_checkout.png"
        layer=".interfaces.IExampleCustomization"
        />

    ...

</configure>

Nota che senza l'attributo del layer, avremmo un conflitto di configurazione con l'originale definizione ++resource++checkout.png.

Viste browser in stile Zope 3

Personalizzare le viste e le pagine di tipo browser

Le viste browser sono analoghe alle page template (i file .pt) degli skin layers. Tuttavia, sono registrati in ZCML per un particolare tipo di contesto. Ecco un esempio, da plone.app.content.browser:

<browser:page
        for="*"
        class=".reviewlist.FullReviewListView"
        name="full_review_list"
        template="full_review_list.pt"
        permission="cmf.ReviewPortalContent" />

Ciò ci dice che la vista è chiamata @@full_review_list (il disambiguatore @@ non è strettamente necessario, ma rende chiaro il fatto che si tratta di una vista e non, per esempio, di un elemento di contenuto). E' disponibile per gli utenti che hanno il permesso di Revisionare i contenuti del portale, e per qualsiasi contesto (for="*"). L'implementazione è eseguita in una classe chiamata FullReviewListView in reviewlist.py nella directory corrente, che è implicitamente riscritta da una template chiamata full_review_list.pt.

Un modo di personalizzare ciò che abbiamo visto è di fare un overriding per un contesto più specifico (o differente). Il contesto "*" è il più generale (ciò significa zope.interface.Interface). Se avessimo una nostra implementazione per una pagina standard (identificata dall'interfaccia Products.ATContentTypes.interface.document.IATDocument), potremmo far ciò con la seguente dichiarazione nel ZCML del nostro pacchetto:

<browser:page
        for="Products.ATContentTypes.interface.document.IATDocument"
        class="plone.app.content.browser.reviewlist.FullReviewListView"
        name="full_review_list"
        template="document_full_review_list.pt"
        permission="cmf.ReviewPortalContent" />

Abbiamo scelto in quest'esempio di usare il layer di default (ovvero, non abbiamo specificato un layer), ma un tipo di contesto nuovo (l'attributo for, che si riferisce al tipo di contesto in questione). Avremmo potuto inserire una nuova classe (o nessuna), ma qui usiamo la classe di visualizzazione di default da plone.app.content. Da notare che abbiamo convertito il path del modulo da relativo ad assoluto, dato che ora ci troviamo in un pacchetto diverso! Cambiamo il template, che di nuovo è relativo alla directory in cui il file ZCML è stato trovato. Avremmo potuto scegliere di usare solo una classe, o solo un template, se avesse avuto più senso.

Alternativamente, potremmo effettuare queste modifiche con i layer (e così assicurarci che la customizzazione abbia effetto solo quando il prodotto è installato). Ecco un altro esempio, questa volta rimpiazziamo la visualizzazione di default con una nuova, utilizzando la vecchia classe con un nuovo template.

<browser:page
        for="*"
        class="plone.app.content.browser.reviewlist.FullReviewListView"
        name="full_review_list"
        template="standard_full_review_list.pt"
        layer=".interfaces.IExampleCustomization"
        permission="cmf.ReviewPortalContent" />

Da notare che queste due dichiarazioni possono coesistere. In questo caso, di default si userà standard_full_review_list.pt, ma la visualizzazione in una pagina userà document_full_review_list.pt, dato che è più specifico. Ovviamente, si potrebbero usare entrambi modi - un'operazione di override per un contesto e un layer. Il risultato sarebbe che l'override del contesto sarebbe attivo solo quando il layer viene installato.

 

Trovare le viste

Dato che le viste sono sparse tra i vari elementi dei pacchetti, può essere a volte difficile identificare da dove provengano. Spesso, fare una ricerca tra i file nei propri egg è una soluzione veloce ma pericolosa. Oppure, puoi anche agire nei seguenti modi:

  • Vai in portal_view_customizations nel ZMI, dove troverai viste, viewlet e portlet, raggruppati assieme in base al tipo di contesto. Posiziona il cursore sul titolo della visualizzazione e dovresti vedere il nome di un pacchetto e di un template in un piccolo riquadro.
  • Aggiungi /@@zptviews.html al fondo di un particolare URL, per esempio, http://localhost:8080/plone/@@zptviews.html. Ciò mostra le viste (ma non viewlet o portlet) assieme con i loro templates, interfaccia del contesto e file sorgente ZCML.

Le viewlet

Personalizzare le viewlet

Le viewlet sono frammenti di pagina, scritte similmente ad una vista ma organizzate all'interno della pagina attraverso un viewlet manager. Il viewlet manager è responsabile di trovare, ordinare, filtrare e renderizzare le sue viewlet. La maggior parte delle viewlet standard di Plone si trovano nel pacchetto plone.app.layout. Questo è un estratto del suo file viewlets/configure.zcml:

<browser:viewlet
        name="plone.colophon"
        for="*"
        manager=".interfaces.IPortalFooter"
        template="colophon.pt"
        permission="zope.Public"
        />

La denominazione è unica all'interno del portlet manager, che è identificato da un'interfaccia (e definito precedentemente nello stesso file, usando come direttiva <browser:viewletManager />). Qui, stiamo utilizzando una viewlet solo-template, definita per ogni contesto, e non protetta da alcun permesso particolare. E' possibile definire una classe invece del/in aggiunta del template.

Come probabilmente hai intuito, è possibile customizzare la viewlet sia registrando una nuova viewlet con lo stesso nome, nello stesso manager, ma per un contesto più specifico, sia alternativamente, registrandone una nuova con lo stesso nome, nello stesso manager, usando un layer personalizzato. Tutto ciò potrebbe apparire così:

<browser:viewlet
        name="plone.colophon"
        for="*"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        template="funny_colophon.pt"
        permission="zope.Public"
        layer=".interfaces.IExampleCustomization"
        />

Nota come abbiamo trasformato il path da relativo all'interfaccia viewlet manager IPortalFooter in assoluto e come abbiamo specificato un nuovo template (che deve trovarsi nella directory in cui è localizzato il nostro nuovo file ZCML).

Per concludere, è possibile usare l'attributo view per specificare l'interfaccia o la classe della vista della pagina. Con questo tipo di customizzazione, possiamo ottenere una viewlet che appare in modo differenete nelle diverse pagine. L'uso più comune di questa modalità in Plone è registrare viewlets che sono mostrate solo nella visualizzazione principale di una pagina, usando la speciale interfaccia marcatore IViewView, che è applicata alla visualizzazione corrente durante attraversamento se l'utente sta usando il tab Visualizza. Ecco un esempio da plone.app.layout.viewlets:

<browser:viewlet
        name="plone.comments"
        for="Products.CMFCore.interfaces.IContentish"
        manager=".interfaces.IBelowContent"
        view="..globals.interfaces.IViewView"
        class=".comments.CommentsViewlet"
        permission="zope2.View"
        />

Come per le viste, puoi utilizzare le varie parametrizzazioni for, layer e view in modo combinato.

 

Trovare le viewlet

Lo strumento portal_view_customizations ti mostrerà le registrazioni delle viewlet (e i viewlet manager per cui sono registrate). Come per le viste, puoi spostare il cursore sopra il titolo della viewlet per vedere dove è stata registrata. Per scoprire il nome di una viewlet particolare, puoi utilizzare la vista @@manage-viewlets, per esempio http://localhost:8080/plone/@@manage-viewlets.

Per ulteriori informazioni sulle viewlets, leggi Modificare le viewlet nel main_template.

Portlet renderers

Personalizzare il portlet renderer su Plone 3

Le portlets in Plone 3 non sono molto differenti dalle viewlet, ma consistono in un componente persistente, il portlet assignment, contenente la configurazione del portlet, che viene generato usando un portlet renderer. Per la personalizzazione dell'aspetto, devi agire sul portlet renderer.

Dato che i portlet renderer possono incorporare una logica complessa e poggiare su qualche specifica tecnica di registrazione, Plone offre una nuova direttiva ZCML - <plone:portletRenderer /> - che rende la configurazione dei portlet renderers un po' più semplice. Diversamente dalla customizzazione che abbiamo visto per risorse di tipo browser standard, un portlet renderer personalizzato solo-template userà in realtà la classe di visualizzazione renderer (come per la variabile viewlet) che è stata usata per registrare il portlet renderer originale.

In plone.app.portlets, sotto portlets/configure.zcml, puoi trovare questa definizione per il portlet Recent:

<plone:portlet
name="portlets.Recent"
interface=".recent.IRecentPortlet"
assignment=".recent.Assignment"
renderer=".recent.Renderer"
addview=".recent.AddForm"
editview=".recent.EditForm"
/>

Supponiamo di volerlo personalizzare con un nuovo template. Il template di default (come citato dalla classe Renderer in recent.py) è recent.pt nella stessa directory. Copiandolo nel nostro pacchetto, possiamo agire come segue:

<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="example.customization">

<!-- We need to include the package of the portlets we are customising -->
<include package="plone.app.portlets" />

...

<plone:portletRenderer
portlet="plone.app.portlets.portlets.recent.IRecentPortlet"
layer=".interfaces.IExampleCustomization"
template="mostly_recent.pt"
/>

</configure>

Notiamo che stiamo esplicitamente includendo il pacchetto plone.app.portlets nell'elaborazione ZCML, dato che stiamo ora usando implicitamente la sua classe portlet renderer. Definiamo allora un nuovo portlet renderer con un template personalizzato, per il nostro nuovo layer. E' anche possibile usare l'attributo for per customizzare in un particolare tipo di contesto, o l'attributo view per personalizzare in una particolare visualizzazione, come con le viewlets.

Al posto di usare un template personalizzato, potremmo usare una classe renderer completamente nuova. Guardane l'implementazioni in plone.app.portlets per capire come funziona. Da notare infine che diversamente dalle viste e dalle viewlet, i portlet renderer supportano o template o classi, ma non entrambi.

Credits

Autori, provenienza e altri dati su questo documento.

Questo documento è stato realizzato da:

  • Fabrizio reale

Fonti e contributi:

Questo documento è una libera traduzione del testo originale di Martin Aspeli "Customization for developers": http://plone.org/documentation/tutorial/customization-for-developers