Developing bobtemplates.plone templates

Setup dev environment

In the package folder create a virtualenv and install the package:

virtualenv --clear .
./bin/python setup.py develop

Intro

We can have standalone templates and sub-templates for bobtemplates.plone. By convention we will have a python module and a template folder for every template and use some generic functions from the base module.

All templates are living inside the bobtemplates/plone folder, in their own template folder. All module files are placed inside the bobtemplates/plone folder and are referenced from hook commands in the .mrbob.ini file in the template folders. They get called by different mrbob hooks, like pre_render, post_render or pre_question hook and so on.

Standalone templates

Standalone templates are normal templates for mrbob, which are meant to live standalone and are not depending on any other template.

Examples are the buildout and addon templates. For details see the documentation of the mrbob package.

Sub-templates

Sub-templates are templates which are living inside an existing package created by a standalone template like the addon template. These templates extend the existing standalone template structure by new features like a theme or a content_type.

Sub-templates are searching first for the setup.py and a bobtemplate.cfg file inside the package and configure all needed parameters with this information. See also Upgrade existing Plone packages to see how to upgrade an existing package to be compatible with sub-templates. Every sub-template should define a pre_renderer and a post_renderer hook in their .mrbob.ini which points to a method in their sub-templates module.

[template]
pre_render = bobtemplates.plone.<YOURTEMPLATE_MODULE>:pre_renderer
post_render = bobtemplates.plone.<YOURTEMPLATE_MODULE>:post_renderer
from bobtemplates.plone.base import base_prepare_renderer


def pre_renderer(configurator):
    configurator = base_prepare_renderer(configurator)
    configurator.variables['template_id'] = 'content_type'


def post_renderer(configurator):
    """
    """

As you can see, by convention we define a template_id here. We also call the base_prepare_renderer method from the base module first to setup some generic variables. The pre_renderer hook is a good place to set template specific variables. If you need you can override or extend the variables, like we do in the content_type module with the target_folder.

configurator.target_directory = configurator.variables['package_folder']

The post_renderer method is a good place to update configuration files, like we do for example in the theme and content_type sub-templates.

You can also print some useful advice for the developer here, as we do in the vocabulary sub-template for example.

Template Registration

Even though you can use bobtemplates without registration, you should register the template to allow plonecli and future mrbob versions to query for it. The registration is done by adding a Python entry point into the setup.py of bobtemplates.plone and by adding a short method to the bobregistry.py file. You can of course create your own custom package, analog to bobtemplates.plone and register your templates plonecli the same way. This could be used for example for your agency or client specific code structures. If you need help by creating such custom bobtemplates and plonecli integration’s, give us a sign on Gitter: https://gitter.im/plone/plonecli.

Let’s look first on the entry point:

entry_points={
    'mrbob_templates': [
        'plone_addon = bobtemplates.plone.bobregistry:plone_addon',
        'plone_content_type = bobtemplates.plone.bobregistry:plone_content_type',
        'plone_vocabulary = bobtemplates.plone.bobregistry:plone_vocabulary',
    ],

This registers every template globally for mrbob and tools like plonecli. The first part is the global template name and the second part points to a method in the bobregistry module. This method gives back some details for the template.

def plone_vocabulary():
    reg = RegEntry()
    reg.template = 'bobtemplates.plone:vocabulary'
    reg.plonecli_alias = 'vocabulary'
    reg.depend_on = 'plone_addon'
    return reg

The method defines the following things:

  • template: the mrbob template to use
  • plonecli_alias: a short name alias which will be used by plonecli
  • depend_on: an optional global parent template

We use here globally unique template names which have the plone_ prefix. That is because other bobtemplate packages might register templates too and we want to avoid name clashes.

Testing

All templates and sub-templates should have tests for the structure they provide.

These tests will give developers a good starting point to write tests for their own code. Also these tests will be called by Tox and on Travis to make sure that all the structures created by bobtemplates.plone are working and tested.

We run tests for both all the templates with every combination and inside the generated packages.

For example tests could be run only on addon. Alternately, for a package with Dexterity content types, tests could be run first for the add-on template, then inside the package created by the content_type sub-template.

The tests are running after all templates for a case are applied.

To run all tests locally, just run tox without any parameter. You can also run individual tests for a specific environment. To get a list of all environments run tox -l.

$ tox -l
py37-lint
py27-lint
docs
py27-packagetests
py37-packagetests
py27-skeletontests-Plone43-template-addon
py27-skeletontests-Plone51-template-addon
py27-skeletontests-Plone52-template-addon
py37-skeletontests-Plone52-template-addon
py27-skeletontests-Plone43-template-addon_content_type
py27-skeletontests-Plone51-template-addon_content_type
py27-skeletontests-Plone52-template-addon_content_type
py37-skeletontests-Plone52-template-addon_content_type
py27-skeletontests-Plone43-template-addon_view
py27-skeletontests-Plone51-template-addon_view
py27-skeletontests-Plone52-template-addon_view
py37-skeletontests-Plone52-template-addon_view
py27-skeletontests-Plone43-template-addon_viewlet
py27-skeletontests-Plone51-template-addon_viewlet
py27-skeletontests-Plone52-template-addon_viewlet
py37-skeletontests-Plone52-template-addon_viewlet
py27-skeletontests-Plone43-template-addon_portlet
py27-skeletontests-Plone51-template-addon_portlet
py27-skeletontests-Plone52-template-addon_portlet
py37-skeletontests-Plone52-template-addon_portlet
py27-skeletontests-Plone43-template-addon_theme
py27-skeletontests-Plone51-template-addon_theme
py27-skeletontests-Plone52-template-addon_theme
py37-skeletontests-Plone52-template-addon_theme
py27-skeletontests-Plone51-template-addon_theme_barceoneta
py27-skeletontests-Plone52-template-addon_theme_barceoneta
py37-skeletontests-Plone52-template-addon_theme_barceoneta
py27-skeletontests-Plone43-template-addon_vocabulary
py27-skeletontests-Plone51-template-addon_vocabulary
py27-skeletontests-Plone52-template-addon_vocabulary
py37-skeletontests-Plone52-template-addon_vocabulary
py27-skeletontests-Plone43-template-addon_behavior
py27-skeletontests-Plone51-template-addon_behavior
py27-skeletontests-Plone52-template-addon_behavior
py37-skeletontests-Plone52-template-addon_behavior
py27-skeletontests-Plone43-template-addon_restapi_service
py27-skeletontests-Plone51-template-addon_restapi_service
py27-skeletontests-Plone52-template-addon_restapi_service
py37-skeletontests-Plone52-template-addon_restapi_service
py27-skeletontests-Plone43-template-theme_package
py27-skeletontests-Plone51-template-theme_package
coverage-report

You can run just one of them:

tox -e py27-skeletontests-Plone52-template-addon

or call all of the same template but for different Plone versions:

tox -e py27-skeletontests-Plone43-template-addon_content_type,py27-skeletontests-Plone51-template-add-on_content_type,py27-skeletontests-Plone52-template-add-on_content_type

Note

There is no empty space between the list elements!

Running a specific test

The actual tests are written with the pytest module, therefor you can always run them with pytest directly.

To run a specific pytest with Tox, you can pass additional arguments to pytest, buy putting them after the -- parameter.

$ tox -e py36-packagetests -- -k test_set_global_vars

Increase verbosity of Tox/Pytest

tox -e py37-packagetests -vv -- -s

Package tests

Package tests are for testing the code of bobtemplates.plone it self. These code is used to generate and update the structures of the generated packages.

You can find these test in the package-test folder. This is a good place to test everything related to the generation process.

Skeleton tests

Skeleton tests are for testing, that the generated packages are actually work. We generate the packages, with different combinations of sub-templates, build and run the tests inside.

The tests are defined in the directory skeleton-tests and are called by tox as defined in tox.ini.

If you add new test cases (files), make sure that they are in the tox.ini and also included int the Travis matrix, see below!

Skeleton tests it self are using pytest too, but the tests inside the generated packages are Zope tests running by zc.testrunner. Starting from version 4.x, packages generated by bobtemplates.plone are containing also a tox setup by them self. This allows you to easily test your package against multiple Python and Plone versions.

Generating Travis matrix from tox.ini

$ python tox2travis.py
matrix:
    include:
        - env: TOXENV=py37-lint
        python: "3.7"
        - env: TOXENV=py27-lint
        - env: TOXENV=docs
        - env: TOXENV=py27-packagetests
        - env: TOXENV=py37-packagetests
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon
        - env: TOXENV=py27-skeletontests-Plone51-template-addon
        - env: TOXENV=py27-skeletontests-Plone52-template-addon
        - env: TOXENV=py37-skeletontests-Plone52-template-addon
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_content_type
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_content_type
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_content_type
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_content_type
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_view
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_view
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_view
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_view
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_viewlet
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_viewlet
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_viewlet
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_viewlet
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_portlet
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_portlet
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_portlet
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_portlet
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_theme
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_theme
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_theme
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_theme
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_theme_barceoneta
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_theme_barceoneta
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_theme_barceoneta
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_vocabulary
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_vocabulary
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_vocabulary
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_vocabulary
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_behavior
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_behavior
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_behavior
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_behavior
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-addon_restapi_service
        - env: TOXENV=py27-skeletontests-Plone51-template-addon_restapi_service
        - env: TOXENV=py27-skeletontests-Plone52-template-addon_restapi_service
        - env: TOXENV=py37-skeletontests-Plone52-template-addon_restapi_service
        python: "3.7"
        - env: TOXENV=py27-skeletontests-Plone43-template-theme_package
        - env: TOXENV=py27-skeletontests-Plone51-template-theme_package
        - env: TOXENV=coverage-report

replace the current matrix in .travis.yml with the result.