Manual
Note: Return to reference manual view.
1. Installation and configuration
Installation
- Read the right documentation if you have never installed any product.
- Install the product in the file system (as usual).
- Install the product in your Plone portal, via the Plone control panel (as usual).
Principles and rules
Before frantically clicking everywhere, blindly hoping that this will "just work", we would better begin by introducing the principles of CMFNotification and, particularly, the rules:
This is not 'Nam, there are rules. -- Walter Sobchak (The Big Lebowski)
This is a required step. Firstly because if you do not configure CMFNotification, nothing special will happen. You have to configure it. Secondly, understanding what you are doing is the first step to understand why it does not work. This will save your time.
CMFNotification lets you define who will receive e-mails, when and what mail template will be used. Mail templates can be used to have specific mails for specific events (when an event is published with a determined keyword, for example).
To determine who, when and what, you have to define rules. There are two kinds of rules:
- rules that define who we are sending e-mails to. There is one such rule for each supported type of event (item creation, item modification, workflow transition, etc.) ;
- rules that define which mail template we will use.
Configuration
To configure CMFNotification, click on the "Configuration of CMFNotification" configlet in the Plone control panel.
Enabling and disabling features
Labels should be self-explanatory. However, here is a quick guide to the configuration form:
- enable extra subscriptions
if enabled, users will be able to subscribe or unsubscribe to items via the buttons that appear in the "Mail subscription" portlet (which is added in the right column, by default). They will therefore receive an e-mail notification when an event occurs, if the notification of this event is enabled. See Rules for list of users section in the rules reference for further details. Note that this feature is restricted by a permission: CMFNotification: Subscribe/unsubscribe. By default, this permission is granted to Manager and Member roles. Note that anonymous subscription is not implemented.
Default: disabled.
- toggle recursive mode for extra subscriptions
if enabled, an user who has subscribed to a folder will automatically be subscribed to items of this folder (and sub-folders and their subitems, etc.)
Default: enabled.
- toggle debug mode
if enabled, CMFNotification will log the addresses that it sends e-mails to and the messages themselves. This can be particularly helpful when debugging your rules.
Default: disable.
- rules (ignore)
this is a special rule to disallow notification. This can be handy when you want to temporarily disable notification in one click withoug messing up with the whole configuration. A simple rule like this one does the trick:
python: True
You could also want not to send any notification for items in a specific folder. Using an "ignore rule" is much more handy that customizing all rules:
python: here.isInFooSection()
Default: the default rule disable notification of temporary items:
python: getattr(context, 'isTemporary', lambda: False)()
Configuration of the rules
If you are really in a hurry, you may want to look at "How to setup CMFNotification in two minutes" (how-to-setup-in-2-minutes.txt).
If you are not, take your time and read more about rules.
Logging
Every error or potential problem (if no user was found for a notification, if a problem occurs when trying to find or apply the mail template, etc.) is logged. If you do not receive any e-mail, check your Zope log file.
For futher details, see our troubleshooting how-to.
2. Rules reference
General principles
All types of rules follow the same principles:
First, a rule is one line long. It can be as long as we want, though. However, it is suggested that very long rules components be put in a script, a tool/content type method or a view.
A rule have two components, separated by two colons: ::. Spaces can be put before or after the colons, for readability's sake (se examples below):
<match-expr> :: <rule-expr>
The first component is a conditional expression. If this expression is evaluated to True, then the rule matches. The type of the second component and the behaviour of the rule depend of its type (see below).
These expressions can be:
Python expressions, e.g.:
python: here.meta_type == 'Document'
TAL expressions, e.g.:
not: here/keepSecret
an asterisk (*), which basically means "always" or "everyone", depending on the context. A rule with * always match.
Python and TAL expressions are provided some bindings (like here, author, etc.). See the Bindings paragraph below.
Rules
Rules for list of users
These rules are used to decide who should be notified. As all rules, they look like the following:
<match-expr> :: <get-notified-expr>
The <get-notified-expr> expression is used to determine who has to be notified. It must return a list of user names, email addresses or both. If multiple rules match, then the global subscribers list is an union of all subscribers list returned by each rule. In other words, if we have the following rules:
here/iAmTrue :: python: [user1'] here/iAmAlsoTrue :: python: ['user2']
Then both users will receive a mail notification.
Note that this list will be extended by the list of users who have manually subscribed to the item (if we have enabled this feature).
Moreover a label can be added after a selection rule
<match-expr> :: <get-notified-expr> :: <label>
This label lets us decide which e-mail template to use for users that match this rule. In other words: on the same event, we can decide to send a different e-mail for various (groups of) users. More than one rule may use the same label: in this case, as above, the global list of matching users is an union of all subscribers list returned by each rule. Also note that the same user may be retrieved for different rules with different labels: (s)he will then receive more than one e-mail. See below for an example.
If no label is specified, it is considered to be empty string. Users who manually suscribe to the item are also registered under this empty label.
Important: Also note that a security check is done in any case: no user will receive notification of an item which (s)he cannot view. Therefore, you have to think twice if you want to notify all users of our portal when there are a lot of users: security checks are costly. If you are not sure, test NotificationTool.removeUnAuthorizedSubscribers() on a development portal with all users. Note that a future version of CMFNotification might allow certain rules to be trusted; in this case, security checks would not be done.
Some examples:
if we want to notify all users who can view the item:
* :: *
if we want to send an e-mail to only one user when an item is submitted:
python: transition == 'submit' :: python: ['jsmith']
Rules for e-mail template
There are also rules to determine what e-mail template will be used to notify users. They look like this:
<match-expr> :: <template-expr>
<template-expr> is a TAL or Python expression which can return either a page template (ZPT) or the "id" of a page template. This page template will be "applied to" (or "evaluated in the context of") the object to generate the email message of the notification. Again, specific bindings are available (see below).
Contrary to subscribers rules, only the first rule to match is taken in account. In other words, if we have the following rules:
here/iAmTrue :: string:creation_mail_notification here/iAmAlsoTrue :: string:modification_mail_notification
Then the mail template will be the first one that matches: creation_mail_notification.
If user selection rules had labels, template selection is runned for each groups corresponding to each label independantly.
Note that CMFNotification ships with simple yet usable mail template:
- creation_mail_notification: for item creation (not for discussion item, though, see below);
- modification_mail_notification: for item modification;
- workflow_mail_notification: for workflow transitions;
- discussion_mail_notification: for discussion item creation;
- registration_mail_notification: for member registration;
Bindings
Bindings are injected into the expression context when rule expressions and mail templates are evaluated.
Default bindings
The following bindings are always available:
- here: the current item;
- context: same as here;
- current_state: workflow state of the current item;
- author: the current member;
- nothing: equals to None;
- portal;
- request;
- modules: a SecureModuleImporter instance.
Special bindings for template selection rules
The following bindings are only available in the template selection rules (for all events):
label: one of the matching labels of user selection rules. Using label in our mail template selection rules lets us select a different template for users that have matched the corresponding label. For example, we could define these rules for users:
* :: python: ['manager'] :: simple * :: python: ['dev1', 'dev2'] :: detailed
And the following rules for template selection:
python: label == 'simple' :: string:simple_mail_template python: label == 'detailed' :: string:detailed_mail_template
The three users would receive an e-mail:
- manager would receive a simplified notification;
- dev1 and dev2 would received a detailed notification, since we asked for another mail template.
Special bindings for item modification
The following bindings are available in the context of an item modification:
- current: the current version of the object;
- previous: the previous version of the object, if available, or None otherwise (or when the item is created).
Special bindings for workflow transition
The following bindings are available in the context of a workflow transition:
- transition: the transition (as a string) which was triggered;
- current_state: the current workflow state (as a string) of the item;
- previous_state: the previous workflow state (as a string) of the item;
- comments: the (possibly empty) comments.
Special bindings for member registration and modification
The following bindings are available in the context of a member registration or modification:
- current_user: the user who has has registered the member (could be the member him/herself, i.e. an anonymous user, or an existing portal member), or the user who has modified the member properties (can be the user him/herself or another user);
- member: the new portal member;
- properties: the new portal member's properties;
- event: either registration or modification. FIXME: ?
Special bindings for discussion item creation
Since here and context are ambiguous, two special bindings are provided to access the discussed item (discussed_item) and the discussion item itself (discussion_item).
3. Developer notes
Events, patches and so-called architecture
The product defines a set of event subscribers (cf. events/events.txt) that calls appropriate handlers in CMFNotification tool.
The product still patches a some components of CMF, in order to call handlers when specific events occur. Cf. patches.py module for more information. These patches will soon be replaced by appropriate events.
These handlers are implemented by NotificationTool (cf. methods which begin with on*). They are very similar, and therefore call an unique helper method (_handlerHelper()) to do the work. The rest is in the code...
Implementation of the extra subscriptions list
I wanted the subscriptions to be recursive, i.e. that an user subscribed to folder/ is also recursively subscribed to folder/document and folder/subfolder.
The naive answer for that is to keep a mapping whose keys are path of object, and values are email addresses, like this:
{'/': ('boss1@exemple.com', 'boss2@exemple.com'),
'/marketing/': ('marketing@exemple.com', ) }
But keeping paths is good until the paths change. In the example above, if marketing/ is renamed as advertising/, the tool cannot know the previous path of the object. Then, our mapping become quite useless.
The best way to deal with path changes is to store the UID of the objects instead of their path. But then, how can we easily get the entries in the mapping which corresponds to (i.e. which is a parent of the current object)? We would need to loop through all the UIDs and get the corresponding object. This would be expensive.
Therefore, I decided to have two mappings:
- _uid_to_path: key is the object UID, value is the object path;
- _subscriptions: key is the object path, value are the subscribers list;
Since we know when an object is modified, we can keep both mappings updated.
I am open to comments on that point (as I am on the rest of the product, too).