Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 82 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,87 @@
# kitconcept-keywordmanager 🚀
<div align="center">

<h1 align="center">Keyword Manager for Plone</h1>

</div>

<div align="center">

[![Built with Cookieplone](https://img.shields.io/badge/built%20with-Cookieplone-0083be.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

[![PyPI](https://img.shields.io/pypi/v/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - Plone Versions](https://img.shields.io/pypi/frameworkversions/plone/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)

[![npm](https://img.shields.io/npm/v/@kitconcept/volto-keywordmanager)](https://www.npmjs.com/package/@kitconcept/volto-keywordmanager)
[![](https://img.shields.io/badge/-Storybook-ff4785?logo=Storybook&logoColor=white&style=flat-square)](https://kitconcept.github.io/kitconcept-keywordmanager/)


[![GitHub contributors](https://img.shields.io/github/contributors/kitconcept/kitconcept-keywordmanager)](https://github.com/kitconcept/kitconcept-keywordmanager)
[![GitHub Repo stars](https://img.shields.io/github/stars/kitconcept/kitconcept-keywordmanager?style=social)](https://github.com/kitconcept/kitconcept-keywordmanager)

[![CI](https://github.com/kitconcept/kitconcept-keywordmanager/actions/workflows/main.yml/badge.svg)](https://github.com/kitconcept/kitconcept-keywordmanager/actions/workflows/main.yml)

</div>

The **Keyword Manager** is a Plone 6 add-on that lets content editors keep their site's keywords (also called subjects or tags) clean and consistent — without needing developer support. From a dedicated control panel, editors can rename, merge, and delete keywords, and every content item on the site is updated automatically.

> [!WARNING]
> This add-on is meant to be used in combination with the [volto-light-theme](https://github.com/kitconcept/volto-light-theme). If you plan to use this add-on with plain Volto you will have to write your own styles for it. You can use the existing ones via manual import like this `import "@kitconcept/volto-keywordmanager/theme/_main.scss"` or as reference. Read more about theming [here](https://6.docs.plone.org/volto/theming/theming-a-base-theme.html).
> This add-on is designed to work with [volto-light-theme](https://github.com/kitconcept/volto-light-theme). If your site uses a different theme, you will need to provide your own styles or import the existing ones manually: `import "@kitconcept/volto-keywordmanager/theme/_main.scss"`. See the [Volto theming documentation](https://6.docs.plone.org/volto/theming/theming-a-base-theme.html) for details.

Change, merge and delete keywords (subjects) in Plone 6.
![Keyword Manager](./assets/Keyword_Manager.png)

![Keyword Manger](./assets/Keyword_Manager.png)
## Who is this for?

- **Content editors** who manage tags and subjects on a Plone website and want a clean interface to keep keywords organised.
- **Site administrators and technical staff** who need to install and configure the add-on for their institution's Plone instance.

## Features 🔥

- Control Panel (frontend)
- Configurable (backend)
- REST-API Services (backend)
- Utility (backend)
- **Browse all keywords** currently in use, sorted by name or by number of occurrences.
- **Filter keywords** to quickly find a specific term in a long list.
- **Rename a keyword** — the new name is applied to every content item that uses it automatically.
- **Merge keywords** — combine synonyms, fix typos, or resolve ambiguities by merging multiple keywords into one canonical term; all affected content is updated in one step.
- **Delete keywords** — remove terms that are no longer needed.
- **Manage multiple keyword fields** — works with the standard `Subject` field and any other keyword-type index in the catalog.

## Requirements

### Backend

- Plone 6.1 or 6.2
- Python 3.11, 3.12, or 3.13

### Frontend

- Volto 18 or later (Plone's React-based frontend)
- Node.js 24
- pnpm
- [volto-light-theme](https://github.com/kitconcept/volto-light-theme) (recommended; see warning above)

## Installation 🔧

1. Frontend package:

Add the package to your Volto project.

```shell
pnpm add @kitconcept/volto-keywordmanager
```

Then register it as an add-on in your project's `package.json`.

```json
"addons": [
"@kitconcept/volto-keywordmanager"
],
"dependencies": {
"@kitconcept/volto-keywordmanager": "*"
}
```

> [!IMPORTANT]
> Installing the package without adding it to the `addons` array will not enable the control panel.

1. Backend package:

```shell
Expand All @@ -38,12 +94,13 @@ Change, merge and delete keywords (subjects) in Plone 6.
pip install kitconcept.keywordmanager
```

## Control Panel
Then install the add-on in your Plone site from the **Add-ons** control panel.

> [!NOTE]
> This section is a work in progress. Expect more information in the coming releases.
## Usage

The Keyword Manager allows you to maintain the keywords used in your website. Start by selecting the keyword field you want to manage. You can then sort, filter, rename, merge, or delete individual keywords.
Once installed, the Keyword Manager is available in the Plone site control panel.
Start by selecting the keyword field you want to manage, then sort, filter, rename, merge, or delete individual keywords.
Every content item using an affected keyword is updated automatically.

## Configuration

Expand Down Expand Up @@ -89,7 +146,7 @@ config.ALWAYS_REINDEX = (
)
```

## REST-API Services
## REST API

### GET `/@keywords` (or `/path/to/page/@keywords`)

Expand Down Expand Up @@ -133,6 +190,15 @@ km = getUtility(IKeywordManager)

Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md).

## License

This repository contains two packages, each under its own license.

| Package | Location | License |
| ------- | -------- | ------- |
| `kitconcept.keywordmanager` | [backend/](./backend/) | GPL-2.0-only |
| `@kitconcept/volto-keywordmanager` | [frontend/](./frontend/) | MIT |

## Credits and acknowledgements 🙏

This add-on is based on code from Products.PloneKeywordManager, adapted and extended for Plone 6 & Volto.
Expand All @@ -152,3 +218,5 @@ Building on that foundation, this package was created by the kitconcept GmbH to
There is an ongoing effort to bring keyword-management functionality into Plone core itself, tracked as [PLIP: Keyword Manager](https://github.com/plone/volto/issues/5300). This add-on is intended as a step toward that goal, a working, up-to-date implementation that can inform (and hopefully eventually be folded into) that core integration. Getting there will require several steps: stabilizing the add-on for Plone 6, gathering community feedback, aligning with the Volto/core UI patterns, and going through the PLIP review process. Contributions and feedback toward that end are welcome.

Thanks to Maik Jablonski and everyone who has contributed to Products.PloneKeywordManager over the years for the original work this builds on.

Generated using [Cookieplone (2.0.0a3)](https://github.com/plone/cookieplone) and [cookieplone-templates (cda10db)](https://github.com/plone/cookieplone-templates/commit/cda10db886223a9aa9be1b1368484296418bb880) on 2026-05-29 11:44:37.855709. A special thanks to all contributors and supporters!
160 changes: 124 additions & 36 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
# kitconcept.keywordmanager
<div align="center">

Change, merge and delete keywords (subjects) in Plone.
<h1 align="center">Keyword Manager for Plone</h1>
<h2 align="center">kitconcept.keywordmanager</h2>

## Features
</div>

TODO: List our awesome features
<div align="center">

## Installation
[![PyPI](https://img.shields.io/pypi/v/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - Wheel](https://img.shields.io/pypi/wheel/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - License](https://img.shields.io/pypi/l/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)
[![PyPI - Status](https://img.shields.io/pypi/status/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)

[![PyPI - Plone Versions](https://img.shields.io/pypi/frameworkversions/plone/kitconcept.keywordmanager)](https://pypi.org/project/kitconcept.keywordmanager/)

[![CI](https://github.com/kitconcept/kitconcept-keywordmanager/actions/workflows/main.yml/badge.svg)](https://github.com/kitconcept/kitconcept-keywordmanager/actions/workflows/main.yml)

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

[![GitHub contributors](https://img.shields.io/github/contributors/kitconcept/kitconcept-keywordmanager)](https://github.com/kitconcept/kitconcept-keywordmanager)
[![GitHub Repo stars](https://img.shields.io/github/stars/kitconcept/kitconcept-keywordmanager?style=social)](https://github.com/kitconcept/kitconcept-keywordmanager)

</div>

The backend package for Keyword Manager for Plone — a Plone 6 add-on that lets content editors rename, merge, and delete keywords (subjects/tags) across a site, with all content updated automatically. See also the frontend package [@kitconcept/volto-keywordmanager](https://www.npmjs.com/package/@kitconcept/volto-keywordmanager).

![Keyword Manager](https://raw.githubusercontent.com/kitconcept/kitconcept-keywordmanager/main/assets/Keyword_Manager.png)

## Features 🔥

- **Browse all keywords** currently in use, sorted by name or by number of occurrences.
- **Filter keywords** to quickly find a specific term in a long list.
- **Rename a keyword** — the new name is applied to every content item that uses it automatically.
- **Merge keywords** — combine synonyms, fix typos, or resolve ambiguities by merging multiple keywords into one canonical term; all affected content is updated in one step.
- **Delete keywords** — remove terms that are no longer needed.
- **Manage multiple keyword fields** — works with the standard `Subject` field and any other keyword-type index in the catalog.

## Requirements

- Plone 6.1 or 6.2
- Python 3.11, 3.12, or 3.13

## Installation 🔧

Install kitconcept.keywordmanager with uv.

Expand All @@ -20,7 +56,89 @@ Create the Plone site.
make create-site
```

## Contribute
## Configuration

This package allows for some configuration.

To configure one of the following options, import the config module like so:

```py
from kitconcept.keywordmanager import config
```

### Options

The keywords permission allows you to set a custom permission who should be able to manage keywords.

```py
config.MANAGE_KEYWORDS_PERMISSION = "kitconcept.keywordmanager: Manage Keywords"
```

The meta type of the keyword indexes can be set. This is only useful if you're one of those crazy people that use custom indexes.

```py
config.META_TYPE = "KeywordIndex"
```

There are indexes of `META_TYPE` we know we don't want to manage because bad things will happen. You can exclude those using:

```py
config.IGNORE_INDEXES = [
"object_provides",
"allowedRolesAndUsers",
"getRawRelatedItems",
"getEventType",
"block_types",
]
```

You can set a list of indexes that should always be reindex when merging or deleting keywords on objects. Most people won't need this.

```py
config.ALWAYS_REINDEX = ("SearchableText",)
```

## REST API

### GET `/@keywords` (or `/path/to/page/@keywords`)

| Parameter | Source | Type / Values | Required | Default | Description |
| ------------ | ------ | --------------------------- | -------- | --------- | --------------------------- |
| `idx` | form | string | no | "Subject" | The keyword index to query. |
| `sort_order` | form | "ascending" or "descending" | no | — | The sort order of results. |
| `sort_on` | form | "keyword" or "occurrence" | no | — | The field to sort on. |

### PATCH `/@keywords` (or `/path/to/page/@keywords`)

| Parameter | Source | Type / Values | Required | Default | Description |
| -------------- | ------ | ------------- | -------- | --------- | -------------------------------------- |
| `idx` | form | string | no | "Subject" | The keyword index to query. |
| `new_keyword` | body | string | yes | — | The name of the keyword to be created. |
| `old_keywords` | body | list[string] | yes | — | The old keywords to be deleted. |

### DELETE `/@keywords` (or `/path/to/page/@keywords`)

| Parameter | Source | Type / Values | Required | Default | Description |
| --------- | ------ | ------------- | -------- | --------- | --------------------------------------- |
| `idx` | form | string | no | "Subject" | The keyword index to query. |
| `items` | body | list | yes | — | The name of the keywords to be deleted. |

### GET `/@keywordIndex`

No parameters.

## Utility

Getting the utility.

```py
from kitconcept.keywordmanager.interfaces import IKeywordManager
from zope.component import getUtility

km = getUtility(IKeywordManager)
```

## Contributing 🐛

- [Issue tracker](https://github.com/kitconcept/kitconcept-keywordmanager/issues)
- [Source code](https://github.com/kitconcept/kitconcept-keywordmanager/)
Expand Down Expand Up @@ -48,36 +166,6 @@ make create-site
make install
```


### Add features using `plonecli` or `bobtemplates.plone`

This package provides markers as strings (`<!-- extra stuff goes here -->`) that are compatible with [`plonecli`](https://github.com/plone/plonecli) and [`bobtemplates.plone`](https://github.com/plone/bobtemplates.plone).
These markers act as hooks to add all kinds of features through subtemplates, including behaviors, control panels, upgrade steps, or other subtemplates from `bobtemplates.plone`.
`plonecli` is a command line client for `bobtemplates.plone`, adding autocompletion and other features.

To add a feature as a subtemplate to your package, use the following command pattern.

```shell
make add <template_name>
```

For example, you can add a content type to your package with the following command.

```shell
make add content_type
```

You can add a behavior with the following command.

```shell
make add behavior
```

```{seealso}
You can check the list of available subtemplates in the [`bobtemplates.plone` `README.md` file](https://github.com/plone/bobtemplates.plone/?tab=readme-ov-file#provided-subtemplates).
See also the documentation of [Mockup and Patternslib](https://6.docs.plone.org/classic-ui/mockup.html) for how to build the UI toolkit for Classic UI.
```

## License

The project is licensed under GPLv2.
Expand Down
1 change: 1 addition & 0 deletions backend/news/3.documentation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Updated the backend README and synced it with the top-level README. @tisto
3 changes: 2 additions & 1 deletion backend/src/kitconcept/keywordmanager/services/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def reply(self):

if not isinstance(keywords, list):
raise BadRequest(
f"Invalid request: 'items' must be of type 'list', but received '{type(keywords).__name__}'."
f"Invalid request: 'items' must be of type 'list', "
f"but received '{type(keywords).__name__}'."
)
if not keywords:
raise BadRequest("Invalid request: missing required parameter 'items'.")
Expand Down
3 changes: 2 additions & 1 deletion backend/src/kitconcept/keywordmanager/services/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def reply(self):
if sort_order := self.request.form.get("sort_order"):
if sort_order not in ("ascending", "descending"):
raise BadRequest(
f"Invalid sort_order '{sort_order}'. Must be 'ascending' or 'descending'."
f"Invalid sort_order '{sort_order}'. Must be 'ascending' "
"or 'descending'."
)
reverse = sort_order == "descending"

Expand Down
6 changes: 4 additions & 2 deletions backend/src/kitconcept/keywordmanager/services/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ def reply(self):

if not isinstance(new_keyword, str):
raise BadRequest(
f"Invalid request: 'new_keyword' must be of type 'str', but received '{type(new_keyword).__name__}'."
f"Invalid request: 'new_keyword' must be of type 'str', "
f"but received '{type(new_keyword).__name__}'."
)
if not new_keyword:
raise BadRequest(
"Invalid request: missing required parameter 'new_keyword'."
)
if not isinstance(old_keywords, list):
raise BadRequest(
f"Invalid request: 'old_keywords' must be of type 'list', but received '{type(old_keywords).__name__}'."
f"Invalid request: 'old_keywords' must be of type 'list', "
f"but received '{type(old_keywords).__name__}'."
)
if not old_keywords:
raise BadRequest(
Expand Down
Loading
Loading