Skip to content

Add content type settings and CRUD services - #2070

Open
sneridagh wants to merge 6 commits into
mainfrom
issue-2061-content-types
Open

sneridagh wants to merge 6 commits into
mainfrom
issue-2061-content-types

Conversation

@sneridagh

@sneridagh sneridagh commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Add @content-types at the site root, so frontends can list, configure, create, copy, and delete content types without depending on the Dexterity types control panel.

Step 2b of #2061, stacked on #2071 (shared schema helpers), which is stacked on #2069. Review it against issue-2061-schema-helpers. Behavior editing and workflow changes come in steps 3 and 4. Import and export remain deferred.

Note

This branch was rewritten after review. The previous version is still in the PR's history (5a8d433c). The rewrite moves the generic schema and value handling to #2071, and replaces the seven settings-specific field adapters with the shared converters.

API

Method Path Result
GET /@content-types Every type with its settings, in one request
GET /@content-types/{type} One type with its settings
POST /@content-types Create a Dexterity type, or copy one with copy_from
PATCH /@content-types/{type} Change some settings
DELETE /@content-types/{type} Delete a Dexterity type without items (409 with the count otherwise)

A type's representation has schema and data in the control panel format: a JSON schema whose fieldsets list the fields of each settings adapter, and values converted by the shared field serializers. A frontend can render it with the same form as control panels. Choices have a token and a title, as in control panels and content. A read-only workflow summary and items_total complete the representation.

Core settings:

  • General: title, description
  • Contents: global_allow, allowed_content_types_mode (all, selected, or none), and allowed_content_types
  • Visibility: searchable, default_page
  • Comments: allow_discussion
  • Versioning: versioning (off, manual, or automatic)

Design

  • Extension point. Each fieldset is a named ITypeSettings adapter of the FTI and the request, with title, order, schema, an optional permission, and read(), validate(), write(), and remove() methods. The core fieldsets use the same mechanism, through the TypeSettings base class.
  • Atomic updates. PATCH converts and validates every change before writing any. Validation covers field constraints, autoform modes, unknown fields, add-on validate(), and schema invariants against the merged current values.
  • Lossless settings.
  • Create and copy.
    • New types get the defaults of the classic "Add Content Type" form.
    • A copy keeps the source's actions, method aliases, behavior order, add-on settings, repository policies, and workflow assignment, but gets its own factory and add view.
  • Delete. Deleting clears the type's registry entries, repository policies, workflow assignment, add-on data (through remove()), and its use as an allowed type of other types, so a later type with the same ID starts clean. Only Dexterity types can be deleted, and never the site type.
  • Python API. Create, copy, and remove are plain functions in plone.restapi.typesettings.manage, so they can be reused outside the service, for example by a later import step.
  • Cleanup isn't an event subscriber on purpose. A GenericSetup purge import of typeinfo deletes and re-adds every FTI, so a removal subscriber would wipe workflow chains, policies, and registry entries that other import steps had just set.
  • Item counts come from one lookup in the portal_type index for the whole listing, rather than one catalog query per type.

Known limitations

  • Choice properties carry a vocabulary.@id pointing to @sources, which doesn't resolve outside content. Control panels have the same quirk today. The choices are inlined in the schema, so frontends don't need the link.
  • workflow.id shows the first workflow of a multi-workflow chain. Step 4 owns workflow details.

Validation

  • Full suite passes: 1486 tests.
  • test_services_contenttypes.py has 38 tests, covering:
    • serialization, choices, counts, and the 404 paths
    • partial and atomic updates, all allowed-types modes and their checks against stored values, and term objects as input
    • versioning policies, behaviors, and schema refresh
    • create defaults, encoded IDs, and invalid input
    • copy fidelity and independence, workflow overrides, and custom policies
    • delete guards and cleanup
    • add-on fieldsets: copy and remove, validation, permission, autoform directives, nullable fields, constraints, invariants, and custom field adapters
    • anonymous, member, and site administrator access
  • Seven test_documentation cases generate the content_types_* http-examples. The listing and copy examples save only the request, to avoid a 4,000-line response file.
  • The Sphinx build wasn't run locally, because the docs dependencies aren't installed. Read the Docs builds this PR.

@read-the-docs-community

read-the-docs-community Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 plone.restapi | 🛠️ Build #35671044 | 📁 Comparing aa531ed against latest (e26b660)

  🔍 Preview build  

6 files changed · ± 6 modified

± Modified

@sneridagh

Copy link
Copy Markdown
Member Author

@jenkins-plone-org please run jobs

@sneridagh

Copy link
Copy Markdown
Member Author

@davisagli I just found that:

The GitHub Actions workflows only run for pull requests whose base branch is master or main. plone/plone.restapi#2070 · Add content type settings and… is stacked on issue-2061-acceptance-server, so they never start. meta.yml, tests.yml and test-matrix.yml all use the same trigger:

pull_request:
  branches:
    - master
    - main

Pushes to a feature branch don’t trigger them either, because the push trigger has the same branch filter. That’s why the PR only shows the checks GitHub Actions doesn’t run (Read the Docs, the changelog verifier and the contributor agreement check).

Why is it configured like this?

@sneridagh
sneridagh force-pushed the issue-2061-content-types branch from 5a8d433 to 4dd29d0 Compare October 19, 2026 15:04
@sneridagh
sneridagh changed the base branch from issue-2061-acceptance-server to issue-2061-schema-helpers October 19, 2026 15:04
@sneridagh
sneridagh force-pushed the issue-2061-content-types branch from 4dd29d0 to fc4dbec Compare October 19, 2026 15:34
Comment thread docs/source/endpoints/index.md Resolved
@sneridagh

Copy link
Copy Markdown
Member Author

@jenkins-plone-org please run jobs

@davisagli
davisagli force-pushed the issue-2061-content-types branch from fae2a02 to 01c21f2 Compare November 1, 2026 19:31
Base automatically changed from issue-2061-schema-helpers to main November 1, 2026 20:06
List, configure, create, copy, and delete content types at the site
root (step 2 of #2061).

- Settings come from named ITypeSettings adapters, one fieldset each.
  The core settings (general, contents, visibility, comments,
  versioning) use the same mechanism as add-ons, which can also require
  a permission per fieldset.
- Responses use the control panel format: a JSON schema with fieldsets,
  and data converted by the shared field serializers, so choices have a
  token and a title.
- Allowed content types are two flat fields, allowed_content_types_mode
  (all, selected, none) and allowed_content_types, which standard form
  widgets can render. The selection is kept while all types are allowed.
- Updates validate every change, including invariants against current
  values, before writing any.
- Creating a type uses the defaults of the classic add form. Copying
  keeps the actions, behavior order, settings, repository policies and
  workflow of the source, with its own factory and add view.
- Deleting is limited to Dexterity types without items, and clears the
  type's settings, workflow assignment and use as an allowed type.
- Create, copy and remove are plain functions in
  plone.restapi.typesettings.manage, reusable outside the service.
The endpoints content-types page only showed GET examples of the default
content types, which belong with the content services. Move them to the
content manipulation page and use endpoints/content-types for the
@Content-Types service.
@davisagli
davisagli force-pushed the issue-2061-content-types branch from 01c21f2 to b1d0652 Compare November 1, 2026 20:06

@davisagli davisagli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sneridagh I reviewed the docs, but not the implementation yet.

Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
Comment thread docs/source/endpoints/content-types.md Resolved
| `read()` | Return the current values. |
| `validate(data)` | Return a list of errors for the changed values, each with `field` and `message`. |
| `write(data)` | Store the changed values. |
| `remove()` | Delete what the settings store outside the FTI, before the type is deleted. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will probably want an enabled method that returns a boolean, so that the adapter can show the fields only when its add-on is installed (for example).

We should implement the allow_discussion setting in plone.app.discussion this way, to prove the use case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added enabled() to ITypeSettings (the base class returns True), and the comments settings use it to show only when plone.app.discussion is installed. Moving the allow_discussion setting into plone.app.discussion itself would be a separate PR there, so I'm leaving this thread open for that. Partly done in a129590.

Assisted-by: Claude Opus 5.5

… only when enabled

- The service requires the permission of the content settings control
  panel, so access to types can be given without other control panels.
- ITypeSettings adapters have an enabled() method. The comments settings
  show only when plone.app.discussion is installed.
- Allowed content types link to the plone.app.vocabularies.PortalTypes
  vocabulary instead of listing the choices inline. The settings are
  serialized under the site, so the link resolves.
- Docs: what global_allow means, the schema format, and the copy response.

Assisted-by: Claude Opus 5.5
…default page

- A type's number of items is content_count: items_total is the batching
  name next to items. The error for deleting a type with items uses it too.
- The visibility settings have in_navigation (plone.displayed_types)
  instead of default_page, which Volto and Plone Aurora don't use.

Assisted-by: Claude Opus 5.5
- default_page is back in the visibility settings, next to in_navigation:
  UIs that don't use default pages hide it, but the API keeps it.
- The list and copy examples save their responses too, so changes to them
  show in reviews, although the docs only show the requests.

Assisted-by: Claude Opus 5.5
The list example includes every type. Plone 6.2's Discussion Item has a
shorter description than older versions, so the example must match it.

Assisted-by: Claude Opus 5.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants