Skip to content

Commit

Permalink
Merge branch 'contentrules'
Browse files Browse the repository at this point in the history
  • Loading branch information
gforcada committed Aug 12, 2015
2 parents cd16c50 + 9b74527 commit 9d56e35
Show file tree
Hide file tree
Showing 8 changed files with 502 additions and 3 deletions.
24 changes: 23 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,34 @@ Can be used to:
- moderate: used as a blacklist.
- highlight: used as a whitelist.

The basic idea is to us 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...

Features
--------
- manage a list of words that will be used to search (blacklist/whitelist)
- integration with `plone.app.contentrules`_
- standalone utility
- provide different word lists if you need them
- provide different word lists if you need them,
either a general one (plone.registry based) or on a per contentrule basis
- look for stop words on comments, dexterity and archetypes content types

Where it searches on
--------------------
collective.contentalerts searches either on the comments' text,
on ``getText()`` (for Archetypes based content types) or
on ``text`` (for Dexterity based content types).

Standalone usage
----------------
While the main integration within Plone is via a plone.app.contentrules condition,
``collective.contentalerts`` can also be used as a standalone utility.

Just get the utility (``collective.contentalerts.interfaces.IAlert``) and use
the provided methods.

Examples
--------
Expand Down
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,24 @@
zip_safe=False,
install_requires=[
'plone.api',
'plone.app.contentrules',
'plone.app.registry',
'plone.contentrules',
'plone.registry',
'Products.GenericSetup',
'setuptools',
'Zope2',
'zope.component',
# XXX migrate to z3c.form on Plone 5 (p.a.contentrules 4.0.5)
'zope.formlib',
'zope.i18nmessageid',
'zope.interface',
'zope.schema',
],
extras_require={
'test': [
'plone.app.contenttypes[test]<1.2', # to get Plone 4.3 compatibility
'plone.app.discussion',
'plone.app.testing',
'plone.browserlayer',
],
Expand Down
1 change: 1 addition & 0 deletions src/collective/contentalerts/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<i18n:registerTranslations directory="locales" />

<include file="permissions.zcml" />
<include file="contentrules.zcml" />

<include package=".browser" />

Expand Down
80 changes: 80 additions & 0 deletions src/collective/contentalerts/contentrules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
from OFS.SimpleItem import SimpleItem
from collective.contentalerts import _
from collective.contentalerts.interfaces import IAlert
from collective.contentalerts.interfaces import ITextAlertCondition
from plone.app.contentrules.browser.formhelper import AddForm
from plone.app.contentrules.browser.formhelper import EditForm
from plone.contentrules.rule.interfaces import IRuleElementData
from zope.component import getUtility
from zope.formlib import form
from zope.interface import implementer


class TextAlertConditionExecutor(object):
"""The executor for this condition."""
def __init__(self, context, element, event):
self.context = context
self.element = element
self.event = event

def __call__(self):
text = None

# if it's a comment
if getattr(self.event, 'comment', None):
if getattr(self.event.comment, 'text', None):
text = self.event.comment.text
# if it's a AT/DX
elif getattr(self.event, 'object', None):
if getattr(self.event.object, 'getText', None):
text = self.event.object.getText()
elif getattr(self.event.object, 'text', None):
text = self.event.object.text

if not text or text is None:
return False

stop_words = self.element.stop_words
if stop_words is None or stop_words.strip() == u'':
stop_words = None

alert_utility = getUtility(IAlert)

return alert_utility.has_stop_words(text, stop_words=stop_words)


@implementer(ITextAlertCondition, IRuleElementData)
class TextAlertCondition(SimpleItem):
"""The persistent implementation of the text alert condition."""
stop_words = None
element = 'collective.contentalerts.TextAlert'

@property
def summary(self):
return _(
u'contentrules_text_alert_condition_summary',
default=u'Provide a stop words list, one per line, or leave it '
u'empty to use the shared one (registry based).'
)


class TextAlertConditionAddForm(AddForm):
form_fields = form.FormFields(ITextAlertCondition)
label = _(u'Add a text alert condition')
description = _(u'A text alert condition makes the rule apply '
u'only if there are stop words on the object\'s text.')
form_name = _(u'Configure element')

def create(self, data):
condition = TextAlertCondition()
form.applyChanges(condition, self.form_fields, data)
return condition


class TextAlertConditionEditForm(EditForm):
form_fields = form.FormFields(ITextAlertCondition)
label = _(u'Edit a text alert condition')
description = _(u'A text alert condition makes the rule apply '
u'only if there are stop words on the object\'s text.')
form_name = _(u'Configure element')
42 changes: 42 additions & 0 deletions src/collective/contentalerts/contentrules.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone">

<!-- Text alert condition -->
<adapter
factory="collective.contentalerts.contentrules.TextAlertConditionExecutor"
for="zope.interface.Interface
collective.contentalerts.interfaces.ITextAlertCondition
zope.interface.Interface"
provides="plone.contentrules.rule.interfaces.IExecutable"
/>

<browser:page
class="collective.contentalerts.contentrules.TextAlertConditionAddForm"
for="plone.app.contentrules.browser.interfaces.IRuleConditionAdding"
name="collective.contentalerts.TextAlert"
permission="plone.app.contentrules.ManageContentRules"
/>

<browser:page
class="collective.contentalerts.contentrules.TextAlertConditionEditForm"
for="collective.contentalerts.interfaces.ITextAlertCondition"
name="edit"
permission="plone.app.contentrules.ManageContentRules"
/>

<plone:ruleCondition
addview="collective.contentalerts.TextAlert"
description="Apply when words from a list are found on the text"
editview="edit"
event="zope.component.interfaces.IObjectEvent"
factory="collective.contentalerts.contentrules.TextAlertCondition"
for="*"
name="collective.contentalerts.TextAlert"
schema="collective.contentalerts.interfaces.ITextAlertCondition"
title="Text alert"
/>

</configure>

17 changes: 17 additions & 0 deletions src/collective/contentalerts/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,20 @@ def has_stop_words(text, stop_words=None):
:returns: whether the text contains words from the stop words.
:rtype: bool
"""


class ITextAlertCondition(Interface):
"""Marker interface for the text alert plone.app.contentrules condition."""

stop_words = schema.Text(
title=_(
u'contentrules_text_alert_condition_field_title',
default=u'Stop words'
),
description=_(
u'contentrules_text_alert_condition_field_description',
default=u'One stop word per line, keep it empty if you want to '
u'use the generic one.'
),
required=False,
)
19 changes: 17 additions & 2 deletions src/collective/contentalerts/testing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE
from plone.app.testing import FunctionalTesting
from plone.app.testing import IntegrationTesting
from plone.app.testing import PLONE_FIXTURE
Expand All @@ -19,16 +20,30 @@ def setUpPloneSite(self, portal):
applyProfile(portal, 'collective.contentalerts:default')


class CollectiveContentalertsDexterityLayer(PloneSandboxLayer):

defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE, )

def setUpZope(self, app, configurationContext):
self.loadZCML(package=collective.contentalerts)

def setUpPloneSite(self, portal):
applyProfile(portal, 'collective.contentalerts:default')


COLLECTIVE_CONTENTALERTS_FIXTURE = CollectiveContentalertsLayer()
COLLECTIVE_CONTENTALERTS_DEXTERITY_FIXTURE = CollectiveContentalertsDexterityLayer() # noqa


COLLECTIVE_CONTENTALERTS_INTEGRATION_TESTING = IntegrationTesting(
bases=(COLLECTIVE_CONTENTALERTS_FIXTURE,),
name='CollectiveContentalertsLayer:IntegrationTesting'
)


COLLECTIVE_CONTENTALERTS_FUNCTIONAL_TESTING = FunctionalTesting(
bases=(COLLECTIVE_CONTENTALERTS_FIXTURE,),
name='CollectiveContentalertsLayer:FunctionalTesting'
)
COLLECTIVE_CONTENTALERTS_DEXTERITY_INTEGRATION_TESTING = IntegrationTesting(
bases=(COLLECTIVE_CONTENTALERTS_DEXTERITY_FIXTURE,),
name='CollectiveContentalertsDexterityLayer:IntegrationTesting'
)
Loading

0 comments on commit 9d56e35

Please sign in to comment.