Bookmark and Share
Document Actions

Modificare le viewlet nel main_template

Note: Return to tutorial view.

Questo tutorial spiega cosa sono le viewlet e i viewlet manager e come usarli per personalizzare l'interfaccia di Plone 3 e per creare un nuovo tema (skin).

Introduzione

Questo tutorial spiega come le viewlet possano essere disposte, nascoste o aggiunte (da un prodotto sul filesystem). Incominciamo.

Questo tutorial è una libera traduzione di http://plone.org/documentation/tutorial/customizing-main-template-viewlets/tutorial-all-pages di David Convent.

Plone 3 utilizza i componenti viewlet di Zope3 invece delle tradizionali macro METAL.

Questo tutorial spiega cosa sono le viewlet e i viewlet manager e come si possono usare per personalizzare l'interfaccia di Plone 3 e per creare un nuovo tema (skin).

La scopo di questo documento è mostrare come fare ciò utilizzando un prodotto Python sul filesystem anche se tutto può essere fatto mediante l'accesso via web.

Cominciamo guardando il codice del main_template di Plone, che è il template che monta le varie aree della pagina attorno al contenuto (es: testata, piede, le due colonne laterali, ecc...):

Nel main_template.pt, che si trova in $INSTANCE_HOME/Products/CMFPlone/skins/plone_templates/, possiamo vedere che il tag <div /> con id portal-top contiene solo tre righe di codice. Invece nelle versioni di Plone precedenti alla 3.0 conteneva una lista di chiamate alle macro che generavano le site actions, il box di ricerca, il logo, ecc...

Ecco come appariva quella porzione di codice nelle vecchie versioni del main_template.pt:

<div id="portal-top" i18n:domain="plone">

<div id="portal-header">
  <p class="hiddenStructure">
    <a accesskey="2"
       tal:attributes="href string:${current_page_url}#documentContent"
       i18n:translate="label_skiptocontent">Skip to content.</a> |

    <a accesskey="6"
       tal:attributes="href string:${current_page_url}#portlet-navigation-tree"
       i18n:translate="label_skiptonavigation">Skip to navigation</a>
  </p>

     <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>

     <a metal:use-macro="here/global_logo/macros/portal_logo">
       The portal logo, linked to the portal root
     </a>

     <div metal:use-macro="here/global_skinswitcher/macros/skin_tabs">
       The skin switcher tabs. Based on which role you have, you
       get a selection of skins that you can switch between.
     </div>

     <div metal:use-macro="here/global_sections/macros/portal_tabs">
       The global sections tabs. (Welcome, News etc)
     </div>
  </div>

  <div metal:use-macro="here/global_personalbar/macros/personal_bar">
     The personal bar. (log in, logout etc...)
   </div>

   <div metal:use-macro="here/global_pathbar/macros/path_bar">
     The breadcrumb navigation ("you are here")
   </div>
</div>

 Mentre ora è così:

<div id="portal-top" i18n:domain="plone">
    <div tal:replace="structure provider:plone.portaltop" />
</div>

L'espressione TAL provider è un'espressione di Zope 3 utilizzata per cercare un content provider in una pagina.

In Zope 3 (a partire dalla 3.2) un content provider è un componente che genera una parte di una pagina HTML.

Come le viste di Zope 3, i content provider sono dei multi adapter che adattano il context e la request, e in aggiunta adattano la vista da cui sono richiamati.

Le viewlet sono content provider, piccoli componenti di una pagina che generano un piccolo pezzo di codice HTML.

Nei template di Plone le viewlet non sono richiamate direttamente, ma, per permetterne una gestione organizzata e flessibile, sono aggregate nei viewlet manager.

Anche i viewlet manager sono content provider che restituiscono l'insieme delle viewlet registrate.

Nelle prossime sezioni verrà mostrato cosa sono le viewlet e i viewlet manager, cosa vi si possa fare e quali sono i vantaggi di questo approccio. Per approfondire questo argomento puoi consultare il libro di Philipp Web Component Development with Zope 3 - 2d. edition, chapter 10.4.

 

 

 

Registrazione delle viewlet in Plone 3.0

Come registrare le viewlet in una installazione standard di Plone

Così come tutti i template di Plone 3 che generano codice HTML da componenti Zope 3, anche il main template non richiama direttamente le viewlet bensì i viewlet manager.

Il vantaggio di questo approccio è che ora si possono gestire le varie sezioni delle pagine di Plone senza modificare il main_template.pt.

I viewlet manager del main_template.pt e le relative viewlet sono definite nel package plone.app.layout, nel modulo viewlets. Questo package, presente in ogni installazione di Plone, si trova in $INSTANCE_HOME/lib/python/plone/app/layout/.

Nel file configure.zcml del modulo viewlets ($INSTANCE_HOME/lib/python/plone/app/layout/viewlets/configure.zcml) vi sono le definizioni dei viewlet manager, seguite dalla registrazione delle loro viewlets.

Per esempio il viewlet manager plone.portaltop fornisce l'interfaccia IPortalTop:

<browser:viewletManager
    name="plone.portaltop"
    provides=".interfaces.IPortalTop"
    permission="zope2.View"
    class="plone.app.viewletmanager.manager.OrderedViewletManager"
    />

IPortalTop è un'intefaccia marker (marcatore) per i componenti che gestiscono le viewlet plone.header, plone.personal_bar e plone.path_bar.

Nella dichiarazione di browser:viewlet vengono definite le associazioni tra le viewlet e i viewlet manager, per esempio:

<!-- The portal header -->
<browser:viewlet
  name="plone.header"
  manager=".interfaces.IPortalTop"
  template="portal_header.pt"
  permission="zope2.View"
  />

...

<!-- The personal bar -->
<browser:viewlet
    name="plone.personal_bar"
    manager=".interfaces.IPortalTop"
    class=".common.PersonalBarViewlet"
    permission="zope2.View" 
    />

<!-- The breadcrumbs -->
<browser:viewlet
    name="plone.path_bar"
    manager=".interfaces.IPortalTop"
    class=".common.PathBarViewlet"
    permission="zope2.View" 
    />

Da questo ZCML si nota che la definizione della viewlet plone.header richiama direttamente un template (portal_header.pt), mentre le altre viewlet gestite da IPortalTop sono associate a classi.

In quel template vi è semplicemente la chiamata di un altro viewlet manager, il plone.portalheader:

<div id="portal-header">
    <div tal:replace="structure provider:plone.portalheader" />
</div>

Sempre dal configure.zcml si può notare che plone.portalheader, che implementa l'interfaccia IPortalHeader, gestisce le viewlet plone.skip_links, plone.site_actions, plone.searchbox, plone.logo e plone.global_sections.

Tutte le viewlet elencate finora sono associate con le classi Python definite in plone/app/layout/viewlets/common.py. Queste classi sono delle viste browser di Zope 3 che implementano zope.viewlet.interfaces.IViewlet: ciò significa che hanno un metodo render() che genera i pezzi di codice HTML, i quali saranno inseriti nella pagina.

Si può inoltre notare nel configure.zcml che tutti i viewlet manager sono istanze di OrderedViewletManager (definita in manager.py in plone.app.viewletmanager). Questo significa che le viewlet saranno richiamate con un ordine specifico.

L'ordine delle viewlet è definita in una utility che implementa IViewletSettingsStorage.
Questa utility memorizza l'ordine delle viewlet di un viewlet manager per uno specifico skin. La sua configurazione è demandata ad un profilo di Generic Setup chiamato viewlets.xml.

Per lo skin di default di Plone, l'ordine delle viewlet è definito in Products/CMFPlone/profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
    <order manager="plone.portaltop" skinname="Plone Default">
        <viewlet name="plone.header" />
        <viewlet name="plone.personal_bar" />
        <viewlet name="plone.app.i18n.locales.languageselector" />
        <viewlet name="plone.path_bar" />
    </order>
    <order manager="plone.portalheader" skinname="Plone Default">
        <viewlet name="plone.skip_links" />
        <viewlet name="plone.site_actions" />
        <viewlet name="plone.searchbox" />
        <viewlet name="plone.logo" />
        <viewlet name="plone.global_sections" />
    </order>
</object>

Ora che sappiamo come Plone 3.0 imposta i viewlet manager e le relative viewlet per lo skin Plone Default, possiamo vedere come personalizzare tale configurazione sia per lo skin di default che per uno nuovo con un prodotto Python nel filesystem.

Nei seguenti paragrafi si farà riferimento ad un prodotto chiamato MyTheme. Si può sostituire MyTheme con il nome del prodotto Python che si vuole sviluppare.

Nota

Per generare velocemente un prodotto per uno skin per Plone 3.0 (un prodotto Python da mettere nella cartella Products dell'istanza Zope), si può utilizzare lo script generator di DIYPloneStyle (da svn trunk fino a che non esce la version 3.0). Se invece si desidera creare velocemente un python egg o un package da installare in $INSTANCE_HOME/lib/python/ o altrove nel path del Python, si può usare il template plone3_theme di ZopeSkel (da svn trunk).

Ordinare e nascondere le viewlet

Come cambiare in modo semplice il comportamento delle viewlet da un profilo di Generic Setup

Abbiamo visto nei precedenti paragrafi che l'ordine delle viewlet è memorizzato in una utility. Quell'utility è configurata nel file viewlets.xml di un profile Generic Setup.

Ordinamento

Se si ha solo bisogno di cambiare l'ordine delle viewlet nello skin Plone Default allora si può semplicemente copiare il viewlets.xml originale da CMFPlone/profiles/default/ in MyTheme/profiles/default/ e modificare la copia secondo le proprie necessità.

Qualora fosse richiesta una maggior flessibilità nell'ordinamento delle viewlet si posso usare alcuni parametri per ogni nodo del viewlets.xml.

Un viewlets.xml per un prodotto di skin potrebbe essere simile a questo:

<?xml version="1.0"?>

<object>

  <order manager="plone.portalheader" skinname="My Theme"
         based-on="Plone Default">
    <viewlet name="plone.logo" insert-before="*"/>
  </order>
  <order manager="plone.portaltop" skinname="*">
    <viewlet name="plone.app.i18n.locales.languageselector"
             insert-after="plone.path_bar"/>
  </order>

</object>

Questo codice contiene due espressioni <order />: la prima crea un nuovo skin di nome My Theme basato sul Plone Default. Questo significa che il nuovo skin erediterà l'ordine delle viewlet dall skin su cui è basato. Nell'esempio il plone.logo è messo come primo nel viewlet manager plone.portalheader. La seconda espressione sposta plone.app.i18n.locales.languageselector subito dopo plone.path_bar nel plone.portaltop per tutti gli skins (utilizzando skinname="*").

Nota

Sia DIYPloneStyle che ZopeSkel forniscono un viewlets.xml (contenente della documentazione sui parametri utilizzati) quando generano un nuovo prodotto per un tema.

Nascondere

Anche per nascondere le viewlet si utilizza il viewlets.xml con il nodo <hidden />, che è allo stesso livello di <order />, sempre selezionando lo skin.

Per esempio, per rimuovere il global sections dal proprio skin bisogna aggiungere delle dichiarazioni nel viewlets.xml come le seguenti:

<hidden manager="plone.portalheader" skinname="My Theme">
  <viewlet name="plone.global_sections" />
</hidden>

Mostrare viewlet nascoste

Qualora fosse necessario visualizzare una o più viewlet nascoste per un certo viewlet manager si possono utilizzare i parametri purge e remove del nodo <hidden /> nel viewlets.xml.

Per mostrare tutte le viewlet nascoste per un dato viewlet manager:

<?xml version="1.0"?>
<object>
  <hidden manager="plone.portalheader" skinname="Plone Default"
          purge="True" />
</object>

Per mostrare una specifica viewlet:

<?xml version="1.0"?>
<object>
  <hidden manager="plone.portalheader" skinname="Plone Default">
    <viewlet name="plone.global_sections" remove="True" />
  </hidden>
</object>

Nota

I parametri purge e remove sono supportati anche all'interno delle dichiarazioni <order />.

Aggiungere una viewlet

Come aggiungere una viewlet gestendo più skin contemporaneamente

Un buon punto di partenza è guardare uno degli esempi che accompagnano DIYPloneStyle (nella versione 3.0, ancora in beta, ma già usabile) per meglio capire il meccanismo: il prodotto credits_viewlet, localizzato in DIYPloneStyle/example/,  aggiunge una nuova viewlet nel pié di pagina.

Nella cartella browser/ di credits_viewlet vi sono diversi file:

- browser/
    - __init__.py
    - configure.zcml
    - interfaces.py
    - logo.pt
    - viewlets.py

__init__.py

Un file vuoto che serve solo a rendere browser un modulo Python.

configure.zcml

Contiene le configurazioni Zope 3 per browser.

interfaces.py

L'utilizzo di questo file verrà spiegato successivamente.

logo.pt

Il template che rimpiazzerà il codice HTML contenuto nel main_template.

viewlets.py

Contiene le classi Python delle viewlet.

Sempre nella cartella DIYPloneStyle/example/credits_viewlet vi è profiles/, che contiene i file di configurazione per Generic Setup: skins.xml e viewlets.xml.

Pratica

Registrazione della viewlet

Ora possiamo riprodurre quest'esempio nel nostro prodotto MyTheme.

Per creare una nuova viewlet bisogna scriverne la classe MyTheme/browser/viewlets.py (che renderizza il template MyTheme/browser/credits.pt) e registrarla (in nel MyTheme/browser/configure.zcml) nel viewlet manager da cui vogliamo sia mostrata, per poi posizionarla dove desiderato (in MyTheme/profiles/default/viewlets.xml).

Nota

Se si genera il proprio prodotto con DIYPloneStyle, si deve usare l'opzione --add-viewlet-example richiamando lo script che genera il modello per includere il codice della viewlet di esempio. Il plone3_theme di ZopeSkel include di default il codice di base della viewlet.

E' necessario creare il file vuoto MyTheme/browser/__init__.py per rendere browser/ un package Python.

Modificando MyTheme/browser/viewlets.py otteniamo:

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets.common import ViewletBase

DESIGNER = 'John Doe'

class CreditsViewlet(ViewletBase):
    render = ViewPageTemplateFile('credits.pt')

    def update(self):
        # set here the values that you need to grab from the template.
        # stupid example:
        self.designer = DESIGNER

In MyTheme/browser/credits.pt:

<div id="design-credits"
     i18n:domain="custom_i18n_domain">
    <span i18n:translate="design_by">Design by:
        <span tal:omit-tag="python:True"
              tal:content="view/designer"
              i18n:name="designer">Terry Gilliam</span>.</span>
</div>

In MyTheme/browser/configure.zcml:

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

    <browser:viewlet
        name="example.credits"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        class=".viewlets.CreditsViewlet"
        permission="zope2.View"
        />

</configure>

In MyTheme/profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
  <order manager="plone.portalfooter" skinname="Credits Viewlet Theme"
         based-on="Plone Default">
    <viewlet name="example.credits" insert-before="plone.colophon" />
  </order>
</object>

Infine si registra il nuono skin in MyTheme/profiles/default/skins.xml:

<?xml version="1.0"?>
<object name="portal_skins">
     <skin-path name="Credits Viewlet Theme" based-on="Plone Default" />
 </object>

Ora è possibile riavviare Zope ed installare il nuovo prodotto.

Sarà quindi possibile vedere che il footer del portale Plone è stato modificato e che ora mostra un messaggio indicante il detentore dei diritti sul design del sito.

Skin layer

E' importante notare che andando come amministratore nel Pannello di controllo, poi in Temi, e lì selezionando il Plone Default come tema, la viewlet credits continua ad essere mostrata.

Bisogna perciò far si che questa viewlet sia mostrata solo nello skin per cui è stata progettata. Si potrebbe nasconderla agli altri temi usando il nodo <hidden /> nel file viewlets.xml, ma ciò non sarebbe adatto ad un prodotto da distribuire in quanto non si possono conoscere tutti gli altri temi usati nei portali in cui potrebbe essere installato.

Per fortuna è possibile registrare una viewlet per un unico skin grazie al package plone.theme, che ci permette di impostare uno skin layer di Zope 3  corrispondente alla selezione di uno skin in portal_skins.

In MyTheme/browser/interfaces.py:

from zope.publisher.interfaces.browser import IDefaultBrowserLayer

class IMyThemeSpecific(IDefaultBrowserLayer):
    """Marker interface that defines a Zope 3 skin layer.
       It will be used for the viewlets that we want to add to the
       "My Theme" skin only.
    """

E in MyTheme/browser/configure.zcml:

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

   <interface
      interface=".interfaces.IThemeSpecific"
      type="zope.publisher.interfaces.browser.IBrowserSkinType"
      name="My Theme"
      />

   <browser:viewlet
      name="example.credits"
      manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
      class=".viewlets.CreditsViewlet"
      permission="zope2.View"
      layer=".interfaces.IThemeSpecific"
      />

</configure>

Si consiglia di porre attenzione alla dichiarazione dell'interfaccia dello skin layer di Zope 3 e ai parametri per la viewlet stessa.

Il parametro name nella dichiarazione dell'interfaccia deve essere lo stesso di quello del tema, come è possibile vedere nel Pannello di Controllo > Temi.

L'attributo layer della dichiarazione della viewlet deve essere l'interfaccia utilizzata per definire lo skin layer.

Dopo il riavvio di Zope o il refresh del prodotto, e ovviamente il ricaricamento della pagina, è possibile vedere che il tema Plone Default non mostra più la nostra viewlet.

Fare l'override di una viewlet

Come usare la tecnologia di Zope 3 per fare l'override di una viewlet

Nelle sezioni precedenti si è imparato come aggiungere e nascondere una viewlet per un viewlet manager per uno specifico tema. Sapendo ciò è possibile fare l'override di una viewlet di Plone con una versione personalizzata.

Teoria

Il concetto chiave per fare quanto desiderato è lo skin layer di Zope 3.

Nei capitoli precedenti si è utilizzato lo skin layer di Zope 3 per aggiugere viewlet in modo esclusivo al nostro prodotto, ora verrà utilizzato per l'override di una viewlet standard di Plone.

La logica è questa:

Nel nuovo prodotto (o package) in configure.zcml viene dichiarata una viewlet con lo stesso nome di una di quelle standard di Plone, ma con una classe costruttore o template differente dall'originale.

Utilizzando l'attributo layer nella dichiarazione zcml di una viewlet la leghiamo ad uno skin layer di Zope3. Quest'ultimo ha lo stesso nome del tema aggiunto nel tool portal_skins.

Gli skin layer sono legati allo skin scelto in portal_skins grazie al package plone.theme. Ciò significa che uno skin layer è utilizzato quando il suo nome e lo skin selezionato in portal_skins coincidono.

Le viewlet legate ad uno skin layer sono renderizzate quando quel layer è utilizzato, facendo l'override di tutte le viewlet con lo stesso nome, anche nel caso in cui non siano registrate per quel layer.

Pratica

E' possibile applicare tutto ciò al prodotto che si è iniziato a sviluppare, MyTheme.

L'obiettivo è ora quello di far sì che il footer mostri un testo differente da quello del tema standard di Plone.

  1. Creare/Registrare lo skin layer di Zope 3

    Sia ZopeSkel che DIYPloneStyle generano codice che definisce l'interfaccia dello skin layer in MyTheme/browser/interfaces.py. La classe interfaccia si chiama IThemeSpecific ed eredita da zope.publisher.interfaces.browser.IDefaultBrowserLayer.

    Il template plone3_theme di ZopeSkel genera di default un package che include una cartella browser/ ad uno skin layer di Zope 3.

    Lo script generator di DIYPloneStyle non fa la stessa cosa. Per far sì che anche lui aggiunga lo skin layer di Zope 3 bisogna lanciare lo script con l'opzione --add-viewlet-example. Qualora si fosse già generato il prodotto senza questa opzione, è possibile aggiungere a mano lo skin layer come spiegato nelle sezioni precedenti.

  2. Identificare la viewlet di cui fare l'override

    Per identificare la viewlet che interessa e il viewlet manager presso cui è registrata, Plone fornisce una semplice interfaccia per riodinare, nascondere e mostrare le viewlet all'interno dei proprio viewlet manager. Per accedervi si può usare un URL del tipo http://localhost:8080/portal/@@manage-viewlets dove portal è il sito Plone.

    La pagina di gestione delle viewlet dovrebbe essere simile a qualcosa del genere (utilizzando il tema Plone Default):

     manage-viewlets.jpg

    Alla fine della pagina si può vedere la zona del footer e notare che è generata da un viewlet manager chiamato plone.portalfooter, contenente due viewlet: plone.footer e plone.colophon. La prima viewlet è quella che deve essere modificata dal nostro tema.

  3. Scrivere il codice che genera la viewlet

    Ora che si sa quale viewlet sostituire, bisogna guardare il codice dell'installazione di Plone nel file system. Le viewlet di default di Plone sono dichiarate nel package plone.app.layout.viewlets, che di solito si trova in $INSTANCE_HOME/lib/python/plone/app/layout/viewlets/ anche se potrebbe essere ovunque nel  $PYTHONPATH.

    Alla fine del configure.zcml del package plone.app.layout.viewlets vi è la dichiarazione della viewlet plone.footer di default:

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

    E' interessante notare che questa viewlet non è renderizzata da una vista di Zope 3, ma direttamente dal template footer.pt.

    Si può perciò copiare questo template nella cartella browser/ di MyTheme  (non è necessario cambiarne il nome) e modificarlo in modo che scriva un testo differente dal solito, per esempio:

    <div id="portal-footer" i18n:domain="plone">
      <p>
        <span i18n:translate="design_author" tal:omit-tag="">
          Design by
          <span i18n:name="author" tal:omit-tag="">
            <a href="http://mypage.org">John Doe</a>
          </span>.
        </span>
      </p>
    </div>
  4. Registrare la viewlet personalizzata nel configure.zcml

    Per registrare il template nel viewlet manager del footer del portale è sufficiente copiare la relativa porzione di codice, che si trova in $INSTANCE_HOME/lib/python/plone/app/layout/viewlets/configure.zcml, nel MyTheme/browser/configure.zcml e modificarlo in modo da essere sicuri che utilizzi la giusta interfaccia per il manager e lo skin layer di Zope 3:

    <!-- The customized footer -->
    <browser:viewlet
       name="plone.footer"
       for="*"
       manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
       layer=".interfaces.IThemeSpecific"
       template="footer.pt"
       permission="zope.Public"
       />
  5. Fine

    Installando il nostro prodotto, dopo il riavvio del server Zope, dal Pannello di controllo > Prodotti aggiuntivi possiamo vedere il risultato al fondo della pagina.

     Altro esempio

    Un altro esempio di overriding di una viewlet può essere trovato in DIYPloneStyle/example/custom_footer/. E' abbastanza semplice da capire, soprattutto dopo aver letto fin qua.

    Questo esempio è leggermente differente dal precedente poichè usa una vista browser di Zope 3 per generare la nuova viewlet, ma i principi sono gli stessi.

    Inoltre il layout del prodotto varia un po' rispetto a quelli tipicamente generati con DIYPloneStyle o ZopeSkel. Tutto il codice della viewlet è mantenuto nel primo livello del package nel modulo browser.py, senza avere una sotto cartella browser. Ciò è stato fatto per mostrare che ci sono differenti modi di organizzare un prodotto/package per un tema Plone.

    • In browser.py si trovano IThemeSpecific, l'interfaccia dello skin layer di Zope 3, e FooterViewlet, la classe della viewlet che fa l'override di quella di default di Plone.
    • footer.pt è il template renderizzato da FooterViewlet.
    • profiles/default/ è il tipico profilo di Generic Setup per il prodotto.
    • Sia l'interfaccia che la viewlet e il profilo di GS sono dichiarati in un singolo configure.zcml.

Override di una classe viewlet

Come usare la tecnologia di Zope 3 per fare l'override di una viewlet utilizzandone una definita come classe (la versione originale di questa sezione è stata scritta da Denis Mishunov)

Nelle sezioni precedenti ci si è occupati delle viewlet che hanno un template nella propria definizione. Ma in Plone ci sono parecchie viewlet che hanno una classe Python nella loro definizione invece di un semplice template. Vediamo come personalizzarle.

Le operazioni da compiere non differiscono granché da quanto appreso finora.

Introduzione

Si suppone il lettore capace di trovare la viewlet di cui fare l'override utilizzando la vista @@manage-viewlets mostrata nella sezione precedente. Come esempio si farà l'override della viewlet plone.path_bar, gestita dal viewlet manager plone.portaltop. Questa è la viewlet che genera la barra del percorso di navigazione (conosciuto anche come briciole di pane). In questo caso elimineremo la scritta "Tu sei qui:" dalla viewlet.

  1. Il codice che genera la viewlet

    Nel configure.zcml del package plone.app.layout.viewlets  questa viewlet è dichiarata così:

    <!-- The breadcrumbs -->
    <browser:viewlet
        name="plone.path_bar"
        manager=".interfaces.IPortalTop"
        class=".common.PathBarViewlet"
        permission="zope2.View" 
        />
    Come si vede, la viewlet è generata da una vista Zope 3 definita nella classe common.PathBarViewlet (mostrata qui sotto) del package plone.app.layout.viewlets.
    class PathBarViewlet(ViewletBase):         render = ViewPageTemplateFile('path_bar.pt')
             def update(self):
                portal_state = getMultiAdapter((self.context, self.request),
                                                    name=u'plone_portal_state')
      
                self.navigation_root_url = portal_state.navigation_root_url()
     
                self.is_rtl = portal_state.is_rtl()
        
                breadcrumbs_view = getMultiAdapter((self.context, self.request),
                                                   name='breadcrumbs_view')
                self.breadcrumbs = breadcrumbs_view.breadcrumbs()
    Per rimuovere la scritta "You are here:" dobbiamo modificare il template richiamato dall'espressione render = ViewPageTemplateFile('path_bar.pt').
  2. Personalizzare il template della viewlet

     La linea render = ViewPageTemplateFile('path_bar.pt') definisce il template per la viewlet plone.path_bar. Il template path_bar.pt si trova nel package plone.app.layout.viewlets. Questo template deve essere copiato, senza cambiargli il nome, nella cartella browser/ di MyTheme.

    Si può ora modificare il template eliminando il testo che non desideriamo:

    <div id="portal-breadcrumbs"
         i18n:domain="plone">
    
        <!-- Commentiamo la parte da rimuovere
        <span id="breadcrumbs-you-are-here" i18n:translate="you_are_here">
            You are here:
        </span>
        -->    
            
        <a i18n:translate="tabs_home" tal:attributes="href
        view/navigation_root_url">Home</a>
        <span tal:condition="view/breadcrumbs" class="breadcrumbSeparator">
            <tal:ltr condition="not: view/is_rtl">&rarr;</tal:ltr>
            <tal:rtl condition="view/is_rtl">&raquo;</tal:rtl>
        </span>
        <span tal:repeat="crumb view/breadcrumbs"
            ...
        </span>
    
    </div>
    Ora è sufficiente far sì che la viewlet  plone.path_bar usi questo template.
  3. Fare l'override della classe che utilizza il template

    La dichiarazione di plone.path_bar in lib/python/plone/app/layout/viewlets/configure.zcml usa una classe invece di un semplice template, dobbiamo perciò modificare anche quella. Per fare ciò è sufficiente subclassare la classe di default della viewlet ed effetturare i cambiamenti richiesti per far sì che faccia l'override di quella originale. La sottoclasse PathBarViewlet sarà contenuta nel file viewlets.py in MyTheme/browser/:

    from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
    
    from plone.app.layout.viewlets import common
    
    class PathBarViewlet(common.PathBarViewlet):
        """A custom version of the path bar class 
        """
    
        render = ViewPageTemplateFile('path_bar.pt')
    La sola cosa di cui si fa l'override è il template path_bar.pt, già modificato in MyTheme/browser/. Qualora servisse, potremmo modificare più profondamente la classe. Ora che abbiamo la nostra classe e template personalizzati possiamo registrare queste modifiche in MyTheme/browser/configure.zcml.
  4. Registrare la viewlet personalizzata in configure.zcml

    Per registrare la nuova classe con la viewlet plone.path_bar si può semplicemente copiare il codice ZCML da lib/python/plone/app/layout/viewlets/configure.zcml in MyTheme/browser/configure.zcml e modificarlo assicurandosi che utilizzi la classe e l'interfacccia giusti per il manager e lo skin layer Zope 3:

    <!-- The customized breadcrumbs -->
    <browser:viewlet
        name="plone.path_bar"
        manager="plone.app.layout.viewlets.interfaces.IPortalTop"
        class=".viewlets.PathBarViewlet"
        layer=".interfaces.IThemeSpecific"
        permission="zope2.View" 
        />
    Il significato di layer=".interfaces.IThemeSpecific" è stato spiegato nel precedente capitolo e name e permission rimagono gli stessi della dichiarazione originale della viewlet, sebbene convenga usare un altro name per rendere evidente che questa non è la viewlet standard, ad esempio "MyTheme.path_bar". Come manager utilizziamo il percorso dell'interfaccia IPortalTop implementata dal viewlet manager plone.portaltop. La parte principale della modifica consiste nell'uso come class di quella modificata in MyTheme/browser/viewlets.py.
  5. Punto facoltativo (Solo se si è cambiato il nome per la viewlet nello ZCML)

    Se si è cambiato il nome della viewlet in "MyTheme.path_bar" viene creata una nuova viewlet che deve essere registrata nel profilo di Generic Setup; per evitare di avere le briciole di pane raddoppiate bisogna nascondere la viewlet plone.path_bar originale (come spiegato nelle sezioni precedenti).

  6. Fine

    Ora è possibile riavviare Zope e, se non è stato ancora installato il tema,  lo si può fare Configurazione del sito > Prodotti aggiuntivi

    Finalmente non dovrebbe più esserci la scritta "Tu sei qui:".

Spostare una viewlet da un viewlet manager ad un altro

Per spostare una viewlet da un viewlet manager ad un altro è sufficiente applicare quanto visto finora.

Si può nascondere la viewlet dal viewlet manager da cui vogliamo rimuoverla e registrarla nel viewlet manager per lo zSubject che corrisponde al nostro tema.

Ipotizzando di voler spostare i portal actions dalla cima al fondo della pagina possiamo procedere nel modo seguente:

in browser/interfaces.py definiamo il nostro ZSubject

from zope.publisher.interfaces.browser import IDefaultBrowserLayer

class IMyThemeSpecific(IDefaultBrowserLayer):
"""Marker interface that defines a ZSubject.
It will be used for the viewlets that we want to add to the
"My Theme" skin only.
"""

in browser/configure.zcml:

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

    <interface
        interface=".interfaces.IMyThemeSpecific"
        type="zope.publisher.interfaces.browser.IBrowserSkinType"
        name="My Theme"
        />

    <!-- Moved viewlet registration -->
    <browser:viewlet
        name="plone.site_actions"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        class="plone.app.layout.viewlets.common.SiteActionsViewlet"
        permission="zope2.View"
        layer=".interfaces.IMyThemeSpecific"
        />

</configure>

ed in profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
  <order manager="plone.portalfooter" skinname="My Theme"
         based-on="Plone Default">
    <viewlet name="plone.site_actions" insert-after="*" />
  </order>
  <!-- We hide the one we want to move -->
  <hidden manager="plone.portalheader" skinname="My Theme">
    <viewlet name="plone.site_actions" />
  </hidden>
</object>

Ed ecco fatto tutto senza toccare il main_template.pt.