Responsible Unit

The Responsible Unit feature allows assigning an organizational ContentPage as the responsible unit for content pages. It supports inheritance through the content tree, so a responsible unit set on a section-level page automatically applies to all its children. The feature provides REST API endpoints for frontend consumption and a viewlet for the HTML view.

Overview

A responsible unit is a ContentPage from the organisation area of the site. The feature supports:

  • Assigning a responsible unit to any page that has the behavior enabled

  • Automatic inheritance through the content tree (children inherit from their nearest ancestor)

  • Overriding the inherited value by setting a different responsible unit on a child page

  • Blocking inheritance entirely so a page (and its descendants) have no responsible unit

  • REST API endpoints to retrieve the responsible unit and its back-references

  • A viewlet displaying all pages that reference the current page as their responsible unit

Schema Fields

responsible_unit

A relation field to select a ContentPage as the responsible organizational unit. The reference browser widget restricts selection to ContentPage objects. Optional field.

responsible_unit_blocked

A boolean checkbox to block responsible unit inheritance from parent pages. When checked, the page and its descendants will not inherit a responsible unit from ancestors. Default: False.

Inheritance Logic

The responsible unit for a given page is determined by walking up the content tree from the current page towards the navigation root:

  1. If the current page has a responsible_unit relation set, that value is returned

  2. If the current page has responsible_unit_blocked checked (and no own relation), None is returned – inheritance stops

  3. Otherwise, the same check is repeated on the parent page

  4. The walk stops at the navigation root, which is never checked itself

This means:

  • Set a responsible unit on a section-level page, and all descendant pages inherit it automatically

  • Any descendant can override by setting its own responsible unit

  • Any descendant can block inheritance entirely by checking the blocked flag

  • If a page has both a relation AND the blocked flag, the relation takes precedence (the blocked flag only stops inheritance from parents)

REST API

GET @responsible

Retrieves the responsible unit for the current page. Walks up the content tree to find the nearest ancestor with a responsible unit set and returns the full serialization of the responsible organization page.

http

GET /Plone/topics/my-page/@responsible HTTP/1.1
Host: localhost:8080
Accept: application/json

curl

curl -i -X GET http://localhost:8080/Plone/topics/my-page/@responsible -H "Accept: application/json"

python-requests

requests.get('http://localhost:8080/Plone/topics/my-page/@responsible', headers={'Accept': 'application/json'})

Response when a responsible unit is found:

{
    "@id": "http://localhost:8080/Plone/topics/my-page/@responsible",
    "items": [
        {
            "@id": "http://localhost:8080/Plone/organisation/department-a",
            "@type": "ContentPage",
            "title": "Department A",
            "UID": "abc123..."
        }
    ]
}

Response when no responsible unit is set or inheritance is blocked:

{
    "@id": "http://localhost:8080/Plone/topics/my-page/@responsible",
    "items": []
}

The items array contains at most one element: the full JSON serialization of the responsible ContentPage. The serialization includes all standard fields of the ContentPage.

GET @responsible-backreferences

Returns all pages that reference the current page as their responsible unit. Only pages with direct relations are returned (not pages that inherit via the content tree). Results are sorted alphabetically by title.

http

GET /Plone/organisation/department-a/@responsible-backreferences HTTP/1.1
Host: localhost:8080
Accept: application/json

curl

curl -i -X GET http://localhost:8080/Plone/organisation/department-a/@responsible-backreferences -H "Accept: application/json"

python-requests

requests.get('http://localhost:8080/Plone/organisation/department-a/@responsible-backreferences', headers={'Accept': 'application/json'})

Response:

{
    "@id": "http://localhost:8080/Plone/organisation/department-a/@responsible-backreferences",
    "items": [
        {
            "@id": "http://localhost:8080/Plone/topics/environment",
            "@type": "ContentPage",
            "title": "Environment",
            "UID": "def456..."
        },
        {
            "@id": "http://localhost:8080/Plone/topics/traffic",
            "@type": "ContentPage",
            "title": "Traffic",
            "UID": "ghi789..."
        }
    ]
}

Results are filtered by view permission and effective/expiration dates. Anonymous users will not see pages they do not have permission to view or pages that are inactive.

Back-References Viewlet

A viewlet rendered below the content body displays all pages that reference the current page as their responsible unit. The viewlet is only visible when at least one back-reference exists. Links are sorted alphabetically and point directly to the referencing pages.

Caching

The Responsible Unit feature integrates with the caching system using cache tags, following the same pattern as the @banner endpoint.

Cache Tag Headers

When the endpoints are called by anonymous users, the responses include cache tag headers for CDN invalidation:

@responsible endpoint:

  • responsible – Generic tag for all responsible-related content

  • The UID of the responsible organization page (when one is found)

@responsible-backreferences endpoint:

  • responsible-backreferences – Generic tag for back-reference content

  • The UID of the current context page

Integration Guide

Fetching the Responsible Unit

JavaScript:

async function fetchResponsibleUnit(pageUrl) {
    const response = await fetch(`${pageUrl}/@responsible`, {
        headers: {
            'Accept': 'application/json'
        }
    });
    const data = await response.json();
    if (data.items.length > 0) {
        return data.items[0];
    }
    return null;
}

// Usage
const responsible = await fetchResponsibleUnit('http://localhost:8080/Plone/topics/my-page');
if (responsible) {
    console.log(responsible.title);  // "Department A"
}

Python:

import requests

response = requests.get(
    'http://localhost:8080/Plone/topics/my-page/@responsible',
    headers={'Accept': 'application/json'}
)
items = response.json()['items']
if items:
    print(items[0]['title'])

Fetching Back-References

JavaScript:

async function fetchBackreferences(orgPageUrl) {
    const response = await fetch(`${orgPageUrl}/@responsible-backreferences`, {
        headers: {
            'Accept': 'application/json'
        }
    });
    const data = await response.json();
    return data.items;
}

// Usage
const pages = await fetchBackreferences('http://localhost:8080/Plone/organisation/department-a');
pages.forEach(page => {
    console.log(page.title);
});

Python:

import requests

response = requests.get(
    'http://localhost:8080/Plone/organisation/department-a/@responsible-backreferences',
    headers={'Accept': 'application/json'}
)
for item in response.json()['items']:
    print(item['title'])

File Locations

  • Behavior: wcs/backend/responsible/behavior.py

  • REST API endpoints: wcs/backend/responsible/restapi.py

  • Viewlet: wcs/backend/responsible/viewlet.py

  • Viewlet template: wcs/backend/responsible/templates/backreferences.pt

  • ZCML configuration: wcs/backend/responsible/configure.zcml

  • Tests: wcs/backend/tests/test_responsible.py