9 min read

 

Professional Plone 4 Development

Professional Plone 4 Development

Build robust, content-centric web applications with Plone 4.

        Read more about this book      

(For more resources on Plone, see here.)

Creating a policy package

Our policy package is just a package that can be installed as a Plone add-on. We will use a GenericSetup extension profile in this package to turn a standard Plone installation into one that is configured to our client’s needs.

We could have used a full-site GenericSetup base profile instead, but by using a GenericSetup extension profile we can avoid replicating the majority of the configuration that is done by Plone.

We will use ZopeSkel to create an initial skeleton for the package, which we will call optilux.policy, adopting the optilux.* namespace for all Optilux-specific packages.

In your own code, you should of course use a different namespace. It is usually a good idea to base this on the owning organization’s name, as we have done here. Note that package names should be all lowercase, without spaces, underscores, or other special characters. If you intend to release your code into the Plone Collective, you can use the collective.* namespace, although other namespaces are allowed too. The plone.* namespace is reserved for packages in the core Plone repository, where the copyright has been transferred to the Plone Foundation. You should normally not use this without first coordinating with the Plone Framework Team.

We go into the src/ directory of the buildout and run the following command:

$ ../bin/zopeskel plone optilux.policy

This uses the plone ZopeSkel template to create a new package called optilux.policy. This will ask us a few questions.

We will stick with “easy” mode for now, and answer True when asked whether to register a GenericSetup profile.

Note that ZopeSkel will download some packages used by its local command support. This may mean the initial bin/zopeskel command takes a little while to complete, and assumes that we are currently connected to the internet.

A local command is a feature of PasteScript, upon which ZopeSkel is built. ZopeSkel registers an addcontent command, which can be used to insert additional snippets of code, such as view registrations or new content types, into the initial skeleton generated by ZopeSkel. We will not use this feature, preferring instead to retain full control over the code we write and avoid the potential pitfalls of code generation. If you wish to use this feature, you will either need to install ZopeSkel and PasteScript into the global Python environment, or add PasteScript to the ${zopeskel:eggs} option in buildout.cfg, so that you get access to the bin/paster command.

Run bin/zopeskel –help from the buildout root directory for more information about ZopeSkel and its options.

Distribution details

Let us now take a closer look at what ZopeSkel has generated for us. We will also consider which files should be added to version control, and which files should be ignored.

Item

Version control

Purpose

setup.py

Yes

Contains instructions for how Setuptools/Distribute (and thus Buildout) should manage the package’s distribution. We will make a few modifications to this file later.

optilux.policy.egg-info/

Yes

Contains additional distribution configuration. In this case, ZopeSkel keeps track of which template was used to generate the initial skeleton using this file.

*.egg

No

ZopeSkel downloads a few eggs that are used for its local command support (Paste, PasteScript, and PasteDeploy) into the distribution directory root. If you do not intend to use the local command support, you can delete these. You should not add these to version control.

README.txt

Yes

If you intend to release your package to the public, you should document it here. PyPI requires that this file be present in the root of a distribution. It is also read into the long_description variable in setup.py. PyPI will attempt to render this as reStructuredText markup (see http://docutils.sourceforge.net/rst.html).

docs/

Yes

Contains additional documentation, including the software license (which should be the GNU General Public License, version 2, for any packages that import directly from any of Plone’s GPL-licensed packages) and a change log.

Changes to setup.py

Before we can progress, we will make a few modifications to setup.py. Our revised file looks similar to the following code, with changes highlighted:

from setuptools import setup, find_packages
import os
version = '2.0'
setup(name='optilux.policy',
version=version,
description="Policy package for the Optilux Cinemas project",
long_description=open("README.txt").read() + "n" +
open(os.path.join("docs", "HISTORY.txt")).read(),
# Get more strings from
# http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
"Framework :: Plone",
"Programming Language :: Python",
],
keywords='',
author='Martin Aspeli',
author_email='[email protected]',
url='http://optilux-cinemas.com',
license='GPL',
packages=find_packages(exclude=['ez_setup']),
namespace_packages=['optilux'],
include_package_data=True,
zip_safe=False,
install_requires=[
'setuptools',
'Plone',
],
extras_require={
'test': ['plone.app.testing',]
},
entry_points="""
# -*- Entry points: -*-

[z3c.autoinclude.plugin]
target = plone
""",
# setup_requires=["PasteScript"],
# paster_plugins=["ZopeSkel"],
)

The changes are as follows:

  1. We have added an author name, e-mail address, and updated project URL. These are used as metadata if the distribution is ever uploaded to PyPI. For internal projects, they are less important.
  2. We have declared an explicit dependency on the Plone distribution, that is, on Plone itself. This ensures that when our package is installed, so is Plone. We will shortly update our main working set to contain only the optilux. policy distribution. This dependency ensures that Plone is installed as part of our application policy.
  3. We have then added a [tests] extra, which adds a dependency on plone. app.testing. We will install this extra as part of the following test working set, making plone.app.testing available in the test runner (but not in the Zope runtime).
  4. Finally, we have commented out the setup_requires and paster_plugins options. These are used to support ZopeSkel local commands, which we have decided not to use. The main reason to comment them out is to avoid having Buildout download these additional dependencies into the distribution root directory, saving time, and reducing the number of files in the build. Also note that, unlike distributions downloaded by Buildout in general, there is no “offline” support for these options.

Changes to configure.zcml

We will also make a minor change to the generated configure.zcml file, removing the line:

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

This directive is used to register the package as an old-style Zope 2 product. The main reason to do this is to ensure that the initialize() function is called on Zope startup. This may be a useful hook, but most of the time it is superfluous, and requires additional test setup that can make tests more brittle.

We can also remove the (empty) initialize() function itself from the optilux/policy/__init__.py file, effectively leaving the file blank. Do not delete __init__.py, however, as it is needed to make this directory into a Python package.

Updating the buildout

Before we can use our new distribution, we need to add it to our development buildout. We will consider two scenarios:

  1. The distribution is under version control in a repository module separate to the development buildout itself. This is the recommended approach.
  2. The distribution is not under version control, or is kept inside the version control module of the buildout itself. The example source code that comes with this article is distributed as a simple archive, so it uses this approach.

Given the approach we have taken to separating out our buildout configuration into multiple files, we must first update packages.cfg to add the new package. Under the [sources] section, we could add:

[sources]
optilux.policy = svn https://some-svn-server/optilux.
policy/trunk

Or, for distributions without a separate version control URL:

[sources]
optilux.policy = fs optilux.policy

We must also update the main and test working sets in the same file:

[eggs]
main =
optilux.policy
test =
optilux.policy [test]

Finally, we must tell Buildout to automatically add this distribution as a develop egg when running the development buildout. This is done near the top of buildout.cfg:

auto-checkout =
optilux.policy

We must rerun buildout to let the changes take effect:

$ bin/buildout

We can test that the package is now available for import using the zopepy interpreter:

$ bin/zopepy
>>> from optilux import policy
>>>

The absence of an ImportError tells us that this package will now be known to the Zope instance in the buildout.

To be absolutely sure, you can also open the bin/instance script in a text editor (bin/instance-script.py on Windows) and look for a line in the sys.path mangling referencing the package.

Working sets and component configuration

It is worth deliberating a little more on how Plone and our new policy package are loaded and configured.

At build time:

  1. Buildout installs the [instance] part, which will generate the bin/instance script.
  2. The plone.recipe.zope2instance recipe calculates a working set from its eggs option, which in our buildout references ${eggs:main}.
  3. This contains exactly one distribution: optilux.policy.
  4. This in turn depends on the Plone distribution which in turn causes Buildout to install all of Plone.

Here, we have made a policy decision to depend on a “big” Plone distribution that includes some optional add-ons. We could also have depended on the smaller Products.CMFPlone distribution (which works for Plone 4.0.2 onwards), which includes only the core of Plone, perhaps adding specific dependencies for add-ons we are interested in.

When declaring actual dependencies used by distributions that contain reusable code instead of just policy, you should always depend on the packages you import from or otherwise depend on, and no more. That is, if you import from Products.CMFPlone, you should depend on this, and not on the Plone meta-egg (which itself contains no code, but only declares dependencies on other distributions, including Products. CMFPlone). To learn more about the rationale behind the Products. CMFPlone distribution, see http://dev.plone.org/plone/ticket/10877.

At runtime:

  1. The bin/instance script starts Zope.
  2. Zope loads the site.zcml file (parts/instance/etc/site.zcml) as part of its startup process.
  3. This automatically includes the ZCML configuration for packages in the Products.* namespace, including Products.CMFPlone, Plone’s main package.
  4. Plone uses z3c.autoinclude to automatically load the ZCML configuration of packages that opt into this using the z3c.autoinclude.plugin entry point target = plone.
  5. The optilux.policy distribution contains such an entry point, so it will be configured, along with any packages or files it explicitly includes from its own configure.zcml file.

LEAVE A REPLY

Please enter your comment!
Please enter your name here