Source code for collective.multiworkflow.restapi.serializer.dxcontent
"""Add ``workflow_states`` to `plone.restapi`'s content serialization.
The key sits beside ``review_state`` in the payload of every Dexterity object,
carrying the object's state in every workflow of its chain, formatted as the
``workflow_states`` catalog index holds it.
The patch wraps ``SerializeToJson.__call__`` on the class itself, rather than
binding a subclass in its place. ``SerializeFolderToJson``,
``SerializeCollectionToJson`` and any serializer an add-on derives from them
reach it through ``super().__call__``, so they all carry the key. And because
`plone.restapi`'s ZCML registers the very class that was patched, the order in
which the two packages are configured does not matter.
A subclass overriding ``__call__`` without calling ``super()`` does not carry
the key. Nothing short of patching that subclass as well could change that.
The patch is applied when :mod:`collective.multiworkflow.restapi.serializer` is
imported, which including its ZCML does.
"""
from collective.multiworkflow import logger
from collective.multiworkflow.utils.workflow import formatted_workflow_states
from collective.multiworkflow.utils.workflow import WORKFLOW_STATES
from functools import wraps
from plone.restapi.serializer.converters import json_compatible
from plone.restapi.serializer.dxcontent import SerializeToJson
#: Set once the patch is in place, so a second import cannot wrap the wrapper.
_applied = False
[docs]
def apply_patch() -> None:
"""Wrap ``SerializeToJson.__call__`` so its payload carries ``workflow_states``.
The original method stays reachable as ``__wrapped__``, which is what lets
the spike test assert that upstream still lacks the key.
"""
global _applied
if _applied:
return
original = SerializeToJson.__call__
@wraps(original)
def __call__(
self: SerializeToJson,
version: str | None = None,
include_items: bool = True,
include_expansion: bool = True,
) -> dict:
"""Serialize the object, adding its state in every workflow of its chain.
The states are read from the same object ``review_state`` is read from:
the context itself, or the historical version being serialized.
"""
result = original(
self,
version=version,
include_items=include_items,
include_expansion=include_expansion,
)
obj = self.context if version in (None, "current") else self.getVersion(version)
result[WORKFLOW_STATES] = json_compatible(formatted_workflow_states(obj))
return result
#: Lets a test assert the patch is in place without comparing function
#: identity against a closure.
__call__.patched_by = __name__ # type: ignore[attr-defined]
# method-assign: replacing the method on the class itself is the point of
# the patch; every subclass resolves ``__call__`` through it.
SerializeToJson.__call__ = __call__ # type: ignore[method-assign]
_applied = True
logger.debug("Patched plone.restapi.serializer.dxcontent.SerializeToJson.")