Package: collective.watcherlist
Introductioncollective.watcherlistis a package that enables you to keep a
list of people who want to receive emails when an item gets updated.
The main use case is something likeProducts.Poi, which is an issue
tracker for Plone.  That product lets you create an issue tracker.  In
this tracker people can add issues.  The tracker has managers.
Everytime a new issue is posted, the managers should receive an email.
When a manager responds to an issue, the original poster (and the
other managers) should get an email.  Anyone interested in following
the issue, should be able to add themselves to the list of people who
get an email.  The functionality for this was in Poi, but has now been
factored out into thiscollective.watcherlistpackage.Who should use this package?This is not a package for end users.  Out of the box it does nothing.
It is a package for integrators or developers.  You need to write some
python and zcml in your own package (like Poi now does) to hookcollective.watcherlistup in your code.We gladly use the ZCA (Zope Component Architecture) to allow others to
register their own adapters and own email texts, so outside of Zope
the package does not make much sense.  And we import some code from
Plone too, so you will need that.  If you want to use it in bare Zope
or CMF, contact me: we can probably do some conditional imports
instead.collective.watcherlistmight also be usable as a basis for a
newsletter product.  If you feelSinging and Dancingis overkill
for you, or too hard to adapt to your specific needs, you could try
writing some code aroundcollective.watcherlistinstead.Basic integration stepsIn its simplest form, the integration that is needed, is this:Register an adapter from your content type tocollective.watcherlist.interfaces.IWatcherList.  In a lot of
cases using the default implementation as factory for this adapter
is fine:collective.watcherlist.watchers.WatcherListCreate an html form where people can add themselves to the watcher
list.  This could also bePloneFormGenform with a custom script
adapter as action.Register a BrowserView for your content type, inheriting fromcollective.watcherlist.browser.BaseMailand override its
propertiessubject,plainand/orhtml.Create an event handler or some other code that gets the adapter for
your content type and uses that to send an email with the subject
and contents defined in the browser view you created.Events integrationThis addons play well with zope’s event and plone’s contentrules.
It triggers a zope event on all basic actions done on the watcherlist:ToggleWatchingEventRemovedFromWatchingEventAddedToWatchingEventThose events are registred to be useable as content rule trigger so you can
create a rule based on it.It also provides a content rule action so you can create an action that’s
add or remove the current user to or from the watcherlist attached to the
context.CreditsPeopleMaurits van Rees [maurits] <maurits@vanrees.org> authorGagaro <gagaro42@gmail.com>JeanMichel FRANCOIS aka toutpt <toutpt@gmail.com>CompaniesZest Softwarehttps://zestsoftware.nl/Makina Corpuscollective.watcherlistSample integrationLet’s give an example of what you need to do in your own code to use
this package.  We define a class that holds some info about a party:>>> class Party(object):
...     def __init__(self, reason):
...         self.reason = reason
...         self.invited = []We tell the ZCA how to adapt a Party to a watcher list: how to turn it
into an object that holds a list of interested people and knows how to
send them an email.  Normally you would define an interfaceIParty, say that thePartyclass implements it and use zcml to
register an adapter for that, something like this:<adapter
    for=".interfaces.IParty"
    factory="collective.watcherlist.watchers.WatcherList"
    />Let’s ignore the interface and use python (as that is a bit easier to
use in tests).  We will use the default implementation of a
watcherlist as provided by the package:>>> from zope.component import getGlobalSiteManager
>>> from collective.watcherlist.watchers import WatcherList
>>> sm = getGlobalSiteManager()
>>> sm.registerAdapter(WatcherList, (Party, ))Now we create a Party and invite people:>>> birthday = Party("Maurits' birthday")
>>> birthday.invited.append('Fred')
>>> birthday.invited.append('Mirella')We see if we can get a watcherlist for it:>>> from collective.watcherlist.interfaces import IWatcherList
>>> watcherlist = IWatcherList(birthday)
>>> watcherlist
<collective.watcherlist.watchers.WatcherList object at ...>We can ask several things of this list:>>> watcherlist.watchers
[]
>>> watcherlist.send_emails
True
>>> watcherlist.addresses
()We can add watchers.  These should be email addresses or (at least in
a Plone context) the ids of members in the site.  In your package you
would either create a button or other small form that people can use
to add themselves to the list, or create some code that automatically
adds some people, as Poi does for the creator of a new issue.  The
code is simple:>>> watcherlist.watchers.append('maurits@example.org')
>>> watcherlist.watchers.append('reinout@example.org')
>>> watcherlist.watchers
['maurits@example.org', 'reinout@example.org']
>>> watcherlist.addresses
('maurits@example.org', 'reinout@example.org')You can always switch off email sending.  This has the effect that no
addresses are reported:>>> watcherlist.send_emails = False
>>> watcherlist.watchers
['maurits@example.org', 'reinout@example.org']
>>> watcherlist.addresses
()Undo that:>>> watcherlist.send_emails = True
>>> watcherlist.watchers
['maurits@example.org', 'reinout@example.org']
>>> watcherlist.addresses
('maurits@example.org', 'reinout@example.org')Now we send an email.  We get the email text and subject simply from a
browser view that we define.  In the test this means we need to give
the Party a request object:>>> from zope.publisher.browser import TestRequest
>>> birthday.REQUEST = TestRequest()We now send an invitation email, but this fails:>>> watcherlist.send('invitation')
Traceback (most recent call last):
...
ComponentLookupError...This means we need to create a browser view with that name.  As the
basis we should take the base browser view defined in thecollective.watcherlistpackage.  It contains three properties that
you would normally override: subject, plain and html:>>> from collective.watcherlist.browser import BaseMail
>>> class PartyMail(BaseMail):
...     @property
...     def subject(self):
...         return self.context.reason
...     @property
...     def plain(self):
...         return "Invited are %s" % self.context.invited
...     @property
...     def html(self):
...         return "<p>%s</p>" % self.plainYou would normally register this with zcml, just like any other
browser view.  But here we do that in python code:>>> from zope.interface import Interface
>>> sm.registerAdapter(PartyMail, (Party, TestRequest), Interface, 'invitation')And we send the invitation again, in both plain text and html.  In
this test we have no proper mail host setup, so we simply print the
relevant info so we can see what would happen:>>> watcherlist.send('invitation')
Subject = Maurits' birthday
Addresses = ('maurits@example.org', 'reinout@example.org')
Message =
From...
Content-Type: multipart/alternative;...
...
Content-Type: text/plain; charset="us-ascii"
...
Invited are ['Fred', 'Mirella']
...
Content-Type: text/html; charset="us-ascii"
...
<p>Invited are ['Fred', 'Mirella']</p>
...Let’s skip the html and see if that simplifies the mail:>>> PartyMail.html = ''
>>> watcherlist.send('invitation')
Subject = Maurits' birthday
Addresses = ('maurits@example.org', 'reinout@example.org')
Message =
From...
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
<BLANKLINE>
Invited are ['Fred', 'Mirella']If there is neither plain text nor html, we do not send anything:>>> PartyMail.plain = ''
>>> watcherlist.send('invitation')Let’s add a bit of html again to see that only html goes fine too:>>> PartyMail.html = '<p>You are invited.</p>'
>>> watcherlist.send('invitation')
Subject = Maurits' birthday
Addresses = ('maurits@example.org', 'reinout@example.org')
Message =
From...
MIME-Version: 1.0
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
<BLANKLINE>
<p>You are invited.</p>If we switch off email sending for this watcherlist… no emails are sent:>>> watcherlist.send_emails = False
>>> watcherlist.send('invitation')Reset that:>>> watcherlist.send_emails = TrueLook atProducts.Poifor some more examples of what you can do.Changelog3.1.0 (2018-04-26)Don’t test with Plone 4.1, 4.2 or Python 2.6 anymore.
It should still work, but I don’t want to spend time fixing the build on Travis.
[maurits]3.0.1 (2018-04-26)Declarezope.formlibdependency.  [maurits]3.0 (2016-12-23)Passimmediate=Falseby default.  This used to beTrue.  The
original idea was to send emails immediately, so that we could catch
any errors ourselves and continue with a warning.  But since Plone
4.1 the emails are by default sent at the end of the transaction,
and exceptions are caught.  For immediate mails they are not caught
there.  So for our uses cases it makes most sense to not send emails
immediately, as then Plone does what we want already.  You can still
passimmediate=Truetomailer.simple_send_mailor now also
towatchers.sendif you want the old behavior back.
This fixesissue #8.
[maurits]Checkallow_recursiveon the watcher list.  The default value is
true, which gives the same behavior as before.  When the value is
false, theaddressesproperty only looks for watchers on the
current item, not on the parent.  A sample usage would be in
Products.Poi to only allow recursive if an issue is not yet
assigned.  (I am going to use this in custom code for a client).
[maurits]Strip the email address that we get from a member or the site.
I have a site where some email addresses aretest@example.org\r\n,
which gives an error when sending.
[maurits]2.0 (2016-07-07)Removed backwards compatibility code for Plone 3.3 and 4.0.  We
already were not testing on 3.3 anymore, and 4.0 is too hard to keep
working too.  [maurits]Fixed Plone 5 email sending.  Improved code quality.  Fixed Travis tests.  [maurits]When the internal watchers list is of type list (instead of tuple),
make sure its updated when toggling a watcher [skurfer]1.2 (2013-12-03)Added events, triggers and action for content rules.  [Gagaro]1.1 (2012-11-06)Made compatible with Plone 4.3 (keeping compatibility with Plone 3).
[maurits]Moved tohttps://github.com/collective/collective.watcherlist[maurits]1.0 (2012-04-21)When showing the plain text in the browser as test, force text/plain
as content-type.
[maurits]0.3 (2011-05-09)Catch MailHostErrors when sending email.
[maurits]0.2 (2010-02-27)You can now addonly_these_addressesas an argument to the send
method.  This forces sending only to those addresses and ignoring
all others.
[maurits]Fixed possible UnicodeDecodeError when the plain text or html part
of the email was not unicode.
[maurits]0.1 (2010-02-26)Initial release