diff --git a/docs/backend/annotations.md b/docs/backend/annotations.md index 7371002fd..cb8e6ea62 100644 --- a/docs/backend/annotations.md +++ b/docs/backend/annotations.md @@ -1,13 +1,455 @@ --- myst: html_meta: - "description": "Annotations store arbitrary values on Python objects—such as a Plone site or HTTP request—for storage and caching purposes." - "property=og:description": "Annotations store arbitrary values on Python objects—such as a Plone site or HTTP request—for storage and caching purposes." + "description": "How to use annotations to store arbitrary values on Python objects in Plone" + "property=og:description": "How to use annotations to store arbitrary values on Python objects in Plone" "property=og:title": "Annotations" - "keywords": "Annotations, plone.memoize, cache, portal, content object" + "keywords": "Plone, annotations, zope.annotation, storage, caching" --- (backend-annotations-label)= # Annotations +Annotations provide a conflict-free way to attach arbitrary attributes to Python objects in Plone. +They are used for storage and caching purposes throughout the system. + + +## Introduction + +The annotation pattern allows you to store additional data on objects without modifying their class definitions. +This is particularly useful when you need to extend existing content types with custom data or settings. + +Plone uses annotations for: + +- Storing behavior data from `plone.behavior` package +- Caching values on HTTP request objects (`plone.memoize` cache decorators) +- Storing settings information on the portal or content objects (various add-on products) +- Assigned portlets and their settings + +The [`zope.annotation`](https://pypi.org/project/zope.annotation/) package provides the core implementation. + + +## HTTP request example + +Store cached values on an HTTP request during the lifecycle of a single request. +This allows you to cache computed values when the same computation function might be called from different, unrelated code paths. + +```python +from zope.annotation.interfaces import IAnnotations + +# Use a non-conflicting key based on your package name +KEY = "mypackage.something" + +annotations = IAnnotations(request) + +value = annotations.get(KEY, None) +if value is None: + # Compute value and store it on request object for further look-ups + value = annotations[KEY] = something() +``` + +You can also use the global request from `zope.globalrequest`: + +```python +from zope.annotation.interfaces import IAnnotations +from zope.globalrequest import getRequest + + +def get_cached_data(): + """Get data, using request annotation as cache.""" + request = getRequest() + KEY = "mypackage.cached_data" + + annotations = IAnnotations(request) + data = annotations.get(KEY, None) + if data is None: + data = annotations[KEY] = expensive_computation() + return data +``` + + +## Content annotations + +### Overview and basic usage + +Annotations are the recommended way to extend Plone content with custom settings. + +- Your add-on can store its settings on the Plone site root object using local utilities or annotations. +- You can store custom settings on individual content objects using annotations. + +By default, annotations store: + +- Behavior data from `plone.behavior` package +- Assigned portlets and their settings + +Example of storing a simple value: + +```python +from zope.annotation.interfaces import IAnnotations + +# Assume context variable refers to some content item + +# Use a non-conflicting key based on your company and package name +KEY = "yourcompany.packagename.magicalcontentnavigationsetting" + +annotations = IAnnotations(context) + +# Store a setting on the content item +annotations[KEY] = True + +# Retrieve the setting +value = annotations.get(KEY, False) +``` + + +### Advanced content annotation + +The above example works for storing simple values. +For more complex data, you can create custom annotation classes. +This example shows how to add a simple "Like / Dislike" counter to a content object. + +```python +class LikeDislike: + """Track likes and dislikes for a content item.""" + + def __init__(self): + self.reset() + + def reset(self): + self._likes = set() + self._dislikes = set() + + def liked_by(self, user_id): + """Record that a user liked this item.""" + self._dislikes.discard(user_id) + self._likes.add(user_id) + + def disliked_by(self, user_id): + """Record that a user disliked this item.""" + self._likes.discard(user_id) + self._dislikes.add(user_id) + + def status(self): + """Return the count of likes and dislikes.""" + return len(self._likes), len(self._dislikes) +``` + +```{important} +Ensure your custom annotation class can be [pickled](https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled). +In Zope, this means you cannot hold references to content objects directly in your annotation. +Use the UID of a content object if you need to keep a reference. +``` + +The recommended pattern to get (and create if not existing) your annotation: + +```python +from zope.annotation.interfaces import IAnnotations +from zope.annotation.interfaces import IAttributeAnnotatable + +KEY = "content.like.dislike" # Best practice: define in a config module + + +def get_likes_dislikes_for(item): + """Factory for LikeDislike as annotation of content. + + Args: + item: Any annotatable object (any Plone content) + + Returns: + LikeDislike instance for this item + """ + # Ensure the item is annotatable + if not IAttributeAnnotatable.providedBy(item): + raise TypeError("Item must be annotatable") + + annotations = IAnnotations(item) + return annotations.setdefault(KEY, LikeDislike()) +``` + +This pattern ensures that: + +- You won't create annotations on objects that can't support them. +- A new annotation is created for your context object if it doesn't already exist. +- You can work with your `LikeDislike` annotation object like any Python object. + All attribute changes will be stored automatically in the annotations. + + +### Wrapping your annotation with an adapter + +The `zope.annotation` package provides a `factory()` function that transforms an annotation class into an adapter. +Annotations created this way have location awareness, with `__parent__` and `__name__` attributes. + +Here's the improved example using `zope.annotation.factory()`: + +```python +from zope.annotation import factory +from zope.annotation.interfaces import IAnnotations +from zope.component import adapter +from zope.interface import Interface +from zope.interface import implementer + +KEY = "content.like.dislike" + + +class ILikeDislike(Interface): + """Interface for like/dislike annotation.""" + + def reset(): + """Reinitialize counters.""" + + def liked_by(user_id): + """Record that a user liked this item.""" + + def disliked_by(user_id): + """Record that a user disliked this item.""" + + def status(): + """Return tuple of (likes_count, dislikes_count).""" + + +@implementer(ILikeDislike) +@adapter(Interface) # Adapts any content; use a specific interface for targeted behavior +class LikeDislike: + """Track likes and dislikes for content items.""" + + def __init__(self): + # Unlike regular adapters, the constructor takes no arguments. + # Access the annotated object through self.__parent__ + self.reset() + + def reset(self): + self._likes = set() + self._dislikes = set() + + def liked_by(self, user_id): + self._dislikes.discard(user_id) + self._likes.add(user_id) + + def disliked_by(self, user_id): + self._likes.discard(user_id) + self._dislikes.add(user_id) + + def status(self): + return len(self._likes), len(self._dislikes) + + +# Create the adapter factory +LikeDislikeFactory = factory(LikeDislike, key=KEY) +``` + +Register the adapter in ZCML: + +```xml + +``` + +Or register programmatically: + +```python +from zope.component import provideAdapter + +provideAdapter(LikeDislikeFactory) +``` + +Usage: + +```python +# Get a content item +item = portal["my-document"] + +# Get its annotation via the adapter +like_dislike = ILikeDislike(item) + +# Use the annotation +like_dislike.liked_by("joe") +like_dislike.disliked_by("jane") + +assert like_dislike.status() == (1, 1) +assert like_dislike.__parent__ is item +assert like_dislike.__name__ == KEY +``` + +```{tip} +Read the full documentation in the `README.txt` file in the `zope.annotation` package for more advanced usages. +``` + + +### Using annotations in behaviors + +The most common use of annotations in Plone 6 is through behaviors. +The `plone.behavior` package uses `AnnotationStorage` to store behavior data. + +Example behavior using annotation storage: + +```python +from plone.autoform.interfaces import IFormFieldProvider +from plone.behavior import AnnotationStorage +from plone.supermodel import model +from zope import schema +from zope.interface import provider + + +@provider(IFormFieldProvider) +class IReviewers(model.Schema): + """Behavior to assign reviewers to content.""" + + official_reviewers = schema.List( + title="Official Reviewers", + description="Users who are official reviewers", + value_type=schema.TextLine(), + required=False, + ) + + unofficial_reviewers = schema.List( + title="Unofficial Reviewers", + description="Users who are unofficial reviewers", + value_type=schema.TextLine(), + required=False, + ) +``` + +Register the behavior in ZCML with annotation storage: + +```xml + +``` + +The `AnnotationStorage` factory automatically stores field values in annotations, using the behavior interface's identifier as the annotation key. + + +### Cleaning up content annotations + +```{warning} +If you store Python objects in annotations, you need to clean them up during add-on uninstallation. +Otherwise, if the Python code is removed, you can no longer import or export the Plone site. +Annotations are pickled objects in the database, and pickles don't work if the code is not present. +``` + +Here's how to clean up annotations on content objects: + +```python +from io import StringIO +from zope.annotation.interfaces import IAnnotations +from plone.dexterity.interfaces import IDexterityContainer + + +def clean_up_content_annotations(portal, names): + """Remove annotation entries from content objects in a Plone site. + + This is useful for removing objects that might make the site + un-exportable when add-on code has been removed. + + Args: + portal: Plone site object + names: List of annotation key names to remove + + Returns: + StringIO with log output + """ + output = StringIO() + + def recurse(context): + """Recurse through all content on the Plone site.""" + annotations = IAnnotations(context) + + for name in names: + if name in annotations: + print( + f"Cleaning up annotation {name} on item {context.absolute_url()}", + file=output + ) + del annotations[name] + + # Only recurse into actual folders + if IDexterityContainer.providedBy(context): + for item_id, item in context.contentItems(): + recurse(item) + + recurse(portal) + return output +``` + +You can call this from an uninstall profile or upgrade step: + +```python +def uninstall(context): + """Uninstall handler.""" + portal = context.getSite() + clean_up_content_annotations( + portal, + ["mypackage.annotation.key1", "mypackage.annotation.key2"] + ) +``` + + +### Make your code persistence-free + +There's an issue with custom annotation classes: they create new persistent classes, so your data requires your source code. +This makes your code hard to uninstall, requiring both backward compatibility code and database cleanup. + +An alternative pattern is to use existing persistent base classes instead of creating your own: + +- `BTrees` (for large data sets) +- `persistent.list.PersistentList` +- `persistent.mapping.PersistentMapping` + +Example using `PersistentMapping`: + +```python +from persistent.mapping import PersistentMapping +from zope.annotation.interfaces import IAnnotations + +KEY = "mypackage.likes" + + +def get_likes_for(item): + """Get likes data using PersistentMapping. + + This approach doesn't require custom persistent classes. + """ + annotations = IAnnotations(item) + + if KEY not in annotations: + annotations[KEY] = PersistentMapping({ + "likes": set(), + "dislikes": set(), + }) + + return annotations[KEY] + + +def like_item(item, user_id): + """Record a like from a user.""" + data = get_likes_for(item) + data["dislikes"].discard(user_id) + data["likes"].add(user_id) + + +def dislike_item(item, user_id): + """Record a dislike from a user.""" + data = get_likes_for(item) + data["likes"].discard(user_id) + data["dislikes"].add(user_id) + + +def get_status(item): + """Get the like/dislike counts.""" + data = get_likes_for(item) + return len(data["likes"]), len(data["dislikes"]) +``` + +This pattern is used by add-ons such as `cioppino.twothumbs` and `collective.favoriting`. + + +## Related content + +- [`zope.annotation` on PyPI](https://pypi.org/project/zope.annotation/) +- {doc}`behaviors` +- {doc}`content-types/index` diff --git a/docs/classic-ui/templates.md b/docs/classic-ui/templates.md index b9c012ab6..166214e29 100644 --- a/docs/classic-ui/templates.md +++ b/docs/classic-ui/templates.md @@ -1,13 +1,974 @@ --- myst: html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" + "description": "Page Templates in Plone Classic UI using TAL, TALES, and METAL" + "property=og:description": "Page Templates in Plone Classic UI using TAL, TALES, and METAL" + "property=og:title": "Templates" + "keywords": "Plone, Classic UI, templates, TAL, TALES, METAL, Chameleon, page templates" --- (classic-ui-templates-label)= # Templates +Page Templates are the primary way to generate HTML output in Plone Classic UI. +They are HTML files enhanced with special attributes written in TAL (Template Attribute Language), TALES (TAL Expression Syntax), and METAL (Macro Expansion for TAL). + +Plone uses [Chameleon](https://chameleon.readthedocs.io/) as its template engine, integrated through the Zope framework. +Chameleon is a fast HTML/XML template engine that implements the ZPT (Zope Page Templates) specification with additional features. + + +(templates-basics-label)= + +## Template basics + +A Page Template is a valid HTML or XML file with special `tal:`, `metal:`, and `i18n:` attributes that control how the template is rendered. + +Here is a minimal example: + +```html + + + + +

Hello, World!

+

Placeholder title

+
+ + + +``` + +The three parts serve different purposes: + +TAL (Template Attribute Language) +: Controls the structure and content of the output. + TAL attributes like `tal:content`, `tal:repeat`, and `tal:condition` modify how elements are rendered. + +TALES (TAL Expression Syntax) +: Defines the syntax for expressions used in TAL attributes. + TALES supports path expressions, Python expressions, string expressions, and more. + +METAL (Macro Expansion for TAL) +: Enables template reuse through macros and slots. + Use METAL to inherit from base templates and define reusable template fragments. + + +(templates-filesystem-vs-ttw-label)= + +## Filesystem vs. TTW templates + +Templates in Plone can be stored in two locations, with important security implications: + +**Filesystem templates** (recommended) +: Templates stored in your add-on package's directory structure, typically in `browser/templates/` or `views`. + These templates run as **trusted code** with full Python capabilities, just like any other code in your package. + They have no security restrictions on Python expressions. + +**TTW (Through-The-Web) templates** +: Templates created and stored in the ZODB through the Zope Management Interface (ZMI). + These templates run with **RestrictedPython** sandboxing for security. + They have limited Python functionality: no `import` statements, restricted builtins, and security-checked attribute access. + +```{note} +Always develop templates on the filesystem in your add-on package. +TTW templates are discouraged for production code because they are harder to maintain, version control, and test. +``` + + +(templates-tal-label)= + +## TAL statements + +TAL uses special attributes to control template rendering. +When an element has multiple TAL attributes, they execute in this order: + +1. `tal:define` +2. `tal:condition` +3. `tal:repeat` +4. `tal:content` or `tal:replace` +5. `tal:attributes` +6. `tal:omit-tag` + + +(templates-tal-define-label)= + +### `tal:define` + +Defines one or more variables for use in the template. + +```html +
+

Portal URL: ${portal_url}

+

User: ${user/getId}

+
+``` + +Use semicolons to define multiple variables. +Variables are available within the element and its children. + +For global variables accessible throughout the template, use `tal:define="global varname expression"`. + + +(templates-tal-condition-label)= + +### `tal:condition` + +Conditionally includes or excludes an element and its children. + +```html +

+ ${context/description} +

+ +

+ Found ${python:len(items)} items. +

+ + +

+ No description available. +

+``` + +If the condition evaluates to a false value, the entire element and all its children are removed from the output. + + +(templates-tal-repeat-label)= + +### `tal:repeat` + +Repeats an element for each item in a sequence. + +```html + + + + + + + +
${brain/Title}${brain/Description}
+``` + +Inside a repeat loop, you have access to the `repeat` variable which provides information about the current iteration: + +| Variable | Description | +|----------|-------------| +| `repeat/item/index` | Zero-based index (0, 1, 2, ...) | +| `repeat/item/number` | One-based index (1, 2, 3, ...) | +| `repeat/item/even` | True for even indices | +| `repeat/item/odd` | True for odd indices | +| `repeat/item/start` | True for the first item | +| `repeat/item/end` | True for the last item | +| `repeat/item/length` | Total number of items | +| `repeat/item/letter` | Lowercase letter (a, b, c, ...) | +| `repeat/item/Letter` | Uppercase letter (A, B, C, ...) | + +Example using repeat variables: + +```html + + + + + +
${repeat/item/number}${item/title}
+``` + + +(templates-tal-content-label)= + +### `tal:content` + +Replaces the content of an element with the expression result. + +```html +

Placeholder Title

+ +

+ This placeholder text will be replaced. +

+``` + +By default, content is HTML-escaped. +To insert raw HTML, use the `structure` keyword: + +```html +
+ Raw HTML will be inserted here. +
+``` + + +(templates-tal-replace-label)= + +### `tal:replace` + +Replaces the entire element (not just its content) with the expression result. + +```html +Placeholder + + + + +``` + + +(templates-tal-attributes-label)= + +### `tal:attributes` + +Sets or modifies HTML attributes dynamically. + +```html + + ${context/title} + + + + + + ... + +``` + +Setting an attribute to `None` removes it from the output. + + +(templates-tal-omit-tag-label)= + +### `tal:omit-tag` + +Removes the element tag but keeps its content. + +```html + + This text appears without any wrapper. + + + +
+ Content here +
+``` + + +(templates-tal-on-error-label)= + +### `tal:on-error` + +Provides error handling for template expressions. + +```html +
+

+ This might raise an exception. +

+
+``` + + +(templates-tal-block-label)= + +### Pure TAL blocks + +When you need TAL logic without generating HTML elements, use the `tal:block` style elements: + +```html + + +

${item/Title}

+
+
+``` + +You can use any tag name with the `tal:` prefix: + +```html + +

${item/Title}

+
+``` + + +(templates-tal-switch-label)= + +### `tal:switch` and `tal:case` (Chameleon extension) + +Chameleon provides switch/case statements as an extension to standard TAL: + +```html +
+

This is a document.

+

This is a news item.

+

This is an event.

+

This is something else.

+
+``` + + +(templates-tales-label)= + +## TALES expressions + +TALES (TAL Expression Syntax) defines how expressions are evaluated in templates. +Plone uses **path expressions** as the default expression type. + + +(templates-tales-path-label)= + +### Path expressions + +Path expressions traverse object attributes and items using `/` as a separator. + +```html + +

Title

+ + +

Search term

+ + +

URL

+ + +

Items from view

+``` + +The pipe operator `|` provides fallback values: + +```html + +

Fallback

+ + +

Optional

+ + +

Desc

+``` + +```{warning} +The `|` operator catches missing attributes and `None` values, but it also catches other errors. +Use it sparingly to avoid hiding bugs. +A typo like `context/ttle` (missing 'i') will silently use the fallback. +``` + + +(templates-tales-python-label)= + +### Python expressions + +Python expressions allow full Python syntax. +They must be prefixed with `python:`. + +```html +

TITLE

+ +

+ Showing first 10 of ${python:len(items)} items. +

+ +
    +
  • + ${item/title} +
  • +
+ + +

+ Title (Type) +

+``` + +In filesystem templates, you have full Python access. +In TTW templates, RestrictedPython limits what you can do. + +```{important} +In Plone templates, the default expression type is `path:`, not `python:`. +You must explicitly use the `python:` prefix for Python expressions. +This differs from standalone Chameleon where Python is the default. +``` + + +(templates-tales-string-label)= + +### String expressions + +String expressions create formatted strings with variable interpolation. + +```html + + Edit + + +

+ Greeting +

+ + +``` + +Inside `${...}` you can use any TALES expression, but the default is path. + + +(templates-tales-not-label)= + +### The `not:` expression + +Negates a boolean expression. + +```html +

+ No description available. +

+ +
+ No items found. +
+``` + + +(templates-tales-exists-label)= + +### The `exists:` expression + +Tests whether a path exists without evaluating it. + +```html +

+ Custom field exists: ${context/custom_field} +

+``` + + +(templates-tales-nocall-label)= + +### The `nocall:` expression + +Returns an object without calling it. + +```html + + + Method: ${python:method.__name__} + +``` + + +(templates-built-in-variables-label)= + +## Built-in variables + +Plone templates have access to several built-in variables: + +| Variable | Description | +|----------|-------------| +| `context` | The content object the view is called on | +| `view` | The browser view instance | +| `request` | The current HTTP request object | +| `template` | The template object itself | +| `options` | Additional options passed to the template | +| `nothing` | Equivalent to Python's `None` | +| `default` | Special value to keep the original content | +| `repeat` | Dictionary of repeat variables in loops | +| `attrs` | Original attributes of the current element (in METAL) | + +Additional variables available through helper views: + +```html + +

Portal URL: ${portal_url}

+

User: ${user/getId}

+

Please log in.

+
+``` + + +(templates-metal-label)= + +## METAL macros + +METAL enables template reuse through macros and slots. + + +(templates-metal-define-macro-label)= + +### Defining macros + +Create a reusable template fragment: + +```html + + + + +``` + + +(templates-metal-use-macro-label)= + +### Using macros + +Include a macro in another template: + +```html + +
+ Placeholder for user info +
+ + + + ... + +``` + + +(templates-metal-slots-label)= + +### Defining and filling slots + +Slots allow customization of macro content: + +```html + + +
+
+ Default Header +
+
+ Default Body +
+
+
+ + +
+ +

Custom Header

+
+ +

Custom body content here.

+
+
+``` + + +(templates-main-template-label)= + +### The `main_template` + +Plone's `main_template` provides the full page structure. +Most views fill slots in this template: + +```html + + + + + +

My Custom Content

+

Description

+
+ + + +``` + +Common slots in `main_template`: + +| Slot | Description | +|------|-------------| +| `top_slot` | For setting request parameters (e.g., disabling columns) | +| `head_slot` | Additional content in the HTML `` | +| `style_slot` | For additional CSS | +| `javascript_head_slot` | For additional JavaScript in head | +| `content` | The entire content area | +| `content-core` | The main content area (most commonly used) | +| `content-title` | The page title area | +| `content-description` | The description area | + +Example disabling columns: + +```html + + + +``` + + +(templates-interpolation-label)= + +## Expression interpolation + +Chameleon supports `${...}` syntax for inline expression interpolation in text and attribute values. + + +(templates-interpolation-text-label)= + +### Text interpolation + +```html +

Welcome, ${user/fullname}!

+ +

The current time is ${python:datetime.now().strftime('%H:%M')}.

+ +

You have ${python:len(items)} items in your cart.

+``` + +```{important} +In Plone, `${...}` uses path expressions by default. +Use `${python:...}` for Python expressions. +``` + + +(templates-interpolation-attributes-label)= + +### Attribute interpolation + +```html +Edit + +${context/title} + +
+ ... +
+``` + + +(templates-interpolation-structure-label)= + +### Structure interpolation + +To insert raw HTML without escaping: + +```html +
${structure:context/text/output}
+ + +``` + + +(templates-code-blocks-label)= + +### Python code blocks + +Chameleon allows inline Python code blocks (filesystem templates only): + +```html +
+ +

${greeting}, ${user/fullname}!

+
+``` + +```{warning} +Python code blocks work only in filesystem templates. +TTW templates may skip or restrict these blocks for security. +Use sparingly—complex logic belongs in the view class. +``` + + +(templates-i18n-label)= + +## Internationalization (i18n) + +Templates support translation markup for internationalization. + + +(templates-i18n-domain-label)= + +### Setting the translation domain + +```html + + ... + +``` + + +(templates-i18n-translate-label)= + +### Translating text + +```html + +

Welcome to our site!

+ + +

Welcome to our site!

+ + + +``` + + +(templates-i18n-attributes-label)= + +### Translating attributes + +```html + + +Company Logo +``` + + +(templates-i18n-name-label)= + +### Variable substitution in translations + +```html +

+ Welcome, ${user/fullname}! +

+ + +``` + + +(templates-best-practices-label)= + +## Best practices + + +(templates-best-practices-logic-label)= + +### Keep logic in Python + +Templates should focus on presentation. +Move complex logic to view classes: + +```python +# In your view class +class MyView(BrowserView): + + def get_formatted_items(self): + items = self.context.get_items() + return [ + { + 'title': item.title, + 'url': item.absolute_url(), + 'css_class': 'featured' if item.featured else 'normal', + } + for item in items + if item.is_published() + ] +``` + +```html + + +``` + + +(templates-best-practices-readable-label)= + +### Write readable templates + +Use meaningful variable names: + +```html + + + +``` + + +(templates-best-practices-escape-label)= + +### Always escape user content + +Content is escaped by default. +Only use `structure` when you trust the content: + +```html + +

User input

+ + +
Rich text
+``` + + +(templates-best-practices-fallbacks-label)= + +### Provide fallbacks + +Handle missing or empty values gracefully: + +```html +

+ Description +

+ + +``` + + +(templates-debugging-label)= + +## Debugging templates + + +(templates-debugging-variables-label)= + +### Inspecting available variables + +Use `context` to see all available variables: + +```html +
+
${name}
+
${python:repr(context[name])[:100]}
+
+``` + + +(templates-debugging-pdb-label)= + +### Using the debugger + +In filesystem templates, you can use pdb: + +```html + +``` + + +(templates-debugging-errors-label)= + +### Common errors + +**`KeyError` or `AttributeError`** +: Check for typos in path expressions. + Use `exists:` to test if a path exists. + +**Unexpected output** +: Remember that `tal:content` replaces content, `tal:replace` replaces the entire element. + +**Expression not evaluated** +: Ensure you're using the correct expression type prefix (`python:`, `string:`). + +**Encoding errors** +: Ensure your template files are saved as UTF-8. + + +(templates-reference-label)= + +## Quick reference + + +(templates-reference-tal-label)= + +### TAL statements + +| Statement | Purpose | Example | +|-----------|---------|---------| +| `tal:define` | Define variables | `tal:define="title context/title"` | +| `tal:condition` | Conditional rendering | `tal:condition="context/description"` | +| `tal:repeat` | Loop over items | `tal:repeat="item items"` | +| `tal:content` | Replace element content | `tal:content="context/title"` | +| `tal:replace` | Replace entire element | `tal:replace="context/title"` | +| `tal:attributes` | Set HTML attributes | `tal:attributes="href context/absolute_url"` | +| `tal:omit-tag` | Remove element, keep content | `tal:omit-tag=""` | +| `tal:on-error` | Error handling | `tal:on-error="string:Error"` | + + +(templates-reference-tales-label)= + +### TALES expression types + +| Type | Prefix | Default in Plone | Example | +|------|--------|------------------|---------| +| Path | `path:` | Yes | `context/title` | +| Python | `python:` | No | `python:len(items)` | +| String | `string:` | No | `string:Hello ${name}` | +| Not | `not:` | No | `not:context/description` | +| Exists | `exists:` | No | `exists:context/image` | +| Nocall | `nocall:` | No | `nocall:context/method` | + + +(templates-reference-metal-label)= + +### METAL statements + +| Statement | Purpose | Example | +|-----------|---------|---------| +| `metal:define-macro` | Define reusable macro | `metal:define-macro="card"` | +| `metal:use-macro` | Use a macro | `metal:use-macro="context/@@macros/card"` | +| `metal:define-slot` | Define customizable slot | `metal:define-slot="content"` | +| `metal:fill-slot` | Fill a slot | `metal:fill-slot="content"` | + + +## See also + +- {doc}`views` +- {doc}`viewlets` +- {doc}`template-global-variables` +- [Chameleon documentation](https://chameleon.readthedocs.io/) +- [Plone Training: Page Templates](https://training.plone.org/mastering-plone-5/zpt.html) diff --git a/docs/classic-ui/views.md b/docs/classic-ui/views.md index e2a7097be..273b9a5a0 100644 --- a/docs/classic-ui/views.md +++ b/docs/classic-ui/views.md @@ -715,17 +715,17 @@ However, in most cases: Replace links to Plone 5.2 docs with links to Plone 6 docs. Specifically: -- [TAL page template](https://5.docs.plone.org/adapt-and-extend/theming/templates_css/template_basics) -- [interface](https://5.docs.plone.org/develop/addons/components/interfaces) +- {doc}`TAL page template ` +- {doc}`interface ` ``` - Full Plone page views are a subclass of [`Products.Five.browser.BrowserView`](https://github.com/zopefoundation/Zope/blob/d1814d0a6bddb615629b552de10e9aa5ad30a6da/src/Products/Five/browser/__init__.py#L20) which is a wrapper class. It wraps [`zope.publisher.browser.BrowserView`](https://github.com/zopefoundation/zope.publisher/blob/dea3d4757390d04f6a5b53e696f08d0cab5f6023/src/zope/publisher/browser.py#L958), and adds an acquisition (parent traversal) support for it. -- Views have an attribute `index`, which points to a [TAL page template](https://5.docs.plone.org/adapt-and-extend/theming/templates_css/template_basics) that is responsible for rendering the HTML code. +- Views have an attribute `index`, which points to a {doc}`TAL page template ` that is responsible for rendering the HTML code. You get the HTML output with `self.index()`. The page template gets a context argument `view`, pointing to the view class instance. The `index` value is usually an instance of [`Products.Five.browser.pagetemplate.ViewPageTemplateFile`](https://github.com/zopefoundation/Zope/blob/d1814d0a6bddb615629b552de10e9aa5ad30a6da/src/Products/Five/browser/pagetemplatefile.py#L35) for full Plone pages or [`zope.pagetemplate.pagetemplatefile.PageTemplateFile`](https://github.com/zopefoundation/zope.pagetemplate/blob/14ba59c98e12517b9f8abcdb24bc882bb435ed7c/src/zope/pagetemplate/pagetemplatefile.py#L43) for HTML snippets without using acquisition. -- View classes should implement the [interface](https://5.docs.plone.org/develop/addons/components/interfaces) +- View classes should implement the {doc}`interface ` [`zope.browser.interfaces.IBrowserView`](https://github.com/zopefoundation/zope.browser/blob/1239c75e4e190df992bf34a88b4ead2c952afe86/src/zope/browser/interfaces.py#L27). Views that render page snippets and parts can be direct subclasses of `zope.publisher.browser.BrowserView`, as snippets might not need acquisition support which adds some overhead to the rendering process. @@ -1126,4 +1126,4 @@ One workaround to avoid this mess is to use `aq_inner` when accessing `self.obj` - {doc}`/classic-ui/layers` - {doc}`/classic-ui/templates` -- {doc}`/classic-ui/viewlets` \ No newline at end of file +- {doc}`/classic-ui/viewlets`