From 68c02a111ca7a88a9683d456df4030da947185e1 Mon Sep 17 00:00:00 2001 From: Manik Khajuria Date: Sat, 24 Jan 2026 14:24:31 +0530 Subject: [PATCH 1/5] Conceptual-guides-Component-architecture-completion --- docs/conceptual-guides/components.md | 180 +++++++++++++++++++++++---- 1 file changed, 157 insertions(+), 23 deletions(-) diff --git a/docs/conceptual-guides/components.md b/docs/conceptual-guides/components.md index cd0772963..80000d262 100644 --- a/docs/conceptual-guides/components.md +++ b/docs/conceptual-guides/components.md @@ -167,40 +167,174 @@ A global utility is constructed when Plone is started and ZCML is read. (needs V A local component is either a persistent object in the ZODB or constructed when (TODO: When? Traversal time? Lookup time?) ``` -```{note} -If you need to use parameters, such as context or request, consider using views or adapters instead. -``` To learn about some important utilities in Plone, read the chapter {doc}`/backend/global-utils`. -```{todo} -The rest of this chapter is a work in progress and an outline of great things to come. -Pull requests are welcome! -``` - ### Adapters -- general -- views, viewlets -- forms -- overriding - - adapters - - utilities - - views +Adapters are a core part of the {term}`Zope Component Architecture` (ZCA). +They allow you to extend or change the behavior of an object without modifying its class. +They map one interface to another, allowing objects to gain new behavior without changing their original class. +By moving functionality out of the class and into adapters, Plone achieves loose coupling and a highly modular design, where components depend on interfaces rather than concrete implementations. + +There are two kinds of adapters: + +Normal Adapters + +Normal adapters adapt a single object and are used when behavior depends on just one context. They are commonly applied to content objects to add computed values, helper methods, or additional behavior without modifying the original class. Because they deal with only one parameter, they are simple, lightweight, and easy to understand. + +Multi-Adapters + +Multi-adapters adapt multiple objects at once, passed as a tuple of parameters. They are used when behavior depends on more than one context, such as the content object, the current request, or the active view. + + +#### Views, Viewlets + +In Plone, views and viewlets are implemented as adapters. + +Views: + +In Plone, views are implemented as multi-adapters that adapt both the context (the content object being viewed) and the request (the current HTTP request). Their main responsibility is to produce rendered output, such as HTML pages, JSON responses for APIs, or other representations of content. Views are registered using ZCML, which allows them to be cleanly integrated into the system and easily replaced or customized. Because views are adapters, they can be overridden by registering another view with the same name and interfaces, making it possible to change or extend behavior without modifying any core Plone code. + +Viewlets: + +Viewlets, on the other hand, are small, reusable user interface components that together make up a page layout, such as headers, footers, navigation elements, or portlets. A viewlet is a more complex multi-adapter that adapts the context, request, the current view, and a viewlet manager, which controls where and how the viewlet is rendered on the page. This design allows viewlets to be highly flexible and context-aware. Themes and add-ons commonly override viewlet adapters to customize the look, placement, or behavior of specific UI elements, again without changing Plone’s core templates or logic. + + +#### Forms + +Adapters play a central role in schema-driven forms in Plone. Rather than embedding logic directly inside form or field classes, Plone uses adapters to control field behavior, select appropriate widgets, apply validators, and perform data conversion between user input and stored values. This design brings several important benefits: form logic becomes highly reusable, behavior can be customized per content type or context, and developers can alter or extend form behavior without subclassing forms directly. As a result, forms remain clean, flexible, and easy to maintain, even in large and complex Plone applications. + + +#### Overriding + +Plone allows behavior to be overridden without modifying core code, mainly through adapters and ZCML load order. This keeps customizations clean and upgrade-safe. + +##### Overriding Adapters + +Adapters are overridden by registering a new adapter with the same required and provided interfaces. The last loaded adapter wins, making this approach common in add-ons and themes for changing business logic or content-specific behavior. + +##### Overriding Utilities + +Utilities are global, context-independent services. They are overridden by re-registering the same interface and are typically used for configuration and core services like mail or search. + +##### Overriding Views + +Views can be overridden by: + +Views can be overridden by registering a new view with the same name and controlling ZCML order. This is widely used for custom templates, API changes, and theme customization—all without touching core code. ### Lookup -- lookup adapter - - using the Interface as abstract factory - - using the API - - IRO, names ... -- lookup an utility, names, singletons and factory based +Lookup is the dynamic process of retrieving an adapter or utility from the component registry. +The registry matches the requested interface against registered factories or components. + +#### Lookup an adapter + +There are two main ways to look up adapters. + +Using the interface as an abstract factory +: The notation `IMyInterface(context)` is shorthand for `getAdapter(context, IMyInterface)`. + It uses the interface as an abstract factory and returns the adapted object. + +Using the API +: Use `zope.component.getAdapter()` or `zope.component.getMultiAdapter()` when you want an error if no adapter is found. + Use `queryAdapter()` or `queryMultiAdapter()` when you want `None` instead. + + +For multi-adapters, pass a tuple of objects in the order declared in the registration: + + +##### Interface Resolution Order (IRO) and names + +Adapter lookups consider the interfaces provided by the context in interface resolution order (IRO). +More specific interfaces win over more general ones. + +Named adapters allow multiple implementations for the same required/provided interfaces. +You can pass a `name` to `getAdapter()` or `queryAdapter()` to select a specific implementation. + + + +#### Lookup a utility + +Utilities are looked up by provided interface and optional name. + +Use `getUtility()` when you want an error if the utility is not found. +Use `queryUtility()` when you want `None` instead. + + +To list utilities, use `getUtilitiesFor()` or `getAllUtilitiesRegisteredFor()`: + + +#### Global and local lookup + +Lookups use the current site manager. +In a Plone site, the local component registry is active and falls back to the global registry when needed. +Outside a site context, lookups use the global registry. + ### Events and Subscribers -- general zope events -- lifecycle events -- important events in plone +Events are objects that represent something happening in the system. +Subscribers (event handlers) are callables that react to those events. +Plone uses `zope.event` and the Zope Component Architecture to register and dispatch subscribers. + +#### General Zope events + +Events are interface-driven. +Subscribers are registered for a combination of object interface and event interface. +When an event is notified, all matching subscribers are called. + + +#### Lifecycle events + +The most common lifecycle events in Plone come from `zope.lifecycleevent`, such as: + +`IObjectCreatedEvent` +: Fired when a new object is created. + Often used to set defaults, initialize metadata, or attach related content. + +`IObjectModifiedEvent` +: Fired when an object is modified. + Use it to react to data changes (for example, reindexing or updating derived fields). + +`IObjectMovedEvent` +: Fired when an object is moved or renamed. + Useful for updating references, breadcrumbs, or paths stored elsewhere. + +`IObjectRemovedEvent` +: Fired when an object is removed. + Use it to clean up external resources or related objects. + +`IObjectCopiedEvent` +: Fired when an object is copied. + You can use it to adjust IDs, titles, or external references on the new copy. + +#### Important events in Plone + +Plone adds a few events you may need to handle explicitly: + +Workflow events +: `Products.DCWorkflow.interfaces.IBeforeTransitionEvent` is fired before a workflow transition is executed. + It is useful for validation, veto logic, or preparing state changes. + `Products.DCWorkflow.interfaces.IAfterTransitionEvent` is fired after the transition completes, + and is commonly used for side effects such as notifications or reindexing. + `Products.CMFCore.interfaces.IActionSucceededEvent` is a higher-level event that signals a completed action, + and is often easier to use for general workflow reactions. + +Local roles change event +: `LocalrolesModifiedEvent` is triggered when local roles are updated, for example in the sharing view. + Use it to update cached permissions, invalidate security-related indexes, or refresh UI state. + If your add-on depends on local roles or sharing changes, subscribe to this event. + +Login and logout events +: `Products.PlonePAS.events.UserLoggedInEvent` and + `Products.PlonePAS.events.UserLoggedOutEvent` are fired on successful login and logout. + They are useful for audit trails, login/logout hooks, cleanup tasks, or session initialization. + +Modification date handling +: Dexterity updates the modification date when objects are modified. + If your subscriber changes data, be aware that it may update the modification timestamp and trigger more events. From 4ba86937a522d443ff926fc096927acca0c1212d Mon Sep 17 00:00:00 2001 From: Manik Khajuria Date: Sat, 24 Jan 2026 14:46:45 +0530 Subject: [PATCH 2/5] Removing subcriber docs from backend --- docs/backend/subscribers.md | 178 ------------------------------------ 1 file changed, 178 deletions(-) delete mode 100644 docs/backend/subscribers.md diff --git a/docs/backend/subscribers.md b/docs/backend/subscribers.md deleted file mode 100644 index 71d5b3c99..000000000 --- a/docs/backend/subscribers.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -myst: - html_meta: - "description": "How to add custom event handlers for your type in Plone" - "property=og:description": "How to add custom event handlers for your type in Plone" - "property=og:title": "Subscribers (event handlers)" - "keywords": "Plone, subscribers, event handlers" ---- - -(backend-subscribers-label)= - -# Subscribers (event handlers) - -A _subscriber_ is a callable object that takes one argument, an object that we call the _event_. - -_Events_ are objects that represent something happening in a system. -They are used to extend processing by providing processing plug points. - -A _notification_ alerts subscribers that an event has occurred. - -The {term}`Zope Component Architecture`'s [`zope.event`](https://zopeevent.readthedocs.io/en/latest/) package is used to manage subscribable events in Plone. - -The Plone event system has some notable characteristics: - -- It's simple. -- The calling order of subscribers is random. - You can't set the order in which event handlers are called. -- Events can't be cancelled. - All handlers will always get the event. -- Event handlers can't have return values. -- Exceptions raised in an event handler will interrupt the request processing. - - -## Register an event handler - -Plone events can be scoped: - -- globally (no scope) -- per content type -- per behavior or marker interface - - -### Register an event handler on content type creation - -The following example demonstrates how to register an event handler when a content type is created. - -In your {file}`.product/your/product/configure.zcml` insert the following code. - -{lineno-start=1} -```xml - -``` - -The second line defines to which interface you want to bind the execution of your code. -Here, the event handler code will only be executed if the object is a content type providing the interface `.interfaces.IMyContentTypeClass`. -If you want this to be interface agnostic, insert an asterix `*` as a wildcard instead. - -The third line defines the event on which this should happen, which is `IObjectCreatedEvent`. -For more available possible events to use as a trigger, see {ref}`subscribers-event-types-label`. - -The fourth line gives the path to the callable function to be executed. - -Create your {file}`.product/your/product/your_python_file.py` and insert the following code. - -```python -def your_subscriber(object, event): - # do something with your created content type -``` - - -### Subscribe to an event using ZCML - -Subscribe to a global event using {term}`ZCML` by inserting the following code in your {file}`.product/your/product/configure.zcml`. - -```xml - -``` - -For this event, the Python code in {file}`smartcard.py` would be the following. - -```python -def clear_extra_cookies_on_logout(event): - # What event contains depends on the - # triggerer of the event and event class - request = event.object.REQUEST -``` - -The following example for a custom event subscribes content types to all `IMyEvents` when fired by `IMyObject`. - -```xml - -``` - -The following example shows how to subscribe a content type to the life cycle event. - -```xml - -``` - - -## Fire an event - -Use `zope.event.notify()` to fire event objects to their subscribers. - -The following code shows how to fire an event in unit tests. - -```python -import zope.event -from plone.postpublicationhook.event import AfterPublicationEvent - -event = AfterPublicationEvent(self.portal, self.portal.REQUEST) -zope.event.notify(event) -``` - - -(subscribers-event-types-label)= - -## Event types - -Plone has the following types of events. - - -### Creation events - -`zope.lifecycleevent.IObjectCreatedEvent` is fired for all Zope-ish objects when they are created, or copied via `IObjectCopiedEvent`. -They don't have to be content objects. - -### Modified events - -`zope.lifecycleevent.IObjectModifiedEvent` is called for creation stage events as well, unlike the previous event type. - -### Delete events - -Delete events can be fired several times for the same object. -Some delete event transactions are rolled back. - -### Copy events - -`zope.lifecycleevent.IObjectCopiedEvent` is triggered when an object is copied. -It will also fire `IObjectCreatedEvent` event code. - -### Workflow events - -`Products.DCWorkflow.interfaces.IBeforeTransitionEvent` is triggered before a workflow transition is executed. - -`Products.DCWorkflow.interfaces.IAfterTransitionEvent` is triggered after a workflow transition has been executed. - -The DCWorkflow events are low-level events that can tell you a lot about the previous and current states. - -`Products.CMFCore.interfaces.IActionSucceededEvent` is a higher level event that is more commonly used to react after a workflow action has completed. - -### Zope startup events - -`zope.processlifetime.IProcessStarting` is triggered after the component registry has been loaded and Zope is starting up. - -`zope.processlifetime.IDatabaseOpened` is triggered after the main ZODB database has been opened. - -## Related content - -- {doc}`/backend/behaviors` -- {doc}`/backend/content-types/index` - From 90678ec721b4cd0745f1cb1e77568041f99d6ca2 Mon Sep 17 00:00:00 2001 From: Manik Khajuria Date: Sat, 24 Jan 2026 15:01:47 +0530 Subject: [PATCH 3/5] Removed Subscriber tactree from backend docs --- docs/backend/index.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/backend/index.md b/docs/backend/index.md index 3e7dd3278..467b3b261 100644 --- a/docs/backend/index.md +++ b/docs/backend/index.md @@ -33,7 +33,6 @@ schemas search security sending-email -subscribers traversal-acquisition users-groups vocabularies From d5ed69d5ec2dc20126da979a950cedb0db46edcb Mon Sep 17 00:00:00 2001 From: Manik Khajuria Date: Fri, 13 Feb 2026 13:00:35 +0530 Subject: [PATCH 4/5] Fix documentation style: ensure one sentence per line - Split long sentences into individual lines throughout components.md - Fix Concepts section (Interface, Adapter, Events, Registries, Lookup) - Ensure all paragraphs follow one-sentence-per-line style guide - Resolves formatting issues where text appeared as paragraphs instead of lines --- docs/backend/index.md | 1 + docs/backend/subscribers.md | 164 +++++++++++++++++++++++++++ docs/conceptual-guides/components.md | 113 ++++++++++++------ 3 files changed, 242 insertions(+), 36 deletions(-) create mode 100644 docs/backend/subscribers.md diff --git a/docs/backend/index.md b/docs/backend/index.md index 467b3b261..3e7dd3278 100644 --- a/docs/backend/index.md +++ b/docs/backend/index.md @@ -33,6 +33,7 @@ schemas search security sending-email +subscribers traversal-acquisition users-groups vocabularies diff --git a/docs/backend/subscribers.md b/docs/backend/subscribers.md new file mode 100644 index 000000000..be2820efa --- /dev/null +++ b/docs/backend/subscribers.md @@ -0,0 +1,164 @@ +--- +myst: + html_meta: + "description": "How to register and use event subscribers in Plone" + "property=og:description": "How to register and use event subscribers in Plone" + "property=og:title": "Subscribers (event handlers)" + "keywords": "Plone, events, subscribers, event handlers, ZCA" +--- + +# Subscribers (event handlers) + +A _subscriber_ is a callable object that takes one argument, an object that we call the _event_. + +_Events_ are objects that represent something happening in a system. +They are used to extend processing by providing processing plug points. + +A _notification_ alerts subscribers that an event has occurred. + +The Zope Component Architecture's zope.event package is used to manage subscribable events in Plone. + +The Plone event system has some notable characteristics: + +- It's simple. +- The calling order of subscribers is random. + You can't set the order in which event handlers are called. +- Events can't be cancelled. + All handlers will always get the event. +- Event handlers can't have return values. +- Exceptions raised in an event handler will interrupt the request processing. + +For a conceptual overview of events and subscribers, see {doc}`/conceptual-guides/components`. + +## Register an event handler + +Plone events can be scoped: + +- globally (no scope) +- per content type +- per behavior or marker interface + +### Register an event handler on content type creation + +The following example demonstrates how to register an event handler when a content type is created. + +In your `.product/your/product/configure.zcml` insert the following code. + +```xml + +``` + +The second line defines to which interface you want to bind the execution of your code. +Here, the event handler code will only be executed if the object is a content type providing the interface `.interfaces.IMyContentTypeClass`. +If you want this to be interface agnostic, insert an asterix `*` as a wildcard instead. + +The third line defines the event on which this should happen, which is `IObjectCreatedEvent`. +For more available possible events to use as a trigger, see {ref}`event-types`. + +The fourth line gives the path to the callable function to be executed. + +Create your `.product/your/product/your_python_file.py` and insert the following code. + +```python +def your_subscriber(object, event): + # do something with your created content type +``` + +### Subscribe to an event using ZCML + +Subscribe to a global event using ZCML by inserting the following code in your `.product/your/product/configure.zcml`. + +```xml + +``` + +For this event, the Python code in `smartcard.py` would be the following. + +```python +def clear_extra_cookies_on_logout(event): + # What event contains depends on the + # triggerer of the event and event class + request = event.object.REQUEST +``` + +The following example for a custom event subscribes content types to all `IMyEvents` when fired by `IMyObject`. + +```xml + +``` + +The following example shows how to subscribe a content type to the life cycle event. + +```xml + +``` + +## Fire an event + +Use `zope.event.notify()` to fire event objects to their subscribers. + +The following code shows how to fire an event in unit tests. + +```python +import zope.event +from plone.postpublicationhook.event import AfterPublicationEvent + +event = AfterPublicationEvent(self.portal, self.portal.REQUEST) +zope.event.notify(event) +``` + +(event-types)= +## Event types + +Plone has the following types of events. + +### Creation events + +`zope.lifecycleevent.IObjectCreatedEvent` is fired for all Zope-ish objects when they are created, or copied via `IObjectCopiedEvent`. +They don't have to be content objects. + +### Modified events + +`zope.lifecycleevent.IObjectModifiedEvent` is called for creation stage events as well, unlike the previous event type. + +### Delete events + +Delete events can be fired several times for the same object. +Some delete event transactions are rolled back. + +### Copy events + +`zope.lifecycleevent.IObjectCopiedEvent` is triggered when an object is copied. +It will also fire `IObjectCreatedEvent` event code. + +### Workflow events + +`Products.DCWorkflow.interfaces.IBeforeTransitionEvent` is triggered before a workflow transition is executed. + +`Products.DCWorkflow.interfaces.IAfterTransitionEvent` is triggered after a workflow transition has been executed. + +The DCWorkflow events are low-level events that can tell you a lot about the previous and current states. + +`Products.CMFCore.interfaces.IActionSucceededEvent` is a higher level event that is more commonly used to react after a workflow action has completed. + +### Zope startup events + +`zope.processlifetime.IProcessStarting` is triggered after the component registry has been loaded and Zope is starting up. + +`zope.processlifetime.IDatabaseOpened` is triggered after the main ZODB database has been opened. diff --git a/docs/conceptual-guides/components.md b/docs/conceptual-guides/components.md index 80000d262..bbea623d4 100644 --- a/docs/conceptual-guides/components.md +++ b/docs/conceptual-guides/components.md @@ -9,7 +9,8 @@ myst: # Component architecture -The {term}`Zope Component Architecture` (ZCA) is a Python framework for supporting component-based design and programming, utilizing the design patterns interface, adapter, abstract factory, and publish-subscribe. +The {term}`Zope Component Architecture` (ZCA) is a Python framework for supporting component-based design and programming. +It utilizes the design patterns interface, adapter, abstract factory, and publish-subscribe. Plone logic is wired together by Zope Component Architecture. It provides the "enterprise business logic" engine for Plone. @@ -24,25 +25,30 @@ Interface Adapter : Specific implementation of an interface. - An adapter provides an interface on its own, and adapts one or more objects with specific interfaces. + An adapter provides an interface on its own. + It adapts one or more objects with specific interfaces. Utility : Specific implementation of an interface either as a singleton or factored-on lookup. Events and subscribers -: Events are emitted, and a subscriber may listen to those events. - Events provide an interface, and subscribers are registered for specific interfaces. +: Events are emitted. + A subscriber may listen to those events. + Events provide an interface. + Subscribers are registered for specific interfaces. Events are only dispatched to subscribers matching the interface of the event. Registries : Adapters, utilities, and subscribers are registered in registries. Here the wiring is done. - Additional to the interface, a name might be provided for adapters and utilities (so-called named adapters or named utilities). + Additional to the interface, a name might be provided for adapters and utilities. + These are so-called named adapters or named utilities. Registration can be done in Python code or via {term}`ZCML`, an XML dialect. Lookup : The lookup functions are providing the logic to dynamically factor an adapter or utility matching the object that was fed in. - You can ask to get an adapter to an object and pass in a name and the lookup method introspects the interface provided by the object and searches the registry for matches. + You can ask to get an adapter to an object and pass in a name. + The lookup method introspects the interface provided by the object and searches the registry for matches. ## Design patterns @@ -96,13 +102,17 @@ Plone incorporates the component architecture in its design. ### Registries The component registry is used to register adapters, utilities, and subscribers. -On lookup, it is used to find and initialize the matching adapter for the given objects, and name if given; to adapt; to find the right utility for an interface, and name if given; and to call the matching subscribers for an event. +On lookup, it is used to find and initialize the matching adapter for the given objects, and name if given. +It is used to adapt. +It is used to find the right utility for an interface, and name if given. +It is used to call the matching subscribers for an event. We have two levels of registries. Global component registry : The Global component registry is always and globally available as a singleton. - Configuration is done in Plone using {term}`ZCML` files, which is a {term}`XML`-{term}`DSL`. + Configuration is done in Plone using {term}`ZCML` files. + ZCML is a {term}`XML`-{term}`DSL`. Usually they are named {file}`configure.zcml`, but they may include differently named ZCML files. Local component registry @@ -110,7 +120,8 @@ Local component registry If there are one, two, or more Plone sites created in one database, each has its own local component registry. The local registry is activated and registered on traversal time. If a lookup in the local registry fails, then it falls back to the lookup in the global registry. - In theory, registries can be stacked upon each other in several layers, but in practice in Plone, we have two levels: local and global. + In theory, registries can be stacked upon each other in several layers. + In practice in Plone, we have two levels: local and global. Configuration is done in the profile {term}`GenericSetup` in a file {file}`componentregistry.xml`. Note its syntax is completely different from ZCML. @@ -120,7 +131,8 @@ Local component registry Utility classes provide site-wide utility objects or functions. Compared to "plain Python functions", utilities provide the advantage of being plug-in points without the need for monkey-patching. -They are registered by marker interfaces, and optionally with a name, in which case they are called "named utilities". +They are registered by marker interfaces, and optionally with a name. +In which case they are called "named utilities". Accordingly, utilities can be looked up by name or interface. Site customization logic or add-on products can override utilities for enhanced or modified functionality. @@ -143,7 +155,9 @@ local #### Register a utility -You can register utilities in two ways, either by providing a _factory_, or a callable, which creates the object as a result, or by _component_, a ready-to-use object. +You can register utilities in two ways. +Either by providing a _factory_, or a callable, which creates the object as a result. +Or by _component_, a ready-to-use object. Utility factories take no constructor parameters. A utility factory can be provided by either a function or class. @@ -158,35 +172,42 @@ A utility component can be either a: - function that needs to provide a (marker) interface, or - a global instance of a class implementing a marker interface, or -in the case of a local registry, a so-called "local component" which persists as an object in the ZODB and itself needs to provide a marker interface. +- in the case of a local registry, a so-called "local component" which persists as an object in the ZODB and itself needs to provide a marker interface. Utilities may or may not have a name. -```{todo} -A global utility is constructed when Plone is started and ZCML is read. (needs Verification) -A local component is either a persistent object in the ZODB or constructed when (TODO: When? Traversal time? Lookup time?) -``` +A global utility is constructed when Plone is started and ZCML is loaded. +A local component is stored as a persistent object in the ZODB. To learn about some important utilities in Plone, read the chapter {doc}`/backend/global-utils`. +```{note} +If you need to use parameters, such as context or request, consider using views or adapters instead. +``` ### Adapters Adapters are a core part of the {term}`Zope Component Architecture` (ZCA). They allow you to extend or change the behavior of an object without modifying its class. They map one interface to another, allowing objects to gain new behavior without changing their original class. -By moving functionality out of the class and into adapters, Plone achieves loose coupling and a highly modular design, where components depend on interfaces rather than concrete implementations. +By moving functionality out of the class and into adapters, Plone achieves loose coupling and a highly modular design. +Components depend on interfaces rather than concrete implementations. There are two kinds of adapters: Normal Adapters -Normal adapters adapt a single object and are used when behavior depends on just one context. They are commonly applied to content objects to add computed values, helper methods, or additional behavior without modifying the original class. Because they deal with only one parameter, they are simple, lightweight, and easy to understand. +Normal adapters adapt a single object and are used when behavior depends on just one context. +They are commonly applied to content objects to add computed values, helper methods, or additional behavior without modifying the original class. +Because they deal with only one parameter, they are simple, lightweight, and easy to understand. Multi-Adapters -Multi-adapters adapt multiple objects at once, passed as a tuple of parameters. They are used when behavior depends on more than one context, such as the content object, the current request, or the active view. +Multi-adapters adapt multiple objects at once, passed as a tuple of parameters. +They are used when behavior depends on more than one context, such as the content object, the current request, or the active view.' + +Adapters may or may not have a name. #### Views, Viewlets @@ -195,35 +216,57 @@ In Plone, views and viewlets are implemented as adapters. Views: -In Plone, views are implemented as multi-adapters that adapt both the context (the content object being viewed) and the request (the current HTTP request). Their main responsibility is to produce rendered output, such as HTML pages, JSON responses for APIs, or other representations of content. Views are registered using ZCML, which allows them to be cleanly integrated into the system and easily replaced or customized. Because views are adapters, they can be overridden by registering another view with the same name and interfaces, making it possible to change or extend behavior without modifying any core Plone code. +In Plone, views are implemented as multi-adapters that adapt both the context (the content object being viewed) and the request (the current HTTP request). +Their main responsibility is to produce rendered output, such as HTML pages, JSON responses for APIs, or other representations of content. +Views are registered using ZCML. +This allows them to be cleanly integrated into the system and easily replaced or customized. +Because views are adapters, they can be overridden by registering another view with the same name and interfaces. +This makes it possible to change or extend behavior without modifying any core Plone code. Viewlets: -Viewlets, on the other hand, are small, reusable user interface components that together make up a page layout, such as headers, footers, navigation elements, or portlets. A viewlet is a more complex multi-adapter that adapts the context, request, the current view, and a viewlet manager, which controls where and how the viewlet is rendered on the page. This design allows viewlets to be highly flexible and context-aware. Themes and add-ons commonly override viewlet adapters to customize the look, placement, or behavior of specific UI elements, again without changing Plone’s core templates or logic. +Viewlets are only used in the Classic UI. +Viewlets, on the other hand, are small, reusable user interface components that together make up a page layout, such as headers, footers, navigation elements, or portlets. +A viewlet is a more complex multi-adapter that adapts the context, request, the current view, and a viewlet manager. +The viewlet manager controls where and how the viewlet is rendered on the page. +This design allows viewlets to be highly flexible and context-aware. +Themes and add-ons commonly override viewlet adapters to customize the look, placement, or behavior of specific UI elements. +This is done without changing Plone's core templates or logic. #### Forms -Adapters play a central role in schema-driven forms in Plone. Rather than embedding logic directly inside form or field classes, Plone uses adapters to control field behavior, select appropriate widgets, apply validators, and perform data conversion between user input and stored values. This design brings several important benefits: form logic becomes highly reusable, behavior can be customized per content type or context, and developers can alter or extend form behavior without subclassing forms directly. As a result, forms remain clean, flexible, and easy to maintain, even in large and complex Plone applications. +Adapters play a central role in schema-driven forms in Plone. +Rather than embedding logic directly inside form or field classes, Plone uses adapters to control field behavior, select appropriate widgets, apply validators, and perform data conversion between user input and stored values. +This design brings several important benefits. +Form logic becomes highly reusable. +Behavior can be customized per content type or context. +Developers can alter or extend form behavior without subclassing forms directly. +As a result, forms remain clean, flexible, and easy to maintain, even in large and complex Plone applications. #### Overriding -Plone allows behavior to be overridden without modifying core code, mainly through adapters and ZCML load order. This keeps customizations clean and upgrade-safe. +Plone allows behavior to be overridden without modifying core code, mainly through adapters and ZCML load order. +This keeps customizations clean and upgrade-safe. ##### Overriding Adapters -Adapters are overridden by registering a new adapter with the same required and provided interfaces. The last loaded adapter wins, making this approach common in add-ons and themes for changing business logic or content-specific behavior. +Adapters win based on the specificity of their interfaces, not the order in which they were registered. +So an adapter for `IDexterityContent` will win over an adapter for `Interface`. +This is because `IDexterityContent` is a subclass of `Interface`, so is considered more specific. +If there are two adapters with the same name and required and provided interfaces, the registry will raise an exception when they are registered. ##### Overriding Utilities -Utilities are global, context-independent services. They are overridden by re-registering the same interface and are typically used for configuration and core services like mail or search. +Utilities are global, but there can also be local utilities registered in a persistent registry connected to a particular persistent object, such as the Plone site root. +Utilities are overridden by re-registering the same interface. +They are typically used for configuration and core services like mail or search. ##### Overriding Views -Views can be overridden by: - -Views can be overridden by registering a new view with the same name and controlling ZCML order. This is widely used for custom templates, API changes, and theme customization—all without touching core code. +Views can be overridden by registering a new view with the same name and controlling ZCML order. +This is widely used for custom templates, API changes, and theme customization—all without touching core code. ### Lookup @@ -241,10 +284,6 @@ Using the interface as an abstract factory Using the API : Use `zope.component.getAdapter()` or `zope.component.getMultiAdapter()` when you want an error if no adapter is found. - Use `queryAdapter()` or `queryMultiAdapter()` when you want `None` instead. - - -For multi-adapters, pass a tuple of objects in the order declared in the registration: ##### Interface Resolution Order (IRO) and names @@ -282,6 +321,8 @@ Events are objects that represent something happening in the system. Subscribers (event handlers) are callables that react to those events. Plone uses `zope.event` and the Zope Component Architecture to register and dispatch subscribers. +For information on how to register and use subscribers, see {doc}`/backend/subscribers`. + #### General Zope events Events are interface-driven. @@ -320,10 +361,10 @@ Plone adds a few events you may need to handle explicitly: Workflow events : `Products.DCWorkflow.interfaces.IBeforeTransitionEvent` is fired before a workflow transition is executed. It is useful for validation, veto logic, or preparing state changes. - `Products.DCWorkflow.interfaces.IAfterTransitionEvent` is fired after the transition completes, - and is commonly used for side effects such as notifications or reindexing. - `Products.CMFCore.interfaces.IActionSucceededEvent` is a higher-level event that signals a completed action, - and is often easier to use for general workflow reactions. + `Products.DCWorkflow.interfaces.IAfterTransitionEvent` is fired after the transition completes. + It is commonly used for side effects such as notifications or reindexing. + `Products.CMFCore.interfaces.IActionSucceededEvent` is a higher-level event that signals a completed action. + It is often easier to use for general workflow reactions. Local roles change event : `LocalrolesModifiedEvent` is triggered when local roles are updated, for example in the sharing view. From 9a42f47432778c2716fa8579c86d2d9e272db628 Mon Sep 17 00:00:00 2001 From: Manik-Khajuria-5 Date: Wed, 18 Feb 2026 14:53:50 +0530 Subject: [PATCH 5/5] Removed unnecessary formatting --- docs/backend/subscribers.md | 62 ++++++++++++++++------------ docs/conceptual-guides/components.md | 38 ++++++----------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/docs/backend/subscribers.md b/docs/backend/subscribers.md index be2820efa..a139d1fd6 100644 --- a/docs/backend/subscribers.md +++ b/docs/backend/subscribers.md @@ -1,12 +1,14 @@ --- myst: html_meta: - "description": "How to register and use event subscribers in Plone" - "property=og:description": "How to register and use event subscribers in Plone" + "description": "How to add custom event handlers for your type in Plone" + "property=og:description": "How to add custom event handlers for your type in Plone" "property=og:title": "Subscribers (event handlers)" - "keywords": "Plone, events, subscribers, event handlers, ZCA" + "keywords": "Plone, subscribers, event handlers" --- +(backend-subscribers-label)= + # Subscribers (event handlers) A _subscriber_ is a callable object that takes one argument, an object that we call the _event_. @@ -16,70 +18,72 @@ They are used to extend processing by providing processing plug points. A _notification_ alerts subscribers that an event has occurred. -The Zope Component Architecture's zope.event package is used to manage subscribable events in Plone. +The {term}`Zope Component Architecture`'s [`zope.event`](https://zopeevent.readthedocs.io/en/latest/) package is used to manage subscribable events in Plone. The Plone event system has some notable characteristics: -- It's simple. -- The calling order of subscribers is random. - You can't set the order in which event handlers are called. -- Events can't be cancelled. - All handlers will always get the event. -- Event handlers can't have return values. -- Exceptions raised in an event handler will interrupt the request processing. +- It's simple. +- The calling order of subscribers is random. + You can't set the order in which event handlers are called. +- Events can't be cancelled. + All handlers will always get the event. +- Event handlers can't have return values. +- Exceptions raised in an event handler will interrupt the request processing. -For a conceptual overview of events and subscribers, see {doc}`/conceptual-guides/components`. ## Register an event handler Plone events can be scoped: -- globally (no scope) -- per content type -- per behavior or marker interface +- globally (no scope) +- per content type +- per behavior or marker interface + ### Register an event handler on content type creation The following example demonstrates how to register an event handler when a content type is created. -In your `.product/your/product/configure.zcml` insert the following code. +In your {file}`.product/your/product/configure.zcml` insert the following code. +{lineno-start=1} ```xml + /> ``` -The second line defines to which interface you want to bind the execution of your code. +The second line defines to which interface you want to bind the execution of your code. Here, the event handler code will only be executed if the object is a content type providing the interface `.interfaces.IMyContentTypeClass`. If you want this to be interface agnostic, insert an asterix `*` as a wildcard instead. -The third line defines the event on which this should happen, which is `IObjectCreatedEvent`. -For more available possible events to use as a trigger, see {ref}`event-types`. +The third line defines the event on which this should happen, which is `IObjectCreatedEvent`. +For more available possible events to use as a trigger, see {ref}`subscribers-event-types-label`. The fourth line gives the path to the callable function to be executed. -Create your `.product/your/product/your_python_file.py` and insert the following code. +Create your {file}`.product/your/product/your_python_file.py` and insert the following code. ```python def your_subscriber(object, event): # do something with your created content type ``` + ### Subscribe to an event using ZCML -Subscribe to a global event using ZCML by inserting the following code in your `.product/your/product/configure.zcml`. +Subscribe to a global event using {term}`ZCML` by inserting the following code in your {file}`.product/your/product/configure.zcml`. ```xml + /> ``` -For this event, the Python code in `smartcard.py` would be the following. +For this event, the Python code in {file}`smartcard.py` would be the following. ```python def clear_extra_cookies_on_logout(event): @@ -95,7 +99,7 @@ The following example for a custom event subscribes content types to all `IMyEve for=".interfaces.IMyObject .interfaces.IMyEvent" handler=".content.MyObject.myEventHandler" -/> + /> ``` The following example shows how to subscribe a content type to the life cycle event. @@ -106,9 +110,10 @@ The following example shows how to subscribe a content type to the life cycle ev for=".interfaces.ISitsPatient zope.lifecycleevent.IObjectModifiedEvent" handler=".content.SitsPatient.objectModified" -/> + /> ``` + ## Fire an event Use `zope.event.notify()` to fire event objects to their subscribers. @@ -123,11 +128,14 @@ event = AfterPublicationEvent(self.portal, self.portal.REQUEST) zope.event.notify(event) ``` -(event-types)= + +(subscribers-event-types-label)= + ## Event types Plone has the following types of events. + ### Creation events `zope.lifecycleevent.IObjectCreatedEvent` is fired for all Zope-ish objects when they are created, or copied via `IObjectCopiedEvent`. diff --git a/docs/conceptual-guides/components.md b/docs/conceptual-guides/components.md index bbea623d4..830d2aec6 100644 --- a/docs/conceptual-guides/components.md +++ b/docs/conceptual-guides/components.md @@ -9,8 +9,7 @@ myst: # Component architecture -The {term}`Zope Component Architecture` (ZCA) is a Python framework for supporting component-based design and programming. -It utilizes the design patterns interface, adapter, abstract factory, and publish-subscribe. +The {term}`Zope Component Architecture` (ZCA) is a Python framework for supporting component-based design and programming, utilizing the design patterns interface, adapter, abstract factory, and publish-subscribe. Plone logic is wired together by Zope Component Architecture. It provides the "enterprise business logic" engine for Plone. @@ -25,30 +24,25 @@ Interface Adapter : Specific implementation of an interface. - An adapter provides an interface on its own. - It adapts one or more objects with specific interfaces. + An adapter provides an interface on its own, and adapts one or more objects with specific interfaces. Utility : Specific implementation of an interface either as a singleton or factored-on lookup. Events and subscribers -: Events are emitted. - A subscriber may listen to those events. - Events provide an interface. - Subscribers are registered for specific interfaces. +: Events are emitted, and a subscriber may listen to those events. + Events provide an interface, and subscribers are registered for specific interfaces. Events are only dispatched to subscribers matching the interface of the event. Registries : Adapters, utilities, and subscribers are registered in registries. Here the wiring is done. - Additional to the interface, a name might be provided for adapters and utilities. - These are so-called named adapters or named utilities. + Additional to the interface, a name might be provided for adapters and utilities (so-called named adapters or named utilities). Registration can be done in Python code or via {term}`ZCML`, an XML dialect. Lookup : The lookup functions are providing the logic to dynamically factor an adapter or utility matching the object that was fed in. - You can ask to get an adapter to an object and pass in a name. - The lookup method introspects the interface provided by the object and searches the registry for matches. + You can ask to get an adapter to an object and pass in a name and the lookup method introspects the interface provided by the object and searches the registry for matches. ## Design patterns @@ -102,17 +96,13 @@ Plone incorporates the component architecture in its design. ### Registries The component registry is used to register adapters, utilities, and subscribers. -On lookup, it is used to find and initialize the matching adapter for the given objects, and name if given. -It is used to adapt. -It is used to find the right utility for an interface, and name if given. -It is used to call the matching subscribers for an event. +On lookup, it is used to find and initialize the matching adapter for the given objects, and name if given; to adapt; to find the right utility for an interface, and name if given; and to call the matching subscribers for an event. We have two levels of registries. Global component registry : The Global component registry is always and globally available as a singleton. - Configuration is done in Plone using {term}`ZCML` files. - ZCML is a {term}`XML`-{term}`DSL`. + Configuration is done in Plone using {term}`ZCML` files, which is a {term}`XML`-{term}`DSL`. Usually they are named {file}`configure.zcml`, but they may include differently named ZCML files. Local component registry @@ -120,8 +110,7 @@ Local component registry If there are one, two, or more Plone sites created in one database, each has its own local component registry. The local registry is activated and registered on traversal time. If a lookup in the local registry fails, then it falls back to the lookup in the global registry. - In theory, registries can be stacked upon each other in several layers. - In practice in Plone, we have two levels: local and global. + In theory, registries can be stacked upon each other in several layers, but in practice in Plone, we have two levels: local and global. Configuration is done in the profile {term}`GenericSetup` in a file {file}`componentregistry.xml`. Note its syntax is completely different from ZCML. @@ -131,8 +120,7 @@ Local component registry Utility classes provide site-wide utility objects or functions. Compared to "plain Python functions", utilities provide the advantage of being plug-in points without the need for monkey-patching. -They are registered by marker interfaces, and optionally with a name. -In which case they are called "named utilities". +They are registered by marker interfaces, and optionally with a name, in which case they are called "named utilities". Accordingly, utilities can be looked up by name or interface. Site customization logic or add-on products can override utilities for enhanced or modified functionality. @@ -155,9 +143,7 @@ local #### Register a utility -You can register utilities in two ways. -Either by providing a _factory_, or a callable, which creates the object as a result. -Or by _component_, a ready-to-use object. +You can register utilities in two ways, either by providing a _factory_, or a callable, which creates the object as a result, or by _component_, a ready-to-use object. Utility factories take no constructor parameters. A utility factory can be provided by either a function or class. @@ -172,7 +158,7 @@ A utility component can be either a: - function that needs to provide a (marker) interface, or - a global instance of a class implementing a marker interface, or -- in the case of a local registry, a so-called "local component" which persists as an object in the ZODB and itself needs to provide a marker interface. +in the case of a local registry, a so-called "local component" which persists as an object in the ZODB and itself needs to provide a marker interface. Utilities may or may not have a name.