Nuke trailing white space
svn path=/plone.app.discussion/trunk/; revision=46366
This commit is contained in:
parent
aff8a3709c
commit
a2a17085a3
@ -39,7 +39,7 @@ Captcha = factory(Captcha)
|
||||
|
||||
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
|
||||
registered when a plugin is installed that provides the
|
||||
"plone.app.discussion-captcha" feature.
|
||||
"""
|
||||
adapts(Interface, IDefaultBrowserLayer, CommentForm) # context, request, form
|
||||
@ -72,4 +72,4 @@ class CaptchaExtender(extensible.FormExtender):
|
||||
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
||||
else:
|
||||
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:meta="http://namespaces.zope.org/meta"
|
||||
xmlns:meta="http://namespaces.zope.org/meta"
|
||||
xmlns:zcml="http://namespaces.zope.org/zcml"
|
||||
i18n_domain="plone.app.discussion">
|
||||
|
||||
<!--
|
||||
<!--
|
||||
Plone 3 fixes
|
||||
|
||||
Plone 3 / Zope 2.10 does not recognize the meta:provides feature.
|
||||
|
||||
Plone 3 / Zope 2.10 does not recognize the meta:provides feature.
|
||||
Therefore we claim to provide this feature when a suitable package is
|
||||
installed.
|
||||
installed.
|
||||
-->
|
||||
<configure zcml:condition="installed plone.formwidget.captcha">
|
||||
<meta:provides feature="plone.app.discussion-captcha" />
|
||||
@ -21,11 +21,11 @@
|
||||
|
||||
<!-- Captcha comment form extender -->
|
||||
<configure zcml:condition="have plone.app.discussion-captcha">
|
||||
<!--
|
||||
<!--
|
||||
Register the Captcha form extender and validator only if there are
|
||||
plugins installed that declare to implement a Captcha solution for
|
||||
plone.app.discussion (e.g. plone.formwidget.captcha and
|
||||
plone.formwidget.recaptcha).
|
||||
plone.app.discussion (e.g. plone.formwidget.captcha and
|
||||
plone.formwidget.recaptcha).
|
||||
-->
|
||||
<adapter
|
||||
factory=".captcha.Captcha"
|
||||
@ -44,7 +44,7 @@
|
||||
<adapter
|
||||
factory="collective.akismet.validator.AkismetValidator"
|
||||
provides="z3c.form.interfaces.IValidator"
|
||||
/>
|
||||
/>
|
||||
</configure>
|
||||
|
||||
</configure>
|
||||
|
||||
</configure>
|
||||
|
@ -5,15 +5,15 @@ from Products.Five.browser import BrowserView
|
||||
class View(BrowserView):
|
||||
"""Comment View.
|
||||
|
||||
When the view of a comment object is called directly, redirect to the
|
||||
When the view of a comment object is called directly, redirect to the
|
||||
the page (content object) and the location (HTML-anchor) where the comment
|
||||
has been posted.
|
||||
|
||||
|
||||
Redirect from the comment object URL
|
||||
"/path/to/object/++conversation++default/123456789" to the content object
|
||||
where the comment has been posted appended by an HTML anchor that points to
|
||||
where the comment has been posted appended by an HTML anchor that points to
|
||||
the comment "/path/to/object#comment-123456789".
|
||||
|
||||
|
||||
Context is the comment object. The parent of the comment object is the
|
||||
conversation. The parent of the conversation is the content object where
|
||||
the comment has been posted.
|
||||
@ -24,4 +24,4 @@ class View(BrowserView):
|
||||
self.request.response.redirect(
|
||||
aq_parent(aq_parent(context)).absolute_url() +
|
||||
'#' + str(context.id)
|
||||
)
|
||||
)
|
||||
|
@ -58,7 +58,7 @@
|
||||
tal:attributes="src portrait_url;
|
||||
alt reply/Creator" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="documentByLine" i18n:domain="plone.app.discussion">
|
||||
<tal:name>
|
||||
<a href=""
|
||||
@ -72,16 +72,16 @@
|
||||
<span tal:condition="not: reply/Creator">Anonymous</span>
|
||||
</tal:name>
|
||||
<tal:posted i18n:translate="label_says">says:</tal:posted>
|
||||
<div class="commentDate"
|
||||
<div class="commentDate"
|
||||
tal:content="python:view.format_time(reply.modification_date)">
|
||||
8/23/2001 12:40:44 PM
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="commentBody">
|
||||
|
||||
|
||||
<span tal:replace="structure python:view.cook(reply.getText())" />
|
||||
|
||||
|
||||
<div class="commentActions">
|
||||
<form name="delete"
|
||||
action=""
|
||||
@ -96,7 +96,7 @@
|
||||
i18n:attributes="value label_delete;"
|
||||
/>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- Workflow actions (e.g. 'publish') -->
|
||||
<form name=""
|
||||
action=""
|
||||
@ -115,7 +115,7 @@
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<button class="context reply-to-comment-button hide allowMultiSubmit"
|
||||
|
@ -41,10 +41,10 @@ from plone.z3cform import z2
|
||||
from plone.z3cform.widget import SingleCheckBoxWidget
|
||||
from plone.z3cform.fieldsets import extensible
|
||||
|
||||
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
|
||||
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
|
||||
try:
|
||||
from plone.z3cform.interfaces import IWrappedForm
|
||||
HAS_WRAPPED_FORM = True
|
||||
from plone.z3cform.interfaces import IWrappedForm
|
||||
HAS_WRAPPED_FORM = True
|
||||
except ImportError: # pragma: no cover
|
||||
HAS_WRAPPED_FORM = False
|
||||
|
||||
@ -87,48 +87,48 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# Widgets
|
||||
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
|
||||
self.widgets['text'].addClass("autoresize")
|
||||
self.widgets['user_notification'].label = _(u"")
|
||||
|
||||
self.widgets['user_notification'].label = _(u"")
|
||||
|
||||
# Anonymous / Logged-in
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
if not portal_membership.isAnonymousUser():
|
||||
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
|
||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
# Todo: Since we are not using the author_email field in the
|
||||
# current state, we hide it by default. But we keep the field for
|
||||
# integrators or later use.
|
||||
# Todo: Since we are not using the author_email field in the
|
||||
# current state, we hide it by default. But we keep the field for
|
||||
# integrators or later use.
|
||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
|
||||
|
||||
if not settings.user_notification_enabled or portal_membership.isAnonymousUser():
|
||||
self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
|
||||
def updateActions(self):
|
||||
super(CommentForm, self).updateActions()
|
||||
super(CommentForm, self).updateActions()
|
||||
self.actions['cancel'].addClass("standalone")
|
||||
self.actions['cancel'].addClass("hide")
|
||||
self.actions['comment'].addClass("context")
|
||||
|
||||
@button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
|
||||
self.actions['cancel'].addClass("hide")
|
||||
self.actions['comment'].addClass("context")
|
||||
|
||||
@button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
|
||||
name='comment')
|
||||
def handleComment(self, action):
|
||||
context = aq_inner(self.context)
|
||||
wf = getToolByName(context, 'portal_workflow')
|
||||
|
||||
|
||||
data, errors = self.extractData()
|
||||
if errors:
|
||||
return
|
||||
|
||||
|
||||
text = u""
|
||||
author_name = u""
|
||||
author_email = u""
|
||||
user_notification = None
|
||||
|
||||
# Captcha check for anonymous users (if Captcha is enabled and
|
||||
# Captcha check for anonymous users (if Captcha is enabled and
|
||||
# anonymous commenting is allowed)
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
@ -138,10 +138,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
portal_membership.isAnonymousUser():
|
||||
if not 'captcha' in data:
|
||||
data['captcha'] = u""
|
||||
captcha = CaptchaValidator(self.context,
|
||||
self.request,
|
||||
None,
|
||||
ICaptcha['captcha'],
|
||||
captcha = CaptchaValidator(self.context,
|
||||
self.request,
|
||||
None,
|
||||
ICaptcha['captcha'],
|
||||
None)
|
||||
captcha.validate(data['captcha'])
|
||||
|
||||
@ -156,10 +156,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
author_email = data['author_email']
|
||||
if 'user_notification' in data:
|
||||
user_notification = data['user_notification']
|
||||
|
||||
|
||||
# The add-comment view is called on the conversation object
|
||||
conversation = IConversation(self.__parent__)
|
||||
|
||||
|
||||
# Check if conversation is enabled on this content object
|
||||
if not conversation.enabled():
|
||||
raise Unauthorized, "Discussion is not enabled for this content\
|
||||
@ -175,10 +175,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
comment.text = text
|
||||
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
|
||||
|
||||
can_reply = getSecurityManager().checkPermission('Reply to item',
|
||||
context)
|
||||
|
||||
|
||||
if portal_membership.isAnonymousUser() and \
|
||||
settings.anonymous_comments:
|
||||
# Anonymous Users
|
||||
@ -211,7 +211,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
"""Anonymous user tries to post a comment, but
|
||||
anonymous commenting is disabled. Or user
|
||||
does not have the 'reply to item' permission.""" # pragma: no cover
|
||||
|
||||
|
||||
# Check if the added comment is a reply to an existing comment
|
||||
# or just a regular reply to the content object.
|
||||
if data['in_reply_to']:
|
||||
@ -221,11 +221,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# Add a comment to the conversation
|
||||
comment_id = conversation.addComment(comment)
|
||||
|
||||
# If a user posts a comment and moderation is enabled, a message is
|
||||
# shown to the user that his/her comment awaits moderation. If the user
|
||||
# has 'review comments' permission, he/she is redirected directly
|
||||
# If a user posts a comment and moderation is enabled, a message is
|
||||
# shown to the user that his/her comment awaits moderation. If the user
|
||||
# has 'review comments' permission, he/she is redirected directly
|
||||
# to the comment.
|
||||
can_review = getSecurityManager().checkPermission('Review comments',
|
||||
can_review = getSecurityManager().checkPermission('Review comments',
|
||||
context)
|
||||
comment_review_state = wf.getInfoFor(comment, 'review_state')
|
||||
if comment_review_state == 'pending' and not can_review:
|
||||
@ -254,8 +254,8 @@ class CommentsViewlet(ViewletBase):
|
||||
super(CommentsViewlet, self).update()
|
||||
z2.switch_on(self, request_layer=IFormLayer)
|
||||
self.form = self.form(aq_inner(self.context), self.request)
|
||||
if HAS_WRAPPED_FORM:
|
||||
alsoProvides(self.form, IWrappedForm)
|
||||
if HAS_WRAPPED_FORM:
|
||||
alsoProvides(self.form, IWrappedForm)
|
||||
self.form.update()
|
||||
|
||||
# view methods
|
||||
@ -265,16 +265,16 @@ class CommentsViewlet(ViewletBase):
|
||||
targetMimetype = 'text/html'
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
mimetype = settings.text_transform
|
||||
return transforms.convertTo(targetMimetype,
|
||||
text,
|
||||
context=self,
|
||||
mimetype = settings.text_transform
|
||||
return transforms.convertTo(targetMimetype,
|
||||
text,
|
||||
context=self,
|
||||
mimetype=mimetype).getData()
|
||||
|
||||
|
||||
def can_reply(self):
|
||||
"""Returns true if current user has the 'Reply to item' permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Reply to item',
|
||||
return getSecurityManager().checkPermission('Reply to item',
|
||||
aq_inner(self.context))
|
||||
|
||||
def can_manage(self):
|
||||
@ -282,13 +282,13 @@ class CommentsViewlet(ViewletBase):
|
||||
not want any API changes in beta releases.
|
||||
"""
|
||||
return self.can_review()
|
||||
|
||||
|
||||
def can_review(self):
|
||||
"""Returns true if current user has the 'Review comments' permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Review comments',
|
||||
return getSecurityManager().checkPermission('Review comments',
|
||||
aq_inner(self.context))
|
||||
|
||||
|
||||
def is_discussion_allowed(self):
|
||||
context = aq_inner(self.context)
|
||||
conversation = IConversation(context)
|
||||
@ -301,7 +301,7 @@ class CommentsViewlet(ViewletBase):
|
||||
"""
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
|
||||
if settings.text_transform == "text/x-web-intelligent":
|
||||
message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT))
|
||||
else:
|
||||
@ -356,7 +356,7 @@ class CommentsViewlet(ViewletBase):
|
||||
r = r.copy()
|
||||
r['workflow_status'] = workflow_status
|
||||
yield r
|
||||
|
||||
|
||||
# Return all direct replies
|
||||
if conversation.total_comments > 0:
|
||||
if workflow_actions:
|
||||
@ -395,11 +395,11 @@ class CommentsViewlet(ViewletBase):
|
||||
return settings.show_commenter_image
|
||||
|
||||
def is_anonymous(self):
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
None)
|
||||
return portal_membership.isAnonymousUser()
|
||||
|
||||
|
||||
def login_action(self):
|
||||
return '%s/login_form?came_from=%s' % \
|
||||
(self.navigation_root_url,
|
||||
|
@ -70,7 +70,7 @@
|
||||
class=".moderation.ModerateCommentsEnabled"
|
||||
permission="zope2.View"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Delete comment view -->
|
||||
<browser:page
|
||||
for="plone.app.discussion.interfaces.IComment"
|
||||
@ -131,4 +131,4 @@
|
||||
permission="cmf.ManagePortal"
|
||||
/>
|
||||
|
||||
</configure>
|
||||
</configure>
|
||||
|
@ -5,7 +5,7 @@
|
||||
lang="en"
|
||||
metal:use-macro="here/prefs_main_template/macros/master"
|
||||
i18n:domain="plone">
|
||||
|
||||
|
||||
<metal:block fill-slot="top_slot"
|
||||
tal:define="dummy python:request.set('disable_border',1)" />
|
||||
|
||||
@ -17,14 +17,14 @@
|
||||
|
||||
|
||||
<body>
|
||||
<div id="content"
|
||||
<div id="content"
|
||||
tal:attributes="class view/settings"
|
||||
metal:fill-slot="prefs_configlet_content">
|
||||
|
||||
<script type="text/javascript"
|
||||
tal:attributes="src string:${context/portal_url}/++resource++plone.app.discussion.javascripts/controlpanel.js">
|
||||
</script>
|
||||
|
||||
|
||||
<dl class="portalMessage warning"
|
||||
tal:condition="view/mailhost_warning">
|
||||
<dt i18n:translate="">
|
||||
@ -63,24 +63,24 @@
|
||||
to choose a workflow for the 'Discussion Item' type.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<div metal:use-macro="context/global_statusmessage/macros/portal_message">
|
||||
Portal status message
|
||||
</div>
|
||||
|
||||
|
||||
<a href=""
|
||||
id="setup-link"
|
||||
tal:attributes="href string:$portal_url/plone_control_panel"
|
||||
i18n:translate="">
|
||||
Site Setup
|
||||
</a> ›
|
||||
|
||||
|
||||
<h1 class="documentFirstHeading" tal:content="view/label">View Title</h1>
|
||||
|
||||
|
||||
<div id="layout-contents">
|
||||
<span tal:replace="structure view/contents" />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -21,7 +21,7 @@ from zope.component import getMultiAdapter, queryUtility
|
||||
|
||||
from z3c.form import button
|
||||
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
|
||||
|
||||
|
||||
from plone.app.discussion.interfaces import IDiscussionSettings, _
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
description = _(u"help_discussion_settings_editform",
|
||||
default=u"Some discussion related settings are not located "
|
||||
"in the Discussion Control Panel.\n"
|
||||
"To enable comments for a specific content type, "
|
||||
"To enable comments for a specific content type, "
|
||||
"go to the Types Control Panel of this type and "
|
||||
"choose \"Allow comments\".\n"
|
||||
"To enable the moderation workflow for comments, "
|
||||
@ -73,18 +73,18 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
self.status = self.formErrorsMessage
|
||||
return
|
||||
changes = self.applyChanges(data)
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"),
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"),
|
||||
"info")
|
||||
self.context.REQUEST.RESPONSE.redirect("@@discussion-settings")
|
||||
|
||||
@button.buttonAndHandler(_('Cancel'), name='cancel')
|
||||
def handleCancel(self, action):
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"),
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"),
|
||||
"info")
|
||||
self.request.response.redirect("%s/%s" % (self.context.absolute_url(),
|
||||
self.request.response.redirect("%s/%s" % (self.context.absolute_url(),
|
||||
self.control_panel_view))
|
||||
|
||||
|
||||
|
||||
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
"""Discussion settings control panel.
|
||||
"""
|
||||
@ -100,22 +100,22 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||
wf = wftool.getChainForPortalType('Discussion Item')
|
||||
output = []
|
||||
|
||||
|
||||
# Globally enabled
|
||||
if settings.globally_enabled:
|
||||
output.append("globally_enabled")
|
||||
|
||||
|
||||
# Comment moderation
|
||||
if 'one_state_workflow' not in wf and \
|
||||
'comment_review_workflow' not in wf:
|
||||
output.append("moderation_custom")
|
||||
output.append("moderation_custom")
|
||||
elif settings.moderation_enabled:
|
||||
output.append("moderation_enabled")
|
||||
|
||||
|
||||
# Anonymous comments
|
||||
if settings.anonymous_comments:
|
||||
output.append("anonymous_comments")
|
||||
|
||||
|
||||
# Invalid mail setting
|
||||
ctrlOverview = getMultiAdapter((self.context, self.request),
|
||||
name='overview-controlpanel')
|
||||
@ -127,7 +127,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
discussion_workflow = wftool.getChainForPortalType('Discussion Item')[0]
|
||||
if discussion_workflow:
|
||||
output.append(discussion_workflow)
|
||||
|
||||
|
||||
# Merge all settings into one string
|
||||
return ' '.join(output)
|
||||
|
||||
@ -152,27 +152,27 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
if 'one_state_workflow' in wf or 'comment_review_workflow' in wf:
|
||||
return
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
def notify_configuration_changed(event):
|
||||
"""Event subscriber that is called every time the configuration changed.
|
||||
"""
|
||||
portal = getSite()
|
||||
wftool = getToolByName(portal, 'portal_workflow', None)
|
||||
|
||||
|
||||
if IRecordModifiedEvent.providedBy(event):
|
||||
# Discussion control panel setting changed
|
||||
if event.record.fieldName == 'moderation_enabled':
|
||||
# Moderation enabled has changed
|
||||
if event.record.value == True:
|
||||
# Enable moderation workflow
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'comment_review_workflow')
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'comment_review_workflow')
|
||||
else:
|
||||
# Disable moderation workflow
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'one_state_workflow')
|
||||
|
||||
|
||||
if IConfigurationChangedEvent.providedBy(event):
|
||||
# Types control panel setting changed
|
||||
if 'workflow' in event.data:
|
||||
|
@ -25,22 +25,22 @@ class View(BrowserView):
|
||||
out = []
|
||||
self.total_comments_migrated = 0
|
||||
self.total_comments_deleted = 0
|
||||
|
||||
|
||||
dry_run = self.request.has_key("dry_run")
|
||||
|
||||
|
||||
# This is for testing only.
|
||||
# Do not use transactions during a test.
|
||||
test = self.request.has_key("test")
|
||||
|
||||
test = self.request.has_key("test")
|
||||
|
||||
if not test:
|
||||
transaction.begin() # pragma: no cover
|
||||
|
||||
|
||||
catalog = getToolByName(context, 'portal_catalog')
|
||||
|
||||
|
||||
def log(msg):
|
||||
# encode string before sending it to external world
|
||||
if isinstance(msg, unicode):
|
||||
msg = msg.encode('utf-8') # pragma: no cover
|
||||
# encode string before sending it to external world
|
||||
if isinstance(msg, unicode):
|
||||
msg = msg.encode('utf-8') # pragma: no cover
|
||||
context.plone_log(msg)
|
||||
out.append(msg)
|
||||
|
||||
@ -85,9 +85,9 @@ class View(BrowserView):
|
||||
|
||||
# migrate all talkbacks of the reply
|
||||
talkback = getattr( reply, 'talkback', None )
|
||||
no_replies_left = migrate_replies(context,
|
||||
new_in_reply_to,
|
||||
talkback.getReplies(),
|
||||
no_replies_left = migrate_replies(context,
|
||||
new_in_reply_to,
|
||||
talkback.getReplies(),
|
||||
depth=depth+1)
|
||||
if no_replies_left:
|
||||
# remove reply and talkback
|
||||
@ -97,7 +97,7 @@ class View(BrowserView):
|
||||
log("%sremove %s" % (indent, reply.id))
|
||||
self.total_comments_deleted += 1
|
||||
|
||||
# Return True when all comments on a certain level have been
|
||||
# Return True when all comments on a certain level have been
|
||||
# migrated.
|
||||
return True
|
||||
|
||||
@ -113,14 +113,14 @@ class View(BrowserView):
|
||||
count_comments_old = len(catalog.searchResults(
|
||||
object_provides=IDiscussionResponse.\
|
||||
__identifier__))
|
||||
|
||||
|
||||
log("Found %s Discussion Item objects." % count_discussion_items)
|
||||
log("Found %s old discussion items." % count_comments_old)
|
||||
log("Found %s plone.app.discussion comments." % count_comments_pad)
|
||||
|
||||
log("\n")
|
||||
log("Start comment migration.")
|
||||
|
||||
|
||||
# This loop is necessary to get all contentish objects, but not
|
||||
# the Discussion Items. This wouldn't be necessary if the
|
||||
# zcatalog would support NOT expressions.
|
||||
@ -138,7 +138,7 @@ class View(BrowserView):
|
||||
if replies:
|
||||
conversation = IConversation(obj)
|
||||
log("\n")
|
||||
log("Migrate '%s' (%s)" % (obj.Title(),
|
||||
log("Migrate '%s' (%s)" % (obj.Title(),
|
||||
obj.absolute_url(relative=1)))
|
||||
migrate_replies(context, 0, replies)
|
||||
obj = aq_parent(talkback)
|
||||
@ -152,23 +152,23 @@ class View(BrowserView):
|
||||
if not test: # pragma: no cover
|
||||
transaction.abort() # pragma: no cover
|
||||
log("Abort transaction") # pragma: no cover
|
||||
|
||||
|
||||
log("\n")
|
||||
log("Comment migration finished.")
|
||||
log("\n")
|
||||
|
||||
|
||||
log("%s of %s comments migrated."
|
||||
% (self.total_comments_migrated, count_comments_old))
|
||||
|
||||
|
||||
if self.total_comments_migrated != count_comments_old:
|
||||
log("%s comments could not be migrated."
|
||||
log("%s comments could not be migrated."
|
||||
% (count_comments_old - self.total_comments_migrated)) # pragma: no cover
|
||||
log("Please make sure your portal catalog is up-to-date.") # pragma: no cover
|
||||
|
||||
|
||||
if dry_run and not test:
|
||||
transaction.abort() # pragma: no cover
|
||||
log("Dry run") # pragma: no cover
|
||||
log("Abort transaction") # pragma: no cover
|
||||
if not test:
|
||||
transaction.commit() # pragma: no cover
|
||||
transaction.commit() # pragma: no cover
|
||||
return '\n'.join(out)
|
||||
|
@ -33,11 +33,11 @@
|
||||
<a i18n:name="enable_comment_workflow"
|
||||
i18n:translate="message_enable_comment_workflow" href=""
|
||||
tal:attributes="href string:${context/portal_url}/@@types-controlpanel?type_id=Discussion Item">
|
||||
enable the 'Comment Review Workflow' for the Comment content
|
||||
enable the 'Comment Review Workflow' for the Comment content
|
||||
type</a> before you can moderate comments here.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<form tal:condition="not:items">
|
||||
<fieldset id="fieldset-moderate-comments" class="formPanel">
|
||||
<p id="no-comments-message" i18n:translate="message_nothing_to_moderate">
|
||||
@ -114,7 +114,7 @@
|
||||
<a href=""
|
||||
tal:attributes="href string:${item/getURL}/getText"
|
||||
tal:condition="python:item.Description.endswith('[...]')"
|
||||
i18n:translate="label_show_full_comment_text"
|
||||
i18n:translate="label_show_full_comment_text"
|
||||
class="show-full-comment-text">show full comment text</a>
|
||||
</td>
|
||||
<td class="actions">
|
||||
|
@ -11,14 +11,14 @@ from Products.statusmessages.interfaces import IStatusMessage
|
||||
from plone.app.discussion.interfaces import _
|
||||
from plone.app.discussion.interfaces import IComment
|
||||
|
||||
# Begin ugly hack. It works around a ContentProviderLookupError:
|
||||
# Begin ugly hack. It works around a ContentProviderLookupError:
|
||||
# plone.htmlhead error caused by Zope 2 permissions.
|
||||
# This error occured on Plone 3.3.x only!
|
||||
#
|
||||
# Source:
|
||||
# Source:
|
||||
# http://athenageek.wordpress.com/2008/01/08/
|
||||
# contentproviderlookuperror-plonehtmlhead/
|
||||
#
|
||||
#
|
||||
# Bug report: https://bugs.launchpad.net/zope2/+bug/176566
|
||||
#
|
||||
|
||||
@ -27,7 +27,7 @@ def _getContext(self): # pragma: no cover
|
||||
while getattr(self, '_is_wrapperish', None):
|
||||
self = self.aq_parent
|
||||
return self
|
||||
|
||||
|
||||
ZopeTwoPageTemplateFile._getContext = _getContext # pragma: no cover
|
||||
# End ugly hack.
|
||||
|
||||
@ -61,7 +61,7 @@ class View(BrowserView):
|
||||
return text
|
||||
|
||||
def moderation_enabled(self):
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
"""
|
||||
@ -78,7 +78,7 @@ class View(BrowserView):
|
||||
class ModerateCommentsEnabled(BrowserView):
|
||||
|
||||
def __call__(self):
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
"""
|
||||
@ -91,26 +91,26 @@ class ModerateCommentsEnabled(BrowserView):
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
|
||||
class DeleteComment(BrowserView):
|
||||
"""Delete a comment from a conversation.
|
||||
|
||||
|
||||
This view is always called directly on the comment object:
|
||||
|
||||
|
||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||
@@moderate-delete-comment
|
||||
|
||||
Each table row (comment) in the moderation view contains a hidden input
|
||||
field with the absolute URL of the content object:
|
||||
|
||||
<input type="hidden"
|
||||
|
||||
<input type="hidden"
|
||||
value="http://nohost/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
name="selected_obj_paths:list">
|
||||
|
||||
|
||||
This absolute URL is called from a jQuery method that is bind to the
|
||||
'delete' button of the table row. See javascripts/moderation.js for more
|
||||
details.
|
||||
details.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
@ -132,23 +132,23 @@ class DeleteComment(BrowserView):
|
||||
|
||||
class PublishComment(BrowserView):
|
||||
"""Publish a comment.
|
||||
|
||||
|
||||
This view is always called directly on the comment object:
|
||||
|
||||
|
||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||
@@moderate-publish-comment
|
||||
|
||||
Each table row (comment) in the moderation view contains a hidden input
|
||||
field with the absolute URL of the content object:
|
||||
|
||||
<input type="hidden"
|
||||
|
||||
<input type="hidden"
|
||||
value="http://nohost/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
name="selected_obj_paths:list">
|
||||
|
||||
|
||||
This absolute URL is called from a jQuery method that is bind to the
|
||||
'delete' button of the table row. See javascripts/moderation.js for more
|
||||
details.
|
||||
details.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
@ -171,24 +171,24 @@ class PublishComment(BrowserView):
|
||||
|
||||
class BulkActionsView(BrowserView):
|
||||
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
||||
|
||||
Each table row of the moderation view has a checkbox with the absolute
|
||||
|
||||
Each table row of the moderation view has a checkbox with the absolute
|
||||
path (without host and port) of the comment objects:
|
||||
|
||||
|
||||
<input type="checkbox"
|
||||
name="paths:list"
|
||||
value="/plone/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
id="cb_1286289644723317" />
|
||||
|
||||
If checked, the comment path will occur in the 'paths' variable of
|
||||
the request when the bulk actions view is called. The bulk action
|
||||
(delete, publish, etc.) will be applied to all comments that are
|
||||
|
||||
If checked, the comment path will occur in the 'paths' variable of
|
||||
the request when the bulk actions view is called. The bulk action
|
||||
(delete, publish, etc.) will be applied to all comments that are
|
||||
included.
|
||||
|
||||
|
||||
The paths have to be 'traversable':
|
||||
|
||||
/plone/front-page/++conversation++default/1286289644723317
|
||||
|
||||
/plone/front-page/++conversation++default/1286289644723317
|
||||
|
||||
"""
|
||||
|
||||
@ -219,11 +219,11 @@ class BulkActionsView(BrowserView):
|
||||
|
||||
def publish(self):
|
||||
"""Publishes all comments in the paths variable.
|
||||
|
||||
|
||||
Expects a list of absolute paths (without host and port):
|
||||
|
||||
|
||||
/Plone/startseite/++conversation++default/1286200010610352
|
||||
|
||||
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
for path in self.paths:
|
||||
@ -240,12 +240,12 @@ class BulkActionsView(BrowserView):
|
||||
|
||||
def delete(self):
|
||||
"""Deletes all comments in the paths variable.
|
||||
|
||||
|
||||
Expects a list of absolute paths (without host and port):
|
||||
|
||||
|
||||
/Plone/startseite/++conversation++default/1286200010610352
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
for path in self.paths:
|
||||
comment = context.restrictedTraverse(path)
|
||||
|
@ -18,21 +18,21 @@ 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.
|
||||
"""
|
||||
|
||||
|
||||
implements(ITraversable)
|
||||
adapts(Interface, IBrowserRequest)
|
||||
|
||||
|
||||
def __init__(self, context, request=None):
|
||||
self.context = context
|
||||
self.request = request
|
||||
|
||||
|
||||
def traverse(self, name, ignore):
|
||||
|
||||
|
||||
if name == "default":
|
||||
name = u""
|
||||
|
||||
|
||||
conversation = queryAdapter(self.context, IConversation, name=name)
|
||||
if conversation is None:
|
||||
raise TraversalError(name) # pragma: no cover
|
||||
|
||||
|
||||
return conversation
|
||||
|
@ -49,7 +49,7 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
if settings.captcha in ('captcha', 'recaptcha', 'norobots'):
|
||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
||||
name=settings.captcha)
|
||||
if not captcha.verify(input=value):
|
||||
if settings.captcha == 'norobots':
|
||||
@ -61,6 +61,6 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
|
||||
|
||||
# Register Captcha validator for the Captcha field in the ICaptcha Form
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
||||
field=ICaptcha['captcha'])
|
||||
|
||||
|
@ -54,7 +54,7 @@ except: # pragma: no cover
|
||||
|
||||
COMMENT_TITLE = _(u"comment_title",
|
||||
default=u"${creator} on ${content}")
|
||||
|
||||
|
||||
MAIL_NOTIFICATION_MESSAGE = _(u"mail_notification_message",
|
||||
default=u"A comment on '${title}' "
|
||||
"has been posted here: ${link}")
|
||||
@ -95,7 +95,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
||||
|
||||
author_name = None
|
||||
author_email = None
|
||||
|
||||
|
||||
user_notification = None
|
||||
|
||||
# Note: we want to use zope.component.createObject() to instantiate
|
||||
@ -127,13 +127,13 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
||||
"""The title of the comment.
|
||||
"""
|
||||
if not self.creator:
|
||||
creator = translate(Message(_(u"label_anonymous",
|
||||
creator = translate(Message(_(u"label_anonymous",
|
||||
default=u"Anonymous")))
|
||||
else:
|
||||
creator = self.creator
|
||||
creator = creator
|
||||
|
||||
# Fetch the content object (the parent of the comment is the
|
||||
# Fetch the content object (the parent of the comment is the
|
||||
# conversation, the parent of the conversation is the content object).
|
||||
content = aq_base(self.__parent__.__parent__)
|
||||
title = translate(
|
||||
@ -141,7 +141,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
||||
mapping={'creator': creator,
|
||||
'content': safe_unicode(content.Title())}))
|
||||
return title
|
||||
|
||||
|
||||
def Creator(self):
|
||||
"""The name of the person who wrote the comment.
|
||||
"""
|
||||
@ -176,8 +176,8 @@ def notify_content_object(obj, event):
|
||||
"""Tell the content object when a comment is added
|
||||
"""
|
||||
content_obj = aq_parent(aq_parent(obj))
|
||||
content_obj.reindexObject(idxs=('total_comments',
|
||||
'last_comment_date',
|
||||
content_obj.reindexObject(idxs=('total_comments',
|
||||
'last_comment_date',
|
||||
'commentators',))
|
||||
|
||||
def notify_content_object_deleted(obj, event):
|
||||
@ -189,14 +189,14 @@ def notify_content_object_deleted(obj, event):
|
||||
for comment in conversation.getComments():
|
||||
del conversation[comment.id]
|
||||
|
||||
def notify_user(obj, event):
|
||||
def notify_user(obj, event):
|
||||
"""Tell users when a comment has been added.
|
||||
|
||||
This method composes and sends emails to all users that have added a
|
||||
|
||||
This method composes and sends emails to all users that have added a
|
||||
comment to this conversation and enabled user notification.
|
||||
|
||||
This requires the user_notification setting to be enabled in the
|
||||
discussion control panel.
|
||||
|
||||
This requires the user_notification setting to be enabled in the
|
||||
discussion control panel.
|
||||
"""
|
||||
|
||||
# Check if user notification is enabled
|
||||
@ -268,36 +268,36 @@ def notify_user(obj, event):
|
||||
|
||||
def notify_moderator(obj, event):
|
||||
"""Tell the moderator when a comment needs attention.
|
||||
|
||||
|
||||
This method sends an email to the site admin (mail control panel setting)
|
||||
if comment moderation is enabled and a new comment has been added that
|
||||
needs to be approved.
|
||||
|
||||
This requires the moderator_notification to be enabled in the discussion
|
||||
control panel and the comment_review_workflow enabled for the comment
|
||||
needs to be approved.
|
||||
|
||||
This requires the moderator_notification to be enabled in the discussion
|
||||
control panel and the comment_review_workflow enabled for the comment
|
||||
content type.
|
||||
"""
|
||||
|
||||
|
||||
# Check if moderator notification is enabled
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
if not settings.moderator_notification_enabled:
|
||||
return
|
||||
|
||||
|
||||
# Check if the current workflow implements a pending state.
|
||||
wf_tool = getToolByName(obj, 'portal_workflow')
|
||||
comment_workflow = wf_tool.getChainForPortalType('Discussion Item')[0]
|
||||
comment_workflow = wf_tool[comment_workflow]
|
||||
if 'pending' not in comment_workflow.states:
|
||||
return
|
||||
|
||||
|
||||
# Get informations that are necessary to send an email
|
||||
mail_host = getToolByName(obj, 'MailHost')
|
||||
portal_url = getToolByName(obj, 'portal_url')
|
||||
portal = portal_url.getPortalObject()
|
||||
sender = portal.getProperty('email_from_address')
|
||||
mto = portal.getProperty('email_from_address')
|
||||
|
||||
|
||||
# Check if a sender address is available
|
||||
if not sender:
|
||||
return
|
||||
@ -305,7 +305,7 @@ def notify_moderator(obj, event):
|
||||
conversation = aq_parent(obj)
|
||||
content_object = aq_parent(conversation)
|
||||
|
||||
# Compose email
|
||||
# Compose email
|
||||
#comment = conversation.getComments().next()
|
||||
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
|
||||
message = translate(Message(MAIL_NOTIFICATION_MESSAGE,
|
||||
@ -327,10 +327,10 @@ def notify_moderator(obj, event):
|
||||
message)
|
||||
else: # pragma: no cover
|
||||
try:
|
||||
mail_host.secureSend(message,
|
||||
mto,
|
||||
sender,
|
||||
subject=subject,
|
||||
mail_host.secureSend(message,
|
||||
mto,
|
||||
sender,
|
||||
subject=subject,
|
||||
charset='utf-8')
|
||||
except SMTPException, e:
|
||||
logger.error('SMTP exception (%s) while trying to send an ' +
|
||||
|
@ -10,11 +10,11 @@
|
||||
<five:registerPackage package="." />
|
||||
|
||||
<include package="plone.indexer" />
|
||||
|
||||
<!-- XXX: Excluding plone.app.uuid for Plone 3 is only a temporary 'fix'.
|
||||
plone.app.uuid causes the IObjectAddedEvent to be fired twice on
|
||||
|
||||
<!-- XXX: Excluding plone.app.uuid for Plone 3 is only a temporary 'fix'.
|
||||
plone.app.uuid causes the IObjectAddedEvent to be fired twice on
|
||||
Plone 3. This causes the email notifications to be send twice.
|
||||
|
||||
|
||||
This means that https://dev.plone.org/plone/ticket/10652 is fixed only
|
||||
for Plone 4.
|
||||
-->
|
||||
@ -29,7 +29,7 @@
|
||||
<include package=".browser" />
|
||||
|
||||
<i18n:registerTranslations directory="locales" />
|
||||
|
||||
|
||||
<!-- Register the installation GenericSetup extension profile -->
|
||||
<genericsetup:registerProfile
|
||||
name="default"
|
||||
@ -59,7 +59,7 @@
|
||||
<require attributes="Title Creator getId getText" permission="zope2.View" />
|
||||
</class>
|
||||
|
||||
|
||||
|
||||
<!-- XXX: Excluding plone.uuid for Plone 3 is only a temporary 'fix'.
|
||||
See the comment above -->
|
||||
<configure zcml:condition="have plone-4">
|
||||
|
@ -484,6 +484,6 @@ class CommentReplies(ConversationReplies):
|
||||
comment.in_reply_to = self.comment_id
|
||||
return self.conversation.addComment(comment)
|
||||
|
||||
# Dict API is inherited, written in terms of self.conversation and
|
||||
# Dict API is inherited, written in terms of self.conversation and
|
||||
# self.children
|
||||
|
||||
|
@ -31,7 +31,7 @@ class IDiscussionSettings(Interface):
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
anonymous_comments = schema.Bool(
|
||||
title=_(u"label_anonymous_comments",
|
||||
default="Enable anonymous comments"),
|
||||
@ -43,7 +43,7 @@ class IDiscussionSettings(Interface):
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
moderation_enabled = schema.Bool(
|
||||
title=_(u"label_moderation_enabled",
|
||||
default="Enable comment moderation"),
|
||||
@ -70,7 +70,7 @@ class IDiscussionSettings(Interface):
|
||||
"'Intelligent text' converts plain text into HTML " +
|
||||
"where line breaks and indentation is preserved, " +
|
||||
"and web and email addresses are made into " +
|
||||
"clickable links."),
|
||||
"clickable links."),
|
||||
required=True,
|
||||
default='text/plain',
|
||||
vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary',
|
||||
@ -100,7 +100,7 @@ class IDiscussionSettings(Interface):
|
||||
required=False,
|
||||
default=True,
|
||||
)
|
||||
|
||||
|
||||
moderator_notification_enabled = schema.Bool(
|
||||
title=_(u"label_moderator_notification_enabled",
|
||||
default=u"Enable moderator email notification"),
|
||||
|
@ -12,18 +12,18 @@
|
||||
zope.lifecycleevent.interfaces.IObjectAddedEvent"
|
||||
handler=".comment.notify_user"
|
||||
/>
|
||||
|
||||
|
||||
<subscriber
|
||||
for="plone.app.discussion.interfaces.IComment
|
||||
zope.lifecycleevent.interfaces.IObjectAddedEvent"
|
||||
handler=".comment.notify_moderator"
|
||||
/>
|
||||
|
||||
|
||||
</configure>
|
||||
|
||||
|
||||
|
||||
<!-- Plone 4 Event Subscribers -->
|
||||
|
||||
|
||||
<configure zcml:condition="installed zope.app.container">
|
||||
|
||||
<subscriber
|
||||
|
@ -15,7 +15,7 @@ def patchedClearFindAndRebuild(self):
|
||||
with an indexObject method), and reindexes them.
|
||||
This may take a long time.
|
||||
"""
|
||||
|
||||
|
||||
def indexObject(obj, path):
|
||||
|
||||
if (base_hasattr(obj, 'indexObject') and
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<componentregistry>
|
||||
<utilities>
|
||||
<utility
|
||||
<utility
|
||||
interface="plone.app.discussion.interfaces.ICommentingTool"
|
||||
object="portal_discussion"
|
||||
/>
|
||||
|
@ -79,7 +79,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="actor" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
@ -91,7 +91,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="comments" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
@ -103,7 +103,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="review_history" for_catalog="False"
|
||||
for_status="False" update_always="False">
|
||||
<description i18n:translate="">
|
||||
@ -117,7 +117,7 @@
|
||||
<guard-permission>Review portal content</guard-permission>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="time" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
|
@ -52,7 +52,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="actor" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
@ -64,7 +64,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="comments" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
@ -76,7 +76,7 @@
|
||||
<guard>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="review_history" for_catalog="False"
|
||||
for_status="False" update_always="False">
|
||||
<description i18n:translate="">
|
||||
@ -90,7 +90,7 @@
|
||||
<guard-permission>Review portal content</guard-permission>
|
||||
</guard>
|
||||
</variable>
|
||||
|
||||
|
||||
<variable variable_id="time" for_catalog="False"
|
||||
for_status="True" update_always="True">
|
||||
<description i18n:translate="">
|
||||
|
@ -42,12 +42,12 @@
|
||||
zope.lifecycleevent.interfaces.IObjectRemovedEvent"
|
||||
handler=".comment.notify_content_object_deleted"
|
||||
/>
|
||||
|
||||
|
||||
</configure>
|
||||
|
||||
|
||||
|
||||
<!-- Plone 4 Event Subscribers -->
|
||||
|
||||
|
||||
<configure zcml:condition="installed zope.app.container">
|
||||
|
||||
<subscriber
|
||||
@ -88,7 +88,7 @@
|
||||
</configure>
|
||||
|
||||
<!-- Control panel event subscribers -->
|
||||
|
||||
|
||||
<subscriber
|
||||
for="plone.app.controlpanel.interfaces.IConfigurationChangedEvent"
|
||||
handler=".browser.controlpanel.notify_configuration_changed"
|
||||
@ -97,6 +97,6 @@
|
||||
<subscriber
|
||||
for="plone.registry.interfaces.IRecordModifiedEvent"
|
||||
handler=".browser.controlpanel.notify_configuration_changed"
|
||||
/>
|
||||
/>
|
||||
|
||||
</configure>
|
||||
|
@ -23,12 +23,12 @@ class PloneAppDiscussion(PloneSandboxLayer):
|
||||
USER_WITH_FULLNAME_PASSWORD = 'secret'
|
||||
MANAGER_USER_NAME = 'manager'
|
||||
MANAGER_USER_PASSWORD = 'secret'
|
||||
|
||||
|
||||
def setUpZope(self, app, configurationContext):
|
||||
# Load ZCML
|
||||
import plone.app.discussion
|
||||
xmlconfig.file('configure.zcml',
|
||||
plone.app.discussion,
|
||||
xmlconfig.file('configure.zcml',
|
||||
plone.app.discussion,
|
||||
context=configurationContext)
|
||||
|
||||
def setUpPloneSite(self, portal):
|
||||
@ -55,8 +55,8 @@ class PloneAppDiscussion(PloneSandboxLayer):
|
||||
['Member'],
|
||||
[],
|
||||
)
|
||||
mtool = getToolByName(portal, 'portal_membership', None)
|
||||
mtool.addMember('jim', 'Jim', ['Member'], [])
|
||||
mtool = getToolByName(portal, 'portal_membership', None)
|
||||
mtool.addMember('jim', 'Jim', ['Member'], [])
|
||||
mtool.getMemberById('jim').setMemberProperties({"fullname": 'Jim Fult\xc3\xb8rn'})
|
||||
|
||||
acl_users.userFolderAddUser(
|
||||
@ -68,8 +68,8 @@ class PloneAppDiscussion(PloneSandboxLayer):
|
||||
|
||||
PLONE_APP_DISCUSSION_FIXTURE = PloneAppDiscussion()
|
||||
PLONE_APP_DISCUSSION_INTEGRATION_TESTING = IntegrationTesting(
|
||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
||||
name="PloneAppDiscussion:Integration")
|
||||
PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING = FunctionalTesting(
|
||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
||||
name="PloneAppDiscussion:Functional")
|
||||
|
@ -19,13 +19,13 @@ class CatalogSetupTest(PloneTestCase):
|
||||
layer = DiscussionLayer
|
||||
|
||||
def test_catalog_installed(self):
|
||||
self.failUnless('total_comments' in
|
||||
self.failUnless('total_comments' in
|
||||
self.portal.portal_catalog.indexes())
|
||||
self.failUnless('commentators' in
|
||||
self.failUnless('commentators' in
|
||||
self.portal.portal_catalog.indexes())
|
||||
self.failUnless('total_comments' in
|
||||
self.failUnless('total_comments' in
|
||||
self.portal.portal_catalog.schema())
|
||||
self.failUnless('in_response_to' in
|
||||
self.failUnless('in_response_to' in
|
||||
self.portal.portal_catalog.schema())
|
||||
|
||||
def test_collection_criteria_installed(self):
|
||||
@ -44,8 +44,8 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
def afterSetUp(self):
|
||||
# First we need to create some content.
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
Title='Document 1',
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
Title='Document 1',
|
||||
type_name='Document')
|
||||
|
||||
self.catalog = getToolByName(self.portal, 'portal_catalog')
|
||||
@ -64,7 +64,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
self.comment_id = new_comment1_id
|
||||
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -88,7 +88,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
'++conversation++default/%s' % new_comment2_id)
|
||||
comment2.reindexObject()
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -97,7 +97,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
|
||||
def test_last_comment_date(self):
|
||||
self.failUnless(self.doc1_brain.has_key('last_comment_date'))
|
||||
self.assertEquals(self.doc1_brain.last_comment_date,
|
||||
self.assertEquals(self.doc1_brain.last_comment_date,
|
||||
datetime(2006, 9, 17, 14, 18, 12))
|
||||
|
||||
# Add another comment and check if last comment date is updated.
|
||||
@ -113,31 +113,31 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
'++conversation++default/%s' % new_comment2_id)
|
||||
comment2.reindexObject()
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
doc1_brain = brains[0]
|
||||
self.assertEquals(doc1_brain.last_comment_date,
|
||||
self.assertEquals(doc1_brain.last_comment_date,
|
||||
datetime(2009, 9, 17, 14, 18, 12))
|
||||
|
||||
# Remove the comment again
|
||||
del self.conversation[new_comment2_id]
|
||||
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
doc1_brain = brains[0]
|
||||
|
||||
self.assertEquals(doc1_brain.last_comment_date,
|
||||
self.assertEquals(doc1_brain.last_comment_date,
|
||||
datetime(2006, 9, 17, 14, 18, 12))
|
||||
|
||||
# remove all comments
|
||||
del self.conversation[self.new_comment1_id]
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -161,7 +161,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
comment2.reindexObject()
|
||||
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -172,7 +172,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
# remove one comments
|
||||
del self.conversation[new_comment2_id]
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -182,7 +182,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
# remove all comments
|
||||
del self.conversation[self.new_comment1_id]
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Document"
|
||||
)
|
||||
@ -191,7 +191,7 @@ class ConversationCatalogTest(PloneTestCase):
|
||||
|
||||
def test_conversation_indexes_not_in_comments(self):
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.portal.doc1.getPhysicalPath()) },
|
||||
portal_type = "Discussion Item"
|
||||
)
|
||||
@ -208,8 +208,8 @@ class CommentCatalogTest(PloneTestCase):
|
||||
"""Create a document with a comment.
|
||||
"""
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
self.catalog = getToolByName(self.portal, 'portal_catalog')
|
||||
|
||||
@ -226,13 +226,13 @@ class CommentCatalogTest(PloneTestCase):
|
||||
self.comment = self.portal.doc1.restrictedTraverse(
|
||||
'++conversation++default/%s' % new_comment1_id)
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.comment.getPhysicalPath()) })
|
||||
self.comment_brain = brains[0]
|
||||
|
||||
def test_title(self):
|
||||
self.assertEquals(self.comment_brain.Title, 'Jim on Document 1')
|
||||
|
||||
|
||||
def test_no_name_title(self):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
@ -242,7 +242,7 @@ class CommentCatalogTest(PloneTestCase):
|
||||
comment = self.portal.doc1.restrictedTraverse(
|
||||
'++conversation++default/%s' % cid)
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(comment.getPhysicalPath()) })
|
||||
comment_brain = brains[0]
|
||||
self.assertEquals(comment_brain.Title, "Anonymous on Document 1")
|
||||
@ -266,13 +266,13 @@ class CommentCatalogTest(PloneTestCase):
|
||||
|
||||
def test_add_comment(self):
|
||||
self.failUnless(self.comment_brain)
|
||||
|
||||
|
||||
def test_delete_comment(self):
|
||||
# Make sure a comment is removed from the catalog as well when it is
|
||||
# deleted.
|
||||
del self.conversation[self.comment_id]
|
||||
brains = self.catalog.searchResults(
|
||||
path = {'query' :
|
||||
path = {'query' :
|
||||
'/'.join(self.comment.getPhysicalPath()) })
|
||||
self.assertEquals(len(brains), 0)
|
||||
|
||||
@ -285,7 +285,7 @@ class CommentCatalogTest(PloneTestCase):
|
||||
self.portal.manage_delObjects(["doc1"])
|
||||
brains = self.catalog.searchResults(portal_type = 'Discussion Item')
|
||||
self.assertEquals(len(brains), 0)
|
||||
|
||||
|
||||
def test_clear_and_rebuild_catalog(self):
|
||||
# Clear and rebuild catalog
|
||||
self.catalog.clearFindAndRebuild()
|
||||
@ -353,7 +353,7 @@ class CommentCatalogTest(PloneTestCase):
|
||||
self.assertEquals(len(brains), 6)
|
||||
|
||||
def test_collection(self):
|
||||
self.portal.invokeFactory(id='topic', type_name='Topic')
|
||||
self.portal.invokeFactory(id='topic', type_name='Topic')
|
||||
topic = self.portal.topic
|
||||
crit = topic.addCriterion('Type', 'ATSimpleStringCriterion')
|
||||
crit.setValue('Comment')
|
||||
|
@ -37,7 +37,7 @@ class CommentTest(PloneTestCase):
|
||||
self.catalog = getToolByName(self.portal, 'portal_catalog')
|
||||
self.document_brain = self.catalog.searchResults(
|
||||
portal_type = 'Document')[0]
|
||||
|
||||
|
||||
def test_factory(self):
|
||||
comment1 = createObject('plone.Comment')
|
||||
self.assert_(IComment.providedBy(comment1))
|
||||
@ -71,7 +71,7 @@ class CommentTest(PloneTestCase):
|
||||
conversation.addComment(comment1)
|
||||
comment_brain = self.catalog.searchResults(
|
||||
portal_type = 'Discussion Item')[0]
|
||||
|
||||
|
||||
# comment should only have a UID if plone.uuid is present
|
||||
try:
|
||||
from plone.uuid.interfaces import IUUID
|
||||
@ -80,7 +80,7 @@ class CommentTest(PloneTestCase):
|
||||
self.failIf(comment_brain.UID)
|
||||
else:
|
||||
self.failUnless(comment_brain.UID)
|
||||
|
||||
|
||||
def test_uid_is_unique(self):
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
comment1 = createObject('plone.Comment')
|
||||
@ -89,12 +89,12 @@ class CommentTest(PloneTestCase):
|
||||
conversation.addComment(comment2)
|
||||
brains = self.catalog.searchResults(
|
||||
portal_type = 'Discussion Item')
|
||||
|
||||
|
||||
# make sure uids are either both None (i.e. without plone.uuid),
|
||||
# or not equal
|
||||
if brains[0].UID != None or brains[1].UID != None:
|
||||
self.assertNotEquals(brains[0].UID, brains[1].UID)
|
||||
|
||||
|
||||
def test_comment_uid_differs_from_content_uid(self):
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
comment1 = createObject('plone.Comment')
|
||||
|
@ -4,7 +4,7 @@ import time
|
||||
from datetime import datetime
|
||||
|
||||
from AccessControl import Unauthorized
|
||||
|
||||
|
||||
from zope.component import createObject, queryUtility
|
||||
|
||||
from OFS.Image import Image
|
||||
@ -31,7 +31,7 @@ from Products.PloneTestCase.ptc import PloneTestCase
|
||||
|
||||
from plone.app.discussion.browser.comments import CommentsViewlet
|
||||
from plone.app.discussion.browser.comments import CommentForm
|
||||
from plone.app.discussion.interfaces import IConversation
|
||||
from plone.app.discussion.interfaces import IConversation
|
||||
from plone.app.discussion.tests.layer import DiscussionLayer
|
||||
from plone.app.discussion.interfaces import IDiscussionSettings
|
||||
|
||||
@ -44,8 +44,8 @@ class TestCommentForm(PloneTestCase):
|
||||
self.loginAsPortalOwner()
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Document', self.portal, 'doc1')
|
||||
self.dtool = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
self.dtool = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
None)
|
||||
self.dtool.overrideDiscussionFor(self.portal.doc1, False)
|
||||
self.mtool = getToolByName(self.folder, 'portal_membership', None)
|
||||
@ -57,11 +57,11 @@ class TestCommentForm(PloneTestCase):
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings)
|
||||
settings.globally_enabled = True
|
||||
|
||||
|
||||
def test_add_comment(self):
|
||||
"""Post a comment as logged-in user.
|
||||
"""
|
||||
|
||||
|
||||
# Allow discussion
|
||||
self.dtool.overrideDiscussionFor(self.portal.doc1, True)
|
||||
self.viewlet = CommentsViewlet(self.context, self.request, None, None)
|
||||
@ -72,99 +72,99 @@ class TestCommentForm(PloneTestCase):
|
||||
alsoProvides(request, IFormLayer)
|
||||
alsoProvides(request, IAttributeAnnotatable)
|
||||
return request
|
||||
|
||||
|
||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
||||
provides=Interface,
|
||||
factory=CommentForm,
|
||||
name=u"comment-form")
|
||||
|
||||
|
||||
# The form should return an error if the comment text field is empty
|
||||
request = make_request(form={})
|
||||
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
name=u"comment-form")
|
||||
commentForm.update()
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
self.assertEquals(len(errors), 1)
|
||||
self.failIf(commentForm.handleComment(commentForm, "foo"))
|
||||
|
||||
# The form is submitted successfully, if the required text field is
|
||||
|
||||
# The form is submitted successfully, if the required text field is
|
||||
# filled out
|
||||
request = make_request(form={'form.widgets.text': u'bar'})
|
||||
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
name=u"comment-form")
|
||||
commentForm.update()
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
self.assertEquals(len(errors), 0)
|
||||
self.failIf(commentForm.handleComment(commentForm, "foo"))
|
||||
|
||||
|
||||
def test_add_anonymous_comment(self):
|
||||
"""Add a comment as anonymous.
|
||||
"""
|
||||
|
||||
|
||||
# Allow discussion
|
||||
self.dtool.overrideDiscussionFor(self.portal.doc1, True)
|
||||
self.viewlet = CommentsViewlet(self.context, self.request, None, None)
|
||||
|
||||
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
settings.anonymous_comments = True
|
||||
|
||||
# Logout
|
||||
self.logout()
|
||||
|
||||
|
||||
def make_request(form={}):
|
||||
request = TestRequest()
|
||||
request.form.update(form)
|
||||
alsoProvides(request, IFormLayer)
|
||||
alsoProvides(request, IAttributeAnnotatable)
|
||||
return request
|
||||
|
||||
|
||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
||||
provides=Interface,
|
||||
factory=CommentForm,
|
||||
name=u"comment-form")
|
||||
|
||||
|
||||
# Post an anonymous comment and provide a name
|
||||
request = make_request(form={'form.widgets.name': u'john doe',
|
||||
'form.widgets.text': u'bar'})
|
||||
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
name=u"comment-form")
|
||||
commentForm.update()
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
self.assertEquals(len(errors), 0)
|
||||
self.failIf(commentForm.handleComment(commentForm, "action"))
|
||||
|
||||
|
||||
def test_can_not_add_comments_if_discussion_is_not_allowed(self):
|
||||
"""Make sure that comments can't be posted if discussion is disabled.
|
||||
"""
|
||||
|
||||
|
||||
# Discussion is disabled by default
|
||||
|
||||
|
||||
def make_request(form={}):
|
||||
request = TestRequest()
|
||||
request.form.update(form)
|
||||
alsoProvides(request, IFormLayer)
|
||||
alsoProvides(request, IAttributeAnnotatable)
|
||||
return request
|
||||
|
||||
|
||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
||||
provides=Interface,
|
||||
factory=CommentForm,
|
||||
name=u"comment-form")
|
||||
|
||||
|
||||
request = make_request(form={'form.widgets.text': u'bar'})
|
||||
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
name=u"comment-form")
|
||||
commentForm.update()
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
# No form errors, but raise unauthorized because discussion is not
|
||||
# allowed
|
||||
self.assertEquals(len(errors), 0)
|
||||
@ -172,52 +172,52 @@ class TestCommentForm(PloneTestCase):
|
||||
commentForm.handleComment,
|
||||
commentForm,
|
||||
"foo")
|
||||
|
||||
|
||||
def test_anonymous_can_not_add_comments_if_discussion_is_not_allowed(self):
|
||||
"""Make sure that anonymous users can't post comments if anonymous
|
||||
comments are disabled.
|
||||
"""
|
||||
|
||||
|
||||
# Anonymous comments are disabled by default
|
||||
|
||||
|
||||
self.logout()
|
||||
|
||||
|
||||
def make_request(form={}):
|
||||
request = TestRequest()
|
||||
request.form.update(form)
|
||||
alsoProvides(request, IFormLayer)
|
||||
alsoProvides(request, IAttributeAnnotatable)
|
||||
return request
|
||||
|
||||
|
||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
||||
provides=Interface,
|
||||
factory=CommentForm,
|
||||
name=u"comment-form")
|
||||
|
||||
|
||||
request = make_request(form={'form.widgets.text': u'bar'})
|
||||
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
commentForm = getMultiAdapter((self.context, request),
|
||||
name=u"comment-form")
|
||||
commentForm.update()
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||
|
||||
self.assertEquals(len(errors), 0)
|
||||
self.assertRaises(Unauthorized,
|
||||
commentForm.handleComment,
|
||||
commentForm,
|
||||
"foo")
|
||||
|
||||
|
||||
|
||||
class TestCommentsViewlet(PloneTestCase):
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
self.loginAsPortalOwner()
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Document', self.portal, 'doc1')
|
||||
self.portal_discussion = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
self.portal_discussion = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
None)
|
||||
self.mtool = getToolByName(self.folder, 'portal_membership')
|
||||
self.memberdata = self.portal.portal_memberdata
|
||||
@ -229,29 +229,29 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings)
|
||||
settings.globally_enabled = True
|
||||
|
||||
|
||||
def test_cook(self):
|
||||
text = """First paragraph
|
||||
|
||||
|
||||
Second paragraph"""
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
"<p>First paragraph<br /> <br /> Second paragraph</p>")
|
||||
"<p>First paragraph<br /><br /> Second paragraph</p>")
|
||||
|
||||
def test_cook_no_html(self):
|
||||
text = """<b>Got HTML?</b>"""
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
"<p><b>Got HTML?</b></p>")
|
||||
|
||||
|
||||
def test_cook_with_no_ascii_characters(self):
|
||||
text = """Umlaute sind ä, ö und ü."""
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
"<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>")
|
||||
|
||||
|
||||
def test_cook_links(self):
|
||||
text = "Go to http://www.plone.org"
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
self.assertEquals(self.viewlet.cook(text),
|
||||
"<p>Go to http://www.plone.org</p>")
|
||||
|
||||
|
||||
def test_can_reply(self):
|
||||
# Portal owner can reply
|
||||
self.failUnless(self.viewlet.can_reply())
|
||||
@ -268,7 +268,7 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
# The reviewer role has the 'Review comments' permission
|
||||
self.portal.acl_users._doAddUser('reviewer', 'secret', ['Reviewer'], [])
|
||||
self.login('reviewer')
|
||||
self.failUnless(self.viewlet.can_review())
|
||||
self.failUnless(self.viewlet.can_review())
|
||||
|
||||
def test_can_manage(self):
|
||||
"""We keep this method for backward compatibility. This method has been
|
||||
@ -283,8 +283,8 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
# The reviewer role has the 'Review comments' permission
|
||||
self.portal.acl_users._doAddUser('reviewer', 'secret', ['Reviewer'], [])
|
||||
self.login('reviewer')
|
||||
self.failUnless(self.viewlet.can_manage())
|
||||
|
||||
self.failUnless(self.viewlet.can_manage())
|
||||
|
||||
def test_is_discussion_allowed(self):
|
||||
# By default, discussion is disabled
|
||||
self.failIf(self.viewlet.is_discussion_allowed())
|
||||
@ -309,12 +309,11 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
|
||||
# Make sure the comment description is changes accordingly
|
||||
self.assertEquals(
|
||||
self.viewlet.comment_transform_message(),
|
||||
self.viewlet.comment_transform_message(),
|
||||
"You can add a comment by filling out the form below. " +
|
||||
"Plain text formatting. Web and email addresses are transformed " +
|
||||
"into clickable links.")
|
||||
|
||||
|
||||
|
||||
def test_has_replies(self):
|
||||
self.assertEquals(self.viewlet.has_replies(), False)
|
||||
comment = createObject('plone.Comment')
|
||||
@ -331,7 +330,7 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
conversation.addComment(comment)
|
||||
conversation.addComment(comment)
|
||||
replies = self.viewlet.get_replies()
|
||||
self.assertEquals(len(tuple(replies)), 2)
|
||||
self.assertEquals(len(tuple(replies)), 2)
|
||||
replies = self.viewlet.get_replies()
|
||||
replies.next()
|
||||
replies.next()
|
||||
@ -344,7 +343,7 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
c1 = conversation.addComment(comment)
|
||||
self.assertEquals(
|
||||
len(tuple(self.viewlet.get_replies(workflow_actions=True))), 1)
|
||||
len(tuple(self.viewlet.get_replies(workflow_actions=True))), 1)
|
||||
# Enable moderation workflow
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
('Discussion Item',),
|
||||
@ -356,8 +355,8 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
'publish')
|
||||
self.assertEquals(reply['actions'][0]['url'],
|
||||
'http://nohost/plone/doc1/++conversation++default/%s' % int(c1) +
|
||||
'/content_status_modify?workflow_action=publish')
|
||||
|
||||
'/content_status_modify?workflow_action=publish')
|
||||
|
||||
def test_get_commenter_home_url(self):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
@ -369,17 +368,17 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
|
||||
def test_get_commenter_home_url_is_none(self):
|
||||
self.failIf(self.viewlet.get_commenter_home_url())
|
||||
|
||||
|
||||
def test_get_commenter_portrait(self):
|
||||
|
||||
# Add a user with a member image
|
||||
self.mtool.addMember('jim', 'Jim', ['Member'], [])
|
||||
self.memberdata._setPortrait(Image(id='jim',
|
||||
self.memberdata._setPortrait(Image(id='jim',
|
||||
file=dummy.File(),
|
||||
title=''), 'jim')
|
||||
self.assertEqual(self.memberdata._getPortrait('jim').getId(),
|
||||
self.assertEqual(self.memberdata._getPortrait('jim').getId(),
|
||||
'jim')
|
||||
self.assertEqual(self.memberdata._getPortrait('jim').meta_type,
|
||||
self.assertEqual(self.memberdata._getPortrait('jim').meta_type,
|
||||
'Image')
|
||||
|
||||
# Add a conversation with a comment
|
||||
@ -395,13 +394,13 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
portrait_url = self.viewlet.get_commenter_portrait('jim')
|
||||
|
||||
# Check if the correct member image URL is returned
|
||||
self.assertEquals(portrait_url,
|
||||
self.assertEquals(portrait_url,
|
||||
'http://nohost/plone/portal_memberdata/portraits/jim')
|
||||
|
||||
def test_get_commenter_portrait_is_none(self):
|
||||
self.assertEquals(self.viewlet.get_commenter_portrait(),
|
||||
self.assertEquals(self.viewlet.get_commenter_portrait(),
|
||||
'defaultUser.gif')
|
||||
|
||||
|
||||
def test_get_commenter_portrait_without_userimage(self):
|
||||
|
||||
# Create a user without a user image
|
||||
@ -431,14 +430,14 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
'anonymous_comments'] = True
|
||||
# Test if anonymous discussion is allowed for the viewlet
|
||||
self.failUnless(self.viewlet.anonymous_discussion_allowed())
|
||||
|
||||
|
||||
def test_show_commenter_image(self):
|
||||
self.failUnless(self.viewlet.show_commenter_image())
|
||||
registry = queryUtility(IRegistry)
|
||||
registry['plone.app.discussion.interfaces.IDiscussionSettings.' +
|
||||
'show_commenter_image'] = False
|
||||
'show_commenter_image'] = False
|
||||
self.failIf(self.viewlet.show_commenter_image())
|
||||
|
||||
|
||||
def test_is_anonymous(self):
|
||||
self.failIf(self.viewlet.is_anonymous())
|
||||
self.logout()
|
||||
@ -447,8 +446,8 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
def test_login_action(self):
|
||||
self.viewlet.update()
|
||||
self.assertEquals(self.viewlet.login_action(),
|
||||
'http://nohost/plone/login_form?came_from=http%3A//nohost')
|
||||
|
||||
'http://nohost/plone/login_form?came_from=http%3A//nohost')
|
||||
|
||||
def test_format_time(self):
|
||||
python_time = datetime(2009, 02, 01, 23, 32, 03, 57)
|
||||
# Python Time must be utc time. There seems to be no too simple way
|
||||
@ -464,5 +463,6 @@ class TestCommentsViewlet(PloneTestCase):
|
||||
localized_time = self.viewlet.format_time(python_time)
|
||||
self.assertEquals(localized_time, "Feb 01, 2009 11:32 PM")
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.defaultTestLoader.loadTestsFromName(__name__)
|
||||
|
@ -27,9 +27,9 @@ class RegistryTest(PloneTestCase):
|
||||
def test_registry_registered(self):
|
||||
registry = queryUtility(IRegistry)
|
||||
self.failUnless(registry.forInterface(IDiscussionSettings))
|
||||
|
||||
|
||||
def test_discussion_controlpanel_view(self):
|
||||
view = getMultiAdapter((self.portal, self.portal.REQUEST),
|
||||
view = getMultiAdapter((self.portal, self.portal.REQUEST),
|
||||
name="discussion-settings")
|
||||
view = view.__of__(self.portal)
|
||||
self.failUnless(view())
|
||||
@ -45,7 +45,7 @@ class RegistryTest(PloneTestCase):
|
||||
self.failUnless('globally_enabled' in IDiscussionSettings)
|
||||
self.assertEquals(
|
||||
self.registry['plone.app.discussion.interfaces.' +
|
||||
'IDiscussionSettings.globally_enabled'],
|
||||
'IDiscussionSettings.globally_enabled'],
|
||||
False)
|
||||
|
||||
def test_anonymous_comments(self):
|
||||
@ -59,16 +59,16 @@ class RegistryTest(PloneTestCase):
|
||||
self.failUnless('moderation_enabled' in IDiscussionSettings)
|
||||
self.assertEquals(
|
||||
self.registry['plone.app.discussion.interfaces.' +
|
||||
'IDiscussionSettings.moderation_enabled'],
|
||||
'IDiscussionSettings.moderation_enabled'],
|
||||
False)
|
||||
|
||||
|
||||
def test_text_transform(self):
|
||||
self.failUnless('text_transform' in IDiscussionSettings)
|
||||
self.assertEquals(
|
||||
self.registry['plone.app.discussion.interfaces.' +
|
||||
'IDiscussionSettings.text_transform'],
|
||||
'text/plain')
|
||||
|
||||
|
||||
def test_captcha(self):
|
||||
# Check globally_enabled record
|
||||
self.failUnless('captcha' in IDiscussionSettings)
|
||||
@ -79,12 +79,12 @@ class RegistryTest(PloneTestCase):
|
||||
def test_show_commenter_image(self):
|
||||
# Check show_commenter_image record
|
||||
self.failUnless('show_commenter_image' in IDiscussionSettings)
|
||||
self.assertEquals(self.registry['plone.app.discussion.interfaces.' +
|
||||
self.assertEquals(self.registry['plone.app.discussion.interfaces.' +
|
||||
'IDiscussionSettings.show_commenter_image'], True)
|
||||
|
||||
def test_moderator_notification_enabled(self):
|
||||
# Check show_commenter_image record
|
||||
self.failUnless('moderator_notification_enabled' in
|
||||
self.failUnless('moderator_notification_enabled' in
|
||||
IDiscussionSettings)
|
||||
self.assertEquals(self.registry['plone.app.discussion.interfaces.' +
|
||||
'IDiscussionSettings.moderator_notification_enabled'], False)
|
||||
@ -108,39 +108,39 @@ class ConfigurationChangedSubscriberTest(PloneTestCase):
|
||||
# Set up the registry
|
||||
registry = queryUtility(IRegistry)
|
||||
self.settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
|
||||
def test_moderation_enabled_in_discussion_control_panel_changed(self):
|
||||
"""Make sure the 'Discussion Item' workflow is changed properly, when
|
||||
the 'comment_moderation' setting in the discussion control panel
|
||||
"""Make sure the 'Discussion Item' workflow is changed properly, when
|
||||
the 'comment_moderation' setting in the discussion control panel
|
||||
changes.
|
||||
"""
|
||||
# By default the one_state_workflow without moderation is enabled
|
||||
self.assertEquals(('one_state_workflow',),
|
||||
self.portal.portal_workflow.getChainForPortalType(
|
||||
'Discussion Item'))
|
||||
|
||||
'Discussion Item'))
|
||||
|
||||
# Enable moderation in the discussion control panel
|
||||
self.settings.moderation_enabled = True
|
||||
|
||||
self.settings.moderation_enabled = True
|
||||
|
||||
# Make sure the comment_review_workflow with moderation enabled is
|
||||
# enabled
|
||||
self.assertEquals(('comment_review_workflow',),
|
||||
self.portal.portal_workflow.getChainForPortalType(
|
||||
'Discussion Item'))
|
||||
'Discussion Item'))
|
||||
# And back
|
||||
self.settings.moderation_enabled = False
|
||||
self.assertEquals(('one_state_workflow',),
|
||||
self.portal.portal_workflow.getChainForPortalType(
|
||||
'Discussion Item'))
|
||||
|
||||
'Discussion Item'))
|
||||
|
||||
def test_change_workflow_in_types_control_panel(self):
|
||||
"""Make sure the setting in the discussion control panel is changed
|
||||
accordingly, when the workflow for the 'Discussion Item' changed in
|
||||
the types control panel.
|
||||
accordingly, when the workflow for the 'Discussion Item' changed in
|
||||
the types control panel.
|
||||
"""
|
||||
# By default, moderation is disabled
|
||||
self.settings.moderation_enabled = False
|
||||
|
||||
|
||||
# Enable the 'comment_review_workflow' with moderation enabled
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
('Discussion Item',),
|
||||
@ -159,11 +159,11 @@ class ConfigurationChangedSubscriberTest(PloneTestCase):
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
('Discussion Item',),
|
||||
('intranet_workflow',))
|
||||
|
||||
# Setting has not changed. A Custom workflow disables the
|
||||
|
||||
# Setting has not changed. A Custom workflow disables the
|
||||
# enable_moderation checkbox in the discussion control panel. The
|
||||
# setting itself remains unchanged.
|
||||
self.settings.moderation_enabled = True
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.defaultTestLoader.loadTestsFromName(__name__)
|
||||
|
@ -29,20 +29,20 @@ class ConversationTest(PloneTestCase):
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Document', self.portal, 'doc1')
|
||||
self.typetool = typetool
|
||||
self.portal_discussion = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
self.portal_discussion = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
None)
|
||||
# Allow discussion
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings)
|
||||
settings.globally_enabled = True
|
||||
|
||||
|
||||
def test_add_comment(self):
|
||||
# Create a conversation. In this case we doesn't assign it to an
|
||||
# object, as we just want to check the Conversation object API.
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# factory to allow different factories to be swapped in
|
||||
|
||||
comment = createObject('plone.Comment')
|
||||
@ -59,7 +59,7 @@ class ConversationTest(PloneTestCase):
|
||||
self.assertEquals(len(list(conversation.getComments())), 1)
|
||||
self.assertEquals(len(tuple(conversation.getThreads())), 1)
|
||||
self.assertEquals(conversation.total_comments, 1)
|
||||
self.assert_(conversation.last_comment_date - datetime.utcnow() <
|
||||
self.assert_(conversation.last_comment_date - datetime.utcnow() <
|
||||
timedelta(seconds=1))
|
||||
|
||||
def test_delete_comment(self):
|
||||
@ -67,7 +67,7 @@ class ConversationTest(PloneTestCase):
|
||||
# object, as we just want to check the Conversation object API.
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# factory to allow different factories to be swapped in
|
||||
|
||||
comment = createObject('plone.Comment')
|
||||
@ -148,7 +148,7 @@ class ConversationTest(PloneTestCase):
|
||||
], list(conversation.getThreads()))
|
||||
|
||||
def test_delete_comment_when_content_object_is_deleted(self):
|
||||
# Make sure all comments of a content object are deleted when the
|
||||
# Make sure all comments of a content object are deleted when the
|
||||
# object itself is deleted.
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
comment = createObject('plone.Comment')
|
||||
@ -157,11 +157,11 @@ class ConversationTest(PloneTestCase):
|
||||
|
||||
# Delete the content object
|
||||
self.portal.manage_delObjects(['doc1'])
|
||||
|
||||
|
||||
# Make sure the comment has been deleted as well
|
||||
self.assertEquals(len(list(conversation.getComments())), 0)
|
||||
self.assertEquals(len(tuple(conversation.getThreads())), 0)
|
||||
self.assertEquals(conversation.total_comments, 0)
|
||||
self.assertEquals(conversation.total_comments, 0)
|
||||
|
||||
def test_allow_discussion(self):
|
||||
# This is not a real test! It's only there to understand the
|
||||
@ -188,20 +188,20 @@ class ConversationTest(PloneTestCase):
|
||||
portal_discussion = getToolByName(self.portal, 'portal_discussion')
|
||||
self.assertEquals(portal_discussion.isDiscussionAllowedFor(
|
||||
self.portal.doc1), False)
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
False)
|
||||
|
||||
# The allow discussion flag is None by default
|
||||
self.failIf(getattr(self.portal.doc1, 'allow_discussion', None))
|
||||
|
||||
# But isDiscussionAllowedFor, also checks if discussion is allowed on
|
||||
# the content type. So we allow discussion on the Document content
|
||||
# But isDiscussionAllowedFor, also checks if discussion is allowed on
|
||||
# the content type. So we allow discussion on the Document content
|
||||
# type and check if the Document object allows discussion now.
|
||||
document_fti = getattr(portal_types, 'Document')
|
||||
document_fti.manage_changeProperties(allow_discussion = True)
|
||||
self.assertEquals(portal_discussion.isDiscussionAllowedFor(
|
||||
self.portal.doc1), True)
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
True)
|
||||
|
||||
# We can also override the allow_discussion locally
|
||||
@ -209,16 +209,16 @@ class ConversationTest(PloneTestCase):
|
||||
# Check if the Document discussion is disabled
|
||||
self.assertEquals(portal_discussion.isDiscussionAllowedFor(
|
||||
self.portal.doc1), False)
|
||||
# Check that the local allow_discussion flag is now explicitly set to
|
||||
# Check that the local allow_discussion flag is now explicitly set to
|
||||
# False
|
||||
self.assertEquals(getattr(self.portal.doc1, 'allow_discussion', None),
|
||||
self.assertEquals(getattr(self.portal.doc1, 'allow_discussion', None),
|
||||
False)
|
||||
|
||||
# Disallow discussion on the Document content type again
|
||||
document_fti.manage_changeProperties(allow_discussion = False)
|
||||
self.assertEquals(portal_discussion.isDiscussionAllowedFor(
|
||||
self.portal.doc1), False)
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
self.assertEquals(self.portal.doc1.getTypeInfo().allowDiscussion(),
|
||||
False)
|
||||
|
||||
# Now we override allow_discussion again (True) for the Document
|
||||
@ -226,14 +226,14 @@ class ConversationTest(PloneTestCase):
|
||||
self.portal_discussion.overrideDiscussionFor(self.portal.doc1, True)
|
||||
self.assertEquals(portal_discussion.isDiscussionAllowedFor(
|
||||
self.portal.doc1), True)
|
||||
self.assertEquals(getattr(self.portal.doc1, 'allow_discussion', None),
|
||||
self.assertEquals(getattr(self.portal.doc1, 'allow_discussion', None),
|
||||
True)
|
||||
|
||||
def test_comments_enabled_on_doc_in_subfolder(self):
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Folder', self.portal, 'folder1')
|
||||
typetool.constructContent('Document', self.portal.folder1, 'doc2')
|
||||
|
||||
|
||||
folder = self.portal.folder1
|
||||
folder.allowDiscussion(False)
|
||||
self.assertFalse(hasattr(aq_base(folder), 'allow_discussion'))
|
||||
@ -241,11 +241,11 @@ class ConversationTest(PloneTestCase):
|
||||
self.assertTrue(aq_base(folder).allow_discussion)
|
||||
folder.allowDiscussion(False)
|
||||
self.assertFalse(aq_base(folder).allow_discussion)
|
||||
|
||||
|
||||
doc = self.portal.folder1.doc2
|
||||
conversation = IConversation(doc)
|
||||
self.assertEquals(conversation.enabled(), False)
|
||||
|
||||
|
||||
# We have to allow discussion on Document content type, since
|
||||
# otherwise allow_discussion will always return False
|
||||
portal_types = getToolByName(self.portal, 'portal_types')
|
||||
@ -409,7 +409,7 @@ class ConversationTest(PloneTestCase):
|
||||
# object, as we just want to check the Conversation object API.
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# factory to allow different factories to be swapped in
|
||||
|
||||
comment1 = createObject('plone.Comment')
|
||||
@ -571,9 +571,9 @@ class ConversationTest(PloneTestCase):
|
||||
new_comment3_id = conversation.addComment(comment3)
|
||||
|
||||
# check if the latest comment is exactly one day old
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
timedelta(hours=23, minutes=59, seconds=59))
|
||||
self.assert_(conversation.last_comment_date >
|
||||
self.assert_(conversation.last_comment_date >
|
||||
datetime.utcnow() - timedelta(days=1, seconds=1))
|
||||
|
||||
# remove the latest comment
|
||||
@ -581,9 +581,9 @@ class ConversationTest(PloneTestCase):
|
||||
|
||||
# check if the latest comment has been updated
|
||||
# the latest comment should be exactly two days old
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
timedelta(days=1, hours=23, minutes=59, seconds=59))
|
||||
self.assert_(conversation.last_comment_date > datetime.utcnow() -
|
||||
self.assert_(conversation.last_comment_date > datetime.utcnow() -
|
||||
timedelta(days=2, seconds=1))
|
||||
|
||||
# remove the latest comment again
|
||||
@ -591,9 +591,9 @@ class ConversationTest(PloneTestCase):
|
||||
|
||||
# check if the latest comment has been updated
|
||||
# the latest comment should be exactly four days old
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
self.assert_(conversation.last_comment_date < datetime.utcnow() -
|
||||
timedelta(days=3, hours=23, minutes=59, seconds=59))
|
||||
self.assert_(conversation.last_comment_date > datetime.utcnow() -
|
||||
self.assert_(conversation.last_comment_date > datetime.utcnow() -
|
||||
timedelta(days=4, seconds=2))
|
||||
|
||||
def test_get_comments_full(self):
|
||||
@ -678,15 +678,15 @@ class ConversationTest(PloneTestCase):
|
||||
'++conversation++default')
|
||||
self.assert_(IConversation.providedBy(conversation))
|
||||
|
||||
self.assertEquals(('', 'plone', 'doc1', '++conversation++default'),
|
||||
self.assertEquals(('', 'plone', 'doc1', '++conversation++default'),
|
||||
conversation.getPhysicalPath())
|
||||
# XXX: conversation.absolute_url() returns different values dependent
|
||||
# on the Plone version used.
|
||||
# Plone 3.3:
|
||||
#self.assertEquals('plone/doc1/%2B%2Bconversation%2B%2Bdefault',
|
||||
#self.assertEquals('plone/doc1/%2B%2Bconversation%2B%2Bdefault',
|
||||
#conversation.absolute_url())
|
||||
# Plone 4:
|
||||
#self.assertEquals('http://nohost/plone/doc1/++conversation++default',
|
||||
#self.assertEquals('http://nohost/plone/doc1/++conversation++default',
|
||||
#conversation.absolute_url())
|
||||
|
||||
def test_parent(self):
|
||||
|
@ -9,9 +9,9 @@ try:
|
||||
import unittest2 as unittest
|
||||
import pprint
|
||||
import interlude
|
||||
|
||||
|
||||
from plone.testing import layered
|
||||
|
||||
|
||||
from plone.app.discussion.testing import \
|
||||
PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING
|
||||
PLONE4 = True
|
||||
@ -26,7 +26,7 @@ normal_testfiles = [
|
||||
]
|
||||
|
||||
if PLONE4:
|
||||
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTests([
|
||||
@ -41,9 +41,9 @@ if PLONE4:
|
||||
return suite
|
||||
|
||||
else:
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite([])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
||||
|
@ -17,14 +17,14 @@ from plone.indexer.delegate import DelegatingIndexerFactory
|
||||
|
||||
from plone.app.discussion import catalog
|
||||
|
||||
LONG_TEXT = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
|
||||
LONG_TEXT = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
|
||||
amet."""
|
||||
|
||||
LONG_TEXT_CUT = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
LONG_TEXT_CUT = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At [...]"""
|
||||
|
||||
|
||||
@ -37,8 +37,8 @@ class ConversationIndexersTest(PloneTestCase):
|
||||
def afterSetUp(self):
|
||||
# First we need to create some content.
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
|
||||
# Create a conversation.
|
||||
@ -71,7 +71,7 @@ class ConversationIndexersTest(PloneTestCase):
|
||||
self.conversation = conversation
|
||||
|
||||
def test_conversation_total_comments(self):
|
||||
self.assert_(isinstance(catalog.total_comments,
|
||||
self.assert_(isinstance(catalog.total_comments,
|
||||
DelegatingIndexerFactory))
|
||||
self.assertEquals(catalog.total_comments(self.portal.doc1)(), 3)
|
||||
del self.conversation[self.new_id1]
|
||||
@ -81,12 +81,12 @@ class ConversationIndexersTest(PloneTestCase):
|
||||
self.assertEquals(catalog.total_comments(self.portal.doc1)(), 0)
|
||||
|
||||
def test_conversation_last_comment_date(self):
|
||||
self.assert_(isinstance(catalog.last_comment_date,
|
||||
self.assert_(isinstance(catalog.last_comment_date,
|
||||
DelegatingIndexerFactory))
|
||||
self.assertEquals(catalog.last_comment_date(self.portal.doc1)(),
|
||||
self.assertEquals(catalog.last_comment_date(self.portal.doc1)(),
|
||||
datetime(2009, 4, 12, 11, 12, 12))
|
||||
del self.conversation[self.new_id3]
|
||||
self.assertEquals(catalog.last_comment_date(self.portal.doc1)(),
|
||||
self.assertEquals(catalog.last_comment_date(self.portal.doc1)(),
|
||||
datetime(2007, 12, 13, 4, 18, 12))
|
||||
del self.conversation[self.new_id2]
|
||||
del self.conversation[self.new_id1]
|
||||
@ -94,11 +94,12 @@ class ConversationIndexersTest(PloneTestCase):
|
||||
|
||||
def test_conversation_commentators(self):
|
||||
pass
|
||||
#self.assertEquals(catalog.commentators(self.portal.doc1)(),
|
||||
#self.assertEquals(catalog.commentators(self.portal.doc1)(),
|
||||
# ('Jim', 'Emma', 'Lukas'))
|
||||
#self.assert_(isinstance(catalog.commentators,
|
||||
#self.assert_(isinstance(catalog.commentators,
|
||||
# DelegatingIndexerFactory))
|
||||
|
||||
|
||||
class CommentIndexersTest(PloneTestCase):
|
||||
|
||||
layer = DiscussionLayer
|
||||
@ -106,15 +107,15 @@ class CommentIndexersTest(PloneTestCase):
|
||||
def afterSetUp(self):
|
||||
# First we need to create some content.
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
|
||||
# Create a conversation. In this case we doesn't assign it to an
|
||||
# object, as we just want to check the Conversation object API.
|
||||
conversation = IConversation(self.portal.doc1)
|
||||
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# Add a comment. Note: in real life, we always create comments via the
|
||||
# factory to allow different factories to be swapped in
|
||||
|
||||
comment = createObject('plone.Comment')
|
||||
@ -132,7 +133,7 @@ class CommentIndexersTest(PloneTestCase):
|
||||
self.assert_(isinstance(catalog.title, DelegatingIndexerFactory))
|
||||
|
||||
def test_description(self):
|
||||
self.assertEquals(catalog.description(self.comment)(),
|
||||
self.assertEquals(catalog.description(self.comment)(),
|
||||
'Lorem ipsum dolor sit amet.')
|
||||
self.assert_(isinstance(catalog.description, DelegatingIndexerFactory))
|
||||
|
||||
@ -144,23 +145,23 @@ class CommentIndexersTest(PloneTestCase):
|
||||
comment_long.text = LONG_TEXT
|
||||
|
||||
self.conversation.addComment(comment_long)
|
||||
self.assertEquals(catalog.description(comment_long)(),
|
||||
LONG_TEXT_CUT.replace("\n", ""))
|
||||
self.assertEquals(catalog.description(comment_long)(),
|
||||
LONG_TEXT_CUT.replace("\n", " "))
|
||||
|
||||
def test_dates(self):
|
||||
# Test if created, modified, effective etc. are set correctly
|
||||
self.assertEquals(catalog.created(self.comment)(),
|
||||
self.assertEquals(catalog.created(self.comment)(),
|
||||
DateTime(2006, 9, 17, 14, 18, 12))
|
||||
self.assertEquals(catalog.effective(self.comment)(),
|
||||
self.assertEquals(catalog.effective(self.comment)(),
|
||||
DateTime(2006, 9, 17, 14, 18, 12))
|
||||
self.assertEquals(catalog.modified(self.comment)(),
|
||||
self.assertEquals(catalog.modified(self.comment)(),
|
||||
DateTime(2008, 3, 12, 7, 32, 52))
|
||||
|
||||
def test_searchable_text(self):
|
||||
# Test if searchable text is a concatenation of title and comment text
|
||||
self.assertEquals(catalog.searchable_text(self.comment)(),
|
||||
self.assertEquals(catalog.searchable_text(self.comment)(),
|
||||
('Lorem ipsum dolor sit amet.'))
|
||||
self.assert_(isinstance(catalog.searchable_text,
|
||||
self.assert_(isinstance(catalog.searchable_text,
|
||||
DelegatingIndexerFactory))
|
||||
|
||||
def test_creator(self):
|
||||
@ -171,5 +172,6 @@ class CommentIndexersTest(PloneTestCase):
|
||||
# object the comment was added to
|
||||
self.assertEquals(catalog.in_response_to(self.comment)(), 'Document 1')
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.defaultTestLoader.loadTestsFromName(__name__)
|
||||
|
@ -18,11 +18,11 @@ from plone.app.discussion.interfaces import IConversation, IComment
|
||||
class MigrationTest(PloneTestCase):
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc',
|
||||
title='Document 1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
# Create a document
|
||||
self.discussion = getToolByName(self.portal, 'portal_discussion', None)
|
||||
@ -35,7 +35,7 @@ class MigrationTest(PloneTestCase):
|
||||
request.set("test", True)
|
||||
context = getattr(self.portal, 'doc')
|
||||
self.view = View(context, request)
|
||||
self.workflow.setChainForPortalTypes(('Discussion Item',),
|
||||
self.workflow.setChainForPortalTypes(('Discussion Item',),
|
||||
'comment_review_workflow')
|
||||
|
||||
self.doc = self.portal.doc
|
||||
@ -59,7 +59,7 @@ class MigrationTest(PloneTestCase):
|
||||
self.view()
|
||||
|
||||
# Make sure a conversation has been created
|
||||
self.failUnless('plone.app.discussion:conversation' in
|
||||
self.failUnless('plone.app.discussion:conversation' in
|
||||
IAnnotations(self.doc))
|
||||
conversation = IConversation(self.doc)
|
||||
|
||||
@ -71,9 +71,9 @@ class MigrationTest(PloneTestCase):
|
||||
self.assertEquals(comment1.Title(), 'Jim on Document 1')
|
||||
self.assertEquals(comment1.text, 'My Text')
|
||||
self.assertEquals(comment1.Creator(), 'Jim')
|
||||
self.assertEquals(comment1.creation_date,
|
||||
self.assertEquals(comment1.creation_date,
|
||||
datetime(2003, 3, 11, 9, 28, 6))
|
||||
self.assertEquals(comment1.modification_date,
|
||||
self.assertEquals(comment1.modification_date,
|
||||
datetime(2009, 7, 12, 19, 38, 7))
|
||||
self.assertEquals(
|
||||
[{'comment': comment1, 'depth': 0, 'id': long(comment1.id)},]
|
||||
|
@ -22,7 +22,7 @@ class ModerationViewTest(PloneTestCase):
|
||||
self.loginAsPortalOwner()
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Document', self.portal, 'doc1')
|
||||
|
||||
|
||||
self.portal_discussion = getToolByName(self.portal,
|
||||
'portal_discussion',
|
||||
None)
|
||||
@ -66,7 +66,7 @@ class ModerationViewTest(PloneTestCase):
|
||||
'++conversation++default/%s' % new_id_3)
|
||||
|
||||
def test_moderation_enabled(self):
|
||||
"""Make sure that moderation_enabled returns true if the comment
|
||||
"""Make sure that moderation_enabled returns true if the comment
|
||||
workflow implements a 'pending' state.
|
||||
"""
|
||||
# The one_state_workflow does not have a 'pending' state
|
||||
@ -81,10 +81,10 @@ class ModerationViewTest(PloneTestCase):
|
||||
def test_old_comments_not_shown_in_moderation_view(self):
|
||||
# Create an old comment and make sure it is not shown
|
||||
# in the moderation view.
|
||||
|
||||
|
||||
# Create old comment
|
||||
discussion = getToolByName(self.portal, 'portal_discussion', None)
|
||||
discussion.overrideDiscussionFor(self.portal.doc1, 1)
|
||||
discussion.overrideDiscussionFor(self.portal.doc1, 1)
|
||||
talkback = discussion.getDiscussionFor(self.portal.doc1)
|
||||
self.portal.doc1.talkback.createReply('My Title', 'My Text', Creator='Jim')
|
||||
reply = talkback.getReplies()[0]
|
||||
@ -96,7 +96,7 @@ class ModerationViewTest(PloneTestCase):
|
||||
self.failUnless('Jim' in reply.listCreators())
|
||||
self.assertEquals(talkback.replyCount(self.portal.doc1), 1)
|
||||
self.assertEquals(reply.inReplyTo(), self.portal.doc1)
|
||||
|
||||
|
||||
# Make sure only the two new comments are shown
|
||||
self.view()
|
||||
self.assertEquals(len(self.view.comments), 3)
|
||||
@ -110,10 +110,10 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
self.loginAsPortalOwner()
|
||||
typetool = self.portal.portal_types
|
||||
typetool.constructContent('Document', self.portal, 'doc1')
|
||||
self.wf = getToolByName(self.portal,
|
||||
self.wf = getToolByName(self.portal,
|
||||
'portal_workflow',
|
||||
None)
|
||||
|
||||
|
||||
self.request = self.app.REQUEST
|
||||
self.context = self.portal
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
@ -149,7 +149,7 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
'++conversation++default/%s' % new_id_3)
|
||||
|
||||
self.conversation = conversation
|
||||
|
||||
|
||||
def test_default_bulkaction(self):
|
||||
# Make sure no error is raised when no bulk actions has been supplied
|
||||
self.request = self.app.REQUEST
|
||||
@ -158,14 +158,14 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
||||
view = BulkActionsView(self.context, self.request)
|
||||
self.failIf(view())
|
||||
|
||||
|
||||
def test_retract(self):
|
||||
self.request = self.app.REQUEST
|
||||
self.context = self.portal
|
||||
self.request.set('form.select.BulkAction', 'retract')
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
||||
view = BulkActionsView(self.context, self.request)
|
||||
|
||||
|
||||
self.assertRaises(NotImplementedError,
|
||||
view)
|
||||
|
||||
@ -173,10 +173,10 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
self.request = self.app.REQUEST
|
||||
self.context = self.portal
|
||||
self.request.set('form.select.BulkAction', 'publish')
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
||||
view = BulkActionsView(self.context, self.request)
|
||||
view()
|
||||
|
||||
|
||||
# Count published comments
|
||||
published_comments = 0
|
||||
for r in self.conversation.getThreads():
|
||||
@ -184,17 +184,17 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
|
||||
if workflow_status == 'published':
|
||||
published_comments += 1
|
||||
|
||||
|
||||
# Make sure the comment has been published
|
||||
self.assertEquals(published_comments, 1)
|
||||
|
||||
|
||||
def test_mark_as_spam(self):
|
||||
self.request = self.app.REQUEST
|
||||
self.context = self.portal
|
||||
self.request.set('form.select.BulkAction', 'mark_as_spam')
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
||||
view = BulkActionsView(self.context, self.request)
|
||||
|
||||
|
||||
self.assertRaises(NotImplementedError,
|
||||
view)
|
||||
|
||||
@ -204,14 +204,14 @@ class ModerationBulkActionsViewTest(PloneTestCase):
|
||||
|
||||
# Initially we have three comments
|
||||
self.assertEquals(self.conversation.total_comments, 3)
|
||||
|
||||
|
||||
# Delete two comments with bulk actions
|
||||
self.request.set('form.select.BulkAction', 'delete')
|
||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath()),
|
||||
'/'.join(self.comment3.getPhysicalPath())])
|
||||
'/'.join(self.comment3.getPhysicalPath())])
|
||||
view = BulkActionsView(self.context, self.request)
|
||||
view()
|
||||
|
||||
|
||||
# Make sure that the two comments have been deleted
|
||||
self.assertEquals(self.conversation.total_comments, 1)
|
||||
comment = self.conversation.getComments().next()
|
||||
|
@ -50,9 +50,9 @@ class TestUserNotificationUnit(PloneTestCase):
|
||||
self.portal.MailHost = self.portal._original_MailHost
|
||||
sm = getSiteManager(context=self.portal)
|
||||
sm.unregisterUtility(provided=IMailHost)
|
||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
||||
provided=IMailHost)
|
||||
|
||||
|
||||
def test_notify_user(self):
|
||||
# Add a comment with user notification enabled. Add another comment
|
||||
# and make sure an email is send to the user of the first comment.
|
||||
@ -74,7 +74,7 @@ class TestUserNotificationUnit(PloneTestCase):
|
||||
# We expect the headers to be properly header encoded (7-bit):
|
||||
#>>> 'Subject: =?utf-8?q?Some_t=C3=A4st_subject=2E?=' in msg
|
||||
#True
|
||||
# # The output should be encoded in a reasonable manner
|
||||
# # The output should be encoded in a reasonable manner
|
||||
# (in this case quoted-printable):
|
||||
#>>> msg
|
||||
#'...Another t=C3=A4st message...You are receiving this mail \
|
||||
@ -97,9 +97,9 @@ class TestUserNotificationUnit(PloneTestCase):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
self.conversation.addComment(comment)
|
||||
|
||||
|
||||
self.assertEquals(len(self.mailhost.messages), 0)
|
||||
|
||||
|
||||
def test_do_not_notify_user_when_email_address_is_given(self):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
@ -109,7 +109,7 @@ class TestUserNotificationUnit(PloneTestCase):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
self.conversation.addComment(comment)
|
||||
|
||||
|
||||
self.assertEquals(len(self.mailhost.messages), 0)
|
||||
|
||||
def test_do_not_notify_user_when_no_sender_is_available(self):
|
||||
@ -126,7 +126,7 @@ class TestUserNotificationUnit(PloneTestCase):
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
self.conversation.addComment(comment)
|
||||
|
||||
|
||||
self.assertEquals(len(self.mailhost.messages), 0)
|
||||
|
||||
def test_notify_only_once(self):
|
||||
@ -179,13 +179,13 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
# We need to fake a valid mail setup
|
||||
self.portal.email_from_address = "portal@plone.test"
|
||||
self.mailhost = self.portal.MailHost
|
||||
|
||||
|
||||
# Enable comment moderation
|
||||
self.portal.portal_types['Document'].allow_discussion = True
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
('Discussion Item',),
|
||||
('comment_review_workflow',))
|
||||
|
||||
|
||||
# Enable moderator notification setting
|
||||
registry = queryUtility(IRegistry)
|
||||
registry['plone.app.discussion.interfaces.IDiscussionSettings.' +
|
||||
@ -201,9 +201,9 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
self.portal.MailHost = self.portal._original_MailHost
|
||||
sm = getSiteManager(context=self.portal)
|
||||
sm.unregisterUtility(provided=IMailHost)
|
||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
||||
provided=IMailHost)
|
||||
|
||||
|
||||
def test_notify_moderator(self):
|
||||
# Add a comment and make sure an email is send to the moderator.
|
||||
comment = createObject('plone.Comment')
|
||||
@ -213,7 +213,7 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
self.assertEquals(len(self.mailhost.messages), 1)
|
||||
self.failUnless(self.mailhost.messages[0])
|
||||
msg = self.mailhost.messages[0]
|
||||
|
||||
|
||||
if not isinstance(msg, str):
|
||||
# Plone 3
|
||||
self.failUnless('portal@plone.test' in msg.mfrom)
|
||||
@ -221,17 +221,17 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
else:
|
||||
#Plone 4
|
||||
self.failUnless('To: portal@plone.test' in msg)
|
||||
self.failUnless('From: portal@plone.test' in msg)
|
||||
self.failUnless('From: portal@plone.test' in msg)
|
||||
|
||||
#We expect the headers to be properly header encoded (7-bit):
|
||||
#>>> 'Subject: =?utf-8?q?Some_t=C3=A4st_subject=2E?=' in msg
|
||||
#True
|
||||
|
||||
#The output should be encoded in a reasonable manner (in this case
|
||||
#The output should be encoded in a reasonable manner (in this case
|
||||
# quoted-printable):
|
||||
#>>> msg
|
||||
#'...Another t=C3=A4st message...You are receiving this mail because
|
||||
# T=C3=A4st user\ntest@plone.test...is sending feedback about the site
|
||||
#'...Another t=C3=A4st message...You are receiving this mail because
|
||||
# T=C3=A4st user\ntest@plone.test...is sending feedback about the site
|
||||
# you administer at...
|
||||
|
||||
def test_do_not_notify_moderator_when_no_sender_is_available(self):
|
||||
@ -243,9 +243,9 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
comment.text = 'Comment text'
|
||||
self.conversation.addComment(comment)
|
||||
self.assertEquals(len(self.mailhost.messages), 0)
|
||||
|
||||
|
||||
def test_do_not_notify_moderator_when_notification_is_disabled(self):
|
||||
# Disable moderator notification setting and make sure no email is send
|
||||
# Disable moderator notification setting and make sure no email is send
|
||||
# to the moderator.
|
||||
registry = queryUtility(IRegistry)
|
||||
registry['plone.app.discussion.interfaces.IDiscussionSettings.' +
|
||||
@ -263,7 +263,7 @@ class TestModeratorNotificationUnit(PloneTestCase):
|
||||
self.portal.portal_workflow.setChainForPortalTypes(
|
||||
('Discussion Item',),
|
||||
('one_state_workflow',))
|
||||
|
||||
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
self.conversation.addComment(comment)
|
||||
|
@ -14,8 +14,8 @@ class ToolTest(PloneTestCase):
|
||||
def afterSetUp(self):
|
||||
# First we need to create some content.
|
||||
self.loginAsPortalOwner()
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
self.portal.invokeFactory(id='doc1',
|
||||
title='Document 1',
|
||||
type_name='Document')
|
||||
|
||||
def test_tool_indexing(self):
|
||||
|
@ -23,7 +23,7 @@ class WorkflowSetupTest(PloneTestCase):
|
||||
"""
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
"""Create a document and allow discussion.
|
||||
"""
|
||||
@ -31,13 +31,13 @@ class WorkflowSetupTest(PloneTestCase):
|
||||
self.portal_discussion = self.portal.portal_discussion
|
||||
self.folder.invokeFactory('Document', 'doc1')
|
||||
self.doc = self.folder.doc1
|
||||
|
||||
|
||||
def test_workflows_installed(self):
|
||||
"""Make sure both comment workflows have been installed properly.
|
||||
"""
|
||||
self.failUnless('one_state_workflow' in
|
||||
self.failUnless('one_state_workflow' in
|
||||
self.portal.portal_workflow.objectIds())
|
||||
self.failUnless('comment_review_workflow' in
|
||||
self.failUnless('comment_review_workflow' in
|
||||
self.portal.portal_workflow.objectIds())
|
||||
|
||||
def test_default_workflow(self):
|
||||
@ -46,7 +46,7 @@ class WorkflowSetupTest(PloneTestCase):
|
||||
self.assertEquals(('one_state_workflow',),
|
||||
self.portal.portal_workflow.getChainForPortalType(
|
||||
'Discussion Item'))
|
||||
|
||||
|
||||
def test_review_comments_permission(self):
|
||||
#'Review comments' in self.portal.permissionsOfRole('Admin')
|
||||
|
||||
@ -65,12 +65,12 @@ class PermissionsSetupTest(PloneTestCase):
|
||||
"""
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
portal = self.portal
|
||||
mtool = self.portal.portal_membership
|
||||
self.checkPermission = mtool.checkPermission
|
||||
|
||||
|
||||
def test_reply_to_item_permission_assigned(self):
|
||||
"""Make sure the 'Reply to item' permission is properly assigned.
|
||||
By default this permission is assigned to 'Member' and 'Manager'.
|
||||
@ -96,7 +96,7 @@ class CommentOneStateWorkflowTest(PloneTestCase):
|
||||
"""
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
"""Create a document with comments and enable the one.
|
||||
"""
|
||||
@ -106,31 +106,31 @@ class CommentOneStateWorkflowTest(PloneTestCase):
|
||||
'one_state_workflow')
|
||||
self.folder.invokeFactory('Document', 'doc1')
|
||||
self.doc = self.folder.doc1
|
||||
|
||||
|
||||
# Add a comment
|
||||
conversation = IConversation(self.folder.doc1)
|
||||
comment = createObject('plone.Comment')
|
||||
comment.text = 'Comment text'
|
||||
cid = conversation.addComment(comment)
|
||||
|
||||
|
||||
self.comment = self.folder.doc1.restrictedTraverse(\
|
||||
'++conversation++default/%s' % cid)
|
||||
|
||||
|
||||
self.portal.acl_users._doAddUser('member', 'secret', ['Member'], [])
|
||||
self.portal.acl_users._doAddUser('reviewer', 'secret', ['Reviewer'], [])
|
||||
self.portal.acl_users._doAddUser('manager', 'secret', ['Manager'], [])
|
||||
self.portal.acl_users._doAddUser('editor' , ' secret', ['Editor'],[])
|
||||
self.portal.acl_users._doAddUser('reader', 'secret', ['Reader'], [])
|
||||
|
||||
|
||||
def test_initial_workflow_state(self):
|
||||
"""Make sure the initial workflow state of a comment is 'published'.
|
||||
"""
|
||||
self.assertEqual(self.workflow.getInfoFor(self.doc, 'review_state'),
|
||||
self.assertEqual(self.workflow.getInfoFor(self.doc, 'review_state'),
|
||||
'published')
|
||||
|
||||
|
||||
def test_view_comments(self):
|
||||
"""Make sure published comments can be viewed by everyone.
|
||||
"""
|
||||
"""
|
||||
# Owner is allowed
|
||||
#self.login(default_user)
|
||||
#self.failUnless(checkPerm(View, self.doc))
|
||||
@ -149,14 +149,14 @@ class CommentOneStateWorkflowTest(PloneTestCase):
|
||||
# Reader is allowed
|
||||
self.login('reader')
|
||||
self.failUnless(checkPerm(View, self.comment))
|
||||
|
||||
|
||||
|
||||
class CommentReviewWorkflowTest(PloneTestCase):
|
||||
"""Test the comment_review_workflow that ships with plone.app.discussion.
|
||||
"""
|
||||
|
||||
layer = DiscussionLayer
|
||||
|
||||
|
||||
def afterSetUp(self):
|
||||
# Allow discussion and
|
||||
self.loginAsPortalOwner()
|
||||
@ -217,7 +217,7 @@ class CommentReviewWorkflowTest(PloneTestCase):
|
||||
def test_publish(self):
|
||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
||||
self.portal.REQUEST.form['workflow_action'] = 'publish'
|
||||
self.assertEquals('pending',
|
||||
self.assertEquals('pending',
|
||||
self.portal.portal_workflow.getInfoFor(
|
||||
self.comment, 'review_state'))
|
||||
view = self.comment.restrictedTraverse('@@moderate-publish-comment')
|
||||
|
@ -15,18 +15,18 @@ from OFS.SimpleItem import SimpleItem
|
||||
|
||||
|
||||
class CommentingTool(UniqueObject, SimpleItem):
|
||||
|
||||
|
||||
interface.implements(ICommentingTool)
|
||||
|
||||
|
||||
meta_type = 'plone.app.discussion tool'
|
||||
id = 'portal_discussion'
|
||||
|
||||
|
||||
def reindexObject(self, object):
|
||||
"""Remove from catalog.
|
||||
"""
|
||||
catalog = getToolByName(self, 'portal_catalog')
|
||||
return catalog.reindexObject(object)
|
||||
|
||||
|
||||
indexObject = reindexObject
|
||||
|
||||
def unindexObject(self, object):
|
||||
@ -34,7 +34,7 @@ class CommentingTool(UniqueObject, SimpleItem):
|
||||
"""
|
||||
catalog = getToolByName(self, 'portal_catalog')
|
||||
return catalog.unindexObject(object)
|
||||
|
||||
|
||||
def uniqueValuesFor(self, name):
|
||||
""" return unique values for FieldIndex name """
|
||||
catalog = getToolByName(self, 'portal_catalog')
|
||||
@ -47,14 +47,14 @@ class CommentingTool(UniqueObject, SimpleItem):
|
||||
"""
|
||||
catalog = getToolByName(self, 'portal_catalog')
|
||||
object_provides = [IComment.__identifier__]
|
||||
|
||||
|
||||
if 'object_provides' in kw:
|
||||
kw_provides = kw['object_provides']
|
||||
if isinstance(str, kw_provides):
|
||||
object_provides.append(kw_provides)
|
||||
else:
|
||||
object_provides.extend(kw_provides)
|
||||
|
||||
|
||||
if REQUEST is not None and 'object_provides' in REQUEST.form:
|
||||
rq_provides = REQUEST.form['object_provides']
|
||||
del REQUEST.form['object_provides']
|
||||
@ -62,7 +62,7 @@ class CommentingTool(UniqueObject, SimpleItem):
|
||||
object_provides.append(rq_provides)
|
||||
else:
|
||||
object_provides.extend(rq_provides)
|
||||
|
||||
|
||||
kw['object_provides'] = object_provides
|
||||
return catalog.searchResults(REQUEST, **kw)
|
||||
|
||||
@ -72,7 +72,7 @@ def index_object(obj, event):
|
||||
tool = queryUtility(ICommentingTool)
|
||||
if tool is not None:
|
||||
tool.indexObject(obj)
|
||||
|
||||
|
||||
def unindex_object(obj, event):
|
||||
"""Unindex the object when removed
|
||||
"""
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
|
||||
|
||||
from plone.app.discussion.interfaces import _
|
||||
from plone.app.discussion.interfaces import _
|
||||
|
||||
HAS_CAPTCHA = False
|
||||
try:
|
||||
@ -55,17 +55,17 @@ def captcha_vocabulary(context):
|
||||
SimpleTerm(
|
||||
value='recaptcha',
|
||||
token='recaptcha',
|
||||
title='ReCaptcha'))
|
||||
|
||||
title='ReCaptcha'))
|
||||
|
||||
if HAS_AKISMET: # pragma: no cover
|
||||
terms.append(
|
||||
SimpleTerm(
|
||||
value='akismet',
|
||||
token='akismet',
|
||||
title='Akismet'))
|
||||
|
||||
|
||||
if HAS_NOROBOTS: # pragma: no cover
|
||||
terms.append(
|
||||
terms.append(
|
||||
SimpleTerm(
|
||||
value='norobots',
|
||||
token='norobots',
|
||||
|
Loading…
Reference in New Issue
Block a user