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