Skip to content

Commit

Permalink
Merge 25f3bc2 into e125055
Browse files Browse the repository at this point in the history
  • Loading branch information
gforcada committed Mar 10, 2016
2 parents e125055 + 25f3bc2 commit 001b4d5
Show file tree
Hide file tree
Showing 12 changed files with 700 additions and 64 deletions.
18 changes: 17 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,25 @@ Changelog

0.8 (unreleased)
----------------
- **Renamed the registry setting**, now two lists are used: ``forbidden_words`` and ``inadequate_words``.
See the ``README.rst`` for instructions on how to create an upgrade step to migrate them.
[gforcada]

- Updated ``IAlert`` utility to use either both stop words list,
or just one if told so (passed as an argument).
[gforcada]

- Nothing changed yet.
- Add a ``has_forbidden_words`` method to ``IAlert`` utility.
It allows to check only for forbidden stop words only.
[gforcada]

- Make ``@@review-objects`` view more generic by allowing a marker interface and review states to be passed.
This allows filtering which elements will be checked for stop words.
[gforcada]

- Triple the content rules so one can decide to monitor for any kind of word,
only forbidden words or only inadequate ones.
[gforcada]

0.7 (2016-01-22)
----------------
Expand Down
58 changes: 53 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,41 @@
========================
collective.contentalerts
========================
Get alerts whenever a (custom list of) word is found on a content object,
Get alerts whenever a (two custom lists of) word is found on a content object,
be that object any content type (Dexterity, Archetypes or comments).

Can be used to:

- moderate: used as a blacklist.
- highlight: used as a whitelist.

The basic idea is to use the power of `plone.app.contentrules`_ to inspect
The main idea is to use the power of `plone.app.contentrules`_ to inspect
content being generated by users for certain words.

Upon that, regular `plone.app.contentrules`_ actions can be triggered:
send an email, notify the user, apply a workflow transition...

The secondary idea is to be able to do different actions on the content depending on if the words
where found on one or the other list.

Features
--------
- manage a list of words that will be used to search (blacklist/whitelist)
- manage two lists of words that will be used to search (blacklist/whitelist)

- named ``forbidden_words`` and ``inadequate_words``

- integration with `plone.app.contentrules`_:

- a condition to look for stop words
- conditions to look for stop words (either from any list or from a specific one)
- string substitutions to add on emails the snippets where the stop words
where found (one for documents and one for comments)

- standalone utility

- with methods to either look for words on both lists or only on one of them

- provide different word lists if you need them,
either a general one (plone.registry based) or on a per contentrule basis
either two general ones (plone.registry based) or on a per contentrule basis
- look for stop words on comments, dexterity and archetypes content types
- apply a marker interface to objects that are found to have stop words
- mark objects as reviewed
Expand Down Expand Up @@ -78,6 +87,45 @@ Install collective.contentalerts by adding it to your buildout::

and then running "bin/buildout"

Upgrade notes
-------------
If you are upgrading from 0.7 to any later version there's one manual step that needs to be done.

On version 1.0 the single list of stop words was split into ``inadequate_words`` and ``forbidden_words``.

As it can not be guessed which list the former list is supposed to map,
no automatic migration is provided.

An `upgrade step <http://docs.plone.org/develop/addons/components/genericsetup.html#upgrade-steps>`_ needs to be written then.

See below an example on how to migrate the former list to the new ``forbidden_words`` list::

from plone import api
from plone.registry.interfaces import IRegistry
from zope.component import getUtility

# safe the stop words on the old location
old_setting = 'collective.contentalerts.interfaces.IStopWords.stop_words'
current_forbidden_words = api.portal.get_registry_record(name=old_setting)

# update registry
setup = api.portal.get_tool('portal_setup')
setup.runImportStepFromProfile(
'profile-collective.contentalerts:default',
'plone.app.registry'
)

# set the stop words on the new field
api.portal.set_registry_record(
name='collective.contentalerts.interfaces.IStopWords.forbidden_words',
value=current_forbidden_words
)

# remove the old setting
registry = getUtility(IRegistry)
del registry.records[old_setting]


Contribute
----------
- Issue Tracker: https://github.com/collective/collective.contentalerts/issues
Expand Down
21 changes: 17 additions & 4 deletions src/collective/contentalerts/browser/review_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ def __call__(self):
start, size, entries = self._check_parameters()

catalog = api.portal.get_tool('portal_catalog')
brains = catalog(
object_provides=IStopWordsVerified.__identifier__,
sort_on='effective',
)

query = {
'sort_on': 'effective',
}

# get the marker interface, if any, to filter objects
provides = self.request.get('type', None)
if provides:
query['object_provides'] = provides
else:
query['object_provides'] = IStopWordsVerified.__identifier__

# get the workflow states, if any, to filter objects
states = self.request.get('states', None)
if states:
query['review_state'] = states.split(',')

brains = catalog(query)
for brain in brains[start:start + size]:
verify_brain(brain, entries)

Expand Down
52 changes: 44 additions & 8 deletions src/collective/contentalerts/contentrules.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from collective.contentalerts import _
from collective.contentalerts.interfaces import IAlert
from collective.contentalerts.interfaces import IHasStopWords
from collective.contentalerts.interfaces import IInadequateTextAlertCondition
from collective.contentalerts.interfaces import IForbiddenTextAlertCondition
from collective.contentalerts.interfaces import ITextAlertCondition
from collective.contentalerts.utilities import get_text_from_object
from OFS.SimpleItem import SimpleItem
Expand All @@ -24,19 +26,31 @@ def __init__(self, context, element, event):
self.event = event

def __call__(self):
return self.check()

def check(self, forbidden=False, inadequate=False):
text = get_text_from_object(self.event)
if not text:
return False

stop_words = self.element.stop_words
if stop_words is None or stop_words.strip() == u'':
stop_words = None
else:
request = self.context.REQUEST
request.set('stop_words', stop_words)

alert_utility = getUtility(IAlert)
ret_value = alert_utility.has_stop_words(text, stop_words=stop_words)

if forbidden:
ret_value = alert_utility.has_forbidden_words(text)
elif inadequate:
ret_value = alert_utility.has_inadequate_words(text)
else:
stop_words = self.element.stop_words
if stop_words is None or stop_words.strip() == u'':
stop_words = None
else:
request = self.context.REQUEST
request.set('stop_words', stop_words)

ret_value = alert_utility.has_stop_words(
text,
stop_words=stop_words
)

# get the object to apply/remove the marker interface
obj = None
Expand Down Expand Up @@ -64,6 +78,18 @@ def _apply_marker_interface(obj, has_stop_words):
obj.reindexObject(idxs=('object_provides', ))


class InadequateTextAlertConditionExecutor(TextAlertConditionExecutor):

def __call__(self):
return self.check(inadequate=True)


class ForbiddenTextAlertConditionExecutor(TextAlertConditionExecutor):

def __call__(self):
return self.check(forbidden=True)


@implementer(ITextAlertCondition, IRuleElementData)
class TextAlertCondition(SimpleItem):
"""The persistent implementation of the text alert condition."""
Expand All @@ -79,6 +105,16 @@ def summary(self):
)


@implementer(IInadequateTextAlertCondition, IRuleElementData)
class InadequateTextAlertCondition(TextAlertCondition):
""""""


@implementer(IForbiddenTextAlertCondition, IRuleElementData)
class ForbiddenTextAlertCondition(TextAlertCondition):
""""""


class TextAlertConditionAddForm(AddForm):
form_fields = form.FormFields(ITextAlertCondition)
label = _(u'Add a text alert condition')
Expand Down
40 changes: 40 additions & 0 deletions src/collective/contentalerts/contentrules.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@
provides="plone.contentrules.rule.interfaces.IExecutable"
/>

<adapter
factory="collective.contentalerts.contentrules.InadequateTextAlertConditionExecutor"
for="zope.interface.Interface
collective.contentalerts.interfaces.IInadequateTextAlertCondition
zope.interface.Interface"
provides="plone.contentrules.rule.interfaces.IExecutable"
/>

<adapter
factory="collective.contentalerts.contentrules.ForbiddenTextAlertConditionExecutor"
for="zope.interface.Interface
collective.contentalerts.interfaces.IForbiddenTextAlertCondition
zope.interface.Interface"
provides="plone.contentrules.rule.interfaces.IExecutable"
/>

<browser:page
class="collective.contentalerts.contentrules.TextAlertConditionAddForm"
for="plone.app.contentrules.browser.interfaces.IRuleConditionAdding"
Expand All @@ -38,6 +54,30 @@
title="Text alert"
/>

<plone:ruleCondition
addview="collective.contentalerts.TextAlert"
description="Apply when inadequate words are found on the text"
editview="edit"
event="zope.component.interfaces.IObjectEvent"
factory="collective.contentalerts.contentrules.InadequateTextAlertCondition"
for="*"
name="collective.contentalerts.InadequateTextAlert"
schema="collective.contentalerts.interfaces.IInadequateTextAlertCondition"
title="Inadequate text alert"
/>

<plone:ruleCondition
addview="collective.contentalerts.TextAlert"
description="Apply when forbidden words are found on the text"
editview="edit"
event="zope.component.interfaces.IObjectEvent"
factory="collective.contentalerts.contentrules.ForbiddenTextAlertCondition"
for="*"
name="collective.contentalerts.ForbiddenTextAlert"
schema="collective.contentalerts.interfaces.IForbiddenTextAlertCondition"
title="Forbidden text alert"
/>

<!-- Content rules string substitution -->
<adapter
factory="collective.contentalerts.contentrules.TextAlertSubstitution"
Expand Down
47 changes: 44 additions & 3 deletions src/collective/contentalerts/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@ class ICollectiveContentalertsLayer(IDefaultBrowserLayer):
class IStopWords(Interface):
"""Registry settings schema being used by this distribution."""

stop_words = schema.Text(
forbidden_words = schema.Text(
title=_(
u'settings_stop_words_list_title',
u'settings_forbidden_words_list_title',
default=u'Forbidden words'
),
description=_(
u'settings_forbidden_words_list_description',
default=u'Words/sentences that will prevent an object to be made '
u'public, one per line.'
),
required=False,
)

inadequate_words = schema.Text(
title=_(
u'settings_inadequate_words_list_title',
default=u'List'
),
description=_(
u'settings_stop_words_list_description',
u'settings_inadequate_words_list_description',
default=u'Words/sentences that will generate an alert, '
u'one per line.'
),
Expand Down Expand Up @@ -57,6 +70,26 @@ def has_stop_words(text, stop_words=None):
:rtype: bool
"""

def has_forbidden_words(text):
"""Checks if the given text has words from the forbidden stop words
list
:param text: where forbidden words will be searched on.
:type text: str
:returns: whether the text contains forbidden words.
:rtype: bool
"""

def has_inadequate_words(text):
"""Checks if the given text has words from the inadequate stop words
list
:param text: where inadequate words will be searched on.
:type text: str
:returns: whether the text contains forbidden words.
:rtype: bool
"""


class ITextAlertCondition(Interface):
"""Schema for the text alert plone.app.contentrules condition."""
Expand All @@ -75,6 +108,14 @@ class ITextAlertCondition(Interface):
)


class IInadequateTextAlertCondition(ITextAlertCondition):
pass


class IForbiddenTextAlertCondition(ITextAlertCondition):
pass


class IHasStopWords(Interface):
"""Marker interface attached to objects that have stop words."""

Expand Down
Loading

0 comments on commit 001b4d5

Please sign in to comment.