Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more info about RichText field and the usage of transforms and RichTextValue #1648

Merged
merged 9 commits into from
May 9, 2024
167 changes: 167 additions & 0 deletions docs/backend/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ See [`z3c.relationfield`](https://pypi.org/project/z3c.relationfield/) for more
| `RelationChoice` | `RelationValue` | A `Choice` field intended to store `RelationValue`s | See {ref}`Choice <fields-in-zope-schema-label>` |


(backend-fields-richtext-label)=

### Fields in `plone.app.textfield`

See [`plone.app.textfield`](https://pypi.org/project/plone.app.textfield/) for more details.
Expand All @@ -124,6 +126,171 @@ See [`plone.app.textfield`](https://pypi.org/project/plone.app.textfield/) for m
| `RichText` | `RichTextValue` | Stores a `RichTextValue`, which encapsulates a raw text value, the source MIME type, and a cached copy of the raw text transformed to the default output MIME type. | `IField`, `IRichText` |


The RichText field allows for alternative markups and content filtering.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

```python
from plone.app.textfield import RichText
from plone.supermodel import model

class ITestSchema(model.Schema):

body = RichText(title="Body text")
```

The `RichText` field constructor can take the following arguments, in addition to the usual arguments for a `Text` field.

`default_mime_type`
: A string representing the default MIME type of the input markup.
This defaults to `text/html`.

`output_mime_type`
: A string representing the default output MIME type.
This defaults to `text/x-html-safe`, which is a Plone-specific MIME type that disallows certain tags.
Use the {guilabel}`HTML Filtering` control panel in Plone to control the tags.

`allowed_mime_types`
: A tuple of strings giving a vocabulary of allowed input MIME types.
If this is `None` (the default), the allowable types will be restricted to those set in Plone's {guilabel}`Markup` control panel.

The *default* field can be set to either a Unicode object (in which case it will be assumed to be a string of the default MIME type) or a {ref}`backend-fields-richtextvalue-label`.


#### reStructuredText transformation

Below is an example of a field that allows StructuredText and reStructuredText, transformed to HTML by default.

```python
from plone.app.textfield import RichText
from plone.supermodel import model

defaultBody = """\
Background
==========

Please fill this in

Details
-------

And this
"""

class ITestSchema(model.Schema):

body = RichText(
title="Body text",
default_mime_type='text/x-rst',
output_mime_type='text/x-html',
allowed_mime_types=('text/x-rst', 'text/structured',),
MrTango marked this conversation as resolved.
Show resolved Hide resolved
default=defaultBody,
)
```


(backend-fields-richtextvalue-label)=

#### RichTextValue
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

The `RichText` field, most of the time does not store a string.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
Instead, it stores a `RichTextValue` object.
This is an immutable object that has the following properties.

`raw`
: A Unicode string with the original input markup.

`mimeType`
: The MIME type of the original markup, for example `text/html` or `text/structured`.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

`encoding`
: The default character encoding used when transforming the input markup.
Most likely, this will be UTF-8.

`raw_encoded`
: The raw input encoded in the given encoding.

`outputMimeType`
: The MIME type of the default output, taken from the field at the time of instantiation.

`output`
: A Unicode object representing the transformed output.
If possible, this is cached persistently, until the `RichTextValue` is replaced with a new one (as happens when an edit form is saved, for example).

The storage of the `RichTextValue` object is optimized for the case where the transformed output will be read frequently (for example, on the view screen of the content object) and the raw value will be read infrequently (for example, on the edit screen).
Because the output value is cached indefinitely, you will need to replace the `RichTextValue` object with a new one if any of the transformation parameters change.
However, as we will see below, it is possible to apply a different transformation on demand, if you need to.

The code snippet below shows how a `RichTextValue` object can be constructed in code.
In this case, we have a raw input string of type `text/plain` that will be transformed to a default output of `text/html`.
Note that we would normally look up the default output type from the field instance.

```python
from plone.app.textfield.value import RichTextValue

context.body = RichTextValue("Some input text", 'text/plain', 'text/html')
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
```

The standard widget used for a `RichText` field will correctly store this type of object for you, so it is rarely necessary to create one yourself.


##### Using RichText fields in templates
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

If you use a `DisplayForm`, the display widget for the `RichText` field will render the transformed output markup automatically.
If you write TAL manually, you may try something like the following.

```xml
<div tal:content="structure context/body" />
```

This, however, will render a string as follows.

```html
RichTextValue object. (Did you mean <attribute>.raw or <attribute>.output?)
```

The correct syntax is:

```xml
<div tal:content="structure context/body/output" />
```

This will render the cached, transformed output.
This operation is approximately as efficient as rendering a simple `Text` field, since the transformation is only applied once, when the value is first saved.


##### Alternative transformations

Sometimes, you may want to invoke alternative transformations.
Under the hood, the default implementation uses the `portal_transforms` tool to calculate a transform chain from the raw value's input MIME type to the desired output MIME type.
If you need to write your own transforms, take a look at [this tutorial](https://5.docs.plone.org/develop/plone/misc/portal_transforms.html).
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
This is abstracted behind an `ITransformer` adapter to allow alternative implementations.

To invoke a transformation in code, you can use the following syntax.

```python
from plone.app.textfield.interfaces import ITransformer

transformer = ITransformer(context)
transformedValue = transformer(context.body, 'text/plain')
MrTango marked this conversation as resolved.
Show resolved Hide resolved
```

The `__call__()` method of the `ITransformer` adapter takes a `RichTextValue` object and an output MIME type as parameters.

If you write a page template, there is an even more convenient syntax.

```xml
<div tal:content="structure context/@@text-transform/body/text/plain" />
```

The first traversal name gives the name of the field on the context (`body` in this case).
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
The second and third give the output MIME type.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
If the MIME type is omitted, the default output MIME type will be used.

```{note}
Unlike the `output` property, the value is not cached, and so will be calculated each time the page is rendered.
```



### Fields in `plone.schema`

See {ref}`backend-ploneschema-label` for more details.
Expand Down