{ "info": { "author": "Maurits van Rees", "author_email": "maurits@vanrees.org", "bugtrack_url": null, "classifiers": [ "Framework :: Plone", "Framework :: Plone :: 4.3", "Framework :: Plone :: 5.0", "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python", "Programming Language :: Python :: 2.7" ], "description": ".. image:: https://secure.travis-ci.org/collective/collective.watcherlist.png?branch=master\n :target: http://travis-ci.org/collective/collective.watcherlist\n :alt: Travis CI badge\n\n.. image:: https://coveralls.io/repos/collective/collective.watcherlist/badge.png?branch=master\n :target: https://coveralls.io/r/collective/collective.watcherlist\n :alt: Coveralls badge\n\n\nIntroduction\n============\n\n``collective.watcherlist`` is a package that enables you to keep a\nlist of people who want to receive emails when an item gets updated.\nThe main use case is something like `Products.Poi `_, which is an issue\ntracker for Plone. That product lets you create an issue tracker. In\nthis tracker people can add issues. The tracker has managers.\nEverytime a new issue is posted, the managers should receive an email.\nWhen a manager responds to an issue, the original poster (and the\nother managers) should get an email. Anyone interested in following\nthe issue, should be able to add themselves to the list of people who\nget an email. The functionality for this was in Poi, but has now been\nfactored out into this ``collective.watcherlist`` package.\n\n\nWho should use this package?\n============================\n\nThis is not a package for end users. Out of the box it does nothing.\nIt is a package for integrators or developers. You need to write some\npython and zcml in your own package (like Poi now does) to hook\n``collective.watcherlist`` up in your code.\n\nWe gladly use the ZCA (Zope Component Architecture) to allow others to\nregister their own adapters and own email texts, so outside of Zope\nthe package does not make much sense. And we import some code from\nPlone too, so you will need that. If you want to use it in bare Zope\nor CMF, contact me: we can probably do some conditional imports\ninstead.\n\n``collective.watcherlist`` might also be usable as a basis for a\nnewsletter product. If you feel `Singing and Dancing `_ is overkill\nfor you, or too hard to adapt to your specific needs, you could try\nwriting some code around ``collective.watcherlist`` instead.\n\n\nBasic integration steps\n=======================\n\nIn its simplest form, the integration that is needed, is this:\n\n- Register an adapter from your content type to\n ``collective.watcherlist.interfaces.IWatcherList``. In a lot of\n cases using the default implementation as factory for this adapter\n is fine: ``collective.watcherlist.watchers.WatcherList``\n\n- Create an html form where people can add themselves to the watcher\n list. This could also be `PloneFormGen `_ form with a custom script\n adapter as action.\n\n- Register a BrowserView for your content type, inheriting from\n ``collective.watcherlist.browser.BaseMail`` and override its\n properties ``subject``, ``plain`` and/or ``html``.\n\n- Create an event handler or some other code that gets the adapter for\n your content type and uses that to send an email with the subject\n and contents defined in the browser view you created.\n\nEvents integration\n==================\n\nThis addons play well with zope's event and plone's contentrules.\nIt triggers a zope event on all basic actions done on the watcherlist:\n\n* ToggleWatchingEvent\n* RemovedFromWatchingEvent\n* AddedToWatchingEvent\n\nThose events are registred to be useable as content rule trigger so you can\ncreate a rule based on it.\n\nIt also provides a content rule action so you can create an action that's\nadd or remove the current user to or from the watcherlist attached to the\ncontext.\n\nCredits\n=======\n\nPeople\n------\n\n* Maurits van Rees [maurits] author\n* Gagaro \n* JeanMichel FRANCOIS aka toutpt \n\nCompanies\n---------\n\n* Zest Software https://zestsoftware.nl/\n* `Makina Corpus `_\n\ncollective.watcherlist\n======================\n\nSample integration\n------------------\n\nLet's give an example of what you need to do in your own code to use\nthis package. We define a class that holds some info about a party::\n\n >>> class Party(object):\n ... def __init__(self, reason):\n ... self.reason = reason\n ... self.invited = []\n\nWe tell the ZCA how to adapt a Party to a watcher list: how to turn it\ninto an object that holds a list of interested people and knows how to\nsend them an email. Normally you would define an interface\n``IParty``, say that the ``Party`` class implements it and use zcml to\nregister an adapter for that, something like this::\n\n \n\nLet's ignore the interface and use python (as that is a bit easier to\nuse in tests). We will use the default implementation of a\nwatcherlist as provided by the package::\n\n >>> from zope.component import getGlobalSiteManager\n >>> from collective.watcherlist.watchers import WatcherList\n >>> sm = getGlobalSiteManager()\n >>> sm.registerAdapter(WatcherList, (Party, ))\n\n.. This documentation doubles as automated test, so we run into a test\n.. detail here: the WatcherList adapter stores the watchers in an\n.. annotation, so we need to tell the ZCA how to do that; for standard\n.. Plone/Archetypes content types this is already done, so you usually do\n.. not need to care about this. Oh, we can hide this, nice::\n :hide:\n\n >>> from zope.annotation.interfaces import IAnnotations\n >>> from zope.annotation.attribute import AttributeAnnotations\n >>> sm.registerAdapter(AttributeAnnotations, (Party, ), IAnnotations)\n\nNow we create a Party and invite people::\n\n >>> birthday = Party(\"Maurits' birthday\")\n >>> birthday.invited.append('Fred')\n >>> birthday.invited.append('Mirella')\n\nWe see if we can get a watcherlist for it::\n\n >>> from collective.watcherlist.interfaces import IWatcherList\n >>> watcherlist = IWatcherList(birthday)\n >>> watcherlist\n \n\nWe can ask several things of this list::\n\n >>> watcherlist.watchers\n []\n >>> watcherlist.send_emails\n True\n >>> watcherlist.addresses\n ()\n\nWe can add watchers. These should be email addresses or (at least in\na Plone context) the ids of members in the site. In your package you\nwould either create a button or other small form that people can use\nto add themselves to the list, or create some code that automatically\nadds some people, as Poi does for the creator of a new issue. The\ncode is simple::\n\n >>> watcherlist.watchers.append('maurits@example.org')\n >>> watcherlist.watchers.append('reinout@example.org')\n >>> watcherlist.watchers\n ['maurits@example.org', 'reinout@example.org']\n >>> watcherlist.addresses\n ('maurits@example.org', 'reinout@example.org')\n\nYou can always switch off email sending. This has the effect that no\naddresses are reported::\n\n >>> watcherlist.send_emails = False\n >>> watcherlist.watchers\n ['maurits@example.org', 'reinout@example.org']\n >>> watcherlist.addresses\n ()\n\nUndo that::\n\n >>> watcherlist.send_emails = True\n >>> watcherlist.watchers\n ['maurits@example.org', 'reinout@example.org']\n >>> watcherlist.addresses\n ('maurits@example.org', 'reinout@example.org')\n\nNow we send an email. We get the email text and subject simply from a\nbrowser view that we define. In the test this means we need to give\nthe Party a request object::\n\n >>> from zope.publisher.browser import TestRequest\n >>> birthday.REQUEST = TestRequest()\n\nWe now send an invitation email, but this fails::\n\n >>> watcherlist.send('invitation')\n Traceback (most recent call last):\n ...\n ComponentLookupError...\n\nThis means we need to create a browser view with that name. As the\nbasis we should take the base browser view defined in the\n``collective.watcherlist`` package. It contains three properties that\nyou would normally override: subject, plain and html::\n\n >>> from collective.watcherlist.browser import BaseMail\n >>> class PartyMail(BaseMail):\n ... @property\n ... def subject(self):\n ... return self.context.reason\n ... @property\n ... def plain(self):\n ... return \"Invited are %s\" % self.context.invited\n ... @property\n ... def html(self):\n ... return \"

%s

\" % self.plain\n\nYou would normally register this with zcml, just like any other\nbrowser view. But here we do that in python code::\n\n >>> from zope.interface import Interface\n >>> sm.registerAdapter(PartyMail, (Party, TestRequest), Interface, 'invitation')\n\nAnd we send the invitation again, in both plain text and html. In\nthis test we have no proper mail host setup, so we simply print the\nrelevant info so we can see what would happen::\n\n >>> watcherlist.send('invitation')\n Subject = Maurits' birthday\n Addresses = ('maurits@example.org', 'reinout@example.org')\n Message =\n From...\n Content-Type: multipart/alternative;...\n ...\n Content-Type: text/plain; charset=\"us-ascii\"\n ...\n Invited are ['Fred', 'Mirella']\n ...\n Content-Type: text/html; charset=\"us-ascii\"\n ...\n

Invited are ['Fred', 'Mirella']

\n ...\n\nLet's skip the html and see if that simplifies the mail::\n\n >>> PartyMail.html = ''\n >>> watcherlist.send('invitation')\n Subject = Maurits' birthday\n Addresses = ('maurits@example.org', 'reinout@example.org')\n Message =\n From...\n MIME-Version: 1.0\n Content-Type: text/plain; charset=\"us-ascii\"\n Content-Transfer-Encoding: 7bit\n \n Invited are ['Fred', 'Mirella']\n\nIf there is neither plain text nor html, we do not send anything::\n\n >>> PartyMail.plain = ''\n >>> watcherlist.send('invitation')\n\nLet's add a bit of html again to see that only html goes fine too::\n\n >>> PartyMail.html = '

You are invited.

'\n >>> watcherlist.send('invitation')\n Subject = Maurits' birthday\n Addresses = ('maurits@example.org', 'reinout@example.org')\n Message =\n From...\n MIME-Version: 1.0\n Content-Type: text/html; charset=\"us-ascii\"\n Content-Transfer-Encoding: 7bit\n \n

You are invited.

\n\nIf we switch off email sending for this watcherlist... no emails are sent::\n\n >>> watcherlist.send_emails = False\n >>> watcherlist.send('invitation')\n\nReset that::\n\n >>> watcherlist.send_emails = True\n\nLook at `Products.Poi `_ for some more examples of what you can do.\n\nChangelog\n=========\n\n3.1.0 (2018-04-26)\n------------------\n\n- Don't test with Plone 4.1, 4.2 or Python 2.6 anymore.\n It should still work, but I don't want to spend time fixing the build on Travis.\n [maurits]\n\n\n3.0.1 (2018-04-26)\n------------------\n\n- Declare ``zope.formlib`` dependency. [maurits]\n\n\n3.0 (2016-12-23)\n----------------\n\n- Pass ``immediate=False`` by default. This used to be ``True``. The\n original idea was to send emails immediately, so that we could catch\n any errors ourselves and continue with a warning. But since Plone\n 4.1 the emails are by default sent at the end of the transaction,\n and exceptions are caught. For immediate mails they are not caught\n there. So for our uses cases it makes most sense to not send emails\n immediately, as then Plone does what we want already. You can still\n pass ``immediate=True`` to ``mailer.simple_send_mail`` or now also\n to ``watchers.send`` if you want the old behavior back.\n This fixes `issue #8 `_.\n [maurits]\n\n- Check ``allow_recursive`` on the watcher list. The default value is\n true, which gives the same behavior as before. When the value is\n false, the ``addresses`` property only looks for watchers on the\n current item, not on the parent. A sample usage would be in\n Products.Poi to only allow recursive if an issue is not yet\n assigned. (I am going to use this in custom code for a client).\n [maurits]\n\n- Strip the email address that we get from a member or the site.\n I have a site where some email addresses are ``test@example.org\\r\\n``,\n which gives an error when sending.\n [maurits]\n\n\n2.0 (2016-07-07)\n----------------\n\n- Removed backwards compatibility code for Plone 3.3 and 4.0. We\n already were not testing on 3.3 anymore, and 4.0 is too hard to keep\n working too. [maurits]\n\n- Fixed Plone 5 email sending. Improved code quality. Fixed Travis tests. [maurits]\n\n- When the internal watchers list is of type list (instead of tuple),\n make sure its updated when toggling a watcher [skurfer]\n\n1.2 (2013-12-03)\n----------------\n\n- Added events, triggers and action for content rules. [Gagaro]\n\n\n1.1 (2012-11-06)\n----------------\n\n- Made compatible with Plone 4.3 (keeping compatibility with Plone 3).\n [maurits]\n\n- Moved to https://github.com/collective/collective.watcherlist\n [maurits]\n\n\n1.0 (2012-04-21)\n----------------\n\n- When showing the plain text in the browser as test, force text/plain\n as content-type.\n [maurits]\n\n\n0.3 (2011-05-09)\n----------------\n\n- Catch MailHostErrors when sending email.\n [maurits]\n\n\n0.2 (2010-02-27)\n----------------\n\n- You can now add ``only_these_addresses`` as an argument to the send\n method. This forces sending only to those addresses and ignoring\n all others.\n [maurits]\n\n- Fixed possible UnicodeDecodeError when the plain text or html part\n of the email was not unicode.\n [maurits]\n\n\n0.1 (2010-02-26)\n----------------\n\n- Initial release", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/collective/collective.watcherlist", "keywords": "Plone notifications watching", "license": "GPL", "maintainer": "", "maintainer_email": "", "name": "collective.watcherlist", "package_url": "https://pypi.org/project/collective.watcherlist/", "platform": "", "project_url": "https://pypi.org/project/collective.watcherlist/", "project_urls": { "Homepage": "https://github.com/collective/collective.watcherlist" }, "release_url": "https://pypi.org/project/collective.watcherlist/3.1.0/", "requires_dist": null, "requires_python": "", "summary": "Send emails from Plone to interested members (watchers)", "version": "3.1.0" }, "last_serial": 3811853, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "c0a1b2282d06fb099f6665ddf45c3858", "sha256": "e8f311854ceaf8cd4eb97496ca010bf279ccfbd83a7fe2347f5c95058c78ed7a" }, "downloads": -1, "filename": "collective.watcherlist-0.1.zip", "has_sig": false, "md5_digest": "c0a1b2282d06fb099f6665ddf45c3858", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41504, "upload_time": "2010-02-26T22:38:52", "url": "https://files.pythonhosted.org/packages/f0/ca/7244d14950fb197926e2c6be3f99388c524871972c866918b10d2f04b1d9/collective.watcherlist-0.1.zip" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "db19e219b5b65c51c9ced1423e9f56ef", "sha256": "744330c3e1ab708ede97b103a0ff70b6e3b8ec48a9c11d3859f89706a32e4765" }, "downloads": -1, "filename": "collective.watcherlist-0.2.zip", "has_sig": false, "md5_digest": "db19e219b5b65c51c9ced1423e9f56ef", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 42029, "upload_time": "2010-02-27T01:31:16", "url": "https://files.pythonhosted.org/packages/87/67/bfcc82e1ed65eee3c955ea01407af849c3e164a700e636e609ccd0ec4f73/collective.watcherlist-0.2.zip" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "cd40ba4736db1accf41554846157fdfd", "sha256": "e560de2f2e70c85464d825c6f4efba6d200dc64aae516ec27c21595980708583" }, "downloads": -1, "filename": "collective.watcherlist-0.3.zip", "has_sig": false, "md5_digest": "cd40ba4736db1accf41554846157fdfd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44209, "upload_time": "2011-05-09T17:49:28", "url": "https://files.pythonhosted.org/packages/35/87/e6edfba861068f34133fbce4f27aa2ca2e7d8dba694e6bf4a1623f8ad9dc/collective.watcherlist-0.3.zip" } ], "1.0": [ { "comment_text": "", "digests": { "md5": "6e112546f02eaa45c14f7e43ab60eee8", "sha256": "e5358f4151df7139112a2d8761be26d6303f279268295a8c3fa47f84eca7fef9" }, "downloads": -1, "filename": "collective.watcherlist-1.0.zip", "has_sig": false, "md5_digest": "6e112546f02eaa45c14f7e43ab60eee8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44826, "upload_time": "2012-04-21T01:35:52", "url": "https://files.pythonhosted.org/packages/1d/b9/cd0c9abc49944b1ace71fb8e14f731a85f045edbacc826558e253beeded2/collective.watcherlist-1.0.zip" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "ec46635267093af52151f5640700329d", "sha256": "c00bcb75fd2f29f23340644bfe86067a2c13c21e58a07da7bcb293418fb99858" }, "downloads": -1, "filename": "collective.watcherlist-1.1.zip", "has_sig": false, "md5_digest": "ec46635267093af52151f5640700329d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45576, "upload_time": "2012-11-06T11:30:39", "url": "https://files.pythonhosted.org/packages/ea/b1/ad209f3bda7e39f8bc90db78dd2396404f4580f1733970c31a9bc0883017/collective.watcherlist-1.1.zip" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "f57d3ea4d6462bb1dff500a2d0adcc20", "sha256": "c5c4caf311fc7b2df4739027499f2343d18d531546eaaf672a0b0dca82073d93" }, "downloads": -1, "filename": "collective.watcherlist-1.2.zip", "has_sig": false, "md5_digest": "f57d3ea4d6462bb1dff500a2d0adcc20", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 55710, "upload_time": "2013-12-03T13:29:26", "url": "https://files.pythonhosted.org/packages/6a/37/d26963076a60d1aa83de110f96428a7d6e2a732f7649e506679f0513f91e/collective.watcherlist-1.2.zip" } ], "2.0": [ { "comment_text": "", "digests": { "md5": "a9ac82c86b712baa4e9db3406718844e", "sha256": "39815450053b5c9bc19f3dfc74bc1b17f75c96522f669c138003e3aa6b056a89" }, "downloads": -1, "filename": "collective.watcherlist-2.0.tar.gz", "has_sig": false, "md5_digest": "a9ac82c86b712baa4e9db3406718844e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34388, "upload_time": "2016-07-07T14:16:45", "url": "https://files.pythonhosted.org/packages/94/86/93a4e32a94efbbf277f9e34ae7ce97e39528b0a7b93289ec3bb8a67d91d0/collective.watcherlist-2.0.tar.gz" } ], "3.0": [ { "comment_text": "", "digests": { "md5": "09d9ec2576b66b42752126b0c8ba767f", "sha256": "afe968568f15716c2c558c9282bc17c9f5fc6b7732667af7a27c65f335f95461" }, "downloads": -1, "filename": "collective.watcherlist-3.0.tar.gz", "has_sig": false, "md5_digest": "09d9ec2576b66b42752126b0c8ba767f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35712, "upload_time": "2016-12-23T18:08:55", "url": "https://files.pythonhosted.org/packages/8a/4a/0097fdb2d901674ed88ce2ec7ccdc380d24269432c7ba4ecb849e0f4ffcb/collective.watcherlist-3.0.tar.gz" } ], "3.0.1": [ { "comment_text": "", "digests": { "md5": "d0dfaf67011f93e2b97dc8bb84c704be", "sha256": "89d25a68f8b88560cbfccd45e97c5f47208626215bc783360486e889aae3fd4b" }, "downloads": -1, "filename": "collective.watcherlist-3.0.1.tar.gz", "has_sig": false, "md5_digest": "d0dfaf67011f93e2b97dc8bb84c704be", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32899, "upload_time": "2018-04-26T20:39:09", "url": "https://files.pythonhosted.org/packages/e6/19/22a62254d35052c139a4495d2e614221c0705ccb8adf2e8a0123b8290c75/collective.watcherlist-3.0.1.tar.gz" } ], "3.1.0": [ { "comment_text": "", "digests": { "md5": "1e16bc58e2d0e47abefcb5e186d7e741", "sha256": "ae7fe3918479f5cd4a5faef415c930690d468c7caa098376d386d34b69f647c6" }, "downloads": -1, "filename": "collective.watcherlist-3.1.0.tar.gz", "has_sig": false, "md5_digest": "1e16bc58e2d0e47abefcb5e186d7e741", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33071, "upload_time": "2018-04-26T20:54:05", "url": "https://files.pythonhosted.org/packages/fe/d7/eeb09281abd8635f8c0fccca00f9aa9dbf2ff2287948f3b799abd4f03640/collective.watcherlist-3.1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1e16bc58e2d0e47abefcb5e186d7e741", "sha256": "ae7fe3918479f5cd4a5faef415c930690d468c7caa098376d386d34b69f647c6" }, "downloads": -1, "filename": "collective.watcherlist-3.1.0.tar.gz", "has_sig": false, "md5_digest": "1e16bc58e2d0e47abefcb5e186d7e741", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33071, "upload_time": "2018-04-26T20:54:05", "url": "https://files.pythonhosted.org/packages/fe/d7/eeb09281abd8635f8c0fccca00f9aa9dbf2ff2287948f3b799abd4f03640/collective.watcherlist-3.1.0.tar.gz" } ] }