Creating, Customizing, and Assigning Portlets Automatically for Plone 3.3

Exclusive offer: get 50% off this eBook here
Plone 3 Products Development Cookbook

Plone 3 Products Development Cookbook — Save 50%

70 simple but incredibly effective recipes for creating your own feature rich, modern Plone add-on products by diving into its development framework

$26.99    $13.50
by Juan Pablo Giménez Marcos F. Romero | May 2010 | Cookbooks Content Management Open Source Web Development

This article by Juan Pablo Giménez and Marcos F. Romero, authors of Plone 3.3 Products Development Cookbook will give you step-by-step instructions for adding portlets. Portlets are used to provide contextual information about the main contents of a page.

In this article, we will cover:

  • Creating a portlet package
  • Customizing a new portlet according to our requirements
  • Testing portlets
  • Assigning portlets automatically
  • (For more resources on Plone, see here.)

    Introduction

    One of the major changes from Plone 2.5 to Plone 3.0 was the complete refactoring of its portlets engine. In Plone 2.5, left and right side portlets were managed from Zope Management Interface (ZMI) by setting two special properties: left_slots and right_slots.

    This was not so hard but was cumbersome. Any TALES path expression—ZPT macros typically—could be manually inserted in the above two properties and would be displayed as a bit of HTML to the final user.

    The major drawback for site managers with the old-style approach was not just the uncomfortable way of setting which portlets to display, but the lack of any configuration options for them. For example, if we wanted to present the latest published news items, we wouldn't have any means of telling how many of them to show, unless the portlet itself were intelligent enough to get that value from some other contextual property. But again, we were still stuck in the ZMI.

    Fortunately, this has changed enormously in Plone 3.x:

    • Portlets can now be configured and their settings are mantained in ZODB.
    • Portlets are now managed via a user-friendly, Plone-like interface. Just click on the Manage portlets link below each of the portlets columns (see the following screenshot).

    In the above screen, if we click on the News portlet link, we are presented with a special configuration form to choose the Number of items to display and the Workflow state(s) we want to consider when showing news items.

    If you have read previous chapters, you might be correctly guessing that Zope 3 components (zope.formlib mainly) are behind the portlets configuration forms.

    In the next sections, we'll look again at the customer's requirements:

    • Advertisement banners will be located in several areas of every page
    • Advertisement banners may vary according to the section of the website

    Creating a portlet package

    Once again, paster comes to the rescue. As in Creating a product package structure and Creating an Archetypes product with paster, we will use the paster command here to create all the necessary boilerplate (and even more) to get a fully working portlet.

    Getting ready

    As we are still at the development stage, we should run the following commands in our buildout's src folder:

    cd ./src

    How to do it...

    1. Run the paster command: We are going to create a new egg called pox.portlet.banner. The pox prefix is from PloneOpenX (the website we are working on) and it will be the namespace of our product.

      Portlets eggs usually have a nested portlet namespace as in plone.portlet.collection or plone.portlet.static. We have chosen the pox main namespace as in the previous eggs we have created, and the banner suffix corresponds to the portlet name. For more information about eggs and packages names read http://www.martinaspeli.net/articles/the-naming-ofthings-package-names-and-namespaces.
      If you want to add portlets to an existing package instead of creating a new one, the steps covered in this chapter should tell you all you need to know (the use of paster addcontent portlet local command will be of great help).

      In your src folder, run the following command:

      paster create -t plone3_portlet

      This paster command creates a product using the plone3_portlet template. When run, it will output some informative text, and then a short wizard will be started to select options for the package:

      Option

      Value

      Enter project name

      pox.portlet.banner

      Expert Mode?

      easy

      Version

      1.0

      Description

      Portlet to show banners

      Portlet Name

      Banner portlet

      Portlet Type

      BannerPortlet

      After selecting the last option, you'll get an output like this (a little longer actually):

      Creating directory ./pox.portlet.banner
      ...
      Recursing into +namespace_package+
      Recursing into +namespace_package2+
      Recursing into +package+
      Recursing into profiles
      Creating ./pox.portlet.banner/pox/portlet/banner/profiles/
      Recursing into default
      Creating ./pox.portlet.banner/pox/portlet/banner/profiles/default/
      Copying metadata.xml_tmpl to ./pox.portlet.banner/pox/portlet/banner/profiles/default/metadata.xml
      Copying portlets.xml_tmpl to ./pox.portlet.banner/pox/portlet/banner/profiles/default/portlets.xml

      This tells us that even the GenericSetup extension profile has also been created by paster. This means that we can install the new Banner portlet product (as entered in the portlet_name option above).

    2. Install the product: To tell our Zope instance about the new product, we must update the buildout.cfg file as follows:

      [buildout]
      ...
      eggs =
      ...
      pox.portlet.banner
      ...
      develop =
      src/pox.portlet.banner

      We can automatically install the product during buildout. Add a pox.portlet.banner line inside the products parameter of the [plonesite] part:

      [plonesite]
      recipe = collective.recipe.plonesite
      ...
      products =
      ...
      pox.portlet.banner

    3. Build your instance and, if you want to, launch it to see the new empty Banner portlet:

      ./bin/buildout
      ./bin/instance fg

    4. Check the new portlet: After implementing the changes above, if you click on the Manage portlets link in the site's home page (or anywhere in the Plone site), you will see a new Banner portlet option in the drop-down menu. A new box in the portlet column will then be shown.

    The Header/Body text/Footer box shown above matches the template definition in the bannerportlet.pt file the way paster created it.

    Plone 3 Products Development Cookbook 70 simple but incredibly effective recipes for creating your own feature rich, modern Plone add-on products by diving into its development framework
    Published: May 2010
    eBook Price: $26.99
    Book Price: $44.99
    See more
    Select your format and quantity:

    (For more resources on Plone, see here.)

    How it works...

    Let's have a look at the different folders and files paster has just created:

    Portlet component configuration file

    In the pox.portlet.banner main folder, open configure.zcml. It contains the <genericsetup:registerProfile /> directive for this product to be listed in the portal_quickinstaller tool and in the Add-on Products configlet in Plone Control Panel.

    Most important is the <plone:portlet /> directive used to register the generated portlet and to let it available to be used everywhere we can add portlets (left and right columns or user’s dashboard, for instance):

    <!-- If the portlet has no configurable parameters, you
    can remove the EditForm declaration in bannerportlet.py
    and delete the 'editview' attribute from this statement.
    -->
    <plone:portlet
    name="pox.portlet.banner.BannerPortlet"
    interface=".bannerportlet.IBannerPortlet"
    assignment=".bannerportlet.Assignment"
    view_permission="zope2.View"
    edit_permission="cmf.ManagePortal"
    renderer=".bannerportlet.Renderer"
    addview=".bannerportlet.AddForm"
    editview=".bannerportlet.EditForm"
    />

    Portlet registration has several properties which are explained in this table:

    Property

    Description

    name

    The name of the portlet. Think of it as the name of a browser (Zope 3) view.

    interface

    The actual class-used for this portlet display logic-will be the one implementing this interface.

    IBannerPortlet in our example.

    assignment

    The implementing class.

    view_permission/edit_permission

    Zope 3-like permissions to guard the viewing and editing of the portlet.

    renderer

    A class with a special render method to tell what to show in the user interface (generally a ZPT file).

    Other helper methods can be created and they will all be available via the view variable inside the template.

    addview

    An add form class, pretty much like previous add form examples we already saw.

    editview

    Same as above. If the portlet had no configuration options, we could just skip this property.

    Portlet module

    In the automatically created source files, open the bannerportlet.py file to find:

    Element

    Description

    The interface (IBannerPortlet in our case)

    It's empty for the time being, which means that no configuration settings can be applied to the portlet.

    An Assignment class

    The interface implementation class with a @property decorated title method to tell how to call this portlet within the management screen.

    An AddForm class with a special create method

    If our portlet had no settings, AddForm could inherit from base.NullAddForm instead.

    An EditForm class

    A class to handle edition of the portlet configuration options. If the portlet had no settings, we could skip this class.

    GenericSetup import step

    As we said before, there's also a profiles/default folder with this portlets.xml file to tell GenericSetup to register the portlet:

    <?xml version="1.0"?>
    <portlets>

    <!-- Portlet type registrations -->

    <portlet
    addview="pox.portlet.banner.BannerPortlet"
    title="Banner portlet"
    description="Portlet to show banners"
    />
    </portlets>

    Note that the addview property matches the addview in the configure.zcml file described earlier.

    See also

  • Creating an Archetypes product with paster
  • Creating a product package structure
  • Creating the user interface for Zope 3 content types
  • Creating a policy product
  • Protecting operations with permissions
  • Customizing a new portlet according to our requirements

    In the previous recipe, we covered how to create a new portlet package and then we examined some of its most remarkable pieces of code.

    In this task, we will modify some of the previously explained classes and modules to achieve the desired results: to display several Banners inside a single portlet.

    How to do it...

    1. Add a schema to the portlet interface: In the bannerportlet module, change the IBannerPortlet interface definition for the following block of code:

      ...
      from pox.banner.interfaces import ISection
      from plone.app.vocabularies.catalog import \
      SearchableTextSourceBinder
      ...

      class IBannerPortlet(IPortletDataProvider):
      section = schema.Choice(
      title=_(u"Section"),
      description=_(u"Base section where banners will be
      fetched from."),
      required=True,
      source=SearchableTextSourceBinder(
      {'object_provides': ISection.__identifier__,}))

      initial = schema.Int(
      title=_(u"Initial Banner"),
      description=_(u"First banner to show from the above
      Section."))

      final = schema.Int(
      title=_(u"Final Banner"),
      description=_(u"Last banner to show. Leave it empty to
      show all banners."),
      required=False)

    2. Initialize data in the Assignment class: In the same file, change the Assignment class with the following lines:

      class Assignment(base.Assignment):
      implements(IBannerPortlet)

      section = u""
      initial = 1
      final = 0

      def __init__(self, section=u"", initial = 1, final = 0):
      self.section = section
      self.initial = initial
      self.final = final

      @property
      def title(self):
      return "Banner portlet"

    3. Tweak forms by changing widgets: Modify the AddForm and EditForm classes to change the default widget used by a Choice field with this folder-navigation oriented one:

      ...
      from plone.app.form.widgets.uberselectionwidget import \
      UberSelectionWidget
      ...
      class AddForm(base.AddForm):
      form_fields = form.Fields(IBannerPortlet)
      form_fields['section'].custom_widget = UberSelectionWidget

      def create(self, data):
      return Assignment(**data)

      class EditForm(base.EditForm):
      form_fields = form.Fields(IBannerPortlet)
      form_fields['section'].custom_widget = UberSelectionWidget

    4. State what and when to show in the portlet: In the same bannerportlet module, add these lines:

      ...
      from Acquisition import aq_inner
      from pox.banner.interfaces import IBanner
      from plone.memoize import ram
      from time import time

      RAM_CACHE_SECONDS = 60
      ...
      def _banners_cachekey(method, self):
      """
      Returns key used by @ram.cache.
      """
      the_key = [self.data.section,
      self.data.initial,
      self.data.final]
      the_key.append(time() // RAM_CACHE_SECONDS)
      return tuple(the_key)

      class Renderer(base.Renderer):
      render = ViewPageTemplateFile('bannerportlet.pt')

      @property
      def available(self):
      return len(self.banners())
      @ram.cache(_banners_cachekey)
      def banners(self):
      """ Returns banner HTML from selected section
      """
      banners=[]
      context = aq_inner(self.context)
      # Get the catalog
      catalog = getToolByName(context, 'portal_catalog')
      if self.data.section:
      # Is it a min:max or just min: query?
      if not self.data.final:
      position_range = "min:"
      range = (self.data.initial - 1, )
      elif self.data.final == self.data.initial:
      position_range = ""
      range = self.data.initial - 1
      else:
      position_range = "min:max"
      range = (self.data.initial - 1,
      self.data.final - 1)

      # From the selected section
      # get all the banners
      # in range
      banners = catalog(
      path =
      {"query": '/'.join(portal.getPhysicalPath()) + \
      self.data.section,
      "depth": 1},
      object_provides = IBanner.__identifier__,
      getObjPositionInParent =
      {"query": range,
      "range": position_range})
      banners = [banner.body for banner in banners]
      return banners

    5. Modify the ZPT file to display the portlet: Change bannerportlet.pt with this short TAL-improved XHTML:

      <dl class="portlet portletBannerPortlet"
      i18n:domain="pox.portlet.banner">

      <dt class="portletHeader">
      <span class="portletTopLeft"></span>
      <tal:title i18n:translate="">Advertisement</tal:title>
      <span class="portletTopRight"></span>
      </dt>

      <dd class="portletItem">
      <tal:banners repeat="banner view/banners">
      <div tal:content="structure banner">
      Banner HTML
      </div>
      </tal:banners>
      </dd>
      </dl>

    6. There's one remaining task that is not related to portlet creation, but to this particular portlet. Banner objects store their HTML code in a body field, which is not included by default as metadata (catalog columns) in Plone portal_catalog. To correct this, we have created a catalog.xml file in profiles/default of the pox.banner package to tell us which new metadata columns we need in the catalog:

      <?xml version="1.0"?>
      <object name="portal_catalog" meta_type="Plone Catalog Tool">
      <column value="body"/>
      </object>

    Although this field's name is body, which might be reminiscent of long-formatted text, the contents stored in it are limited: merely the HTML code to render the banner. That's why we included it as metadata in the catalog. Note that it's bad practice to put big fields in catalog.

    Once GenericSetup has created this new column, we must re-index all existing Banners to update their catalog information. To do this, we have also created a custom import step in pox.banner that runs the new reindexBanner method in the setuphandlers module (available in the accompanying source code).

    How it works...

    The code in Step 1 is just another schema definition with three fields. For section, we are using a schema.Choice field, which expects a source argument with the vocabulary to be used. In this case, we are using the very useful SearchableTextSourceBinder method that returns a vocabulary with all objects found in portal_catalog with the passed query. Once we select a Section object, its full path will be stored in the field. You can see how we use it in the banners method in Step 4.

    The changes to the Assignment class in Step 2 provide sensible default values for the three fields in the portlet schema: the highlighted lines show the default values when displaying the add form. The __init__ method is called from the create method in AddForm class (when submitting the portlet configuration data for the first time) and sets the values for every field.

    In Step 4, inside the Renderer class, we created a @property decorated available method that returns True if the portlet should be displayed (that is, has contents to show, is appropriate for the current user, and so on) or False if not.

    The render method is used to tell what to show to the final user, in this case, a ZPT file.

    The banners decorated method is used to get all the Banners to display. Inside the template file, this method will be available via a special view variable: view/banners will return the tuple of found banners.

    In the template in Step 5, the repeat tag uses the banners method from the Renderer class referenced by the view variable.

    See also

  • Creating a folderish content type
  • Creating content types with Dexterity
  • Improving performance by tweaking expensive code
  • Adding configuration options in Plone control panel
  • Plone 3 Products Development Cookbook 70 simple but incredibly effective recipes for creating your own feature rich, modern Plone add-on products by diving into its development framework
    Published: May 2010
    eBook Price: $26.99
    Book Price: $44.99
    See more
    Select your format and quantity:

    (For more resources on Plone, see here.)

    Testing portlets

    When working with portlets, we can test two things:

    • Whether the portlet is correctly installed and ready to be used
    • Whether the portlet shows what we wanted under the expected conditions

    Fortunately, paster creates a test suite with these two test cases. Given that it can't know what we want to show in any portlet (paster magic doesn't cover Divination, a rather fuzzy discipline), there's a part that we must complete: the test_render method in the test_portlet module.

    If we have a look at test_portlet in the pox.portlet.banner.tests package, we'll find something that we haven't seen so far: a PyUnit test.

    We have already talked about our favorite testing approach (doctest), so we won't give more details here. Nevertheless, paster has done a very good job so we don't need to reinvent the wheel. Let's see our render test

    How to do it...

    1. Update the afterSetup method: Open the test_portlet.py file in tests sub-package and modify the afterSetup method in the TestRenderer class with the following:

      ...
      from zope.publisher.browser import TestRequest
      from zope.annotation.interfaces import IAttributeAnnotatable
      from zope.interface import classImplements
      ...

      class TestRenderer(TestCase):
      def afterSetUp(self):
      #log in as manager to install pox.banner product
      self.setRoles(('Manager', ))
      self.portal.portal_quickinstaller.installProduct(\
      'pox.banner')

      # create a pox-root Section
      self.portal.invokeFactory('Section', 'pox-root')

      # Then, 2 Sections with 2 Banners each are created.
      folder = getattr(self.portal, 'pox-root')
      folder.invokeFactory('Section', 'mars')
      folder.invokeFactory('Section', 'earth')
      folder.mars.invokeFactory('Banner', 'banner1')
      folder.mars.banner1.body = "<span>Good bye</span>"
      folder.mars.banner1.reindexObject()
      folder.mars.invokeFactory('Banner', 'banner2')
      folder.mars.banner2.body = "<span>Mars</span>"
      folder.mars.banner2.reindexObject()
      folder.earth.invokeFactory('Banner', 'banner1')
      folder.earth.banner1.body = "<span>Hello</span>"
      folder.earth.banner1.reindexObject()
      folder.earth.invokeFactory('Banner', 'banner2', )
      folder.earth.banner2.body = "<span>World!</span>"
      folder.earth.banner2.reindexObject()

    2. In the same module and class, update the test_render method:

      def test_render(self):
      classImplements(TestRequest, IAttributeAnnotatable)
      # new portlet with "mars" as the configurable section
      r = self.renderer(
      context = self.portal,
      request=TestRequest(),
      assignment = bannerportlet.Assignment(section=
      '/pox-root/mars')
      )
      r = r.__of__(self.folder)
      r.update()
      # render the portlet and start the tests
      output = r.render()
      self.failUnless('<span>Good bye</span>' in output)
      self.failUnless('<span>Mars</span>' in output)
      self.failIf('<span>Hello</span>' in output)
      self.failIf('<span>World!</span>' in output)
      # new portlet with "earth" as the configurable section
      r = self.renderer(
      context = self.portal,
      request=TestRequest(),
      assignment = bannerportlet.Assignment(section=
      '/pox-root/earth')
      )
      r = r.__of__(self.folder)
      r.update()
      # render the portlet and start the opposite tests
      output = r.render()
      self.failUnless('<span>Hello</span>' in output)
      self.failUnless('<span>World!</span>' in output)
      self.failIf('<span>Good bye</span>' in output)
      self.failIf('<span>Mars</span>' in output)

    3. Test the portlet: Run this command in your buildout folder:

      ./bin/instance test -s pox.portlet.banner

    How it works...

    The code in Step 1 of the How to do it... section creates the correct environment for our test to take place:

    • We make sure that pox.banner is installed in Plone. The afterSetUp method is run before any test method is executed. In this way we can do everything we need for the following tests such as creating Sections and Banners.
    • Then we create a pox-root Section in the root of the site. Bear in mind that the pox.controlpanel product has not been installed for this test run, so we don't have any pox-root section that is already created.

    Step 2 shows the code of the test itself:

    • We have slightly changed the original paster Assignment call to pass the section argument our portlet Assignment class expects.
    • The output variable contains the HTML snippet rendered from the portlet associated ZPT file (bannerportlet.pt in our case).
    • Then the actual tests begin: for a /pox-root/mars Section portlet there should be two banners—Good bye and Mars—and nothing else (neither Hello nor World!).
    • After that, a new configuration is set to our portlet—associated to another Section—and we make the opposed test: for a /pox-root/earth Section portlet, Hello and World! banners are shown, unlike Good bye and Mars that are not displayed.

    Note the use of a special TestRequest method to create new requests every time we call to renderer. This is necessary because the portlet's main method (banners) is decorated with @ram.cache which keeps a cache of the returned value as long as the _banners_cachekey method returns the same value.

    See also

    • Working with paster generated test suites
    • Adding configuration options in Plone control panel

    Assigning portlets automatically

    As we said in the introduction to the article, one of the main advantages of Plone 3.x portlets is the great improvement with regards to the portlets management user interface. Portlets can now be assigned very easily in a friendly and typical Plone administration area.

    However, as Plone developers, we always want to automate things, and if there's a customer requirement that states there must be a certain portlet under certain conditions, then it is not just a wish but a need.

    There are four categories we can use for portlets assignment:

    1. Context: Portlets created in folders via the Manage portlets link.
    2. Group: Portlets associated to a group will be displayed just for those users belonging to it. These portlets can be created or managed when visiting the Group details page in Site Setup
    3. Content type: Portlets related to all objects of an explicit content type. They can be managed in the Types configlet in Plone Control Panel by choosing the selected content type and then clicking on Manage portlets assigned to this content type.
    4. User: Portlets inserted into a member's dashboard. You can view and manage your personal dashboard at http://localhost:8080/plone/dashboard.

    In this task, we will automatically associate a new portlet to a specific content type. Nevertheless, the code presented here can be easily changed to work with other portlet categories.

    Getting ready

    For this task to be more useful, we have created a new portlet inside the pox.video package. This portlet displays a Flash player (FlowPlayer actually) with every Video included as a related item in any object.

    Since we haven't explained this portlet anywhere before, first download the source code. Here is a summarized list of the steps taken to produce the portlet in the pox.video package:

    1. A new inlinevideoportlet.py file was added in the portlets sub-package.
    2. The matching inlinevideoportlet.pt file was also created with the required markup.
    3. The new inlinevideoportlet portlet was registered in the configure.zcml file.
    4. Some JavaScript (jQuery actually) was added to the video.js source file inside the browser/flowplayer folder.
    5. The new portlet was included in the portlets.xml file in the profiles/default folder as a new import step for GenericSetup.

    We encourage you to have a look at this new portlet, especially the Python module and the component configuration (ZCML) file.

    Since the configuration we are going to set involves XNewsItem and Video content types (they are separate products) and it is very specific to this particular project, we have chosen to add a new installation routine (a GenericSetup import step) in the pox.policy package. Get an updated copy of it from here.

    How to do it...

    The following changes will be made in the pox.policy package:

    1. Create this portlets.xml import step in the profiles/default folder:

      <?xml version="1.0"?>
      <portlets>
      <assignment
      category="content_type"
      key="XNewsItem"
      manager="plone.rightcolumn"
      name="inlinevideo"
      type="pox.video.InlineVideoPortlet"
      />
      </portlets>

    2. Create or update metadata.xml in profiles/default to install dependency products:

      <?xml version="1.0"?>
      <metadata>
      <version>1</version>
      <dependencies>
      ...
      <dependency>profile-Products.poxContentTypes:default
      </dependency>
      <dependency>profile-pox.video:default</dependency>
      </dependencies>
      </metadata>

    3. Relaunch and reinstall the pox.policy product for these changes to take effect. After creating and publishing Video and XNewsItem objects and linking them properly (Video as a related item of XNewsItem) you should get a result like this:

    How it works...

    The new portlet assignment in the XML file in Step 1 has five properties:

    • Depending on the portlet group we want to deal with, we should use different combinations for category and key:

      Group

      category

      key

      Context, for portlets in a specific path of the site

      context

      The path of the object we want

      to assign portlets to.

      Group, for portlets to be displayed to users in a certain group

      group

      The group ID.

      Content type, for portlets associated to every object of a content type

      content_type

      The content meta type.

      User, for portlets in user's dashboard

      user

      The user ID. It works just for Dashboard portlet managers.

    • Once we are clear which group of portlets we want to update, we can choose the portlet manager that will render the portlet: typically plone.leftcolumn and plone.rightcolumn (for left and right portlet columns).
    • Plone has a special container for portlets for every combination of category, key, and manager. The name property is actually the portlet ID to be uniquely identified in that container.
    • The type property matches the name of the <portlet /> directive in its configuration file. In our case, this is configure.zcml in the pox.video.portlets package:

      <plone:portlet
      name="pox.video.InlineVideoPortlet"
      interface=".inlinevideoportlet.IInlineVideoPortlet"
      assignment=".inlinevideoportlet.Assignment"
      view_permission="zope2.View"
      edit_permission="cmf.ManagePortal"
      renderer=".inlinevideoportlet.Renderer"
      addview=".inlinevideoportlet.AddForm"
      />

    There's more...

    Assigning portlets programmatically

    Martin Aspeli's Professional Plone Development shows an example of adding context portlets automatically after content creation. You can find it in its accompanying source code at http://www.packtpub.com/Professional-Plone-web-applications-CMS.

    Portlet managers

    We won't cover in this article how to create portlet managers, as there's already a great add-on product that can be used to add them in several places in the final web page: ContentWellPortlets.

    If you need to place even more portlet managers, you can check ContentWellPortlets code and documentation to achieve similar results; consider starting from the browser/configure.zcml file.

    These online tutorials can be also of great help:

    See also

    • Modifying the view of a content type with jQuery
    • Creating a policy product
    • Adding user groups

    Summary

    In this article, we covered:

  • Creating a portlet package
  • Customizing a new portlet according to our requirements
  • Testing portlets
  • Assigning portlets automatically

  • Further resources on this subject:

     

     

     

     


    About the Author :


    Juan Pablo Giménez

    Juan Pablo Giménez is a programmer with more than 10 years of expertise in the FOSS field. He started as a C programmer and Linux sysadmin but quickly turned to web development, founding and directing Rcom, an IT firm based in Rosario, Argentina. Five years ago he started working with Plone, his last and true love.

    Marcos F. Romero

    Marcos F. Romero has been a software developer since 1997. Since 1999 he has worked on numerous projects of sites and web applications. In 2007 he started to participate in Plone projects. For over 10 years he has been interested in Usability as a discipline applicable to everyday activities, which he can luckily actively employ in Inter-Cultura, a company that specializes in this discipline, where he has been working for several years.

    Books From Packt


    Plone 3 Multimedia
    Plone 3 Multimedia

    Plone 3 Site Administration
    Plone 3 Site Administration

    Plone 3 Intranets
    Plone 3 Intranets

    Plone 3 for Education
    Plone 3 for Education

    Plone 3 Theming
    Plone 3 Theming

    jQuery 1.4 Reference Guide
    jQuery 1.4 Reference Guide

    Practical Plone 3: A Beginner's Guide to Building Powerful Websites
    Practical Plone 3: A Beginner's Guide to Building Powerful Websites

    Wordpress and Flash 10x Cookbook
    Wordpress and Flash 10x Cookbook


    Code Download and Errata
    Packt Anytime, Anywhere
    Register Books
    Print Upgrades
    eBook Downloads
    Video Support
    Contact Us
    Awards Voting Nominations Previous Winners
    Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
    Resources
    Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software