Add computed fields#

A computed field is a property that the wizard never asks about. Cookieplone renders its default as a Jinja2 expression and stores the result with the other answers, so template files use it like any answer.

The examples on this page come from the example repository docs/_examples/template-features/ in the Cookieplone source tree.

Define a computed field#

In your template's cookieplone.json, set "format": "computed" on a property, and write the expression in default:

{
  "docs_enabled": {
    "type": "string",
    "format": "computed",
    "default": "{{ '1' if cookiecutter.has_docs else '0' }}"
  }
}

Expressions reach the answers through the cookiecutter namespace, for example cookiecutter.has_docs.

Use filters in computed fields#

A computed field can transform an answer with a filter:

{
  "package_path": {
    "type": "string",
    "format": "computed",
    "default": "{{ cookiecutter.python_package_name | package_path }}"
  }
}

Cookieplone's filters are not available by default. List each filter that the template uses in config.extensions:

{
  "config": {
    "extensions": [
      "cookieplone.filters.package_name",
      "cookieplone.filters.package_namespace",
      "cookieplone.filters.package_path",
      "cookieplone.filters.pascal_case"
    ]
  }
}

A filter that is missing from the list stops the generation with an error such as No filter named 'pascal_case'. See Use built-in filters.

Order computed fields#

Cookieplone asks all visible questions first. Then it computes the hidden fields in the order they appear in properties. A computed field can use every answer to a visible question, and the computed fields that appear before it:

{
  "package_path": {
    "type": "string",
    "format": "computed",
    "default": "{{ cookiecutter.python_package_name | package_path }}"
  },
  "module_path": {
    "type": "string",
    "format": "computed",
    "default": "src/{{ cookiecutter.package_path }}"
  }
}

A reference to a computed field that appears later renders as an empty string, without an error. Check the order when a computed value comes out empty.

Add a constant field#

A property with "format": "constant" is hidden too, but Cookieplone uses its default as-is and doesn't render it:

{
  "generator": {
    "type": "string",
    "format": "constant",
    "default": "example-templates 1.0"
  }
}

Check the result#

The example template defines the computed and constant fields above:

{
  "id": "features",
  "schema": {
    "title": "Template features",
    "description": "Validators, computed fields, and filters.",
    "version": "2.0",
    "properties": {
      "project_title": {
        "type": "string",
        "title": "Project title",
        "default": "Example Add-on"
      },
      "project_slug": {
        "type": "string",
        "title": "Project slug",
        "default": "example-addon",
        "validator": "my_validators.no_spaces"
      },
      "python_package_name": {
        "type": "string",
        "title": "Python package name",
        "default": "collective.example_addon"
      },
      "author_email": {
        "type": "string",
        "title": "Author email",
        "default": "jane@example.com",
        "validator": "cookieplone.validators.not_empty"
      },
      "has_docs": {
        "type": "boolean",
        "title": "Add documentation?",
        "default": true
      },
      "package_path": {
        "type": "string",
        "format": "computed",
        "default": "{{ cookiecutter.python_package_name | package_path }}"
      },
      "module_path": {
        "type": "string",
        "format": "computed",
        "default": "src/{{ cookiecutter.package_path }}"
      },
      "class_name": {
        "type": "string",
        "format": "computed",
        "default": "{{ cookiecutter.python_package_name | package_name | pascal_case }}"
      },
      "docs_enabled": {
        "type": "string",
        "format": "computed",
        "default": "{{ '1' if cookiecutter.has_docs else '0' }}"
      },
      "generator": {
        "type": "string",
        "format": "constant",
        "default": "example-templates 1.0"
      }
    }
  },
  "config": {
    "extensions": [
      "cookieplone.filters.package_name",
      "cookieplone.filters.package_namespace",
      "cookieplone.filters.package_path",
      "cookieplone.filters.pascal_case"
    ]
  }
}

Its README.md uses them:

# {{ cookiecutter.project_title }}

- Package: `{{ cookiecutter.python_package_name }}`
- Namespace: `{{ cookiecutter.python_package_name | package_namespace }}`
- Code: `{{ cookiecutter.module_path }}`
- Main class: `{{ cookiecutter.class_name }}`
- Documentation enabled: {{ cookiecutter.docs_enabled }}
- Generated by: {{ cookiecutter.generator }}

With the default answers, the generated README.md contains:

# Example Add-on

- Package: `collective.example_addon`
- Namespace: `collective`
- Code: `src/collective/example_addon`
- Main class: `ExampleAddon`
- Documentation enabled: 1
- Generated by: example-templates 1.0