diff --git a/CHANGES.rst b/CHANGES.rst index 17628e7..b646add 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,8 @@ New features: Bug fixes: +- Show email in moderation view [ksuess] + - Remove plone.app.robotframework extras (reload and ride). They are not needed and they are not Python 3 compatible. [gforcada] diff --git a/docs/source/conf.py b/docs/source/conf.py index d2a393e..71374d4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,7 +17,7 @@ import os # 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 @@ pygments_style = 'sphinx' #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 @@ html_static_path = ['_static'] 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 @@ htmlhelp_basename = 'ploneappdiscussiondoc' # 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/docs/source/howtos/howto_extend_the_comment_form.txt b/docs/source/howtos/howto_extend_the_comment_form.txt index 6f3b604..605e0ef 100644 --- a/docs/source/howtos/howto_extend_the_comment_form.txt +++ b/docs/source/howtos/howto_extend_the_comment_form.txt @@ -47,7 +47,7 @@ comment form with the "website" field:: from zope import schema from zope.annotation import factory - from zope.component import adapts + from zope.component import adapter from zope.interface import Interface from zope.publisher.interfaces.browser import IDefaultBrowserLayer @@ -61,9 +61,9 @@ comment form with the "website" field:: website = schema.TextLine(title=u"Website", required=False) # Persistent class that implements the ICommentExtenderFields interface + @adapter(Comment) class CommentExtenderFields(Persistent): interface.implements(ICommentExtenderFields) - adapts(Comment) website = u"" # CommentExtenderFields factory @@ -71,9 +71,8 @@ comment form with the "website" field:: # Extending the comment form with the fields defined in the # ICommentExtenderFields interface. + @adapter(Interface, IDefaultBrowserLayer, CommentForm) class CommentExtender(extensible.FormExtender): - adapts(Interface, IDefaultBrowserLayer, CommentForm) - fields = Fields(ICommentExtenderFields) def __init__(self, context, request, form): diff --git a/plone/app/discussion/browser/captcha.py b/plone/app/discussion/browser/captcha.py index ffbda2f..090fce9 100644 --- a/plone/app/discussion/browser/captcha.py +++ b/plone/app/discussion/browser/captcha.py @@ -12,29 +12,30 @@ from z3c.form import interfaces 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 @@ class CaptchaExtender(extensible.FormExtender): 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 6b9b5d3..ab5f32d 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 @@ class EditCommentForm(CommentForm): 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 d89fff0..fbe0136 100644 --- a/plone/app/discussion/browser/moderation.pt +++ b/plone/app/discussion/browser/moderation.pt @@ -88,7 +88,8 @@ - - + + Name + +
+ Email + +
+ + diff --git a/plone/app/discussion/browser/traversal.py b/plone/app/discussion/browser/traversal.py index a9c95e0..1e2251c 100644 --- a/plone/app/discussion/browser/traversal.py +++ b/plone/app/discussion/browser/traversal.py @@ -4,7 +4,7 @@ IDiscussion container for the context, from which traversal will continue 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 @@ -14,6 +14,7 @@ from zope.traversing.interfaces import TraversalError @implementer(ITraversable) +@adapter(Interface, IBrowserRequest) class ConversationNamespace(object): """Allow traversal into a conversation via a ++conversation++name namespace. The name is the name of an adapter from context to @@ -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 3c56ac3..d1582e9 100644 --- a/plone/app/discussion/browser/validator.py +++ b/plone/app/discussion/browser/validator.py @@ -8,7 +8,7 @@ from plone.app.discussion.interfaces import IDiscussionSettings 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 @@ -33,8 +33,8 @@ except ImportError: @implementer(IValidator) +@adapter(Interface, IDiscussionLayer, Interface, IField, Interface) 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 ede2674..1ada61b 100644 --- a/plone/app/discussion/catalog.py +++ b/plone/app/discussion/catalog.py @@ -77,8 +77,7 @@ 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 +98,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 92c1c50..5a7f689 100644 --- a/plone/app/discussion/comment.py +++ b/plone/app/discussion/comment.py @@ -42,7 +42,8 @@ import logging 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 @@ MAIL_NOTIFICATION_MESSAGE = _( 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 @@ MAIL_NOTIFICATION_MESSAGE_MODERATOR = _( 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 525a64d..4963cce 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -30,7 +30,6 @@ from Products.CMFPlone.interfaces import IHideFromBreadcrumbs 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 @@ -326,12 +325,12 @@ else: @implementer(IReplies) +@adapter(Conversation) # relies on implementation details 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 @@ -402,6 +401,7 @@ class ConversationReplies(object): @implementer(IReplies) +@adapter(Comment) 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 5799602..549eaf9 100644 --- a/plone/app/discussion/interfaces.py +++ b/plone/app/discussion/interfaces.py @@ -11,13 +11,15 @@ from zope.interface import Interface 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 1612edd..4280a6e 100644 --- a/plone/app/discussion/testing.py +++ b/plone/app/discussion/testing.py @@ -11,7 +11,6 @@ from plone.app.testing import TEST_USER_ID 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 7cc743c..cd654d9 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) [>> 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@acme.com' + >>> 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@acme.com' in browser.contents + True diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py index 95256d1..c2d30d0 100644 --- a/plone/app/discussion/tests/test_conversation.py +++ b/plone/app/discussion/tests/test_conversation.py @@ -175,8 +175,8 @@ class ConversationTest(unittest.TestCase): 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 @@ class ConversationTest(unittest.TestCase): # 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 892ee55..7ca7976 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.interfaces import IReplies 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 8d4083e..d9114f4 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 85f100b..7c8755b 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 @@ install_requires = [ 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 @@ setup(name='plone.app.discussion', 'plone.app.contentrules', 'plone.app.contenttypes[test]', 'plone.app.robotframework', - ] + ], }, entry_points=""" [z3c.autoinclude.plugin] target = plone """, ) -