plone.app.discussion/moderation: Test

ksuess <jenkins-z4DKO/[email protected]> Fri, 28 Jul 2017 12:34:21 -0700 (PDT)
Newsgroups gmane.comp.web.zope.plone.cvs
Message-ID <[email protected]>
Repository: plone.app.discussion
Branch: refs/heads/moderation
Date: 2017-07-28T19:12:49Z
Author: Katja Süss (ksuess) <[email protected]>
Commit: https://github.com/plone/plone.app.discussion/commit/78abff152d1e796e87f0f720cd7fd34bf1bb354c

Test

Files changed:
M docs/source/conf.py
M plone/app/discussion/browser/captcha.py
M plone/app/discussion/browser/comment.py
M plone/app/discussion/browser/moderation.pt
M plone/app/discussion/browser/traversal.py
M plone/app/discussion/browser/validator.py
M plone/app/discussion/catalog.py
M plone/app/discussion/comment.py
M plone/app/discussion/conversation.py
M plone/app/discussion/interfaces.py
M plone/app/discussion/testing.py
M plone/app/discussion/tests/functional_test_comment_review_workflow.txt
M plone/app/discussion/tests/test_conversation.py
M plone/app/discussion/tests/test_events.py
M plone/app/discussion/upgrades.py
M setup.py

diff --git a/docs/source/conf.py b/docs/source/conf.py
index d2a393e0..71374d4b 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -17,7 +17,7 @@
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+# sys.path.append(os.path.abspath('.'))
 
 # -- General configuration ----------------------------------------------------
 
@@ -95,7 +95,7 @@
 #modindex_common_prefix = []
 
 
-# -- Options for HTML output ---------------------------------------------------
+# -- Options for HTML output ---------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  Major themes that come with
 # Sphinx are currently 'default' and 'sphinxdoc'.
@@ -169,7 +169,7 @@
 htmlhelp_basename = 'ploneappdiscussiondoc'
 
 
-# -- Options for LaTeX output --------------------------------------------------
+# -- Options for LaTeX output --------------------------------------------
 
 # The paper size ('letter' or 'a4').
 #latex_paper_size = 'letter'
@@ -180,8 +180,8 @@
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
-  ('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
-   u'Timo Stollenwerk', 'manual'),
+    ('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
+     u'Timo Stollenwerk', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
diff --git a/plone/app/discussion/browser/captcha.py b/plone/app/discussion/browser/captcha.py
index ffbda2fe..090fce95 100644
--- a/plone/app/discussion/browser/captcha.py
+++ b/plone/app/discussion/browser/captcha.py
@@ -12,29 +12,30 @@
 from z3c.form.field import Fields
 from zope import interface
 from zope.annotation import factory
-from zope.component import adapts
+from zope.component import adapter
 from zope.component import queryUtility
 from zope.interface import Interface
 from zope.publisher.interfaces.browser import IDefaultBrowserLayer
 
 
+@adapter(Comment)
 @interface.implementer(ICaptcha)
 class Captcha(Persistent):
     """Captcha input field.
     """
-    adapts(Comment)
-    captcha = u""
+    captcha = u''
+
 
 Captcha = factory(Captcha)
 
 
+# context, request, form
+@adapter(Interface, IDefaultBrowserLayer, CommentForm)
 class CaptchaExtender(extensible.FormExtender):
     """Extends the comment form with a Captcha. This Captcha extender is only
     registered when a plugin is installed that provides the
     "plone.app.discussion-captcha" feature.
     """
-    # context, request, form
-    adapts(Interface, IDefaultBrowserLayer, CommentForm)
 
     fields = Fields(ICaptcha)
 
@@ -65,5 +66,3 @@ def update(self):
                 self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
             else:
                 self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
-
-
diff --git a/plone/app/discussion/browser/comment.py b/plone/app/discussion/browser/comment.py
index 6b9b5d31..ab5f32dc 100644
--- a/plone/app/discussion/browser/comment.py
+++ b/plone/app/discussion/browser/comment.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# coding: utf-8
 from AccessControl import getSecurityManager
 from Acquisition import aq_inner
 from Acquisition import aq_parent
@@ -112,6 +112,5 @@ def handle_cancel(self, action):
             type='info')
         return self._redirect(target=self.context.absolute_url())
 
-EditComment = wrap_form(EditCommentForm)
 
-# EOF
+EditComment = wrap_form(EditCommentForm)
diff --git a/plone/app/discussion/browser/moderation.pt b/plone/app/discussion/browser/moderation.pt
index 24b2fe67..aca24bd3 100644
--- a/plone/app/discussion/browser/moderation.pt
+++ b/plone/app/discussion/browser/moderation.pt
@@ -106,11 +106,11 @@
                                 </td>
                                 <td>
                                     <span tal:content="python:item.author_name or item.Creator">Name</span>
-                                    <tal:email tal:condition="email"><br /><a 
+                                    <tal:email tal:condition="email"><br /><a
                                         tal:attributes="href string:mailto:$email"
                                         tal:content="email">Email</a></tal:email>
                                 </td>
-                                <td tal:content="python:toLocalizedTime(item.ModificationDate, 
+                                <td tal:content="python:toLocalizedTime(item.ModificationDate,
                                       long_format=1)" />
                                 <td>
                                     <a tal:attributes="href item/getURL"
diff --git a/plone/app/discussion/browser/traversal.py b/plone/app/discussion/browser/traversal.py
index a9c95e0e..8574417e 100644
--- a/plone/app/discussion/browser/traversal.py
+++ b/plone/app/discussion/browser/traversal.py
@@ -4,7 +4,7 @@
 into an actual comment object.
 """
 from plone.app.discussion.interfaces import IConversation
-from zope.component import adapts
+from zope.component import adapter
 from zope.component import queryAdapter
 from zope.interface import implementer
 from zope.interface import Interface
@@ -13,6 +13,7 @@
 from zope.traversing.interfaces import TraversalError
 
 
+@adapter(Interface, IBrowserRequest)
 @implementer(ITraversable)
 class ConversationNamespace(object):
     """Allow traversal into a conversation via a ++conversation++name
@@ -21,7 +22,6 @@ class ConversationNamespace(object):
     (unnamed) adapter. This is to work around a bug in OFS.Traversable which
     does not allow traversal to namespaces with an empty string name.
     """
-    adapts(Interface, IBrowserRequest)
 
     def __init__(self, context, request=None):
         self.context = context
diff --git a/plone/app/discussion/browser/validator.py b/plone/app/discussion/browser/validator.py
index 3c56ac3c..6323da4a 100644
--- a/plone/app/discussion/browser/validator.py
+++ b/plone/app/discussion/browser/validator.py
@@ -8,7 +8,7 @@
 from plone.registry.interfaces import IRegistry
 from z3c.form import validator
 from z3c.form.interfaces import IValidator
-from zope.component import adapts
+from zope.component import adapter
 from zope.component import getMultiAdapter
 from zope.component import queryUtility
 from zope.interface import implementer
@@ -32,9 +32,9 @@
     pass
 
 
+@adapter(Interface, IDiscussionLayer, Interface, IField, Interface)
 @implementer(IValidator)
 class CaptchaValidator(validator.SimpleFieldValidator):
-    adapts(Interface, IDiscussionLayer, Interface, IField, Interface)
     #       Object, Request, Form, Field, Widget,
     # We adapt the CaptchaValidator class to all form fields (IField)
 
diff --git a/plone/app/discussion/catalog.py b/plone/app/discussion/catalog.py
index ede26748..87c3d35f 100644
--- a/plone/app/discussion/catalog.py
+++ b/plone/app/discussion/catalog.py
@@ -77,8 +77,10 @@ def creator(object):
 @indexer(IComment)
 def description(object):
     # Return the first 25 words of the comment text and append ' [...]'
-    text = join(object.getText(
-        targetMimetype='text/plain').split()[:MAX_DESCRIPTION])
+    text = join(
+            object.getText(targetMimetype='text/plain')
+            .split()[:MAX_DESCRIPTION]
+            )
     if len(object.getText().split()) > 25:
         text += ' [...]'
     return text
@@ -99,37 +101,43 @@ def in_response_to(object):
 @indexer(IComment)
 def effective(object):
     # the catalog index needs Zope DateTime instead of Python datetime
-    return DateTime(object.creation_date.year,
-                    object.creation_date.month,
-                    object.creation_date.day,
-                    object.creation_date.hour,
-                    object.creation_date.minute,
-                    object.creation_date.second,
-                    'GMT')
+    return DateTime(
+            object.creation_date.year,
+            object.creation_date.month,
+            object.creation_date.day,
+            object.creation_date.hour,
+            object.creation_date.minute,
+            object.creation_date.second,
+            'GMT',
+            )
 
 
 @indexer(IComment)
 def created(object):
     # the catalog index needs Zope DateTime instead of Python datetime
-    return DateTime(object.creation_date.year,
-                    object.creation_date.month,
-                    object.creation_date.day,
-                    object.creation_date.hour,
-                    object.creation_date.minute,
-                    object.creation_date.second,
-                    'GMT')
+    return DateTime(
+            object.creation_date.year,
+            object.creation_date.month,
+            object.creation_date.day,
+            object.creation_date.hour,
+            object.creation_date.minute,
+            object.creation_date.second,
+            'GMT',
+            )
 
 
 @indexer(IComment)
 def modified(object):
     # the catalog index needs Zope DateTime instead of Python datetime
-    return DateTime(object.modification_date.year,
-                    object.modification_date.month,
-                    object.modification_date.day,
-                    object.modification_date.hour,
-                    object.modification_date.minute,
-                    object.modification_date.second,
-                    'GMT')
+    return DateTime(
+            object.modification_date.year,
+            object.modification_date.month,
+            object.modification_date.day,
+            object.modification_date.hour,
+            object.modification_date.minute,
+            object.modification_date.second,
+            'GMT',
+            )
 
 
 # Override the conversation indexers for comments
diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py
index 92c1c504..5a7f6892 100644
--- a/plone/app/discussion/comment.py
+++ b/plone/app/discussion/comment.py
@@ -42,7 +42,8 @@
 
 COMMENT_TITLE = _(
     u'comment_title',
-    default=u'${author_name} on ${content}')
+    default=u'${author_name} on ${content}',
+    )
 
 MAIL_NOTIFICATION_MESSAGE = _(
     u'mail_notification_message',
@@ -50,7 +51,8 @@
             u'has been posted here: ${link}\n\n'
             u'---\n'
             u'${text}\n'
-            u'---\n')
+            u'---\n',
+    )
 
 MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
     u'mail_notification_message_moderator',
@@ -60,7 +62,8 @@
             u'${text}\n'
             u'---\n\n'
             u'Approve comment:\n${link_approve}\n\n'
-            u'Delete comment:\n${link_delete}\n')
+            u'Delete comment:\n${link_delete}\n',
+    )
 
 logger = logging.getLogger('plone.app.discussion')
 
diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py
index 525a64d5..989f31eb 100644
--- a/plone/app/discussion/conversation.py
+++ b/plone/app/discussion/conversation.py
@@ -30,7 +30,6 @@
 from zope.annotation.interfaces import IAnnotatable
 from zope.annotation.interfaces import IAnnotations
 from zope.component import adapter
-from zope.component import adapts
 from zope.container.contained import ContainerModifiedEvent
 from zope.event import notify
 from zope.interface import implementer
@@ -325,13 +324,13 @@ def conversationCanonicalAdapterFactory(content):  # pragma: no cover
         return conversationAdapterFactory(content)
 
 
+@adapter(Conversation)  # relies on implementation details
 @implementer(IReplies)
 class ConversationReplies(object):
     """An IReplies adapter for conversations.
 
     This makes it easy to work with top-level comments.
     """
-    adapts(Conversation)  # relies on implementation details
 
     def __init__(self, context):
         self.conversation = context
@@ -401,6 +400,7 @@ def children(self):
         return self.conversation._children.get(self.comment_id, LLSet())
 
 
+@adapter(Comment)
 @implementer(IReplies)
 class CommentReplies(ConversationReplies):
     """An IReplies adapter for comments.
@@ -412,8 +412,6 @@ class CommentReplies(ConversationReplies):
     # most likely, anyone writing a different type of Conversation will also
     # have a different type of Comment
 
-    adapts(Comment)
-
     def __init__(self, context):
         self.comment = context
         self.conversation = aq_parent(self.comment)
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 5799602e..549eaf97 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -11,13 +11,15 @@
 from zope.interface import Invalid
 from zope.interface.common.mapping import IIterableMapping
 
+
 def isEmail(value):
     portal = getUtility(ISiteRoot)
     reg_tool = getToolByName(portal, 'portal_registration')
     if not (value and reg_tool.isValidEmail(value)):
-        raise Invalid(_("Invalid email address."))
+        raise Invalid(_('Invalid email address.'))
     return True
 
+
 class IConversation(IIterableMapping):
     """A conversation about a content object.
 
@@ -160,7 +162,10 @@ class IComment(Interface):
 
     # for anonymous comments only, set to None for logged in comments
     author_name = schema.TextLine(title=_(u'Name'), required=False)
-    author_email = schema.TextLine(title=_(u'Email'), required=False, constraint=isEmail)
+    author_email = schema.TextLine(title=_(u'Email'),
+                                   required=False,
+                                   constraint=isEmail,
+                                   )
 
     title = schema.TextLine(title=_(u'label_subject',
                                     default=u'Subject'))
diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py
index 1612edd0..4280a6e0 100644
--- a/plone/app/discussion/testing.py
+++ b/plone/app/discussion/testing.py
@@ -11,7 +11,6 @@
 from plone.registry.interfaces import IRegistry
 from Products.CMFCore.utils import getToolByName
 from zope.component import queryUtility
-from zope.configuration import xmlconfig
 
 
 try:
@@ -40,9 +39,9 @@ class PloneAppDiscussion(PloneSandboxLayer):
     def setUpZope(self, app, configurationContext):
         # Load ZCML
         import plone.app.discussion
-        xmlconfig.file('configure.zcml',
-                       plone.app.discussion,
-                       context=configurationContext)
+        self.loadZCML(package=plone.app.discussion,
+                      context=configurationContext,
+                      )
 
     def setUpPloneSite(self, portal):
         # Install into Plone site using portal_setup
diff --git a/plone/app/discussion/tests/functional_test_comment_review_workflow.txt b/plone/app/discussion/tests/functional_test_comment_review_workflow.txt
index 7cc743c6..cd654d9c 100644
--- a/plone/app/discussion/tests/functional_test_comment_review_workflow.txt
+++ b/plone/app/discussion/tests/functional_test_comment_review_workflow.txt
@@ -182,7 +182,7 @@ flaw? Though, the comment is published properly.
     >>> browser.handleErrors = False
     >>> browser.raiseHttpErrors = True
 
-Make sure anonyous users see the approved comment, but not the unapproved ones.
+Make sure anonymous users see the approved comment, but not the unapproved ones.
 
     >>> unprivileged_browser.open(urldoc)
     >>> 'First anonymous comment' in unprivileged_browser.contents
@@ -230,3 +230,53 @@ Make sure the catalog has been updated properly.
 
     >>> portal.portal_catalog.searchResults(id='doc', total_comments=0)
     [<Products...]
+
+
+Moderation view
+---------------
+
+Enable anonymous comment with email.
+
+    >>> browser.open(portal_url + '/logout')
+    >>> browser.open(portal_url + '/login_form')
+    >>> browser.getControl(name='__ac_name').value = 'admin'
+    >>> browser.getControl(name='__ac_password').value = 'secret'
+    >>> browser.getControl(name='submit').click()
+    >>> browser.open(portal_url+'/@@discussion-controlpanel')
+    >>> browser.getControl(name='form.widgets.anonymous_comments:list').value = 'selected'
+    >>> browser.getControl(name='form.widgets.anonymous_email_enabled:list').value = 'selected'
+    >>> browser.getControl(name='form.buttons.save').click()
+    >>> browser.open(portal_url + '/logout')
+
+Now we can post an anonymous comment.
+
+    >>> unprivileged_browser.open(urldoc)
+    >>> unprivileged_browser.getControl(name='form.widgets.text').value = "This is an anonymous comment"
+    >>> unprivileged_browser.getControl(name='form.widgets.author_name').value = u'John'
+    >>> unprivileged_browser.getControl(name='form.widgets.author_email').value = 'john-PV5Ro7/[email protected]'
+    >>> unprivileged_browser.getControl(name='form.buttons.comment').click()
+
+
+Check that the form has been properly submitted.
+
+    >>> unprivileged_browser.url
+    'http://nohost/plone/doc/document_view'
+
+    >>> 'Your comment awaits moderator approval.' in unprivileged_browser.contents
+    True
+
+Change to Moderation view.
+
+    >>> browser.open(urldoc)
+    >>> browser.getLink("Moderate comments").click()
+
+The new comment is shown in moderation view with authors name and email.
+
+    >>> browser.url
+    'http://nohost/plone/@@moderate-comments'
+    
+    >>> 'John' in browser.contents
+    True
+
+    >>> 'john-PV5Ro7/[email protected]' in browser.contents
+    True
diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py
index 95256d1d..c2d30d05 100644
--- a/plone/app/discussion/tests/test_conversation.py
+++ b/plone/app/discussion/tests/test_conversation.py
@@ -175,8 +175,8 @@ def test_delete_recursive(self):
         del conversation[new_id_1]
 
         self.assertEqual([
-            {'comment': comment2,     'depth': 0, 'id': new_id_2},
-            {'comment': comment2_1,   'depth': 1, 'id': new_id_2_1},
+            {'comment': comment2, 'depth': 0, 'id': new_id_2},
+            {'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
         ], list(conversation.getThreads()))
 
     def test_delete_comment_when_content_object_is_deleted(self):
@@ -608,12 +608,12 @@ def test_get_threads(self):
         # Get threads
 
         self.assertEqual([
-            {'comment': comment1,     'depth': 0, 'id': new_id_1},
-            {'comment': comment1_1,   'depth': 1, 'id': new_id_1_1},
+            {'comment': comment1, 'depth': 0, 'id': new_id_1},
+            {'comment': comment1_1, 'depth': 1, 'id': new_id_1_1},
             {'comment': comment1_1_1, 'depth': 2, 'id': new_id_1_1_1},
-            {'comment': comment1_2,   'depth': 1, 'id': new_id_1_2},
-            {'comment': comment2,     'depth': 0, 'id': new_id_2},
-            {'comment': comment2_1,   'depth': 1, 'id': new_id_2_1},
+            {'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
+            {'comment': comment2, 'depth': 0, 'id': new_id_2},
+            {'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
         ], list(conversation.getThreads()))
 
     def test_get_threads_batched(self):
diff --git a/plone/app/discussion/tests/test_events.py b/plone/app/discussion/tests/test_events.py
index 892ee556..7ca79766 100644
--- a/plone/app/discussion/tests/test_events.py
+++ b/plone/app/discussion/tests/test_events.py
@@ -4,8 +4,8 @@
 from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING  # noqa
 from plone.app.testing import setRoles
 from plone.app.testing import TEST_USER_ID
-from zope.component import createObject
 from Zope2.App import zcml
+from zope.component import createObject
 
 import Products.Five
 import unittest
diff --git a/plone/app/discussion/upgrades.py b/plone/app/discussion/upgrades.py
index 8d4083e2..d9114f4e 100644
--- a/plone/app/discussion/upgrades.py
+++ b/plone/app/discussion/upgrades.py
@@ -60,4 +60,4 @@ def upgrade_comment_workflows(context):
                 wf.updateRoleMappingsFor(comment)
             comment.reindexObjectSecurity()
         except (AttributeError, KeyError):
-            logger.info('Could not reindex comment %s' % brain.getURL())
+            logger.info('Could not reindex comment {0}'.format(brain.getURL()))
diff --git a/setup.py b/setup.py
index 85f100b5..7c8755b7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,9 @@
+# encoding: utf-8
+
 from setuptools import find_packages
 from setuptools import setup
 
+
 version = '3.0.3.dev0'
 
 install_requires = [
@@ -26,15 +29,15 @@
 
 setup(name='plone.app.discussion',
       version=version,
-      description="Enhanced discussion support for Plone",
-      long_description=open("README.rst").read() + "\n" +
-      open("CHANGES.rst").read(),
+      description='Enhanced discussion support for Plone',
+      long_description=open('README.rst').read() + '\n' +
+      open('CHANGES.rst').read(),
       classifiers=[
-          "Framework :: Plone",
-          "Framework :: Plone :: 5.0",
-          "Framework :: Plone :: 5.1",
-          "Programming Language :: Python",
-          "Programming Language :: Python :: 2.7",
+          'Framework :: Plone',
+          'Framework :: Plone :: 5.0',
+          'Framework :: Plone :: 5.1',
+          'Programming Language :: Python',
+          'Programming Language :: Python :: 2.7',
       ],
       keywords='plone discussion',
       author='Timo Stollenwerk - Plone Foundation',
@@ -54,11 +57,10 @@
               'plone.app.contentrules',
               'plone.app.contenttypes[test]',
               'plone.app.robotframework',
-          ]
+          ],
       },
       entry_points="""
       [z3c.autoinclude.plugin]
       target = plone
       """,
       )
-



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot