Book and Library

The Book feature provides a comprehensive system for creating structured, hierarchical documentation with automatic table of contents numbering, PDF export capabilities, and user access management. The Library content type serves as a container for organizing multiple books.

Overview

The Book system consists of:

  • Library - Container for organizing multiple books

  • Book - Main container with table of contents, custom styling, and PDF export

  • Chapter - Hierarchical content units with automatic numbering

  • Paragraph - Content blocks within chapters

Key features include:

  • Automatic table of contents generation with hierarchical numbering (1, 1.1, 1.1.1, etc.)

  • PDF export via WeasyPrint integration

  • Custom CSS styling per book

  • User registration and access request workflows

  • REST API with enhanced serialization

Content Types

Library

A container specifically designed for organizing books.

Portal Type: Library

Allowed Content Types: Book only

Book

The main documentation container with chapters and table of contents support.

Portal Type: Book

Allowed Content Types: Chapter only

Schema Fields (via behaviors):

  • custom_css (Text) - Custom CSS with variable substitution

  • include_default_css (Bool) - Include default book CSS (default: True)

  • header_image (NamedBlobImage) - Header image for PDF export

Chapter

Hierarchical content container that can nest other chapters.

Portal Type: Chapter

Allowed Content Types:

  • Chapter - Nested chapters (subchapters)

  • Paragraph - Content blocks

  • TableBlock - Table content

  • FileListingBlock - File listings

  • MediaFolder - Media containers

  • CommentBlock - Comments

Schema Fields:

  • hide (Bool) - Hide from table of contents (default: False)

Paragraph

A content block within chapters with optional TOC visibility.

Table of Contents

The Toc class generates hierarchical chapter numbers automatically.

Number Generation

Chapters receive automatic numbers based on their position in the hierarchy:

  • First chapter: 1

  • Second chapter: 2

  • First subchapter of chapter 1: 1.1

  • Second subchapter of chapter 1: 1.2

  • First subchapter of chapter 2: 2.1

  • Deep nesting: 1.1.1, 1.1.2, 2.1.1, etc.

Usage:

from wcs.backend.book.toc import Toc

# Get chapter number
chapter = context  # A Chapter object
number = Toc(chapter).number()  # Returns "1.1.2" or None if hidden

TOC Visibility

Chapters can be hidden from the table of contents:

# Hide chapter from TOC
chapter.hide = True
chapter.reindexObject()

Hidden chapters:

  • Do not appear in table of contents

  • Return None from Toc(chapter).number()

  • Their children still calculate numbers correctly (skipping the hidden parent in numbering)

Catalog Index

The show_in_toc index enables efficient TOC queries:

# Query chapters visible in TOC
from plone import api

catalog = api.portal.get_tool('portal_catalog')
results = catalog.searchResults(
    portal_type='Chapter',
    show_in_toc=True,
    path='/Plone/my-book'
)

REST API

Enhanced Serialization

Books and chapters include automatic TOC number calculation in REST API responses.

Book Response:

{
    "@id": "http://localhost:8080/Plone/my-book",
    "@type": "Book",
    "title": "User Manual",
    "items": [
        {
            "@id": "http://localhost:8080/Plone/my-book/chapter-1",
            "@type": "Chapter",
            "title": "Introduction",
            "number": "1"
        },
        {
            "@id": "http://localhost:8080/Plone/my-book/chapter-2",
            "@type": "Chapter",
            "title": "Getting Started",
            "number": "2"
        }
    ]
}

Chapter Response:

{
    "@id": "http://localhost:8080/Plone/my-book/chapter-1/subchapter-1",
    "@type": "Chapter",
    "title": "Installation",
    "number": "1.1",
    "items": [
        {
            "@type": "Paragraph",
            "title": "Prerequisites",
            "number": "1.1.1"
        }
    ]
}

GET @book-keywords

Returns all unique keywords in a book with occurrence counts.

http

GET /Plone/my-book/@book-keywords HTTP/1.1
Host: localhost:8080
Accept: application/json

curl

curl -i -X GET http://localhost:8080/Plone/my-book/@book-keywords -H "Accept: application/json"

python-requests

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

Response:

{
    "keywords": [
        {"keyword": "Baurecht", "count": 5},
        {"keyword": "Eigentum", "count": 3},
        {"keyword": "Vertrag", "count": 2}
    ],
    "total": 3
}

GET @contextnavigation

Enhanced context navigation for books with TOC numbers.

http

GET /Plone/my-book/@contextnavigation?expand.contextnavigation.bottomLevel=3 HTTP/1.1
Host: localhost:8080
Accept: application/json

curl

curl -i -X GET 'http://localhost:8080/Plone/my-book/@contextnavigation?expand.contextnavigation.bottomLevel=3' -H "Accept: application/json"

python-requests

requests.get('http://localhost:8080/Plone/my-book/@contextnavigation?expand.contextnavigation.bottomLevel=3', headers={'Accept': 'application/json'})

Response:

{
    "items": [
        {
            "@id": "http://localhost:8080/Plone/my-book/chapter-1",
            "title": "Introduction",
            "number": "1",
            "items": [
                {
                    "@id": "http://localhost:8080/Plone/my-book/chapter-1/overview",
                    "title": "Overview",
                    "number": "1.1"
                }
            ]
        },
        {
            "@id": "http://localhost:8080/Plone/my-book/chapter-2",
            "title": "Getting Started",
            "number": "2"
        }
    ]
}

PDF Export

Books support PDF generation via WeasyPrint integration with an external PDF server.

Configuration

Environment variables:

  • PDFSERVER_URL - PDF server URL (default: http://localhost:8040)

  • PLONE_BACKEND_HOST - Backend URL for PDF server to fetch content

PDF Server Endpoints

POST @pdfserver-convert

Initiates PDF conversion for the current content.

http

POST /Plone/my-book/@pdfserver-convert HTTP/1.1
Host: localhost:8080
Accept: application/json
Content-Type: application/json

curl

curl -i -X POST http://localhost:8080/Plone/my-book/@pdfserver-convert -H "Accept: application/json" -H "Content-Type: application/json"

python-requests

requests.post('http://localhost:8080/Plone/my-book/@pdfserver-convert', headers={'Accept': 'application/json', 'Content-Type': 'application/json'})

Response:

{
    "uid": "conversion-job-uid",
    "status": "pending"
}

GET @pdfserver-status

Check conversion status.

http

GET /Plone/my-book/@pdfserver-status?uid=conversion-job-uid HTTP/1.1
Host: localhost:8080
Accept: application/json

curl

curl -i -X GET 'http://localhost:8080/Plone/my-book/@pdfserver-status?uid=conversion-job-uid' -H "Accept: application/json"

python-requests

requests.get('http://localhost:8080/Plone/my-book/@pdfserver-status?uid=conversion-job-uid', headers={'Accept': 'application/json'})

Response:

{
    "uid": "conversion-job-uid",
    "status": "completed"
}

GET @@pdfserver-download

Download the generated PDF.

http

GET /Plone/my-book/@@pdfserver-download?uid=conversion-job-uid HTTP/1.1
Host: localhost:8080

curl

curl -i -X GET 'http://localhost:8080/Plone/my-book/@@pdfserver-download?uid=conversion-job-uid'

python-requests

requests.get('http://localhost:8080/Plone/my-book/@@pdfserver-download?uid=conversion-job-uid')

Response: PDF file download

WeasyPrint Views

The following views render content for PDF generation:

  • @@view_weasyprint - Main WeasyPrint template for books

  • @@toc_weasyprint - Table of contents for PDF

  • @@chapter_weasyprint - Chapter rendering

Custom CSS

Books support custom CSS with variable substitution:

/* Available variables: $portal_url, $book_url */
.book-header {
    background-image: url($book_url/@@images/header_image);
}

.book-logo {
    background-image: url($portal_url/++resource++images/logo.png);
}

CSS is served via:

  • @@custom-book.css - Custom CSS with variable substitution

  • @@book-variables.css - CSS custom properties for dates and images

User Access Management

The Book feature includes a complete user registration and access request workflow.

Book Owner Behavior

Books can have the IBookOwner behavior for managing access:

Fields:

  • moderator_email - Email for access request notifications

  • fallback_email - Fallback email if moderator is unavailable

  • group - Plone group for book access

Registration Form

A custom registration form (@@register) allows users to:

  1. Create a new account

  2. Select books to request access to

  3. Trigger email notifications to book moderators

Browser View: @@register (available when IBackendBookLayer is active)

Request Book Access

Logged-in users can request access to additional books.

Browser Form: @@request-book-access

REST API:

http

POST /Plone/@request-book-access HTTP/1.1
Host: localhost:8080
Accept: application/json
Content-Type: application/json

{
    "books": ["book-uid-1", "book-uid-2"]
}

curl

curl -i -X POST http://localhost:8080/Plone/@request-book-access -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"books": ["book-uid-1", "book-uid-2"]}'

python-requests

requests.post('http://localhost:8080/Plone/@request-book-access', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'books': ['book-uid-1', 'book-uid-2']})

Response:

{
    "success": true
}

Approve Access

Book moderators can approve access requests via @@book-share:

/Plone/my-book/@@book-share?userid=john.doe

This:

  1. Adds the user to the book’s configured group

  2. Sends a confirmation email to the user

Email Templates

The following email templates are used:

  • new-user-access-book-request-mail - New user registration request

  • user-access-granted - Access approval notification

  • new-user-created-and-access-to-book-mail - Admin-created user with book access

PDF Views

  • @@pdf_view - PDF conversion interface

  • @@pdfserver-download - PDF download

AI Ask (RAG) Configuration

Books can individually enable or disable the AI Ask feature. This allows administrators to control which books expose the AI Ask tab to users.

Prerequisites

  • Global RAG must be enabled via the RAG_ENABLED=true environment variable

  • The book must have rag_enabled set to True

When both conditions are met, the “AI Ask” tab appears in the book’s navigation alongside “Table of Contents” and “Keyword Search”.

Permissions

The rag_enabled field requires the Manage RAG properties permission to edit. By default, only Manager and Site Administrator roles have this permission.

REST API

The rag_enabled field is included in the book’s REST API response:

// JavaScript example using fetch
const response = await fetch('/plone/my-book', {
    headers: {
        'Accept': 'application/json',
        'Authorization': 'Bearer <token>'
    }
});
const book = await response.json();
console.log(book.rag_enabled);  // true or false

Asking Questions

When enabled, the AI Ask feature allows users to ask natural language questions about the book’s content. The system searches through the book’s chapters and paragraphs using hybrid search (combining keyword and semantic matching) and generates answers using an LLM.

The @rag-ask endpoint supports a path parameter to scope questions to a specific book:

// Ask a question scoped to a specific book
const response = await fetch('/plone/@rag-ask', {
    method: 'POST',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        question: 'What is covered in chapter 3?',
        path: '/plone/my-book'
    })
});

See the RAG (Retrieval-Augmented Generation) documentation for complete details on the RAG system configuration and API.

Integration Guide

Querying TOC Structure

from plone import api
from wcs.backend.book.toc import Toc

book = api.content.get(path='/library/my-book')
catalog = api.portal.get_tool('portal_catalog')

# Get all visible chapters
chapters = catalog.searchResults(
    portal_type='Chapter',
    path='/'.join(book.getPhysicalPath()),
    show_in_toc=True,
    sort_on='getObjPositionInParent'
)

for brain in chapters:
    chapter = brain.getObject()
    number = Toc(chapter).number()
    print(f"{number} {chapter.Title()}")

File Locations

Core Module:

  • wcs/backend/book/content.py - Content type classes (Library, Book, Chapter, Paragraph)

  • wcs/backend/book/behaviors.py - Behaviors (IShowInToc, IBookCustomCSS, IBookConfiguration, IBookOwner)

  • wcs/backend/book/toc.py - Table of contents generator

  • wcs/backend/book/utils.py - Utility functions

  • wcs/backend/book/indexer.py - Catalog indexers (show_in_toc, book_keywords)

  • wcs/backend/book/keyword_utils.py - Keyword extraction utilities

  • wcs/backend/book/configure.zcml - ZCML configuration

REST API:

  • wcs/backend/book/restapi.py - Serializers and services

Views:

  • wcs/backend/book/views.py - Browser views

  • wcs/backend/book/templates/ - View templates

PDF Generation:

  • wcs/backend/book/pdfserver_client.py - PDF server client

  • wcs/backend/book/weasyprint/views.py - WeasyPrint views

  • wcs/backend/book/weasyprint/templates/ - PDF templates

  • wcs/backend/book/weasyprint/resources/ - PDF CSS resources

User Management:

  • wcs/backend/book/register_form.py - Registration form

  • wcs/backend/book/emails.py - Email adapters

  • wcs/backend/book/templates/emails/ - Email templates

Type Definitions:

  • wcs/backend/profiles/default/types/Library.xml

  • wcs/backend/profiles/default/types/Book.xml

  • wcs/backend/profiles/default/types/Chapter.xml

  • wcs/backend/profiles/default/types/Paragraph.xml