Personalização para desenvolvedores
Personalização para desenvolvedores
Este tutorial fornece uma visão geral de como personalizar diferentes aspectos do Plone 3
Introdução
O que estamos fazendo aqui?
O Plone tem uma forte cultura de personalização. Ou seja, praticamente todos os aspectos do Plone pode ser personalizado ou substituído, sem tocar no código-fonte original. Este tutorial irá descrever as principais formas em que os componentes visuais em Plone 3 pode ser personalizado por desenvolvedores..
É possível que os administradores do site personalizem vários templates através da web, usando a ferramenta portal_skins (para os recursos tradicionais do CMF na layer skin) ou a ferramenta portal_view_customizations (para Zope 3 views, viewlets and portlets). Vamos explicar a diferença em mais detalhes em seguida. A personalização através da web (ou seja, pela ZMI) é ótima para correções rápidas ou experiências, mas isso não é a forma de construir ou implantar um projeto sério. Este tutorial concentra-se em customizações baseado no sistema de arquivos (filesystem), como um desenvolvedor faria em um produto complementar.
Este tutorial mostra os principais tipos de personalização através de um pacote chamado example.customization. O tutorial de buildout irá mostrar-lhe como instalá-lo no seu buildout.
Se você simplesmente quiser ver a versão mais recente do código-fonte, você pode procurá-lo no repositório de Subversion Collective.
Skin camada de personalização
A moda antiga
Tradicionalmente, a personalização em Zope foi feito por meio de aquisições. Por exemplo, um desenvolvedor poderia ter colocado um template de página na raiz do site e adquiri-lo para tomar um objeto em uma subpasta /foo/bar. Se o desenvolvedor queria uma renderização diferente para os objetos apenas /foo/bar/baz, ele pode colocar um template com o mesmo nome (id) nessa pasta, o que prevalece.
A partir do Plone (e o CMF) todos os usuários administradores são permitidos a criarem seus próprios conteúdos, colocando templates e códigos dentro da estrutura do site e tornando a hierarquia confusa. Portanto, os desenvolvedores CMF inventaram a ferramenta portal_skins. Se você acessar esta ferramenta na ZMI, você vai encontrar uma ou mais camadas (layers) em sua aba Properties, notavelmente "Plone Default". A camada (layer) (que tendem a chamar temas para evitar confusão), é apenas uma lista ordenada de camadas de Skin. As camadas de skin listadas na aba Properties correspondem às pastas dentro da ferramenta portal_sink. Quando o Zope está à procura de um recurso (logo.jpg, por exemplo) ele irá procurar na pasta atual e em seguida as pastas pai até a raiz do site. Se o recurso não pode ser encontrado, o CMF vai procurá-lo nas camadas de Skin para o tema atual, começando no topo da lista e descendo até encontrar uma.
Observe a custom é a primeira na lista de entrada. É assim que funciona a personalização através da web (ZMI). O item copiado para esta pasta tem precedência sobre as entradas existentes. Note também que todas as outras pastas são de somente leitura. Isso é porque elas são pastas no ZODB, pasta estas que refletem os arquivos dentro de uma pasta especial no sistema de arquivos.
Assim, para criar um produto que substitui alguns recursos em uma camada de skin que vem com o Plone, devemos:
- Registrar o novo diretório de arquivos
- Insira este diretório na lista da camada de skin atual, normalmente logo abaixo da pasta custom
- Copie os recursos relevantes para o novo diretório da camada de skin, mantendo o mesmo nome
- Personalize esta copia
Nota: Alguns recursos terão um arquivo de metadados associados. Se assim for, você deve copiar o arquivo .metadata juntamente com o recursos ao personaliza-lo..
Tradicionalmente, todos dos recursos do Plone - imagens, folhas de estilos, arquivos JavaScripts, templates de páginas e Python Scripts, como manipuladores de formulários, foram mantidos em camadas de skin. No entanto, como você pode imaginar isto tornou-se um pouco embaraçoso no Plone que cresceu, já que o mecanismo de camada de skin pressupõe que cada recurso tenha um nome global e único (id). Ainda assim, como o Plone 3, a maioria das views do core, imagens e folhas de estilos que fazem parte do Plone são mantidos em camadas de skin que tem os nomes prefixados com "plone_", como "plone_images" ou "plone_templates". No sistema de arquivos, você irá encontrar estes dentro de CMFPlone/skins. Você pode procurar as subpastas dessa pasta para encontrar vários recursos.
Em geral, se um recurso é mencionado na interface do Plone (por exemplo, a partir de uma url, uma ação em portal_actions ou um alias no portal_types) e não é prefixados com @@ (@@view, por exemplo) ou ++resource++ (++resource++stylesheet.css, por exemplo), então é provável que seja encontrado em uma camada de skin.
Registrando e instalando uma camada de skin baseados no sistema de arquivos
No produto example.customization, temos um subdiretório skin/, com uma camada de skin único diretório, example_customiation. Para sermos capazes de registrar esse diretório, temos que dizer ao diretório de nível superior do CMF. Fazemo-lo no arquivo configure.zcml do pacote:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:cmf="http://namespaces.zope.org/cmf"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="example.customization">
<cmf:registerDirectory name="example_customization"/>
...
</configure>
Precisamos então criar o diretório e instalá-lo para a skin corrente quando o produto é instalado em seu Plone Site. Fazemos isso usando o GenericSetup. Primeiro, devemos registrar um novo "extension profile" para que o produto é instalado. Isso é feito em configure.zcml, como 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"
/>
Então nós usamos o skins.xml import handler para configurar a ferramenta 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>
Se quiséssemos, poderíamos registar como muitos diretórios vistos como nós queríamos aqui.
Para testar tudo isso, nós colocamos uma nova logo em skins/example_customizations/logo.jpg substituindo a logo logo.jpg de CMFPlone/skins/plone_images. Quando o produto é instalado, você verá esta logo em vez do padrão do Plone. Poder ser necessários fazer uma atualização em seu navegador.
Para remover as personalizações introduzidas pelo produto, remova a camada inserida na lista de camadas no tema atual na aba Properties na ferrametna portal_skin na ZMI.
Browser layers
Customisation Zope 3-style
When Zope 3 was designed, many of the lessons from CMF were incorporated and the technology refined. The idea of a single, global namespace with customisation possible by id only was supplanted by the notion of named resources being registered for a context type (so that the view called @@viewwhen invoked on a Page looks different to the view with the same name invoked on a Folder, say), and possibly registered for a browser layer. A Zope 3 browser layer is similar in purpose to a CMF skin layer, but is implemented differently.
A browser layer is just a marker interface which is applied to the request upon traversal. A Zope 3 browser resource is a multi-adapter on the context and the request, and when the request is marked with a particular interface, the Component Architecture may find a more specific adapter in a view registered for that particular layer. If that made no sense, don't worry. All you need to know is this:
- We define a marker interface (which is just a class with no body, deriving fromzope.interface.Interface) representing the browser layer.
- We ensure that the interface is applied to the request automatically, enabling the browser layer in our site.
- We register (using ZCML) browser resources, views, viewlets and portlets for this new layer, allowing them to override the defaults (which are implicitly registered for the default browser layer).
In Plone, there are two main ways of enabling a custom browser layer interface. The first is to use the mechanisms of plone.theme. This package, which ships with Plone by default, allows us to link a browser layer with a particular theme (skin) in portal_skins (not to be confused with a skin layer). When the theme is enabled in portal_skins, the layer is in effect. This is useful for products that install a whole new theme in Plone. It's less useful for general (non-theme) products, since only one plone.theme-installed layer is active at any given time. We really want layer installation to be additive, so that we can install any number of products, each providing its own layer.
To do that, we need to use the plone.browserlayer package, which is part of Plone 3.1 and later (it is possible to install it as an add-on in Plone 3.0, too).
In example.customization, you will find a layer marker interface in browser/interfaces.py:
from zope.interface import Interface
class IExampleCustomization(Interface):
"""This interface is registered in profiles/default/browserlayer.xml,
and is referenced in the 'layer' option of various browser resources.
When the product is installed, this marker interface will be applied
to every request, allowing layer-specific customisation.
"""
This is then installed when the package is installed, via the browserlayer.xml GenericSetup import step, found in profiles/default:
<?xml version="1.0"?>
<!-- Register the package-specific browser layer, so that it will be activated
when this product is installed. -->
<layers>
<layer name="example.customization.layer"
interface="example.customization.browser.interfaces.IExampleCustomization" />
</layers>
The examples later in this tutorial will reference this marker interface.
Zope 3 browser resources
How to customise images and stylesheets registered as Zope 3 browser layers
Zope 3 allows browser resources, notably images and style sheets, to be registered under a special namespace. For example, if you register an image resource with the name wibble.gif, the browser resource would be addressable as http://yoursite.com/++resource++wibble.gif. This serves to get the resource out of the flat, global namespace. Browser resources don't do much by themselves. They are typically installed in a registry such as portal_css, portal_javascripts, or portal_kss, or used in actions or other links.
Like all Zope 3 browser components, browser resources are registered with a ZCML directive in thebrowser namespace that takes, among other things, a layer attribute. The layer should resolve to an interface.
As an example, take the plone.app.iterate package. In its browser/configure.zcml file, you will find the following definition:
<browser:resource
name="checkout.png"
image="checkout.png"
/>
This defines a resource, ++resource++checkout.png, which is used in the check-out action when Iterate is installed. If we wanted to turn this rather pretty image into an ugly pink, we could customise it for theIExampleCustomization layer. With a custom image called ugly_checkout.png in our own browser/directory, we would add the following in 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>
Note that without the layer attribute, we would get a configuration conflict with the original++resource++checkout.png definition.
Zope 3 browser views
Customising browser views and pages
Browser views are analogous to page templates (.pt files) in skin layers. However, they are registered in ZCML for a particular type of context. Here is an example, from plone.app.content.browser:
<browser:page
for="*"
class=".reviewlist.FullReviewListView"
name="full_review_list"
template="full_review_list.pt"
permission="cmf.ReviewPortalContent" />
This tells us that the view is called @@full_review_list (the @@ disambiguator is not strictly necessary, but it makes it clear that this is a view and not, say, a content item). It is available to users that currently have the Review portal content permission, and it is available for any context (for="*"). The implementation is held in a class called FullReviewListView in reviewlist.py in the current directory, which is implicitly rendered by a template called full_review_list.pt.
One way to customise this is to provide an override for a more specific (or different) context. The "*" context is the most general (under the hood, this means zope.interface.Interface). If we had our own implementation for a standard Page (as identified by theProducts.ATContentTypes.interface.document.IATDocument interface), we could do so with the following declaration in our own package's ZCML:
<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" />
Here, we have chosen to use the default layer (that is, we haven't specified a layer), but a new context type (the for attribute, which points to the interface of the context type in question). We could have provided a new class (or none at all), but here we use the default view class from plone.app.content. Note that we converted it from a relative module path to an absolute one, since we are now in a different package! We do change the template, which again is relative to the directory where the ZCML file is found. We could have chosen to use a class only, or a template only, if that made more sense.
Alternatively, we could customise by layer (and thus ensure that the customisation only takes effect when the product is installed). Here is another example, this time replacing the default view with a new one, using the old class with a new 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" />
Note that these two declarations can co-exist. In this case, the default will usestandard_full_review_list.pt, but the view on a Page will use document_full_review_list.pt, since that is more specific. Of course, we could do both - use a context override and a layer. The result would be that the context override only took effect when the layer is installed.
Finding views
Since views are spread out across packages, it can sometimes be difficult to identify where they come from. Often, doing a file search in your egg cache is a quick-and-dirty solution. However, you can also do either of the following:
- Go to portal_view_customizations in the ZMI, where you will find views, viewlets and portlet renderers, grouped together by their context type. Hover your mouse over the view title, and you should see a package name and a template name in a tool tip.
- Append /@@zptviews.html to the end of a particular URL, e.g.http://localhost:8080/plone/@@zptviews.html. This shows views with templates (but not viewlets or portlet renderers), along with their template, context type interface and source ZCML file.
Viewlets
Customising Zope 3 viewlets
Viewlets are snippets of a page, written a bit like a view but composed into the page via a viewlet manager. The viewlet manager is responsible for finding, ordering, filtering and rendering its viewlets. Most of Plone's standard viewlets are found in the plone.app.layout package. Here is an excerpt from itsviewlets/configure.zcml file:
<browser:viewlet
name="plone.colophon"
for="*"
manager=".interfaces.IPortalFooter"
template="colophon.pt"
permission="zope.Public"
/>
The name is unique within the portlet manager, which is identified by an interface (and defined earlier in the same file, using a <browser:viewletManager /> directive). Here, we are using a template-only viewlet, defined for any context, and not protected by any particular permission. It is possible to define aclass instead of or in addition to the the template.
As you may have guessed, it's possible to customise the viewlet either by registering a new viewlet with the same name, in the same manager, but for a more specific context. Alternatively, we can register a new viewlet with the same name, in the same manager, using a custom layer. That could look like this:
<browser:viewlet
name="plone.colophon"
for="*"
manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
template="funny_colophon.pt"
permission="zope.Public"
layer=".interfaces.IExampleCustomization"
/>
Notice how we have turned the relative module path to the IPortalFooter viewlet manager interface into an absolute one and specified a new template (found relative to the directory where our new ZCML file is located).
Finally, it's possible to use the view attribute to specify an interface or class of a page view. With this type of customisation, you can have a viewlet that looks different on different pages. The most common use of this in Plone is to register viewlets that are only shown on the main "view" of a page, by using the special IViewView marker interface, which is applied to the current view during traversal if the user is looking at the "view" tab of the current context. Here is an example from 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"
/>
As with views, you can of course use the for, layer and view customisation dimensions in combination.
Finding viewlets
The portal_view_customizations tool will show you viewlet registrations (and the viewlet managers they are registered for). As with views, you can hover over the viewlet name to see where it is registered in a tool tip. To discover the name of a particular viewlet, you can use the @@manage-viewlets view, e.g. as http://localhost:8080/plone/@@manage-viewlets.
For more information about viewlets, see the tutorial on customising the viewlets in main_template.
Portlet renderers
Customising Plone 3 portlet renderers
Portlets in Plone 3 are not too dissimilar to viewlets, but they consist of a persistent component, theportlet assignment, containing the configuration of the portlet, which is rendered using a portlet renderer. For visual customisations, you want to get at the portlet renderer.
Since portlet renderers can incorporate complex logic and relies on some specific registration techniques, Plone comes with a new ZCML directive - <plone:portletRenderer /> - which makes customising portlet renderers a little easier. Unlike the customisation we have seen for standard browser resources, a template-only custom portlet renderer will actually use the renderer view class (as its viewvariable) that was used to register the original portlet renderer.
In plone.app.portlets, under portlets/configure.zcml, you will find this definition for the Recent portlet:
<plone:portlet
name="portlets.Recent"
interface=".recent.IRecentPortlet"
assignment=".recent.Assignment"
renderer=".recent.Renderer"
addview=".recent.AddForm"
editview=".recent.EditForm"
/>
Let's say we wanted to customise this with a new template. The default template (as referenced by theRenderer class in recent.py) is recent.pt in the same directory. Copying that to our own package, we can do the following:
<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>
Note that we are explicitly including the plone.app.portlets package in ZCML processing, since we are now using its portlet Renderer class implicitly. We then define a new portlet renderer with a custom template, for our new layer. It is also possible to use the for attribute to customise for a particular type of context, or the view attribute to customise for a particular view, as with viewlets.
Instead of using a custom template, we could use a whole new renderer class. See the implementations in plone.app.portlets to understand how this would work. Note that unlike views and viewlets, portlet renderers support either template or class, but not both.
