This commit is contained in:
Jens W. Klein 2022-05-01 23:14:09 +02:00
parent e72d86b985
commit 34b758f2bd
38 changed files with 2155 additions and 2179 deletions

View File

@ -24,39 +24,40 @@ import sys
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'repoze.sphinx.autointerface'
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"repoze.sphinx.autointerface",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.txt'
source_suffix = ".txt"
# The encoding of source files.
# source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'plone.app.discussion'
copyright = u'2010, Timo Stollenwerk - Plone Foundation'
project = u"plone.app.discussion"
copyright = u"2010, Timo Stollenwerk - Plone Foundation"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0'
version = "2.0"
# The full version, including alpha/beta/rc tags.
release = '2.0'
release = "2.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -90,7 +91,7 @@ exclude_trees = []
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@ -108,7 +109,7 @@ pygments_style = 'sphinx'
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
html_theme_path = ["_themes"]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@ -129,7 +130,7 @@ html_theme_path = ['_themes']
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
@ -167,7 +168,7 @@ html_static_path = ['_static']
# html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'ploneappdiscussiondoc'
htmlhelp_basename = "ploneappdiscussiondoc"
# -- Options for LaTeX output --------------------------------------------
@ -181,8 +182,13 @@ htmlhelp_basename = 'ploneappdiscussiondoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
u'Timo Stollenwerk', 'manual'),
(
"index",
"ploneappdiscussion.tex",
u"plone.app.discussion Documentation",
u"Timo Stollenwerk",
"manual",
),
]
# The name of an image file (relative to this directory) to place at the top of
@ -204,4 +210,4 @@ latex_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
intersphinx_mapping = {"http://docs.python.org/": None}

View File

@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__import__('pkg_resources').declare_namespace(__name__)
__import__("pkg_resources").declare_namespace(__name__)

View File

@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__import__('pkg_resources').declare_namespace(__name__)
__import__("pkg_resources").declare_namespace(__name__)

View File

@ -2,4 +2,4 @@
from zope.i18nmessageid import MessageFactory
_ = MessageFactory('plone')
_ = MessageFactory("plone")

View File

@ -21,9 +21,9 @@ from zope.publisher.interfaces.browser import IDefaultBrowserLayer
@adapter(Comment)
@interface.implementer(ICaptcha)
class Captcha(Persistent):
"""Captcha input field.
"""
captcha = u''
"""Captcha input field."""
captcha = u""
Captcha = factory(Captcha)
@ -47,22 +47,24 @@ class CaptchaExtender(extensible.FormExtender):
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
self.captcha = settings.captcha
portal_membership = getToolByName(self.context, 'portal_membership')
portal_membership = getToolByName(self.context, "portal_membership")
self.isAnon = portal_membership.isAnonymousUser()
def update(self):
if self.captcha != 'disabled' and self.isAnon:
if self.captcha != "disabled" and self.isAnon:
# Add a captcha field if captcha is enabled in the registry
self.add(ICaptcha, prefix='')
if self.captcha == 'captcha':
self.add(ICaptcha, prefix="")
if self.captcha == "captcha":
from plone.formwidget.captcha import CaptchaFieldWidget
self.form.fields['captcha'].widgetFactory = CaptchaFieldWidget
elif self.captcha == 'recaptcha':
self.form.fields["captcha"].widgetFactory = CaptchaFieldWidget
elif self.captcha == "recaptcha":
from plone.formwidget.recaptcha import ReCaptchaFieldWidget
self.form.fields['captcha'].widgetFactory = \
ReCaptchaFieldWidget
elif self.captcha == 'norobots':
self.form.fields["captcha"].widgetFactory = ReCaptchaFieldWidget
elif self.captcha == "norobots":
from collective.z3cform.norobots import NorobotsFieldWidget
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
self.form.fields["captcha"].widgetFactory = NorobotsFieldWidget
else:
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
self.form.fields["captcha"].mode = interfaces.HIDDEN_MODE

View File

@ -37,8 +37,7 @@ class View(BrowserView):
context = aq_inner(self.context)
registry = getUtility(IRegistry)
view_action_types = registry.get(
'plone.types_use_view_action_in_listings', [])
view_action_types = registry.get("plone.types_use_view_action_in_listings", [])
obj = aq_parent(aq_parent(context))
url = obj.absolute_url()
@ -49,33 +48,34 @@ class View(BrowserView):
will redirect right to the binary object, bypassing comments.
"""
if obj.portal_type in view_action_types:
url = '{0}/view'.format(url)
url = "{0}/view".format(url)
self.request.response.redirect('{0}#{1}'.format(url, context.id))
self.request.response.redirect("{0}#{1}".format(url, context.id))
class EditCommentForm(CommentForm):
"""Form to edit an existing comment."""
ignoreContext = True
id = 'edit-comment-form'
label = _(u'edit_comment_form_title', default=u'Edit comment')
id = "edit-comment-form"
label = _(u"edit_comment_form_title", default=u"Edit comment")
def updateWidgets(self):
super(EditCommentForm, self).updateWidgets()
self.widgets['text'].value = self.context.text
self.widgets["text"].value = self.context.text
# We have to rename the id, otherwise TinyMCE can't initialize
# because there are two textareas with the same id.
self.widgets['text'].id = 'overlay-comment-text'
self.widgets["text"].id = "overlay-comment-text"
def _redirect(self, target=''):
def _redirect(self, target=""):
if not target:
portal_state = getMultiAdapter((self.context, self.request),
name=u'plone_portal_state')
portal_state = getMultiAdapter(
(self.context, self.request), name=u"plone_portal_state"
)
target = portal_state.portal_url()
self.request.response.redirect(target)
@button.buttonAndHandler(_(u'label_save',
default=u'Save'), name='comment')
@button.buttonAndHandler(_(u"label_save", default=u"Save"), name="comment")
def handleComment(self, action):
# Validate form
@ -84,32 +84,28 @@ class EditCommentForm(CommentForm):
return
# Check permissions
can_edit = getSecurityManager().checkPermission(
'Edit comments',
self.context)
mtool = getToolByName(self.context, 'portal_membership')
can_edit = getSecurityManager().checkPermission("Edit comments", self.context)
mtool = getToolByName(self.context, "portal_membership")
if mtool.isAnonymousUser() or not can_edit:
return
# Update text
self.context.text = data['text']
self.context.text = data["text"]
# Notify that the object has been modified
notify(ObjectModifiedEvent(self.context))
# Redirect to comment
IStatusMessage(self.request).add(_(u'comment_edit_notification',
default='Comment was edited'),
type='info')
return self._redirect(
target=self.action.replace('@@edit-comment', '@@view'))
IStatusMessage(self.request).add(
_(u"comment_edit_notification", default="Comment was edited"), type="info"
)
return self._redirect(target=self.action.replace("@@edit-comment", "@@view"))
@button.buttonAndHandler(_(u'cancel_form_button',
default=u'Cancel'), name='cancel')
@button.buttonAndHandler(_(u"cancel_form_button", default=u"Cancel"), name="cancel")
def handle_cancel(self, action):
IStatusMessage(self.request).add(
_(u'comment_edit_cancel_notification',
default=u'Edit comment cancelled'),
type='info')
_(u"comment_edit_cancel_notification", default=u"Edit comment cancelled"),
type="info",
)
return self._redirect(target=self.context.absolute_url())

View File

@ -35,28 +35,28 @@ from zope.interface import alsoProvides
COMMENT_DESCRIPTION_PLAIN_TEXT = _(
u'comment_description_plain_text',
default=u'You can add a comment by filling out the form below. '
u'Plain text formatting.',
u"comment_description_plain_text",
default=u"You can add a comment by filling out the form below. "
u"Plain text formatting.",
)
COMMENT_DESCRIPTION_MARKDOWN = _(
u'comment_description_markdown',
default=u'You can add a comment by filling out the form below. '
u'Plain text formatting. You can use the Markdown syntax for '
u'links and images.',
u"comment_description_markdown",
default=u"You can add a comment by filling out the form below. "
u"Plain text formatting. You can use the Markdown syntax for "
u"links and images.",
)
COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
u'comment_description_intelligent_text',
default=u'You can add a comment by filling out the form below. '
u'Plain text formatting. Web and email addresses are '
u'transformed into clickable links.',
u"comment_description_intelligent_text",
default=u"You can add a comment by filling out the form below. "
u"Plain text formatting. Web and email addresses are "
u"transformed into clickable links.",
)
COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
u'comment_description_moderation_enabled',
default=u'Comments are moderated.',
u"comment_description_moderation_enabled",
default=u"Comments are moderated.",
)
@ -64,30 +64,31 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
ignoreContext = True # don't use context to get widget data
id = None
label = _(u'Add a comment')
fields = field.Fields(IComment).omit('portal_type',
'__parent__',
'__name__',
'comment_id',
'mime_type',
'creator',
'creation_date',
'modification_date',
'author_username',
'title')
label = _(u"Add a comment")
fields = field.Fields(IComment).omit(
"portal_type",
"__parent__",
"__name__",
"comment_id",
"mime_type",
"creator",
"creation_date",
"modification_date",
"author_username",
"title",
)
def updateFields(self):
super(CommentForm, self).updateFields()
self.fields['user_notification'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields["user_notification"].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
# Widgets
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
self.widgets['text'].addClass('autoresize')
self.widgets['user_notification'].label = _(u'')
self.widgets["in_reply_to"].mode = interfaces.HIDDEN_MODE
self.widgets["text"].addClass("autoresize")
self.widgets["user_notification"].label = _(u"")
# Reset widget field settings to their defaults, which may be changed
# further on. Otherwise, the email field might get set to required
# when an anonymous user visits, and then remain required when an
@ -97,19 +98,19 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# would have no effect until the instance was restarted. Note that the
# widget is new each time, but the field is the same item in memory as
# the previous time.
self.widgets['author_email'].field.required = False
self.widgets["author_email"].field.required = False
# The widget is new, but its 'required' setting is based on the
# previous value on the field, so we need to reset it here. Changing
# the field in updateFields does not help.
self.widgets['author_email'].required = False
self.widgets["author_email"].required = False
# Rename the id of the text widgets because there can be css-id
# clashes with the text field of documents when using and overlay
# with TinyMCE.
self.widgets['text'].id = 'form-widgets-comment-text'
self.widgets["text"].id = "form-widgets-comment-text"
# Anonymous / Logged-in
mtool = getToolByName(self.context, 'portal_membership')
mtool = getToolByName(self.context, "portal_membership")
anon = mtool.isAnonymousUser()
registry = queryUtility(IRegistry)
@ -119,52 +120,51 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
if settings.anonymous_email_enabled:
# according to IDiscussionSettings.anonymous_email_enabled:
# 'If selected, anonymous user will have to give their email.'
self.widgets['author_email'].field.required = True
self.widgets['author_email'].required = True
self.widgets["author_email"].field.required = True
self.widgets["author_email"].required = True
else:
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
self.widgets["author_email"].mode = interfaces.HIDDEN_MODE
else:
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
self.widgets["author_name"].mode = interfaces.HIDDEN_MODE
self.widgets["author_email"].mode = interfaces.HIDDEN_MODE
member = mtool.getAuthenticatedMember()
member_email = member.getProperty('email')
member_email = member.getProperty("email")
# Hide the user_notification checkbox if user notification is disabled
# or the user is not logged in. Also check if the user has a valid
# email address
member_email_is_empty = member_email == ''
member_email_is_empty = member_email == ""
user_notification_disabled = not settings.user_notification_enabled
if member_email_is_empty or user_notification_disabled or anon:
self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
self.widgets["user_notification"].mode = interfaces.HIDDEN_MODE
def updateActions(self):
super(CommentForm, self).updateActions()
self.actions['cancel'].addClass('btn btn-secondary')
self.actions['cancel'].addClass('hide')
self.actions['comment'].addClass('btn btn-primary')
self.actions["cancel"].addClass("btn btn-secondary")
self.actions["cancel"].addClass("hide")
self.actions["comment"].addClass("btn btn-primary")
def get_author(self, data):
context = aq_inner(self.context)
# some attributes are not always set
author_name = u''
author_name = u""
# Make sure author_name/ author_email is properly encoded
if 'author_name' in data:
author_name = safe_unicode(data['author_name'])
if 'author_email' in data:
author_email = safe_unicode(data['author_email'])
if "author_name" in data:
author_name = safe_unicode(data["author_name"])
if "author_email" in data:
author_email = safe_unicode(data["author_email"])
# Set comment author properties for anonymous users or members
portal_membership = getToolByName(context, 'portal_membership')
portal_membership = getToolByName(context, "portal_membership")
anon = portal_membership.isAnonymousUser()
if not anon and getSecurityManager().checkPermission(
'Reply to item', context):
if not anon and getSecurityManager().checkPermission("Reply to item", context):
# Member
member = portal_membership.getAuthenticatedMember()
email = safe_unicode(member.getProperty('email'))
fullname = member.getProperty('fullname')
if not fullname or fullname == '':
email = safe_unicode(member.getProperty("email"))
fullname = member.getProperty("fullname")
if not fullname or fullname == "":
fullname = member.getUserName()
fullname = safe_unicode(fullname)
author_name = fullname
@ -179,7 +179,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
def create_comment(self, data):
context = aq_inner(self.context)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
@ -200,42 +200,44 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
comment.author_name, comment.author_email = self.get_author(data)
# Set comment author properties for anonymous users or members
portal_membership = getToolByName(context, 'portal_membership')
portal_membership = getToolByName(context, "portal_membership")
anon = portal_membership.isAnonymousUser()
if anon and anonymous_comments:
# Anonymous Users
comment.user_notification = None
elif not anon and getSecurityManager().checkPermission(
'Reply to item', context):
"Reply to item", context
):
# Member
member = portal_membership.getAuthenticatedMember()
memberid = member.getId()
user = member.getUser()
comment.changeOwnership(user, recursive=False)
comment.manage_setLocalRoles(memberid, ['Owner'])
comment.manage_setLocalRoles(memberid, ["Owner"])
comment.creator = memberid
comment.author_username = memberid
else: # pragma: no cover
raise Unauthorized(
u'Anonymous user tries to post a comment, but anonymous '
u'commenting is disabled. Or user does not have the '
u"Anonymous user tries to post a comment, but anonymous "
u"commenting is disabled. Or user does not have the "
u"'reply to item' permission.",
)
return comment
@button.buttonAndHandler(_(u'add_comment_button', default=u'Comment'),
name='comment')
@button.buttonAndHandler(
_(u"add_comment_button", default=u"Comment"), name="comment"
)
def handleComment(self, action):
context = aq_inner(self.context)
# Check if conversation is enabled on this content object
if not self.__parent__.restrictedTraverse(
'@@conversation_view',
"@@conversation_view",
).enabled():
raise Unauthorized(
'Discussion is not enabled for this content object.',
"Discussion is not enabled for this content object.",
)
# Validation form
@ -246,28 +248,26 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# Validate Captcha
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
portal_membership = getToolByName(self.context, 'portal_membership')
captcha_enabled = settings.captcha != 'disabled'
portal_membership = getToolByName(self.context, "portal_membership")
captcha_enabled = settings.captcha != "disabled"
anonymous_comments = settings.anonymous_comments
anon = portal_membership.isAnonymousUser()
if captcha_enabled and anonymous_comments and anon:
if 'captcha' not in data:
data['captcha'] = u''
captcha = CaptchaValidator(self.context,
self.request,
None,
ICaptcha['captcha'],
None)
captcha.validate(data['captcha'])
if "captcha" not in data:
data["captcha"] = u""
captcha = CaptchaValidator(
self.context, self.request, None, ICaptcha["captcha"], None
)
captcha.validate(data["captcha"])
# Create comment
comment = self.create_comment(data)
# Add comment to conversation
conversation = IConversation(self.__parent__)
if data['in_reply_to']:
if data["in_reply_to"]:
# Add a reply to an existing comment
conversation_to_reply_to = conversation.get(data['in_reply_to'])
conversation_to_reply_to = conversation.get(data["in_reply_to"])
replies = IReplies(conversation_to_reply_to)
comment_id = replies.addComment(comment)
else:
@ -279,25 +279,24 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# 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',
context)
workflowTool = getToolByName(context, 'portal_workflow')
can_review = getSecurityManager().checkPermission("Review comments", context)
workflowTool = getToolByName(context, "portal_workflow")
comment_review_state = workflowTool.getInfoFor(
comment,
'review_state',
"review_state",
None,
)
if comment_review_state == 'pending' and not can_review:
if comment_review_state == "pending" and not can_review:
# Show info message when comment moderation is enabled
IStatusMessage(self.context.REQUEST).addStatusMessage(
_('Your comment awaits moderator approval.'),
type='info')
_("Your comment awaits moderator approval."), type="info"
)
self.request.response.redirect(self.action)
else:
# Redirect to comment (inside a content object page)
self.request.response.redirect(self.action + '#' + str(comment_id))
self.request.response.redirect(self.action + "#" + str(comment_id))
@button.buttonAndHandler(_(u'Cancel'))
@button.buttonAndHandler(_(u"Cancel"))
def handleCancel(self, action):
# This method should never be called, it's only there to show
# a cancel button that is handled by a jQuery method.
@ -307,15 +306,15 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
class CommentsViewlet(ViewletBase):
form = CommentForm
index = ViewPageTemplateFile('comments.pt')
index = ViewPageTemplateFile("comments.pt")
def update(self):
super(CommentsViewlet, self).update()
discussion_allowed = self.is_discussion_allowed()
anonymous_allowed_or_can_reply = (
self.is_anonymous() and
self.anonymous_discussion_allowed() or
self.can_reply()
self.is_anonymous()
and self.anonymous_discussion_allowed()
or self.can_reply()
)
if discussion_allowed and anonymous_allowed_or_can_reply:
z2.switch_on(self, request_layer=IFormLayer)
@ -326,10 +325,10 @@ class CommentsViewlet(ViewletBase):
# view methods
def can_reply(self):
"""Returns true if current user has the 'Reply to item' permission.
"""
return getSecurityManager().checkPermission('Reply to item',
aq_inner(self.context))
"""Returns true if current user has the 'Reply to item' permission."""
return getSecurityManager().checkPermission(
"Reply to item", aq_inner(self.context)
)
def can_manage(self):
"""We keep this method for <= 1.0b9 backward compatibility. Since we do
@ -338,18 +337,17 @@ class CommentsViewlet(ViewletBase):
return self.can_review()
def can_review(self):
"""Returns true if current user has the 'Review comments' permission.
"""
return getSecurityManager().checkPermission('Review comments',
aq_inner(self.context))
"""Returns true if current user has the 'Review comments' permission."""
return getSecurityManager().checkPermission(
"Review comments", aq_inner(self.context)
)
def can_delete_own(self, comment):
"""Returns true if the current user can delete the comment. Only
comments without replies can be deleted.
"""
try:
return comment.restrictedTraverse(
'@@delete-own-comment').can_delete()
return comment.restrictedTraverse("@@delete-own-comment").can_delete()
except Unauthorized:
return False
@ -358,8 +356,7 @@ class CommentsViewlet(ViewletBase):
no replies. This is used to prepare hidden form buttons for JS.
"""
try:
return comment.restrictedTraverse(
'@@delete-own-comment').could_delete()
return comment.restrictedTraverse("@@delete-own-comment").could_delete()
except Unauthorized:
return False
@ -367,19 +364,17 @@ class CommentsViewlet(ViewletBase):
"""Returns true if current user has the 'Edit comments'
permission.
"""
return getSecurityManager().checkPermission('Edit comments',
aq_inner(reply))
return getSecurityManager().checkPermission("Edit comments", aq_inner(reply))
def can_delete(self, reply):
"""Returns true if current user has the 'Delete comments'
permission.
"""
return getSecurityManager().checkPermission('Delete comments',
aq_inner(reply))
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
def is_discussion_allowed(self):
context = aq_inner(self.context)
return context.restrictedTraverse('@@conversation_view').enabled()
return context.restrictedTraverse("@@conversation_view").enabled()
def comment_transform_message(self):
"""Returns the description that shows up above the comment text,
@ -391,34 +386,41 @@ class CommentsViewlet(ViewletBase):
settings = registry.forInterface(IDiscussionSettings, check=False)
# text transform setting
if settings.text_transform == 'text/x-web-intelligent':
message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT),
context=self.request)
elif settings.text_transform == 'text/x-web-markdown':
message = translate(Message(COMMENT_DESCRIPTION_MARKDOWN),
context=self.request)
if settings.text_transform == "text/x-web-intelligent":
message = translate(
Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT), context=self.request
)
elif settings.text_transform == "text/x-web-markdown":
message = translate(
Message(COMMENT_DESCRIPTION_MARKDOWN), context=self.request
)
else:
message = translate(Message(COMMENT_DESCRIPTION_PLAIN_TEXT),
context=self.request)
message = translate(
Message(COMMENT_DESCRIPTION_PLAIN_TEXT), context=self.request
)
# comment workflow
wftool = getToolByName(context, 'portal_workflow', None)
workflow_chain = wftool.getChainForPortalType('Discussion Item')
wftool = getToolByName(context, "portal_workflow", None)
workflow_chain = wftool.getChainForPortalType("Discussion Item")
if workflow_chain:
comment_workflow = workflow_chain[0]
comment_workflow = wftool[comment_workflow]
# check if the current workflow implements a pending state. If this
# is true comments are moderated
if 'pending' in comment_workflow.states:
message = message + ' ' + \
translate(Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
context=self.request)
if "pending" in comment_workflow.states:
message = (
message
+ " "
+ translate(
Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
context=self.request,
)
)
return message
def has_replies(self, workflow_actions=False):
"""Returns true if there are replies.
"""
"""Returns true if there are replies."""
if self.get_replies(workflow_actions) is not None:
try:
next(self.get_replies(workflow_actions))
@ -442,31 +444,32 @@ class CommentsViewlet(ViewletBase):
if conversation is None:
return iter([])
wf = getToolByName(context, 'portal_workflow')
wf = getToolByName(context, "portal_workflow")
# workflow_actions is only true when user
# has 'Manage portal' permission
def replies_with_workflow_actions():
# Generator that returns replies dict with workflow actions
for r in conversation.getThreads():
comment_obj = r['comment']
comment_obj = r["comment"]
# list all possible workflow actions
actions = [
a for a in wf.listActionInfos(object=comment_obj)
if a['category'] == 'workflow' and a['allowed']
a
for a in wf.listActionInfos(object=comment_obj)
if a["category"] == "workflow" and a["allowed"]
]
r = r.copy()
r['actions'] = actions
r["actions"] = actions
yield r
def published_replies():
# Generator that returns replies dict with workflow status.
for r in conversation.getThreads():
comment_obj = r['comment']
workflow_status = wf.getInfoFor(comment_obj, 'review_state')
if workflow_status == 'published':
comment_obj = r["comment"]
workflow_status = wf.getInfoFor(comment_obj, "review_state")
if workflow_status == "published":
r = r.copy()
r['workflow_status'] = workflow_status
r["workflow_status"] = workflow_status
yield r
# Return all direct replies
@ -480,20 +483,16 @@ class CommentsViewlet(ViewletBase):
if username is None:
return None
else:
return '{0}/author/{1}'.format(self.context.portal_url(), username)
return "{0}/author/{1}".format(self.context.portal_url(), username)
def get_commenter_portrait(self, username=None):
if username is None:
# return the default user image if no username is given
return 'defaultUser.png'
return "defaultUser.png"
else:
portal_membership = getToolByName(self.context,
'portal_membership',
None)
return portal_membership\
.getPersonalPortrait(username)\
.absolute_url()
portal_membership = getToolByName(self.context, "portal_membership", None)
return portal_membership.getPersonalPortrait(username).absolute_url()
def anonymous_discussion_allowed(self):
# Check if anonymous comments are allowed in the registry
@ -520,20 +519,18 @@ class CommentsViewlet(ViewletBase):
return settings.show_commenter_image
def is_anonymous(self):
portal_membership = getToolByName(self.context,
'portal_membership',
None)
portal_membership = getToolByName(self.context, "portal_membership", None)
return portal_membership.isAnonymousUser()
def login_action(self):
return '{0}/login_form?came_from={1}'.format(
return "{0}/login_form?came_from={1}".format(
self.navigation_root_url,
quote(self.request.get('URL', '')),
quote(self.request.get("URL", "")),
)
def format_time(self, time):
# We have to transform Python datetime into Zope DateTime
# before we can call toLocalizedTime.
util = getToolByName(self.context, 'translation_service')
util = getToolByName(self.context, "translation_service")
zope_time = DateTime(time.isoformat())
return util.toLocalizedTime(zope_time, long_format=True)

View File

@ -28,42 +28,40 @@ except ImportError:
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
"""Discussion settings form.
"""
"""Discussion settings form."""
schema = IDiscussionSettings
id = 'DiscussionSettingsEditForm'
label = _(u'Discussion settings')
id = "DiscussionSettingsEditForm"
label = _(u"Discussion settings")
description = _(
u'help_discussion_settings_editform',
default=u'Some discussion related settings are not '
u'located in the Discussion Control Panel.\n'
u'To enable comments for a specific content type, '
u'go to the Types Control Panel of this type and '
u"help_discussion_settings_editform",
default=u"Some discussion related settings are not "
u"located in the Discussion Control Panel.\n"
u"To enable comments for a specific content type, "
u"go to the Types Control Panel of this type and "
u'choose "Allow comments".\n'
u'To enable the moderation workflow for comments, '
u'go to the Types Control Panel, choose '
u"To enable the moderation workflow for comments, "
u"go to the Types Control Panel, choose "
u'"Comment" and set workflow to '
u'"Comment Review Workflow".',
)
def updateFields(self):
super(DiscussionSettingsEditForm, self).updateFields()
self.fields['globally_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['moderation_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['edit_comment_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['delete_own_comment_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['anonymous_comments'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['moderator_notification_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['user_notification_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields["globally_enabled"].widgetFactory = SingleCheckBoxFieldWidget
self.fields["moderation_enabled"].widgetFactory = SingleCheckBoxFieldWidget
self.fields["edit_comment_enabled"].widgetFactory = SingleCheckBoxFieldWidget
self.fields[
"delete_own_comment_enabled"
].widgetFactory = SingleCheckBoxFieldWidget
self.fields["anonymous_comments"].widgetFactory = SingleCheckBoxFieldWidget
self.fields["show_commenter_image"].widgetFactory = SingleCheckBoxFieldWidget
self.fields[
"moderator_notification_enabled"
].widgetFactory = SingleCheckBoxFieldWidget
self.fields[
"user_notification_enabled"
].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self):
try:
@ -73,33 +71,31 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
# provide auto-upgrade
update_registry(self.context)
super(DiscussionSettingsEditForm, self).updateWidgets()
self.widgets['globally_enabled'].label = _(u'Enable Comments')
self.widgets['anonymous_comments'].label = _(u'Anonymous Comments')
self.widgets['show_commenter_image'].label = _(u'Commenter Image')
self.widgets['moderator_notification_enabled'].label = _(
u'Moderator Email Notification',
self.widgets["globally_enabled"].label = _(u"Enable Comments")
self.widgets["anonymous_comments"].label = _(u"Anonymous Comments")
self.widgets["show_commenter_image"].label = _(u"Commenter Image")
self.widgets["moderator_notification_enabled"].label = _(
u"Moderator Email Notification",
)
self.widgets['user_notification_enabled'].label = _(
u'User Email Notification',
self.widgets["user_notification_enabled"].label = _(
u"User Email Notification",
)
@button.buttonAndHandler(_('Save'), name=None)
@button.buttonAndHandler(_("Save"), name=None)
def handleSave(self, action):
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
self.applyChanges(data)
IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'),
'info')
self.context.REQUEST.RESPONSE.redirect('@@discussion-controlpanel')
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info")
self.context.REQUEST.RESPONSE.redirect("@@discussion-controlpanel")
@button.buttonAndHandler(_('Cancel'), name='cancel')
@button.buttonAndHandler(_("Cancel"), name="cancel")
def handleCancel(self, action):
IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'),
'info')
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"), "info")
self.request.response.redirect(
'{0}/{1}'.format(
"{0}/{1}".format(
self.context.absolute_url(),
self.control_panel_view,
),
@ -107,10 +103,10 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
"""Discussion settings control panel.
"""
"""Discussion settings control panel."""
form = DiscussionSettingsEditForm
index = ViewPageTemplateFile('controlpanel.pt')
index = ViewPageTemplateFile("controlpanel.pt")
def __call__(self):
self.mailhost_warning()
@ -130,39 +126,40 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
"""
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
wftool = getToolByName(self.context, 'portal_workflow', None)
workflow_chain = wftool.getChainForPortalType('Discussion Item')
wftool = getToolByName(self.context, "portal_workflow", None)
workflow_chain = wftool.getChainForPortalType("Discussion Item")
output = []
# Globally enabled
if settings.globally_enabled:
output.append('globally_enabled')
output.append("globally_enabled")
# Comment moderation
one_state_worklow_disabled = \
'comment_one_state_workflow' not in workflow_chain
comment_review_workflow_disabled = \
'comment_review_workflow' not in workflow_chain
one_state_worklow_disabled = "comment_one_state_workflow" not in workflow_chain
comment_review_workflow_disabled = (
"comment_review_workflow" not in workflow_chain
)
if one_state_worklow_disabled and comment_review_workflow_disabled:
output.append('moderation_custom')
output.append("moderation_custom")
elif settings.moderation_enabled:
output.append('moderation_enabled')
output.append("moderation_enabled")
if settings.edit_comment_enabled:
output.append('edit_comment_enabled')
output.append("edit_comment_enabled")
if settings.delete_own_comment_enabled:
output.append('delete_own_comment_enabled')
output.append("delete_own_comment_enabled")
# Anonymous comments
if settings.anonymous_comments:
output.append('anonymous_comments')
output.append("anonymous_comments")
# Invalid mail setting
ctrlOverview = getMultiAdapter((self.context, self.request),
name='overview-controlpanel')
ctrlOverview = getMultiAdapter(
(self.context, self.request), name="overview-controlpanel"
)
if ctrlOverview.mailhost_warning():
output.append('invalid_mail_setup')
output.append("invalid_mail_setup")
# Workflow
if workflow_chain:
@ -170,69 +167,71 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
output.append(discussion_workflow)
# Merge all settings into one string
return ' '.join(output)
return " ".join(output)
def mailhost_warning(self):
"""Returns true if mailhost is not configured properly.
"""
"""Returns true if mailhost is not configured properly."""
# Copied from Products.CMFPlone/controlpanel/browser/overview.py
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
mailhost = mail_settings.smtp_host
email = mail_settings.email_from_address
if mailhost and email:
pass
else:
message = _(u'discussion_text_no_mailhost_configured',
default=u'You have not configured a mail host or a site \'From\' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.') # noqa: E501
IStatusMessage(self.request).addStatusMessage(message, 'warning')
message = _(
u"discussion_text_no_mailhost_configured",
default=u"You have not configured a mail host or a site 'From' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.",
) # noqa: E501
IStatusMessage(self.request).addStatusMessage(message, "warning")
def custom_comment_workflow_warning(self):
"""Return True if a custom comment workflow is enabled."""
wftool = getToolByName(self.context, 'portal_workflow', None)
workflow_chain = wftool.getChainForPortalType('Discussion Item')
one_state_workflow_enabled = \
'comment_one_state_workflow' in workflow_chain
comment_review_workflow_enabled = \
'comment_review_workflow' in workflow_chain
wftool = getToolByName(self.context, "portal_workflow", None)
workflow_chain = wftool.getChainForPortalType("Discussion Item")
one_state_workflow_enabled = "comment_one_state_workflow" in workflow_chain
comment_review_workflow_enabled = "comment_review_workflow" in workflow_chain
if one_state_workflow_enabled or comment_review_workflow_enabled:
pass
else:
message = _(u'discussion_text_custom_comment_workflow',
default=u'You have configured a custom workflow for the \'Discussion Item\' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default \'Discussion Item\' workflows. Go to the Types control panel to choose a workflow for the \'Discussion Item\' type.') # noqa: E501
IStatusMessage(self.request).addStatusMessage(message, 'warning')
message = _(
u"discussion_text_custom_comment_workflow",
default=u"You have configured a custom workflow for the 'Discussion Item' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default 'Discussion Item' workflows. Go to the Types control panel to choose a workflow for the 'Discussion Item' type.",
) # noqa: E501
IStatusMessage(self.request).addStatusMessage(message, "warning")
def notify_configuration_changed(event):
"""Event subscriber that is called every time the configuration changed.
"""
"""Event subscriber that is called every time the configuration changed."""
portal = getSite()
wftool = getToolByName(portal, 'portal_workflow', None)
wftool = getToolByName(portal, "portal_workflow", None)
if IRecordModifiedEvent.providedBy(event):
# Discussion control panel setting changed
if event.record.fieldName == 'moderation_enabled':
if event.record.fieldName == "moderation_enabled":
# Moderation enabled has changed
if event.record.value is 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',),
'comment_one_state_workflow')
wftool.setChainForPortalTypes(
("Discussion Item",), "comment_one_state_workflow"
)
if IConfigurationChangedEvent.providedBy(event):
# Types control panel setting changed
if 'workflow' in event.data:
if "workflow" in event.data:
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
workflow_chain = wftool.getChainForPortalType('Discussion Item')
workflow_chain = wftool.getChainForPortalType("Discussion Item")
if workflow_chain:
workflow = workflow_chain[0]
if workflow == 'comment_one_state_workflow':
if workflow == "comment_one_state_workflow":
settings.moderation_enabled = False
elif workflow == 'comment_review_workflow':
elif workflow == "comment_review_workflow":
settings.moderation_enabled = True
else:
# Custom workflow

View File

@ -14,6 +14,7 @@ from zope.component import queryUtility
try:
from plone.dexterity.interfaces import IDexterityContent
DEXTERITY_INSTALLED = True
except ImportError:
DEXTERITY_INSTALLED = False
@ -26,15 +27,14 @@ def traverse_parents(context):
if not IPloneSiteRoot.providedBy(obj):
obj_is_folderish = IFolderish.providedBy(obj)
obj_is_stuctural = not INonStructuralFolder.providedBy(obj)
if (obj_is_folderish and obj_is_stuctural):
flag = getattr(obj, 'allow_discussion', None)
if obj_is_folderish and obj_is_stuctural:
flag = getattr(obj, "allow_discussion", None)
if flag is not None:
return flag
return None
class ConversationView(object):
def enabled(self):
if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context):
return self._enabled_for_dexterity_types()
@ -82,7 +82,7 @@ class ConversationView(object):
return False
# If discussion is disabled for the object, bail out
obj_flag = getattr(aq_base(context), 'allow_discussion', None)
obj_flag = getattr(aq_base(context), "allow_discussion", None)
if obj_flag is False:
return False
@ -91,16 +91,16 @@ class ConversationView(object):
folder_allow_discussion = traverse_parents(context)
if folder_allow_discussion:
if not getattr(self, 'allow_discussion', None):
if not getattr(self, "allow_discussion", None):
return True
else:
if obj_flag:
return True
# Check if discussion is allowed on the content type
portal_types = getToolByName(self, 'portal_types')
portal_types = getToolByName(self, "portal_types")
document_fti = getattr(portal_types, context.portal_type)
if not document_fti.getProperty('allow_discussion'):
if not document_fti.getProperty("allow_discussion"):
# If discussion is not allowed on the content type,
# check if 'allow discussion' is overridden on the content object.
if not obj_flag:
@ -134,11 +134,11 @@ class ConversationView(object):
return False
# Check if discussion is allowed on the content object
if safe_hasattr(context, 'allow_discussion'):
if safe_hasattr(context, "allow_discussion"):
if context.allow_discussion is not None:
return context.allow_discussion
# Check if discussion is allowed on the content type
portal_types = getToolByName(self, 'portal_types')
portal_types = getToolByName(self, "portal_types")
document_fti = getattr(portal_types, context.portal_type)
return document_fti.getProperty('allow_discussion')
return document_fti.getProperty("allow_discussion")

View File

@ -18,21 +18,20 @@ from zope.event import notify
# Translations for generated values in buttons
# States
_('comment_pending', default='pending')
_("comment_pending", default="pending")
# _('comment_approved', default='published')
_('comment_published', default='published')
_('comment_rejected', default='rejected')
_('comment_spam', default='marked as spam')
_("comment_published", default="published")
_("comment_rejected", default="rejected")
_("comment_spam", default="marked as spam")
# Transitions
_('Recall')
_('Approve')
_('Reject')
_('Spam')
_("Recall")
_("Approve")
_("Reject")
_("Spam")
PMF = _
class TranslationHelper(BrowserView):
def translate(self, text=""):
return _(text)
@ -44,22 +43,21 @@ class TranslationHelper(BrowserView):
class View(BrowserView):
"""Show comment moderation view."""
template = ViewPageTemplateFile('moderation.pt')
template = ViewPageTemplateFile("moderation.pt")
try:
template.id = '@@moderate-comments'
template.id = "@@moderate-comments"
except AttributeError:
# id is not writeable in Zope 2.12
pass
def __init__(self, context, request):
super(View, self).__init__(context, request)
self.workflowTool = getToolByName(self.context, 'portal_workflow')
self.workflowTool = getToolByName(self.context, "portal_workflow")
self.transitions = []
def __call__(self):
self.request.set('disable_border', True)
self.request.set('review_state',
self.request.get('review_state', 'pending'))
self.request.set("disable_border", True)
self.request.set("review_state", self.request.get("review_state", "pending"))
return self.template()
def comments(self):
@ -67,15 +65,19 @@ class View(BrowserView):
review_state is string or list of strings.
"""
catalog = getToolByName(self.context, 'portal_catalog')
if self.request.review_state == 'all':
return catalog(object_provides=IComment.__identifier__,
sort_on='created',
sort_order='reverse')
return catalog(object_provides=IComment.__identifier__,
catalog = getToolByName(self.context, "portal_catalog")
if self.request.review_state == "all":
return catalog(
object_provides=IComment.__identifier__,
sort_on="created",
sort_order="reverse",
)
return catalog(
object_provides=IComment.__identifier__,
review_state=self.request.review_state,
sort_on='created',
sort_order='reverse')
sort_on="created",
sort_order="reverse",
)
def moderation_enabled(self):
"""Return true if a review workflow is enabled on 'Discussion Item'
@ -84,11 +86,10 @@ class View(BrowserView):
A 'review workflow' is characterized by implementing a 'pending'
workflow state.
"""
workflows = self.workflowTool.getChainForPortalType(
'Discussion Item')
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
if workflows:
comment_workflow = self.workflowTool[workflows[0]]
if 'pending' in comment_workflow.states:
if "pending" in comment_workflow.states:
return True
return False
@ -100,11 +101,10 @@ class View(BrowserView):
A 'review multipe state workflow' is characterized by implementing
a 'rejected' workflow state and a 'spam' workflow state.
"""
workflows = self.workflowTool.getChainForPortalType(
'Discussion Item')
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
if workflows:
comment_workflow = self.workflowTool[workflows[0]]
if 'spam' in comment_workflow.states:
if "spam" in comment_workflow.states:
return True
return False
@ -125,27 +125,26 @@ class View(BrowserView):
"""
if obj:
transitions = [
a for a in self.workflowTool.listActionInfos(object=obj)
if a['category'] == 'workflow' and a['allowed']
a
for a in self.workflowTool.listActionInfos(object=obj)
if a["category"] == "workflow" and a["allowed"]
]
return transitions
class ModerateCommentsEnabled(BrowserView):
def __call__(self):
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
content type. A 'review workflow' is characterized by implementing
a 'pending' workflow state.
"""
context = aq_inner(self.context)
workflowTool = getToolByName(context, 'portal_workflow', None)
comment_workflow = workflowTool.getChainForPortalType(
'Discussion Item')
workflowTool = getToolByName(context, "portal_workflow", None)
comment_workflow = workflowTool.getChainForPortalType("Discussion Item")
if comment_workflow:
comment_workflow = comment_workflow[0]
comment_workflow = workflowTool[comment_workflow]
if 'pending' in comment_workflow.states:
if "pending" in comment_workflow.states:
return True
return False
@ -184,13 +183,15 @@ class DeleteComment(BrowserView):
content_object.reindexObject()
notify(CommentDeletedEvent(self.context, comment))
IStatusMessage(self.context.REQUEST).addStatusMessage(
_('Comment deleted.'),
type='info')
_("Comment deleted."), type="info"
)
came_from = self.context.REQUEST.HTTP_REFERER
# if the referrer already has a came_from in it, don't redirect back
if (len(came_from) == 0 or 'came_from=' in came_from or
not getToolByName(
content_object, 'portal_url').isURLInPortal(came_from)):
if (
len(came_from) == 0
or "came_from=" in came_from
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
):
came_from = content_object.absolute_url()
return self.context.REQUEST.RESPONSE.redirect(came_from)
@ -198,8 +199,7 @@ class DeleteComment(BrowserView):
"""Returns true if current user has the 'Delete comments'
permission.
"""
return getSecurityManager().checkPermission('Delete comments',
aq_inner(reply))
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
class DeleteOwnComment(DeleteComment):
@ -213,21 +213,18 @@ class DeleteOwnComment(DeleteComment):
"""
def could_delete(self, comment=None):
"""Returns true if the comment could be deleted if it had no replies.
"""
"""Returns true if the comment could be deleted if it had no replies."""
sm = getSecurityManager()
comment = comment or aq_inner(self.context)
userid = sm.getUser().getId()
return (
sm.checkPermission('Delete own comments', comment) and
'Owner' in comment.get_local_roles_for_userid(userid)
)
return sm.checkPermission(
"Delete own comments", comment
) and "Owner" in comment.get_local_roles_for_userid(userid)
def can_delete(self, comment=None):
comment = comment or self.context
return (
len(IReplies(aq_inner(comment))) == 0 and
self.could_delete(comment=comment)
return len(IReplies(aq_inner(comment))) == 0 and self.could_delete(
comment=comment
)
def __call__(self):
@ -262,33 +259,37 @@ class CommentTransition(BrowserView):
"""Call CommentTransition."""
comment = aq_inner(self.context)
content_object = aq_parent(aq_parent(comment))
workflow_action = self.request.form.get('workflow_action', 'publish')
workflowTool = getToolByName(self.context, 'portal_workflow')
workflow_action = self.request.form.get("workflow_action", "publish")
workflowTool = getToolByName(self.context, "portal_workflow")
workflowTool.doActionFor(comment, workflow_action)
comment.reindexObject()
content_object.reindexObject(idxs=['total_comments'])
content_object.reindexObject(idxs=["total_comments"])
notify(CommentPublishedEvent(self.context, comment))
# for complexer workflows:
notify(CommentTransitionEvent(self.context, comment))
comment_state_translated = ''
comment_state_translated = ""
if workflowTool.getWorkflowsFor(comment):
review_state_new = workflowTool.getInfoFor(ob=comment, name='review_state')
review_state_new = workflowTool.getInfoFor(ob=comment, name="review_state")
helper = self.context.restrictedTraverse("translationhelper")
comment_state_translated = helper.translate_comment_review_state(review_state_new)
comment_state_translated = helper.translate_comment_review_state(
review_state_new
)
msgid = _(
"comment_transmitted",
default='Comment ${comment_state_translated}.',
mapping={"comment_state_translated": comment_state_translated})
default="Comment ${comment_state_translated}.",
mapping={"comment_state_translated": comment_state_translated},
)
translated = self.context.translate(msgid)
IStatusMessage(self.request).add(translated, type='info')
IStatusMessage(self.request).add(translated, type="info")
came_from = self.context.REQUEST.HTTP_REFERER
# if the referrer already has a came_from in it, don't redirect back
if (len(came_from) == 0
or 'came_from=' in came_from
or not getToolByName(
content_object, 'portal_url').isURLInPortal(came_from)):
if (
len(came_from) == 0
or "came_from=" in came_from
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
):
came_from = content_object.absolute_url()
return self.context.REQUEST.RESPONSE.redirect(came_from)
@ -318,18 +319,18 @@ class BulkActionsView(BrowserView):
def __init__(self, context, request):
super(BulkActionsView, self).__init__(context, request)
self.workflowTool = getToolByName(context, 'portal_workflow')
self.workflowTool = getToolByName(context, "portal_workflow")
def __call__(self):
"""Call BulkActionsView."""
if 'form.select.BulkAction' in self.request:
bulkaction = self.request.get('form.select.BulkAction')
self.paths = self.request.get('paths')
if "form.select.BulkAction" in self.request:
bulkaction = self.request.get("form.select.BulkAction")
self.paths = self.request.get("paths")
if self.paths:
if bulkaction == '-1':
if bulkaction == "-1":
# no bulk action was selected
pass
elif bulkaction == 'delete':
elif bulkaction == "delete":
self.delete()
else:
self.transmit(bulkaction)
@ -346,13 +347,14 @@ class BulkActionsView(BrowserView):
comment = context.restrictedTraverse(path)
content_object = aq_parent(aq_parent(comment))
allowed_transitions = [
transition['id'] for transition in self.workflowTool.listActionInfos(object=comment)
if transition['category'] == 'workflow' and transition['allowed']
transition["id"]
for transition in self.workflowTool.listActionInfos(object=comment)
if transition["category"] == "workflow" and transition["allowed"]
]
if action in allowed_transitions:
self.workflowTool.doActionFor(comment, action)
comment.reindexObject()
content_object.reindexObject(idxs=['total_comments'])
content_object.reindexObject(idxs=["total_comments"])
notify(CommentPublishedEvent(content_object, comment))
# for complexer workflows:
notify(CommentTransitionEvent(self.context, comment))
@ -370,5 +372,5 @@ class BulkActionsView(BrowserView):
conversation = aq_parent(comment)
content_object = aq_parent(conversation)
del conversation[comment.id]
content_object.reindexObject(idxs=['total_comments'])
content_object.reindexObject(idxs=["total_comments"])
notify(CommentDeletedEvent(content_object, comment))

View File

@ -29,8 +29,8 @@ class ConversationNamespace(object):
def traverse(self, name, ignore):
if name == 'default':
name = u''
if name == "default":
name = u""
conversation = queryAdapter(self.context, IConversation, name=name)
if conversation is None:

View File

@ -44,11 +44,12 @@ class CaptchaValidator(validator.SimpleFieldValidator):
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
if settings.captcha in ('captcha', 'recaptcha', 'norobots'):
captcha = getMultiAdapter((aq_inner(self.context), self.request),
name=settings.captcha)
if settings.captcha in ("captcha", "recaptcha", "norobots"):
captcha = getMultiAdapter(
(aq_inner(self.context), self.request), name=settings.captcha
)
if not captcha.verify(input=value):
if settings.captcha == 'norobots':
if settings.captcha == "norobots":
raise WrongNorobotsAnswer
else:
raise WrongCaptchaCode
@ -57,5 +58,4 @@ class CaptchaValidator(validator.SimpleFieldValidator):
# Register Captcha validator for the Captcha field in the ICaptcha Form
validator.WidgetValidatorDiscriminators(CaptchaValidator,
field=ICaptcha['captcha'])
validator.WidgetValidatorDiscriminators(CaptchaValidator, field=ICaptcha["captcha"])

View File

@ -25,7 +25,7 @@ MAX_DESCRIPTION = 25
def total_comments(object):
# Total number of comments on a conversation
# Indexers won't work on old discussion items
if object.meta_type != 'Discussion Item':
if object.meta_type != "Discussion Item":
try:
conversation = IConversation(object)
return conversation.total_comments()
@ -39,7 +39,7 @@ def total_comments(object):
def last_comment_date(object):
# Date of the latest comment on a conversation
# Indexers won't work on old discussion items
if object.meta_type != 'Discussion Item':
if object.meta_type != "Discussion Item":
try:
conversation = IConversation(object)
return conversation.last_comment_date
@ -53,7 +53,7 @@ def last_comment_date(object):
def commentators(object):
# List of commentators on a conversation
# Indexers won't work on old discussion items
if object.meta_type != 'Discussion Item':
if object.meta_type != "Discussion Item":
try:
conversation = IConversation(object)
return conversation.public_commentators
@ -62,6 +62,7 @@ def commentators(object):
# implemented an adapter for it
pass
# Comment Indexers
@ -76,24 +77,24 @@ def creator(object):
return
value = safe_unicode(object.creator)
if six.PY2:
return value.encode('utf8')
return value.encode("utf8")
return value
@indexer(IComment)
def description(object):
# Return the first 25 words of the comment text and append ' [...]'
text = ' '.join(
object.getText(targetMimetype='text/plain').split()[:MAX_DESCRIPTION],
text = " ".join(
object.getText(targetMimetype="text/plain").split()[:MAX_DESCRIPTION],
)
if len(object.getText().split()) > 25:
text += ' [...]'
text += " [...]"
return text
@indexer(IComment)
def searchable_text(object):
return object.getText(targetMimetype='text/plain')
return object.getText(targetMimetype="text/plain")
@indexer(IComment)
@ -113,7 +114,7 @@ def effective(object):
object.creation_date.hour,
object.creation_date.minute,
object.creation_date.second,
'GMT',
"GMT",
)
@ -127,7 +128,7 @@ def created(object):
object.creation_date.hour,
object.creation_date.minute,
object.creation_date.second,
'GMT',
"GMT",
)
@ -141,7 +142,7 @@ def modified(object):
object.modification_date.hour,
object.modification_date.minute,
object.modification_date.second,
'GMT',
"GMT",
)

View File

@ -44,57 +44,66 @@ import six
COMMENT_TITLE = _(
u'comment_title',
default=u'${author_name} on ${content}',
u"comment_title",
default=u"${author_name} on ${content}",
)
MAIL_NOTIFICATION_MESSAGE = _(
u'mail_notification_message',
u"mail_notification_message",
default=u'A comment on "${title}" '
u'has been posted here: ${link}\n\n'
u'---\n'
u'${text}\n'
u'---\n',
u"has been posted here: ${link}\n\n"
u"---\n"
u"${text}\n"
u"---\n",
)
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
u'mail_notification_message_moderator2',
u"mail_notification_message_moderator2",
default=u'A comment on "${title}" '
u'has been posted by ${commentator}\n'
u'here: ${link}\n\n'
u'---\n\n'
u'${text}\n\n'
u'---\n\n'
u'Log in to moderate.\n\n',
u"has been posted by ${commentator}\n"
u"here: ${link}\n\n"
u"---\n\n"
u"${text}\n\n"
u"---\n\n"
u"Log in to moderate.\n\n",
)
logger = logging.getLogger('plone.app.discussion')
logger = logging.getLogger("plone.app.discussion")
@implementer(IComment)
class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
RoleManager, Owned, Implicit, Persistent):
class Comment(
CatalogAware,
WorkflowAware,
DynamicType,
Traversable,
RoleManager,
Owned,
Implicit,
Persistent,
):
"""A comment.
This object attempts to be as lightweight as possible. We implement a
number of standard methods instead of subclassing, to have total control
over what goes into the object.
"""
security = ClassSecurityInfo()
meta_type = portal_type = 'Discussion Item'
meta_type = portal_type = "Discussion Item"
# This needs to be kept in sync with types/Discussion_Item.xml title
fti_title = 'Comment'
fti_title = "Comment"
__parent__ = None
comment_id = None # long
in_reply_to = None # long
title = u''
title = u""
mime_type = None
text = u''
text = u""
creator = None
creation_date = None
@ -113,14 +122,17 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
def __init__(self):
self.creation_date = self.modification_date = datetime.utcnow()
self.mime_type = 'text/plain'
self.mime_type = "text/plain"
user = getSecurityManager().getUser()
if user and user.getId():
aclpath = [x for x in user.getPhysicalPath() if x]
self._owner = (aclpath, user.getId(),)
self._owner = (
aclpath,
user.getId(),
)
self.__ac_local_roles__ = {
user.getId(): ['Owner'],
user.getId(): ["Owner"],
}
@property
@ -137,32 +149,32 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
def getText(self, targetMimetype=None):
"""The body text of a comment."""
transforms = getToolByName(self, 'portal_transforms')
transforms = getToolByName(self, "portal_transforms")
if targetMimetype is None:
targetMimetype = 'text/x-html-safe'
targetMimetype = "text/x-html-safe"
sourceMimetype = getattr(self, 'mime_type', None)
sourceMimetype = getattr(self, "mime_type", None)
if sourceMimetype is None:
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
sourceMimetype = settings.text_transform
text = self.text
if text is None:
return ''
return ""
if six.PY2 and isinstance(text, six.text_type):
text = text.encode('utf8')
text = text.encode("utf8")
transform = transforms.convertTo(
targetMimetype,
text,
context=self,
mimetype=sourceMimetype)
targetMimetype, text, context=self, mimetype=sourceMimetype
)
if transform:
return transform.getData()
else:
logger = logging.getLogger('plone.app.discussion')
msg = u'Transform "{0}" => "{1}" not available. Failed to ' \
logger = logging.getLogger("plone.app.discussion")
msg = (
u'Transform "{0}" => "{1}" not available. Failed to '
u'transform comment "{2}".'
)
logger.error(
msg.format(
sourceMimetype,
@ -182,8 +194,8 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
author_name = translate(
Message(
_(
u'label_anonymous',
default=u'Anonymous',
u"label_anonymous",
default=u"Anonymous",
),
),
)
@ -194,9 +206,14 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
# conversation, the parent of the conversation is the content object).
content = aq_base(self.__parent__.__parent__)
title = translate(
Message(COMMENT_TITLE,
mapping={'author_name': safe_unicode(author_name),
'content': safe_unicode(content.Title())}))
Message(
COMMENT_TITLE,
mapping={
"author_name": safe_unicode(author_name),
"content": safe_unicode(content.Title()),
},
)
)
return title
def Creator(self):
@ -224,20 +241,18 @@ CommentFactory = Factory(Comment)
def notify_workflow(obj, event):
"""Tell the workflow tool when a comment is added
"""
tool = getToolByName(obj, 'portal_workflow', None)
"""Tell the workflow tool when a comment is added"""
tool = getToolByName(obj, "portal_workflow", None)
if tool is not None:
tool.notifyCreated(obj)
def notify_content_object(obj, event):
"""Tell the content object when a comment is added
"""
"""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',
'commentators'))
content_obj.reindexObject(
idxs=("total_comments", "last_comment_date", "commentators")
)
def notify_content_object_deleted(obj, event):
@ -251,40 +266,40 @@ def notify_content_object_deleted(obj, event):
def notify_comment_added(obj, event):
""" Notify custom discussion events when a comment is added or replied
"""
"""Notify custom discussion events when a comment is added or replied"""
conversation = aq_parent(obj)
context = aq_parent(conversation)
if getattr(obj, 'in_reply_to', None):
if getattr(obj, "in_reply_to", None):
return notify(ReplyAddedEvent(context, obj))
return notify(CommentAddedEvent(context, obj))
def notify_comment_modified(obj, event):
""" Notify custom discussion events when a comment, or a reply, is modified
"""
"""Notify custom discussion events when a comment, or a reply, is modified"""
conversation = aq_parent(obj)
context = aq_parent(conversation)
if getattr(obj, 'in_reply_to', None):
if getattr(obj, "in_reply_to", None):
return notify(ReplyModifiedEvent(context, obj))
return notify(CommentModifiedEvent(context, obj))
def notify_comment_removed(obj, event):
""" Notify custom discussion events when a comment or reply is removed
"""
"""Notify custom discussion events when a comment or reply is removed"""
conversation = aq_parent(obj)
context = aq_parent(conversation)
if getattr(obj, 'in_reply_to', None):
if getattr(obj, "in_reply_to", None):
return notify(ReplyRemovedEvent(context, obj))
return notify(CommentRemovedEvent(context, obj))
def notify_content_object_moved(obj, event):
"""Update all comments of a content object that has been moved.
"""
if event.oldParent is None or event.newParent is None \
or event.oldName is None or event.newName is None:
"""Update all comments of a content object that has been moved."""
if (
event.oldParent is None
or event.newParent is None
or event.oldName is None
or event.newName is None
):
return
# This method is also called for sublocations of moved objects. We
@ -293,21 +308,19 @@ def notify_content_object_moved(obj, event):
# in the object hierarchy. The object is already moved at this point. so
# obj.getPhysicalPath retruns the new path get the part of the path that
# was moved.
moved_path = obj.getPhysicalPath()[
len(event.newParent.getPhysicalPath()) + 1:
]
moved_path = obj.getPhysicalPath()[len(event.newParent.getPhysicalPath()) + 1 :]
# Remove comments at the old location from catalog
catalog = getToolByName(obj, 'portal_catalog')
old_path = '/'.join(
event.oldParent.getPhysicalPath() +
(event.oldName,) +
moved_path,
catalog = getToolByName(obj, "portal_catalog")
old_path = "/".join(
event.oldParent.getPhysicalPath() + (event.oldName,) + moved_path,
)
brains = catalog.searchResults(
dict(
path={"query": old_path},
portal_type="Discussion Item",
)
)
brains = catalog.searchResults(dict(
path={'query': old_path},
portal_type='Discussion Item',
))
for brain in brains:
catalog.uncatalog_object(brain.getPath())
# Reindex comment at the new location
@ -334,9 +347,9 @@ def notify_user(obj, event):
return
# Get informations that are necessary to send an email
mail_host = getToolByName(obj, 'MailHost')
mail_host = getToolByName(obj, "MailHost")
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
sender = mail_settings.email_from_address
# Check if a sender address is available
@ -360,15 +373,14 @@ def notify_user(obj, event):
if not emails:
return
subject = translate(_(u'A comment has been posted.'),
context=obj.REQUEST)
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
message = translate(
Message(
MAIL_NOTIFICATION_MESSAGE,
mapping={
'title': safe_unicode(content_object.title),
'link': content_object.absolute_url() + '/view#' + obj.id,
'text': obj.text,
"title": safe_unicode(content_object.title),
"link": content_object.absolute_url() + "/view#" + obj.id,
"text": obj.text,
},
),
context=obj.REQUEST,
@ -381,12 +393,11 @@ def notify_user(obj, event):
email,
sender,
subject,
charset='utf-8',
charset="utf-8",
)
except SMTPException:
logger.error(
'SMTP exception while trying to send an ' +
'email from %s to %s',
"SMTP exception while trying to send an " + "email from %s to %s",
sender,
email,
)
@ -412,9 +423,9 @@ def notify_moderator(obj, event):
return
# Get informations that are necessary to send an email
mail_host = getToolByName(obj, 'MailHost')
mail_host = getToolByName(obj, "MailHost")
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
sender = mail_settings.email_from_address
if settings.moderator_email:
@ -430,22 +441,23 @@ def notify_moderator(obj, event):
content_object = aq_parent(conversation)
# Compose email
subject = translate(_(u'A comment has been posted.'), context=obj.REQUEST)
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
message = translate(
Message(
MAIL_NOTIFICATION_MESSAGE_MODERATOR,
mapping={
'title': safe_unicode(content_object.title),
'link': content_object.absolute_url() + '/view#' + obj.id,
'text': obj.text,
'commentator': obj.author_email or translate(
"title": safe_unicode(content_object.title),
"link": content_object.absolute_url() + "/view#" + obj.id,
"text": obj.text,
"commentator": obj.author_email
or translate(
Message(
_(
u'label_anonymous',
default=u'Anonymous',
u"label_anonymous",
default=u"Anonymous",
),
),
),
)
},
),
context=obj.REQUEST,
@ -453,12 +465,12 @@ def notify_moderator(obj, event):
# Send email
try:
mail_host.send(message, mto, sender, subject, charset='utf-8')
mail_host.send(message, mto, sender, subject, charset="utf-8")
except SMTPException as e:
logger.error(
'SMTP exception (%s) while trying to send an ' +
'email notification to the comment moderator ' +
'(from %s to %s, message: %s)',
"SMTP exception (%s) while trying to send an "
+ "email notification to the comment moderator "
+ "(from %s to %s, message: %s)",
e,
sender,
mto,

View File

@ -7,101 +7,94 @@ from plone.app.discussion import _
try:
from plone.stringinterp.adapters import BaseSubstitution
except ImportError:
class BaseSubstitution(object):
""" Fallback class if plone.stringinterp is not available
"""
"""Fallback class if plone.stringinterp is not available"""
def __init__(self, context, **kwargs):
self.context = context
try:
from plone.app.contentrules.handlers import execute
except ImportError:
def execute(context, event):
return False
def execute_comment(event):
""" Execute comment content rules
"""
"""Execute comment content rules"""
execute(event.object, event)
class CommentSubstitution(BaseSubstitution):
""" Comment string substitution
"""
"""Comment string substitution"""
def __init__(self, context, **kwargs):
super(CommentSubstitution, self).__init__(context, **kwargs)
@property
def event(self):
""" event that triggered the content rule
"""
return self.context.REQUEST.get('event')
"""event that triggered the content rule"""
return self.context.REQUEST.get("event")
@property
def comment(self):
""" Get changed inline comment
"""
"""Get changed inline comment"""
return self.event.comment
class Id(CommentSubstitution):
""" Comment id string substitution
"""
category = _(u'Comments')
description = _(u'Comment id')
"""Comment id string substitution"""
category = _(u"Comments")
description = _(u"Comment id")
def safe_call(self):
""" Safe call
"""
return getattr(self.comment, 'comment_id', u'')
"""Safe call"""
return getattr(self.comment, "comment_id", u"")
class Text(CommentSubstitution):
""" Comment text
"""
category = _(u'Comments')
description = _(u'Comment text')
"""Comment text"""
category = _(u"Comments")
description = _(u"Comment text")
def safe_call(self):
""" Safe call
"""
return getattr(self.comment, 'text', u'')
"""Safe call"""
return getattr(self.comment, "text", u"")
class AuthorUserName(CommentSubstitution):
""" Comment author user name string substitution
"""
category = _(u'Comments')
description = _(u'Comment author user name')
"""Comment author user name string substitution"""
category = _(u"Comments")
description = _(u"Comment author user name")
def safe_call(self):
""" Safe call
"""
return getattr(self.comment, 'author_username', u'')
"""Safe call"""
return getattr(self.comment, "author_username", u"")
class AuthorFullName(CommentSubstitution):
""" Comment author full name string substitution
"""
category = _(u'Comments')
description = _(u'Comment author full name')
"""Comment author full name string substitution"""
category = _(u"Comments")
description = _(u"Comment author full name")
def safe_call(self):
""" Safe call
"""
return getattr(self.comment, 'author_name', u'')
"""Safe call"""
return getattr(self.comment, "author_name", u"")
class AuthorEmail(CommentSubstitution):
""" Comment author email string substitution
"""
category = _(u'Comments')
description = _(u'Comment author email')
"""Comment author email string substitution"""
category = _(u"Comments")
description = _(u"Comment author email")
def safe_call(self):
""" Safe call
"""
return getattr(self.comment, 'author_email', u'')
"""Safe call"""
return getattr(self.comment, "author_email", u"")

View File

@ -51,7 +51,7 @@ class Conversation(Traversable, Persistent, Explicit):
__allow_access_to_unprotected_subobjects__ = True
def __init__(self, id='++conversation++default'):
def __init__(self, id="++conversation++default"):
self.id = id
# username -> count of comments; key is removed when count reaches 0
@ -72,12 +72,11 @@ class Conversation(Traversable, Persistent, Explicit):
def enabled(self):
parent = aq_inner(self.__parent__)
return parent.restrictedTraverse('@@conversation_view').enabled()
return parent.restrictedTraverse("@@conversation_view").enabled()
def total_comments(self):
public_comments = [
x for x in self.values()
if user_nobody.has_permission('View', x)
x for x in self.values() if user_nobody.has_permission("View", x)
]
return len(public_comments)
@ -88,7 +87,7 @@ class Conversation(Traversable, Persistent, Explicit):
comment_keys = self._comments.keys()
for comment_key in reversed(comment_keys):
comment = self._comments[comment_key]
if user_nobody.has_permission('View', comment):
if user_nobody.has_permission("View", comment):
return comment.creation_date
return None
@ -100,7 +99,7 @@ class Conversation(Traversable, Persistent, Explicit):
def public_commentators(self):
retval = set()
for comment in self._comments.values():
if not user_nobody.has_permission('View', comment):
if not user_nobody.has_permission("View", comment):
continue
retval.add(comment.author_username)
return tuple(retval)
@ -109,8 +108,7 @@ class Conversation(Traversable, Persistent, Explicit):
return self._comments.keys()
def getComments(self, start=0, size=None):
"""Get unthreaded comments
"""
"""Get unthreaded comments"""
count = 0
for comment in self._comments.values(min=start):
# Yield the acquisition wrapped comment
@ -121,12 +119,11 @@ class Conversation(Traversable, Persistent, Explicit):
return
def getThreads(self, start=0, size=None, root=0, depth=None):
"""Get threaded comments
"""
"""Get threaded comments"""
def recurse(comment_id, d=0):
# Yield the current comment before we look for its children
yield {'id': comment_id, 'comment': self[comment_id], 'depth': d}
yield {"id": comment_id, "comment": self[comment_id], "depth": d}
# Recurse if there are children and we are not out of our depth
if depth is None or d + 1 < depth:
@ -209,8 +206,7 @@ class Conversation(Traversable, Persistent, Explicit):
return int(key) in self._comments
def __getitem__(self, key):
"""Get an item by its int key
"""
"""Get an item by its int key"""
try:
comment_id = int(key)
except ValueError:
@ -218,8 +214,7 @@ class Conversation(Traversable, Persistent, Explicit):
return self._comments[comment_id].__of__(self)
def __delitem__(self, key, suppress_container_modified=False):
"""Delete an item by its int key
"""
"""Delete an item by its int key"""
key = int(key)
@ -269,7 +264,13 @@ class Conversation(Traversable, Persistent, Explicit):
return self._comments.keys()
def items(self):
return [(i[0], i[1].__of__(self),) for i in self._comments.items()]
return [
(
i[0],
i[1].__of__(self),
)
for i in self._comments.items()
]
def values(self):
return [v.__of__(self) for v in self._comments.values()]
@ -283,7 +284,10 @@ class Conversation(Traversable, Persistent, Explicit):
def iteritems(self):
for k, v in six.iteritems(self._comments):
yield (k, v.__of__(self),)
yield (
k,
v.__of__(self),
)
def allowedContentTypes(self):
return []
@ -309,6 +313,7 @@ try:
except ImportError:
pass
else:
@implementer(IConversation) # pragma: no cover
@adapter(IAnnotatable) # pragma: no cover
def conversationCanonicalAdapterFactory(content): # pragma: no cover
@ -350,16 +355,14 @@ class ConversationReplies(object):
return int(key) in self.children
def __getitem__(self, key):
"""Get an item by its int key
"""
"""Get an item by its int key"""
key = int(key)
if key not in self.children:
raise KeyError(key)
return self.conversation[key]
def __delitem__(self, key):
"""Delete an item by its int key
"""
"""Delete an item by its int key"""
key = int(key)
if key not in self.children:
raise KeyError(key)
@ -392,7 +395,10 @@ class ConversationReplies(object):
def iteritems(self):
for key in self.children:
yield (key, self.conversation[key],)
yield (
key,
self.conversation[key],
)
@property
def children(self):
@ -418,11 +424,12 @@ class CommentReplies(ConversationReplies):
self.conversation = aq_parent(self.comment)
conversation_has_no_children = not hasattr(
self.conversation,
'_children',
"_children",
)
if self.conversation is None or conversation_has_no_children:
raise TypeError("This adapter doesn't know what to do with the "
'parent conversation')
raise TypeError(
"This adapter doesn't know what to do with the " "parent conversation"
)
self.comment_id = self.comment.comment_id

View File

@ -16,8 +16,7 @@ from zope.interface import implementer
@implementer(IDiscussionEvent)
class DiscussionEvent(object):
""" Custom event
"""
"""Custom event"""
def __init__(self, context, comment, **kwargs):
self.object = context
@ -28,55 +27,47 @@ class DiscussionEvent(object):
# Add event to the request to be able to access comment attributes
# in content-rules dynamic strings
request = context.REQUEST
request.set('event', self)
request.set("event", self)
@implementer(ICommentAddedEvent)
class CommentAddedEvent(DiscussionEvent):
""" Event to be triggered when a Comment is added
"""
"""Event to be triggered when a Comment is added"""
@implementer(ICommentModifiedEvent)
class CommentModifiedEvent(DiscussionEvent):
""" Event to be triggered when a Comment is modified
"""
"""Event to be triggered when a Comment is modified"""
@implementer(ICommentRemovedEvent)
class CommentRemovedEvent(DiscussionEvent):
""" Event to be triggered when a Comment is removed
"""
"""Event to be triggered when a Comment is removed"""
@implementer(IReplyAddedEvent)
class ReplyAddedEvent(DiscussionEvent):
""" Event to be triggered when a Comment reply is added
"""
"""Event to be triggered when a Comment reply is added"""
@implementer(IReplyModifiedEvent)
class ReplyModifiedEvent(DiscussionEvent):
""" Event to be triggered when a Comment reply is modified
"""
"""Event to be triggered when a Comment reply is modified"""
@implementer(IReplyRemovedEvent)
class ReplyRemovedEvent(DiscussionEvent):
""" Event to be triggered when a Comment reply is removed
"""
"""Event to be triggered when a Comment reply is removed"""
@implementer(ICommentDeletedEvent)
class CommentDeletedEvent(DiscussionEvent):
""" Event to be triggered when a Comment is deleted
"""
"""Event to be triggered when a Comment is deleted"""
@implementer(ICommentPublishedEvent)
class CommentPublishedEvent(DiscussionEvent):
""" Event to be triggered when a Comment is publicated
"""
"""Event to be triggered when a Comment is publicated"""
@implementer(ICommentTransitionEvent)

View File

@ -14,9 +14,9 @@ from zope.interface.interfaces import IObjectEvent
def isEmail(value):
portal = getUtility(ISiteRoot)
reg_tool = getToolByName(portal, 'portal_registration')
reg_tool = getToolByName(portal, "portal_registration")
if not (value and reg_tool.isValidEmail(value)):
raise Invalid(_('Invalid email address.'))
raise Invalid(_("Invalid email address."))
return True
@ -42,25 +42,24 @@ class IConversation(IIterableMapping):
"""
total_comments = schema.Int(
title=_(u'Total number of public comments on this item'),
title=_(u"Total number of public comments on this item"),
min=0,
readonly=True,
)
last_comment_date = schema.Date(
title=_(u'Date of the most recent public comment'),
title=_(u"Date of the most recent public comment"),
readonly=True,
)
commentators = schema.Set(
title=_(u'The set of unique commentators (usernames)'),
title=_(u"The set of unique commentators (usernames)"),
readonly=True,
)
public_commentators = schema.Set(
title=_(
u'The set of unique commentators (usernames) '
u'of published_comments',
u"The set of unique commentators (usernames) " u"of published_comments",
),
readonly=True,
)
@ -72,8 +71,7 @@ class IConversation(IIterableMapping):
"""
def __delitem__(key):
"""Delete the comment with the given key. The key is a long id.
"""
"""Delete the comment with the given key. The key is a long id."""
def getComments(start=0, size=None):
"""Return an iterator of comment objects for rendering.
@ -130,8 +128,7 @@ class IReplies(IIterableMapping):
"""
def __delitem__(key):
"""Delete the comment with the given key. The key is a long id.
"""
"""Delete the comment with the given key. The key is a long id."""
class IComment(Interface):
@ -141,61 +138,58 @@ class IComment(Interface):
"""
portal_type = schema.ASCIILine(
title=_(u'Portal type'),
default='Discussion Item',
title=_(u"Portal type"),
default="Discussion Item",
)
__parent__ = schema.Object(
title=_(u'Conversation'), schema=Interface)
__parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
__name__ = schema.TextLine(title=_(u'Name'))
__name__ = schema.TextLine(title=_(u"Name"))
comment_id = schema.Int(
title=_(u'A comment id unique to this conversation'))
comment_id = schema.Int(title=_(u"A comment id unique to this conversation"))
in_reply_to = schema.Int(
title=_(u'Id of comment this comment is in reply to'),
title=_(u"Id of comment this comment is in reply to"),
required=False,
)
# for logged in comments - set to None for anonymous
author_username = schema.TextLine(title=_(u'Name'), required=False)
author_username = schema.TextLine(title=_(u"Name"), required=False)
# for anonymous comments only, set to None for logged in comments
author_name = schema.TextLine(title=_(u'Name'), required=False)
author_email = schema.TextLine(title=_(u'Email'),
author_name = schema.TextLine(title=_(u"Name"), required=False)
author_email = schema.TextLine(
title=_(u"Email"),
required=False,
constraint=isEmail,
)
title = schema.TextLine(title=_(u'label_subject',
default=u'Subject'))
title = schema.TextLine(title=_(u"label_subject", default=u"Subject"))
mime_type = schema.ASCIILine(title=_(u'MIME type'), default='text/plain')
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(
title=_(
u'label_comment',
default=u'Comment',
u"label_comment",
default=u"Comment",
),
)
user_notification = schema.Bool(
title=_(
u'Notify me of new comments via email.',
u"Notify me of new comments via email.",
),
required=False,
)
creator = schema.TextLine(title=_(u'Username of the commenter'))
creation_date = schema.Date(title=_(u'Creation date'))
modification_date = schema.Date(title=_(u'Modification date'))
creator = schema.TextLine(title=_(u"Username of the commenter"))
creation_date = schema.Date(title=_(u"Creation date"))
modification_date = schema.Date(title=_(u"Modification date"))
class ICaptcha(Interface):
"""Captcha/ReCaptcha text field to extend the existing comment form.
"""
captcha = schema.TextLine(title=_(u'Captcha'),
required=False)
"""Captcha/ReCaptcha text field to extend the existing comment form."""
captcha = schema.TextLine(title=_(u"Captcha"), required=False)
class IDiscussionSettings(Interface):
@ -210,40 +204,38 @@ class IDiscussionSettings(Interface):
# - Search control panel: Show comments in search results
globally_enabled = schema.Bool(
title=_(u'label_globally_enabled',
default=u'Globally enable comments'),
title=_(u"label_globally_enabled", default=u"Globally enable comments"),
description=_(
u'help_globally_enabled',
default=u'If selected, users are able to post comments on the '
u'site. However, you will still need to enable comments '
u'for specific content types, folders or content '
u'objects before users will be able to post comments.',
u"help_globally_enabled",
default=u"If selected, users are able to post comments on the "
u"site. However, you will still need to enable comments "
u"for specific content types, folders or content "
u"objects before users will be able to post comments.",
),
required=False,
default=False,
)
anonymous_comments = schema.Bool(
title=_(u'label_anonymous_comments',
default='Enable anonymous comments'),
title=_(u"label_anonymous_comments", default="Enable anonymous comments"),
description=_(
u'help_anonymous_comments',
default=u'If selected, anonymous users are able to post '
u'comments without logging in. It is highly '
u'recommended to use a captcha solution to prevent '
u'spam if this setting is enabled.',
u"help_anonymous_comments",
default=u"If selected, anonymous users are able to post "
u"comments without logging in. It is highly "
u"recommended to use a captcha solution to prevent "
u"spam if this setting is enabled.",
),
required=False,
default=False,
)
anonymous_email_enabled = schema.Bool(
title=_(u'label_anonymous_email_enabled',
default=u'Enable anonymous email field'),
title=_(
u"label_anonymous_email_enabled", default=u"Enable anonymous email field"
),
description=_(
u'help_anonymous_email_enabled',
default=u'If selected, anonymous user will have to '
u'give their email.',
u"help_anonymous_email_enabled",
default=u"If selected, anonymous user will have to " u"give their email.",
),
required=False,
default=False,
@ -251,130 +243,137 @@ class IDiscussionSettings(Interface):
moderation_enabled = schema.Bool(
title=_(
u'label_moderation_enabled',
default='Enable comment moderation',
u"label_moderation_enabled",
default="Enable comment moderation",
),
description=_(
u'help_moderation_enabled',
u"help_moderation_enabled",
default=u'If selected, comments will enter a "Pending" state '
u'in which they are invisible to the public. A user '
u"in which they are invisible to the public. A user "
u'with the "Review comments" permission ("Reviewer" '
u'or "Manager") can approve comments to make them '
u'visible to the public. If you want to enable a '
u'custom comment workflow, you have to go to the '
u'types control panel.',
u"visible to the public. If you want to enable a "
u"custom comment workflow, you have to go to the "
u"types control panel.",
),
required=False,
default=False,
)
edit_comment_enabled = schema.Bool(
title=_(u'label_edit_comment_enabled',
default='Enable editing of comments'),
description=_(u'help_edit_comment_enabled',
default=u'If selected, supports editing '
title=_(u"label_edit_comment_enabled", default="Enable editing of comments"),
description=_(
u"help_edit_comment_enabled",
default=u"If selected, supports editing "
'of comments for users with the "Edit comments" '
'permission.'),
"permission.",
),
required=False,
default=False,
)
delete_own_comment_enabled = schema.Bool(
title=_(u'label_delete_own_comment_enabled',
default='Enable deleting own comments'),
description=_(u'help_delete_own_comment_enabled',
default=u'If selected, supports deleting '
'of own comments for users with the '
'"Delete own comments" permission.'),
title=_(
u"label_delete_own_comment_enabled", default="Enable deleting own comments"
),
description=_(
u"help_delete_own_comment_enabled",
default=u"If selected, supports deleting "
"of own comments for users with the "
'"Delete own comments" permission.',
),
required=False,
default=False,
)
text_transform = schema.Choice(
title=_(u'label_text_transform',
default='Comment text transform'),
title=_(u"label_text_transform", default="Comment text transform"),
description=_(
u'help_text_transform',
default=u'Use this setting to choose if the comment text '
u'should be transformed in any way. You can choose '
u"help_text_transform",
default=u"Use this setting to choose if the comment text "
u"should be transformed in any way. You can choose "
u'between "Plain text" and "Intelligent text". '
u'"Intelligent text" converts plain text into HTML '
u'where line breaks and indentation is preserved, '
u'and web and email addresses are made into '
u'clickable links.'),
u"where line breaks and indentation is preserved, "
u"and web and email addresses are made into "
u"clickable links.",
),
required=True,
default='text/plain',
vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary',
default="text/plain",
vocabulary="plone.app.discussion.vocabularies.TextTransformVocabulary",
)
captcha = schema.Choice(
title=_(u'label_captcha',
default='Captcha'),
title=_(u"label_captcha", default="Captcha"),
description=_(
u'help_captcha',
default=u'Use this setting to enable or disable Captcha '
u'validation for comments. Install '
u'plone.formwidget.captcha, '
u'plone.formwidget.recaptcha, collective.akismet, or '
u'collective.z3cform.norobots if there are no options '
u'available.'),
u"help_captcha",
default=u"Use this setting to enable or disable Captcha "
u"validation for comments. Install "
u"plone.formwidget.captcha, "
u"plone.formwidget.recaptcha, collective.akismet, or "
u"collective.z3cform.norobots if there are no options "
u"available.",
),
required=True,
default='disabled',
vocabulary='plone.app.discussion.vocabularies.CaptchaVocabulary',
default="disabled",
vocabulary="plone.app.discussion.vocabularies.CaptchaVocabulary",
)
show_commenter_image = schema.Bool(
title=_(u'label_show_commenter_image',
default=u'Show commenter image'),
title=_(u"label_show_commenter_image", default=u"Show commenter image"),
description=_(
u'help_show_commenter_image',
default=u'If selected, an image of the user is shown next to '
u'the comment.'),
u"help_show_commenter_image",
default=u"If selected, an image of the user is shown next to "
u"the comment.",
),
required=False,
default=True,
)
moderator_notification_enabled = schema.Bool(
title=_(u'label_moderator_notification_enabled',
default=u'Enable moderator email notification'),
title=_(
u"label_moderator_notification_enabled",
default=u"Enable moderator email notification",
),
description=_(
u'help_moderator_notification_enabled',
default=u'If selected, the moderator is notified if a comment '
u'needs attention. The moderator email address can '
u'be set below.'),
u"help_moderator_notification_enabled",
default=u"If selected, the moderator is notified if a comment "
u"needs attention. The moderator email address can "
u"be set below.",
),
required=False,
default=False,
)
moderator_email = schema.ASCIILine(
title=_(
u'label_moderator_email',
default=u'Moderator Email Address',
u"label_moderator_email",
default=u"Moderator Email Address",
),
description=_(
u'help_moderator_email',
default=u'Address to which moderator notifications '
u'will be sent.'),
u"help_moderator_email",
default=u"Address to which moderator notifications " u"will be sent.",
),
required=False,
)
user_notification_enabled = schema.Bool(
title=_(
u'label_user_notification_enabled',
default=u'Enable user email notification',
u"label_user_notification_enabled",
default=u"Enable user email notification",
),
description=_(
u'help_user_notification_enabled',
default=u'If selected, users can choose to be notified '
u'of new comments by email.'),
u"help_user_notification_enabled",
default=u"If selected, users can choose to be notified "
u"of new comments by email.",
),
required=False,
default=False,
)
class IDiscussionLayer(Interface):
"""Request marker installed via browserlayer.xml.
"""
"""Request marker installed via browserlayer.xml."""
class ICommentingTool(Interface):
@ -384,54 +383,46 @@ class ICommentingTool(Interface):
of Plone that had a portal_discussion tool.
"""
#
# Custom events
#
class IDiscussionEvent(IObjectEvent):
""" Discussion custom event
"""
"""Discussion custom event"""
class ICommentAddedEvent(IDiscussionEvent):
""" Comment added
"""
"""Comment added"""
class ICommentModifiedEvent(IDiscussionEvent):
""" Comment modified
"""
"""Comment modified"""
class ICommentRemovedEvent(IDiscussionEvent):
""" Comment removed
"""
"""Comment removed"""
class IReplyAddedEvent(IDiscussionEvent):
""" Comment reply added
"""
"""Comment reply added"""
class IReplyModifiedEvent(IDiscussionEvent):
""" Comment reply modified
"""
"""Comment reply modified"""
class IReplyRemovedEvent(IDiscussionEvent):
""" Comment reply removed
"""
"""Comment reply removed"""
class ICommentPublishedEvent(IDiscussionEvent):
""" Notify user on comment publication
"""
"""Notify user on comment publication"""
class ICommentDeletedEvent(IDiscussionEvent):
""" Notify user on comment delete
"""
"""Notify user on comment delete"""
class ICommentTransitionEvent(IDiscussionEvent):

View File

@ -3,12 +3,10 @@ from Products.CMFCore.utils import getToolByName
def index_object(obj, event):
"""Index the object when it is added/modified to the conversation.
"""
"""Index the object when it is added/modified to the conversation."""
obj.indexObject()
def unindex_object(obj, event):
"""Unindex the object when it is removed from the conversation.
"""
"""Unindex the object when it is removed from the conversation."""
obj.unindexObject()

View File

@ -15,40 +15,43 @@ from zope.component import queryUtility
try:
import plone.app.collection # noqa
COLLECTION_TYPE = 'Collection'
COLLECTION_TYPE = "Collection"
except ImportError:
COLLECTION_TYPE = 'Topic'
COLLECTION_TYPE = "Topic"
class PloneAppDiscussion(PloneSandboxLayer):
defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)
USER_NAME = 'johndoe'
USER_PASSWORD = 'secret'
MEMBER_NAME = 'janedoe'
MEMBER_PASSWORD = 'secret'
USER_WITH_FULLNAME_NAME = 'jim'
USER_WITH_FULLNAME_FULLNAME = 'Jim Fulton'
USER_WITH_FULLNAME_PASSWORD = 'secret'
MANAGER_USER_NAME = 'manager'
MANAGER_USER_PASSWORD = 'secret'
REVIEWER_NAME = 'reviewer'
REVIEWER_PASSWORD = 'secret'
USER_NAME = "johndoe"
USER_PASSWORD = "secret"
MEMBER_NAME = "janedoe"
MEMBER_PASSWORD = "secret"
USER_WITH_FULLNAME_NAME = "jim"
USER_WITH_FULLNAME_FULLNAME = "Jim Fulton"
USER_WITH_FULLNAME_PASSWORD = "secret"
MANAGER_USER_NAME = "manager"
MANAGER_USER_PASSWORD = "secret"
REVIEWER_NAME = "reviewer"
REVIEWER_PASSWORD = "secret"
def setUpZope(self, app, configurationContext):
# Load ZCML
import plone.app.discussion
self.loadZCML(package=plone.app.discussion,
self.loadZCML(
package=plone.app.discussion,
context=configurationContext,
)
def setUpPloneSite(self, portal):
# Install into Plone site using portal_setup
applyProfile(portal, 'plone.app.discussion:default')
applyProfile(portal, "plone.app.discussion:default")
# Creates some users
acl_users = getToolByName(portal, 'acl_users')
acl_users = getToolByName(portal, "acl_users")
acl_users.userFolderAddUser(
self.USER_NAME,
self.USER_PASSWORD,
@ -58,41 +61,42 @@ class PloneAppDiscussion(PloneSandboxLayer):
acl_users.userFolderAddUser(
self.MEMBER_NAME,
self.MEMBER_PASSWORD,
['Member'],
["Member"],
[],
)
acl_users.userFolderAddUser(
self.USER_WITH_FULLNAME_NAME,
self.USER_WITH_FULLNAME_PASSWORD,
['Member'],
["Member"],
[],
)
acl_users.userFolderAddUser(
self.REVIEWER_NAME,
self.REVIEWER_PASSWORD,
['Member'],
["Member"],
[],
)
mtool = getToolByName(portal, 'portal_membership', None)
gtool = getToolByName(portal, 'portal_groups', None)
gtool.addPrincipalToGroup(self.REVIEWER_NAME, 'Reviewers')
mtool.addMember('jim', 'Jim', ['Member'], [])
mtool.getMemberById('jim').setMemberProperties(
{'fullname': 'Jim Fult\xc3\xb8rn'})
mtool = getToolByName(portal, "portal_membership", None)
gtool = getToolByName(portal, "portal_groups", None)
gtool.addPrincipalToGroup(self.REVIEWER_NAME, "Reviewers")
mtool.addMember("jim", "Jim", ["Member"], [])
mtool.getMemberById("jim").setMemberProperties(
{"fullname": "Jim Fult\xc3\xb8rn"}
)
acl_users.userFolderAddUser(
self.MANAGER_USER_NAME,
self.MANAGER_USER_PASSWORD,
['Manager'],
["Manager"],
[],
)
# Add a document
setRoles(portal, TEST_USER_ID, ['Manager'])
setRoles(portal, TEST_USER_ID, ["Manager"])
portal.invokeFactory(
id='doc1',
title='Document 1',
type_name='Document',
id="doc1",
title="Document 1",
type_name="Document",
)
@ -112,12 +116,12 @@ class PloneAppDiscussionRobot(PloneAppDiscussion):
PLONE_APP_DISCUSSION_ROBOT_FIXTURE = PloneAppDiscussionRobot()
PLONE_APP_DISCUSSION_FIXTURE = PloneAppDiscussion()
PLONE_APP_DISCUSSION_INTEGRATION_TESTING = IntegrationTesting(
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
name='PloneAppDiscussion:Integration')
bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Integration"
)
PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING = FunctionalTesting(
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
name='PloneAppDiscussion:Functional')
bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Functional"
)
PLONE_APP_DISCUSSION_ROBOT_TESTING = FunctionalTesting(
bases=(PLONE_APP_DISCUSSION_ROBOT_FIXTURE,),
name='PloneAppDiscussion:Robot',
name="PloneAppDiscussion:Robot",
)

View File

@ -23,33 +23,29 @@ class CatalogSetupTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.portal = self.layer["portal"]
def test_catalog_installed(self):
self.assertTrue(
'total_comments' in
self.portal.portal_catalog.indexes(),
"total_comments" in self.portal.portal_catalog.indexes(),
)
self.assertTrue(
'commentators' in
self.portal.portal_catalog.indexes(),
"commentators" in self.portal.portal_catalog.indexes(),
)
self.assertTrue(
'total_comments' in
self.portal.portal_catalog.schema(),
"total_comments" in self.portal.portal_catalog.schema(),
)
self.assertTrue(
'in_response_to' in
self.portal.portal_catalog.schema(),
"in_response_to" in self.portal.portal_catalog.schema(),
)
def test_collection_criteria_installed(self):
if 'portal_atct' not in self.portal:
if "portal_atct" not in self.portal:
return
try:
self.portal.portal_atct.getIndex('commentators')
self.portal.portal_atct.getIndex('total_comments')
self.portal.portal_atct.getMetadata('total_comments')
self.portal.portal_atct.getIndex("commentators")
self.portal.portal_atct.getIndex("total_comments")
self.portal.portal_atct.getMetadata("total_comments")
except AttributeError:
self.fail()
@ -59,19 +55,19 @@ class ConversationCatalogTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
self.catalog = getToolByName(self.portal, 'portal_catalog')
self.catalog = getToolByName(self.portal, "portal_catalog")
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.title = 'Comment 1'
comment1.text = 'Comment text'
comment1.creator = 'jim'
comment1.author_username = 'Jim'
comment1 = createObject("plone.Comment")
comment1.title = "Comment 1"
comment1.text = "Comment text"
comment1.creator = "jim"
comment1.author_username = "Jim"
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12)
@ -81,9 +77,9 @@ class ConversationCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
self.conversation = conversation
@ -93,54 +89,54 @@ class ConversationCatalogTest(unittest.TestCase):
self.new_comment1_id = new_comment1_id
def test_total_comments(self):
self.assertTrue('total_comments' in self.doc1_brain)
self.assertTrue("total_comments" in self.doc1_brain)
self.assertEqual(self.doc1_brain.total_comments, 1)
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
new_comment2_id = self.conversation.addComment(comment2)
comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment2_id),
"++conversation++default/{0}".format(new_comment2_id),
)
comment2.reindexObject()
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
self.assertEqual(doc1_brain.total_comments, 2)
def test_last_comment_date(self):
self.assertTrue('last_comment_date' in self.doc1_brain)
self.assertTrue("last_comment_date" in self.doc1_brain)
self.assertEqual(
self.doc1_brain.last_comment_date,
datetime(2006, 9, 17, 14, 18, 12),
)
# Add another comment and check if last comment date is updated.
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
comment2.creation_date = datetime(2009, 9, 17, 14, 18, 12)
comment2.modification_date = datetime(2009, 9, 17, 14, 18, 12)
new_comment2_id = self.conversation.addComment(comment2)
comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment2_id),
"++conversation++default/{0}".format(new_comment2_id),
)
comment2.reindexObject()
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
@ -155,9 +151,9 @@ class ConversationCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
@ -171,44 +167,44 @@ class ConversationCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
self.assertEqual(doc1_brain.last_comment_date, None)
def test_commentators(self):
self.assertTrue('commentators' in self.doc1_brain)
self.assertEqual(self.doc1_brain.commentators, ('Jim',))
self.assertTrue("commentators" in self.doc1_brain)
self.assertEqual(self.doc1_brain.commentators, ("Jim",))
# add another comment with another author
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2.creator = 'emma'
comment2.author_username = 'Emma'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
comment2.creator = "emma"
comment2.author_username = "Emma"
new_comment2_id = self.conversation.addComment(comment2)
comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment2_id),
"++conversation++default/{0}".format(new_comment2_id),
)
comment2.reindexObject()
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
self.assertEqual(
sorted(doc1_brain.commentators),
sorted(('Jim', 'Emma')),
sorted(("Jim", "Emma")),
)
# remove one comments
@ -216,22 +212,22 @@ class ConversationCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
self.assertEqual(doc1_brain.commentators, ('Jim',))
self.assertEqual(doc1_brain.commentators, ("Jim",))
# remove all comments
del self.conversation[self.new_comment1_id]
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
@ -241,9 +237,9 @@ class ConversationCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Discussion Item',
portal_type="Discussion Item",
),
)
comment1_brain = brains[0]
@ -252,14 +248,14 @@ class ConversationCatalogTest(unittest.TestCase):
self.assertEqual(comment1_brain.total_comments, None)
def test_dont_index_private_commentators(self):
self.comment1.manage_permission('View', roles=tuple())
self.comment1.manage_permission("View", roles=tuple())
self.portal.doc1.reindexObject()
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
doc1_brain = brains[0]
@ -271,70 +267,70 @@ class CommentCatalogTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.catalog = getToolByName(self.portal, 'portal_catalog')
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.catalog = getToolByName(self.portal, "portal_catalog")
conversation = IConversation(self.portal.doc1)
self.conversation = conversation
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1.creator = 'jim'
comment1.author_name = 'Jim'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1.creator = "jim"
comment1.author_name = "Jim"
new_comment1_id = conversation.addComment(comment1)
self.comment_id = new_comment1_id
# Comment brain
self.comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment1_id),
"++conversation++default/{0}".format(new_comment1_id),
)
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.comment.getPhysicalPath()),
"query": "/".join(self.comment.getPhysicalPath()),
},
),
)
self.comment_brain = brains[0]
def test_title(self):
self.assertEqual(self.comment_brain.Title, 'Jim on Document 1')
self.assertEqual(self.comment_brain.Title, "Jim on Document 1")
def test_no_name_title(self):
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
cid = self.conversation.addComment(comment)
# Comment brain
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(cid),
"++conversation++default/{0}".format(cid),
)
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(comment.getPhysicalPath()),
"query": "/".join(comment.getPhysicalPath()),
},
),
)
comment_brain = brains[0]
self.assertEqual(comment_brain.Title, 'Anonymous on Document 1')
self.assertEqual(comment_brain.Title, "Anonymous on Document 1")
def test_type(self):
self.assertEqual(self.comment_brain.portal_type, 'Discussion Item')
self.assertEqual(self.comment_brain.Type, 'Comment')
self.assertEqual(self.comment_brain.portal_type, "Discussion Item")
self.assertEqual(self.comment_brain.Type, "Comment")
def test_review_state(self):
self.assertEqual(self.comment_brain.review_state, 'published')
self.assertEqual(self.comment_brain.review_state, "published")
def test_creator(self):
self.assertEqual(self.comment_brain.Creator, 'jim')
self.assertEqual(self.comment_brain.Creator, "jim")
def test_in_response_to(self):
"""Make sure in_response_to returns the title or id of the content
object the comment was added to.
"""
self.assertEqual(self.comment_brain.in_response_to, 'Document 1')
self.assertEqual(self.comment_brain.in_response_to, "Document 1")
def test_add_comment(self):
self.assertTrue(self.comment_brain)
@ -346,7 +342,7 @@ class CommentCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.comment.getPhysicalPath()),
"query": "/".join(self.comment.getPhysicalPath()),
},
),
)
@ -354,54 +350,54 @@ class CommentCatalogTest(unittest.TestCase):
def test_reindex_comment(self):
# Make sure a comment is reindexed on the catalog when is modified
self.comment.text = 'Another text'
self.comment.text = "Another text"
notify(ObjectModifiedEvent(self.comment))
brains = self.catalog.searchResults(SearchableText='Another text')
brains = self.catalog.searchResults(SearchableText="Another text")
self.assertEqual(len(brains), 1)
def test_remove_comments_when_content_object_is_removed(self):
"""Make sure all comments are removed from the catalog, if the content
object is removed.
"""
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
self.assertEqual(len(brains), 1)
self.portal.manage_delObjects(['doc1'])
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
self.portal.manage_delObjects(["doc1"])
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
self.assertEqual(len(brains), 0)
def test_move_comments_when_content_object_is_moved(self):
# Create two folders and a content object with a comment
self.portal.invokeFactory(
id='folder1',
title='Folder 1',
type_name='Folder',
id="folder1",
title="Folder 1",
type_name="Folder",
)
self.portal.invokeFactory(
id='folder2',
title='Folder 2',
type_name='Folder',
id="folder2",
title="Folder 2",
type_name="Folder",
)
self.portal.folder1.invokeFactory(
id='moveme',
title='Move Me',
type_name='Document',
id="moveme",
title="Move Me",
type_name="Document",
)
conversation = IConversation(self.portal.folder1.moveme)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
comment_id = conversation.addComment(comment)
# We need to commit here so that _p_jar isn't None and move will work
transaction.savepoint(optimistic=True)
# Move moveme from folder1 to folder2
cp = self.portal.folder1.manage_cutObjects(ids=('moveme',))
cp = self.portal.folder1.manage_cutObjects(ids=("moveme",))
self.portal.folder2.manage_pasteObjects(cp)
# Make sure no old comment brains are
brains = self.catalog.searchResults(
dict(
portal_type='Discussion Item',
portal_type="Discussion Item",
path={
'query': '/'.join(self.portal.folder1.getPhysicalPath()),
"query": "/".join(self.portal.folder1.getPhysicalPath()),
},
),
)
@ -409,61 +405,60 @@ class CommentCatalogTest(unittest.TestCase):
brains = self.catalog.searchResults(
dict(
portal_type='Discussion Item',
portal_type="Discussion Item",
path={
'query': '/'.join(self.portal.folder2.getPhysicalPath()),
"query": "/".join(self.portal.folder2.getPhysicalPath()),
},
),
)
self.assertEqual(len(brains), 1)
self.assertEqual(
brains[0].getPath(),
'/plone/folder2/moveme/++conversation++default/' +
str(comment_id),
"/plone/folder2/moveme/++conversation++default/" + str(comment_id),
)
def test_move_upper_level_folder(self):
# create a folder with a nested structure
self.portal.invokeFactory(
id='sourcefolder',
title='Source Folder',
type_name='Folder',
id="sourcefolder",
title="Source Folder",
type_name="Folder",
)
self.portal.sourcefolder.invokeFactory(
id='moveme',
title='Move Me',
type_name='Folder',
id="moveme",
title="Move Me",
type_name="Folder",
)
self.portal.sourcefolder.moveme.invokeFactory(
id='mydocument',
title='My Document',
type_name='Folder',
id="mydocument",
title="My Document",
type_name="Folder",
)
self.portal.invokeFactory(
id='targetfolder',
title='Target Folder',
type_name='Folder',
id="targetfolder",
title="Target Folder",
type_name="Folder",
)
# create comment on my-document
conversation = IConversation(
self.portal.sourcefolder.moveme.mydocument,
)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
comment_id = conversation.addComment(comment)
# We need to commit here so that _p_jar isn't None and move will work
transaction.savepoint(optimistic=True)
# Move moveme from folder1 to folder2
cp = self.portal.sourcefolder.manage_cutObjects(ids=('moveme',))
cp = self.portal.sourcefolder.manage_cutObjects(ids=("moveme",))
self.portal.targetfolder.manage_pasteObjects(cp)
# Make sure no old comment brains are left
brains = self.catalog.searchResults(
dict(
portal_type='Discussion Item',
path={'query': '/plone/sourcefolder/moveme'},
portal_type="Discussion Item",
path={"query": "/plone/sourcefolder/moveme"},
),
)
self.assertEqual(len(brains), 0)
@ -471,49 +466,47 @@ class CommentCatalogTest(unittest.TestCase):
# make sure comments are correctly index on the target
brains = self.catalog.searchResults(
dict(
portal_type='Discussion Item',
path={'query': '/plone/targetfolder/moveme'},
portal_type="Discussion Item",
path={"query": "/plone/targetfolder/moveme"},
),
)
self.assertEqual(len(brains), 1)
self.assertEqual(
brains[0].getPath(),
'/plone/targetfolder/moveme/mydocument/++conversation++default/' +
str(comment_id),
"/plone/targetfolder/moveme/mydocument/++conversation++default/"
+ str(comment_id),
)
def test_update_comments_when_content_object_is_renamed(self):
# We need to commit here so that _p_jar isn't None and move will work
transaction.savepoint(optimistic=True)
self.portal.manage_renameObject('doc1', 'doc2')
self.portal.manage_renameObject("doc1", "doc2")
brains = self.catalog.searchResults(
portal_type='Discussion Item',
portal_type="Discussion Item",
)
self.assertEqual(len(brains), 1)
self.assertEqual(
brains[0].getPath(),
'/plone/doc2/++conversation++default/' +
str(self.comment_id),
"/plone/doc2/++conversation++default/" + str(self.comment_id),
)
def test_clear_and_rebuild_catalog(self):
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
self.assertTrue(brains)
# Clear and rebuild catalog
self.catalog.clearFindAndRebuild()
# Check if comment is still there
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
self.assertTrue(brains)
comment_brain = brains[0]
self.assertEqual(comment_brain.Title, u'Jim on Document 1')
self.assertEqual(comment_brain.Title, u"Jim on Document 1")
self.assertEqual(
comment_brain.getPath(),
'/plone/doc1/++conversation++default/' +
str(self.comment_id),
"/plone/doc1/++conversation++default/" + str(self.comment_id),
)
def test_clear_and_rebuild_catalog_for_nested_comments(self):
@ -528,25 +521,25 @@ class CommentCatalogTest(unittest.TestCase):
# +- Comment 2
# +- Comment 2_1
comment1_1 = createObject('plone.Comment')
comment1_1.title = 'Re: Comment 1'
comment1_1.text = 'Comment text'
comment1_1 = createObject("plone.Comment")
comment1_1.title = "Re: Comment 1"
comment1_1.text = "Comment text"
comment1_1_1 = createObject('plone.Comment')
comment1_1_1.title = 'Re: Re: Comment 1'
comment1_1_1.text = 'Comment text'
comment1_1_1 = createObject("plone.Comment")
comment1_1_1.title = "Re: Re: Comment 1"
comment1_1_1.text = "Comment text"
comment1_2 = createObject('plone.Comment')
comment1_2.title = 'Re: Comment 1 (2)'
comment1_2.text = 'Comment text'
comment1_2 = createObject("plone.Comment")
comment1_2.title = "Re: Comment 1 (2)"
comment1_2.text = "Comment text"
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
comment2_1 = createObject('plone.Comment')
comment2_1.title = 'Re: Comment 2'
comment2_1.text = 'Comment text'
comment2_1 = createObject("plone.Comment")
comment2_1.title = "Re: Comment 2"
comment2_1.text = "Comment text"
# Create the nested comment structure
new_id_1 = self.conversation.addComment(self.comment)
@ -568,7 +561,7 @@ class CommentCatalogTest(unittest.TestCase):
self.catalog.clearFindAndRebuild()
# Check if comments are still there
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
self.assertTrue(brains)
self.assertEqual(len(brains), 6)
@ -578,19 +571,19 @@ class NoConversationCatalogTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.catalog = getToolByName(self.portal, 'portal_catalog')
self.catalog = getToolByName(self.portal, "portal_catalog")
conversation = IConversation(self.portal.doc1)
brains = self.catalog.searchResults(
dict(
path={
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
"query": "/".join(self.portal.doc1.getPhysicalPath()),
},
portal_type='Document',
portal_type="Document",
),
)
self.conversation = conversation
@ -598,11 +591,10 @@ class NoConversationCatalogTest(unittest.TestCase):
self.doc1_brain = brains[0]
def test_total_comments(self):
self.assertTrue('total_comments' in self.doc1_brain)
self.assertTrue("total_comments" in self.doc1_brain)
self.assertEqual(self.doc1_brain.total_comments, 0)
# Make sure no conversation has been created
self.assertTrue(
'plone.app.discussion:conversation' not in
IAnnotations(self.portal.doc1),
"plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1),
)

View File

@ -18,7 +18,7 @@ import six
import unittest
logger = logging.getLogger('plone.app.discussion.tests')
logger = logging.getLogger("plone.app.discussion.tests")
logger.addHandler(logging.StreamHandler())
@ -27,30 +27,30 @@ class CommentTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.catalog = getToolByName(self.portal, 'portal_catalog')
self.document_brain = self.catalog.searchResults(
portal_type='Document')[0]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
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')
comment1 = createObject("plone.Comment")
self.assertTrue(IComment.providedBy(comment1))
def test_UTCDates(self):
utc_to_local_diff = \
datetime.datetime.now() - datetime.datetime.utcnow()
utc_to_local_diff = datetime.datetime.now() - datetime.datetime.utcnow()
utc_to_local_diff = abs(utc_to_local_diff.seconds)
if utc_to_local_diff < 60:
logger.warning('Your computer is living in a timezone where local '
'time equals utc time. Some potential errors can '
'get hidden by that')
comment1 = createObject('plone.Comment')
logger.warning(
"Your computer is living in a timezone where local "
"time equals utc time. Some potential errors can "
"get hidden by that"
)
comment1 = createObject("plone.Comment")
local_utc = datetime.datetime.utcnow()
for date in (comment1.creation_date, comment1.modification_date):
difference = abs(date - local_utc)
@ -60,171 +60,166 @@ class CommentTest(unittest.TestCase):
self.assertFalse(difference // 10)
def test_id(self):
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
comment1.comment_id = 123
self.assertEqual('123', comment1.id)
self.assertEqual('123', comment1.getId())
self.assertEqual(u'123', comment1.__name__)
self.assertEqual("123", comment1.id)
self.assertEqual("123", comment1.getId())
self.assertEqual(u"123", comment1.__name__)
def test_uid(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
conversation.addComment(comment1)
comment_brain = self.catalog.searchResults(
portal_type='Discussion Item',
portal_type="Discussion Item",
)[0]
self.assertTrue(comment_brain.UID)
def test_uid_is_unique(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
conversation.addComment(comment1)
comment2 = createObject('plone.Comment')
comment2 = createObject("plone.Comment")
conversation.addComment(comment2)
brains = self.catalog.searchResults(
portal_type='Discussion Item',
portal_type="Discussion Item",
)
self.assertNotEqual(brains[0].UID, brains[1].UID)
def test_comment_uid_differs_from_content_uid(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
conversation.addComment(comment1)
comment_brain = self.catalog.searchResults(
portal_type='Discussion Item',
portal_type="Discussion Item",
)[0]
self.assertNotEqual(self.document_brain.UID, comment_brain.UID)
def test_title(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.author_name = 'Jim Fulton'
comment1 = createObject("plone.Comment")
comment1.author_name = "Jim Fulton"
conversation.addComment(comment1)
self.assertEqual('Jim Fulton on Document 1', comment1.Title())
self.assertEqual("Jim Fulton on Document 1", comment1.Title())
def test_no_name_title(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
conversation.addComment(comment1)
self.assertEqual('Anonymous on Document 1', comment1.Title())
self.assertEqual("Anonymous on Document 1", comment1.Title())
def test_title_special_characters(self):
self.portal.invokeFactory(
id='doc_sp_chars',
title=u'Document äüö',
type_name='Document',
id="doc_sp_chars",
title=u"Document äüö",
type_name="Document",
)
conversation = IConversation(self.portal.doc_sp_chars)
comment1 = createObject('plone.Comment')
comment1.author_name = u'Tarek Ziadé'
comment1 = createObject("plone.Comment")
comment1.author_name = u"Tarek Ziadé"
conversation.addComment(comment1)
self.assertEqual(u'Tarek Ziadé on Document äüö', comment1.Title())
self.assertEqual(u"Tarek Ziadé on Document äüö", comment1.Title())
def test_title_special_characters_utf8(self):
self.portal.invokeFactory(
id='doc_sp_chars_utf8',
title='Document ëïû',
type_name='Document',
id="doc_sp_chars_utf8",
title="Document ëïû",
type_name="Document",
)
conversation = IConversation(self.portal.doc_sp_chars_utf8)
comment1 = createObject('plone.Comment')
comment1.author_name = 'Hüüb Bôûmä'
comment1 = createObject("plone.Comment")
comment1.author_name = "Hüüb Bôûmä"
conversation.addComment(comment1)
self.assertEqual(u'Hüüb Bôûmä on Document ëïû', comment1.Title())
self.assertEqual(u"Hüüb Bôûmä on Document ëïû", comment1.Title())
def test_creator(self):
comment1 = createObject('plone.Comment')
comment1.creator = 'jim'
self.assertEqual('jim', comment1.Creator())
comment1 = createObject("plone.Comment")
comment1.creator = "jim"
self.assertEqual("jim", comment1.Creator())
def test_creator_author_name(self):
comment1 = createObject('plone.Comment')
comment1.author_name = 'joey'
self.assertEqual('joey', comment1.Creator())
comment1 = createObject("plone.Comment")
comment1.author_name = "joey"
self.assertEqual("joey", comment1.Creator())
def test_owner(self):
comment1 = createObject('plone.Comment')
self.assertEqual((['plone', 'acl_users'], TEST_USER_ID),
comment1.getOwnerTuple())
comment1 = createObject("plone.Comment")
self.assertEqual(
(["plone", "acl_users"], TEST_USER_ID), comment1.getOwnerTuple()
)
def test_type(self):
comment1 = createObject('plone.Comment')
self.assertEqual(comment1.Type(), 'Comment')
comment1 = createObject("plone.Comment")
self.assertEqual(comment1.Type(), "Comment")
def test_mime_type(self):
comment1 = createObject('plone.Comment')
self.assertEqual(comment1.mime_type, 'text/plain')
comment1 = createObject("plone.Comment")
self.assertEqual(comment1.mime_type, "text/plain")
def test_getText(self):
comment1 = createObject('plone.Comment')
comment1.text = 'First paragraph\n\nSecond_paragraph'
comment1 = createObject("plone.Comment")
comment1.text = "First paragraph\n\nSecond_paragraph"
self.assertEqual(
''.join(comment1.getText().split()),
'<p>Firstparagraph<br><br>Second_paragraph</p>',
"".join(comment1.getText().split()),
"<p>Firstparagraph<br><br>Second_paragraph</p>",
)
def test_getText_escapes_HTML(self):
comment1 = createObject('plone.Comment')
comment1.text = '<b>Got HTML?</b>'
comment1 = createObject("plone.Comment")
comment1.text = "<b>Got HTML?</b>"
self.assertEqual(
comment1.getText(),
'<p>&lt;b&gt;Got HTML?&lt;/b&gt;</p>',
"<p>&lt;b&gt;Got HTML?&lt;/b&gt;</p>",
)
def test_getText_with_non_ascii_characters(self):
comment1 = createObject('plone.Comment')
comment1.text = u'Umlaute sind ä, ö und ü.'
out = b'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>'
comment1 = createObject("plone.Comment")
comment1.text = u"Umlaute sind ä, ö und ü."
out = b"<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>"
if six.PY2:
self.assertEqual(
comment1.getText(),
out)
self.assertEqual(comment1.getText(), out)
else:
self.assertEqual(
comment1.getText(),
out.decode('utf8'))
self.assertEqual(comment1.getText(), out.decode("utf8"))
def test_getText_doesnt_link(self):
comment1 = createObject('plone.Comment')
comment1.text = 'Go to http://www.plone.org'
comment1 = createObject("plone.Comment")
comment1.text = "Go to http://www.plone.org"
self.assertEqual(
comment1.getText(),
'<p>Go to http://www.plone.org</p>',
"<p>Go to http://www.plone.org</p>",
)
def test_getText_uses_comment_mime_type(self):
comment1 = createObject('plone.Comment')
comment1.text = 'Go to http://www.plone.org'
comment1.mime_type = 'text/x-web-intelligent'
comment1 = createObject("plone.Comment")
comment1.text = "Go to http://www.plone.org"
comment1.mime_type = "text/x-web-intelligent"
self.assertEqual(
comment1.getText(),
'Go to <a href="http://www.plone.org" ' +
'rel="nofollow">http://www.plone.org</a>',
'Go to <a href="http://www.plone.org" '
+ 'rel="nofollow">http://www.plone.org</a>',
)
def test_getText_uses_comment_mime_type_html(self):
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
comment1.text = 'Go to <a href="http://www.plone.org">plone.org</a>'
comment1.mime_type = 'text/html'
comment1.mime_type = "text/html"
self.assertEqual(
comment1.getText(),
'Go to <a href="http://www.plone.org">plone.org</a>',
)
def test_getText_w_custom_targetMimetype(self):
comment1 = createObject('plone.Comment')
comment1.text = 'para'
self.assertEqual(comment1.getText(targetMimetype='text/plain'), 'para')
comment1 = createObject("plone.Comment")
comment1.text = "para"
self.assertEqual(comment1.getText(targetMimetype="text/plain"), "para")
def test_getText_invalid_transformation_raises_error(self):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.mime_type = 'text/x-html-safe'
comment1.text = 'para'
comment1 = createObject("plone.Comment")
comment1.mime_type = "text/x-html-safe"
comment1.text = "para"
conversation.addComment(comment1)
self.assertEqual(
comment1.getText(targetMimetype='text/html'),
'para')
self.assertEqual(comment1.getText(targetMimetype="text/html"), "para")
def test_traversal(self):
# make sure comments are traversable, have an id, absolute_url and
@ -232,26 +227,29 @@ class CommentTest(unittest.TestCase):
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
new_comment1_id = conversation.addComment(comment1)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment1_id),
"++conversation++default/{0}".format(new_comment1_id),
)
self.assertTrue(IComment.providedBy(comment))
self.assertEqual(
(
'', 'plone', 'doc1', '++conversation++default',
"",
"plone",
"doc1",
"++conversation++default",
str(new_comment1_id),
),
comment.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default/' +
str(new_comment1_id), comment.absolute_url(),
"http://nohost/plone/doc1/++conversation++default/" + str(new_comment1_id),
comment.absolute_url(),
)
def test_view_blob_types(self):
@ -260,68 +258,67 @@ class CommentTest(unittest.TestCase):
version of the url with a /view in it.
"""
self.portal.invokeFactory(
id='image1',
title='Image',
type_name='Image',
id="image1",
title="Image",
type_name="Image",
)
conversation = IConversation(self.portal.image1)
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
new_comment1_id = conversation.addComment(comment1)
comment = self.portal.image1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment1_id),
"++conversation++default/{0}".format(new_comment1_id),
)
view = View(comment, self.request)
View.__call__(view)
response = self.request.response
self.assertIn('/view', response.headers['location'])
self.assertIn("/view", response.headers["location"])
def test_workflow(self):
"""Basic test for the 'comment_review_workflow'
"""
"""Basic test for the 'comment_review_workflow'"""
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow,'),
("Discussion Item",),
("comment_review_workflow,"),
)
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
new_comment1_id = conversation.addComment(comment1)
comment = conversation[new_comment1_id]
# Make sure comments use the 'comment_review_workflow'
chain = self.portal.portal_workflow.getChainFor(comment)
self.assertEqual(('comment_review_workflow',), chain)
self.assertEqual(("comment_review_workflow",), chain)
# Ensure the initial state was entered and recorded
self.assertEqual(
1,
len(comment.workflow_history['comment_review_workflow']),
len(comment.workflow_history["comment_review_workflow"]),
)
self.assertEqual(
None,
comment.workflow_history['comment_review_workflow'][0]['action'],
comment.workflow_history["comment_review_workflow"][0]["action"],
)
self.assertEqual(
'pending',
self.portal.portal_workflow.getInfoFor(comment, 'review_state'),
"pending",
self.portal.portal_workflow.getInfoFor(comment, "review_state"),
)
def test_fti(self):
# test that we can look up an FTI for Discussion Item
self.assertIn(
'Discussion Item',
"Discussion Item",
self.portal.portal_types.objectIds(),
)
comment1 = createObject('plone.Comment')
comment1 = createObject("plone.Comment")
fti = self.portal.portal_types.getTypeInfo(comment1)
self.assertEqual('Discussion Item', fti.getTypeInfo(comment1).getId())
self.assertEqual("Discussion Item", fti.getTypeInfo(comment1).getId())
def test_view(self):
# make sure that the comment view is there and redirects to the right
@ -332,21 +329,21 @@ class CommentTest(unittest.TestCase):
conversation = IConversation(self.portal.doc1)
# Create a comment
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
# Add comment to the conversation
new_comment1_id = conversation.addComment(comment1)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_comment1_id),
"++conversation++default/{0}".format(new_comment1_id),
)
# make sure the view is there
self.assertTrue(
getMultiAdapter(
(comment, self.request),
name='view',
name="view",
),
)
@ -364,11 +361,11 @@ class RepliesTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
def test_add_comment(self):
# Add comments to a CommentReplies adapter
@ -380,16 +377,16 @@ class RepliesTest(unittest.TestCase):
# Add a comment to the conversation
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
# Add a reply to the CommentReplies adapter of the first comment
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
@ -417,16 +414,16 @@ class RepliesTest(unittest.TestCase):
# Add a comment to the conversation
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
# Add a reply to the CommentReplies adapter of the first comment
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
@ -448,83 +445,86 @@ class RepliesTest(unittest.TestCase):
# physical path
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
conversation.addComment(comment1)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = conversation.addComment(comment)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
# Add a reply to the CommentReplies adapter of the first comment
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
new_re_id = replies.addComment(re_comment)
re_comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_re_id),
"++conversation++default/{0}".format(new_re_id),
)
# Add a reply to the reply
re_re_comment = createObject('plone.Comment')
re_re_comment.text = 'Comment text'
re_re_comment = createObject("plone.Comment")
re_re_comment.text = "Comment text"
replies = IReplies(re_comment)
new_re_re_id = replies.addComment(re_re_comment)
re_re_comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_re_re_id),
"++conversation++default/{0}".format(new_re_re_id),
)
# Add a reply to the replies reply
re_re_re_comment = createObject('plone.Comment')
re_re_re_comment.text = 'Comment text'
re_re_re_comment = createObject("plone.Comment")
re_re_re_comment.text = "Comment text"
replies = IReplies(re_re_comment)
new_re_re_re_id = replies.addComment(re_re_re_comment)
re_re_re_comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_re_re_re_id),
"++conversation++default/{0}".format(new_re_re_re_id),
)
self.assertEqual(
('', 'plone', 'doc1', '++conversation++default', str(new_id)),
("", "plone", "doc1", "++conversation++default", str(new_id)),
comment.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default/' +
str(new_id), comment.absolute_url(),
"http://nohost/plone/doc1/++conversation++default/" + str(new_id),
comment.absolute_url(),
)
self.assertEqual(
('', 'plone', 'doc1', '++conversation++default', str(new_re_id)),
("", "plone", "doc1", "++conversation++default", str(new_re_id)),
re_comment.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default/' +
str(new_re_id),
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_id),
re_comment.absolute_url(),
)
self.assertEqual(
(
'', 'plone', 'doc1', '++conversation++default',
"",
"plone",
"doc1",
"++conversation++default",
str(new_re_re_id),
),
re_re_comment.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default/' +
str(new_re_re_id),
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_id),
re_re_comment.absolute_url(),
)
self.assertEqual(
(
'', 'plone', 'doc1', '++conversation++default',
"",
"plone",
"doc1",
"++conversation++default",
str(new_re_re_re_id),
),
re_re_re_comment.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default/' +
str(new_re_re_re_id),
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_re_id),
re_re_re_comment.absolute_url(),
)

View File

@ -40,23 +40,23 @@ class TestCommentForm(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
self.folder = self.portal['test-folder']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal.invokeFactory("Folder", "test-folder")
self.folder = self.portal["test-folder"]
interface.alsoProvides(
self.portal.REQUEST,
interfaces.IDiscussionLayer,
)
wftool = getToolByName(self.portal, 'portal_workflow')
wftool.doActionFor(self.portal.doc1, action='publish')
wftool = getToolByName(self.portal, "portal_workflow")
wftool.doActionFor(self.portal.doc1, action="publish")
self.portal.doc1.allow_discussion = True
self.membershipTool = getToolByName(self.folder, 'portal_membership')
self.membershipTool = getToolByName(self.folder, "portal_membership")
self.memberdata = self.portal.portal_memberdata
self.context = getattr(self.portal, 'doc1')
self.context = getattr(self.portal, "doc1")
# Allow discussion
registry = queryUtility(IRegistry)
@ -64,8 +64,7 @@ class TestCommentForm(unittest.TestCase):
settings.globally_enabled = True
def test_add_comment(self):
"""Post a comment as logged-in user.
"""
"""Post a comment as logged-in user."""
# Allow discussion
self.portal.doc1.allow_discussion = True
@ -82,7 +81,7 @@ class TestCommentForm(unittest.TestCase):
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form',
name=u"comment-form",
)
# The form should return an error if the comment text field is empty
@ -90,46 +89,45 @@ class TestCommentForm(unittest.TestCase):
commentForm = getMultiAdapter(
(self.context, request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 1)
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
# The form is submitted successfully, if the required text field is
# filled out
request = make_request(form={'form.widgets.text': u'bar'})
request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter(
(self.context, request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
comments = IConversation(commentForm.context).getComments()
comments = [comment for comment in comments] # consume iterator
self.assertEqual(len(comments), 1)
for comment in comments:
self.assertEqual(comment.text, u'bar')
self.assertEqual(comment.creator, 'test_user_1_')
self.assertEqual(comment.getOwner().getUserName(), 'test-user')
self.assertEqual(comment.text, u"bar")
self.assertEqual(comment.creator, "test_user_1_")
self.assertEqual(comment.getOwner().getUserName(), "test-user")
local_roles = comment.get_local_roles()
self.assertEqual(len(local_roles), 1)
userid, roles = local_roles[0]
self.assertEqual(userid, 'test_user_1_')
self.assertEqual(userid, "test_user_1_")
self.assertEqual(len(roles), 1)
self.assertEqual(roles[0], 'Owner')
self.assertEqual(roles[0], "Owner")
def test_edit_comment(self):
"""Edit a comment as logged-in user.
"""
"""Edit a comment as logged-in user."""
# Allow discussion
self.portal.doc1.allow_discussion = True
@ -146,65 +144,64 @@ class TestCommentForm(unittest.TestCase):
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form',
name=u"comment-form",
)
provideAdapter(
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=EditCommentForm,
name=u'edit-comment-form',
name=u"edit-comment-form",
)
# The form is submitted successfully, if the required text field is
# filled out
request = make_request(form={'form.widgets.text': u'bar'})
request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter(
(self.context, request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
# Edit the last comment
conversation = IConversation(self.context)
comment = [x for x in conversation.getComments()][-1]
request = make_request(form={'form.widgets.text': u'foobar'})
request = make_request(form={"form.widgets.text": u"foobar"})
editForm = getMultiAdapter(
(comment, request),
name=u'edit-comment-form',
name=u"edit-comment-form",
)
editForm.update()
data, errors = editForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(editForm.handleComment(editForm, 'foo'))
self.assertFalse(editForm.handleComment(editForm, "foo"))
comment = [x for x in conversation.getComments()][-1]
self.assertEqual(comment.text, u'foobar')
self.assertEqual(comment.text, u"foobar")
comments = IConversation(commentForm.context).getComments()
comments = [c for c in comments] # consume iterator
self.assertEqual(len(comments), 1)
for comment in comments:
self.assertEqual(comment.text, u'foobar')
self.assertEqual(comment.creator, 'test_user_1_')
self.assertEqual(comment.text, u"foobar")
self.assertEqual(comment.creator, "test_user_1_")
self.assertEqual(comment.getOwner().getUserName(), 'test-user')
self.assertEqual(comment.getOwner().getUserName(), "test-user")
local_roles = comment.get_local_roles()
self.assertEqual(len(local_roles), 1)
userid, roles = local_roles[0]
self.assertEqual(userid, 'test_user_1_')
self.assertEqual(userid, "test_user_1_")
self.assertEqual(len(roles), 1)
self.assertEqual(roles[0], 'Owner')
self.assertEqual(roles[0], "Owner")
def test_delete_comment(self):
"""Delete a comment as logged-in user.
"""
"""Delete a comment as logged-in user."""
# Allow discussion
self.portal.doc1.allow_discussion = True
@ -221,48 +218,47 @@ class TestCommentForm(unittest.TestCase):
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form',
name=u"comment-form",
)
# The form is submitted successfully, if the required text field is
# filled out
form_request = make_request(form={'form.widgets.text': u'bar'})
form_request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter(
(self.context, form_request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
# Delete the last comment
conversation = IConversation(self.context)
comment = [x for x in conversation.getComments()][-1]
deleteView = getMultiAdapter(
(comment, self.request),
name=u'moderate-delete-comment',
name=u"moderate-delete-comment",
)
# try to delete last comment without 'Delete comments' permission
setRoles(self.portal, TEST_USER_ID, ['Member'])
setRoles(self.portal, TEST_USER_ID, ["Member"])
self.assertRaises(
Unauthorized,
comment.restrictedTraverse,
'@@moderate-delete-comment',
"@@moderate-delete-comment",
)
deleteView()
self.assertEqual(1, len([x for x in conversation.getComments()]))
# try to delete last comment with 'Delete comments' permission
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
deleteView()
self.assertEqual(0, len([x for x in conversation.getComments()]))
setRoles(self.portal, TEST_USER_ID, ['Manager'])
setRoles(self.portal, TEST_USER_ID, ["Manager"])
def test_delete_own_comment(self):
"""Delete own comment as logged-in user.
"""
"""Delete own comment as logged-in user."""
# Allow discussion
self.portal.doc1.allow_discussion = True
@ -279,42 +275,42 @@ class TestCommentForm(unittest.TestCase):
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form',
name=u"comment-form",
)
# The form is submitted successfully, if the required text field is
# filled out
form_request = make_request(form={'form.widgets.text': u'bar'})
form_request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter(
(self.context, form_request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
# Delete the last comment
conversation = IConversation(self.context)
comment = [x for x in conversation.getComments()][-1]
deleteView = getMultiAdapter(
(comment, self.request),
name=u'delete-own-comment',
name=u"delete-own-comment",
)
# try to delete last comment with johndoe
setRoles(self.portal, 'johndoe', ['Member'])
login(self.portal, 'johndoe')
setRoles(self.portal, "johndoe", ["Member"])
login(self.portal, "johndoe")
self.assertRaises(
Unauthorized,
comment.restrictedTraverse,
'@@delete-own-comment',
"@@delete-own-comment",
)
self.assertEqual(1, len([x for x in conversation.getComments()]))
# try to delete last comment with the same user that created it
login(self.portal, TEST_USER_NAME)
setRoles(self.portal, TEST_USER_ID, ['Member'])
setRoles(self.portal, TEST_USER_ID, ["Member"])
deleteView()
self.assertEqual(0, len([x for x in conversation.getComments()]))
@ -337,40 +333,43 @@ class TestCommentForm(unittest.TestCase):
alsoProvides(request, IAttributeAnnotatable)
return request
provideAdapter(adapts=(Interface, IBrowserRequest),
provideAdapter(
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form')
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',
})
request = make_request(
form={
"form.widgets.name": u"john doe",
"form.widgets.text": u"bar",
}
)
commentForm = getMultiAdapter(
(self.context, request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
self.assertEqual(len(errors), 0)
self.assertFalse(commentForm.handleComment(commentForm, 'action'))
self.assertFalse(commentForm.handleComment(commentForm, "action"))
comments = IConversation(commentForm.context).getComments()
comments = [comment for comment in comments] # consume itertor
self.assertEqual(len(comments), 1)
for comment in IConversation(commentForm.context).getComments():
self.assertEqual(comment.text, u'bar')
self.assertEqual(comment.text, u"bar")
self.assertIsNone(comment.creator)
roles = comment.get_local_roles()
self.assertEqual(len(roles), 0)
def test_can_not_add_comments_if_discussion_is_not_allowed(self):
"""Make sure that comments can't be posted if discussion is disabled.
"""
"""Make sure that comments can't be posted if discussion is disabled."""
# Disable discussion
registry = queryUtility(IRegistry)
@ -384,16 +383,18 @@ class TestCommentForm(unittest.TestCase):
alsoProvides(request, IAttributeAnnotatable)
return request
provideAdapter(adapts=(Interface, IBrowserRequest),
provideAdapter(
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form')
name=u"comment-form",
)
request = make_request(form={'form.widgets.text': u'bar'})
request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter(
(self.context, request),
name=u'comment-form',
name=u"comment-form",
)
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
@ -402,10 +403,7 @@ class TestCommentForm(unittest.TestCase):
# allowed
self.assertEqual(len(errors), 0)
self.assertRaises(Unauthorized,
commentForm.handleComment,
commentForm,
'foo')
self.assertRaises(Unauthorized, 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
@ -423,15 +421,16 @@ class TestCommentForm(unittest.TestCase):
alsoProvides(request, IAttributeAnnotatable)
return request
provideAdapter(adapts=(Interface, IBrowserRequest),
provideAdapter(
adapts=(Interface, IBrowserRequest),
provides=Interface,
factory=CommentForm,
name=u'comment-form')
name=u"comment-form",
)
request = make_request(form={'form.widgets.text': u'bar'})
request = make_request(form={"form.widgets.text": u"bar"})
commentForm = getMultiAdapter((self.context, request),
name=u'comment-form')
commentForm = getMultiAdapter((self.context, request), name=u"comment-form")
commentForm.update()
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
@ -440,7 +439,7 @@ class TestCommentForm(unittest.TestCase):
Unauthorized,
commentForm.handleComment,
commentForm,
'foo',
"foo",
)
@ -449,22 +448,22 @@ class TestCommentsViewlet(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
self.folder = self.portal['test-folder']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal.invokeFactory("Folder", "test-folder")
self.folder = self.portal["test-folder"]
interface.alsoProvides(
self.request,
interfaces.IDiscussionLayer,
)
self.workflowTool = getToolByName(self.portal, 'portal_workflow')
self.workflowTool.setDefaultChain('comment_one_state_workflow')
self.workflowTool = getToolByName(self.portal, "portal_workflow")
self.workflowTool.setDefaultChain("comment_one_state_workflow")
self.membershipTool = getToolByName(self.folder, 'portal_membership')
self.membershipTool = getToolByName(self.folder, "portal_membership")
self.memberdata = self.portal.portal_memberdata
context = getattr(self.portal, 'doc1')
context = getattr(self.portal, "doc1")
self.viewlet = CommentsViewlet(context, self.request, None, None)
# Allow discussion
@ -486,9 +485,8 @@ class TestCommentsViewlet(unittest.TestCase):
# Anonymous has no 'can review' permission
self.assertFalse(self.viewlet.can_review())
# The reviewer role has the 'Review comments' permission
self.portal.acl_users._doAddUser(
'reviewer', 'secret', ['Reviewer'], [])
login(self.portal, 'reviewer')
self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], [])
login(self.portal, "reviewer")
self.assertTrue(self.viewlet.can_review())
def test_can_manage(self):
@ -502,9 +500,8 @@ class TestCommentsViewlet(unittest.TestCase):
# Anonymous has no 'can review' permission
self.assertFalse(self.viewlet.can_manage())
# The reviewer role has the 'Review comments' permission
self.portal.acl_users._doAddUser(
'reviewer', 'secret', ['Reviewer'], [])
login(self.portal, 'reviewer')
self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], [])
login(self.portal, "reviewer")
self.assertTrue(self.viewlet.can_manage())
def test_is_discussion_allowed(self):
@ -521,46 +518,48 @@ class TestCommentsViewlet(unittest.TestCase):
self.assertTrue(self.viewlet.comment_transform_message())
self.assertEqual(
self.viewlet.comment_transform_message(),
'You can add a comment by filling out the form below. Plain ' +
'text formatting.')
"You can add a comment by filling out the form below. Plain "
+ "text formatting.",
)
# Set text transform to intelligent text
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
settings.text_transform = 'text/x-web-intelligent'
settings.text_transform = "text/x-web-intelligent"
# Make sure the comment description changes accordingly
self.assertEqual(
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.',
"You can add a comment by filling out the form below. "
+ "Plain text formatting. Web and email addresses are transformed "
+ "into clickable links.",
)
# Enable moderation workflow
self.workflowTool.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow,'))
("Discussion Item",), ("comment_review_workflow,")
)
# Make sure the comment description shows that comments are moderated
self.assertEqual(
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. Comments are moderated.')
"You can add a comment by filling out the form below. "
+ "Plain text formatting. Web and email addresses are transformed "
+ "into clickable links. Comments are moderated.",
)
def test_has_replies(self):
self.assertEqual(self.viewlet.has_replies(), False)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
conversation = IConversation(self.portal.doc1)
conversation.addComment(comment)
self.assertEqual(self.viewlet.has_replies(), True)
def test_get_replies(self):
self.assertFalse(self.viewlet.get_replies())
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
conversation = IConversation(self.portal.doc1)
conversation.addComment(comment)
conversation.addComment(comment)
@ -583,8 +582,8 @@ class TestCommentsViewlet(unittest.TestCase):
def test_get_replies_with_workflow_actions(self):
self.assertFalse(self.viewlet.get_replies(workflow_actions=True))
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
conversation = IConversation(self.portal.doc1)
c1 = conversation.addComment(comment)
self.assertEqual(
@ -593,32 +592,34 @@ class TestCommentsViewlet(unittest.TestCase):
)
# Enable moderation workflow
self.workflowTool.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow,'),
("Discussion Item",),
("comment_review_workflow,"),
)
# Check if workflow actions are available
reply = next(self.viewlet.get_replies(workflow_actions=True))
self.assertTrue('actions' in reply)
self.assertTrue("actions" in reply)
self.assertEqual(
reply['actions'][0]['id'],
'mark_as_spam',
reply["actions"][0]["id"],
"mark_as_spam",
)
expected_url = (
"http://nohost/plone/doc1/++conversation++default/{0}"
"/content_status_modify?workflow_action=mark_as_spam"
)
expected_url = 'http://nohost/plone/doc1/++conversation++default/{0}' \
'/content_status_modify?workflow_action=mark_as_spam'
self.assertEqual(
reply['actions'][0]['url'],
reply["actions"][0]["url"],
expected_url.format(int(c1)),
)
def test_get_commenter_home_url(self):
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
IConversation(self.portal.doc1)
portal_membership = getToolByName(self.portal, 'portal_membership')
portal_membership = getToolByName(self.portal, "portal_membership")
m = portal_membership.getAuthenticatedMember()
self.assertEqual(
self.viewlet.get_commenter_home_url(m.getUserName()),
'http://nohost/plone/author/test-user',
"http://nohost/plone/author/test-user",
)
def test_get_commenter_home_url_is_none(self):
@ -627,72 +628,77 @@ class TestCommentsViewlet(unittest.TestCase):
def test_get_commenter_portrait(self):
# Add a user with a member image
self.membershipTool.addMember('jim', 'Jim', ['Member'], [])
self.memberdata._setPortrait(Image(
id='jim',
self.membershipTool.addMember("jim", "Jim", ["Member"], [])
self.memberdata._setPortrait(
Image(
id="jim",
file=dummy.File(),
title='',
), 'jim')
self.assertEqual(
self.memberdata._getPortrait('jim').getId(),
'jim',
title="",
),
"jim",
)
self.assertEqual(
self.memberdata._getPortrait('jim').meta_type,
'Image',
self.memberdata._getPortrait("jim").getId(),
"jim",
)
self.assertEqual(
self.memberdata._getPortrait("jim").meta_type,
"Image",
)
# Add a conversation with a comment
conversation = IConversation(self.portal.doc1)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment.Creator = 'Jim'
comment.author_username = 'jim'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.Creator = "Jim"
comment.author_username = "jim"
conversation.addComment(comment)
# Call get_commenter_portrait method of the viewlet
self.viewlet.update()
portrait_url = self.viewlet.get_commenter_portrait('jim')
portrait_url = self.viewlet.get_commenter_portrait("jim")
# Check if the correct member image URL is returned
self.assertEqual(
portrait_url,
'http://nohost/plone/portal_memberdata/portraits/jim',
"http://nohost/plone/portal_memberdata/portraits/jim",
)
def test_get_commenter_portrait_is_none(self):
self.assertTrue(
self.viewlet.get_commenter_portrait() in (
'defaultUser.png',
'defaultUser.gif',
self.viewlet.get_commenter_portrait()
in (
"defaultUser.png",
"defaultUser.gif",
),
)
def test_get_commenter_portrait_without_userimage(self):
# Create a user without a user image
self.membershipTool.addMember('jim', 'Jim', ['Member'], [])
self.membershipTool.addMember("jim", "Jim", ["Member"], [])
# Add a conversation with a comment
conversation = IConversation(self.portal.doc1)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment.Creator = 'Jim'
comment.author_username = 'jim'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.Creator = "Jim"
comment.author_username = "jim"
conversation.addComment(comment)
# Call get_commenter_portrait method of the viewlet
self.viewlet.update()
portrait_url = self.viewlet.get_commenter_portrait('jim')
portrait_url = self.viewlet.get_commenter_portrait("jim")
# Check if the correct default member image URL is returned.
# Note that Products.PlonePAS 4.0.5 and later have .png and
# earlier versions have .gif.
self.assertTrue(
portrait_url in (
'http://nohost/plone/defaultUser.png',
'http://nohost/plone/defaultUser.gif',
portrait_url
in (
"http://nohost/plone/defaultUser.png",
"http://nohost/plone/defaultUser.gif",
),
)
@ -702,8 +708,8 @@ class TestCommentsViewlet(unittest.TestCase):
# Allow anonymous discussion
registry = queryUtility(IRegistry)
registry[
'plone.app.discussion.interfaces.IDiscussionSettings.' +
'anonymous_comments'
"plone.app.discussion.interfaces.IDiscussionSettings."
+ "anonymous_comments"
] = True
# Test if anonymous discussion is allowed for the viewlet
self.assertTrue(self.viewlet.anonymous_discussion_allowed())
@ -712,8 +718,8 @@ class TestCommentsViewlet(unittest.TestCase):
self.assertTrue(self.viewlet.show_commenter_image())
registry = queryUtility(IRegistry)
registry[
'plone.app.discussion.interfaces.IDiscussionSettings.' +
'show_commenter_image'
"plone.app.discussion.interfaces.IDiscussionSettings."
+ "show_commenter_image"
] = False
self.assertFalse(self.viewlet.show_commenter_image())
@ -726,7 +732,7 @@ class TestCommentsViewlet(unittest.TestCase):
self.viewlet.update()
self.assertEqual(
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):
@ -739,9 +745,8 @@ class TestCommentsViewlet(unittest.TestCase):
# a correct utc time that can be used to make datetime set the utc
# time of the local time given above. That way, the time for the
# example below is correct within each time zone, independent of DST
python_time = datetime(
*time.gmtime(time.mktime(python_time.timetuple()))[:7])
python_time = datetime(*time.gmtime(time.mktime(python_time.timetuple()))[:7])
localized_time = self.viewlet.format_time(python_time)
self.assertTrue(
localized_time in ['Feb 01, 2009 11:32 PM', '2009-02-01 23:32'],
localized_time in ["Feb 01, 2009 11:32 PM", "2009-02-01 23:32"],
)

View File

@ -19,31 +19,33 @@ import unittest
class CommentContentRulesTest(unittest.TestCase):
""" Test custom comments events
"""
"""Test custom comments events"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
# Setup sandbox
self.portal = self.layer['portal']
self.request = self.layer['request']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
# Setup current user properties
member = self.portal.portal_membership.getMemberById(TEST_USER_ID)
member.setMemberProperties({
'fullname': 'X Manager',
'email': 'xmanager@example.com',
})
member.setMemberProperties(
{
"fullname": "X Manager",
"email": "xmanager@example.com",
}
)
setRoles(self.portal, TEST_USER_ID, ['Manager'])
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.document = self.portal['doc1']
self.document = self.portal["doc1"]
comment = createObject('plone.Comment')
comment.text = 'This is a comment'
comment.author_username = 'jim'
comment.author_name = 'Jim'
comment.author_email = 'jim@example.com'
comment = createObject("plone.Comment")
comment.text = "This is a comment"
comment.author_username = "jim"
comment.author_name = "Jim"
comment.author_email = "jim@example.com"
conversation = IConversation(self.document)
conversation.addComment(comment)
@ -54,58 +56,61 @@ class CommentContentRulesTest(unittest.TestCase):
self.assertTrue(IRuleEventType.providedBy(IReplyRemovedEvent))
def testCommentIdStringSubstitution(self):
comment_id = getAdapter(self.document, IStringSubstitution,
name=u'comment_id')
comment_id = getAdapter(self.document, IStringSubstitution, name=u"comment_id")
self.assertIsInstance(comment_id(), int)
def testCommentTextStringSubstitution(self):
comment_text = getAdapter(self.document, IStringSubstitution,
name=u'comment_text')
self.assertEqual(comment_text(), u'This is a comment')
comment_text = getAdapter(
self.document, IStringSubstitution, name=u"comment_text"
)
self.assertEqual(comment_text(), u"This is a comment")
def testCommentUserIdStringSubstitution(self):
comment_user_id = getAdapter(self.document, IStringSubstitution,
name=u'comment_user_id')
self.assertEqual(comment_user_id(), u'jim')
comment_user_id = getAdapter(
self.document, IStringSubstitution, name=u"comment_user_id"
)
self.assertEqual(comment_user_id(), u"jim")
def testCommentUserFullNameStringSubstitution(self):
comment_user_fullname = getAdapter(self.document, IStringSubstitution,
name=u'comment_user_fullname')
self.assertEqual(comment_user_fullname(), u'Jim')
comment_user_fullname = getAdapter(
self.document, IStringSubstitution, name=u"comment_user_fullname"
)
self.assertEqual(comment_user_fullname(), u"Jim")
def testCommentUserEmailStringSubstitution(self):
comment_user_email = getAdapter(self.document, IStringSubstitution,
name=u'comment_user_email')
self.assertEqual(comment_user_email(), u'jim@example.com')
comment_user_email = getAdapter(
self.document, IStringSubstitution, name=u"comment_user_email"
)
self.assertEqual(comment_user_email(), u"jim@example.com")
class ReplyContentRulesTest(unittest.TestCase):
""" Test custom comments events
"""
"""Test custom comments events"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
# Setup sandbox
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.document = self.portal['doc1']
self.document = self.portal["doc1"]
conversation = IConversation(self.document)
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'This is a comment'
comment = createObject("plone.Comment")
comment.text = "This is a comment"
new_id = replies.addComment(comment)
comment = self.document.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
re_comment = createObject('plone.Comment')
re_comment.text = 'This is a reply'
re_comment.author_username = 'julia'
re_comment.author_name = 'Juliana'
re_comment.author_email = 'julia@example.com'
re_comment = createObject("plone.Comment")
re_comment.text = "This is a reply"
re_comment.author_username = "julia"
re_comment.author_name = "Juliana"
re_comment.author_email = "julia@example.com"
replies = IReplies(comment)
replies.addComment(re_comment)
@ -114,7 +119,7 @@ class ReplyContentRulesTest(unittest.TestCase):
reply_id = getAdapter(
self.document,
IStringSubstitution,
name=u'comment_id',
name=u"comment_id",
)
self.assertIsInstance(reply_id(), int)
@ -122,30 +127,30 @@ class ReplyContentRulesTest(unittest.TestCase):
reply_text = getAdapter(
self.document,
IStringSubstitution,
name=u'comment_text',
name=u"comment_text",
)
self.assertEqual(reply_text(), u'This is a reply')
self.assertEqual(reply_text(), u"This is a reply")
def testReplyUserIdStringSubstitution(self):
reply_user_id = getAdapter(
self.document,
IStringSubstitution,
name=u'comment_user_id',
name=u"comment_user_id",
)
self.assertEqual(reply_user_id(), u'julia')
self.assertEqual(reply_user_id(), u"julia")
def testReplyUserFullNameStringSubstitution(self):
reply_user_fullname = getAdapter(
self.document,
IStringSubstitution,
name=u'comment_user_fullname',
name=u"comment_user_fullname",
)
self.assertEqual(reply_user_fullname(), u'Juliana')
self.assertEqual(reply_user_fullname(), u"Juliana")
def testReplyUserEmailStringSubstitution(self):
reply_user_email = getAdapter(
self.document,
IStringSubstitution,
name=u'comment_user_email',
name=u"comment_user_email",
)
self.assertEqual(reply_user_email(), u'julia@example.com')
self.assertEqual(reply_user_email(), u"julia@example.com")

View File

@ -19,8 +19,8 @@ class RegistryTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.registry = Registry()
self.registry.registerInterface(IDiscussionSettings)
@ -31,99 +31,100 @@ class RegistryTest(unittest.TestCase):
def test_discussion_controlpanel_view(self):
view = getMultiAdapter(
(self.portal, self.portal.REQUEST),
name='discussion-controlpanel',
name="discussion-controlpanel",
)
self.assertTrue(view())
def test_discussion_in_controlpanel(self):
# Check if discussion is in the control panel
self.controlpanel = getToolByName(self.portal, 'portal_controlpanel')
self.controlpanel = getToolByName(self.portal, "portal_controlpanel")
self.assertTrue(
'discussion' in [
a.getAction(self)['id']
for a in self.controlpanel.listActions()
],
"discussion"
in [a.getAction(self)["id"] for a in self.controlpanel.listActions()],
)
def test_globally_enabled(self):
# Check globally_enabled record
self.assertTrue('globally_enabled' in IDiscussionSettings)
self.assertTrue("globally_enabled" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.globally_enabled'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.globally_enabled"
],
False,
)
def test_anonymous_comments(self):
# Check anonymous_comments record
self.assertTrue('anonymous_comments' in IDiscussionSettings)
self.assertTrue("anonymous_comments" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.anonymous_comments'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.anonymous_comments"
],
False,
)
def test_moderation_enabled(self):
# Check globally_enabled record
self.assertTrue('moderation_enabled' in IDiscussionSettings)
self.assertTrue("moderation_enabled" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.moderation_enabled'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.moderation_enabled"
],
False,
)
def test_edit_comment_enabled(self):
# Check edit_comment_enabled record
self.assertTrue('edit_comment_enabled' in IDiscussionSettings)
self.assertTrue("edit_comment_enabled" in IDiscussionSettings)
self.assertEqual(
self.registry['plone.app.discussion.interfaces.' +
'IDiscussionSettings.edit_comment_enabled'],
self.registry[
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.edit_comment_enabled"
],
False,
)
def test_delete_own_comment_enabled(self):
# Check delete_own_comment_enabled record
self.assertTrue('delete_own_comment_enabled' in IDiscussionSettings)
self.assertTrue("delete_own_comment_enabled" in IDiscussionSettings)
self.assertEqual(
self.registry['plone.app.discussion.interfaces.' +
'IDiscussionSettings.delete_own_comment_enabled'],
self.registry[
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.delete_own_comment_enabled"
],
False,
)
def test_text_transform(self):
self.assertTrue('text_transform' in IDiscussionSettings)
self.assertTrue("text_transform" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.text_transform'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.text_transform"
],
'text/plain',
"text/plain",
)
def test_captcha(self):
# Check globally_enabled record
self.assertTrue('captcha' in IDiscussionSettings)
self.assertTrue("captcha" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.captcha'
"plone.app.discussion.interfaces." + "IDiscussionSettings.captcha"
],
'disabled',
"disabled",
)
def test_show_commenter_image(self):
# Check show_commenter_image record
self.assertTrue('show_commenter_image' in IDiscussionSettings)
self.assertTrue("show_commenter_image" in IDiscussionSettings)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.show_commenter_image'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.show_commenter_image"
],
True,
)
@ -131,12 +132,12 @@ class RegistryTest(unittest.TestCase):
def test_moderator_notification_enabled(self):
# Check show_commenter_image record
self.assertTrue(
'moderator_notification_enabled' in IDiscussionSettings,
"moderator_notification_enabled" in IDiscussionSettings,
)
self.assertEqual(
self.registry[
'plone.app.discussion.interfaces.' +
'IDiscussionSettings.moderator_notification_enabled'
"plone.app.discussion.interfaces."
+ "IDiscussionSettings.moderator_notification_enabled"
],
False,
)
@ -156,8 +157,8 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
registry = queryUtility(IRegistry)
self.settings = registry.forInterface(IDiscussionSettings, check=False)
@ -169,9 +170,9 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# By default the comment_one_state_workflow without moderation is
# enabled
self.assertEqual(
('comment_one_state_workflow',),
("comment_one_state_workflow",),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item',
"Discussion Item",
),
)
@ -181,17 +182,17 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# Make sure the comment_review_workflow with moderation enabled is
# enabled
self.assertEqual(
('comment_review_workflow',),
("comment_review_workflow",),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item',
"Discussion Item",
),
)
# And back
self.settings.moderation_enabled = False
self.assertEqual(
('comment_one_state_workflow',),
("comment_one_state_workflow",),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item',
"Discussion Item",
),
)
@ -205,8 +206,8 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# Enable the 'comment_review_workflow' with moderation enabled
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow',),
("Discussion Item",),
("comment_review_workflow",),
)
# Make sure the moderation_enabled settings has changed
@ -214,15 +215,15 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# Enable the 'comment_review_workflow' with moderation enabled
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_one_state_workflow',),
("Discussion Item",),
("comment_one_state_workflow",),
)
self.settings.moderation_enabled = True
# Enable a 'custom' discussion workflow
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('intranet_workflow',),
("Discussion Item",),
("intranet_workflow",),
)
# Setting has not changed. A Custom workflow disables the

View File

@ -27,6 +27,7 @@ import unittest
try:
from plone.dexterity.interfaces import IDexterityContent
DEXTERITY = True
except ImportError:
DEXTERITY = False
@ -37,15 +38,14 @@ class ConversationTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
interface.alsoProvides(
self.portal.REQUEST, interfaces.IDiscussionLayer)
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
interface.alsoProvides(self.portal.REQUEST, interfaces.IDiscussionLayer)
self.typetool = self.portal.portal_types
self.portal_discussion = getToolByName(
self.portal,
'portal_discussion',
"portal_discussion",
None,
)
# Allow discussion
@ -54,7 +54,7 @@ class ConversationTest(unittest.TestCase):
settings.globally_enabled = True
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
def test_add_comment(self):
# Create a conversation. In this case we doesn't assign it to an
@ -64,8 +64,8 @@ class ConversationTest(unittest.TestCase):
# 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')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = conversation.addComment(comment)
@ -81,20 +81,19 @@ class ConversationTest(unittest.TestCase):
self.assertEqual(len(tuple(conversation.getThreads())), 1)
self.assertEqual(conversation.total_comments(), 1)
self.assertTrue(
conversation.last_comment_date - datetime.utcnow() <
timedelta(seconds=1),
conversation.last_comment_date - datetime.utcnow() < timedelta(seconds=1),
)
def test_private_comment(self):
conversation = IConversation(self.portal.doc1)
comment = createObject('plone.Comment')
comment.author_username = 'nobody'
comment = createObject("plone.Comment")
comment.author_username = "nobody"
conversation.addComment(comment)
comment.manage_permission('View', roles=tuple())
comment.manage_permission("View", roles=tuple())
self.assertEqual(0, conversation.total_comments())
self.assertEqual(None, conversation.last_comment_date)
self.assertEqual(['nobody'], list(conversation.commentators))
self.assertEqual(["nobody"], list(conversation.commentators))
self.assertEqual([], list(conversation.public_commentators))
def test_delete_comment(self):
@ -105,8 +104,8 @@ class ConversationTest(unittest.TestCase):
# 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')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = conversation.addComment(comment)
@ -141,23 +140,23 @@ class ConversationTest(unittest.TestCase):
# +- Comment 2_1
# Create all comments
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1_1 = createObject('plone.Comment')
comment1_1.text = 'Comment text'
comment1_1 = createObject("plone.Comment")
comment1_1.text = "Comment text"
comment1_1_1 = createObject('plone.Comment')
comment1_1_1.text = 'Comment text'
comment1_1_1 = createObject("plone.Comment")
comment1_1_1.text = "Comment text"
comment1_2 = createObject('plone.Comment')
comment1_2.text = 'Comment text'
comment1_2 = createObject("plone.Comment")
comment1_2.text = "Comment text"
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment2_1 = createObject('plone.Comment')
comment2_1.text = 'Comment text'
comment2_1 = createObject("plone.Comment")
comment2_1.text = "Comment text"
# Create the nested comment structure
new_id_1 = conversation.addComment(comment1)
@ -177,21 +176,24 @@ class ConversationTest(unittest.TestCase):
del conversation[new_id_1]
self.assertEqual([
{'comment': comment2, 'depth': 0, 'id': new_id_2},
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
], list(conversation.getThreads()))
self.assertEqual(
[
{"comment": comment2, "depth": 0, "id": new_id_2},
{"comment": comment2_1, "depth": 1, "id": new_id_2_1},
],
list(conversation.getThreads()),
)
def test_delete_comment_when_content_object_is_deleted(self):
# 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')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
conversation.addComment(comment)
# Delete the content object
self.portal.manage_delObjects(['doc1'])
self.portal.manage_delObjects(["doc1"])
# Make sure the comment has been deleted as well
self.assertEqual(len(list(conversation.getComments())), 0)
@ -200,8 +202,8 @@ class ConversationTest(unittest.TestCase):
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')
typetool.constructContent("Folder", self.portal, "folder1")
typetool.constructContent("Document", self.portal.folder1, "doc2")
folder = self.portal.folder1
@ -211,13 +213,13 @@ class ConversationTest(unittest.TestCase):
self.assertFalse(aq_base(folder).allow_discussion)
doc = self.portal.folder1.doc2
conversation = doc.restrictedTraverse('@@conversation_view')
conversation = doc.restrictedTraverse("@@conversation_view")
self.assertEqual(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')
document_fti = getattr(portal_types, 'Document')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "Document")
document_fti.manage_changeProperties(allow_discussion=True)
self.assertEqual(conversation.enabled(), True)
@ -225,13 +227,12 @@ class ConversationTest(unittest.TestCase):
def test_disable_commenting_globally(self):
# Create a conversation.
conversation = self.portal.doc1.restrictedTraverse(
'@@conversation_view')
conversation = self.portal.doc1.restrictedTraverse("@@conversation_view")
# We have to allow discussion on Document content type, since
# otherwise allow_discussion will always return False
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "Document")
document_fti.manage_changeProperties(allow_discussion=True)
# Check if conversation is enabled now
@ -251,14 +252,14 @@ class ConversationTest(unittest.TestCase):
def test_allow_discussion_for_news_items(self):
self.typetool.constructContent('News Item', self.portal, 'newsitem')
self.typetool.constructContent("News Item", self.portal, "newsitem")
newsitem = self.portal.newsitem
conversation = newsitem.restrictedTraverse('@@conversation_view')
conversation = newsitem.restrictedTraverse("@@conversation_view")
# We have to allow discussion on Document content type, since
# otherwise allow_discussion will always return False
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'News Item')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "News Item")
document_fti.manage_changeProperties(allow_discussion=True)
# Check if conversation is enabled now
@ -280,23 +281,23 @@ class ConversationTest(unittest.TestCase):
# Create a conversation.
conversation = self.portal.doc1.restrictedTraverse(
'@@conversation_view',
"@@conversation_view",
)
# The Document content type is disabled by default
self.assertEqual(conversation.enabled(), False)
# Allow discussion on Document content type
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "Document")
document_fti.manage_changeProperties(allow_discussion=True)
# Check if conversation is enabled now
self.assertEqual(conversation.enabled(), True)
# Disallow discussion on Document content type
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "Document")
document_fti.manage_changeProperties(allow_discussion=False)
# Check if conversation is enabled now
@ -308,17 +309,17 @@ class ConversationTest(unittest.TestCase):
# plone.app.contenttypes does not have this restriction any longer.
# Create a folder
self.typetool.constructContent('Folder', self.portal, 'f1')
self.typetool.constructContent("Folder", self.portal, "f1")
# Usually we don't create a conversation on a folder
conversation = self.portal.f1.restrictedTraverse('@@conversation_view')
conversation = self.portal.f1.restrictedTraverse("@@conversation_view")
# Allow discussion for the folder
self.portal.f1.allow_discussion = True
# Allow discussion on Folder content type
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Folder')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, "Folder")
document_fti.manage_changeProperties(allow_discussion=True)
self.assertTrue(conversation.enabled())
@ -328,7 +329,7 @@ class ConversationTest(unittest.TestCase):
# Create a conversation.
conversation = self.portal.doc1.restrictedTraverse(
'@@conversation_view',
"@@conversation_view",
)
# Discussion is disallowed by default
@ -353,13 +354,13 @@ class ConversationTest(unittest.TestCase):
# 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')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
new_id1 = conversation.addComment(comment1)
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
new_id2 = conversation.addComment(comment2)
@ -410,14 +411,14 @@ class ConversationTest(unittest.TestCase):
# comments via the factory to allow different factories to be
# swapped in
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment3 = createObject('plone.Comment')
comment3.text = 'Comment text'
comment3 = createObject("plone.Comment")
comment3.text = "Comment text"
conversation.addComment(comment1)
conversation.addComment(comment2)
@ -439,49 +440,49 @@ class ConversationTest(unittest.TestCase):
# Note: in real life, we always create
# comments via the factory to allow different factories to be
# swapped in
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1.author_username = 'Jim'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1.author_username = "Jim"
conversation.addComment(comment1)
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2.author_username = 'Joe'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment2.author_username = "Joe"
conversation.addComment(comment2)
comment3 = createObject('plone.Comment')
comment3.text = 'Comment text'
comment3.author_username = 'Jack'
comment3 = createObject("plone.Comment")
comment3.text = "Comment text"
comment3.author_username = "Jack"
new_comment3_id = conversation.addComment(comment3)
comment4 = createObject('plone.Comment')
comment4.text = 'Comment text'
comment4.author_username = 'Jack'
comment4 = createObject("plone.Comment")
comment4.text = "Comment text"
comment4.author_username = "Jack"
new_comment4_id = conversation.addComment(comment4)
# check if all commentators are in the commentators list
self.assertEqual(conversation.total_comments(), 4)
self.assertTrue('Jim' in conversation.commentators)
self.assertTrue('Joe' in conversation.commentators)
self.assertTrue('Jack' in conversation.commentators)
self.assertTrue("Jim" in conversation.commentators)
self.assertTrue("Joe" in conversation.commentators)
self.assertTrue("Jack" in conversation.commentators)
# remove the comment from Jack
del conversation[new_comment3_id]
# check if Jack is still in the commentators list (since
# he had added two comments)
self.assertTrue('Jim' in conversation.commentators)
self.assertTrue('Joe' in conversation.commentators)
self.assertTrue('Jack' in conversation.commentators)
self.assertTrue("Jim" in conversation.commentators)
self.assertTrue("Joe" in conversation.commentators)
self.assertTrue("Jack" in conversation.commentators)
self.assertEqual(conversation.total_comments(), 3)
# remove the second comment from Jack
del conversation[new_comment4_id]
# check if Jack has been removed from the commentators list
self.assertTrue('Jim' in conversation.commentators)
self.assertTrue('Joe' in conversation.commentators)
self.assertFalse('Jack' in conversation.commentators)
self.assertTrue("Jim" in conversation.commentators)
self.assertTrue("Joe" in conversation.commentators)
self.assertFalse("Jack" in conversation.commentators)
self.assertEqual(conversation.total_comments(), 2)
def test_last_comment_date(self):
@ -496,29 +497,29 @@ class ConversationTest(unittest.TestCase):
# Note: in real life, we always create
# comments via the factory to allow different factories to be
# swapped in
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1.creation_date = datetime.utcnow() - timedelta(4)
conversation.addComment(comment1)
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment2.creation_date = datetime.utcnow() - timedelta(2)
new_comment2_id = conversation.addComment(comment2)
comment3 = createObject('plone.Comment')
comment3.text = 'Comment text'
comment3 = createObject("plone.Comment")
comment3.text = "Comment text"
comment3.creation_date = datetime.utcnow() - timedelta(1)
new_comment3_id = conversation.addComment(comment3)
# check if the latest comment is exactly one day old
self.assertTrue(
conversation.last_comment_date < datetime.utcnow() -
timedelta(hours=23, minutes=59, seconds=59),
conversation.last_comment_date
< datetime.utcnow() - timedelta(hours=23, minutes=59, seconds=59),
)
self.assertTrue(
conversation.last_comment_date >
datetime.utcnow() - timedelta(days=1, seconds=1),
conversation.last_comment_date
> datetime.utcnow() - timedelta(days=1, seconds=1),
)
# remove the latest comment
@ -527,12 +528,12 @@ class ConversationTest(unittest.TestCase):
# check if the latest comment has been updated
# the latest comment should be exactly two days old
self.assertTrue(
conversation.last_comment_date < datetime.utcnow() -
timedelta(days=1, hours=23, minutes=59, seconds=59),
conversation.last_comment_date
< datetime.utcnow() - timedelta(days=1, hours=23, minutes=59, seconds=59),
)
self.assertTrue(
conversation.last_comment_date > datetime.utcnow() -
timedelta(days=2, seconds=1),
conversation.last_comment_date
> datetime.utcnow() - timedelta(days=2, seconds=1),
)
# remove the latest comment again
@ -541,12 +542,12 @@ class ConversationTest(unittest.TestCase):
# check if the latest comment has been updated
# the latest comment should be exactly four days old
self.assertTrue(
conversation.last_comment_date < datetime.utcnow() -
timedelta(days=3, hours=23, minutes=59, seconds=59),
conversation.last_comment_date
< datetime.utcnow() - timedelta(days=3, hours=23, minutes=59, seconds=59),
)
self.assertTrue(
conversation.last_comment_date > datetime.utcnow() -
timedelta(days=4, seconds=2),
conversation.last_comment_date
> datetime.utcnow() - timedelta(days=4, seconds=2),
)
def test_get_comments_full(self):
@ -574,23 +575,23 @@ class ConversationTest(unittest.TestCase):
# +- Comment 2_1
# Create all comments
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1_1 = createObject('plone.Comment')
comment1_1.text = 'Comment text'
comment1_1 = createObject("plone.Comment")
comment1_1.text = "Comment text"
comment1_1_1 = createObject('plone.Comment')
comment1_1_1.text = 'Comment text'
comment1_1_1 = createObject("plone.Comment")
comment1_1_1.text = "Comment text"
comment1_2 = createObject('plone.Comment')
comment1_2.text = 'Comment text'
comment1_2 = createObject("plone.Comment")
comment1_2.text = "Comment text"
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment2_1 = createObject('plone.Comment')
comment2_1.text = 'Comment text'
comment2_1 = createObject("plone.Comment")
comment2_1.text = "Comment text"
# Create the nested comment structure
new_id_1 = conversation.addComment(comment1)
@ -610,14 +611,17 @@ class ConversationTest(unittest.TestCase):
# Get threads
self.assertEqual([
{'comment': comment1, 'depth': 0, 'id': new_id_1},
{'comment': comment1_1, 'depth': 1, 'id': new_id_1_1},
{'comment': comment1_1_1, 'depth': 2, 'id': new_id_1_1_1},
{'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
{'comment': comment2, 'depth': 0, 'id': new_id_2},
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
], list(conversation.getThreads()))
self.assertEqual(
[
{"comment": comment1, "depth": 0, "id": new_id_1},
{"comment": comment1_1, "depth": 1, "id": new_id_1_1},
{"comment": comment1_1_1, "depth": 2, "id": new_id_1_1_1},
{"comment": comment1_2, "depth": 1, "id": new_id_1_2},
{"comment": comment2, "depth": 0, "id": new_id_2},
{"comment": comment2_1, "depth": 1, "id": new_id_2_1},
],
list(conversation.getThreads()),
)
def test_get_threads_batched(self):
# TODO: test start, size, root and depth arguments to getThreads() # noqa T000
@ -628,16 +632,16 @@ class ConversationTest(unittest.TestCase):
# make sure we can traverse to conversations and get a URL and path
conversation = self.portal.doc1.restrictedTraverse(
'++conversation++default',
"++conversation++default",
)
self.assertTrue(IConversation.providedBy(conversation))
self.assertEqual(
('', 'plone', 'doc1', '++conversation++default'),
("", "plone", "doc1", "++conversation++default"),
conversation.getPhysicalPath(),
)
self.assertEqual(
'http://nohost/plone/doc1/++conversation++default',
"http://nohost/plone/doc1/++conversation++default",
conversation.absolute_url(),
)
@ -646,7 +650,7 @@ class ConversationTest(unittest.TestCase):
# can't be converted to int
conversation = self.portal.doc1.restrictedTraverse(
'++conversation++default/ThisCantBeRight',
"++conversation++default/ThisCantBeRight",
)
self.assertEqual(conversation, None)
@ -660,17 +664,16 @@ class ConversationTest(unittest.TestCase):
self.assertTrue(conversation.__parent__)
self.assertTrue(aq_parent(conversation))
self.assertEqual(conversation.__parent__.getId(), 'doc1')
self.assertEqual(conversation.__parent__.getId(), "doc1")
def test_discussion_item_not_in_bad_types(self):
self.assertFalse('Discussion Item' in BAD_TYPES)
self.assertFalse("Discussion Item" in BAD_TYPES)
def test_no_comment(self):
IConversation(self.portal.doc1)
# Make sure no conversation has been created
self.assertTrue(
'plone.app.discussion:conversation' not in
IAnnotations(self.portal.doc1),
"plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1),
)
@ -679,8 +682,8 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
interface.alsoProvides(
self.portal.REQUEST,
interfaces.IDiscussionLayer,
@ -693,7 +696,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
)
def _makeOne(self, *args, **kw):
return self.portal.doc1.restrictedTraverse('@@conversation_view')
return self.portal.doc1.restrictedTraverse("@@conversation_view")
def _globally_enable_discussion(self, value):
registry = queryUtility(IRegistry)
@ -701,7 +704,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
settings.globally_enabled = value
def _enable_discussion_on_portal_type(self, portal_type, allow_discussion):
portal_types = getToolByName(self.portal, 'portal_types')
portal_types = getToolByName(self.portal, "portal_types")
document_fti = getattr(portal_types, portal_type)
document_fti.manage_changeProperties(allow_discussion=allow_discussion)
@ -719,14 +722,14 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
def test_conversation_needs_to_be_enabled_globally_and_for_type(self):
if DEXTERITY:
self._globally_enable_discussion(True)
self._enable_discussion_on_portal_type('Document', True)
self._enable_discussion_on_portal_type("Document", True)
conversation = self._makeOne(self.portal.doc1)
self.assertTrue(conversation.enabled())
def test_disable_discussion(self):
if DEXTERITY:
self._globally_enable_discussion(True)
self._enable_discussion_on_portal_type('Document', True)
self._enable_discussion_on_portal_type("Document", True)
self.portal.doc1.allow_discussion = False
conversation = self._makeOne(self.portal.doc1)
self.assertFalse(conversation.enabled())
@ -734,7 +737,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
def test_enable_discussion(self):
if DEXTERITY:
self._globally_enable_discussion(True)
self._enable_discussion_on_portal_type('Document', True)
self._enable_discussion_on_portal_type("Document", True)
self.portal.doc1.allow_discussion = True
conversation = self._makeOne(self.portal.doc1)
self.assertTrue(conversation.enabled())
@ -747,11 +750,11 @@ class RepliesTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
def test_add_comment(self):
# Add comments to a ConversationReplies adapter
@ -762,8 +765,8 @@ class RepliesTest(unittest.TestCase):
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
@ -789,8 +792,8 @@ class RepliesTest(unittest.TestCase):
replies = IReplies(conversation)
# Add a comment.
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
@ -828,39 +831,39 @@ class RepliesTest(unittest.TestCase):
# +- Comment 2_1
# Create all comments
comment1 = createObject('plone.Comment')
comment1.text = 'Comment text'
comment1 = createObject("plone.Comment")
comment1.text = "Comment text"
comment1_1 = createObject('plone.Comment')
comment1_1.text = 'Comment text'
comment1_1 = createObject("plone.Comment")
comment1_1.text = "Comment text"
comment1_1_1 = createObject('plone.Comment')
comment1_1_1.text = 'Comment text'
comment1_1_1 = createObject("plone.Comment")
comment1_1_1.text = "Comment text"
comment1_2 = createObject('plone.Comment')
comment1_2.text = 'Comment text'
comment1_2 = createObject("plone.Comment")
comment1_2.text = "Comment text"
comment2 = createObject('plone.Comment')
comment2.text = 'Comment text'
comment2 = createObject("plone.Comment")
comment2.text = "Comment text"
comment2_1 = createObject('plone.Comment')
comment2_1.text = 'Comment text'
comment2_1 = createObject("plone.Comment")
comment2_1.text = "Comment text"
# Create the nested comment structure
new_id_1 = replies.addComment(comment1)
comment1 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_1),
"++conversation++default/{0}".format(new_id_1),
)
replies_to_comment1 = IReplies(comment1)
new_id_2 = replies.addComment(comment2)
comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_2),
"++conversation++default/{0}".format(new_id_2),
)
replies_to_comment2 = IReplies(comment2)
new_id_1_1 = replies_to_comment1.addComment(comment1_1)
comment1_1 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_1_1),
"++conversation++default/{0}".format(new_id_1_1),
)
replies_to_comment1_1 = IReplies(comment1_1)
replies_to_comment1_1.addComment(comment1_1_1)

View File

@ -21,8 +21,8 @@ import unittest
class EventsRegistry(object):
""" Fake registry to be used while testing discussion events
"""
"""Fake registry to be used while testing discussion events"""
commentAdded = False
commentModified = False
commentRemoved = False
@ -30,6 +30,7 @@ class EventsRegistry(object):
replyModified = False
replyRemoved = False
#
# Fake event handlers
#
@ -65,19 +66,19 @@ def reply_removed(doc, evt):
class CommentEventsTest(unittest.TestCase):
""" Test custom comments events
"""
"""Test custom comments events"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
# Setup sandbox
self.portal = self.layer['portal']
self.request = self.layer['request']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
self.registry = EventsRegistry
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.document = self.portal['doc1']
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.document = self.portal["doc1"]
#
# Subscribers
@ -106,23 +107,23 @@ class CommentEventsTest(unittest.TestCase):
</configure>
"""
zcml.load_config('configure.zcml', Products.Five)
zcml.load_config("configure.zcml", Products.Five)
zcml.load_string(configure)
def test_addEvent(self):
self.assertFalse(self.registry.commentAdded)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
conversation = IConversation(self.document)
conversation.addComment(comment)
self.assertTrue(self.registry.commentAdded)
def test_modifyEvent(self):
self.assertFalse(self.registry.commentModified)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
conversation = IConversation(self.document)
new_id = conversation.addComment(comment)
comment = self.document.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
comment.text = "foo"
notify(ObjectModifiedEvent(comment))
@ -130,7 +131,7 @@ class CommentEventsTest(unittest.TestCase):
def test_removedEvent(self):
self.assertFalse(self.registry.commentRemoved)
comment = createObject('plone.Comment')
comment = createObject("plone.Comment")
conversation = IConversation(self.document)
cid = conversation.addComment(comment)
del conversation[cid]
@ -138,17 +139,17 @@ class CommentEventsTest(unittest.TestCase):
class RepliesEventsTest(unittest.TestCase):
""" Test custom replies events
"""
"""Test custom replies events"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
self.portal = self.layer["portal"]
self.request = self.layer["request"]
self.registry = EventsRegistry
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.document = self.portal['doc1']
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.document = self.portal["doc1"]
#
# Subscribers
@ -177,7 +178,7 @@ class RepliesEventsTest(unittest.TestCase):
</configure>
"""
zcml.load_config('configure.zcml', Products.Five)
zcml.load_config("configure.zcml", Products.Five)
zcml.load_string(configure)
def test_addEvent(self):
@ -186,15 +187,15 @@ class RepliesEventsTest(unittest.TestCase):
conversation = IConversation(self.document)
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
comment = self.document.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
replies.addComment(re_comment)
@ -206,14 +207,14 @@ class RepliesEventsTest(unittest.TestCase):
conversation = IConversation(self.document)
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment_id = replies.addComment(comment)
comment = self.document.restrictedTraverse(
'++conversation++default/{0}'.format(comment_id),
"++conversation++default/{0}".format(comment_id),
)
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
new_id = replies.addComment(re_comment)
reply = replies[new_id]
@ -227,15 +228,15 @@ class RepliesEventsTest(unittest.TestCase):
conversation = IConversation(self.portal.doc1)
replies = IReplies(conversation)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
new_id = replies.addComment(comment)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id),
"++conversation++default/{0}".format(new_id),
)
re_comment = createObject('plone.Comment')
re_comment.text = 'Comment text'
re_comment = createObject("plone.Comment")
re_comment.text = "Comment text"
replies = IReplies(comment)
new_re_id = replies.addComment(re_comment)

View File

@ -12,29 +12,29 @@ import unittest
optionflags = (
doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE
doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE
)
normal_testfiles = [
'functional_test_comments.txt',
'functional_test_comment_review_workflow.txt',
"functional_test_comments.txt",
"functional_test_comment_review_workflow.txt",
]
def test_suite():
suite = unittest.TestSuite()
suite.addTests([
suite.addTests(
[
layered(
doctest.DocFileSuite(
test,
optionflags=optionflags,
globs={
'pprint': pprint.pprint,
}
"pprint": pprint.pprint,
},
),
layer=PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING,
)
for test in normal_testfiles
])
]
)
return suite

View File

@ -28,41 +28,40 @@ sed diam voluptua. At [...]"""
class ConversationIndexersTest(unittest.TestCase):
"""Conversation Indexer Tests
"""
"""Conversation Indexer Tests"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
workflow.doActionFor(self.portal.doc1, "publish")
# Create a conversation.
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.text = 'Comment Text'
comment1.creator = 'jim'
comment1.author_username = 'Jim'
comment1 = createObject("plone.Comment")
comment1.text = "Comment Text"
comment1.creator = "jim"
comment1.author_username = "Jim"
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12)
self.new_id1 = conversation.addComment(comment1)
comment2 = createObject('plone.Comment')
comment2.text = 'Comment Text'
comment2.creator = 'emma'
comment2.author_username = 'Emma'
comment2 = createObject("plone.Comment")
comment2.text = "Comment Text"
comment2.creator = "emma"
comment2.author_username = "Emma"
comment2.creation_date = datetime(2007, 12, 13, 4, 18, 12)
comment2.modification_date = datetime(2007, 12, 13, 4, 18, 12)
self.new_id2 = conversation.addComment(comment2)
comment3 = createObject('plone.Comment')
comment3.text = 'Comment Text'
comment3.creator = 'lukas'
comment3.author_username = 'Lukas'
comment3 = createObject("plone.Comment")
comment3.text = "Comment Text"
comment3.creator = "lukas"
comment3.author_username = "Lukas"
comment3.creation_date = datetime(2009, 4, 12, 11, 12, 12)
comment3.modification_date = datetime(2009, 4, 12, 11, 12, 12)
self.new_id3 = conversation.addComment(comment3)
@ -70,10 +69,12 @@ class ConversationIndexersTest(unittest.TestCase):
self.conversation = conversation
def test_conversation_total_comments(self):
self.assertTrue(isinstance(
self.assertTrue(
isinstance(
catalog.total_comments,
DelegatingIndexerFactory,
))
)
)
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 3)
del self.conversation[self.new_id1]
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 2)
@ -82,10 +83,12 @@ class ConversationIndexersTest(unittest.TestCase):
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 0)
def test_conversation_last_comment_date(self):
self.assertTrue(isinstance(
self.assertTrue(
isinstance(
catalog.last_comment_date,
DelegatingIndexerFactory,
))
)
)
self.assertEqual(
catalog.last_comment_date(self.portal.doc1)(),
datetime(2009, 4, 12, 11, 12, 12),
@ -112,8 +115,8 @@ class CommentIndexersTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
# Create a conversation. In this case we doesn't assign it to an
# object, as we just want to check the Conversation object API.
@ -122,10 +125,10 @@ class CommentIndexersTest(unittest.TestCase):
# 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')
comment.text = 'Lorem ipsum dolor sit amet.'
comment.creator = 'jim'
comment.author_name = 'Jim'
comment = createObject("plone.Comment")
comment.text = "Lorem ipsum dolor sit amet."
comment.creator = "jim"
comment.author_name = "Jim"
comment.creation_date = datetime(2006, 9, 17, 14, 18, 12)
comment.modification_date = datetime(2008, 3, 12, 7, 32, 52)
@ -134,60 +137,61 @@ class CommentIndexersTest(unittest.TestCase):
self.conversation = conversation
def test_title(self):
self.assertEqual(catalog.title(self.comment)(), 'Jim on Document 1')
self.assertEqual(catalog.title(self.comment)(), "Jim on Document 1")
self.assertTrue(isinstance(catalog.title, DelegatingIndexerFactory))
def test_description(self):
self.assertEqual(
catalog.description(self.comment)(),
'Lorem ipsum dolor sit amet.',
"Lorem ipsum dolor sit amet.",
)
self.assertTrue(
isinstance(catalog.description, DelegatingIndexerFactory))
self.assertTrue(isinstance(catalog.description, DelegatingIndexerFactory))
def test_description_long(self):
# Create a 50 word comment and make sure the description returns
# only the first 25 words
comment_long = createObject('plone.Comment')
comment_long.title = 'Long Comment'
comment_long = createObject("plone.Comment")
comment_long.title = "Long Comment"
comment_long.text = LONG_TEXT
self.conversation.addComment(comment_long)
self.assertEqual(
catalog.description(comment_long)(),
LONG_TEXT_CUT.replace('\n', ' '),
LONG_TEXT_CUT.replace("\n", " "),
)
def test_dates(self):
# Test if created, modified, effective etc. are set correctly
self.assertEqual(
catalog.created(self.comment)(),
DateTime(2006, 9, 17, 14, 18, 12, 'GMT'),
DateTime(2006, 9, 17, 14, 18, 12, "GMT"),
)
self.assertEqual(
catalog.effective(self.comment)(),
DateTime(2006, 9, 17, 14, 18, 12, 'GMT'),
DateTime(2006, 9, 17, 14, 18, 12, "GMT"),
)
self.assertEqual(
catalog.modified(self.comment)(),
DateTime(2008, 3, 12, 7, 32, 52, 'GMT'),
DateTime(2008, 3, 12, 7, 32, 52, "GMT"),
)
def test_searchable_text(self):
# Test if searchable text is a concatenation of title and comment text
self.assertEqual(
catalog.searchable_text(self.comment)(),
('Lorem ipsum dolor sit amet.'),
("Lorem ipsum dolor sit amet."),
)
self.assertTrue(isinstance(
self.assertTrue(
isinstance(
catalog.searchable_text,
DelegatingIndexerFactory,
))
)
)
def test_creator(self):
self.assertEqual(catalog.creator(self.comment)(), ('jim'))
self.assertEqual(catalog.creator(self.comment)(), ("jim"))
def test_in_response_to(self):
# make sure in_response_to returns the title or id of the content
# object the comment was added to
self.assertEqual(catalog.in_response_to(self.comment)(), 'Document 1')
self.assertEqual(catalog.in_response_to(self.comment)(), "Document 1")

View File

@ -23,59 +23,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.wf = getToolByName(self.portal,
'portal_workflow',
None)
self.app = self.layer["app"]
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.wf = getToolByName(self.portal, "portal_workflow", None)
self.context = self.portal
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
'comment_review_workflow',
("Discussion Item",),
"comment_review_workflow",
)
self.wf_tool = self.portal.portal_workflow
# Add a conversation with three comments
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.title = 'Comment 1'
comment1.text = 'Comment text'
comment1.Creator = 'Jim'
comment1 = createObject("plone.Comment")
comment1.title = "Comment 1"
comment1.text = "Comment text"
comment1.Creator = "Jim"
new_id_1 = conversation.addComment(comment1)
self.comment1 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_1),
"++conversation++default/{0}".format(new_id_1),
)
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2.Creator = 'Joe'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
comment2.Creator = "Joe"
new_id_2 = conversation.addComment(comment2)
self.comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_2),
"++conversation++default/{0}".format(new_id_2),
)
comment3 = createObject('plone.Comment')
comment3.title = 'Comment 3'
comment3.text = 'Comment text'
comment3.Creator = 'Emma'
comment3 = createObject("plone.Comment")
comment3.title = "Comment 3"
comment3.text = "Comment text"
comment3.Creator = "Emma"
new_id_3 = conversation.addComment(comment3)
self.comment3 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_3),
"++conversation++default/{0}".format(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.set('form.select.BulkAction', '-1')
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
self.request.set("form.select.BulkAction", "-1")
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
view = BulkActionsView(self.portal, self.request)
self.assertFalse(view())
def test_publish(self):
self.request.set('form.select.BulkAction', 'publish')
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
self.request.set("form.select.BulkAction", "publish")
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
view = BulkActionsView(self.portal, self.request)
view()
@ -83,16 +81,16 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
# Count published comments
published_comments = 0
for r in self.conversation.getThreads():
comment_obj = r['comment']
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
if workflow_status == 'published':
comment_obj = r["comment"]
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
if workflow_status == "published":
published_comments += 1
# Make sure the comment has been published
self.assertEqual(published_comments, 1)
def test_mark_as_spam(self):
self.request.set('form.select.BulkAction', 'mark_as_spam')
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
self.request.set("form.select.BulkAction", "mark_as_spam")
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
view = BulkActionsView(self.portal, self.request)
@ -101,9 +99,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
# Count spam comments
spam_comments = 0
for r in self.conversation.getThreads():
comment_obj = r['comment']
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
if workflow_status == 'spam':
comment_obj = r["comment"]
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
if workflow_status == "spam":
spam_comments += 1
# Make sure the comment has been marked as spam
self.assertEqual(spam_comments, 1)
@ -112,9 +110,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
# Initially we have three comments
self.assertEqual(len(self.conversation.objectIds()), 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())])
self.request.set("form.select.BulkAction", "delete")
self.request.set(
"paths",
[
"/".join(self.comment1.getPhysicalPath()),
"/".join(self.comment3.getPhysicalPath()),
],
)
view = BulkActionsView(self.app, self.request)
view()

View File

@ -23,21 +23,19 @@ class ModerationViewTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal_discussion = getToolByName(self.portal,
'portal_discussion',
None)
self.membership_tool = getToolByName(self.portal,
'portal_membership')
self.app = self.layer["app"]
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal_discussion = getToolByName(self.portal, "portal_discussion", None)
self.membership_tool = getToolByName(self.portal, "portal_membership")
self.memberdata = self.portal.portal_memberdata
request = self.app.REQUEST
context = getattr(self.portal, 'doc1')
context = getattr(self.portal, "doc1")
self.view = View(context, request)
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',), 'comment_review_workflow')
("Discussion Item",), "comment_review_workflow"
)
self.wf_tool = self.portal.portal_workflow
def test_moderation_enabled(self):
@ -45,15 +43,17 @@ class ModerationViewTest(unittest.TestCase):
workflow implements a 'pending' state.
"""
# If workflow is not set, enabled must return False
self.wf_tool.setChainForPortalTypes(('Discussion Item',), ())
self.wf_tool.setChainForPortalTypes(("Discussion Item",), ())
self.assertEqual(self.view.moderation_enabled(), False)
# The comment_one_state_workflow does not have a 'pending' state
self.wf_tool.setChainForPortalTypes(('Discussion Item',),
('comment_one_state_workflow,'))
self.wf_tool.setChainForPortalTypes(
("Discussion Item",), ("comment_one_state_workflow,")
)
self.assertEqual(self.view.moderation_enabled(), False)
# The comment_review_workflow does have a 'pending' state
self.wf_tool.setChainForPortalTypes(('Discussion Item',),
('comment_review_workflow,'))
self.wf_tool.setChainForPortalTypes(
("Discussion Item",), ("comment_review_workflow,")
)
self.assertEqual(self.view.moderation_enabled(), True)
@ -62,59 +62,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.wf = getToolByName(self.portal,
'portal_workflow',
None)
self.app = self.layer["app"]
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.wf = getToolByName(self.portal, "portal_workflow", None)
self.context = self.portal
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
'comment_review_workflow',
("Discussion Item",),
"comment_review_workflow",
)
self.wf_tool = self.portal.portal_workflow
# Add a conversation with three comments
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')
comment1.title = 'Comment 1'
comment1.text = 'Comment text'
comment1.Creator = 'Jim'
comment1 = createObject("plone.Comment")
comment1.title = "Comment 1"
comment1.text = "Comment text"
comment1.Creator = "Jim"
new_id_1 = conversation.addComment(comment1)
self.comment1 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_1),
"++conversation++default/{0}".format(new_id_1),
)
comment2 = createObject('plone.Comment')
comment2.title = 'Comment 2'
comment2.text = 'Comment text'
comment2.Creator = 'Joe'
comment2 = createObject("plone.Comment")
comment2.title = "Comment 2"
comment2.text = "Comment text"
comment2.Creator = "Joe"
new_id_2 = conversation.addComment(comment2)
self.comment2 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_2),
"++conversation++default/{0}".format(new_id_2),
)
comment3 = createObject('plone.Comment')
comment3.title = 'Comment 3'
comment3.text = 'Comment text'
comment3.Creator = 'Emma'
comment3 = createObject("plone.Comment")
comment3.title = "Comment 3"
comment3.text = "Comment text"
comment3.Creator = "Emma"
new_id_3 = conversation.addComment(comment3)
self.comment3 = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(new_id_3),
"++conversation++default/{0}".format(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.set('form.select.BulkAction', '-1')
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
self.request.set("form.select.BulkAction", "-1")
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
view = BulkActionsView(self.portal, self.request)
self.assertFalse(view())
def test_publish(self):
self.request.set('form.select.BulkAction', 'publish')
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
self.request.set("form.select.BulkAction", "publish")
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
view = BulkActionsView(self.portal, self.request)
view()
@ -122,9 +120,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
# Count published comments
published_comments = 0
for r in self.conversation.getThreads():
comment_obj = r['comment']
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
if workflow_status == 'published':
comment_obj = r["comment"]
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
if workflow_status == "published":
published_comments += 1
# Make sure the comment has been published
self.assertEqual(published_comments, 1)
@ -133,9 +131,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
# Initially we have three comments
self.assertEqual(len(self.conversation.objectIds()), 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())])
self.request.set("form.select.BulkAction", "delete")
self.request.set(
"paths",
[
"/".join(self.comment1.getPhysicalPath()),
"/".join(self.comment3.getPhysicalPath()),
],
)
view = BulkActionsView(self.app, self.request)
view()
@ -153,41 +156,41 @@ class RedirectionTest(unittest.TestCase):
def setUp(self):
# Update settings.
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
self.request = self.layer["request"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
# applyProfile(self.portal, 'plone.app.discussion:default')
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings)
settings.globally_enabled = True
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow',),
("Discussion Item",),
("comment_review_workflow",),
)
# Create page plus comment.
self.portal.invokeFactory(
id='page',
title='Page 1',
type_name='Document',
id="page",
title="Page 1",
type_name="Document",
)
self.page = self.portal.page
self.conversation = IConversation(self.page)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.comment_id = self.conversation.addComment(comment)
self.comment = list(self.conversation.getComments())[0]
def test_regression(self):
page_url = self.page.absolute_url()
self.request['HTTP_REFERER'] = page_url
self.request["HTTP_REFERER"] = page_url
for Klass in (DeleteComment, CommentTransition):
view = Klass(self.comment, self.request)
view.__parent__ = self.comment
self.assertEqual(page_url, view())
def test_valid_next_url(self):
self.request['HTTP_REFERER'] = 'http://attacker.com'
self.request["HTTP_REFERER"] = "http://attacker.com"
for Klass in (DeleteComment, CommentTransition):
view = Klass(self.comment, self.request)
view.__parent__ = self.comment
self.assertNotEqual('http://attacker.com', view())
self.assertNotEqual("http://attacker.com", view())

View File

@ -23,45 +23,46 @@ class TestUserNotificationUnit(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
# Set up a mock mailhost
self.portal._original_MailHost = self.portal.MailHost
self.portal.MailHost = mailhost = MockMailHost('MailHost')
self.portal.MailHost = mailhost = MockMailHost("MailHost")
sm = getSiteManager(context=self.portal)
sm.unregisterUtility(provided=IMailHost)
sm.registerUtility(mailhost, provided=IMailHost)
# We need to fake a valid mail setup
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings.email_from_address = 'portal@plone.test'
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
mail_settings.email_from_address = "portal@plone.test"
self.mailhost = self.portal.MailHost
# Enable user notification setting
registry = queryUtility(IRegistry)
registry['plone.app.discussion.interfaces.IDiscussionSettings' +
'.user_notification_enabled'] = True
registry[
"plone.app.discussion.interfaces.IDiscussionSettings"
+ ".user_notification_enabled"
] = True
# Archetypes content types store data as utf-8 encoded strings
# The missing u in front of a string is therefor not missing
self.portal.doc1.title = 'Kölle Alaaf' # What is 'Fasching'?
self.portal.doc1.title = "Kölle Alaaf" # What is 'Fasching'?
self.conversation = IConversation(self.portal.doc1)
def beforeTearDown(self):
self.portal.MailHost = self.portal._original_MailHost
sm = getSiteManager(context=self.portal)
sm.unregisterUtility(provided=IMailHost)
sm.registerUtility(aq_base(self.portal._original_MailHost),
provided=IMailHost)
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.
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
comment.author_email = 'john@plone.test'
comment.author_email = "john@plone.test"
self.conversation.addComment(comment)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment_id = self.conversation.addComment(comment)
@ -69,52 +70,46 @@ class TestUserNotificationUnit(unittest.TestCase):
self.assertTrue(self.mailhost.messages[0])
msg = self.mailhost.messages[0]
msg = msg.decode("utf-8")
self.assertIn('To: john@plone.test', msg)
self.assertIn('From: portal@plone.test', msg)
self.assertIn("To: john@plone.test", msg)
self.assertIn("From: portal@plone.test", msg)
# We expect the headers to be properly header encoded (7-bit):
self.assertIn(
'Subject: =?utf-8?q?A_comment_has_been_posted=2E?=',
msg)
self.assertIn("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=", msg)
# The output should be encoded in a reasonable manner
# (in this case quoted-printable).
# Depending on which Python version and which Products.MailHost version,
# you may get lines separated by '\n' or '\r\n' in here.
msg = msg.replace('\r\n', '\n')
self.assertIn(
'A comment on "K=C3=B6lle Alaaf" has been posted here:',
msg)
self.assertIn(
'http://nohost/plone/d=\noc1/view#{0}'.format(comment_id),
msg)
self.assertIn('Comment text', msg)
self.assertNotIn('Approve comment', msg)
self.assertNotIn('Delete comment', msg)
msg = msg.replace("\r\n", "\n")
self.assertIn('A comment on "K=C3=B6lle Alaaf" has been posted here:', msg)
self.assertIn("http://nohost/plone/d=\noc1/view#{0}".format(comment_id), msg)
self.assertIn("Comment text", msg)
self.assertNotIn("Approve comment", msg)
self.assertNotIn("Delete comment", msg)
def test_do_not_notify_user_when_notification_is_disabled(self):
registry = queryUtility(IRegistry)
registry[
'plone.app.discussion.interfaces.IDiscussionSettings.' +
'user_notification_enabled'
"plone.app.discussion.interfaces.IDiscussionSettings."
+ "user_notification_enabled"
] = False
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
comment.author_email = 'john@plone.test'
comment.author_email = "john@plone.test"
self.conversation.addComment(comment)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)
self.assertEqual(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'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
self.conversation.addComment(comment)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)
@ -124,15 +119,15 @@ class TestUserNotificationUnit(unittest.TestCase):
# Set sender mail address to none and make sure no email is send to
# the moderator.
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
mail_settings.email_from_address = None
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
comment.author_email = 'john@plone.test'
comment.author_email = "john@plone.test"
self.conversation.addComment(comment)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)
self.assertEqual(len(self.mailhost.messages), 0)
@ -141,15 +136,15 @@ class TestUserNotificationUnit(unittest.TestCase):
# When a user has added two comments in a conversation and has
# both times requested email notification, do not send him two
# emails when another comment has been added.
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
comment.author_email = 'john@plone.test'
comment.author_email = "john@plone.test"
self.conversation.addComment(comment)
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.user_notification = True
comment.author_email = 'john@plone.test'
comment.author_email = "john@plone.test"
self.conversation.addComment(comment)
@ -165,48 +160,47 @@ class TestModeratorNotificationUnit(unittest.TestCase):
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
# Set up a mock mailhost
self.portal._original_MailHost = self.portal.MailHost
self.portal.MailHost = mailhost = MockMailHost('MailHost')
self.portal.MailHost = mailhost = MockMailHost("MailHost")
sm = getSiteManager(context=self.portal)
sm.unregisterUtility(provided=IMailHost)
sm.registerUtility(mailhost, provided=IMailHost)
# We need to fake a valid mail setup
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings.email_from_address = 'portal@plone.test'
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
mail_settings.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_types["Document"].allow_discussion = True
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow',),
("Discussion Item",),
("comment_review_workflow",),
)
# Enable moderator notification setting
registry = queryUtility(IRegistry)
registry[
'plone.app.discussion.interfaces.IDiscussionSettings.' +
'moderator_notification_enabled'
"plone.app.discussion.interfaces.IDiscussionSettings."
+ "moderator_notification_enabled"
] = True
# Archetypes content types store data as utf-8 encoded strings
# The missing u in front of a string is therefor not missing
self.portal.doc1.title = 'Kölle Alaaf' # What is 'Fasching'?
self.portal.doc1.title = "Kölle Alaaf" # What is 'Fasching'?
self.conversation = IConversation(self.portal.doc1)
def beforeTearDown(self):
self.portal.MailHost = self.portal._original_MailHost
sm = getSiteManager(context=self.portal)
sm.unregisterUtility(provided=IMailHost)
sm.registerUtility(aq_base(self.portal._original_MailHost),
provided=IMailHost)
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')
comment.text = 'Comment text'
comment.author_email = 'john@plone.test'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment.author_email = "john@plone.test"
comment_id = self.conversation.addComment(comment)
@ -214,54 +208,41 @@ class TestModeratorNotificationUnit(unittest.TestCase):
self.assertTrue(self.mailhost.messages[0])
msg = self.mailhost.messages[0]
msg = msg.decode("utf-8")
self.assertTrue('To: portal@plone.test' in msg)
self.assertTrue('From: portal@plone.test' in msg)
self.assertTrue("To: portal@plone.test" in msg)
self.assertTrue("From: portal@plone.test" in msg)
# We expect the headers to be properly header encoded (7-bit):
self.assertTrue(
'Subject: =?utf-8?q?A_comment_has_been_posted=2E?='
in msg)
self.assertTrue("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=" in msg)
# The output should be encoded in a reasonable manner
# (in this case quoted-printable):
self.assertTrue(
'A comment on "K=C3=B6lle Alaaf" has been posted'
in msg
)
self.assertIn(
'http://nohost/plone/doc1/view#{0}'.format(comment_id),
msg
)
self.assertIn(
comment.author_email,
msg
)
self.assertIn(
comment.text,
msg
)
self.assertTrue('A comment on "K=C3=B6lle Alaaf" has been posted' in msg)
self.assertIn("http://nohost/plone/doc1/view#{0}".format(comment_id), msg)
self.assertIn(comment.author_email, msg)
self.assertIn(comment.text, msg)
def test_notify_moderator_specific_address(self):
# A moderator email address can be specified in the control panel.
registry = queryUtility(IRegistry)
registry['plone.app.discussion.interfaces.IDiscussionSettings' +
'.moderator_email'] = 'test@example.com'
comment = createObject('plone.Comment')
comment.text = 'Comment text'
registry[
"plone.app.discussion.interfaces.IDiscussionSettings" + ".moderator_email"
] = "test@example.com"
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)
self.assertEqual(len(self.mailhost.messages), 1)
msg = self.mailhost.messages[0]
msg = msg.decode("utf-8")
self.assertTrue('To: test@example.com' in msg)
self.assertTrue("To: test@example.com" in msg)
def test_do_not_notify_moderator_when_no_sender_is_available(self):
# Set sender mail address to nonw and make sure no email is send to the
# moderator.
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
mail_settings.email_from_address = None
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)
@ -271,10 +252,12 @@ class TestModeratorNotificationUnit(unittest.TestCase):
# Disable moderator notification setting and make sure no email is send
# to the moderator.
registry = queryUtility(IRegistry)
registry['plone.app.discussion.interfaces.IDiscussionSettings.' +
'moderator_notification_enabled'] = False
comment = createObject('plone.Comment')
comment.text = 'Comment text'
registry[
"plone.app.discussion.interfaces.IDiscussionSettings."
+ "moderator_notification_enabled"
] = False
comment = createObject("plone.Comment")
comment.text = "Comment text"
self.conversation.addComment(comment)

View File

@ -11,19 +11,21 @@ import unittest
def test_suite():
suite = unittest.TestSuite()
current_dir = os.path.abspath(os.path.dirname(__file__))
robot_dir = os.path.join(current_dir, 'robot')
robot_dir = os.path.join(current_dir, "robot")
robot_tests = [
os.path.join('robot', doc) for doc in
os.listdir(robot_dir) if doc.endswith('.robot') and
doc.startswith('test_')
os.path.join("robot", doc)
for doc in os.listdir(robot_dir)
if doc.endswith(".robot") and doc.startswith("test_")
]
for robot_test in robot_tests:
robottestsuite = robotsuite.RobotTestSuite(robot_test)
robottestsuite.level = ROBOT_TEST_LEVEL
suite.addTests([
suite.addTests(
[
layered(
robottestsuite,
layer=PLONE_APP_DISCUSSION_ROBOT_TESTING,
),
])
]
)
return suite

View File

@ -21,48 +21,51 @@ import unittest
class WorkflowSetupTest(unittest.TestCase):
"""Make sure the workflows are set up properly.
"""
"""Make sure the workflows are set up properly."""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
self.folder = self.portal['test-folder']
self.portal.portal_types['Document'].allow_discussion = True
self.folder.invokeFactory('Document', 'doc1')
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal.invokeFactory("Folder", "test-folder")
self.folder = self.portal["test-folder"]
self.portal.portal_types["Document"].allow_discussion = True
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.assertTrue('comment_one_state_workflow' in
self.portal.portal_workflow.objectIds())
self.assertTrue('comment_review_workflow' in
self.portal.portal_workflow.objectIds())
"""Make sure both comment workflows have been installed properly."""
self.assertTrue(
"comment_one_state_workflow" in self.portal.portal_workflow.objectIds()
)
self.assertTrue(
"comment_review_workflow" in self.portal.portal_workflow.objectIds()
)
def test_default_workflow(self):
"""Make sure one_state_workflow is the default workflow.
"""
"""Make sure one_state_workflow is the default workflow."""
self.assertEqual(
('comment_one_state_workflow',),
("comment_one_state_workflow",),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item',
"Discussion Item",
),
)
def test_review_comments_permission(self):
# 'Review comments' in self.portal.permissionsOfRole('Admin')
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
self.assertTrue(self.portal.portal_membership.checkPermission(
'Review comments', self.folder), self.folder)
setRoles(self.portal, TEST_USER_ID, ['Member'])
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
self.assertTrue(
self.portal.portal_membership.checkPermission(
"Review comments", self.folder
),
self.folder,
)
setRoles(self.portal, TEST_USER_ID, ["Member"])
self.assertFalse(
self.portal.portal_membership.checkPermission(
'Review comments',
"Review comments",
self.folder,
),
self.folder,
@ -73,14 +76,13 @@ class WorkflowSetupTest(unittest.TestCase):
class PermissionsSetupTest(unittest.TestCase):
"""Make sure the permissions are set up properly.
"""
"""Make sure the permissions are set up properly."""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
mtool = self.portal.portal_membership
self.checkPermission = mtool.checkPermission
@ -90,14 +92,14 @@ class PermissionsSetupTest(unittest.TestCase):
plone.app.discussion assigns this permission to 'Authenticated' as
well to emulate the behavior of the old commenting system.
"""
ReplyToItemPerm = 'Reply to item'
ReplyToItemPerm = "Reply to item"
# should be allowed as Member
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
# should be allowed as Authenticated
setRoles(self.portal, TEST_USER_ID, ['Authenticated'])
setRoles(self.portal, TEST_USER_ID, ["Authenticated"])
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
# should be allowed as Manager
setRoles(self.portal, TEST_USER_ID, ['Manager'])
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
# should not be allowed as anonymous
logout()
@ -105,70 +107,66 @@ class PermissionsSetupTest(unittest.TestCase):
class CommentOneStateWorkflowTest(unittest.TestCase):
"""Test the comment_one_state_workflow that ships with plone.app.discussion.
"""
"""Test the comment_one_state_workflow that ships with plone.app.discussion."""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
self.folder = self.portal['test-folder']
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal.invokeFactory("Folder", "test-folder")
self.folder = self.portal["test-folder"]
self.catalog = self.portal.portal_catalog
self.workflow = self.portal.portal_workflow
self.folder.invokeFactory('Document', 'doc1')
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'
comment = createObject("plone.Comment")
comment.text = "Comment text"
cid = conversation.addComment(comment)
self.comment = self.folder.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(cid),
"++conversation++default/{0}".format(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'], [])
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 'private'.
"""
"""Make sure the initial workflow state of a comment is 'private'."""
self.assertEqual(
self.workflow.getInfoFor(self.doc, 'review_state'),
'private',
self.workflow.getInfoFor(self.doc, "review_state"),
"private",
)
def test_view_comments(self):
"""Make sure published comments can be viewed by everyone.
"""
"""Make sure published comments can be viewed by everyone."""
# Owner is allowed
# self.login(default_user)
# self.assertTrue(checkPerm(View, self.doc))
# Member is allowed
login(self.portal, TEST_USER_NAME)
workflow = self.portal.portal_workflow
workflow.doActionFor(self.doc, 'publish')
workflow.doActionFor(self.doc, "publish")
login(self.portal, 'member')
login(self.portal, "member")
self.assertTrue(checkPerm(View, self.comment))
# Reviewer is allowed
login(self.portal, 'reviewer')
login(self.portal, "reviewer")
self.assertTrue(checkPerm(View, self.comment))
# Anonymous is allowed
logout()
self.assertTrue(checkPerm(View, self.comment))
# Editor is allowed
login(self.portal, 'editor')
login(self.portal, "editor")
self.assertTrue(checkPerm(View, self.comment))
# Reader is allowed
login(self.portal, 'reader')
login(self.portal, "reader")
self.assertTrue(checkPerm(View, self.comment))
def test_comment_on_private_content_not_visible_to_world(self):
@ -179,7 +177,7 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
from plone.app.discussion.upgrades import upgrade_comment_workflows
# Fake permission according to earlier one_comment_workflow.
self.comment._View_Permission = ('Anonymous',)
self.comment._View_Permission = ("Anonymous",)
# Anonymous can see the comment.
logout()
self.assertTrue(checkPerm(View, self.comment))
@ -188,8 +186,8 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
upgrade_comment_workflows(self.portal.portal_setup)
# The workflow chain is still what we want.
self.assertEqual(
self.portal.portal_workflow.getChainFor('Discussion Item'),
('comment_one_state_workflow',),
self.portal.portal_workflow.getChainFor("Discussion Item"),
("comment_one_state_workflow",),
)
# A Manager can still see the comment.
self.assertTrue(checkPerm(View, self.comment))
@ -199,112 +197,112 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
class CommentReviewWorkflowTest(unittest.TestCase):
"""Test the comment_review_workflow that ships with plone.app.discussion.
"""
"""Test the comment_review_workflow that ships with plone.app.discussion."""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.portal.invokeFactory('Folder', 'test-folder')
self.folder = self.portal['test-folder']
self.portal = self.layer["portal"]
setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.portal.invokeFactory("Folder", "test-folder")
self.folder = self.portal["test-folder"]
# Allow discussion on the Document content type
self.portal.portal_types['Document'].allow_discussion = True
self.portal.portal_types["Document"].allow_discussion = True
# Set workflow for Discussion item to review workflow
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('comment_review_workflow',),
("Discussion Item",),
("comment_review_workflow",),
)
# Create a conversation for this Document
conversation = IConversation(self.portal.doc1)
# Add a comment.
comment = createObject('plone.Comment')
comment.text = 'Comment text'
comment = createObject("plone.Comment")
comment.text = "Comment text"
comment_id = conversation.addComment(comment)
comment = self.portal.doc1.restrictedTraverse(
'++conversation++default/{0}'.format(comment_id),
"++conversation++default/{0}".format(comment_id),
)
self.conversation = conversation
self.comment_id = comment_id
self.comment = comment
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
alsoProvides(self.portal.REQUEST, IDiscussionLayer)
def test_delete(self):
self.portal.REQUEST.form['comment_id'] = self.comment_id
view = self.comment.restrictedTraverse('@@moderate-delete-comment')
self.portal.REQUEST.form["comment_id"] = self.comment_id
view = self.comment.restrictedTraverse("@@moderate-delete-comment")
view()
self.assertFalse(self.comment_id in self.conversation.objectIds())
def test_delete_as_anonymous(self):
# Make sure that anonymous users can not delete comments
logout()
self.portal.REQUEST.form['comment_id'] = self.comment_id
self.portal.REQUEST.form["comment_id"] = self.comment_id
self.assertRaises(
Unauthorized,
self.comment.restrictedTraverse,
'@@moderate-delete-comment',
"@@moderate-delete-comment",
)
self.assertTrue(self.comment_id in self.conversation.objectIds())
def test_delete_as_user(self):
# Make sure that members can not delete comments
logout()
setRoles(self.portal, TEST_USER_ID, ['Member'])
self.portal.REQUEST.form['comment_id'] = self.comment_id
setRoles(self.portal, TEST_USER_ID, ["Member"])
self.portal.REQUEST.form["comment_id"] = self.comment_id
self.assertRaises(
Unauthorized,
self.comment.restrictedTraverse,
'@@moderate-delete-comment',
"@@moderate-delete-comment",
)
self.assertTrue(self.comment_id in self.conversation.objectIds())
def test_publish(self):
self.portal.REQUEST.form['comment_id'] = self.comment_id
self.portal.REQUEST.form['workflow_action'] = 'publish'
self.portal.REQUEST.form["comment_id"] = self.comment_id
self.portal.REQUEST.form["workflow_action"] = "publish"
self.assertEqual(
'pending',
"pending",
self.portal.portal_workflow.getInfoFor(
self.comment,
'review_state',
"review_state",
),
)
view = self.comment.restrictedTraverse('@@transmit-comment')
view = self.comment.restrictedTraverse("@@transmit-comment")
view()
self.assertEqual(
'published',
"published",
self.portal.portal_workflow.getInfoFor(
self.comment,
'review_state',
"review_state",
),
)
def test_publish_as_anonymous(self):
logout()
self.portal.REQUEST.form['comment_id'] = self.comment_id
self.portal.REQUEST.form['workflow_action'] = 'publish'
self.portal.REQUEST.form["comment_id"] = self.comment_id
self.portal.REQUEST.form["workflow_action"] = "publish"
self.assertEqual(
'pending', self.portal.portal_workflow.getInfoFor(
"pending",
self.portal.portal_workflow.getInfoFor(
self.comment,
'review_state',
"review_state",
),
)
self.assertRaises(
Unauthorized,
self.comment.restrictedTraverse,
'@@transmit-comment',
"@@transmit-comment",
)
self.assertEqual(
'pending',
"pending",
self.portal.portal_workflow.getInfoFor(
self.comment,
'review_state',
"review_state",
),
)
@ -315,7 +313,7 @@ class CommentReviewWorkflowTest(unittest.TestCase):
# publish comment and check again
login(self.portal, TEST_USER_NAME)
workflow = self.portal.portal_workflow
workflow.doActionFor(self.comment, 'publish')
workflow.doActionFor(self.comment, "publish")
logout()
self.assertFalse(checkPerm(View, self.comment))
@ -324,7 +322,7 @@ class CommentReviewWorkflowTest(unittest.TestCase):
from plone.app.discussion.upgrades import upgrade_comment_workflows
# Fake permission according to earlier comment_review_workflow.
self.comment._View_Permission = ('Anonymous',)
self.comment._View_Permission = ("Anonymous",)
# Anonymous can see the comment.
logout()
self.assertTrue(checkPerm(View, self.comment))
@ -333,8 +331,9 @@ class CommentReviewWorkflowTest(unittest.TestCase):
upgrade_comment_workflows(self.portal.portal_setup)
# The workflow chain is still what we want.
self.assertEqual(
self.portal.portal_workflow.getChainFor('Discussion Item'),
('comment_review_workflow',))
self.portal.portal_workflow.getChainFor("Discussion Item"),
("comment_review_workflow",),
)
# A Manager can still see the comment.
self.assertTrue(checkPerm(View, self.comment))
# Anonymous cannot see the comment.

View File

@ -17,62 +17,60 @@ from zope.component import queryUtility
@interface.implementer(ICommentingTool)
class CommentingTool(UniqueObject, SimpleItem):
meta_type = 'plone.app.discussion tool'
id = 'portal_discussion'
meta_type = "plone.app.discussion tool"
id = "portal_discussion"
def reindexObject(self, object):
# Reindex in catalog.
catalog = getToolByName(self, 'portal_catalog')
catalog = getToolByName(self, "portal_catalog")
return catalog.reindexObject(object)
indexObject = reindexObject
def unindexObject(self, object):
# Remove from catalog.
catalog = getToolByName(self, 'portal_catalog')
catalog = getToolByName(self, "portal_catalog")
return catalog.unindexObject(object)
def uniqueValuesFor(self, name):
# return unique values for FieldIndex name
catalog = getToolByName(self, 'portal_catalog')
catalog = getToolByName(self, "portal_catalog")
return catalog.uniqueValuesFor(name)
def searchResults(self, REQUEST=None, **kw):
# Calls ZCatalog.searchResults with extra arguments that
# limit the results to what the user is allowed to see.
catalog = getToolByName(self, 'portal_catalog')
catalog = getToolByName(self, "portal_catalog")
object_provides = [IComment.__identifier__]
if 'object_provides' in kw:
kw_provides = kw['object_provides']
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']
if REQUEST is not None and "object_provides" in REQUEST.form:
rq_provides = REQUEST.form["object_provides"]
del REQUEST.form["object_provides"]
if isinstance(str, rq_provides):
object_provides.append(rq_provides)
else:
object_provides.extend(rq_provides)
kw['object_provides'] = object_provides
kw["object_provides"] = object_provides
return catalog.searchResults(REQUEST, **kw)
def index_object(obj, event):
"""Index the object when added to the conversation
"""
"""Index the object when added to the conversation"""
tool = queryUtility(ICommentingTool)
if tool is not None:
tool.indexObject(obj)
def unindex_object(obj, event):
"""Unindex the object when removed
"""
"""Unindex the object when removed"""
tool = queryUtility(ICommentingTool)
if tool is not None:
tool.unindexObject(obj)

View File

@ -7,8 +7,8 @@ from zope.component import getUtility
import logging
default_profile = 'profile-plone.app.discussion:default'
logger = logging.getLogger('plone.app.discussion')
default_profile = "profile-plone.app.discussion:default"
logger = logging.getLogger("plone.app.discussion")
def update_registry(context):
@ -17,7 +17,7 @@ def update_registry(context):
def update_rolemap(context):
context.runImportStepFromProfile(default_profile, 'rolemap')
context.runImportStepFromProfile(default_profile, "rolemap")
def upgrade_comment_workflows_retain_current_workflow(context):
@ -25,16 +25,16 @@ def upgrade_comment_workflows_retain_current_workflow(context):
# import step will change it to comment_one_state_workflow. This is good.
# If it was anything else, we should restore this. So get the original
# chain.
portal_type = 'Discussion Item'
wf_tool = getToolByName(context, 'portal_workflow')
portal_type = "Discussion Item"
wf_tool = getToolByName(context, "portal_workflow")
orig_chain = list(wf_tool.getChainFor(portal_type))
# Run the workflow step. This sets the chain to
# comment_one_state_workflow.
context.runImportStepFromProfile(default_profile, 'workflow')
context.runImportStepFromProfile(default_profile, "workflow")
# Restore original workflow chain if needed.
old_workflow = 'one_state_workflow'
old_workflow = "one_state_workflow"
if old_workflow not in orig_chain:
# Restore the chain. Probably comment_review_workflow.
wf_tool.setChainForPortalTypes([portal_type], orig_chain)
@ -43,7 +43,7 @@ def upgrade_comment_workflows_retain_current_workflow(context):
if old_workflow in orig_chain:
# Replace with new one.
idx = orig_chain.index(old_workflow)
orig_chain[idx] = 'comment_one_state_workflow'
orig_chain[idx] = "comment_one_state_workflow"
# Restore the chain.
wf_tool.setChainForPortalTypes([portal_type], orig_chain)
@ -51,9 +51,9 @@ def upgrade_comment_workflows_retain_current_workflow(context):
def upgrade_comment_workflows_apply_rolemapping(context):
# Now go over the comments, update their role mappings, and reindex the
# allowedRolesAndUsers index.
portal_type = 'Discussion Item'
catalog = getToolByName(context, 'portal_catalog')
wf_tool = getToolByName(context, 'portal_workflow')
portal_type = "Discussion Item"
catalog = getToolByName(context, "portal_catalog")
wf_tool = getToolByName(context, "portal_workflow")
new_chain = list(wf_tool.getChainFor(portal_type))
workflows = [wf_tool.getWorkflowById(wf_id) for wf_id in new_chain]
for brain in catalog.unrestrictedSearchResults(portal_type=portal_type):
@ -63,7 +63,7 @@ def upgrade_comment_workflows_apply_rolemapping(context):
wf.updateRoleMappingsFor(comment)
comment.reindexObjectSecurity()
except (AttributeError, KeyError):
logger.info('Could not reindex comment {0}'.format(brain.getURL()))
logger.info("Could not reindex comment {0}".format(brain.getURL()))
def upgrade_comment_workflows(context):
@ -72,7 +72,7 @@ def upgrade_comment_workflows(context):
def add_js_to_plone_legacy(context):
context.runImportStepFromProfile(default_profile, 'plone.app.registry')
context.runImportStepFromProfile(default_profile, "plone.app.registry")
def extend_review_workflow(context):

View File

@ -7,6 +7,7 @@ from zope.schema.vocabulary import SimpleVocabulary
HAS_CAPTCHA = False
try:
import plone.formwidget.captcha # noqa
HAS_CAPTCHA = True # pragma: no cover
except ImportError:
pass
@ -14,6 +15,7 @@ except ImportError:
HAS_RECAPTCHA = False
try:
import plone.formwidget.recaptcha # noqa
HAS_RECAPTCHA = True # pragma: no cover
except ImportError:
pass
@ -21,6 +23,7 @@ except ImportError:
HAS_AKISMET = False
try:
import collective.akismet # noqa
HAS_AKISMET = True # pragma: no cover
except ImportError:
pass
@ -28,73 +31,48 @@ except ImportError:
HAS_NOROBOTS = False
try:
import collective.z3cform.norobots # noqa
HAS_NOROBOTS = True # pragma: no cover
except ImportError:
pass
def captcha_vocabulary(context):
"""Vocabulary with all available captcha implementations.
"""
"""Vocabulary with all available captcha implementations."""
terms = []
terms.append(
SimpleTerm(
value='disabled',
token='disabled',
title=_(u'Disabled')))
terms.append(SimpleTerm(value="disabled", token="disabled", title=_(u"Disabled")))
if HAS_CAPTCHA: # pragma: no cover
terms.append(
SimpleTerm(
value='captcha',
token='captcha',
title='Captcha'))
terms.append(SimpleTerm(value="captcha", token="captcha", title="Captcha"))
if HAS_RECAPTCHA: # pragma: no cover
terms.append(
SimpleTerm(
value='recaptcha',
token='recaptcha',
title='ReCaptcha'))
SimpleTerm(value="recaptcha", token="recaptcha", title="ReCaptcha")
)
if HAS_AKISMET: # pragma: no cover
terms.append(
SimpleTerm(
value='akismet',
token='akismet',
title='Akismet'))
terms.append(SimpleTerm(value="akismet", token="akismet", title="Akismet"))
if HAS_NOROBOTS: # pragma: no cover
terms.append(
SimpleTerm(
value='norobots',
token='norobots',
title='Norobots'))
terms.append(SimpleTerm(value="norobots", token="norobots", title="Norobots"))
return SimpleVocabulary(terms)
def text_transform_vocabulary(context):
"""Vocabulary with all available portal_transform transformations.
"""
"""Vocabulary with all available portal_transform transformations."""
terms = []
terms.append(SimpleTerm(value="text/plain", token="text/plain", title="Plain text"))
terms.append(SimpleTerm(value="text/html", token="text/html", title="HTML"))
terms.append(
SimpleTerm(
value='text/plain',
token='text/plain',
title='Plain text'))
value="text/x-web-markdown", token="text/x-web-markdown", title="Markdown"
)
)
terms.append(
SimpleTerm(
value='text/html',
token='text/html',
title='HTML'))
terms.append(
SimpleTerm(
value='text/x-web-markdown',
token='text/x-web-markdown',
title='Markdown'))
terms.append(
SimpleTerm(
value='text/x-web-intelligent',
token='text/x-web-intelligent',
title='Intelligent text'))
value="text/x-web-intelligent",
token="text/x-web-intelligent",
title="Intelligent text",
)
)
return SimpleVocabulary(terms)

View File

@ -4,34 +4,34 @@ from setuptools import find_packages
from setuptools import setup
version = '4.0.0a7.dev0'
version = "4.0.0a7.dev0"
install_requires = [
'setuptools',
'plone.app.layout',
'plone.app.registry',
'plone.app.uuid',
'plone.app.z3cform',
'plone.indexer',
'plone.registry',
'plone.z3cform',
'six',
'ZODB3',
'zope.interface',
'zope.component',
'zope.annotation',
'zope.event',
'zope.container',
'zope.lifecycleevent',
'zope.site',
'z3c.form>=2.3.3',
"setuptools",
"plone.app.layout",
"plone.app.registry",
"plone.app.uuid",
"plone.app.z3cform",
"plone.indexer",
"plone.registry",
"plone.z3cform",
"six",
"ZODB3",
"zope.interface",
"zope.component",
"zope.annotation",
"zope.event",
"zope.container",
"zope.lifecycleevent",
"zope.site",
"z3c.form>=2.3.3",
]
setup(name='plone.app.discussion',
setup(
name="plone.app.discussion",
version=version,
description='Enhanced discussion support for Plone',
long_description=open('README.rst').read() + '\n' +
open('CHANGES.rst').read(),
description="Enhanced discussion support for Plone",
long_description=open("README.rst").read() + "\n" + open("CHANGES.rst").read(),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@ -46,24 +46,24 @@ setup(name='plone.app.discussion',
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
keywords='plone discussion',
author='Timo Stollenwerk - Plone Foundation',
author_email='plone-developers@lists.sourceforge.net',
url='https://pypi.org/project/plone.app.discussion',
license='GPL',
keywords="plone discussion",
author="Timo Stollenwerk - Plone Foundation",
author_email="plone-developers@lists.sourceforge.net",
url="https://pypi.org/project/plone.app.discussion",
license="GPL",
packages=find_packages(),
namespace_packages=['plone', 'plone.app'],
namespace_packages=["plone", "plone.app"],
include_package_data=True,
zip_safe=False,
install_requires=install_requires,
extras_require={
'test': [
'plone.app.testing',
'plone.stringinterp',
'plone.contentrules',
'plone.app.contentrules',
'plone.app.contenttypes[test]',
'plone.app.robotframework',
"test": [
"plone.app.testing",
"plone.stringinterp",
"plone.contentrules",
"plone.app.contentrules",
"plone.app.contenttypes[test]",
"plone.app.robotframework",
],
},
entry_points="""