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:example:: curl python-requests GET /Plone/topics/my-page/@responsible HTTP/1.1 Host: localhost:8080 Accept: application/json **Response when a responsible unit is found:** .. code-block:: json { "@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:** .. code-block:: json { "@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:example:: curl python-requests GET /Plone/organisation/department-a/@responsible-backreferences HTTP/1.1 Host: localhost:8080 Accept: application/json **Response:** .. code-block:: json { "@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:** .. code-block:: 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:** .. code-block:: 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:** .. code-block:: 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:** .. code-block:: 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``