Using a real-world example, this presentation guides attendees to the process of transforming an old-style product into a fresh, extensible add-on for Plone 3. It will cover the basics to make an old product work on Plone 3 and also advanced topics on how to make it more robust and extensible, using new development approaches.
1. Moving an old-style product to Plone 3
Plone Conference 2008 - Washington D.C.
Ricardo Alves
rsa@eurotux.com
October 27, 2008
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
2. Contents
Contents
1 About the Example
2 Get it to work
3 Benefit from Plone 3
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
3. About the Example
TuxLiveFM
Product
Add-on for Plone 2.5
Archetypes-based content types
Mixin classes
New portlet
Skin layers
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
4. About the Example
TuxLiveFM – Structure
Products/
TuxLiveFM/
Extensions/
Install.py
__init__.py
config.py
content/
show.py
episode.py
series.py
__init__.py
skins/
tuxfmlive/
portlet_lastepisode.pt
radioshow_view.pt
radioepisode_view.pt
tuxlivefm.css.dtml
search_episodes.pt
license.txt
readme.txt
version.txt
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
5. Get it to work
Get it to work
Fix broken code due to API changes
(CMFCorePermissions, ...)
Use GenericSetup for product installation
Migrate portlets
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
6. Get it to work
Product Installation
Define a profile using GenericSetup
TuxLiveFM/configure.zcml
<configure
xmlns=quot;http://namespaces.zope.org/zopequot;
xmlns:genericsetup=quot;http://namespaces.zope.org/genericsetupquot;
i18n_domain=quot;TuxLiveFMquot;>
<genericsetup:registerProfile
name=quot;tuxlivefmquot;
title=quot;TuxLiveFMquot;
directory=quot;profiles/defaultquot;
description=quot;TuxLiveFM product.quot;
provides=quot;Products.GenericSetup.interfaces.EXTENSIONquot;
/>
</configure>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
7. Get it to work
Product Installation - Types
TuxLiveFM/Extensions/Install.py
...
# install types
classes = listTypes(PROJECT_NAME)
installTypes(self, out, classes, PROJECT_NAME)
...
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
8. Get it to work
Product Installation - Types
profiles/default/types.xml
<?xml version=quot;1.0quot;?>
<object name=quot;portal_typesquot; meta_type=quot;Plone Types Toolquot;>
<object name=quot;RadioShowquot;
meta_type=quot;Factory-based Type Information with dynamic viewsquot;/>
<object name=quot;RadioEpisodequot;
meta_type=quot;Factory-based Type Information with dynamic viewsquot;/>
</object>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
9. Get it to work
Product Installation - Types
profiles/default/types/RadioShow.xml
<?xml version=quot;1.0quot;?>
<object name=quot;RadioShowquot;
meta_type=quot;Factory-based Type Information with dynamic viewsquot;
i18n:domain=quot;plonequot;
xmlns:i18n=quot;http://xml.zope.org/namespaces/i18nquot;>
<property name=quot;titlequot; i18n:translate=quot;quot;>Radio Show</property>
<property name=quot;descriptionquot;
i18n:translate=quot;quot;>Describes a Radio Show.</property>
<property name=quot;content_iconquot;>document_icon.gif</property>
<property name=quot;content_meta_typequot;>RadioShow</property>
<property name=quot;productquot;>tuxfm</property>
<property name=quot;factoryquot;>addRadioShow</property>
<property name=quot;immediate_viewquot;>base_view</property>
<property name=quot;global_allowquot;>True</property>
<property name=quot;filter_content_typesquot;>True</property>
<property name=quot;allowed_content_typesquot;>
...
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
10. Get it to work
Product Installation - Skins
TuxLiveFM/Extensions/Install.py
...
# install skins
install_subskin(self, out, GLOBALS)
...
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
11. Get it to work
Product Installation - Skins
profiles/default/skins.xml
<?xml version=quot;1.0quot;?>
<object name=quot;portal_skinsquot; allow_any=quot;Falsequot;
cookie_persistence=quot;Falsequot; default_skin=quot;TuxLiveFMquot;>
<object name=quot;tuxlivefm_templatesquot;
meta_type=quot;Filesystem Directory Viewquot;
directory=quot;Products.TuxLiveFm:skins/tuxlivefm_templatesquot;/>
<skin-path name=quot;TuxLiveFMquot; based-on=quot;Plone Defaultquot;>
<layer name=quot;tuxlivefm_templatesquot; insert-after=quot;customquot;/>
</skin-path>
</object>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
12. Get it to work
Product Installation - CSS
TuxLiveFM/Extensions/Install.py
...
# register stylesheets
css_tool = getToolByName(self, ’portal_css’)
stylesheets = css_tool.getResources()
stylesheet_ids = [x.getId() for x in stylesheets]
if ’tuxlivefm.css’ not in stylesheet_ids:
css_tool.registerStylesheet(
’tuxlivefm.css’,
expression=’’,
media=’screen’,
rel=’stylesheet’,
rendering=’import’)
...
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
13. Get it to work
Product Installation - CSS
profiles/default/cssregistry.xml
<?xml version=quot;1.0quot;?>
<object name=quot;portal_cssquot;>
<stylesheet title=quot;quot; cacheable=quot;Truequot; compression=quot;safequot;
cookable=quot;Truequot; enabled=quot;1quot; expression=quot;quot;
id=quot;tuxlivefm.cssquot; media=quot;screenquot; rel=quot;stylesheetquot;
rendering=quot;importquot;/>
</object>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
14. Get it to work
Migrate portlets
Convert legacy portlets, or
Create classic portlet
<assignment
manager=quot;plone.rightcolumnquot;
category=quot;content_typequot;
key=quot;RadioShowquot;
type=quot;portlets.Classicquot;>
<property name=quot;templatequot;>portlet_lastepisode</property>
<property name=quot;macroquot;>portlet</property>
</assignment>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
15. Benefit from Plone 3
New Components for specialized behavior
Define interfaces
class IRadioSeries(Interface):
def lastEpisodeURL(self):
quot;quot;quot; Returns the URL of the last episode available.
quot;quot;quot;
def searchEpisodes(self, **kwargs):
quot;quot;quot; Perform search in the available episodes.
quot;quot;quot;
Replace mixin class with an adapter
<adapter factory=quot;.content.series.RadioSeriesquot;
for=quot;.interfaces.IRadioShowquot;
provides=quot;.interfaces.IRadioSeriesquot;
/>
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
16. Benefit from Plone 3
Use browser views
Add browser view EpisodeSearchView
Move related methods to the view class.
class SearchEpisodesView(BrowserView):
def searchEpisodes(self, **kwargs):
series = IRadioSeries(self.context)
return series.searchEpisodes(**kwargs)
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
17. Benefit from Plone 3
Deprecate old code
Use zope.deprecation for modules to be removed
Deprecation period of one or two feature releases
import zope.deprecation
zope.deprecation.deprecated(’ShowSeriesMixin’,
quot;Products.TuxLiveFM.content.series.ShowSeriesMixin has quot;
quot;been deprecated and will be removed in version 0.3.quot;)
Move deprecated templates to another layer.
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3
18. Benefit from Plone 3
Use Zope 3 Interfaces with Archetypes
Define Zope 3 schema interface for content types
from zope import schema
...
class IRadioShow(Interface):
body = schema.Text(title=_(u’Body’))
...
Use ATFieldProperty as a bridge from fields to Python properties
from Products.Archetypes import public as atapi
...
body = atapi.ATFieldProperty(’body’)
...
Ricardo Alves rsa@eurotux.com Moving an old-style product to Plone 3