Test
This commit is contained in:
parent
fb7c68d5e5
commit
78abff152d
@ -17,7 +17,7 @@ import os
|
|||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# 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 ----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ pygments_style = 'sphinx'
|
|||||||
#modindex_common_prefix = []
|
#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
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
@ -169,7 +169,7 @@ html_static_path = ['_static']
|
|||||||
htmlhelp_basename = 'ploneappdiscussiondoc'
|
htmlhelp_basename = 'ploneappdiscussiondoc'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
# The paper size ('letter' or 'a4').
|
||||||
#latex_paper_size = 'letter'
|
#latex_paper_size = 'letter'
|
||||||
@ -180,8 +180,8 @@ htmlhelp_basename = 'ploneappdiscussiondoc'
|
|||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
|
('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
|
||||||
u'Timo Stollenwerk', 'manual'),
|
u'Timo Stollenwerk', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -12,29 +12,30 @@ from z3c.form import interfaces
|
|||||||
from z3c.form.field import Fields
|
from z3c.form.field import Fields
|
||||||
from zope import interface
|
from zope import interface
|
||||||
from zope.annotation import factory
|
from zope.annotation import factory
|
||||||
from zope.component import adapts
|
from zope.component import adapter
|
||||||
from zope.component import queryUtility
|
from zope.component import queryUtility
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
||||||
|
|
||||||
|
|
||||||
|
@adapter(Comment)
|
||||||
@interface.implementer(ICaptcha)
|
@interface.implementer(ICaptcha)
|
||||||
class Captcha(Persistent):
|
class Captcha(Persistent):
|
||||||
"""Captcha input field.
|
"""Captcha input field.
|
||||||
"""
|
"""
|
||||||
adapts(Comment)
|
captcha = u''
|
||||||
captcha = u""
|
|
||||||
|
|
||||||
Captcha = factory(Captcha)
|
Captcha = factory(Captcha)
|
||||||
|
|
||||||
|
|
||||||
|
# context, request, form
|
||||||
|
@adapter(Interface, IDefaultBrowserLayer, CommentForm)
|
||||||
class CaptchaExtender(extensible.FormExtender):
|
class CaptchaExtender(extensible.FormExtender):
|
||||||
"""Extends the comment form with a Captcha. This Captcha extender is only
|
"""Extends the comment form with a Captcha. This Captcha extender is only
|
||||||
registered when a plugin is installed that provides the
|
registered when a plugin is installed that provides the
|
||||||
"plone.app.discussion-captcha" feature.
|
"plone.app.discussion-captcha" feature.
|
||||||
"""
|
"""
|
||||||
# context, request, form
|
|
||||||
adapts(Interface, IDefaultBrowserLayer, CommentForm)
|
|
||||||
|
|
||||||
fields = Fields(ICaptcha)
|
fields = Fields(ICaptcha)
|
||||||
|
|
||||||
@ -65,5 +66,3 @@ class CaptchaExtender(extensible.FormExtender):
|
|||||||
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
||||||
else:
|
else:
|
||||||
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# coding: utf-8
|
||||||
from AccessControl import getSecurityManager
|
from AccessControl import getSecurityManager
|
||||||
from Acquisition import aq_inner
|
from Acquisition import aq_inner
|
||||||
from Acquisition import aq_parent
|
from Acquisition import aq_parent
|
||||||
@ -112,6 +112,5 @@ class EditCommentForm(CommentForm):
|
|||||||
type='info')
|
type='info')
|
||||||
return self._redirect(target=self.context.absolute_url())
|
return self._redirect(target=self.context.absolute_url())
|
||||||
|
|
||||||
EditComment = wrap_form(EditCommentForm)
|
|
||||||
|
|
||||||
# EOF
|
EditComment = wrap_form(EditCommentForm)
|
||||||
|
@ -106,11 +106,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span tal:content="python:item.author_name or item.Creator">Name</span>
|
<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:attributes="href string:mailto:$email"
|
||||||
tal:content="email">Email</a></tal:email>
|
tal:content="email">Email</a></tal:email>
|
||||||
</td>
|
</td>
|
||||||
<td tal:content="python:toLocalizedTime(item.ModificationDate,
|
<td tal:content="python:toLocalizedTime(item.ModificationDate,
|
||||||
long_format=1)" />
|
long_format=1)" />
|
||||||
<td>
|
<td>
|
||||||
<a tal:attributes="href item/getURL"
|
<a tal:attributes="href item/getURL"
|
||||||
|
@ -4,7 +4,7 @@ IDiscussion container for the context, from which traversal will continue
|
|||||||
into an actual comment object.
|
into an actual comment object.
|
||||||
"""
|
"""
|
||||||
from plone.app.discussion.interfaces import IConversation
|
from plone.app.discussion.interfaces import IConversation
|
||||||
from zope.component import adapts
|
from zope.component import adapter
|
||||||
from zope.component import queryAdapter
|
from zope.component import queryAdapter
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
@ -13,6 +13,7 @@ from zope.traversing.interfaces import ITraversable
|
|||||||
from zope.traversing.interfaces import TraversalError
|
from zope.traversing.interfaces import TraversalError
|
||||||
|
|
||||||
|
|
||||||
|
@adapter(Interface, IBrowserRequest)
|
||||||
@implementer(ITraversable)
|
@implementer(ITraversable)
|
||||||
class ConversationNamespace(object):
|
class ConversationNamespace(object):
|
||||||
"""Allow traversal into a conversation via a ++conversation++name
|
"""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
|
(unnamed) adapter. This is to work around a bug in OFS.Traversable which
|
||||||
does not allow traversal to namespaces with an empty string name.
|
does not allow traversal to namespaces with an empty string name.
|
||||||
"""
|
"""
|
||||||
adapts(Interface, IBrowserRequest)
|
|
||||||
|
|
||||||
def __init__(self, context, request=None):
|
def __init__(self, context, request=None):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
@ -8,7 +8,7 @@ from plone.app.discussion.interfaces import IDiscussionSettings
|
|||||||
from plone.registry.interfaces import IRegistry
|
from plone.registry.interfaces import IRegistry
|
||||||
from z3c.form import validator
|
from z3c.form import validator
|
||||||
from z3c.form.interfaces import IValidator
|
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 getMultiAdapter
|
||||||
from zope.component import queryUtility
|
from zope.component import queryUtility
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
@ -32,9 +32,9 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@adapter(Interface, IDiscussionLayer, Interface, IField, Interface)
|
||||||
@implementer(IValidator)
|
@implementer(IValidator)
|
||||||
class CaptchaValidator(validator.SimpleFieldValidator):
|
class CaptchaValidator(validator.SimpleFieldValidator):
|
||||||
adapts(Interface, IDiscussionLayer, Interface, IField, Interface)
|
|
||||||
# Object, Request, Form, Field, Widget,
|
# Object, Request, Form, Field, Widget,
|
||||||
# We adapt the CaptchaValidator class to all form fields (IField)
|
# We adapt the CaptchaValidator class to all form fields (IField)
|
||||||
|
|
||||||
|
@ -77,8 +77,10 @@ def creator(object):
|
|||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def description(object):
|
def description(object):
|
||||||
# Return the first 25 words of the comment text and append ' [...]'
|
# Return the first 25 words of the comment text and append ' [...]'
|
||||||
text = join(object.getText(
|
text = join(
|
||||||
targetMimetype='text/plain').split()[:MAX_DESCRIPTION])
|
object.getText(targetMimetype='text/plain')
|
||||||
|
.split()[:MAX_DESCRIPTION]
|
||||||
|
)
|
||||||
if len(object.getText().split()) > 25:
|
if len(object.getText().split()) > 25:
|
||||||
text += ' [...]'
|
text += ' [...]'
|
||||||
return text
|
return text
|
||||||
@ -99,37 +101,43 @@ def in_response_to(object):
|
|||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def effective(object):
|
def effective(object):
|
||||||
# the catalog index needs Zope DateTime instead of Python datetime
|
# the catalog index needs Zope DateTime instead of Python datetime
|
||||||
return DateTime(object.creation_date.year,
|
return DateTime(
|
||||||
object.creation_date.month,
|
object.creation_date.year,
|
||||||
object.creation_date.day,
|
object.creation_date.month,
|
||||||
object.creation_date.hour,
|
object.creation_date.day,
|
||||||
object.creation_date.minute,
|
object.creation_date.hour,
|
||||||
object.creation_date.second,
|
object.creation_date.minute,
|
||||||
'GMT')
|
object.creation_date.second,
|
||||||
|
'GMT',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def created(object):
|
def created(object):
|
||||||
# the catalog index needs Zope DateTime instead of Python datetime
|
# the catalog index needs Zope DateTime instead of Python datetime
|
||||||
return DateTime(object.creation_date.year,
|
return DateTime(
|
||||||
object.creation_date.month,
|
object.creation_date.year,
|
||||||
object.creation_date.day,
|
object.creation_date.month,
|
||||||
object.creation_date.hour,
|
object.creation_date.day,
|
||||||
object.creation_date.minute,
|
object.creation_date.hour,
|
||||||
object.creation_date.second,
|
object.creation_date.minute,
|
||||||
'GMT')
|
object.creation_date.second,
|
||||||
|
'GMT',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def modified(object):
|
def modified(object):
|
||||||
# the catalog index needs Zope DateTime instead of Python datetime
|
# the catalog index needs Zope DateTime instead of Python datetime
|
||||||
return DateTime(object.modification_date.year,
|
return DateTime(
|
||||||
object.modification_date.month,
|
object.modification_date.year,
|
||||||
object.modification_date.day,
|
object.modification_date.month,
|
||||||
object.modification_date.hour,
|
object.modification_date.day,
|
||||||
object.modification_date.minute,
|
object.modification_date.hour,
|
||||||
object.modification_date.second,
|
object.modification_date.minute,
|
||||||
'GMT')
|
object.modification_date.second,
|
||||||
|
'GMT',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Override the conversation indexers for comments
|
# Override the conversation indexers for comments
|
||||||
|
@ -42,7 +42,8 @@ import logging
|
|||||||
|
|
||||||
COMMENT_TITLE = _(
|
COMMENT_TITLE = _(
|
||||||
u'comment_title',
|
u'comment_title',
|
||||||
default=u'${author_name} on ${content}')
|
default=u'${author_name} on ${content}',
|
||||||
|
)
|
||||||
|
|
||||||
MAIL_NOTIFICATION_MESSAGE = _(
|
MAIL_NOTIFICATION_MESSAGE = _(
|
||||||
u'mail_notification_message',
|
u'mail_notification_message',
|
||||||
@ -50,7 +51,8 @@ MAIL_NOTIFICATION_MESSAGE = _(
|
|||||||
u'has been posted here: ${link}\n\n'
|
u'has been posted here: ${link}\n\n'
|
||||||
u'---\n'
|
u'---\n'
|
||||||
u'${text}\n'
|
u'${text}\n'
|
||||||
u'---\n')
|
u'---\n',
|
||||||
|
)
|
||||||
|
|
||||||
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
||||||
u'mail_notification_message_moderator',
|
u'mail_notification_message_moderator',
|
||||||
@ -60,7 +62,8 @@ MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
|||||||
u'${text}\n'
|
u'${text}\n'
|
||||||
u'---\n\n'
|
u'---\n\n'
|
||||||
u'Approve comment:\n${link_approve}\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')
|
logger = logging.getLogger('plone.app.discussion')
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ from Products.CMFPlone.interfaces import IHideFromBreadcrumbs
|
|||||||
from zope.annotation.interfaces import IAnnotatable
|
from zope.annotation.interfaces import IAnnotatable
|
||||||
from zope.annotation.interfaces import IAnnotations
|
from zope.annotation.interfaces import IAnnotations
|
||||||
from zope.component import adapter
|
from zope.component import adapter
|
||||||
from zope.component import adapts
|
|
||||||
from zope.container.contained import ContainerModifiedEvent
|
from zope.container.contained import ContainerModifiedEvent
|
||||||
from zope.event import notify
|
from zope.event import notify
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
@ -325,13 +324,13 @@ else:
|
|||||||
return conversationAdapterFactory(content)
|
return conversationAdapterFactory(content)
|
||||||
|
|
||||||
|
|
||||||
|
@adapter(Conversation) # relies on implementation details
|
||||||
@implementer(IReplies)
|
@implementer(IReplies)
|
||||||
class ConversationReplies(object):
|
class ConversationReplies(object):
|
||||||
"""An IReplies adapter for conversations.
|
"""An IReplies adapter for conversations.
|
||||||
|
|
||||||
This makes it easy to work with top-level comments.
|
This makes it easy to work with top-level comments.
|
||||||
"""
|
"""
|
||||||
adapts(Conversation) # relies on implementation details
|
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.conversation = context
|
self.conversation = context
|
||||||
@ -401,6 +400,7 @@ class ConversationReplies(object):
|
|||||||
return self.conversation._children.get(self.comment_id, LLSet())
|
return self.conversation._children.get(self.comment_id, LLSet())
|
||||||
|
|
||||||
|
|
||||||
|
@adapter(Comment)
|
||||||
@implementer(IReplies)
|
@implementer(IReplies)
|
||||||
class CommentReplies(ConversationReplies):
|
class CommentReplies(ConversationReplies):
|
||||||
"""An IReplies adapter for comments.
|
"""An IReplies adapter for comments.
|
||||||
@ -412,8 +412,6 @@ class CommentReplies(ConversationReplies):
|
|||||||
# most likely, anyone writing a different type of Conversation will also
|
# most likely, anyone writing a different type of Conversation will also
|
||||||
# have a different type of Comment
|
# have a different type of Comment
|
||||||
|
|
||||||
adapts(Comment)
|
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.comment = context
|
self.comment = context
|
||||||
self.conversation = aq_parent(self.comment)
|
self.conversation = aq_parent(self.comment)
|
||||||
|
@ -11,13 +11,15 @@ from zope.interface import Interface
|
|||||||
from zope.interface import Invalid
|
from zope.interface import Invalid
|
||||||
from zope.interface.common.mapping import IIterableMapping
|
from zope.interface.common.mapping import IIterableMapping
|
||||||
|
|
||||||
|
|
||||||
def isEmail(value):
|
def isEmail(value):
|
||||||
portal = getUtility(ISiteRoot)
|
portal = getUtility(ISiteRoot)
|
||||||
reg_tool = getToolByName(portal, 'portal_registration')
|
reg_tool = getToolByName(portal, 'portal_registration')
|
||||||
if not (value and reg_tool.isValidEmail(value)):
|
if not (value and reg_tool.isValidEmail(value)):
|
||||||
raise Invalid(_("Invalid email address."))
|
raise Invalid(_('Invalid email address.'))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class IConversation(IIterableMapping):
|
class IConversation(IIterableMapping):
|
||||||
"""A conversation about a content object.
|
"""A conversation about a content object.
|
||||||
|
|
||||||
@ -160,7 +162,10 @@ class IComment(Interface):
|
|||||||
|
|
||||||
# for anonymous comments only, set to None for logged in comments
|
# for anonymous comments only, set to None for logged in comments
|
||||||
author_name = schema.TextLine(title=_(u'Name'), required=False)
|
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',
|
title = schema.TextLine(title=_(u'label_subject',
|
||||||
default=u'Subject'))
|
default=u'Subject'))
|
||||||
|
@ -11,7 +11,6 @@ from plone.app.testing import TEST_USER_ID
|
|||||||
from plone.registry.interfaces import IRegistry
|
from plone.registry.interfaces import IRegistry
|
||||||
from Products.CMFCore.utils import getToolByName
|
from Products.CMFCore.utils import getToolByName
|
||||||
from zope.component import queryUtility
|
from zope.component import queryUtility
|
||||||
from zope.configuration import xmlconfig
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -40,9 +39,9 @@ class PloneAppDiscussion(PloneSandboxLayer):
|
|||||||
def setUpZope(self, app, configurationContext):
|
def setUpZope(self, app, configurationContext):
|
||||||
# Load ZCML
|
# Load ZCML
|
||||||
import plone.app.discussion
|
import plone.app.discussion
|
||||||
xmlconfig.file('configure.zcml',
|
self.loadZCML(package=plone.app.discussion,
|
||||||
plone.app.discussion,
|
context=configurationContext,
|
||||||
context=configurationContext)
|
)
|
||||||
|
|
||||||
def setUpPloneSite(self, portal):
|
def setUpPloneSite(self, portal):
|
||||||
# Install into Plone site using portal_setup
|
# Install into Plone site using portal_setup
|
||||||
|
@ -182,7 +182,7 @@ flaw? Though, the comment is published properly.
|
|||||||
>>> browser.handleErrors = False
|
>>> browser.handleErrors = False
|
||||||
>>> browser.raiseHttpErrors = True
|
>>> 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)
|
>>> unprivileged_browser.open(urldoc)
|
||||||
>>> 'First anonymous comment' in unprivileged_browser.contents
|
>>> '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)
|
>>> portal.portal_catalog.searchResults(id='doc', total_comments=0)
|
||||||
[<Products...]
|
[<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@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
|
||||||
|
@ -175,8 +175,8 @@ class ConversationTest(unittest.TestCase):
|
|||||||
del conversation[new_id_1]
|
del conversation[new_id_1]
|
||||||
|
|
||||||
self.assertEqual([
|
self.assertEqual([
|
||||||
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
||||||
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
||||||
], list(conversation.getThreads()))
|
], list(conversation.getThreads()))
|
||||||
|
|
||||||
def test_delete_comment_when_content_object_is_deleted(self):
|
def test_delete_comment_when_content_object_is_deleted(self):
|
||||||
@ -608,12 +608,12 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# Get threads
|
# Get threads
|
||||||
|
|
||||||
self.assertEqual([
|
self.assertEqual([
|
||||||
{'comment': comment1, 'depth': 0, 'id': new_id_1},
|
{'comment': comment1, 'depth': 0, 'id': new_id_1},
|
||||||
{'comment': comment1_1, 'depth': 1, 'id': new_id_1_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_1_1, 'depth': 2, 'id': new_id_1_1_1},
|
||||||
{'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
|
{'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
|
||||||
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
||||||
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
||||||
], list(conversation.getThreads()))
|
], list(conversation.getThreads()))
|
||||||
|
|
||||||
def test_get_threads_batched(self):
|
def test_get_threads_batched(self):
|
||||||
|
@ -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.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa
|
||||||
from plone.app.testing import setRoles
|
from plone.app.testing import setRoles
|
||||||
from plone.app.testing import TEST_USER_ID
|
from plone.app.testing import TEST_USER_ID
|
||||||
from zope.component import createObject
|
|
||||||
from Zope2.App import zcml
|
from Zope2.App import zcml
|
||||||
|
from zope.component import createObject
|
||||||
|
|
||||||
import Products.Five
|
import Products.Five
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -60,4 +60,4 @@ def upgrade_comment_workflows(context):
|
|||||||
wf.updateRoleMappingsFor(comment)
|
wf.updateRoleMappingsFor(comment)
|
||||||
comment.reindexObjectSecurity()
|
comment.reindexObjectSecurity()
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
logger.info('Could not reindex comment %s' % brain.getURL())
|
logger.info('Could not reindex comment {0}'.format(brain.getURL()))
|
||||||
|
22
setup.py
22
setup.py
@ -1,6 +1,9 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
version = '3.0.3.dev0'
|
version = '3.0.3.dev0'
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
@ -26,15 +29,15 @@ install_requires = [
|
|||||||
|
|
||||||
setup(name='plone.app.discussion',
|
setup(name='plone.app.discussion',
|
||||||
version=version,
|
version=version,
|
||||||
description="Enhanced discussion support for Plone",
|
description='Enhanced discussion support for Plone',
|
||||||
long_description=open("README.rst").read() + "\n" +
|
long_description=open('README.rst').read() + '\n' +
|
||||||
open("CHANGES.rst").read(),
|
open('CHANGES.rst').read(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Framework :: Plone",
|
'Framework :: Plone',
|
||||||
"Framework :: Plone :: 5.0",
|
'Framework :: Plone :: 5.0',
|
||||||
"Framework :: Plone :: 5.1",
|
'Framework :: Plone :: 5.1',
|
||||||
"Programming Language :: Python",
|
'Programming Language :: Python',
|
||||||
"Programming Language :: Python :: 2.7",
|
'Programming Language :: Python :: 2.7',
|
||||||
],
|
],
|
||||||
keywords='plone discussion',
|
keywords='plone discussion',
|
||||||
author='Timo Stollenwerk - Plone Foundation',
|
author='Timo Stollenwerk - Plone Foundation',
|
||||||
@ -54,11 +57,10 @@ setup(name='plone.app.discussion',
|
|||||||
'plone.app.contentrules',
|
'plone.app.contentrules',
|
||||||
'plone.app.contenttypes[test]',
|
'plone.app.contenttypes[test]',
|
||||||
'plone.app.robotframework',
|
'plone.app.robotframework',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[z3c.autoinclude.plugin]
|
[z3c.autoinclude.plugin]
|
||||||
target = plone
|
target = plone
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user