black
This commit is contained in:
parent
e72d86b985
commit
34b758f2bd
@ -24,184 +24,190 @@ import sys
|
|||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc',
|
extensions = [
|
||||||
'sphinx.ext.doctest',
|
"sphinx.ext.autodoc",
|
||||||
'sphinx.ext.intersphinx',
|
"sphinx.ext.doctest",
|
||||||
'sphinx.ext.todo',
|
"sphinx.ext.intersphinx",
|
||||||
'sphinx.ext.coverage',
|
"sphinx.ext.todo",
|
||||||
'repoze.sphinx.autointerface'
|
"sphinx.ext.coverage",
|
||||||
]
|
"repoze.sphinx.autointerface",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix of source filenames.
|
||||||
source_suffix = '.txt'
|
source_suffix = ".txt"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8'
|
# source_encoding = 'utf-8'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'plone.app.discussion'
|
project = u"plone.app.discussion"
|
||||||
copyright = u'2010, Timo Stollenwerk - Plone Foundation'
|
copyright = u"2010, Timo Stollenwerk - Plone Foundation"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '2.0'
|
version = "2.0"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '2.0'
|
release = "2.0"
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#language = None
|
# language = None
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
#today = ''
|
# today = ''
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
#today_fmt = '%B %d, %Y'
|
# today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
# List of documents that shouldn't be included in the build.
|
# List of documents that shouldn't be included in the build.
|
||||||
#unused_docs = []
|
# unused_docs = []
|
||||||
|
|
||||||
# List of directories, relative to source directory, that shouldn't be searched
|
# List of directories, relative to source directory, that shouldn't be searched
|
||||||
# for source files.
|
# for source files.
|
||||||
exclude_trees = []
|
exclude_trees = []
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
#default_role = None
|
# default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
#add_function_parentheses = True
|
# add_function_parentheses = True
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
#add_module_names = True
|
# add_module_names = True
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
#show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# 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.
|
# A list of ignored prefixes for module index sorting.
|
||||||
#modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------
|
# -- Options for HTML output ---------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
#html_theme = 'plone'
|
# html_theme = 'plone'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# 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
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
#html_title = None
|
# html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
#html_short_title = None
|
# html_short_title = None
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
#html_logo = None
|
# html_logo = None
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
# pixels large.
|
# pixels large.
|
||||||
#html_favicon = None
|
# html_favicon = None
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# 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,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# 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,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
# html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
# typographically correct entities.
|
# typographically correct entities.
|
||||||
#html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
#html_sidebars = {}
|
# html_sidebars = {}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
#html_additional_pages = {}
|
# html_additional_pages = {}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#html_use_modindex = True
|
# html_use_modindex = True
|
||||||
|
|
||||||
# If false, no index is generated.
|
# If false, no index is generated.
|
||||||
#html_use_index = True
|
# html_use_index = True
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
# If true, the index is split into individual pages for each letter.
|
||||||
#html_split_index = False
|
# html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
#html_show_sourcelink = True
|
# html_show_sourcelink = True
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
# base URL from which the finished HTML is served.
|
# base URL from which the finished HTML is served.
|
||||||
#html_use_opensearch = ''
|
# html_use_opensearch = ''
|
||||||
|
|
||||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
#html_file_suffix = ''
|
# html_file_suffix = ''
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'ploneappdiscussiondoc'
|
htmlhelp_basename = "ploneappdiscussiondoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
# The paper size ('letter' or 'a4').
|
||||||
#latex_paper_size = 'letter'
|
# latex_paper_size = 'letter'
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#latex_font_size = '10pt'
|
# latex_font_size = '10pt'
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation',
|
(
|
||||||
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
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
# the title page.
|
# the title page.
|
||||||
#latex_logo = None
|
# latex_logo = None
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
# not chapters.
|
# not chapters.
|
||||||
#latex_use_parts = False
|
# latex_use_parts = False
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#latex_preamble = ''
|
# latex_preamble = ''
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#latex_appendices = []
|
# latex_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#latex_use_modindex = True
|
# latex_use_modindex = True
|
||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
intersphinx_mapping = {"http://docs.python.org/": None}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
__import__('pkg_resources').declare_namespace(__name__)
|
__import__("pkg_resources").declare_namespace(__name__)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
__import__('pkg_resources').declare_namespace(__name__)
|
__import__("pkg_resources").declare_namespace(__name__)
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
from zope.i18nmessageid import MessageFactory
|
from zope.i18nmessageid import MessageFactory
|
||||||
|
|
||||||
|
|
||||||
_ = MessageFactory('plone')
|
_ = MessageFactory("plone")
|
||||||
|
@ -21,9 +21,9 @@ from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
|||||||
@adapter(Comment)
|
@adapter(Comment)
|
||||||
@interface.implementer(ICaptcha)
|
@interface.implementer(ICaptcha)
|
||||||
class Captcha(Persistent):
|
class Captcha(Persistent):
|
||||||
"""Captcha input field.
|
"""Captcha input field."""
|
||||||
"""
|
|
||||||
captcha = u''
|
captcha = u""
|
||||||
|
|
||||||
|
|
||||||
Captcha = factory(Captcha)
|
Captcha = factory(Captcha)
|
||||||
@ -47,22 +47,24 @@ class CaptchaExtender(extensible.FormExtender):
|
|||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
self.captcha = settings.captcha
|
self.captcha = settings.captcha
|
||||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
portal_membership = getToolByName(self.context, "portal_membership")
|
||||||
self.isAnon = portal_membership.isAnonymousUser()
|
self.isAnon = portal_membership.isAnonymousUser()
|
||||||
|
|
||||||
def update(self):
|
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
|
# Add a captcha field if captcha is enabled in the registry
|
||||||
self.add(ICaptcha, prefix='')
|
self.add(ICaptcha, prefix="")
|
||||||
if self.captcha == 'captcha':
|
if self.captcha == "captcha":
|
||||||
from plone.formwidget.captcha import CaptchaFieldWidget
|
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
|
from plone.formwidget.recaptcha import ReCaptchaFieldWidget
|
||||||
self.form.fields['captcha'].widgetFactory = \
|
|
||||||
ReCaptchaFieldWidget
|
self.form.fields["captcha"].widgetFactory = ReCaptchaFieldWidget
|
||||||
elif self.captcha == 'norobots':
|
elif self.captcha == "norobots":
|
||||||
from collective.z3cform.norobots import NorobotsFieldWidget
|
from collective.z3cform.norobots import NorobotsFieldWidget
|
||||||
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
|
||||||
|
self.form.fields["captcha"].widgetFactory = NorobotsFieldWidget
|
||||||
else:
|
else:
|
||||||
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
self.form.fields["captcha"].mode = interfaces.HIDDEN_MODE
|
||||||
|
@ -37,8 +37,7 @@ class View(BrowserView):
|
|||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
|
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
view_action_types = registry.get(
|
view_action_types = registry.get("plone.types_use_view_action_in_listings", [])
|
||||||
'plone.types_use_view_action_in_listings', [])
|
|
||||||
|
|
||||||
obj = aq_parent(aq_parent(context))
|
obj = aq_parent(aq_parent(context))
|
||||||
url = obj.absolute_url()
|
url = obj.absolute_url()
|
||||||
@ -49,33 +48,34 @@ class View(BrowserView):
|
|||||||
will redirect right to the binary object, bypassing comments.
|
will redirect right to the binary object, bypassing comments.
|
||||||
"""
|
"""
|
||||||
if obj.portal_type in view_action_types:
|
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):
|
class EditCommentForm(CommentForm):
|
||||||
"""Form to edit an existing comment."""
|
"""Form to edit an existing comment."""
|
||||||
|
|
||||||
ignoreContext = True
|
ignoreContext = True
|
||||||
id = 'edit-comment-form'
|
id = "edit-comment-form"
|
||||||
label = _(u'edit_comment_form_title', default=u'Edit comment')
|
label = _(u"edit_comment_form_title", default=u"Edit comment")
|
||||||
|
|
||||||
def updateWidgets(self):
|
def updateWidgets(self):
|
||||||
super(EditCommentForm, self).updateWidgets()
|
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
|
# We have to rename the id, otherwise TinyMCE can't initialize
|
||||||
# because there are two textareas with the same id.
|
# 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:
|
if not target:
|
||||||
portal_state = getMultiAdapter((self.context, self.request),
|
portal_state = getMultiAdapter(
|
||||||
name=u'plone_portal_state')
|
(self.context, self.request), name=u"plone_portal_state"
|
||||||
|
)
|
||||||
target = portal_state.portal_url()
|
target = portal_state.portal_url()
|
||||||
self.request.response.redirect(target)
|
self.request.response.redirect(target)
|
||||||
|
|
||||||
@button.buttonAndHandler(_(u'label_save',
|
@button.buttonAndHandler(_(u"label_save", default=u"Save"), name="comment")
|
||||||
default=u'Save'), name='comment')
|
|
||||||
def handleComment(self, action):
|
def handleComment(self, action):
|
||||||
|
|
||||||
# Validate form
|
# Validate form
|
||||||
@ -84,32 +84,28 @@ class EditCommentForm(CommentForm):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions
|
||||||
can_edit = getSecurityManager().checkPermission(
|
can_edit = getSecurityManager().checkPermission("Edit comments", self.context)
|
||||||
'Edit comments',
|
mtool = getToolByName(self.context, "portal_membership")
|
||||||
self.context)
|
|
||||||
mtool = getToolByName(self.context, 'portal_membership')
|
|
||||||
if mtool.isAnonymousUser() or not can_edit:
|
if mtool.isAnonymousUser() or not can_edit:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update text
|
# Update text
|
||||||
self.context.text = data['text']
|
self.context.text = data["text"]
|
||||||
# Notify that the object has been modified
|
# Notify that the object has been modified
|
||||||
notify(ObjectModifiedEvent(self.context))
|
notify(ObjectModifiedEvent(self.context))
|
||||||
|
|
||||||
# Redirect to comment
|
# Redirect to comment
|
||||||
IStatusMessage(self.request).add(_(u'comment_edit_notification',
|
IStatusMessage(self.request).add(
|
||||||
default='Comment was edited'),
|
_(u"comment_edit_notification", default="Comment was edited"), type="info"
|
||||||
type='info')
|
)
|
||||||
return self._redirect(
|
return self._redirect(target=self.action.replace("@@edit-comment", "@@view"))
|
||||||
target=self.action.replace('@@edit-comment', '@@view'))
|
|
||||||
|
|
||||||
@button.buttonAndHandler(_(u'cancel_form_button',
|
@button.buttonAndHandler(_(u"cancel_form_button", default=u"Cancel"), name="cancel")
|
||||||
default=u'Cancel'), name='cancel')
|
|
||||||
def handle_cancel(self, action):
|
def handle_cancel(self, action):
|
||||||
IStatusMessage(self.request).add(
|
IStatusMessage(self.request).add(
|
||||||
_(u'comment_edit_cancel_notification',
|
_(u"comment_edit_cancel_notification", default=u"Edit comment cancelled"),
|
||||||
default=u'Edit comment cancelled'),
|
type="info",
|
||||||
type='info')
|
)
|
||||||
return self._redirect(target=self.context.absolute_url())
|
return self._redirect(target=self.context.absolute_url())
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,28 +35,28 @@ from zope.interface import alsoProvides
|
|||||||
|
|
||||||
|
|
||||||
COMMENT_DESCRIPTION_PLAIN_TEXT = _(
|
COMMENT_DESCRIPTION_PLAIN_TEXT = _(
|
||||||
u'comment_description_plain_text',
|
u"comment_description_plain_text",
|
||||||
default=u'You can add a comment by filling out the form below. '
|
default=u"You can add a comment by filling out the form below. "
|
||||||
u'Plain text formatting.',
|
u"Plain text formatting.",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMENT_DESCRIPTION_MARKDOWN = _(
|
COMMENT_DESCRIPTION_MARKDOWN = _(
|
||||||
u'comment_description_markdown',
|
u"comment_description_markdown",
|
||||||
default=u'You can add a comment by filling out the form below. '
|
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"Plain text formatting. You can use the Markdown syntax for "
|
||||||
u'links and images.',
|
u"links and images.",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
|
COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
|
||||||
u'comment_description_intelligent_text',
|
u"comment_description_intelligent_text",
|
||||||
default=u'You can add a comment by filling out the form below. '
|
default=u"You can add a comment by filling out the form below. "
|
||||||
u'Plain text formatting. Web and email addresses are '
|
u"Plain text formatting. Web and email addresses are "
|
||||||
u'transformed into clickable links.',
|
u"transformed into clickable links.",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
|
COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
|
||||||
u'comment_description_moderation_enabled',
|
u"comment_description_moderation_enabled",
|
||||||
default=u'Comments are moderated.',
|
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
|
ignoreContext = True # don't use context to get widget data
|
||||||
id = None
|
id = None
|
||||||
label = _(u'Add a comment')
|
label = _(u"Add a comment")
|
||||||
fields = field.Fields(IComment).omit('portal_type',
|
fields = field.Fields(IComment).omit(
|
||||||
'__parent__',
|
"portal_type",
|
||||||
'__name__',
|
"__parent__",
|
||||||
'comment_id',
|
"__name__",
|
||||||
'mime_type',
|
"comment_id",
|
||||||
'creator',
|
"mime_type",
|
||||||
'creation_date',
|
"creator",
|
||||||
'modification_date',
|
"creation_date",
|
||||||
'author_username',
|
"modification_date",
|
||||||
'title')
|
"author_username",
|
||||||
|
"title",
|
||||||
|
)
|
||||||
|
|
||||||
def updateFields(self):
|
def updateFields(self):
|
||||||
super(CommentForm, self).updateFields()
|
super(CommentForm, self).updateFields()
|
||||||
self.fields['user_notification'].widgetFactory = \
|
self.fields["user_notification"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
SingleCheckBoxFieldWidget
|
|
||||||
|
|
||||||
def updateWidgets(self):
|
def updateWidgets(self):
|
||||||
super(CommentForm, self).updateWidgets()
|
super(CommentForm, self).updateWidgets()
|
||||||
|
|
||||||
# Widgets
|
# Widgets
|
||||||
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
|
self.widgets["in_reply_to"].mode = interfaces.HIDDEN_MODE
|
||||||
self.widgets['text'].addClass('autoresize')
|
self.widgets["text"].addClass("autoresize")
|
||||||
self.widgets['user_notification'].label = _(u'')
|
self.widgets["user_notification"].label = _(u"")
|
||||||
# Reset widget field settings to their defaults, which may be changed
|
# Reset widget field settings to their defaults, which may be changed
|
||||||
# further on. Otherwise, the email field might get set to required
|
# further on. Otherwise, the email field might get set to required
|
||||||
# when an anonymous user visits, and then remain required when an
|
# 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
|
# 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
|
# widget is new each time, but the field is the same item in memory as
|
||||||
# the previous time.
|
# 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
|
# 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
|
# previous value on the field, so we need to reset it here. Changing
|
||||||
# the field in updateFields does not help.
|
# 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
|
# Rename the id of the text widgets because there can be css-id
|
||||||
# clashes with the text field of documents when using and overlay
|
# clashes with the text field of documents when using and overlay
|
||||||
# with TinyMCE.
|
# with TinyMCE.
|
||||||
self.widgets['text'].id = 'form-widgets-comment-text'
|
self.widgets["text"].id = "form-widgets-comment-text"
|
||||||
|
|
||||||
# Anonymous / Logged-in
|
# Anonymous / Logged-in
|
||||||
mtool = getToolByName(self.context, 'portal_membership')
|
mtool = getToolByName(self.context, "portal_membership")
|
||||||
anon = mtool.isAnonymousUser()
|
anon = mtool.isAnonymousUser()
|
||||||
|
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -119,52 +120,51 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
if settings.anonymous_email_enabled:
|
if settings.anonymous_email_enabled:
|
||||||
# according to IDiscussionSettings.anonymous_email_enabled:
|
# according to IDiscussionSettings.anonymous_email_enabled:
|
||||||
# 'If selected, anonymous user will have to give their email.'
|
# 'If selected, anonymous user will have to give their email.'
|
||||||
self.widgets['author_email'].field.required = True
|
self.widgets["author_email"].field.required = True
|
||||||
self.widgets['author_email'].required = True
|
self.widgets["author_email"].required = True
|
||||||
else:
|
else:
|
||||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
self.widgets["author_email"].mode = interfaces.HIDDEN_MODE
|
||||||
else:
|
else:
|
||||||
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
|
self.widgets["author_name"].mode = interfaces.HIDDEN_MODE
|
||||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
self.widgets["author_email"].mode = interfaces.HIDDEN_MODE
|
||||||
|
|
||||||
member = mtool.getAuthenticatedMember()
|
member = mtool.getAuthenticatedMember()
|
||||||
member_email = member.getProperty('email')
|
member_email = member.getProperty("email")
|
||||||
|
|
||||||
# Hide the user_notification checkbox if user notification is disabled
|
# 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
|
# or the user is not logged in. Also check if the user has a valid
|
||||||
# email address
|
# email address
|
||||||
member_email_is_empty = member_email == ''
|
member_email_is_empty = member_email == ""
|
||||||
user_notification_disabled = not settings.user_notification_enabled
|
user_notification_disabled = not settings.user_notification_enabled
|
||||||
if member_email_is_empty or user_notification_disabled or anon:
|
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):
|
def updateActions(self):
|
||||||
super(CommentForm, self).updateActions()
|
super(CommentForm, self).updateActions()
|
||||||
self.actions['cancel'].addClass('btn btn-secondary')
|
self.actions["cancel"].addClass("btn btn-secondary")
|
||||||
self.actions['cancel'].addClass('hide')
|
self.actions["cancel"].addClass("hide")
|
||||||
self.actions['comment'].addClass('btn btn-primary')
|
self.actions["comment"].addClass("btn btn-primary")
|
||||||
|
|
||||||
def get_author(self, data):
|
def get_author(self, data):
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
# some attributes are not always set
|
# some attributes are not always set
|
||||||
author_name = u''
|
author_name = u""
|
||||||
|
|
||||||
# Make sure author_name/ author_email is properly encoded
|
# Make sure author_name/ author_email is properly encoded
|
||||||
if 'author_name' in data:
|
if "author_name" in data:
|
||||||
author_name = safe_unicode(data['author_name'])
|
author_name = safe_unicode(data["author_name"])
|
||||||
if 'author_email' in data:
|
if "author_email" in data:
|
||||||
author_email = safe_unicode(data['author_email'])
|
author_email = safe_unicode(data["author_email"])
|
||||||
|
|
||||||
# Set comment author properties for anonymous users or members
|
# 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()
|
anon = portal_membership.isAnonymousUser()
|
||||||
if not anon and getSecurityManager().checkPermission(
|
if not anon and getSecurityManager().checkPermission("Reply to item", context):
|
||||||
'Reply to item', context):
|
|
||||||
# Member
|
# Member
|
||||||
member = portal_membership.getAuthenticatedMember()
|
member = portal_membership.getAuthenticatedMember()
|
||||||
email = safe_unicode(member.getProperty('email'))
|
email = safe_unicode(member.getProperty("email"))
|
||||||
fullname = member.getProperty('fullname')
|
fullname = member.getProperty("fullname")
|
||||||
if not fullname or fullname == '':
|
if not fullname or fullname == "":
|
||||||
fullname = member.getUserName()
|
fullname = member.getUserName()
|
||||||
fullname = safe_unicode(fullname)
|
fullname = safe_unicode(fullname)
|
||||||
author_name = fullname
|
author_name = fullname
|
||||||
@ -179,7 +179,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
|
|
||||||
def create_comment(self, data):
|
def create_comment(self, data):
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
|
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
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)
|
comment.author_name, comment.author_email = self.get_author(data)
|
||||||
|
|
||||||
# Set comment author properties for anonymous users or members
|
# 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()
|
anon = portal_membership.isAnonymousUser()
|
||||||
if anon and anonymous_comments:
|
if anon and anonymous_comments:
|
||||||
# Anonymous Users
|
# Anonymous Users
|
||||||
comment.user_notification = None
|
comment.user_notification = None
|
||||||
elif not anon and getSecurityManager().checkPermission(
|
elif not anon and getSecurityManager().checkPermission(
|
||||||
'Reply to item', context):
|
"Reply to item", context
|
||||||
|
):
|
||||||
# Member
|
# Member
|
||||||
member = portal_membership.getAuthenticatedMember()
|
member = portal_membership.getAuthenticatedMember()
|
||||||
memberid = member.getId()
|
memberid = member.getId()
|
||||||
user = member.getUser()
|
user = member.getUser()
|
||||||
comment.changeOwnership(user, recursive=False)
|
comment.changeOwnership(user, recursive=False)
|
||||||
comment.manage_setLocalRoles(memberid, ['Owner'])
|
comment.manage_setLocalRoles(memberid, ["Owner"])
|
||||||
comment.creator = memberid
|
comment.creator = memberid
|
||||||
comment.author_username = memberid
|
comment.author_username = memberid
|
||||||
|
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise Unauthorized(
|
raise Unauthorized(
|
||||||
u'Anonymous user tries to post a comment, but anonymous '
|
u"Anonymous user tries to post a comment, but anonymous "
|
||||||
u'commenting is disabled. Or user does not have the '
|
u"commenting is disabled. Or user does not have the "
|
||||||
u"'reply to item' permission.",
|
u"'reply to item' permission.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return comment
|
return comment
|
||||||
|
|
||||||
@button.buttonAndHandler(_(u'add_comment_button', default=u'Comment'),
|
@button.buttonAndHandler(
|
||||||
name='comment')
|
_(u"add_comment_button", default=u"Comment"), name="comment"
|
||||||
|
)
|
||||||
def handleComment(self, action):
|
def handleComment(self, action):
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
|
|
||||||
# Check if conversation is enabled on this content object
|
# Check if conversation is enabled on this content object
|
||||||
if not self.__parent__.restrictedTraverse(
|
if not self.__parent__.restrictedTraverse(
|
||||||
'@@conversation_view',
|
"@@conversation_view",
|
||||||
).enabled():
|
).enabled():
|
||||||
raise Unauthorized(
|
raise Unauthorized(
|
||||||
'Discussion is not enabled for this content object.',
|
"Discussion is not enabled for this content object.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validation form
|
# Validation form
|
||||||
@ -246,28 +248,26 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
# Validate Captcha
|
# Validate Captcha
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
portal_membership = getToolByName(self.context, "portal_membership")
|
||||||
captcha_enabled = settings.captcha != 'disabled'
|
captcha_enabled = settings.captcha != "disabled"
|
||||||
anonymous_comments = settings.anonymous_comments
|
anonymous_comments = settings.anonymous_comments
|
||||||
anon = portal_membership.isAnonymousUser()
|
anon = portal_membership.isAnonymousUser()
|
||||||
if captcha_enabled and anonymous_comments and anon:
|
if captcha_enabled and anonymous_comments and anon:
|
||||||
if 'captcha' not in data:
|
if "captcha" not in data:
|
||||||
data['captcha'] = u''
|
data["captcha"] = u""
|
||||||
captcha = CaptchaValidator(self.context,
|
captcha = CaptchaValidator(
|
||||||
self.request,
|
self.context, self.request, None, ICaptcha["captcha"], None
|
||||||
None,
|
)
|
||||||
ICaptcha['captcha'],
|
captcha.validate(data["captcha"])
|
||||||
None)
|
|
||||||
captcha.validate(data['captcha'])
|
|
||||||
|
|
||||||
# Create comment
|
# Create comment
|
||||||
comment = self.create_comment(data)
|
comment = self.create_comment(data)
|
||||||
|
|
||||||
# Add comment to conversation
|
# Add comment to conversation
|
||||||
conversation = IConversation(self.__parent__)
|
conversation = IConversation(self.__parent__)
|
||||||
if data['in_reply_to']:
|
if data["in_reply_to"]:
|
||||||
# Add a reply to an existing comment
|
# 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)
|
replies = IReplies(conversation_to_reply_to)
|
||||||
comment_id = replies.addComment(comment)
|
comment_id = replies.addComment(comment)
|
||||||
else:
|
else:
|
||||||
@ -279,25 +279,24 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
# shown to the user that his/her comment awaits moderation. If the user
|
# shown to the user that his/her comment awaits moderation. If the user
|
||||||
# has 'review comments' permission, he/she is redirected directly
|
# has 'review comments' permission, he/she is redirected directly
|
||||||
# to the comment.
|
# to the comment.
|
||||||
can_review = getSecurityManager().checkPermission('Review comments',
|
can_review = getSecurityManager().checkPermission("Review comments", context)
|
||||||
context)
|
workflowTool = getToolByName(context, "portal_workflow")
|
||||||
workflowTool = getToolByName(context, 'portal_workflow')
|
|
||||||
comment_review_state = workflowTool.getInfoFor(
|
comment_review_state = workflowTool.getInfoFor(
|
||||||
comment,
|
comment,
|
||||||
'review_state',
|
"review_state",
|
||||||
None,
|
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
|
# Show info message when comment moderation is enabled
|
||||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||||
_('Your comment awaits moderator approval.'),
|
_("Your comment awaits moderator approval."), type="info"
|
||||||
type='info')
|
)
|
||||||
self.request.response.redirect(self.action)
|
self.request.response.redirect(self.action)
|
||||||
else:
|
else:
|
||||||
# Redirect to comment (inside a content object page)
|
# 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):
|
def handleCancel(self, action):
|
||||||
# This method should never be called, it's only there to show
|
# This method should never be called, it's only there to show
|
||||||
# a cancel button that is handled by a jQuery method.
|
# a cancel button that is handled by a jQuery method.
|
||||||
@ -307,15 +306,15 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
class CommentsViewlet(ViewletBase):
|
class CommentsViewlet(ViewletBase):
|
||||||
|
|
||||||
form = CommentForm
|
form = CommentForm
|
||||||
index = ViewPageTemplateFile('comments.pt')
|
index = ViewPageTemplateFile("comments.pt")
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
super(CommentsViewlet, self).update()
|
super(CommentsViewlet, self).update()
|
||||||
discussion_allowed = self.is_discussion_allowed()
|
discussion_allowed = self.is_discussion_allowed()
|
||||||
anonymous_allowed_or_can_reply = (
|
anonymous_allowed_or_can_reply = (
|
||||||
self.is_anonymous() and
|
self.is_anonymous()
|
||||||
self.anonymous_discussion_allowed() or
|
and self.anonymous_discussion_allowed()
|
||||||
self.can_reply()
|
or self.can_reply()
|
||||||
)
|
)
|
||||||
if discussion_allowed and anonymous_allowed_or_can_reply:
|
if discussion_allowed and anonymous_allowed_or_can_reply:
|
||||||
z2.switch_on(self, request_layer=IFormLayer)
|
z2.switch_on(self, request_layer=IFormLayer)
|
||||||
@ -326,10 +325,10 @@ class CommentsViewlet(ViewletBase):
|
|||||||
# view methods
|
# view methods
|
||||||
|
|
||||||
def can_reply(self):
|
def can_reply(self):
|
||||||
"""Returns true if current user has the 'Reply to item' permission.
|
"""Returns true if current user has the 'Reply to item' permission."""
|
||||||
"""
|
return getSecurityManager().checkPermission(
|
||||||
return getSecurityManager().checkPermission('Reply to item',
|
"Reply to item", aq_inner(self.context)
|
||||||
aq_inner(self.context))
|
)
|
||||||
|
|
||||||
def can_manage(self):
|
def can_manage(self):
|
||||||
"""We keep this method for <= 1.0b9 backward compatibility. Since we do
|
"""We keep this method for <= 1.0b9 backward compatibility. Since we do
|
||||||
@ -338,18 +337,17 @@ class CommentsViewlet(ViewletBase):
|
|||||||
return self.can_review()
|
return self.can_review()
|
||||||
|
|
||||||
def can_review(self):
|
def can_review(self):
|
||||||
"""Returns true if current user has the 'Review comments' permission.
|
"""Returns true if current user has the 'Review comments' permission."""
|
||||||
"""
|
return getSecurityManager().checkPermission(
|
||||||
return getSecurityManager().checkPermission('Review comments',
|
"Review comments", aq_inner(self.context)
|
||||||
aq_inner(self.context))
|
)
|
||||||
|
|
||||||
def can_delete_own(self, comment):
|
def can_delete_own(self, comment):
|
||||||
"""Returns true if the current user can delete the comment. Only
|
"""Returns true if the current user can delete the comment. Only
|
||||||
comments without replies can be deleted.
|
comments without replies can be deleted.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return comment.restrictedTraverse(
|
return comment.restrictedTraverse("@@delete-own-comment").can_delete()
|
||||||
'@@delete-own-comment').can_delete()
|
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -358,8 +356,7 @@ class CommentsViewlet(ViewletBase):
|
|||||||
no replies. This is used to prepare hidden form buttons for JS.
|
no replies. This is used to prepare hidden form buttons for JS.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return comment.restrictedTraverse(
|
return comment.restrictedTraverse("@@delete-own-comment").could_delete()
|
||||||
'@@delete-own-comment').could_delete()
|
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -367,19 +364,17 @@ class CommentsViewlet(ViewletBase):
|
|||||||
"""Returns true if current user has the 'Edit comments'
|
"""Returns true if current user has the 'Edit comments'
|
||||||
permission.
|
permission.
|
||||||
"""
|
"""
|
||||||
return getSecurityManager().checkPermission('Edit comments',
|
return getSecurityManager().checkPermission("Edit comments", aq_inner(reply))
|
||||||
aq_inner(reply))
|
|
||||||
|
|
||||||
def can_delete(self, reply):
|
def can_delete(self, reply):
|
||||||
"""Returns true if current user has the 'Delete comments'
|
"""Returns true if current user has the 'Delete comments'
|
||||||
permission.
|
permission.
|
||||||
"""
|
"""
|
||||||
return getSecurityManager().checkPermission('Delete comments',
|
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
|
||||||
aq_inner(reply))
|
|
||||||
|
|
||||||
def is_discussion_allowed(self):
|
def is_discussion_allowed(self):
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
return context.restrictedTraverse('@@conversation_view').enabled()
|
return context.restrictedTraverse("@@conversation_view").enabled()
|
||||||
|
|
||||||
def comment_transform_message(self):
|
def comment_transform_message(self):
|
||||||
"""Returns the description that shows up above the comment text,
|
"""Returns the description that shows up above the comment text,
|
||||||
@ -391,34 +386,41 @@ class CommentsViewlet(ViewletBase):
|
|||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
|
|
||||||
# text transform setting
|
# text transform setting
|
||||||
if settings.text_transform == 'text/x-web-intelligent':
|
if settings.text_transform == "text/x-web-intelligent":
|
||||||
message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT),
|
message = translate(
|
||||||
context=self.request)
|
Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT), context=self.request
|
||||||
elif settings.text_transform == 'text/x-web-markdown':
|
)
|
||||||
message = translate(Message(COMMENT_DESCRIPTION_MARKDOWN),
|
elif settings.text_transform == "text/x-web-markdown":
|
||||||
context=self.request)
|
message = translate(
|
||||||
|
Message(COMMENT_DESCRIPTION_MARKDOWN), context=self.request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
message = translate(Message(COMMENT_DESCRIPTION_PLAIN_TEXT),
|
message = translate(
|
||||||
context=self.request)
|
Message(COMMENT_DESCRIPTION_PLAIN_TEXT), context=self.request
|
||||||
|
)
|
||||||
|
|
||||||
# comment workflow
|
# comment workflow
|
||||||
wftool = getToolByName(context, 'portal_workflow', None)
|
wftool = getToolByName(context, "portal_workflow", None)
|
||||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||||
if workflow_chain:
|
if workflow_chain:
|
||||||
comment_workflow = workflow_chain[0]
|
comment_workflow = workflow_chain[0]
|
||||||
comment_workflow = wftool[comment_workflow]
|
comment_workflow = wftool[comment_workflow]
|
||||||
# check if the current workflow implements a pending state. If this
|
# check if the current workflow implements a pending state. If this
|
||||||
# is true comments are moderated
|
# is true comments are moderated
|
||||||
if 'pending' in comment_workflow.states:
|
if "pending" in comment_workflow.states:
|
||||||
message = message + ' ' + \
|
message = (
|
||||||
translate(Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
|
message
|
||||||
context=self.request)
|
+ " "
|
||||||
|
+ translate(
|
||||||
|
Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
|
||||||
|
context=self.request,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def has_replies(self, workflow_actions=False):
|
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:
|
if self.get_replies(workflow_actions) is not None:
|
||||||
try:
|
try:
|
||||||
next(self.get_replies(workflow_actions))
|
next(self.get_replies(workflow_actions))
|
||||||
@ -442,31 +444,32 @@ class CommentsViewlet(ViewletBase):
|
|||||||
if conversation is None:
|
if conversation is None:
|
||||||
return iter([])
|
return iter([])
|
||||||
|
|
||||||
wf = getToolByName(context, 'portal_workflow')
|
wf = getToolByName(context, "portal_workflow")
|
||||||
# workflow_actions is only true when user
|
# workflow_actions is only true when user
|
||||||
# has 'Manage portal' permission
|
# has 'Manage portal' permission
|
||||||
|
|
||||||
def replies_with_workflow_actions():
|
def replies_with_workflow_actions():
|
||||||
# Generator that returns replies dict with workflow actions
|
# Generator that returns replies dict with workflow actions
|
||||||
for r in conversation.getThreads():
|
for r in conversation.getThreads():
|
||||||
comment_obj = r['comment']
|
comment_obj = r["comment"]
|
||||||
# list all possible workflow actions
|
# list all possible workflow actions
|
||||||
actions = [
|
actions = [
|
||||||
a for a in wf.listActionInfos(object=comment_obj)
|
a
|
||||||
if a['category'] == 'workflow' and a['allowed']
|
for a in wf.listActionInfos(object=comment_obj)
|
||||||
|
if a["category"] == "workflow" and a["allowed"]
|
||||||
]
|
]
|
||||||
r = r.copy()
|
r = r.copy()
|
||||||
r['actions'] = actions
|
r["actions"] = actions
|
||||||
yield r
|
yield r
|
||||||
|
|
||||||
def published_replies():
|
def published_replies():
|
||||||
# Generator that returns replies dict with workflow status.
|
# Generator that returns replies dict with workflow status.
|
||||||
for r in conversation.getThreads():
|
for r in conversation.getThreads():
|
||||||
comment_obj = r['comment']
|
comment_obj = r["comment"]
|
||||||
workflow_status = wf.getInfoFor(comment_obj, 'review_state')
|
workflow_status = wf.getInfoFor(comment_obj, "review_state")
|
||||||
if workflow_status == 'published':
|
if workflow_status == "published":
|
||||||
r = r.copy()
|
r = r.copy()
|
||||||
r['workflow_status'] = workflow_status
|
r["workflow_status"] = workflow_status
|
||||||
yield r
|
yield r
|
||||||
|
|
||||||
# Return all direct replies
|
# Return all direct replies
|
||||||
@ -480,20 +483,16 @@ class CommentsViewlet(ViewletBase):
|
|||||||
if username is None:
|
if username is None:
|
||||||
return None
|
return None
|
||||||
else:
|
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):
|
def get_commenter_portrait(self, username=None):
|
||||||
|
|
||||||
if username is None:
|
if username is None:
|
||||||
# return the default user image if no username is given
|
# return the default user image if no username is given
|
||||||
return 'defaultUser.png'
|
return "defaultUser.png"
|
||||||
else:
|
else:
|
||||||
portal_membership = getToolByName(self.context,
|
portal_membership = getToolByName(self.context, "portal_membership", None)
|
||||||
'portal_membership',
|
return portal_membership.getPersonalPortrait(username).absolute_url()
|
||||||
None)
|
|
||||||
return portal_membership\
|
|
||||||
.getPersonalPortrait(username)\
|
|
||||||
.absolute_url()
|
|
||||||
|
|
||||||
def anonymous_discussion_allowed(self):
|
def anonymous_discussion_allowed(self):
|
||||||
# Check if anonymous comments are allowed in the registry
|
# Check if anonymous comments are allowed in the registry
|
||||||
@ -520,20 +519,18 @@ class CommentsViewlet(ViewletBase):
|
|||||||
return settings.show_commenter_image
|
return settings.show_commenter_image
|
||||||
|
|
||||||
def is_anonymous(self):
|
def is_anonymous(self):
|
||||||
portal_membership = getToolByName(self.context,
|
portal_membership = getToolByName(self.context, "portal_membership", None)
|
||||||
'portal_membership',
|
|
||||||
None)
|
|
||||||
return portal_membership.isAnonymousUser()
|
return portal_membership.isAnonymousUser()
|
||||||
|
|
||||||
def login_action(self):
|
def login_action(self):
|
||||||
return '{0}/login_form?came_from={1}'.format(
|
return "{0}/login_form?came_from={1}".format(
|
||||||
self.navigation_root_url,
|
self.navigation_root_url,
|
||||||
quote(self.request.get('URL', '')),
|
quote(self.request.get("URL", "")),
|
||||||
)
|
)
|
||||||
|
|
||||||
def format_time(self, time):
|
def format_time(self, time):
|
||||||
# We have to transform Python datetime into Zope DateTime
|
# We have to transform Python datetime into Zope DateTime
|
||||||
# before we can call toLocalizedTime.
|
# before we can call toLocalizedTime.
|
||||||
util = getToolByName(self.context, 'translation_service')
|
util = getToolByName(self.context, "translation_service")
|
||||||
zope_time = DateTime(time.isoformat())
|
zope_time = DateTime(time.isoformat())
|
||||||
return util.toLocalizedTime(zope_time, long_format=True)
|
return util.toLocalizedTime(zope_time, long_format=True)
|
||||||
|
@ -28,42 +28,40 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||||
"""Discussion settings form.
|
"""Discussion settings form."""
|
||||||
"""
|
|
||||||
schema = IDiscussionSettings
|
schema = IDiscussionSettings
|
||||||
id = 'DiscussionSettingsEditForm'
|
id = "DiscussionSettingsEditForm"
|
||||||
label = _(u'Discussion settings')
|
label = _(u"Discussion settings")
|
||||||
description = _(
|
description = _(
|
||||||
u'help_discussion_settings_editform',
|
u"help_discussion_settings_editform",
|
||||||
default=u'Some discussion related settings are not '
|
default=u"Some discussion related settings are not "
|
||||||
u'located in the Discussion Control Panel.\n'
|
u"located in the Discussion Control Panel.\n"
|
||||||
u'To enable comments for a specific content type, '
|
u"To enable comments for a specific content type, "
|
||||||
u'go to the Types Control Panel of this type and '
|
u"go to the Types Control Panel of this type and "
|
||||||
u'choose "Allow comments".\n'
|
u'choose "Allow comments".\n'
|
||||||
u'To enable the moderation workflow for comments, '
|
u"To enable the moderation workflow for comments, "
|
||||||
u'go to the Types Control Panel, choose '
|
u"go to the Types Control Panel, choose "
|
||||||
u'"Comment" and set workflow to '
|
u'"Comment" and set workflow to '
|
||||||
u'"Comment Review Workflow".',
|
u'"Comment Review Workflow".',
|
||||||
)
|
)
|
||||||
|
|
||||||
def updateFields(self):
|
def updateFields(self):
|
||||||
super(DiscussionSettingsEditForm, self).updateFields()
|
super(DiscussionSettingsEditForm, self).updateFields()
|
||||||
self.fields['globally_enabled'].widgetFactory = \
|
self.fields["globally_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
SingleCheckBoxFieldWidget
|
self.fields["moderation_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
self.fields['moderation_enabled'].widgetFactory = \
|
self.fields["edit_comment_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
SingleCheckBoxFieldWidget
|
self.fields[
|
||||||
self.fields['edit_comment_enabled'].widgetFactory = \
|
"delete_own_comment_enabled"
|
||||||
SingleCheckBoxFieldWidget
|
].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
self.fields['delete_own_comment_enabled'].widgetFactory = \
|
self.fields["anonymous_comments"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
SingleCheckBoxFieldWidget
|
self.fields["show_commenter_image"].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
self.fields['anonymous_comments'].widgetFactory = \
|
self.fields[
|
||||||
SingleCheckBoxFieldWidget
|
"moderator_notification_enabled"
|
||||||
self.fields['show_commenter_image'].widgetFactory = \
|
].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
SingleCheckBoxFieldWidget
|
self.fields[
|
||||||
self.fields['moderator_notification_enabled'].widgetFactory = \
|
"user_notification_enabled"
|
||||||
SingleCheckBoxFieldWidget
|
].widgetFactory = SingleCheckBoxFieldWidget
|
||||||
self.fields['user_notification_enabled'].widgetFactory = \
|
|
||||||
SingleCheckBoxFieldWidget
|
|
||||||
|
|
||||||
def updateWidgets(self):
|
def updateWidgets(self):
|
||||||
try:
|
try:
|
||||||
@ -73,33 +71,31 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
|||||||
# provide auto-upgrade
|
# provide auto-upgrade
|
||||||
update_registry(self.context)
|
update_registry(self.context)
|
||||||
super(DiscussionSettingsEditForm, self).updateWidgets()
|
super(DiscussionSettingsEditForm, self).updateWidgets()
|
||||||
self.widgets['globally_enabled'].label = _(u'Enable Comments')
|
self.widgets["globally_enabled"].label = _(u"Enable Comments")
|
||||||
self.widgets['anonymous_comments'].label = _(u'Anonymous Comments')
|
self.widgets["anonymous_comments"].label = _(u"Anonymous Comments")
|
||||||
self.widgets['show_commenter_image'].label = _(u'Commenter Image')
|
self.widgets["show_commenter_image"].label = _(u"Commenter Image")
|
||||||
self.widgets['moderator_notification_enabled'].label = _(
|
self.widgets["moderator_notification_enabled"].label = _(
|
||||||
u'Moderator Email Notification',
|
u"Moderator Email Notification",
|
||||||
)
|
)
|
||||||
self.widgets['user_notification_enabled'].label = _(
|
self.widgets["user_notification_enabled"].label = _(
|
||||||
u'User Email Notification',
|
u"User Email Notification",
|
||||||
)
|
)
|
||||||
|
|
||||||
@button.buttonAndHandler(_('Save'), name=None)
|
@button.buttonAndHandler(_("Save"), name=None)
|
||||||
def handleSave(self, action):
|
def handleSave(self, action):
|
||||||
data, errors = self.extractData()
|
data, errors = self.extractData()
|
||||||
if errors:
|
if errors:
|
||||||
self.status = self.formErrorsMessage
|
self.status = self.formErrorsMessage
|
||||||
return
|
return
|
||||||
self.applyChanges(data)
|
self.applyChanges(data)
|
||||||
IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'),
|
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info")
|
||||||
'info')
|
self.context.REQUEST.RESPONSE.redirect("@@discussion-controlpanel")
|
||||||
self.context.REQUEST.RESPONSE.redirect('@@discussion-controlpanel')
|
|
||||||
|
|
||||||
@button.buttonAndHandler(_('Cancel'), name='cancel')
|
@button.buttonAndHandler(_("Cancel"), name="cancel")
|
||||||
def handleCancel(self, action):
|
def handleCancel(self, action):
|
||||||
IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'),
|
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"), "info")
|
||||||
'info')
|
|
||||||
self.request.response.redirect(
|
self.request.response.redirect(
|
||||||
'{0}/{1}'.format(
|
"{0}/{1}".format(
|
||||||
self.context.absolute_url(),
|
self.context.absolute_url(),
|
||||||
self.control_panel_view,
|
self.control_panel_view,
|
||||||
),
|
),
|
||||||
@ -107,10 +103,10 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||||
"""Discussion settings control panel.
|
"""Discussion settings control panel."""
|
||||||
"""
|
|
||||||
form = DiscussionSettingsEditForm
|
form = DiscussionSettingsEditForm
|
||||||
index = ViewPageTemplateFile('controlpanel.pt')
|
index = ViewPageTemplateFile("controlpanel.pt")
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
self.mailhost_warning()
|
self.mailhost_warning()
|
||||||
@ -130,39 +126,40 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
|||||||
"""
|
"""
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
wftool = getToolByName(self.context, 'portal_workflow', None)
|
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
# Globally enabled
|
# Globally enabled
|
||||||
if settings.globally_enabled:
|
if settings.globally_enabled:
|
||||||
output.append('globally_enabled')
|
output.append("globally_enabled")
|
||||||
|
|
||||||
# Comment moderation
|
# Comment moderation
|
||||||
one_state_worklow_disabled = \
|
one_state_worklow_disabled = "comment_one_state_workflow" not in workflow_chain
|
||||||
'comment_one_state_workflow' not in workflow_chain
|
comment_review_workflow_disabled = (
|
||||||
comment_review_workflow_disabled = \
|
"comment_review_workflow" not in workflow_chain
|
||||||
'comment_review_workflow' not in workflow_chain
|
)
|
||||||
if one_state_worklow_disabled and comment_review_workflow_disabled:
|
if one_state_worklow_disabled and comment_review_workflow_disabled:
|
||||||
output.append('moderation_custom')
|
output.append("moderation_custom")
|
||||||
elif settings.moderation_enabled:
|
elif settings.moderation_enabled:
|
||||||
output.append('moderation_enabled')
|
output.append("moderation_enabled")
|
||||||
|
|
||||||
if settings.edit_comment_enabled:
|
if settings.edit_comment_enabled:
|
||||||
output.append('edit_comment_enabled')
|
output.append("edit_comment_enabled")
|
||||||
|
|
||||||
if settings.delete_own_comment_enabled:
|
if settings.delete_own_comment_enabled:
|
||||||
output.append('delete_own_comment_enabled')
|
output.append("delete_own_comment_enabled")
|
||||||
|
|
||||||
# Anonymous comments
|
# Anonymous comments
|
||||||
if settings.anonymous_comments:
|
if settings.anonymous_comments:
|
||||||
output.append('anonymous_comments')
|
output.append("anonymous_comments")
|
||||||
|
|
||||||
# Invalid mail setting
|
# Invalid mail setting
|
||||||
ctrlOverview = getMultiAdapter((self.context, self.request),
|
ctrlOverview = getMultiAdapter(
|
||||||
name='overview-controlpanel')
|
(self.context, self.request), name="overview-controlpanel"
|
||||||
|
)
|
||||||
if ctrlOverview.mailhost_warning():
|
if ctrlOverview.mailhost_warning():
|
||||||
output.append('invalid_mail_setup')
|
output.append("invalid_mail_setup")
|
||||||
|
|
||||||
# Workflow
|
# Workflow
|
||||||
if workflow_chain:
|
if workflow_chain:
|
||||||
@ -170,69 +167,71 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
|||||||
output.append(discussion_workflow)
|
output.append(discussion_workflow)
|
||||||
|
|
||||||
# Merge all settings into one string
|
# Merge all settings into one string
|
||||||
return ' '.join(output)
|
return " ".join(output)
|
||||||
|
|
||||||
def mailhost_warning(self):
|
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
|
# Copied from Products.CMFPlone/controlpanel/browser/overview.py
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
mailhost = mail_settings.smtp_host
|
mailhost = mail_settings.smtp_host
|
||||||
email = mail_settings.email_from_address
|
email = mail_settings.email_from_address
|
||||||
if mailhost and email:
|
if mailhost and email:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
message = _(u'discussion_text_no_mailhost_configured',
|
message = _(
|
||||||
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
|
u"discussion_text_no_mailhost_configured",
|
||||||
IStatusMessage(self.request).addStatusMessage(message, 'warning')
|
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):
|
def custom_comment_workflow_warning(self):
|
||||||
"""Return True if a custom comment workflow is enabled."""
|
"""Return True if a custom comment workflow is enabled."""
|
||||||
wftool = getToolByName(self.context, 'portal_workflow', None)
|
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||||
one_state_workflow_enabled = \
|
one_state_workflow_enabled = "comment_one_state_workflow" in workflow_chain
|
||||||
'comment_one_state_workflow' in workflow_chain
|
comment_review_workflow_enabled = "comment_review_workflow" in workflow_chain
|
||||||
comment_review_workflow_enabled = \
|
|
||||||
'comment_review_workflow' in workflow_chain
|
|
||||||
if one_state_workflow_enabled or comment_review_workflow_enabled:
|
if one_state_workflow_enabled or comment_review_workflow_enabled:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
message = _(u'discussion_text_custom_comment_workflow',
|
message = _(
|
||||||
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
|
u"discussion_text_custom_comment_workflow",
|
||||||
IStatusMessage(self.request).addStatusMessage(message, 'warning')
|
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):
|
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()
|
portal = getSite()
|
||||||
wftool = getToolByName(portal, 'portal_workflow', None)
|
wftool = getToolByName(portal, "portal_workflow", None)
|
||||||
|
|
||||||
if IRecordModifiedEvent.providedBy(event):
|
if IRecordModifiedEvent.providedBy(event):
|
||||||
# Discussion control panel setting changed
|
# Discussion control panel setting changed
|
||||||
if event.record.fieldName == 'moderation_enabled':
|
if event.record.fieldName == "moderation_enabled":
|
||||||
# Moderation enabled has changed
|
# Moderation enabled has changed
|
||||||
if event.record.value is True:
|
if event.record.value is True:
|
||||||
# Enable moderation workflow
|
# Enable moderation workflow
|
||||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
wftool.setChainForPortalTypes(
|
||||||
'comment_review_workflow')
|
("Discussion Item",), "comment_review_workflow"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Disable moderation workflow
|
# Disable moderation workflow
|
||||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
wftool.setChainForPortalTypes(
|
||||||
'comment_one_state_workflow')
|
("Discussion Item",), "comment_one_state_workflow"
|
||||||
|
)
|
||||||
|
|
||||||
if IConfigurationChangedEvent.providedBy(event):
|
if IConfigurationChangedEvent.providedBy(event):
|
||||||
# Types control panel setting changed
|
# Types control panel setting changed
|
||||||
if 'workflow' in event.data:
|
if "workflow" in event.data:
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||||
if workflow_chain:
|
if workflow_chain:
|
||||||
workflow = workflow_chain[0]
|
workflow = workflow_chain[0]
|
||||||
if workflow == 'comment_one_state_workflow':
|
if workflow == "comment_one_state_workflow":
|
||||||
settings.moderation_enabled = False
|
settings.moderation_enabled = False
|
||||||
elif workflow == 'comment_review_workflow':
|
elif workflow == "comment_review_workflow":
|
||||||
settings.moderation_enabled = True
|
settings.moderation_enabled = True
|
||||||
else:
|
else:
|
||||||
# Custom workflow
|
# Custom workflow
|
||||||
|
@ -14,6 +14,7 @@ from zope.component import queryUtility
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from plone.dexterity.interfaces import IDexterityContent
|
from plone.dexterity.interfaces import IDexterityContent
|
||||||
|
|
||||||
DEXTERITY_INSTALLED = True
|
DEXTERITY_INSTALLED = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DEXTERITY_INSTALLED = False
|
DEXTERITY_INSTALLED = False
|
||||||
@ -26,15 +27,14 @@ def traverse_parents(context):
|
|||||||
if not IPloneSiteRoot.providedBy(obj):
|
if not IPloneSiteRoot.providedBy(obj):
|
||||||
obj_is_folderish = IFolderish.providedBy(obj)
|
obj_is_folderish = IFolderish.providedBy(obj)
|
||||||
obj_is_stuctural = not INonStructuralFolder.providedBy(obj)
|
obj_is_stuctural = not INonStructuralFolder.providedBy(obj)
|
||||||
if (obj_is_folderish and obj_is_stuctural):
|
if obj_is_folderish and obj_is_stuctural:
|
||||||
flag = getattr(obj, 'allow_discussion', None)
|
flag = getattr(obj, "allow_discussion", None)
|
||||||
if flag is not None:
|
if flag is not None:
|
||||||
return flag
|
return flag
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ConversationView(object):
|
class ConversationView(object):
|
||||||
|
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context):
|
if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context):
|
||||||
return self._enabled_for_dexterity_types()
|
return self._enabled_for_dexterity_types()
|
||||||
@ -42,7 +42,7 @@ class ConversationView(object):
|
|||||||
return self._enabled_for_archetypes()
|
return self._enabled_for_archetypes()
|
||||||
|
|
||||||
def _enabled_for_archetypes(self):
|
def _enabled_for_archetypes(self):
|
||||||
""" Returns True if discussion is enabled for this conversation.
|
"""Returns True if discussion is enabled for this conversation.
|
||||||
|
|
||||||
This method checks five different settings in order to figure out if
|
This method checks five different settings in order to figure out if
|
||||||
discussion is enabled on a specific content object:
|
discussion is enabled on a specific content object:
|
||||||
@ -82,7 +82,7 @@ class ConversationView(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# If discussion is disabled for the object, bail out
|
# 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:
|
if obj_flag is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -91,16 +91,16 @@ class ConversationView(object):
|
|||||||
folder_allow_discussion = traverse_parents(context)
|
folder_allow_discussion = traverse_parents(context)
|
||||||
|
|
||||||
if folder_allow_discussion:
|
if folder_allow_discussion:
|
||||||
if not getattr(self, 'allow_discussion', None):
|
if not getattr(self, "allow_discussion", None):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if obj_flag:
|
if obj_flag:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check if discussion is allowed on the content type
|
# 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)
|
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,
|
# If discussion is not allowed on the content type,
|
||||||
# check if 'allow discussion' is overridden on the content object.
|
# check if 'allow discussion' is overridden on the content object.
|
||||||
if not obj_flag:
|
if not obj_flag:
|
||||||
@ -109,7 +109,7 @@ class ConversationView(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _enabled_for_dexterity_types(self):
|
def _enabled_for_dexterity_types(self):
|
||||||
""" Returns True if discussion is enabled for this conversation.
|
"""Returns True if discussion is enabled for this conversation.
|
||||||
|
|
||||||
This method checks five different settings in order to figure out if
|
This method checks five different settings in order to figure out if
|
||||||
discussion is enable on a specific content object:
|
discussion is enable on a specific content object:
|
||||||
@ -134,11 +134,11 @@ class ConversationView(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if discussion is allowed on the content object
|
# 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:
|
if context.allow_discussion is not None:
|
||||||
return context.allow_discussion
|
return context.allow_discussion
|
||||||
|
|
||||||
# Check if discussion is allowed on the content type
|
# 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)
|
document_fti = getattr(portal_types, context.portal_type)
|
||||||
return document_fti.getProperty('allow_discussion')
|
return document_fti.getProperty("allow_discussion")
|
||||||
|
@ -18,21 +18,20 @@ from zope.event import notify
|
|||||||
|
|
||||||
# Translations for generated values in buttons
|
# Translations for generated values in buttons
|
||||||
# States
|
# States
|
||||||
_('comment_pending', default='pending')
|
_("comment_pending", default="pending")
|
||||||
# _('comment_approved', default='published')
|
# _('comment_approved', default='published')
|
||||||
_('comment_published', default='published')
|
_("comment_published", default="published")
|
||||||
_('comment_rejected', default='rejected')
|
_("comment_rejected", default="rejected")
|
||||||
_('comment_spam', default='marked as spam')
|
_("comment_spam", default="marked as spam")
|
||||||
# Transitions
|
# Transitions
|
||||||
_('Recall')
|
_("Recall")
|
||||||
_('Approve')
|
_("Approve")
|
||||||
_('Reject')
|
_("Reject")
|
||||||
_('Spam')
|
_("Spam")
|
||||||
PMF = _
|
PMF = _
|
||||||
|
|
||||||
|
|
||||||
class TranslationHelper(BrowserView):
|
class TranslationHelper(BrowserView):
|
||||||
|
|
||||||
def translate(self, text=""):
|
def translate(self, text=""):
|
||||||
return _(text)
|
return _(text)
|
||||||
|
|
||||||
@ -44,22 +43,21 @@ class TranslationHelper(BrowserView):
|
|||||||
class View(BrowserView):
|
class View(BrowserView):
|
||||||
"""Show comment moderation view."""
|
"""Show comment moderation view."""
|
||||||
|
|
||||||
template = ViewPageTemplateFile('moderation.pt')
|
template = ViewPageTemplateFile("moderation.pt")
|
||||||
try:
|
try:
|
||||||
template.id = '@@moderate-comments'
|
template.id = "@@moderate-comments"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# id is not writeable in Zope 2.12
|
# id is not writeable in Zope 2.12
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
super(View, self).__init__(context, request)
|
super(View, self).__init__(context, request)
|
||||||
self.workflowTool = getToolByName(self.context, 'portal_workflow')
|
self.workflowTool = getToolByName(self.context, "portal_workflow")
|
||||||
self.transitions = []
|
self.transitions = []
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
self.request.set('disable_border', True)
|
self.request.set("disable_border", True)
|
||||||
self.request.set('review_state',
|
self.request.set("review_state", self.request.get("review_state", "pending"))
|
||||||
self.request.get('review_state', 'pending'))
|
|
||||||
return self.template()
|
return self.template()
|
||||||
|
|
||||||
def comments(self):
|
def comments(self):
|
||||||
@ -67,15 +65,19 @@ class View(BrowserView):
|
|||||||
|
|
||||||
review_state is string or list of strings.
|
review_state is string or list of strings.
|
||||||
"""
|
"""
|
||||||
catalog = getToolByName(self.context, 'portal_catalog')
|
catalog = getToolByName(self.context, "portal_catalog")
|
||||||
if self.request.review_state == 'all':
|
if self.request.review_state == "all":
|
||||||
return catalog(object_provides=IComment.__identifier__,
|
return catalog(
|
||||||
sort_on='created',
|
object_provides=IComment.__identifier__,
|
||||||
sort_order='reverse')
|
sort_on="created",
|
||||||
return catalog(object_provides=IComment.__identifier__,
|
sort_order="reverse",
|
||||||
|
)
|
||||||
|
return catalog(
|
||||||
|
object_provides=IComment.__identifier__,
|
||||||
review_state=self.request.review_state,
|
review_state=self.request.review_state,
|
||||||
sort_on='created',
|
sort_on="created",
|
||||||
sort_order='reverse')
|
sort_order="reverse",
|
||||||
|
)
|
||||||
|
|
||||||
def moderation_enabled(self):
|
def moderation_enabled(self):
|
||||||
"""Return true if a review workflow is enabled on 'Discussion Item'
|
"""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'
|
A 'review workflow' is characterized by implementing a 'pending'
|
||||||
workflow state.
|
workflow state.
|
||||||
"""
|
"""
|
||||||
workflows = self.workflowTool.getChainForPortalType(
|
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
|
||||||
'Discussion Item')
|
|
||||||
if workflows:
|
if workflows:
|
||||||
comment_workflow = self.workflowTool[workflows[0]]
|
comment_workflow = self.workflowTool[workflows[0]]
|
||||||
if 'pending' in comment_workflow.states:
|
if "pending" in comment_workflow.states:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -100,11 +101,10 @@ class View(BrowserView):
|
|||||||
A 'review multipe state workflow' is characterized by implementing
|
A 'review multipe state workflow' is characterized by implementing
|
||||||
a 'rejected' workflow state and a 'spam' workflow state.
|
a 'rejected' workflow state and a 'spam' workflow state.
|
||||||
"""
|
"""
|
||||||
workflows = self.workflowTool.getChainForPortalType(
|
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
|
||||||
'Discussion Item')
|
|
||||||
if workflows:
|
if workflows:
|
||||||
comment_workflow = self.workflowTool[workflows[0]]
|
comment_workflow = self.workflowTool[workflows[0]]
|
||||||
if 'spam' in comment_workflow.states:
|
if "spam" in comment_workflow.states:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -125,27 +125,26 @@ class View(BrowserView):
|
|||||||
"""
|
"""
|
||||||
if obj:
|
if obj:
|
||||||
transitions = [
|
transitions = [
|
||||||
a for a in self.workflowTool.listActionInfos(object=obj)
|
a
|
||||||
if a['category'] == 'workflow' and a['allowed']
|
for a in self.workflowTool.listActionInfos(object=obj)
|
||||||
|
if a["category"] == "workflow" and a["allowed"]
|
||||||
]
|
]
|
||||||
return transitions
|
return transitions
|
||||||
|
|
||||||
|
|
||||||
class ModerateCommentsEnabled(BrowserView):
|
class ModerateCommentsEnabled(BrowserView):
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||||
content type. A 'review workflow' is characterized by implementing
|
content type. A 'review workflow' is characterized by implementing
|
||||||
a 'pending' workflow state.
|
a 'pending' workflow state.
|
||||||
"""
|
"""
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
workflowTool = getToolByName(context, 'portal_workflow', None)
|
workflowTool = getToolByName(context, "portal_workflow", None)
|
||||||
comment_workflow = workflowTool.getChainForPortalType(
|
comment_workflow = workflowTool.getChainForPortalType("Discussion Item")
|
||||||
'Discussion Item')
|
|
||||||
if comment_workflow:
|
if comment_workflow:
|
||||||
comment_workflow = comment_workflow[0]
|
comment_workflow = comment_workflow[0]
|
||||||
comment_workflow = workflowTool[comment_workflow]
|
comment_workflow = workflowTool[comment_workflow]
|
||||||
if 'pending' in comment_workflow.states:
|
if "pending" in comment_workflow.states:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -184,13 +183,15 @@ class DeleteComment(BrowserView):
|
|||||||
content_object.reindexObject()
|
content_object.reindexObject()
|
||||||
notify(CommentDeletedEvent(self.context, comment))
|
notify(CommentDeletedEvent(self.context, comment))
|
||||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||||
_('Comment deleted.'),
|
_("Comment deleted."), type="info"
|
||||||
type='info')
|
)
|
||||||
came_from = self.context.REQUEST.HTTP_REFERER
|
came_from = self.context.REQUEST.HTTP_REFERER
|
||||||
# if the referrer already has a came_from in it, don't redirect back
|
# 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
|
if (
|
||||||
not getToolByName(
|
len(came_from) == 0
|
||||||
content_object, 'portal_url').isURLInPortal(came_from)):
|
or "came_from=" in came_from
|
||||||
|
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
|
||||||
|
):
|
||||||
came_from = content_object.absolute_url()
|
came_from = content_object.absolute_url()
|
||||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||||
|
|
||||||
@ -198,8 +199,7 @@ class DeleteComment(BrowserView):
|
|||||||
"""Returns true if current user has the 'Delete comments'
|
"""Returns true if current user has the 'Delete comments'
|
||||||
permission.
|
permission.
|
||||||
"""
|
"""
|
||||||
return getSecurityManager().checkPermission('Delete comments',
|
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
|
||||||
aq_inner(reply))
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteOwnComment(DeleteComment):
|
class DeleteOwnComment(DeleteComment):
|
||||||
@ -213,21 +213,18 @@ class DeleteOwnComment(DeleteComment):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def could_delete(self, comment=None):
|
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()
|
sm = getSecurityManager()
|
||||||
comment = comment or aq_inner(self.context)
|
comment = comment or aq_inner(self.context)
|
||||||
userid = sm.getUser().getId()
|
userid = sm.getUser().getId()
|
||||||
return (
|
return sm.checkPermission(
|
||||||
sm.checkPermission('Delete own comments', comment) and
|
"Delete own comments", comment
|
||||||
'Owner' in comment.get_local_roles_for_userid(userid)
|
) and "Owner" in comment.get_local_roles_for_userid(userid)
|
||||||
)
|
|
||||||
|
|
||||||
def can_delete(self, comment=None):
|
def can_delete(self, comment=None):
|
||||||
comment = comment or self.context
|
comment = comment or self.context
|
||||||
return (
|
return len(IReplies(aq_inner(comment))) == 0 and self.could_delete(
|
||||||
len(IReplies(aq_inner(comment))) == 0 and
|
comment=comment
|
||||||
self.could_delete(comment=comment)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
@ -262,33 +259,37 @@ class CommentTransition(BrowserView):
|
|||||||
"""Call CommentTransition."""
|
"""Call CommentTransition."""
|
||||||
comment = aq_inner(self.context)
|
comment = aq_inner(self.context)
|
||||||
content_object = aq_parent(aq_parent(comment))
|
content_object = aq_parent(aq_parent(comment))
|
||||||
workflow_action = self.request.form.get('workflow_action', 'publish')
|
workflow_action = self.request.form.get("workflow_action", "publish")
|
||||||
workflowTool = getToolByName(self.context, 'portal_workflow')
|
workflowTool = getToolByName(self.context, "portal_workflow")
|
||||||
workflowTool.doActionFor(comment, workflow_action)
|
workflowTool.doActionFor(comment, workflow_action)
|
||||||
comment.reindexObject()
|
comment.reindexObject()
|
||||||
content_object.reindexObject(idxs=['total_comments'])
|
content_object.reindexObject(idxs=["total_comments"])
|
||||||
notify(CommentPublishedEvent(self.context, comment))
|
notify(CommentPublishedEvent(self.context, comment))
|
||||||
# for complexer workflows:
|
# for complexer workflows:
|
||||||
notify(CommentTransitionEvent(self.context, comment))
|
notify(CommentTransitionEvent(self.context, comment))
|
||||||
comment_state_translated = ''
|
comment_state_translated = ""
|
||||||
if workflowTool.getWorkflowsFor(comment):
|
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")
|
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 = _(
|
msgid = _(
|
||||||
"comment_transmitted",
|
"comment_transmitted",
|
||||||
default='Comment ${comment_state_translated}.',
|
default="Comment ${comment_state_translated}.",
|
||||||
mapping={"comment_state_translated": comment_state_translated})
|
mapping={"comment_state_translated": comment_state_translated},
|
||||||
|
)
|
||||||
translated = self.context.translate(msgid)
|
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
|
came_from = self.context.REQUEST.HTTP_REFERER
|
||||||
# if the referrer already has a came_from in it, don't redirect back
|
# if the referrer already has a came_from in it, don't redirect back
|
||||||
if (len(came_from) == 0
|
if (
|
||||||
or 'came_from=' in came_from
|
len(came_from) == 0
|
||||||
or not getToolByName(
|
or "came_from=" in came_from
|
||||||
content_object, 'portal_url').isURLInPortal(came_from)):
|
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
|
||||||
|
):
|
||||||
came_from = content_object.absolute_url()
|
came_from = content_object.absolute_url()
|
||||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||||
|
|
||||||
@ -318,18 +319,18 @@ class BulkActionsView(BrowserView):
|
|||||||
|
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
super(BulkActionsView, self).__init__(context, request)
|
super(BulkActionsView, self).__init__(context, request)
|
||||||
self.workflowTool = getToolByName(context, 'portal_workflow')
|
self.workflowTool = getToolByName(context, "portal_workflow")
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""Call BulkActionsView."""
|
"""Call BulkActionsView."""
|
||||||
if 'form.select.BulkAction' in self.request:
|
if "form.select.BulkAction" in self.request:
|
||||||
bulkaction = self.request.get('form.select.BulkAction')
|
bulkaction = self.request.get("form.select.BulkAction")
|
||||||
self.paths = self.request.get('paths')
|
self.paths = self.request.get("paths")
|
||||||
if self.paths:
|
if self.paths:
|
||||||
if bulkaction == '-1':
|
if bulkaction == "-1":
|
||||||
# no bulk action was selected
|
# no bulk action was selected
|
||||||
pass
|
pass
|
||||||
elif bulkaction == 'delete':
|
elif bulkaction == "delete":
|
||||||
self.delete()
|
self.delete()
|
||||||
else:
|
else:
|
||||||
self.transmit(bulkaction)
|
self.transmit(bulkaction)
|
||||||
@ -346,13 +347,14 @@ class BulkActionsView(BrowserView):
|
|||||||
comment = context.restrictedTraverse(path)
|
comment = context.restrictedTraverse(path)
|
||||||
content_object = aq_parent(aq_parent(comment))
|
content_object = aq_parent(aq_parent(comment))
|
||||||
allowed_transitions = [
|
allowed_transitions = [
|
||||||
transition['id'] for transition in self.workflowTool.listActionInfos(object=comment)
|
transition["id"]
|
||||||
if transition['category'] == 'workflow' and transition['allowed']
|
for transition in self.workflowTool.listActionInfos(object=comment)
|
||||||
|
if transition["category"] == "workflow" and transition["allowed"]
|
||||||
]
|
]
|
||||||
if action in allowed_transitions:
|
if action in allowed_transitions:
|
||||||
self.workflowTool.doActionFor(comment, action)
|
self.workflowTool.doActionFor(comment, action)
|
||||||
comment.reindexObject()
|
comment.reindexObject()
|
||||||
content_object.reindexObject(idxs=['total_comments'])
|
content_object.reindexObject(idxs=["total_comments"])
|
||||||
notify(CommentPublishedEvent(content_object, comment))
|
notify(CommentPublishedEvent(content_object, comment))
|
||||||
# for complexer workflows:
|
# for complexer workflows:
|
||||||
notify(CommentTransitionEvent(self.context, comment))
|
notify(CommentTransitionEvent(self.context, comment))
|
||||||
@ -370,5 +372,5 @@ class BulkActionsView(BrowserView):
|
|||||||
conversation = aq_parent(comment)
|
conversation = aq_parent(comment)
|
||||||
content_object = aq_parent(conversation)
|
content_object = aq_parent(conversation)
|
||||||
del conversation[comment.id]
|
del conversation[comment.id]
|
||||||
content_object.reindexObject(idxs=['total_comments'])
|
content_object.reindexObject(idxs=["total_comments"])
|
||||||
notify(CommentDeletedEvent(content_object, comment))
|
notify(CommentDeletedEvent(content_object, comment))
|
||||||
|
@ -29,8 +29,8 @@ class ConversationNamespace(object):
|
|||||||
|
|
||||||
def traverse(self, name, ignore):
|
def traverse(self, name, ignore):
|
||||||
|
|
||||||
if name == 'default':
|
if name == "default":
|
||||||
name = u''
|
name = u""
|
||||||
|
|
||||||
conversation = queryAdapter(self.context, IConversation, name=name)
|
conversation = queryAdapter(self.context, IConversation, name=name)
|
||||||
if conversation is None:
|
if conversation is None:
|
||||||
|
@ -44,11 +44,12 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
|||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
|
|
||||||
if settings.captcha in ('captcha', 'recaptcha', 'norobots'):
|
if settings.captcha in ("captcha", "recaptcha", "norobots"):
|
||||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
captcha = getMultiAdapter(
|
||||||
name=settings.captcha)
|
(aq_inner(self.context), self.request), name=settings.captcha
|
||||||
|
)
|
||||||
if not captcha.verify(input=value):
|
if not captcha.verify(input=value):
|
||||||
if settings.captcha == 'norobots':
|
if settings.captcha == "norobots":
|
||||||
raise WrongNorobotsAnswer
|
raise WrongNorobotsAnswer
|
||||||
else:
|
else:
|
||||||
raise WrongCaptchaCode
|
raise WrongCaptchaCode
|
||||||
@ -57,5 +58,4 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
|||||||
|
|
||||||
|
|
||||||
# Register Captcha validator for the Captcha field in the ICaptcha Form
|
# Register Captcha validator for the Captcha field in the ICaptcha Form
|
||||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
validator.WidgetValidatorDiscriminators(CaptchaValidator, field=ICaptcha["captcha"])
|
||||||
field=ICaptcha['captcha'])
|
|
||||||
|
@ -25,7 +25,7 @@ MAX_DESCRIPTION = 25
|
|||||||
def total_comments(object):
|
def total_comments(object):
|
||||||
# Total number of comments on a conversation
|
# Total number of comments on a conversation
|
||||||
# Indexers won't work on old discussion items
|
# Indexers won't work on old discussion items
|
||||||
if object.meta_type != 'Discussion Item':
|
if object.meta_type != "Discussion Item":
|
||||||
try:
|
try:
|
||||||
conversation = IConversation(object)
|
conversation = IConversation(object)
|
||||||
return conversation.total_comments()
|
return conversation.total_comments()
|
||||||
@ -39,7 +39,7 @@ def total_comments(object):
|
|||||||
def last_comment_date(object):
|
def last_comment_date(object):
|
||||||
# Date of the latest comment on a conversation
|
# Date of the latest comment on a conversation
|
||||||
# Indexers won't work on old discussion items
|
# Indexers won't work on old discussion items
|
||||||
if object.meta_type != 'Discussion Item':
|
if object.meta_type != "Discussion Item":
|
||||||
try:
|
try:
|
||||||
conversation = IConversation(object)
|
conversation = IConversation(object)
|
||||||
return conversation.last_comment_date
|
return conversation.last_comment_date
|
||||||
@ -53,7 +53,7 @@ def last_comment_date(object):
|
|||||||
def commentators(object):
|
def commentators(object):
|
||||||
# List of commentators on a conversation
|
# List of commentators on a conversation
|
||||||
# Indexers won't work on old discussion items
|
# Indexers won't work on old discussion items
|
||||||
if object.meta_type != 'Discussion Item':
|
if object.meta_type != "Discussion Item":
|
||||||
try:
|
try:
|
||||||
conversation = IConversation(object)
|
conversation = IConversation(object)
|
||||||
return conversation.public_commentators
|
return conversation.public_commentators
|
||||||
@ -62,6 +62,7 @@ def commentators(object):
|
|||||||
# implemented an adapter for it
|
# implemented an adapter for it
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Comment Indexers
|
# Comment Indexers
|
||||||
|
|
||||||
|
|
||||||
@ -76,24 +77,24 @@ def creator(object):
|
|||||||
return
|
return
|
||||||
value = safe_unicode(object.creator)
|
value = safe_unicode(object.creator)
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
return value.encode('utf8')
|
return value.encode("utf8")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def description(object):
|
def description(object):
|
||||||
# Return the first 25 words of the comment text and append ' [...]'
|
# Return the first 25 words of the comment text and append ' [...]'
|
||||||
text = ' '.join(
|
text = " ".join(
|
||||||
object.getText(targetMimetype='text/plain').split()[:MAX_DESCRIPTION],
|
object.getText(targetMimetype="text/plain").split()[:MAX_DESCRIPTION],
|
||||||
)
|
)
|
||||||
if len(object.getText().split()) > 25:
|
if len(object.getText().split()) > 25:
|
||||||
text += ' [...]'
|
text += " [...]"
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
def searchable_text(object):
|
def searchable_text(object):
|
||||||
return object.getText(targetMimetype='text/plain')
|
return object.getText(targetMimetype="text/plain")
|
||||||
|
|
||||||
|
|
||||||
@indexer(IComment)
|
@indexer(IComment)
|
||||||
@ -113,7 +114,7 @@ def effective(object):
|
|||||||
object.creation_date.hour,
|
object.creation_date.hour,
|
||||||
object.creation_date.minute,
|
object.creation_date.minute,
|
||||||
object.creation_date.second,
|
object.creation_date.second,
|
||||||
'GMT',
|
"GMT",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -127,7 +128,7 @@ def created(object):
|
|||||||
object.creation_date.hour,
|
object.creation_date.hour,
|
||||||
object.creation_date.minute,
|
object.creation_date.minute,
|
||||||
object.creation_date.second,
|
object.creation_date.second,
|
||||||
'GMT',
|
"GMT",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -141,7 +142,7 @@ def modified(object):
|
|||||||
object.modification_date.hour,
|
object.modification_date.hour,
|
||||||
object.modification_date.minute,
|
object.modification_date.minute,
|
||||||
object.modification_date.second,
|
object.modification_date.second,
|
||||||
'GMT',
|
"GMT",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,57 +44,66 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
COMMENT_TITLE = _(
|
COMMENT_TITLE = _(
|
||||||
u'comment_title',
|
u"comment_title",
|
||||||
default=u'${author_name} on ${content}',
|
default=u"${author_name} on ${content}",
|
||||||
)
|
)
|
||||||
|
|
||||||
MAIL_NOTIFICATION_MESSAGE = _(
|
MAIL_NOTIFICATION_MESSAGE = _(
|
||||||
u'mail_notification_message',
|
u"mail_notification_message",
|
||||||
default=u'A comment on "${title}" '
|
default=u'A comment on "${title}" '
|
||||||
u'has been posted here: ${link}\n\n'
|
u"has been posted here: ${link}\n\n"
|
||||||
u'---\n'
|
u"---\n"
|
||||||
u'${text}\n'
|
u"${text}\n"
|
||||||
u'---\n',
|
u"---\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
||||||
u'mail_notification_message_moderator2',
|
u"mail_notification_message_moderator2",
|
||||||
default=u'A comment on "${title}" '
|
default=u'A comment on "${title}" '
|
||||||
u'has been posted by ${commentator}\n'
|
u"has been posted by ${commentator}\n"
|
||||||
u'here: ${link}\n\n'
|
u"here: ${link}\n\n"
|
||||||
u'---\n\n'
|
u"---\n\n"
|
||||||
u'${text}\n\n'
|
u"${text}\n\n"
|
||||||
u'---\n\n'
|
u"---\n\n"
|
||||||
u'Log in to moderate.\n\n',
|
u"Log in to moderate.\n\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger('plone.app.discussion')
|
logger = logging.getLogger("plone.app.discussion")
|
||||||
|
|
||||||
|
|
||||||
@implementer(IComment)
|
@implementer(IComment)
|
||||||
class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
class Comment(
|
||||||
RoleManager, Owned, Implicit, Persistent):
|
CatalogAware,
|
||||||
|
WorkflowAware,
|
||||||
|
DynamicType,
|
||||||
|
Traversable,
|
||||||
|
RoleManager,
|
||||||
|
Owned,
|
||||||
|
Implicit,
|
||||||
|
Persistent,
|
||||||
|
):
|
||||||
"""A comment.
|
"""A comment.
|
||||||
|
|
||||||
This object attempts to be as lightweight as possible. We implement a
|
This object attempts to be as lightweight as possible. We implement a
|
||||||
number of standard methods instead of subclassing, to have total control
|
number of standard methods instead of subclassing, to have total control
|
||||||
over what goes into the object.
|
over what goes into the object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
security = ClassSecurityInfo()
|
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
|
# This needs to be kept in sync with types/Discussion_Item.xml title
|
||||||
fti_title = 'Comment'
|
fti_title = "Comment"
|
||||||
|
|
||||||
__parent__ = None
|
__parent__ = None
|
||||||
|
|
||||||
comment_id = None # long
|
comment_id = None # long
|
||||||
in_reply_to = None # long
|
in_reply_to = None # long
|
||||||
|
|
||||||
title = u''
|
title = u""
|
||||||
|
|
||||||
mime_type = None
|
mime_type = None
|
||||||
text = u''
|
text = u""
|
||||||
|
|
||||||
creator = None
|
creator = None
|
||||||
creation_date = None
|
creation_date = None
|
||||||
@ -113,14 +122,17 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.creation_date = self.modification_date = datetime.utcnow()
|
self.creation_date = self.modification_date = datetime.utcnow()
|
||||||
self.mime_type = 'text/plain'
|
self.mime_type = "text/plain"
|
||||||
|
|
||||||
user = getSecurityManager().getUser()
|
user = getSecurityManager().getUser()
|
||||||
if user and user.getId():
|
if user and user.getId():
|
||||||
aclpath = [x for x in user.getPhysicalPath() if x]
|
aclpath = [x for x in user.getPhysicalPath() if x]
|
||||||
self._owner = (aclpath, user.getId(),)
|
self._owner = (
|
||||||
|
aclpath,
|
||||||
|
user.getId(),
|
||||||
|
)
|
||||||
self.__ac_local_roles__ = {
|
self.__ac_local_roles__ = {
|
||||||
user.getId(): ['Owner'],
|
user.getId(): ["Owner"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -137,32 +149,32 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
|
|
||||||
def getText(self, targetMimetype=None):
|
def getText(self, targetMimetype=None):
|
||||||
"""The body text of a comment."""
|
"""The body text of a comment."""
|
||||||
transforms = getToolByName(self, 'portal_transforms')
|
transforms = getToolByName(self, "portal_transforms")
|
||||||
|
|
||||||
if targetMimetype is None:
|
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:
|
if sourceMimetype is None:
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
sourceMimetype = settings.text_transform
|
sourceMimetype = settings.text_transform
|
||||||
text = self.text
|
text = self.text
|
||||||
if text is None:
|
if text is None:
|
||||||
return ''
|
return ""
|
||||||
if six.PY2 and isinstance(text, six.text_type):
|
if six.PY2 and isinstance(text, six.text_type):
|
||||||
text = text.encode('utf8')
|
text = text.encode("utf8")
|
||||||
transform = transforms.convertTo(
|
transform = transforms.convertTo(
|
||||||
targetMimetype,
|
targetMimetype, text, context=self, mimetype=sourceMimetype
|
||||||
text,
|
)
|
||||||
context=self,
|
|
||||||
mimetype=sourceMimetype)
|
|
||||||
if transform:
|
if transform:
|
||||||
return transform.getData()
|
return transform.getData()
|
||||||
else:
|
else:
|
||||||
logger = logging.getLogger('plone.app.discussion')
|
logger = logging.getLogger("plone.app.discussion")
|
||||||
msg = u'Transform "{0}" => "{1}" not available. Failed to ' \
|
msg = (
|
||||||
|
u'Transform "{0}" => "{1}" not available. Failed to '
|
||||||
u'transform comment "{2}".'
|
u'transform comment "{2}".'
|
||||||
|
)
|
||||||
logger.error(
|
logger.error(
|
||||||
msg.format(
|
msg.format(
|
||||||
sourceMimetype,
|
sourceMimetype,
|
||||||
@ -182,8 +194,8 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
author_name = translate(
|
author_name = translate(
|
||||||
Message(
|
Message(
|
||||||
_(
|
_(
|
||||||
u'label_anonymous',
|
u"label_anonymous",
|
||||||
default=u'Anonymous',
|
default=u"Anonymous",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -194,9 +206,14 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
# conversation, the parent of the conversation is the content object).
|
# conversation, the parent of the conversation is the content object).
|
||||||
content = aq_base(self.__parent__.__parent__)
|
content = aq_base(self.__parent__.__parent__)
|
||||||
title = translate(
|
title = translate(
|
||||||
Message(COMMENT_TITLE,
|
Message(
|
||||||
mapping={'author_name': safe_unicode(author_name),
|
COMMENT_TITLE,
|
||||||
'content': safe_unicode(content.Title())}))
|
mapping={
|
||||||
|
"author_name": safe_unicode(author_name),
|
||||||
|
"content": safe_unicode(content.Title()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
return title
|
return title
|
||||||
|
|
||||||
def Creator(self):
|
def Creator(self):
|
||||||
@ -224,20 +241,18 @@ CommentFactory = Factory(Comment)
|
|||||||
|
|
||||||
|
|
||||||
def notify_workflow(obj, event):
|
def notify_workflow(obj, event):
|
||||||
"""Tell the workflow tool when a comment is added
|
"""Tell the workflow tool when a comment is added"""
|
||||||
"""
|
tool = getToolByName(obj, "portal_workflow", None)
|
||||||
tool = getToolByName(obj, 'portal_workflow', None)
|
|
||||||
if tool is not None:
|
if tool is not None:
|
||||||
tool.notifyCreated(obj)
|
tool.notifyCreated(obj)
|
||||||
|
|
||||||
|
|
||||||
def notify_content_object(obj, event):
|
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 = aq_parent(aq_parent(obj))
|
||||||
content_obj.reindexObject(idxs=('total_comments',
|
content_obj.reindexObject(
|
||||||
'last_comment_date',
|
idxs=("total_comments", "last_comment_date", "commentators")
|
||||||
'commentators'))
|
)
|
||||||
|
|
||||||
|
|
||||||
def notify_content_object_deleted(obj, event):
|
def notify_content_object_deleted(obj, event):
|
||||||
@ -251,40 +266,40 @@ def notify_content_object_deleted(obj, event):
|
|||||||
|
|
||||||
|
|
||||||
def notify_comment_added(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)
|
conversation = aq_parent(obj)
|
||||||
context = aq_parent(conversation)
|
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(ReplyAddedEvent(context, obj))
|
||||||
return notify(CommentAddedEvent(context, obj))
|
return notify(CommentAddedEvent(context, obj))
|
||||||
|
|
||||||
|
|
||||||
def notify_comment_modified(obj, event):
|
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)
|
conversation = aq_parent(obj)
|
||||||
context = aq_parent(conversation)
|
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(ReplyModifiedEvent(context, obj))
|
||||||
return notify(CommentModifiedEvent(context, obj))
|
return notify(CommentModifiedEvent(context, obj))
|
||||||
|
|
||||||
|
|
||||||
def notify_comment_removed(obj, event):
|
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)
|
conversation = aq_parent(obj)
|
||||||
context = aq_parent(conversation)
|
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(ReplyRemovedEvent(context, obj))
|
||||||
return notify(CommentRemovedEvent(context, obj))
|
return notify(CommentRemovedEvent(context, obj))
|
||||||
|
|
||||||
|
|
||||||
def notify_content_object_moved(obj, event):
|
def notify_content_object_moved(obj, event):
|
||||||
"""Update all comments of a content object that has been moved.
|
"""Update all comments of a content object that has been moved."""
|
||||||
"""
|
if (
|
||||||
if event.oldParent is None or event.newParent is None \
|
event.oldParent is None
|
||||||
or event.oldName is None or event.newName is None:
|
or event.newParent is None
|
||||||
|
or event.oldName is None
|
||||||
|
or event.newName is None
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
# This method is also called for sublocations of moved objects. We
|
# 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
|
# 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
|
# obj.getPhysicalPath retruns the new path get the part of the path that
|
||||||
# was moved.
|
# was moved.
|
||||||
moved_path = obj.getPhysicalPath()[
|
moved_path = obj.getPhysicalPath()[len(event.newParent.getPhysicalPath()) + 1 :]
|
||||||
len(event.newParent.getPhysicalPath()) + 1:
|
|
||||||
]
|
|
||||||
|
|
||||||
# Remove comments at the old location from catalog
|
# Remove comments at the old location from catalog
|
||||||
catalog = getToolByName(obj, 'portal_catalog')
|
catalog = getToolByName(obj, "portal_catalog")
|
||||||
old_path = '/'.join(
|
old_path = "/".join(
|
||||||
event.oldParent.getPhysicalPath() +
|
event.oldParent.getPhysicalPath() + (event.oldName,) + moved_path,
|
||||||
(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:
|
for brain in brains:
|
||||||
catalog.uncatalog_object(brain.getPath())
|
catalog.uncatalog_object(brain.getPath())
|
||||||
# Reindex comment at the new location
|
# Reindex comment at the new location
|
||||||
@ -334,9 +347,9 @@ def notify_user(obj, event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get informations that are necessary to send an email
|
# Get informations that are necessary to send an email
|
||||||
mail_host = getToolByName(obj, 'MailHost')
|
mail_host = getToolByName(obj, "MailHost")
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
sender = mail_settings.email_from_address
|
sender = mail_settings.email_from_address
|
||||||
|
|
||||||
# Check if a sender address is available
|
# Check if a sender address is available
|
||||||
@ -360,15 +373,14 @@ def notify_user(obj, event):
|
|||||||
if not emails:
|
if not emails:
|
||||||
return
|
return
|
||||||
|
|
||||||
subject = translate(_(u'A comment has been posted.'),
|
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
|
||||||
context=obj.REQUEST)
|
|
||||||
message = translate(
|
message = translate(
|
||||||
Message(
|
Message(
|
||||||
MAIL_NOTIFICATION_MESSAGE,
|
MAIL_NOTIFICATION_MESSAGE,
|
||||||
mapping={
|
mapping={
|
||||||
'title': safe_unicode(content_object.title),
|
"title": safe_unicode(content_object.title),
|
||||||
'link': content_object.absolute_url() + '/view#' + obj.id,
|
"link": content_object.absolute_url() + "/view#" + obj.id,
|
||||||
'text': obj.text,
|
"text": obj.text,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
context=obj.REQUEST,
|
context=obj.REQUEST,
|
||||||
@ -381,12 +393,11 @@ def notify_user(obj, event):
|
|||||||
email,
|
email,
|
||||||
sender,
|
sender,
|
||||||
subject,
|
subject,
|
||||||
charset='utf-8',
|
charset="utf-8",
|
||||||
)
|
)
|
||||||
except SMTPException:
|
except SMTPException:
|
||||||
logger.error(
|
logger.error(
|
||||||
'SMTP exception while trying to send an ' +
|
"SMTP exception while trying to send an " + "email from %s to %s",
|
||||||
'email from %s to %s',
|
|
||||||
sender,
|
sender,
|
||||||
email,
|
email,
|
||||||
)
|
)
|
||||||
@ -412,9 +423,9 @@ def notify_moderator(obj, event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get informations that are necessary to send an email
|
# Get informations that are necessary to send an email
|
||||||
mail_host = getToolByName(obj, 'MailHost')
|
mail_host = getToolByName(obj, "MailHost")
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
sender = mail_settings.email_from_address
|
sender = mail_settings.email_from_address
|
||||||
|
|
||||||
if settings.moderator_email:
|
if settings.moderator_email:
|
||||||
@ -430,22 +441,23 @@ def notify_moderator(obj, event):
|
|||||||
content_object = aq_parent(conversation)
|
content_object = aq_parent(conversation)
|
||||||
|
|
||||||
# Compose email
|
# 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 = translate(
|
||||||
Message(
|
Message(
|
||||||
MAIL_NOTIFICATION_MESSAGE_MODERATOR,
|
MAIL_NOTIFICATION_MESSAGE_MODERATOR,
|
||||||
mapping={
|
mapping={
|
||||||
'title': safe_unicode(content_object.title),
|
"title": safe_unicode(content_object.title),
|
||||||
'link': content_object.absolute_url() + '/view#' + obj.id,
|
"link": content_object.absolute_url() + "/view#" + obj.id,
|
||||||
'text': obj.text,
|
"text": obj.text,
|
||||||
'commentator': obj.author_email or translate(
|
"commentator": obj.author_email
|
||||||
|
or translate(
|
||||||
Message(
|
Message(
|
||||||
_(
|
_(
|
||||||
u'label_anonymous',
|
u"label_anonymous",
|
||||||
default=u'Anonymous',
|
default=u"Anonymous",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
context=obj.REQUEST,
|
context=obj.REQUEST,
|
||||||
@ -453,12 +465,12 @@ def notify_moderator(obj, event):
|
|||||||
|
|
||||||
# Send email
|
# Send email
|
||||||
try:
|
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:
|
except SMTPException as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
'SMTP exception (%s) while trying to send an ' +
|
"SMTP exception (%s) while trying to send an "
|
||||||
'email notification to the comment moderator ' +
|
+ "email notification to the comment moderator "
|
||||||
'(from %s to %s, message: %s)',
|
+ "(from %s to %s, message: %s)",
|
||||||
e,
|
e,
|
||||||
sender,
|
sender,
|
||||||
mto,
|
mto,
|
||||||
|
@ -7,101 +7,94 @@ from plone.app.discussion import _
|
|||||||
try:
|
try:
|
||||||
from plone.stringinterp.adapters import BaseSubstitution
|
from plone.stringinterp.adapters import BaseSubstitution
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
class BaseSubstitution(object):
|
class BaseSubstitution(object):
|
||||||
""" Fallback class if plone.stringinterp is not available
|
"""Fallback class if plone.stringinterp is not available"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, context, **kwargs):
|
def __init__(self, context, **kwargs):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from plone.app.contentrules.handlers import execute
|
from plone.app.contentrules.handlers import execute
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
def execute(context, event):
|
def execute(context, event):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def execute_comment(event):
|
def execute_comment(event):
|
||||||
""" Execute comment content rules
|
"""Execute comment content rules"""
|
||||||
"""
|
|
||||||
execute(event.object, event)
|
execute(event.object, event)
|
||||||
|
|
||||||
|
|
||||||
class CommentSubstitution(BaseSubstitution):
|
class CommentSubstitution(BaseSubstitution):
|
||||||
""" Comment string substitution
|
"""Comment string substitution"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, context, **kwargs):
|
def __init__(self, context, **kwargs):
|
||||||
super(CommentSubstitution, self).__init__(context, **kwargs)
|
super(CommentSubstitution, self).__init__(context, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event(self):
|
def event(self):
|
||||||
""" event that triggered the content rule
|
"""event that triggered the content rule"""
|
||||||
"""
|
return self.context.REQUEST.get("event")
|
||||||
return self.context.REQUEST.get('event')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def comment(self):
|
def comment(self):
|
||||||
""" Get changed inline comment
|
"""Get changed inline comment"""
|
||||||
"""
|
|
||||||
return self.event.comment
|
return self.event.comment
|
||||||
|
|
||||||
|
|
||||||
class Id(CommentSubstitution):
|
class Id(CommentSubstitution):
|
||||||
""" Comment id string substitution
|
"""Comment id string substitution"""
|
||||||
"""
|
|
||||||
category = _(u'Comments')
|
category = _(u"Comments")
|
||||||
description = _(u'Comment id')
|
description = _(u"Comment id")
|
||||||
|
|
||||||
def safe_call(self):
|
def safe_call(self):
|
||||||
""" Safe call
|
"""Safe call"""
|
||||||
"""
|
return getattr(self.comment, "comment_id", u"")
|
||||||
return getattr(self.comment, 'comment_id', u'')
|
|
||||||
|
|
||||||
|
|
||||||
class Text(CommentSubstitution):
|
class Text(CommentSubstitution):
|
||||||
""" Comment text
|
"""Comment text"""
|
||||||
"""
|
|
||||||
category = _(u'Comments')
|
category = _(u"Comments")
|
||||||
description = _(u'Comment text')
|
description = _(u"Comment text")
|
||||||
|
|
||||||
def safe_call(self):
|
def safe_call(self):
|
||||||
""" Safe call
|
"""Safe call"""
|
||||||
"""
|
return getattr(self.comment, "text", u"")
|
||||||
return getattr(self.comment, 'text', u'')
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorUserName(CommentSubstitution):
|
class AuthorUserName(CommentSubstitution):
|
||||||
""" Comment author user name string substitution
|
"""Comment author user name string substitution"""
|
||||||
"""
|
|
||||||
category = _(u'Comments')
|
category = _(u"Comments")
|
||||||
description = _(u'Comment author user name')
|
description = _(u"Comment author user name")
|
||||||
|
|
||||||
def safe_call(self):
|
def safe_call(self):
|
||||||
""" Safe call
|
"""Safe call"""
|
||||||
"""
|
return getattr(self.comment, "author_username", u"")
|
||||||
return getattr(self.comment, 'author_username', u'')
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorFullName(CommentSubstitution):
|
class AuthorFullName(CommentSubstitution):
|
||||||
""" Comment author full name string substitution
|
"""Comment author full name string substitution"""
|
||||||
"""
|
|
||||||
category = _(u'Comments')
|
category = _(u"Comments")
|
||||||
description = _(u'Comment author full name')
|
description = _(u"Comment author full name")
|
||||||
|
|
||||||
def safe_call(self):
|
def safe_call(self):
|
||||||
""" Safe call
|
"""Safe call"""
|
||||||
"""
|
return getattr(self.comment, "author_name", u"")
|
||||||
return getattr(self.comment, 'author_name', u'')
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorEmail(CommentSubstitution):
|
class AuthorEmail(CommentSubstitution):
|
||||||
""" Comment author email string substitution
|
"""Comment author email string substitution"""
|
||||||
"""
|
|
||||||
category = _(u'Comments')
|
category = _(u"Comments")
|
||||||
description = _(u'Comment author email')
|
description = _(u"Comment author email")
|
||||||
|
|
||||||
def safe_call(self):
|
def safe_call(self):
|
||||||
""" Safe call
|
"""Safe call"""
|
||||||
"""
|
return getattr(self.comment, "author_email", u"")
|
||||||
return getattr(self.comment, 'author_email', u'')
|
|
||||||
|
@ -51,7 +51,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
|
|
||||||
__allow_access_to_unprotected_subobjects__ = True
|
__allow_access_to_unprotected_subobjects__ = True
|
||||||
|
|
||||||
def __init__(self, id='++conversation++default'):
|
def __init__(self, id="++conversation++default"):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
||||||
# username -> count of comments; key is removed when count reaches 0
|
# username -> count of comments; key is removed when count reaches 0
|
||||||
@ -72,12 +72,11 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
|
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
parent = aq_inner(self.__parent__)
|
parent = aq_inner(self.__parent__)
|
||||||
return parent.restrictedTraverse('@@conversation_view').enabled()
|
return parent.restrictedTraverse("@@conversation_view").enabled()
|
||||||
|
|
||||||
def total_comments(self):
|
def total_comments(self):
|
||||||
public_comments = [
|
public_comments = [
|
||||||
x for x in self.values()
|
x for x in self.values() if user_nobody.has_permission("View", x)
|
||||||
if user_nobody.has_permission('View', x)
|
|
||||||
]
|
]
|
||||||
return len(public_comments)
|
return len(public_comments)
|
||||||
|
|
||||||
@ -88,7 +87,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
comment_keys = self._comments.keys()
|
comment_keys = self._comments.keys()
|
||||||
for comment_key in reversed(comment_keys):
|
for comment_key in reversed(comment_keys):
|
||||||
comment = self._comments[comment_key]
|
comment = self._comments[comment_key]
|
||||||
if user_nobody.has_permission('View', comment):
|
if user_nobody.has_permission("View", comment):
|
||||||
return comment.creation_date
|
return comment.creation_date
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
def public_commentators(self):
|
def public_commentators(self):
|
||||||
retval = set()
|
retval = set()
|
||||||
for comment in self._comments.values():
|
for comment in self._comments.values():
|
||||||
if not user_nobody.has_permission('View', comment):
|
if not user_nobody.has_permission("View", comment):
|
||||||
continue
|
continue
|
||||||
retval.add(comment.author_username)
|
retval.add(comment.author_username)
|
||||||
return tuple(retval)
|
return tuple(retval)
|
||||||
@ -109,8 +108,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
return self._comments.keys()
|
return self._comments.keys()
|
||||||
|
|
||||||
def getComments(self, start=0, size=None):
|
def getComments(self, start=0, size=None):
|
||||||
"""Get unthreaded comments
|
"""Get unthreaded comments"""
|
||||||
"""
|
|
||||||
count = 0
|
count = 0
|
||||||
for comment in self._comments.values(min=start):
|
for comment in self._comments.values(min=start):
|
||||||
# Yield the acquisition wrapped comment
|
# Yield the acquisition wrapped comment
|
||||||
@ -121,12 +119,11 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def getThreads(self, start=0, size=None, root=0, depth=None):
|
def getThreads(self, start=0, size=None, root=0, depth=None):
|
||||||
"""Get threaded comments
|
"""Get threaded comments"""
|
||||||
"""
|
|
||||||
|
|
||||||
def recurse(comment_id, d=0):
|
def recurse(comment_id, d=0):
|
||||||
# Yield the current comment before we look for its children
|
# 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
|
# Recurse if there are children and we are not out of our depth
|
||||||
if depth is None or d + 1 < depth:
|
if depth is None or d + 1 < depth:
|
||||||
@ -209,8 +206,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
return int(key) in self._comments
|
return int(key) in self._comments
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Get an item by its int key
|
"""Get an item by its int key"""
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
comment_id = int(key)
|
comment_id = int(key)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -218,8 +214,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
return self._comments[comment_id].__of__(self)
|
return self._comments[comment_id].__of__(self)
|
||||||
|
|
||||||
def __delitem__(self, key, suppress_container_modified=False):
|
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)
|
key = int(key)
|
||||||
|
|
||||||
@ -269,7 +264,13 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
return self._comments.keys()
|
return self._comments.keys()
|
||||||
|
|
||||||
def items(self):
|
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):
|
def values(self):
|
||||||
return [v.__of__(self) for v in self._comments.values()]
|
return [v.__of__(self) for v in self._comments.values()]
|
||||||
@ -283,7 +284,10 @@ class Conversation(Traversable, Persistent, Explicit):
|
|||||||
|
|
||||||
def iteritems(self):
|
def iteritems(self):
|
||||||
for k, v in six.iteritems(self._comments):
|
for k, v in six.iteritems(self._comments):
|
||||||
yield (k, v.__of__(self),)
|
yield (
|
||||||
|
k,
|
||||||
|
v.__of__(self),
|
||||||
|
)
|
||||||
|
|
||||||
def allowedContentTypes(self):
|
def allowedContentTypes(self):
|
||||||
return []
|
return []
|
||||||
@ -309,6 +313,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@implementer(IConversation) # pragma: no cover
|
@implementer(IConversation) # pragma: no cover
|
||||||
@adapter(IAnnotatable) # pragma: no cover
|
@adapter(IAnnotatable) # pragma: no cover
|
||||||
def conversationCanonicalAdapterFactory(content): # pragma: no cover
|
def conversationCanonicalAdapterFactory(content): # pragma: no cover
|
||||||
@ -350,16 +355,14 @@ class ConversationReplies(object):
|
|||||||
return int(key) in self.children
|
return int(key) in self.children
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Get an item by its int key
|
"""Get an item by its int key"""
|
||||||
"""
|
|
||||||
key = int(key)
|
key = int(key)
|
||||||
if key not in self.children:
|
if key not in self.children:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
return self.conversation[key]
|
return self.conversation[key]
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
"""Delete an item by its int key
|
"""Delete an item by its int key"""
|
||||||
"""
|
|
||||||
key = int(key)
|
key = int(key)
|
||||||
if key not in self.children:
|
if key not in self.children:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
@ -392,7 +395,10 @@ class ConversationReplies(object):
|
|||||||
|
|
||||||
def iteritems(self):
|
def iteritems(self):
|
||||||
for key in self.children:
|
for key in self.children:
|
||||||
yield (key, self.conversation[key],)
|
yield (
|
||||||
|
key,
|
||||||
|
self.conversation[key],
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self):
|
def children(self):
|
||||||
@ -418,11 +424,12 @@ class CommentReplies(ConversationReplies):
|
|||||||
self.conversation = aq_parent(self.comment)
|
self.conversation = aq_parent(self.comment)
|
||||||
conversation_has_no_children = not hasattr(
|
conversation_has_no_children = not hasattr(
|
||||||
self.conversation,
|
self.conversation,
|
||||||
'_children',
|
"_children",
|
||||||
)
|
)
|
||||||
if self.conversation is None or conversation_has_no_children:
|
if self.conversation is None or conversation_has_no_children:
|
||||||
raise TypeError("This adapter doesn't know what to do with the "
|
raise TypeError(
|
||||||
'parent conversation')
|
"This adapter doesn't know what to do with the " "parent conversation"
|
||||||
|
)
|
||||||
|
|
||||||
self.comment_id = self.comment.comment_id
|
self.comment_id = self.comment.comment_id
|
||||||
|
|
||||||
|
@ -16,8 +16,7 @@ from zope.interface import implementer
|
|||||||
|
|
||||||
@implementer(IDiscussionEvent)
|
@implementer(IDiscussionEvent)
|
||||||
class DiscussionEvent(object):
|
class DiscussionEvent(object):
|
||||||
""" Custom event
|
"""Custom event"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, context, comment, **kwargs):
|
def __init__(self, context, comment, **kwargs):
|
||||||
self.object = context
|
self.object = context
|
||||||
@ -28,55 +27,47 @@ class DiscussionEvent(object):
|
|||||||
# Add event to the request to be able to access comment attributes
|
# Add event to the request to be able to access comment attributes
|
||||||
# in content-rules dynamic strings
|
# in content-rules dynamic strings
|
||||||
request = context.REQUEST
|
request = context.REQUEST
|
||||||
request.set('event', self)
|
request.set("event", self)
|
||||||
|
|
||||||
|
|
||||||
@implementer(ICommentAddedEvent)
|
@implementer(ICommentAddedEvent)
|
||||||
class CommentAddedEvent(DiscussionEvent):
|
class CommentAddedEvent(DiscussionEvent):
|
||||||
""" Event to be triggered when a Comment is added
|
"""Event to be triggered when a Comment is added"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(ICommentModifiedEvent)
|
@implementer(ICommentModifiedEvent)
|
||||||
class CommentModifiedEvent(DiscussionEvent):
|
class CommentModifiedEvent(DiscussionEvent):
|
||||||
""" Event to be triggered when a Comment is modified
|
"""Event to be triggered when a Comment is modified"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(ICommentRemovedEvent)
|
@implementer(ICommentRemovedEvent)
|
||||||
class CommentRemovedEvent(DiscussionEvent):
|
class CommentRemovedEvent(DiscussionEvent):
|
||||||
""" Event to be triggered when a Comment is removed
|
"""Event to be triggered when a Comment is removed"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(IReplyAddedEvent)
|
@implementer(IReplyAddedEvent)
|
||||||
class ReplyAddedEvent(DiscussionEvent):
|
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)
|
@implementer(IReplyModifiedEvent)
|
||||||
class ReplyModifiedEvent(DiscussionEvent):
|
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)
|
@implementer(IReplyRemovedEvent)
|
||||||
class ReplyRemovedEvent(DiscussionEvent):
|
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)
|
@implementer(ICommentDeletedEvent)
|
||||||
class CommentDeletedEvent(DiscussionEvent):
|
class CommentDeletedEvent(DiscussionEvent):
|
||||||
""" Event to be triggered when a Comment is deleted
|
"""Event to be triggered when a Comment is deleted"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(ICommentPublishedEvent)
|
@implementer(ICommentPublishedEvent)
|
||||||
class CommentPublishedEvent(DiscussionEvent):
|
class CommentPublishedEvent(DiscussionEvent):
|
||||||
""" Event to be triggered when a Comment is publicated
|
"""Event to be triggered when a Comment is publicated"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(ICommentTransitionEvent)
|
@implementer(ICommentTransitionEvent)
|
||||||
|
@ -14,9 +14,9 @@ from zope.interface.interfaces import IObjectEvent
|
|||||||
|
|
||||||
def isEmail(value):
|
def isEmail(value):
|
||||||
portal = getUtility(ISiteRoot)
|
portal = getUtility(ISiteRoot)
|
||||||
reg_tool = getToolByName(portal, 'portal_registration')
|
reg_tool = getToolByName(portal, "portal_registration")
|
||||||
if not (value and reg_tool.isValidEmail(value)):
|
if not (value and reg_tool.isValidEmail(value)):
|
||||||
raise Invalid(_('Invalid email address.'))
|
raise Invalid(_("Invalid email address."))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -42,25 +42,24 @@ class IConversation(IIterableMapping):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
total_comments = schema.Int(
|
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,
|
min=0,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
last_comment_date = schema.Date(
|
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,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
commentators = schema.Set(
|
commentators = schema.Set(
|
||||||
title=_(u'The set of unique commentators (usernames)'),
|
title=_(u"The set of unique commentators (usernames)"),
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
public_commentators = schema.Set(
|
public_commentators = schema.Set(
|
||||||
title=_(
|
title=_(
|
||||||
u'The set of unique commentators (usernames) '
|
u"The set of unique commentators (usernames) " u"of published_comments",
|
||||||
u'of published_comments',
|
|
||||||
),
|
),
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
@ -72,8 +71,7 @@ class IConversation(IIterableMapping):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __delitem__(key):
|
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):
|
def getComments(start=0, size=None):
|
||||||
"""Return an iterator of comment objects for rendering.
|
"""Return an iterator of comment objects for rendering.
|
||||||
@ -130,8 +128,7 @@ class IReplies(IIterableMapping):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __delitem__(key):
|
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):
|
class IComment(Interface):
|
||||||
@ -141,61 +138,58 @@ class IComment(Interface):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
portal_type = schema.ASCIILine(
|
portal_type = schema.ASCIILine(
|
||||||
title=_(u'Portal type'),
|
title=_(u"Portal type"),
|
||||||
default='Discussion Item',
|
default="Discussion Item",
|
||||||
)
|
)
|
||||||
|
|
||||||
__parent__ = schema.Object(
|
__parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
|
||||||
title=_(u'Conversation'), schema=Interface)
|
|
||||||
|
|
||||||
__name__ = schema.TextLine(title=_(u'Name'))
|
__name__ = schema.TextLine(title=_(u"Name"))
|
||||||
|
|
||||||
comment_id = schema.Int(
|
comment_id = schema.Int(title=_(u"A comment id unique to this conversation"))
|
||||||
title=_(u'A comment id unique to this conversation'))
|
|
||||||
|
|
||||||
in_reply_to = schema.Int(
|
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,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# for logged in comments - set to None for anonymous
|
# 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
|
# for anonymous comments only, set to None for logged in comments
|
||||||
author_name = schema.TextLine(title=_(u'Name'), required=False)
|
author_name = schema.TextLine(title=_(u"Name"), required=False)
|
||||||
author_email = schema.TextLine(title=_(u'Email'),
|
author_email = schema.TextLine(
|
||||||
|
title=_(u"Email"),
|
||||||
required=False,
|
required=False,
|
||||||
constraint=isEmail,
|
constraint=isEmail,
|
||||||
)
|
)
|
||||||
|
|
||||||
title = schema.TextLine(title=_(u'label_subject',
|
title = schema.TextLine(title=_(u"label_subject", default=u"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(
|
text = schema.Text(
|
||||||
title=_(
|
title=_(
|
||||||
u'label_comment',
|
u"label_comment",
|
||||||
default=u'Comment',
|
default=u"Comment",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_notification = schema.Bool(
|
user_notification = schema.Bool(
|
||||||
title=_(
|
title=_(
|
||||||
u'Notify me of new comments via email.',
|
u"Notify me of new comments via email.",
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
creator = schema.TextLine(title=_(u'Username of the commenter'))
|
creator = schema.TextLine(title=_(u"Username of the commenter"))
|
||||||
creation_date = schema.Date(title=_(u'Creation date'))
|
creation_date = schema.Date(title=_(u"Creation date"))
|
||||||
modification_date = schema.Date(title=_(u'Modification date'))
|
modification_date = schema.Date(title=_(u"Modification date"))
|
||||||
|
|
||||||
|
|
||||||
class ICaptcha(Interface):
|
class ICaptcha(Interface):
|
||||||
"""Captcha/ReCaptcha text field to extend the existing comment form.
|
"""Captcha/ReCaptcha text field to extend the existing comment form."""
|
||||||
"""
|
|
||||||
captcha = schema.TextLine(title=_(u'Captcha'),
|
captcha = schema.TextLine(title=_(u"Captcha"), required=False)
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class IDiscussionSettings(Interface):
|
class IDiscussionSettings(Interface):
|
||||||
@ -210,40 +204,38 @@ class IDiscussionSettings(Interface):
|
|||||||
# - Search control panel: Show comments in search results
|
# - Search control panel: Show comments in search results
|
||||||
|
|
||||||
globally_enabled = schema.Bool(
|
globally_enabled = schema.Bool(
|
||||||
title=_(u'label_globally_enabled',
|
title=_(u"label_globally_enabled", default=u"Globally enable comments"),
|
||||||
default=u'Globally enable comments'),
|
|
||||||
description=_(
|
description=_(
|
||||||
u'help_globally_enabled',
|
u"help_globally_enabled",
|
||||||
default=u'If selected, users are able to post comments on the '
|
default=u"If selected, users are able to post comments on the "
|
||||||
u'site. However, you will still need to enable comments '
|
u"site. However, you will still need to enable comments "
|
||||||
u'for specific content types, folders or content '
|
u"for specific content types, folders or content "
|
||||||
u'objects before users will be able to post comments.',
|
u"objects before users will be able to post comments.",
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
anonymous_comments = schema.Bool(
|
anonymous_comments = schema.Bool(
|
||||||
title=_(u'label_anonymous_comments',
|
title=_(u"label_anonymous_comments", default="Enable anonymous comments"),
|
||||||
default='Enable anonymous comments'),
|
|
||||||
description=_(
|
description=_(
|
||||||
u'help_anonymous_comments',
|
u"help_anonymous_comments",
|
||||||
default=u'If selected, anonymous users are able to post '
|
default=u"If selected, anonymous users are able to post "
|
||||||
u'comments without logging in. It is highly '
|
u"comments without logging in. It is highly "
|
||||||
u'recommended to use a captcha solution to prevent '
|
u"recommended to use a captcha solution to prevent "
|
||||||
u'spam if this setting is enabled.',
|
u"spam if this setting is enabled.",
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
anonymous_email_enabled = schema.Bool(
|
anonymous_email_enabled = schema.Bool(
|
||||||
title=_(u'label_anonymous_email_enabled',
|
title=_(
|
||||||
default=u'Enable anonymous email field'),
|
u"label_anonymous_email_enabled", default=u"Enable anonymous email field"
|
||||||
|
),
|
||||||
description=_(
|
description=_(
|
||||||
u'help_anonymous_email_enabled',
|
u"help_anonymous_email_enabled",
|
||||||
default=u'If selected, anonymous user will have to '
|
default=u"If selected, anonymous user will have to " u"give their email.",
|
||||||
u'give their email.',
|
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
@ -251,130 +243,137 @@ class IDiscussionSettings(Interface):
|
|||||||
|
|
||||||
moderation_enabled = schema.Bool(
|
moderation_enabled = schema.Bool(
|
||||||
title=_(
|
title=_(
|
||||||
u'label_moderation_enabled',
|
u"label_moderation_enabled",
|
||||||
default='Enable comment moderation',
|
default="Enable comment moderation",
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
u'help_moderation_enabled',
|
u"help_moderation_enabled",
|
||||||
default=u'If selected, comments will enter a "Pending" state '
|
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'with the "Review comments" permission ("Reviewer" '
|
||||||
u'or "Manager") can approve comments to make them '
|
u'or "Manager") can approve comments to make them '
|
||||||
u'visible to the public. If you want to enable a '
|
u"visible to the public. If you want to enable a "
|
||||||
u'custom comment workflow, you have to go to the '
|
u"custom comment workflow, you have to go to the "
|
||||||
u'types control panel.',
|
u"types control panel.",
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
edit_comment_enabled = schema.Bool(
|
edit_comment_enabled = schema.Bool(
|
||||||
title=_(u'label_edit_comment_enabled',
|
title=_(u"label_edit_comment_enabled", default="Enable editing of comments"),
|
||||||
default='Enable editing of comments'),
|
description=_(
|
||||||
description=_(u'help_edit_comment_enabled',
|
u"help_edit_comment_enabled",
|
||||||
default=u'If selected, supports editing '
|
default=u"If selected, supports editing "
|
||||||
'of comments for users with the "Edit comments" '
|
'of comments for users with the "Edit comments" '
|
||||||
'permission.'),
|
"permission.",
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
delete_own_comment_enabled = schema.Bool(
|
delete_own_comment_enabled = schema.Bool(
|
||||||
title=_(u'label_delete_own_comment_enabled',
|
title=_(
|
||||||
default='Enable deleting own comments'),
|
u"label_delete_own_comment_enabled", default="Enable deleting own comments"
|
||||||
description=_(u'help_delete_own_comment_enabled',
|
),
|
||||||
default=u'If selected, supports deleting '
|
description=_(
|
||||||
'of own comments for users with the '
|
u"help_delete_own_comment_enabled",
|
||||||
'"Delete own comments" permission.'),
|
default=u"If selected, supports deleting "
|
||||||
|
"of own comments for users with the "
|
||||||
|
'"Delete own comments" permission.',
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
text_transform = schema.Choice(
|
text_transform = schema.Choice(
|
||||||
title=_(u'label_text_transform',
|
title=_(u"label_text_transform", default="Comment text transform"),
|
||||||
default='Comment text transform'),
|
|
||||||
description=_(
|
description=_(
|
||||||
u'help_text_transform',
|
u"help_text_transform",
|
||||||
default=u'Use this setting to choose if the comment text '
|
default=u"Use this setting to choose if the comment text "
|
||||||
u'should be transformed in any way. You can choose '
|
u"should be transformed in any way. You can choose "
|
||||||
u'between "Plain text" and "Intelligent text". '
|
u'between "Plain text" and "Intelligent text". '
|
||||||
u'"Intelligent text" converts plain text into HTML '
|
u'"Intelligent text" converts plain text into HTML '
|
||||||
u'where line breaks and indentation is preserved, '
|
u"where line breaks and indentation is preserved, "
|
||||||
u'and web and email addresses are made into '
|
u"and web and email addresses are made into "
|
||||||
u'clickable links.'),
|
u"clickable links.",
|
||||||
|
),
|
||||||
required=True,
|
required=True,
|
||||||
default='text/plain',
|
default="text/plain",
|
||||||
vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary',
|
vocabulary="plone.app.discussion.vocabularies.TextTransformVocabulary",
|
||||||
)
|
)
|
||||||
|
|
||||||
captcha = schema.Choice(
|
captcha = schema.Choice(
|
||||||
title=_(u'label_captcha',
|
title=_(u"label_captcha", default="Captcha"),
|
||||||
default='Captcha'),
|
|
||||||
description=_(
|
description=_(
|
||||||
u'help_captcha',
|
u"help_captcha",
|
||||||
default=u'Use this setting to enable or disable Captcha '
|
default=u"Use this setting to enable or disable Captcha "
|
||||||
u'validation for comments. Install '
|
u"validation for comments. Install "
|
||||||
u'plone.formwidget.captcha, '
|
u"plone.formwidget.captcha, "
|
||||||
u'plone.formwidget.recaptcha, collective.akismet, or '
|
u"plone.formwidget.recaptcha, collective.akismet, or "
|
||||||
u'collective.z3cform.norobots if there are no options '
|
u"collective.z3cform.norobots if there are no options "
|
||||||
u'available.'),
|
u"available.",
|
||||||
|
),
|
||||||
required=True,
|
required=True,
|
||||||
default='disabled',
|
default="disabled",
|
||||||
vocabulary='plone.app.discussion.vocabularies.CaptchaVocabulary',
|
vocabulary="plone.app.discussion.vocabularies.CaptchaVocabulary",
|
||||||
)
|
)
|
||||||
|
|
||||||
show_commenter_image = schema.Bool(
|
show_commenter_image = schema.Bool(
|
||||||
title=_(u'label_show_commenter_image',
|
title=_(u"label_show_commenter_image", default=u"Show commenter image"),
|
||||||
default=u'Show commenter image'),
|
|
||||||
description=_(
|
description=_(
|
||||||
u'help_show_commenter_image',
|
u"help_show_commenter_image",
|
||||||
default=u'If selected, an image of the user is shown next to '
|
default=u"If selected, an image of the user is shown next to "
|
||||||
u'the comment.'),
|
u"the comment.",
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
moderator_notification_enabled = schema.Bool(
|
moderator_notification_enabled = schema.Bool(
|
||||||
title=_(u'label_moderator_notification_enabled',
|
title=_(
|
||||||
default=u'Enable moderator email notification'),
|
u"label_moderator_notification_enabled",
|
||||||
|
default=u"Enable moderator email notification",
|
||||||
|
),
|
||||||
description=_(
|
description=_(
|
||||||
u'help_moderator_notification_enabled',
|
u"help_moderator_notification_enabled",
|
||||||
default=u'If selected, the moderator is notified if a comment '
|
default=u"If selected, the moderator is notified if a comment "
|
||||||
u'needs attention. The moderator email address can '
|
u"needs attention. The moderator email address can "
|
||||||
u'be set below.'),
|
u"be set below.",
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
moderator_email = schema.ASCIILine(
|
moderator_email = schema.ASCIILine(
|
||||||
title=_(
|
title=_(
|
||||||
u'label_moderator_email',
|
u"label_moderator_email",
|
||||||
default=u'Moderator Email Address',
|
default=u"Moderator Email Address",
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
u'help_moderator_email',
|
u"help_moderator_email",
|
||||||
default=u'Address to which moderator notifications '
|
default=u"Address to which moderator notifications " u"will be sent.",
|
||||||
u'will be sent.'),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_notification_enabled = schema.Bool(
|
user_notification_enabled = schema.Bool(
|
||||||
title=_(
|
title=_(
|
||||||
u'label_user_notification_enabled',
|
u"label_user_notification_enabled",
|
||||||
default=u'Enable user email notification',
|
default=u"Enable user email notification",
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
u'help_user_notification_enabled',
|
u"help_user_notification_enabled",
|
||||||
default=u'If selected, users can choose to be notified '
|
default=u"If selected, users can choose to be notified "
|
||||||
u'of new comments by email.'),
|
u"of new comments by email.",
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IDiscussionLayer(Interface):
|
class IDiscussionLayer(Interface):
|
||||||
"""Request marker installed via browserlayer.xml.
|
"""Request marker installed via browserlayer.xml."""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentingTool(Interface):
|
class ICommentingTool(Interface):
|
||||||
@ -384,54 +383,46 @@ class ICommentingTool(Interface):
|
|||||||
of Plone that had a portal_discussion tool.
|
of Plone that had a portal_discussion tool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom events
|
# Custom events
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class IDiscussionEvent(IObjectEvent):
|
class IDiscussionEvent(IObjectEvent):
|
||||||
""" Discussion custom event
|
"""Discussion custom event"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentAddedEvent(IDiscussionEvent):
|
class ICommentAddedEvent(IDiscussionEvent):
|
||||||
""" Comment added
|
"""Comment added"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentModifiedEvent(IDiscussionEvent):
|
class ICommentModifiedEvent(IDiscussionEvent):
|
||||||
""" Comment modified
|
"""Comment modified"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentRemovedEvent(IDiscussionEvent):
|
class ICommentRemovedEvent(IDiscussionEvent):
|
||||||
""" Comment removed
|
"""Comment removed"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IReplyAddedEvent(IDiscussionEvent):
|
class IReplyAddedEvent(IDiscussionEvent):
|
||||||
""" Comment reply added
|
"""Comment reply added"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IReplyModifiedEvent(IDiscussionEvent):
|
class IReplyModifiedEvent(IDiscussionEvent):
|
||||||
""" Comment reply modified
|
"""Comment reply modified"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IReplyRemovedEvent(IDiscussionEvent):
|
class IReplyRemovedEvent(IDiscussionEvent):
|
||||||
""" Comment reply removed
|
"""Comment reply removed"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentPublishedEvent(IDiscussionEvent):
|
class ICommentPublishedEvent(IDiscussionEvent):
|
||||||
""" Notify user on comment publication
|
"""Notify user on comment publication"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentDeletedEvent(IDiscussionEvent):
|
class ICommentDeletedEvent(IDiscussionEvent):
|
||||||
""" Notify user on comment delete
|
"""Notify user on comment delete"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ICommentTransitionEvent(IDiscussionEvent):
|
class ICommentTransitionEvent(IDiscussionEvent):
|
||||||
|
@ -3,12 +3,10 @@ from Products.CMFCore.utils import getToolByName
|
|||||||
|
|
||||||
|
|
||||||
def index_object(obj, event):
|
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()
|
obj.indexObject()
|
||||||
|
|
||||||
|
|
||||||
def unindex_object(obj, event):
|
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()
|
obj.unindexObject()
|
||||||
|
@ -15,40 +15,43 @@ from zope.component import queryUtility
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import plone.app.collection # noqa
|
import plone.app.collection # noqa
|
||||||
COLLECTION_TYPE = 'Collection'
|
|
||||||
|
COLLECTION_TYPE = "Collection"
|
||||||
except ImportError:
|
except ImportError:
|
||||||
COLLECTION_TYPE = 'Topic'
|
COLLECTION_TYPE = "Topic"
|
||||||
|
|
||||||
|
|
||||||
class PloneAppDiscussion(PloneSandboxLayer):
|
class PloneAppDiscussion(PloneSandboxLayer):
|
||||||
|
|
||||||
defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)
|
defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)
|
||||||
|
|
||||||
USER_NAME = 'johndoe'
|
USER_NAME = "johndoe"
|
||||||
USER_PASSWORD = 'secret'
|
USER_PASSWORD = "secret"
|
||||||
MEMBER_NAME = 'janedoe'
|
MEMBER_NAME = "janedoe"
|
||||||
MEMBER_PASSWORD = 'secret'
|
MEMBER_PASSWORD = "secret"
|
||||||
USER_WITH_FULLNAME_NAME = 'jim'
|
USER_WITH_FULLNAME_NAME = "jim"
|
||||||
USER_WITH_FULLNAME_FULLNAME = 'Jim Fulton'
|
USER_WITH_FULLNAME_FULLNAME = "Jim Fulton"
|
||||||
USER_WITH_FULLNAME_PASSWORD = 'secret'
|
USER_WITH_FULLNAME_PASSWORD = "secret"
|
||||||
MANAGER_USER_NAME = 'manager'
|
MANAGER_USER_NAME = "manager"
|
||||||
MANAGER_USER_PASSWORD = 'secret'
|
MANAGER_USER_PASSWORD = "secret"
|
||||||
REVIEWER_NAME = 'reviewer'
|
REVIEWER_NAME = "reviewer"
|
||||||
REVIEWER_PASSWORD = 'secret'
|
REVIEWER_PASSWORD = "secret"
|
||||||
|
|
||||||
def setUpZope(self, app, configurationContext):
|
def setUpZope(self, app, configurationContext):
|
||||||
# Load ZCML
|
# Load ZCML
|
||||||
import plone.app.discussion
|
import plone.app.discussion
|
||||||
self.loadZCML(package=plone.app.discussion,
|
|
||||||
|
self.loadZCML(
|
||||||
|
package=plone.app.discussion,
|
||||||
context=configurationContext,
|
context=configurationContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUpPloneSite(self, portal):
|
def setUpPloneSite(self, portal):
|
||||||
# Install into Plone site using portal_setup
|
# Install into Plone site using portal_setup
|
||||||
applyProfile(portal, 'plone.app.discussion:default')
|
applyProfile(portal, "plone.app.discussion:default")
|
||||||
|
|
||||||
# Creates some users
|
# Creates some users
|
||||||
acl_users = getToolByName(portal, 'acl_users')
|
acl_users = getToolByName(portal, "acl_users")
|
||||||
acl_users.userFolderAddUser(
|
acl_users.userFolderAddUser(
|
||||||
self.USER_NAME,
|
self.USER_NAME,
|
||||||
self.USER_PASSWORD,
|
self.USER_PASSWORD,
|
||||||
@ -58,41 +61,42 @@ class PloneAppDiscussion(PloneSandboxLayer):
|
|||||||
acl_users.userFolderAddUser(
|
acl_users.userFolderAddUser(
|
||||||
self.MEMBER_NAME,
|
self.MEMBER_NAME,
|
||||||
self.MEMBER_PASSWORD,
|
self.MEMBER_PASSWORD,
|
||||||
['Member'],
|
["Member"],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
acl_users.userFolderAddUser(
|
acl_users.userFolderAddUser(
|
||||||
self.USER_WITH_FULLNAME_NAME,
|
self.USER_WITH_FULLNAME_NAME,
|
||||||
self.USER_WITH_FULLNAME_PASSWORD,
|
self.USER_WITH_FULLNAME_PASSWORD,
|
||||||
['Member'],
|
["Member"],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
acl_users.userFolderAddUser(
|
acl_users.userFolderAddUser(
|
||||||
self.REVIEWER_NAME,
|
self.REVIEWER_NAME,
|
||||||
self.REVIEWER_PASSWORD,
|
self.REVIEWER_PASSWORD,
|
||||||
['Member'],
|
["Member"],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
mtool = getToolByName(portal, 'portal_membership', None)
|
mtool = getToolByName(portal, "portal_membership", None)
|
||||||
gtool = getToolByName(portal, 'portal_groups', None)
|
gtool = getToolByName(portal, "portal_groups", None)
|
||||||
gtool.addPrincipalToGroup(self.REVIEWER_NAME, 'Reviewers')
|
gtool.addPrincipalToGroup(self.REVIEWER_NAME, "Reviewers")
|
||||||
mtool.addMember('jim', 'Jim', ['Member'], [])
|
mtool.addMember("jim", "Jim", ["Member"], [])
|
||||||
mtool.getMemberById('jim').setMemberProperties(
|
mtool.getMemberById("jim").setMemberProperties(
|
||||||
{'fullname': 'Jim Fult\xc3\xb8rn'})
|
{"fullname": "Jim Fult\xc3\xb8rn"}
|
||||||
|
)
|
||||||
|
|
||||||
acl_users.userFolderAddUser(
|
acl_users.userFolderAddUser(
|
||||||
self.MANAGER_USER_NAME,
|
self.MANAGER_USER_NAME,
|
||||||
self.MANAGER_USER_PASSWORD,
|
self.MANAGER_USER_PASSWORD,
|
||||||
['Manager'],
|
["Manager"],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add a document
|
# Add a document
|
||||||
setRoles(portal, TEST_USER_ID, ['Manager'])
|
setRoles(portal, TEST_USER_ID, ["Manager"])
|
||||||
portal.invokeFactory(
|
portal.invokeFactory(
|
||||||
id='doc1',
|
id="doc1",
|
||||||
title='Document 1',
|
title="Document 1",
|
||||||
type_name='Document',
|
type_name="Document",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -112,12 +116,12 @@ class PloneAppDiscussionRobot(PloneAppDiscussion):
|
|||||||
PLONE_APP_DISCUSSION_ROBOT_FIXTURE = PloneAppDiscussionRobot()
|
PLONE_APP_DISCUSSION_ROBOT_FIXTURE = PloneAppDiscussionRobot()
|
||||||
PLONE_APP_DISCUSSION_FIXTURE = PloneAppDiscussion()
|
PLONE_APP_DISCUSSION_FIXTURE = PloneAppDiscussion()
|
||||||
PLONE_APP_DISCUSSION_INTEGRATION_TESTING = IntegrationTesting(
|
PLONE_APP_DISCUSSION_INTEGRATION_TESTING = IntegrationTesting(
|
||||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Integration"
|
||||||
name='PloneAppDiscussion:Integration')
|
)
|
||||||
PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING = FunctionalTesting(
|
PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING = FunctionalTesting(
|
||||||
bases=(PLONE_APP_DISCUSSION_FIXTURE,),
|
bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Functional"
|
||||||
name='PloneAppDiscussion:Functional')
|
)
|
||||||
PLONE_APP_DISCUSSION_ROBOT_TESTING = FunctionalTesting(
|
PLONE_APP_DISCUSSION_ROBOT_TESTING = FunctionalTesting(
|
||||||
bases=(PLONE_APP_DISCUSSION_ROBOT_FIXTURE,),
|
bases=(PLONE_APP_DISCUSSION_ROBOT_FIXTURE,),
|
||||||
name='PloneAppDiscussion:Robot',
|
name="PloneAppDiscussion:Robot",
|
||||||
)
|
)
|
||||||
|
@ -23,33 +23,29 @@ class CatalogSetupTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
|
|
||||||
def test_catalog_installed(self):
|
def test_catalog_installed(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'total_comments' in
|
"total_comments" in self.portal.portal_catalog.indexes(),
|
||||||
self.portal.portal_catalog.indexes(),
|
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'commentators' in
|
"commentators" in self.portal.portal_catalog.indexes(),
|
||||||
self.portal.portal_catalog.indexes(),
|
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'total_comments' in
|
"total_comments" in self.portal.portal_catalog.schema(),
|
||||||
self.portal.portal_catalog.schema(),
|
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'in_response_to' in
|
"in_response_to" in self.portal.portal_catalog.schema(),
|
||||||
self.portal.portal_catalog.schema(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_collection_criteria_installed(self):
|
def test_collection_criteria_installed(self):
|
||||||
if 'portal_atct' not in self.portal:
|
if "portal_atct" not in self.portal:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.portal.portal_atct.getIndex('commentators')
|
self.portal.portal_atct.getIndex("commentators")
|
||||||
self.portal.portal_atct.getIndex('total_comments')
|
self.portal.portal_atct.getIndex("total_comments")
|
||||||
self.portal.portal_atct.getMetadata('total_comments')
|
self.portal.portal_atct.getMetadata("total_comments")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
@ -59,19 +55,19 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
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)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.title = 'Comment 1'
|
comment1.title = "Comment 1"
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.creator = 'jim'
|
comment1.creator = "jim"
|
||||||
comment1.author_username = 'Jim'
|
comment1.author_username = "Jim"
|
||||||
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
||||||
comment1.modification_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(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
@ -93,54 +89,54 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
self.new_comment1_id = new_comment1_id
|
self.new_comment1_id = new_comment1_id
|
||||||
|
|
||||||
def test_total_comments(self):
|
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)
|
self.assertEqual(self.doc1_brain.total_comments, 1)
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
new_comment2_id = self.conversation.addComment(comment2)
|
new_comment2_id = self.conversation.addComment(comment2)
|
||||||
|
|
||||||
comment2 = self.portal.doc1.restrictedTraverse(
|
comment2 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_comment2_id),
|
"++conversation++default/{0}".format(new_comment2_id),
|
||||||
)
|
)
|
||||||
comment2.reindexObject()
|
comment2.reindexObject()
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
self.assertEqual(doc1_brain.total_comments, 2)
|
self.assertEqual(doc1_brain.total_comments, 2)
|
||||||
|
|
||||||
def test_last_comment_date(self):
|
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.assertEqual(
|
||||||
self.doc1_brain.last_comment_date,
|
self.doc1_brain.last_comment_date,
|
||||||
datetime(2006, 9, 17, 14, 18, 12),
|
datetime(2006, 9, 17, 14, 18, 12),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add another comment and check if last comment date is updated.
|
# Add another comment and check if last comment date is updated.
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.creation_date = datetime(2009, 9, 17, 14, 18, 12)
|
comment2.creation_date = datetime(2009, 9, 17, 14, 18, 12)
|
||||||
comment2.modification_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)
|
new_comment2_id = self.conversation.addComment(comment2)
|
||||||
|
|
||||||
comment2 = self.portal.doc1.restrictedTraverse(
|
comment2 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_comment2_id),
|
"++conversation++default/{0}".format(new_comment2_id),
|
||||||
)
|
)
|
||||||
comment2.reindexObject()
|
comment2.reindexObject()
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
@ -155,9 +151,9 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
@ -171,44 +167,44 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
self.assertEqual(doc1_brain.last_comment_date, None)
|
self.assertEqual(doc1_brain.last_comment_date, None)
|
||||||
|
|
||||||
def test_commentators(self):
|
def test_commentators(self):
|
||||||
self.assertTrue('commentators' in self.doc1_brain)
|
self.assertTrue("commentators" in self.doc1_brain)
|
||||||
self.assertEqual(self.doc1_brain.commentators, ('Jim',))
|
self.assertEqual(self.doc1_brain.commentators, ("Jim",))
|
||||||
|
|
||||||
# add another comment with another author
|
# add another comment with another author
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.creator = 'emma'
|
comment2.creator = "emma"
|
||||||
comment2.author_username = 'Emma'
|
comment2.author_username = "Emma"
|
||||||
new_comment2_id = self.conversation.addComment(comment2)
|
new_comment2_id = self.conversation.addComment(comment2)
|
||||||
|
|
||||||
comment2 = self.portal.doc1.restrictedTraverse(
|
comment2 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_comment2_id),
|
"++conversation++default/{0}".format(new_comment2_id),
|
||||||
)
|
)
|
||||||
comment2.reindexObject()
|
comment2.reindexObject()
|
||||||
|
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(doc1_brain.commentators),
|
sorted(doc1_brain.commentators),
|
||||||
sorted(('Jim', 'Emma')),
|
sorted(("Jim", "Emma")),
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove one comments
|
# remove one comments
|
||||||
@ -216,22 +212,22 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
self.assertEqual(doc1_brain.commentators, ('Jim',))
|
self.assertEqual(doc1_brain.commentators, ("Jim",))
|
||||||
|
|
||||||
# remove all comments
|
# remove all comments
|
||||||
del self.conversation[self.new_comment1_id]
|
del self.conversation[self.new_comment1_id]
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
@ -241,9 +237,9 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
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]
|
comment1_brain = brains[0]
|
||||||
@ -252,14 +248,14 @@ class ConversationCatalogTest(unittest.TestCase):
|
|||||||
self.assertEqual(comment1_brain.total_comments, None)
|
self.assertEqual(comment1_brain.total_comments, None)
|
||||||
|
|
||||||
def test_dont_index_private_commentators(self):
|
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()
|
self.portal.doc1.reindexObject()
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
doc1_brain = brains[0]
|
doc1_brain = brains[0]
|
||||||
@ -271,70 +267,70 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
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)
|
conversation = IConversation(self.portal.doc1)
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.creator = 'jim'
|
comment1.creator = "jim"
|
||||||
comment1.author_name = 'Jim'
|
comment1.author_name = "Jim"
|
||||||
new_comment1_id = conversation.addComment(comment1)
|
new_comment1_id = conversation.addComment(comment1)
|
||||||
self.comment_id = new_comment1_id
|
self.comment_id = new_comment1_id
|
||||||
|
|
||||||
# Comment brain
|
# Comment brain
|
||||||
self.comment = self.portal.doc1.restrictedTraverse(
|
self.comment = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_comment1_id),
|
"++conversation++default/{0}".format(new_comment1_id),
|
||||||
)
|
)
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.comment.getPhysicalPath()),
|
"query": "/".join(self.comment.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.comment_brain = brains[0]
|
self.comment_brain = brains[0]
|
||||||
|
|
||||||
def test_title(self):
|
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):
|
def test_no_name_title(self):
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
cid = self.conversation.addComment(comment)
|
cid = self.conversation.addComment(comment)
|
||||||
|
|
||||||
# Comment brain
|
# Comment brain
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
comment = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(cid),
|
"++conversation++default/{0}".format(cid),
|
||||||
)
|
)
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(comment.getPhysicalPath()),
|
"query": "/".join(comment.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
comment_brain = brains[0]
|
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):
|
def test_type(self):
|
||||||
self.assertEqual(self.comment_brain.portal_type, 'Discussion Item')
|
self.assertEqual(self.comment_brain.portal_type, "Discussion Item")
|
||||||
self.assertEqual(self.comment_brain.Type, 'Comment')
|
self.assertEqual(self.comment_brain.Type, "Comment")
|
||||||
|
|
||||||
def test_review_state(self):
|
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):
|
def test_creator(self):
|
||||||
self.assertEqual(self.comment_brain.Creator, 'jim')
|
self.assertEqual(self.comment_brain.Creator, "jim")
|
||||||
|
|
||||||
def test_in_response_to(self):
|
def test_in_response_to(self):
|
||||||
"""Make sure in_response_to returns the title or id of the content
|
"""Make sure in_response_to returns the title or id of the content
|
||||||
object the comment was added to.
|
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):
|
def test_add_comment(self):
|
||||||
self.assertTrue(self.comment_brain)
|
self.assertTrue(self.comment_brain)
|
||||||
@ -346,7 +342,7 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.comment.getPhysicalPath()),
|
"query": "/".join(self.comment.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -354,54 +350,54 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_reindex_comment(self):
|
def test_reindex_comment(self):
|
||||||
# Make sure a comment is reindexed on the catalog when is modified
|
# 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))
|
notify(ObjectModifiedEvent(self.comment))
|
||||||
brains = self.catalog.searchResults(SearchableText='Another text')
|
brains = self.catalog.searchResults(SearchableText="Another text")
|
||||||
self.assertEqual(len(brains), 1)
|
self.assertEqual(len(brains), 1)
|
||||||
|
|
||||||
def test_remove_comments_when_content_object_is_removed(self):
|
def test_remove_comments_when_content_object_is_removed(self):
|
||||||
"""Make sure all comments are removed from the catalog, if the content
|
"""Make sure all comments are removed from the catalog, if the content
|
||||||
object is removed.
|
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.assertEqual(len(brains), 1)
|
||||||
self.portal.manage_delObjects(['doc1'])
|
self.portal.manage_delObjects(["doc1"])
|
||||||
brains = self.catalog.searchResults({'portal_type': 'Discussion Item'})
|
brains = self.catalog.searchResults({"portal_type": "Discussion Item"})
|
||||||
self.assertEqual(len(brains), 0)
|
self.assertEqual(len(brains), 0)
|
||||||
|
|
||||||
def test_move_comments_when_content_object_is_moved(self):
|
def test_move_comments_when_content_object_is_moved(self):
|
||||||
# Create two folders and a content object with a comment
|
# Create two folders and a content object with a comment
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='folder1',
|
id="folder1",
|
||||||
title='Folder 1',
|
title="Folder 1",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='folder2',
|
id="folder2",
|
||||||
title='Folder 2',
|
title="Folder 2",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
self.portal.folder1.invokeFactory(
|
self.portal.folder1.invokeFactory(
|
||||||
id='moveme',
|
id="moveme",
|
||||||
title='Move Me',
|
title="Move Me",
|
||||||
type_name='Document',
|
type_name="Document",
|
||||||
)
|
)
|
||||||
conversation = IConversation(self.portal.folder1.moveme)
|
conversation = IConversation(self.portal.folder1.moveme)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment_id = conversation.addComment(comment)
|
comment_id = conversation.addComment(comment)
|
||||||
# We need to commit here so that _p_jar isn't None and move will work
|
# We need to commit here so that _p_jar isn't None and move will work
|
||||||
transaction.savepoint(optimistic=True)
|
transaction.savepoint(optimistic=True)
|
||||||
|
|
||||||
# Move moveme from folder1 to folder2
|
# 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)
|
self.portal.folder2.manage_pasteObjects(cp)
|
||||||
|
|
||||||
# Make sure no old comment brains are
|
# Make sure no old comment brains are
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
path={
|
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(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.folder2.getPhysicalPath()),
|
"query": "/".join(self.portal.folder2.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(len(brains), 1)
|
self.assertEqual(len(brains), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
brains[0].getPath(),
|
brains[0].getPath(),
|
||||||
'/plone/folder2/moveme/++conversation++default/' +
|
"/plone/folder2/moveme/++conversation++default/" + str(comment_id),
|
||||||
str(comment_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_move_upper_level_folder(self):
|
def test_move_upper_level_folder(self):
|
||||||
# create a folder with a nested structure
|
# create a folder with a nested structure
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='sourcefolder',
|
id="sourcefolder",
|
||||||
title='Source Folder',
|
title="Source Folder",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
self.portal.sourcefolder.invokeFactory(
|
self.portal.sourcefolder.invokeFactory(
|
||||||
id='moveme',
|
id="moveme",
|
||||||
title='Move Me',
|
title="Move Me",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
self.portal.sourcefolder.moveme.invokeFactory(
|
self.portal.sourcefolder.moveme.invokeFactory(
|
||||||
id='mydocument',
|
id="mydocument",
|
||||||
title='My Document',
|
title="My Document",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='targetfolder',
|
id="targetfolder",
|
||||||
title='Target Folder',
|
title="Target Folder",
|
||||||
type_name='Folder',
|
type_name="Folder",
|
||||||
)
|
)
|
||||||
|
|
||||||
# create comment on my-document
|
# create comment on my-document
|
||||||
conversation = IConversation(
|
conversation = IConversation(
|
||||||
self.portal.sourcefolder.moveme.mydocument,
|
self.portal.sourcefolder.moveme.mydocument,
|
||||||
)
|
)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment_id = conversation.addComment(comment)
|
comment_id = conversation.addComment(comment)
|
||||||
|
|
||||||
# We need to commit here so that _p_jar isn't None and move will work
|
# We need to commit here so that _p_jar isn't None and move will work
|
||||||
transaction.savepoint(optimistic=True)
|
transaction.savepoint(optimistic=True)
|
||||||
|
|
||||||
# Move moveme from folder1 to folder2
|
# 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)
|
self.portal.targetfolder.manage_pasteObjects(cp)
|
||||||
|
|
||||||
# Make sure no old comment brains are left
|
# Make sure no old comment brains are left
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
path={'query': '/plone/sourcefolder/moveme'},
|
path={"query": "/plone/sourcefolder/moveme"},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(len(brains), 0)
|
self.assertEqual(len(brains), 0)
|
||||||
@ -471,49 +466,47 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
# make sure comments are correctly index on the target
|
# make sure comments are correctly index on the target
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
path={'query': '/plone/targetfolder/moveme'},
|
path={"query": "/plone/targetfolder/moveme"},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(len(brains), 1)
|
self.assertEqual(len(brains), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
brains[0].getPath(),
|
brains[0].getPath(),
|
||||||
'/plone/targetfolder/moveme/mydocument/++conversation++default/' +
|
"/plone/targetfolder/moveme/mydocument/++conversation++default/"
|
||||||
str(comment_id),
|
+ str(comment_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_comments_when_content_object_is_renamed(self):
|
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
|
# We need to commit here so that _p_jar isn't None and move will work
|
||||||
transaction.savepoint(optimistic=True)
|
transaction.savepoint(optimistic=True)
|
||||||
|
|
||||||
self.portal.manage_renameObject('doc1', 'doc2')
|
self.portal.manage_renameObject("doc1", "doc2")
|
||||||
|
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
)
|
)
|
||||||
self.assertEqual(len(brains), 1)
|
self.assertEqual(len(brains), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
brains[0].getPath(),
|
brains[0].getPath(),
|
||||||
'/plone/doc2/++conversation++default/' +
|
"/plone/doc2/++conversation++default/" + str(self.comment_id),
|
||||||
str(self.comment_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_clear_and_rebuild_catalog(self):
|
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)
|
self.assertTrue(brains)
|
||||||
|
|
||||||
# Clear and rebuild catalog
|
# Clear and rebuild catalog
|
||||||
self.catalog.clearFindAndRebuild()
|
self.catalog.clearFindAndRebuild()
|
||||||
|
|
||||||
# Check if comment is still there
|
# 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)
|
self.assertTrue(brains)
|
||||||
comment_brain = brains[0]
|
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(
|
self.assertEqual(
|
||||||
comment_brain.getPath(),
|
comment_brain.getPath(),
|
||||||
'/plone/doc1/++conversation++default/' +
|
"/plone/doc1/++conversation++default/" + str(self.comment_id),
|
||||||
str(self.comment_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_clear_and_rebuild_catalog_for_nested_comments(self):
|
def test_clear_and_rebuild_catalog_for_nested_comments(self):
|
||||||
@ -528,25 +521,25 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
# +- Comment 2
|
# +- Comment 2
|
||||||
# +- Comment 2_1
|
# +- Comment 2_1
|
||||||
|
|
||||||
comment1_1 = createObject('plone.Comment')
|
comment1_1 = createObject("plone.Comment")
|
||||||
comment1_1.title = 'Re: Comment 1'
|
comment1_1.title = "Re: Comment 1"
|
||||||
comment1_1.text = 'Comment text'
|
comment1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1_1 = createObject('plone.Comment')
|
comment1_1_1 = createObject("plone.Comment")
|
||||||
comment1_1_1.title = 'Re: Re: Comment 1'
|
comment1_1_1.title = "Re: Re: Comment 1"
|
||||||
comment1_1_1.text = 'Comment text'
|
comment1_1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_2 = createObject('plone.Comment')
|
comment1_2 = createObject("plone.Comment")
|
||||||
comment1_2.title = 'Re: Comment 1 (2)'
|
comment1_2.title = "Re: Comment 1 (2)"
|
||||||
comment1_2.text = 'Comment text'
|
comment1_2.text = "Comment text"
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
comment2_1 = createObject('plone.Comment')
|
comment2_1 = createObject("plone.Comment")
|
||||||
comment2_1.title = 'Re: Comment 2'
|
comment2_1.title = "Re: Comment 2"
|
||||||
comment2_1.text = 'Comment text'
|
comment2_1.text = "Comment text"
|
||||||
|
|
||||||
# Create the nested comment structure
|
# Create the nested comment structure
|
||||||
new_id_1 = self.conversation.addComment(self.comment)
|
new_id_1 = self.conversation.addComment(self.comment)
|
||||||
@ -568,7 +561,7 @@ class CommentCatalogTest(unittest.TestCase):
|
|||||||
self.catalog.clearFindAndRebuild()
|
self.catalog.clearFindAndRebuild()
|
||||||
|
|
||||||
# Check if comments are still there
|
# 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.assertTrue(brains)
|
||||||
self.assertEqual(len(brains), 6)
|
self.assertEqual(len(brains), 6)
|
||||||
|
|
||||||
@ -578,19 +571,19 @@ class NoConversationCatalogTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
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)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
dict(
|
dict(
|
||||||
path={
|
path={
|
||||||
'query': '/'.join(self.portal.doc1.getPhysicalPath()),
|
"query": "/".join(self.portal.doc1.getPhysicalPath()),
|
||||||
},
|
},
|
||||||
portal_type='Document',
|
portal_type="Document",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
@ -598,11 +591,10 @@ class NoConversationCatalogTest(unittest.TestCase):
|
|||||||
self.doc1_brain = brains[0]
|
self.doc1_brain = brains[0]
|
||||||
|
|
||||||
def test_total_comments(self):
|
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)
|
self.assertEqual(self.doc1_brain.total_comments, 0)
|
||||||
|
|
||||||
# Make sure no conversation has been created
|
# Make sure no conversation has been created
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'plone.app.discussion:conversation' not in
|
"plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1),
|
||||||
IAnnotations(self.portal.doc1),
|
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,7 @@ import six
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('plone.app.discussion.tests')
|
logger = logging.getLogger("plone.app.discussion.tests")
|
||||||
logger.addHandler(logging.StreamHandler())
|
logger.addHandler(logging.StreamHandler())
|
||||||
|
|
||||||
|
|
||||||
@ -27,30 +27,30 @@ class CommentTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.portal.doc1, 'publish')
|
workflow.doActionFor(self.portal.doc1, "publish")
|
||||||
|
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.catalog = getToolByName(self.portal, 'portal_catalog')
|
self.catalog = getToolByName(self.portal, "portal_catalog")
|
||||||
self.document_brain = self.catalog.searchResults(
|
self.document_brain = self.catalog.searchResults(portal_type="Document")[0]
|
||||||
portal_type='Document')[0]
|
|
||||||
|
|
||||||
def test_factory(self):
|
def test_factory(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
self.assertTrue(IComment.providedBy(comment1))
|
self.assertTrue(IComment.providedBy(comment1))
|
||||||
|
|
||||||
def test_UTCDates(self):
|
def test_UTCDates(self):
|
||||||
utc_to_local_diff = \
|
utc_to_local_diff = datetime.datetime.now() - datetime.datetime.utcnow()
|
||||||
datetime.datetime.now() - datetime.datetime.utcnow()
|
|
||||||
utc_to_local_diff = abs(utc_to_local_diff.seconds)
|
utc_to_local_diff = abs(utc_to_local_diff.seconds)
|
||||||
if utc_to_local_diff < 60:
|
if utc_to_local_diff < 60:
|
||||||
logger.warning('Your computer is living in a timezone where local '
|
logger.warning(
|
||||||
'time equals utc time. Some potential errors can '
|
"Your computer is living in a timezone where local "
|
||||||
'get hidden by that')
|
"time equals utc time. Some potential errors can "
|
||||||
comment1 = createObject('plone.Comment')
|
"get hidden by that"
|
||||||
|
)
|
||||||
|
comment1 = createObject("plone.Comment")
|
||||||
local_utc = datetime.datetime.utcnow()
|
local_utc = datetime.datetime.utcnow()
|
||||||
for date in (comment1.creation_date, comment1.modification_date):
|
for date in (comment1.creation_date, comment1.modification_date):
|
||||||
difference = abs(date - local_utc)
|
difference = abs(date - local_utc)
|
||||||
@ -60,171 +60,166 @@ class CommentTest(unittest.TestCase):
|
|||||||
self.assertFalse(difference // 10)
|
self.assertFalse(difference // 10)
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.comment_id = 123
|
comment1.comment_id = 123
|
||||||
self.assertEqual('123', comment1.id)
|
self.assertEqual("123", comment1.id)
|
||||||
self.assertEqual('123', comment1.getId())
|
self.assertEqual("123", comment1.getId())
|
||||||
self.assertEqual(u'123', comment1.__name__)
|
self.assertEqual(u"123", comment1.__name__)
|
||||||
|
|
||||||
def test_uid(self):
|
def test_uid(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
comment_brain = self.catalog.searchResults(
|
comment_brain = self.catalog.searchResults(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
)[0]
|
)[0]
|
||||||
self.assertTrue(comment_brain.UID)
|
self.assertTrue(comment_brain.UID)
|
||||||
|
|
||||||
def test_uid_is_unique(self):
|
def test_uid_is_unique(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
conversation.addComment(comment2)
|
conversation.addComment(comment2)
|
||||||
brains = self.catalog.searchResults(
|
brains = self.catalog.searchResults(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
)
|
)
|
||||||
self.assertNotEqual(brains[0].UID, brains[1].UID)
|
self.assertNotEqual(brains[0].UID, brains[1].UID)
|
||||||
|
|
||||||
def test_comment_uid_differs_from_content_uid(self):
|
def test_comment_uid_differs_from_content_uid(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
comment_brain = self.catalog.searchResults(
|
comment_brain = self.catalog.searchResults(
|
||||||
portal_type='Discussion Item',
|
portal_type="Discussion Item",
|
||||||
)[0]
|
)[0]
|
||||||
self.assertNotEqual(self.document_brain.UID, comment_brain.UID)
|
self.assertNotEqual(self.document_brain.UID, comment_brain.UID)
|
||||||
|
|
||||||
def test_title(self):
|
def test_title(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.author_name = 'Jim Fulton'
|
comment1.author_name = "Jim Fulton"
|
||||||
conversation.addComment(comment1)
|
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):
|
def test_no_name_title(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
conversation.addComment(comment1)
|
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):
|
def test_title_special_characters(self):
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='doc_sp_chars',
|
id="doc_sp_chars",
|
||||||
title=u'Document äüö',
|
title=u"Document äüö",
|
||||||
type_name='Document',
|
type_name="Document",
|
||||||
)
|
)
|
||||||
conversation = IConversation(self.portal.doc_sp_chars)
|
conversation = IConversation(self.portal.doc_sp_chars)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.author_name = u'Tarek Ziadé'
|
comment1.author_name = u"Tarek Ziadé"
|
||||||
conversation.addComment(comment1)
|
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):
|
def test_title_special_characters_utf8(self):
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='doc_sp_chars_utf8',
|
id="doc_sp_chars_utf8",
|
||||||
title='Document ëïû',
|
title="Document ëïû",
|
||||||
type_name='Document',
|
type_name="Document",
|
||||||
)
|
)
|
||||||
conversation = IConversation(self.portal.doc_sp_chars_utf8)
|
conversation = IConversation(self.portal.doc_sp_chars_utf8)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.author_name = 'Hüüb Bôûmä'
|
comment1.author_name = "Hüüb Bôûmä"
|
||||||
conversation.addComment(comment1)
|
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):
|
def test_creator(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.creator = 'jim'
|
comment1.creator = "jim"
|
||||||
self.assertEqual('jim', comment1.Creator())
|
self.assertEqual("jim", comment1.Creator())
|
||||||
|
|
||||||
def test_creator_author_name(self):
|
def test_creator_author_name(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.author_name = 'joey'
|
comment1.author_name = "joey"
|
||||||
self.assertEqual('joey', comment1.Creator())
|
self.assertEqual("joey", comment1.Creator())
|
||||||
|
|
||||||
def test_owner(self):
|
def test_owner(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
self.assertEqual((['plone', 'acl_users'], TEST_USER_ID),
|
self.assertEqual(
|
||||||
comment1.getOwnerTuple())
|
(["plone", "acl_users"], TEST_USER_ID), comment1.getOwnerTuple()
|
||||||
|
)
|
||||||
|
|
||||||
def test_type(self):
|
def test_type(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
self.assertEqual(comment1.Type(), 'Comment')
|
self.assertEqual(comment1.Type(), "Comment")
|
||||||
|
|
||||||
def test_mime_type(self):
|
def test_mime_type(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
self.assertEqual(comment1.mime_type, 'text/plain')
|
self.assertEqual(comment1.mime_type, "text/plain")
|
||||||
|
|
||||||
def test_getText(self):
|
def test_getText(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'First paragraph\n\nSecond_paragraph'
|
comment1.text = "First paragraph\n\nSecond_paragraph"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
''.join(comment1.getText().split()),
|
"".join(comment1.getText().split()),
|
||||||
'<p>Firstparagraph<br><br>Second_paragraph</p>',
|
"<p>Firstparagraph<br><br>Second_paragraph</p>",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_getText_escapes_HTML(self):
|
def test_getText_escapes_HTML(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = '<b>Got HTML?</b>'
|
comment1.text = "<b>Got HTML?</b>"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
comment1.getText(),
|
comment1.getText(),
|
||||||
'<p><b>Got HTML?</b></p>',
|
"<p><b>Got HTML?</b></p>",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_getText_with_non_ascii_characters(self):
|
def test_getText_with_non_ascii_characters(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = u'Umlaute sind ä, ö und ü.'
|
comment1.text = u"Umlaute sind ä, ö und ü."
|
||||||
out = b'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>'
|
out = b"<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>"
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
self.assertEqual(
|
self.assertEqual(comment1.getText(), out)
|
||||||
comment1.getText(),
|
|
||||||
out)
|
|
||||||
else:
|
else:
|
||||||
self.assertEqual(
|
self.assertEqual(comment1.getText(), out.decode("utf8"))
|
||||||
comment1.getText(),
|
|
||||||
out.decode('utf8'))
|
|
||||||
|
|
||||||
def test_getText_doesnt_link(self):
|
def test_getText_doesnt_link(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Go to http://www.plone.org'
|
comment1.text = "Go to http://www.plone.org"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
comment1.getText(),
|
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):
|
def test_getText_uses_comment_mime_type(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Go to http://www.plone.org'
|
comment1.text = "Go to http://www.plone.org"
|
||||||
comment1.mime_type = 'text/x-web-intelligent'
|
comment1.mime_type = "text/x-web-intelligent"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
comment1.getText(),
|
comment1.getText(),
|
||||||
'Go to <a href="http://www.plone.org" ' +
|
'Go to <a href="http://www.plone.org" '
|
||||||
'rel="nofollow">http://www.plone.org</a>',
|
+ 'rel="nofollow">http://www.plone.org</a>',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_getText_uses_comment_mime_type_html(self):
|
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.text = 'Go to <a href="http://www.plone.org">plone.org</a>'
|
||||||
comment1.mime_type = 'text/html'
|
comment1.mime_type = "text/html"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
comment1.getText(),
|
comment1.getText(),
|
||||||
'Go to <a href="http://www.plone.org">plone.org</a>',
|
'Go to <a href="http://www.plone.org">plone.org</a>',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_getText_w_custom_targetMimetype(self):
|
def test_getText_w_custom_targetMimetype(self):
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'para'
|
comment1.text = "para"
|
||||||
self.assertEqual(comment1.getText(targetMimetype='text/plain'), 'para')
|
self.assertEqual(comment1.getText(targetMimetype="text/plain"), "para")
|
||||||
|
|
||||||
def test_getText_invalid_transformation_raises_error(self):
|
def test_getText_invalid_transformation_raises_error(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.mime_type = 'text/x-html-safe'
|
comment1.mime_type = "text/x-html-safe"
|
||||||
comment1.text = 'para'
|
comment1.text = "para"
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
self.assertEqual(
|
self.assertEqual(comment1.getText(targetMimetype="text/html"), "para")
|
||||||
comment1.getText(targetMimetype='text/html'),
|
|
||||||
'para')
|
|
||||||
|
|
||||||
def test_traversal(self):
|
def test_traversal(self):
|
||||||
# make sure comments are traversable, have an id, absolute_url and
|
# make sure comments are traversable, have an id, absolute_url and
|
||||||
@ -232,26 +227,29 @@ class CommentTest(unittest.TestCase):
|
|||||||
|
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
new_comment1_id = conversation.addComment(comment1)
|
new_comment1_id = conversation.addComment(comment1)
|
||||||
|
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
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.assertTrue(IComment.providedBy(comment))
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(
|
(
|
||||||
'', 'plone', 'doc1', '++conversation++default',
|
"",
|
||||||
|
"plone",
|
||||||
|
"doc1",
|
||||||
|
"++conversation++default",
|
||||||
str(new_comment1_id),
|
str(new_comment1_id),
|
||||||
),
|
),
|
||||||
comment.getPhysicalPath(),
|
comment.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default/' +
|
"http://nohost/plone/doc1/++conversation++default/" + str(new_comment1_id),
|
||||||
str(new_comment1_id), comment.absolute_url(),
|
comment.absolute_url(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_view_blob_types(self):
|
def test_view_blob_types(self):
|
||||||
@ -260,68 +258,67 @@ class CommentTest(unittest.TestCase):
|
|||||||
version of the url with a /view in it.
|
version of the url with a /view in it.
|
||||||
"""
|
"""
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='image1',
|
id="image1",
|
||||||
title='Image',
|
title="Image",
|
||||||
type_name='Image',
|
type_name="Image",
|
||||||
)
|
)
|
||||||
conversation = IConversation(self.portal.image1)
|
conversation = IConversation(self.portal.image1)
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
new_comment1_id = conversation.addComment(comment1)
|
new_comment1_id = conversation.addComment(comment1)
|
||||||
comment = self.portal.image1.restrictedTraverse(
|
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 = View(comment, self.request)
|
||||||
View.__call__(view)
|
View.__call__(view)
|
||||||
response = self.request.response
|
response = self.request.response
|
||||||
self.assertIn('/view', response.headers['location'])
|
self.assertIn("/view", response.headers["location"])
|
||||||
|
|
||||||
def test_workflow(self):
|
def test_workflow(self):
|
||||||
"""Basic test for the 'comment_review_workflow'
|
"""Basic test for the 'comment_review_workflow'"""
|
||||||
"""
|
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow,'),
|
("comment_review_workflow,"),
|
||||||
)
|
)
|
||||||
|
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
new_comment1_id = conversation.addComment(comment1)
|
new_comment1_id = conversation.addComment(comment1)
|
||||||
|
|
||||||
comment = conversation[new_comment1_id]
|
comment = conversation[new_comment1_id]
|
||||||
|
|
||||||
# Make sure comments use the 'comment_review_workflow'
|
# Make sure comments use the 'comment_review_workflow'
|
||||||
chain = self.portal.portal_workflow.getChainFor(comment)
|
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
|
# Ensure the initial state was entered and recorded
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
1,
|
1,
|
||||||
len(comment.workflow_history['comment_review_workflow']),
|
len(comment.workflow_history["comment_review_workflow"]),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
None,
|
None,
|
||||||
comment.workflow_history['comment_review_workflow'][0]['action'],
|
comment.workflow_history["comment_review_workflow"][0]["action"],
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'pending',
|
"pending",
|
||||||
self.portal.portal_workflow.getInfoFor(comment, 'review_state'),
|
self.portal.portal_workflow.getInfoFor(comment, "review_state"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_fti(self):
|
def test_fti(self):
|
||||||
# test that we can look up an FTI for Discussion Item
|
# test that we can look up an FTI for Discussion Item
|
||||||
|
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Discussion Item',
|
"Discussion Item",
|
||||||
self.portal.portal_types.objectIds(),
|
self.portal.portal_types.objectIds(),
|
||||||
)
|
)
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
|
|
||||||
fti = self.portal.portal_types.getTypeInfo(comment1)
|
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):
|
def test_view(self):
|
||||||
# make sure that the comment view is there and redirects to the right
|
# 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)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
# Create a comment
|
# Create a comment
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
# Add comment to the conversation
|
# Add comment to the conversation
|
||||||
new_comment1_id = conversation.addComment(comment1)
|
new_comment1_id = conversation.addComment(comment1)
|
||||||
|
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
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
|
# make sure the view is there
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
getMultiAdapter(
|
getMultiAdapter(
|
||||||
(comment, self.request),
|
(comment, self.request),
|
||||||
name='view',
|
name="view",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -364,11 +361,11 @@ class RepliesTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.portal.doc1, 'publish')
|
workflow.doActionFor(self.portal.doc1, "publish")
|
||||||
|
|
||||||
def test_add_comment(self):
|
def test_add_comment(self):
|
||||||
# Add comments to a CommentReplies adapter
|
# Add comments to a CommentReplies adapter
|
||||||
@ -380,16 +377,16 @@ class RepliesTest(unittest.TestCase):
|
|||||||
# Add a comment to the conversation
|
# Add a comment to the conversation
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
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
|
# Add a reply to the CommentReplies adapter of the first comment
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
|
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
|
|
||||||
@ -417,16 +414,16 @@ class RepliesTest(unittest.TestCase):
|
|||||||
# Add a comment to the conversation
|
# Add a comment to the conversation
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
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
|
# Add a reply to the CommentReplies adapter of the first comment
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
|
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
|
|
||||||
@ -448,83 +445,86 @@ class RepliesTest(unittest.TestCase):
|
|||||||
# physical path
|
# physical path
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
new_id = conversation.addComment(comment)
|
new_id = conversation.addComment(comment)
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
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
|
# Add a reply to the CommentReplies adapter of the first comment
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
new_re_id = replies.addComment(re_comment)
|
new_re_id = replies.addComment(re_comment)
|
||||||
re_comment = self.portal.doc1.restrictedTraverse(
|
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
|
# Add a reply to the reply
|
||||||
re_re_comment = createObject('plone.Comment')
|
re_re_comment = createObject("plone.Comment")
|
||||||
re_re_comment.text = 'Comment text'
|
re_re_comment.text = "Comment text"
|
||||||
replies = IReplies(re_comment)
|
replies = IReplies(re_comment)
|
||||||
new_re_re_id = replies.addComment(re_re_comment)
|
new_re_re_id = replies.addComment(re_re_comment)
|
||||||
re_re_comment = self.portal.doc1.restrictedTraverse(
|
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
|
# Add a reply to the replies reply
|
||||||
re_re_re_comment = createObject('plone.Comment')
|
re_re_re_comment = createObject("plone.Comment")
|
||||||
re_re_re_comment.text = 'Comment text'
|
re_re_re_comment.text = "Comment text"
|
||||||
replies = IReplies(re_re_comment)
|
replies = IReplies(re_re_comment)
|
||||||
new_re_re_re_id = replies.addComment(re_re_re_comment)
|
new_re_re_re_id = replies.addComment(re_re_re_comment)
|
||||||
re_re_re_comment = self.portal.doc1.restrictedTraverse(
|
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(
|
self.assertEqual(
|
||||||
('', 'plone', 'doc1', '++conversation++default', str(new_id)),
|
("", "plone", "doc1", "++conversation++default", str(new_id)),
|
||||||
comment.getPhysicalPath(),
|
comment.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default/' +
|
"http://nohost/plone/doc1/++conversation++default/" + str(new_id),
|
||||||
str(new_id), comment.absolute_url(),
|
comment.absolute_url(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
('', 'plone', 'doc1', '++conversation++default', str(new_re_id)),
|
("", "plone", "doc1", "++conversation++default", str(new_re_id)),
|
||||||
re_comment.getPhysicalPath(),
|
re_comment.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default/' +
|
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_id),
|
||||||
str(new_re_id),
|
|
||||||
re_comment.absolute_url(),
|
re_comment.absolute_url(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(
|
(
|
||||||
'', 'plone', 'doc1', '++conversation++default',
|
"",
|
||||||
|
"plone",
|
||||||
|
"doc1",
|
||||||
|
"++conversation++default",
|
||||||
str(new_re_re_id),
|
str(new_re_re_id),
|
||||||
),
|
),
|
||||||
re_re_comment.getPhysicalPath(),
|
re_re_comment.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default/' +
|
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_id),
|
||||||
str(new_re_re_id),
|
|
||||||
re_re_comment.absolute_url(),
|
re_re_comment.absolute_url(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(
|
(
|
||||||
'', 'plone', 'doc1', '++conversation++default',
|
"",
|
||||||
|
"plone",
|
||||||
|
"doc1",
|
||||||
|
"++conversation++default",
|
||||||
str(new_re_re_re_id),
|
str(new_re_re_re_id),
|
||||||
),
|
),
|
||||||
re_re_re_comment.getPhysicalPath(),
|
re_re_re_comment.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default/' +
|
"http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_re_id),
|
||||||
str(new_re_re_re_id),
|
|
||||||
re_re_re_comment.absolute_url(),
|
re_re_re_comment.absolute_url(),
|
||||||
)
|
)
|
||||||
|
@ -40,23 +40,23 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal.invokeFactory('Folder', 'test-folder')
|
self.portal.invokeFactory("Folder", "test-folder")
|
||||||
self.folder = self.portal['test-folder']
|
self.folder = self.portal["test-folder"]
|
||||||
|
|
||||||
interface.alsoProvides(
|
interface.alsoProvides(
|
||||||
self.portal.REQUEST,
|
self.portal.REQUEST,
|
||||||
interfaces.IDiscussionLayer,
|
interfaces.IDiscussionLayer,
|
||||||
)
|
)
|
||||||
|
|
||||||
wftool = getToolByName(self.portal, 'portal_workflow')
|
wftool = getToolByName(self.portal, "portal_workflow")
|
||||||
wftool.doActionFor(self.portal.doc1, action='publish')
|
wftool.doActionFor(self.portal.doc1, action="publish")
|
||||||
self.portal.doc1.allow_discussion = True
|
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.memberdata = self.portal.portal_memberdata
|
||||||
self.context = getattr(self.portal, 'doc1')
|
self.context = getattr(self.portal, "doc1")
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -64,8 +64,7 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
settings.globally_enabled = True
|
settings.globally_enabled = True
|
||||||
|
|
||||||
def test_add_comment(self):
|
def test_add_comment(self):
|
||||||
"""Post a comment as logged-in user.
|
"""Post a comment as logged-in user."""
|
||||||
"""
|
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
self.portal.doc1.allow_discussion = True
|
self.portal.doc1.allow_discussion = True
|
||||||
@ -82,7 +81,7 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
adapts=(Interface, IBrowserRequest),
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The form should return an error if the comment text field is empty
|
# The form should return an error if the comment text field is empty
|
||||||
@ -90,46 +89,45 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, request),
|
(self.context, request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
self.assertEqual(len(errors), 1)
|
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
|
# The form is submitted successfully, if the required text field is
|
||||||
# filled out
|
# filled out
|
||||||
request = make_request(form={'form.widgets.text': u'bar'})
|
request = make_request(form={"form.widgets.text": u"bar"})
|
||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, request),
|
(self.context, request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
|
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
|
||||||
|
|
||||||
comments = IConversation(commentForm.context).getComments()
|
comments = IConversation(commentForm.context).getComments()
|
||||||
comments = [comment for comment in comments] # consume iterator
|
comments = [comment for comment in comments] # consume iterator
|
||||||
self.assertEqual(len(comments), 1)
|
self.assertEqual(len(comments), 1)
|
||||||
|
|
||||||
for comment in comments:
|
for comment in comments:
|
||||||
self.assertEqual(comment.text, u'bar')
|
self.assertEqual(comment.text, u"bar")
|
||||||
self.assertEqual(comment.creator, 'test_user_1_')
|
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()
|
local_roles = comment.get_local_roles()
|
||||||
self.assertEqual(len(local_roles), 1)
|
self.assertEqual(len(local_roles), 1)
|
||||||
userid, roles = local_roles[0]
|
userid, roles = local_roles[0]
|
||||||
self.assertEqual(userid, 'test_user_1_')
|
self.assertEqual(userid, "test_user_1_")
|
||||||
self.assertEqual(len(roles), 1)
|
self.assertEqual(len(roles), 1)
|
||||||
self.assertEqual(roles[0], 'Owner')
|
self.assertEqual(roles[0], "Owner")
|
||||||
|
|
||||||
def test_edit_comment(self):
|
def test_edit_comment(self):
|
||||||
"""Edit a comment as logged-in user.
|
"""Edit a comment as logged-in user."""
|
||||||
"""
|
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
self.portal.doc1.allow_discussion = True
|
self.portal.doc1.allow_discussion = True
|
||||||
@ -146,65 +144,64 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
adapts=(Interface, IBrowserRequest),
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
provideAdapter(
|
provideAdapter(
|
||||||
adapts=(Interface, IBrowserRequest),
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=EditCommentForm,
|
factory=EditCommentForm,
|
||||||
name=u'edit-comment-form',
|
name=u"edit-comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The form is submitted successfully, if the required text field is
|
# The form is submitted successfully, if the required text field is
|
||||||
# filled out
|
# filled out
|
||||||
request = make_request(form={'form.widgets.text': u'bar'})
|
request = make_request(form={"form.widgets.text": u"bar"})
|
||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, request),
|
(self.context, request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
|
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
|
||||||
|
|
||||||
# Edit the last comment
|
# Edit the last comment
|
||||||
conversation = IConversation(self.context)
|
conversation = IConversation(self.context)
|
||||||
comment = [x for x in conversation.getComments()][-1]
|
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(
|
editForm = getMultiAdapter(
|
||||||
(comment, request),
|
(comment, request),
|
||||||
name=u'edit-comment-form',
|
name=u"edit-comment-form",
|
||||||
)
|
)
|
||||||
editForm.update()
|
editForm.update()
|
||||||
data, errors = editForm.extractData() # pylint: disable-msg=W0612
|
data, errors = editForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
self.assertEqual(len(errors), 0)
|
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]
|
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 = IConversation(commentForm.context).getComments()
|
||||||
comments = [c for c in comments] # consume iterator
|
comments = [c for c in comments] # consume iterator
|
||||||
self.assertEqual(len(comments), 1)
|
self.assertEqual(len(comments), 1)
|
||||||
|
|
||||||
for comment in comments:
|
for comment in comments:
|
||||||
self.assertEqual(comment.text, u'foobar')
|
self.assertEqual(comment.text, u"foobar")
|
||||||
self.assertEqual(comment.creator, 'test_user_1_')
|
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()
|
local_roles = comment.get_local_roles()
|
||||||
self.assertEqual(len(local_roles), 1)
|
self.assertEqual(len(local_roles), 1)
|
||||||
userid, roles = local_roles[0]
|
userid, roles = local_roles[0]
|
||||||
self.assertEqual(userid, 'test_user_1_')
|
self.assertEqual(userid, "test_user_1_")
|
||||||
self.assertEqual(len(roles), 1)
|
self.assertEqual(len(roles), 1)
|
||||||
self.assertEqual(roles[0], 'Owner')
|
self.assertEqual(roles[0], "Owner")
|
||||||
|
|
||||||
def test_delete_comment(self):
|
def test_delete_comment(self):
|
||||||
"""Delete a comment as logged-in user.
|
"""Delete a comment as logged-in user."""
|
||||||
"""
|
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
self.portal.doc1.allow_discussion = True
|
self.portal.doc1.allow_discussion = True
|
||||||
@ -221,48 +218,47 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
adapts=(Interface, IBrowserRequest),
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The form is submitted successfully, if the required text field is
|
# The form is submitted successfully, if the required text field is
|
||||||
# filled out
|
# filled out
|
||||||
form_request = make_request(form={'form.widgets.text': u'bar'})
|
form_request = make_request(form={"form.widgets.text": u"bar"})
|
||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, form_request),
|
(self.context, form_request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
|
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
|
||||||
|
|
||||||
# Delete the last comment
|
# Delete the last comment
|
||||||
conversation = IConversation(self.context)
|
conversation = IConversation(self.context)
|
||||||
comment = [x for x in conversation.getComments()][-1]
|
comment = [x for x in conversation.getComments()][-1]
|
||||||
deleteView = getMultiAdapter(
|
deleteView = getMultiAdapter(
|
||||||
(comment, self.request),
|
(comment, self.request),
|
||||||
name=u'moderate-delete-comment',
|
name=u"moderate-delete-comment",
|
||||||
)
|
)
|
||||||
# try to delete last comment without 'Delete comments' permission
|
# 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(
|
self.assertRaises(
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
comment.restrictedTraverse,
|
comment.restrictedTraverse,
|
||||||
'@@moderate-delete-comment',
|
"@@moderate-delete-comment",
|
||||||
)
|
)
|
||||||
deleteView()
|
deleteView()
|
||||||
self.assertEqual(1, len([x for x in conversation.getComments()]))
|
self.assertEqual(1, len([x for x in conversation.getComments()]))
|
||||||
# try to delete last comment with 'Delete comments' permission
|
# try to delete last comment with 'Delete comments' permission
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
|
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
|
||||||
deleteView()
|
deleteView()
|
||||||
self.assertEqual(0, len([x for x in conversation.getComments()]))
|
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):
|
def test_delete_own_comment(self):
|
||||||
"""Delete own comment as logged-in user.
|
"""Delete own comment as logged-in user."""
|
||||||
"""
|
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
self.portal.doc1.allow_discussion = True
|
self.portal.doc1.allow_discussion = True
|
||||||
@ -279,42 +275,42 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
adapts=(Interface, IBrowserRequest),
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The form is submitted successfully, if the required text field is
|
# The form is submitted successfully, if the required text field is
|
||||||
# filled out
|
# filled out
|
||||||
form_request = make_request(form={'form.widgets.text': u'bar'})
|
form_request = make_request(form={"form.widgets.text": u"bar"})
|
||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, form_request),
|
(self.context, form_request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
|
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
self.assertFalse(commentForm.handleComment(commentForm, 'foo'))
|
self.assertFalse(commentForm.handleComment(commentForm, "foo"))
|
||||||
|
|
||||||
# Delete the last comment
|
# Delete the last comment
|
||||||
conversation = IConversation(self.context)
|
conversation = IConversation(self.context)
|
||||||
comment = [x for x in conversation.getComments()][-1]
|
comment = [x for x in conversation.getComments()][-1]
|
||||||
deleteView = getMultiAdapter(
|
deleteView = getMultiAdapter(
|
||||||
(comment, self.request),
|
(comment, self.request),
|
||||||
name=u'delete-own-comment',
|
name=u"delete-own-comment",
|
||||||
)
|
)
|
||||||
# try to delete last comment with johndoe
|
# try to delete last comment with johndoe
|
||||||
setRoles(self.portal, 'johndoe', ['Member'])
|
setRoles(self.portal, "johndoe", ["Member"])
|
||||||
login(self.portal, 'johndoe')
|
login(self.portal, "johndoe")
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
comment.restrictedTraverse,
|
comment.restrictedTraverse,
|
||||||
'@@delete-own-comment',
|
"@@delete-own-comment",
|
||||||
)
|
)
|
||||||
self.assertEqual(1, len([x for x in conversation.getComments()]))
|
self.assertEqual(1, len([x for x in conversation.getComments()]))
|
||||||
# try to delete last comment with the same user that created it
|
# try to delete last comment with the same user that created it
|
||||||
login(self.portal, TEST_USER_NAME)
|
login(self.portal, TEST_USER_NAME)
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Member'])
|
setRoles(self.portal, TEST_USER_ID, ["Member"])
|
||||||
deleteView()
|
deleteView()
|
||||||
self.assertEqual(0, len([x for x in conversation.getComments()]))
|
self.assertEqual(0, len([x for x in conversation.getComments()]))
|
||||||
|
|
||||||
@ -337,40 +333,43 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
alsoProvides(request, IAttributeAnnotatable)
|
alsoProvides(request, IAttributeAnnotatable)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
provideAdapter(
|
||||||
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u'comment-form')
|
name=u"comment-form",
|
||||||
|
)
|
||||||
|
|
||||||
# Post an anonymous comment and provide a name
|
# Post an anonymous comment and provide a name
|
||||||
request = make_request(form={
|
request = make_request(
|
||||||
'form.widgets.name': u'john doe',
|
form={
|
||||||
'form.widgets.text': u'bar',
|
"form.widgets.name": u"john doe",
|
||||||
})
|
"form.widgets.text": u"bar",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
commentForm = getMultiAdapter(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, request),
|
(self.context, request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
self.assertFalse(commentForm.handleComment(commentForm, 'action'))
|
self.assertFalse(commentForm.handleComment(commentForm, "action"))
|
||||||
|
|
||||||
comments = IConversation(commentForm.context).getComments()
|
comments = IConversation(commentForm.context).getComments()
|
||||||
comments = [comment for comment in comments] # consume itertor
|
comments = [comment for comment in comments] # consume itertor
|
||||||
self.assertEqual(len(comments), 1)
|
self.assertEqual(len(comments), 1)
|
||||||
|
|
||||||
for comment in IConversation(commentForm.context).getComments():
|
for comment in IConversation(commentForm.context).getComments():
|
||||||
self.assertEqual(comment.text, u'bar')
|
self.assertEqual(comment.text, u"bar")
|
||||||
self.assertIsNone(comment.creator)
|
self.assertIsNone(comment.creator)
|
||||||
roles = comment.get_local_roles()
|
roles = comment.get_local_roles()
|
||||||
self.assertEqual(len(roles), 0)
|
self.assertEqual(len(roles), 0)
|
||||||
|
|
||||||
def test_can_not_add_comments_if_discussion_is_not_allowed(self):
|
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
|
# Disable discussion
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -384,16 +383,18 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
alsoProvides(request, IAttributeAnnotatable)
|
alsoProvides(request, IAttributeAnnotatable)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
provideAdapter(
|
||||||
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
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(
|
commentForm = getMultiAdapter(
|
||||||
(self.context, request),
|
(self.context, request),
|
||||||
name=u'comment-form',
|
name=u"comment-form",
|
||||||
)
|
)
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
@ -402,10 +403,7 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
# allowed
|
# allowed
|
||||||
self.assertEqual(len(errors), 0)
|
self.assertEqual(len(errors), 0)
|
||||||
|
|
||||||
self.assertRaises(Unauthorized,
|
self.assertRaises(Unauthorized, commentForm.handleComment, commentForm, "foo")
|
||||||
commentForm.handleComment,
|
|
||||||
commentForm,
|
|
||||||
'foo')
|
|
||||||
|
|
||||||
def test_anonymous_can_not_add_comments_if_discussion_is_not_allowed(self):
|
def test_anonymous_can_not_add_comments_if_discussion_is_not_allowed(self):
|
||||||
"""Make sure that anonymous users can't post comments if anonymous
|
"""Make sure that anonymous users can't post comments if anonymous
|
||||||
@ -423,15 +421,16 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
alsoProvides(request, IAttributeAnnotatable)
|
alsoProvides(request, IAttributeAnnotatable)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
provideAdapter(adapts=(Interface, IBrowserRequest),
|
provideAdapter(
|
||||||
|
adapts=(Interface, IBrowserRequest),
|
||||||
provides=Interface,
|
provides=Interface,
|
||||||
factory=CommentForm,
|
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),
|
commentForm = getMultiAdapter((self.context, request), name=u"comment-form")
|
||||||
name=u'comment-form')
|
|
||||||
commentForm.update()
|
commentForm.update()
|
||||||
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
data, errors = commentForm.extractData() # pylint: disable-msg=W0612
|
||||||
|
|
||||||
@ -440,7 +439,7 @@ class TestCommentForm(unittest.TestCase):
|
|||||||
Unauthorized,
|
Unauthorized,
|
||||||
commentForm.handleComment,
|
commentForm.handleComment,
|
||||||
commentForm,
|
commentForm,
|
||||||
'foo',
|
"foo",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -449,22 +448,22 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal.invokeFactory('Folder', 'test-folder')
|
self.portal.invokeFactory("Folder", "test-folder")
|
||||||
self.folder = self.portal['test-folder']
|
self.folder = self.portal["test-folder"]
|
||||||
interface.alsoProvides(
|
interface.alsoProvides(
|
||||||
self.request,
|
self.request,
|
||||||
interfaces.IDiscussionLayer,
|
interfaces.IDiscussionLayer,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.workflowTool = getToolByName(self.portal, 'portal_workflow')
|
self.workflowTool = getToolByName(self.portal, "portal_workflow")
|
||||||
self.workflowTool.setDefaultChain('comment_one_state_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
|
self.memberdata = self.portal.portal_memberdata
|
||||||
context = getattr(self.portal, 'doc1')
|
context = getattr(self.portal, "doc1")
|
||||||
self.viewlet = CommentsViewlet(context, self.request, None, None)
|
self.viewlet = CommentsViewlet(context, self.request, None, None)
|
||||||
|
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
@ -486,9 +485,8 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
# Anonymous has no 'can review' permission
|
# Anonymous has no 'can review' permission
|
||||||
self.assertFalse(self.viewlet.can_review())
|
self.assertFalse(self.viewlet.can_review())
|
||||||
# The reviewer role has the 'Review comments' permission
|
# The reviewer role has the 'Review comments' permission
|
||||||
self.portal.acl_users._doAddUser(
|
self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], [])
|
||||||
'reviewer', 'secret', ['Reviewer'], [])
|
login(self.portal, "reviewer")
|
||||||
login(self.portal, 'reviewer')
|
|
||||||
self.assertTrue(self.viewlet.can_review())
|
self.assertTrue(self.viewlet.can_review())
|
||||||
|
|
||||||
def test_can_manage(self):
|
def test_can_manage(self):
|
||||||
@ -502,9 +500,8 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
# Anonymous has no 'can review' permission
|
# Anonymous has no 'can review' permission
|
||||||
self.assertFalse(self.viewlet.can_manage())
|
self.assertFalse(self.viewlet.can_manage())
|
||||||
# The reviewer role has the 'Review comments' permission
|
# The reviewer role has the 'Review comments' permission
|
||||||
self.portal.acl_users._doAddUser(
|
self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], [])
|
||||||
'reviewer', 'secret', ['Reviewer'], [])
|
login(self.portal, "reviewer")
|
||||||
login(self.portal, 'reviewer')
|
|
||||||
self.assertTrue(self.viewlet.can_manage())
|
self.assertTrue(self.viewlet.can_manage())
|
||||||
|
|
||||||
def test_is_discussion_allowed(self):
|
def test_is_discussion_allowed(self):
|
||||||
@ -521,46 +518,48 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
self.assertTrue(self.viewlet.comment_transform_message())
|
self.assertTrue(self.viewlet.comment_transform_message())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.viewlet.comment_transform_message(),
|
self.viewlet.comment_transform_message(),
|
||||||
'You can add a comment by filling out the form below. Plain ' +
|
"You can add a comment by filling out the form below. Plain "
|
||||||
'text formatting.')
|
+ "text formatting.",
|
||||||
|
)
|
||||||
|
|
||||||
# Set text transform to intelligent text
|
# Set text transform to intelligent text
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
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
|
# Make sure the comment description changes accordingly
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.viewlet.comment_transform_message(),
|
self.viewlet.comment_transform_message(),
|
||||||
'You can add a comment by filling out the form below. ' +
|
"You can add a comment by filling out the form below. "
|
||||||
'Plain text formatting. Web and email addresses are transformed ' +
|
+ "Plain text formatting. Web and email addresses are transformed "
|
||||||
'into clickable links.',
|
+ "into clickable links.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enable moderation workflow
|
# Enable moderation workflow
|
||||||
self.workflowTool.setChainForPortalTypes(
|
self.workflowTool.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",), ("comment_review_workflow,")
|
||||||
('comment_review_workflow,'))
|
)
|
||||||
|
|
||||||
# Make sure the comment description shows that comments are moderated
|
# Make sure the comment description shows that comments are moderated
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.viewlet.comment_transform_message(),
|
self.viewlet.comment_transform_message(),
|
||||||
'You can add a comment by filling out the form below. ' +
|
"You can add a comment by filling out the form below. "
|
||||||
'Plain text formatting. Web and email addresses are transformed ' +
|
+ "Plain text formatting. Web and email addresses are transformed "
|
||||||
'into clickable links. Comments are moderated.')
|
+ "into clickable links. Comments are moderated.",
|
||||||
|
)
|
||||||
|
|
||||||
def test_has_replies(self):
|
def test_has_replies(self):
|
||||||
self.assertEqual(self.viewlet.has_replies(), False)
|
self.assertEqual(self.viewlet.has_replies(), False)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
self.assertEqual(self.viewlet.has_replies(), True)
|
self.assertEqual(self.viewlet.has_replies(), True)
|
||||||
|
|
||||||
def test_get_replies(self):
|
def test_get_replies(self):
|
||||||
self.assertFalse(self.viewlet.get_replies())
|
self.assertFalse(self.viewlet.get_replies())
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
@ -583,8 +582,8 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_replies_with_workflow_actions(self):
|
def test_get_replies_with_workflow_actions(self):
|
||||||
self.assertFalse(self.viewlet.get_replies(workflow_actions=True))
|
self.assertFalse(self.viewlet.get_replies(workflow_actions=True))
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
c1 = conversation.addComment(comment)
|
c1 = conversation.addComment(comment)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -593,32 +592,34 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
# Enable moderation workflow
|
# Enable moderation workflow
|
||||||
self.workflowTool.setChainForPortalTypes(
|
self.workflowTool.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow,'),
|
("comment_review_workflow,"),
|
||||||
)
|
)
|
||||||
# Check if workflow actions are available
|
# Check if workflow actions are available
|
||||||
reply = next(self.viewlet.get_replies(workflow_actions=True))
|
reply = next(self.viewlet.get_replies(workflow_actions=True))
|
||||||
self.assertTrue('actions' in reply)
|
self.assertTrue("actions" in reply)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
reply['actions'][0]['id'],
|
reply["actions"][0]["id"],
|
||||||
'mark_as_spam',
|
"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(
|
self.assertEqual(
|
||||||
reply['actions'][0]['url'],
|
reply["actions"][0]["url"],
|
||||||
expected_url.format(int(c1)),
|
expected_url.format(int(c1)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_commenter_home_url(self):
|
def test_get_commenter_home_url(self):
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
IConversation(self.portal.doc1)
|
IConversation(self.portal.doc1)
|
||||||
portal_membership = getToolByName(self.portal, 'portal_membership')
|
portal_membership = getToolByName(self.portal, "portal_membership")
|
||||||
m = portal_membership.getAuthenticatedMember()
|
m = portal_membership.getAuthenticatedMember()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.viewlet.get_commenter_home_url(m.getUserName()),
|
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):
|
def test_get_commenter_home_url_is_none(self):
|
||||||
@ -627,72 +628,77 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
def test_get_commenter_portrait(self):
|
def test_get_commenter_portrait(self):
|
||||||
|
|
||||||
# Add a user with a member image
|
# Add a user with a member image
|
||||||
self.membershipTool.addMember('jim', 'Jim', ['Member'], [])
|
self.membershipTool.addMember("jim", "Jim", ["Member"], [])
|
||||||
self.memberdata._setPortrait(Image(
|
self.memberdata._setPortrait(
|
||||||
id='jim',
|
Image(
|
||||||
|
id="jim",
|
||||||
file=dummy.File(),
|
file=dummy.File(),
|
||||||
title='',
|
title="",
|
||||||
), 'jim')
|
),
|
||||||
self.assertEqual(
|
"jim",
|
||||||
self.memberdata._getPortrait('jim').getId(),
|
|
||||||
'jim',
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.memberdata._getPortrait('jim').meta_type,
|
self.memberdata._getPortrait("jim").getId(),
|
||||||
'Image',
|
"jim",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.memberdata._getPortrait("jim").meta_type,
|
||||||
|
"Image",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add a conversation with a comment
|
# Add a conversation with a comment
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.Creator = 'Jim'
|
comment.Creator = "Jim"
|
||||||
comment.author_username = 'jim'
|
comment.author_username = "jim"
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
|
|
||||||
# Call get_commenter_portrait method of the viewlet
|
# Call get_commenter_portrait method of the viewlet
|
||||||
self.viewlet.update()
|
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
|
# Check if the correct member image URL is returned
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
portrait_url,
|
portrait_url,
|
||||||
'http://nohost/plone/portal_memberdata/portraits/jim',
|
"http://nohost/plone/portal_memberdata/portraits/jim",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_commenter_portrait_is_none(self):
|
def test_get_commenter_portrait_is_none(self):
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.viewlet.get_commenter_portrait() in (
|
self.viewlet.get_commenter_portrait()
|
||||||
'defaultUser.png',
|
in (
|
||||||
'defaultUser.gif',
|
"defaultUser.png",
|
||||||
|
"defaultUser.gif",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_commenter_portrait_without_userimage(self):
|
def test_get_commenter_portrait_without_userimage(self):
|
||||||
|
|
||||||
# Create a user without a user image
|
# 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
|
# Add a conversation with a comment
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.Creator = 'Jim'
|
comment.Creator = "Jim"
|
||||||
comment.author_username = 'jim'
|
comment.author_username = "jim"
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
|
|
||||||
# Call get_commenter_portrait method of the viewlet
|
# Call get_commenter_portrait method of the viewlet
|
||||||
self.viewlet.update()
|
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.
|
# Check if the correct default member image URL is returned.
|
||||||
# Note that Products.PlonePAS 4.0.5 and later have .png and
|
# Note that Products.PlonePAS 4.0.5 and later have .png and
|
||||||
# earlier versions have .gif.
|
# earlier versions have .gif.
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
portrait_url in (
|
portrait_url
|
||||||
'http://nohost/plone/defaultUser.png',
|
in (
|
||||||
'http://nohost/plone/defaultUser.gif',
|
"http://nohost/plone/defaultUser.png",
|
||||||
|
"http://nohost/plone/defaultUser.gif",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -702,8 +708,8 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
# Allow anonymous discussion
|
# Allow anonymous discussion
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry[
|
registry[
|
||||||
'plone.app.discussion.interfaces.IDiscussionSettings.' +
|
"plone.app.discussion.interfaces.IDiscussionSettings."
|
||||||
'anonymous_comments'
|
+ "anonymous_comments"
|
||||||
] = True
|
] = True
|
||||||
# Test if anonymous discussion is allowed for the viewlet
|
# Test if anonymous discussion is allowed for the viewlet
|
||||||
self.assertTrue(self.viewlet.anonymous_discussion_allowed())
|
self.assertTrue(self.viewlet.anonymous_discussion_allowed())
|
||||||
@ -712,8 +718,8 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
self.assertTrue(self.viewlet.show_commenter_image())
|
self.assertTrue(self.viewlet.show_commenter_image())
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry[
|
registry[
|
||||||
'plone.app.discussion.interfaces.IDiscussionSettings.' +
|
"plone.app.discussion.interfaces.IDiscussionSettings."
|
||||||
'show_commenter_image'
|
+ "show_commenter_image"
|
||||||
] = False
|
] = False
|
||||||
self.assertFalse(self.viewlet.show_commenter_image())
|
self.assertFalse(self.viewlet.show_commenter_image())
|
||||||
|
|
||||||
@ -726,7 +732,7 @@ class TestCommentsViewlet(unittest.TestCase):
|
|||||||
self.viewlet.update()
|
self.viewlet.update()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.viewlet.login_action(),
|
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):
|
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
|
# 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
|
# time of the local time given above. That way, the time for the
|
||||||
# example below is correct within each time zone, independent of DST
|
# example below is correct within each time zone, independent of DST
|
||||||
python_time = datetime(
|
python_time = datetime(*time.gmtime(time.mktime(python_time.timetuple()))[:7])
|
||||||
*time.gmtime(time.mktime(python_time.timetuple()))[:7])
|
|
||||||
localized_time = self.viewlet.format_time(python_time)
|
localized_time = self.viewlet.format_time(python_time)
|
||||||
self.assertTrue(
|
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"],
|
||||||
)
|
)
|
||||||
|
@ -19,31 +19,33 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
class CommentContentRulesTest(unittest.TestCase):
|
class CommentContentRulesTest(unittest.TestCase):
|
||||||
""" Test custom comments events
|
"""Test custom comments events"""
|
||||||
"""
|
|
||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Setup sandbox
|
# Setup sandbox
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
|
|
||||||
# Setup current user properties
|
# Setup current user properties
|
||||||
member = self.portal.portal_membership.getMemberById(TEST_USER_ID)
|
member = self.portal.portal_membership.getMemberById(TEST_USER_ID)
|
||||||
member.setMemberProperties({
|
member.setMemberProperties(
|
||||||
'fullname': 'X Manager',
|
{
|
||||||
'email': 'xmanager@example.com',
|
"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 = createObject("plone.Comment")
|
||||||
comment.text = 'This is a comment'
|
comment.text = "This is a comment"
|
||||||
comment.author_username = 'jim'
|
comment.author_username = "jim"
|
||||||
comment.author_name = 'Jim'
|
comment.author_name = "Jim"
|
||||||
comment.author_email = 'jim@example.com'
|
comment.author_email = "jim@example.com"
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
|
|
||||||
@ -54,58 +56,61 @@ class CommentContentRulesTest(unittest.TestCase):
|
|||||||
self.assertTrue(IRuleEventType.providedBy(IReplyRemovedEvent))
|
self.assertTrue(IRuleEventType.providedBy(IReplyRemovedEvent))
|
||||||
|
|
||||||
def testCommentIdStringSubstitution(self):
|
def testCommentIdStringSubstitution(self):
|
||||||
comment_id = getAdapter(self.document, IStringSubstitution,
|
comment_id = getAdapter(self.document, IStringSubstitution, name=u"comment_id")
|
||||||
name=u'comment_id')
|
|
||||||
self.assertIsInstance(comment_id(), int)
|
self.assertIsInstance(comment_id(), int)
|
||||||
|
|
||||||
def testCommentTextStringSubstitution(self):
|
def testCommentTextStringSubstitution(self):
|
||||||
comment_text = getAdapter(self.document, IStringSubstitution,
|
comment_text = getAdapter(
|
||||||
name=u'comment_text')
|
self.document, IStringSubstitution, name=u"comment_text"
|
||||||
self.assertEqual(comment_text(), u'This is a comment')
|
)
|
||||||
|
self.assertEqual(comment_text(), u"This is a comment")
|
||||||
|
|
||||||
def testCommentUserIdStringSubstitution(self):
|
def testCommentUserIdStringSubstitution(self):
|
||||||
comment_user_id = getAdapter(self.document, IStringSubstitution,
|
comment_user_id = getAdapter(
|
||||||
name=u'comment_user_id')
|
self.document, IStringSubstitution, name=u"comment_user_id"
|
||||||
self.assertEqual(comment_user_id(), u'jim')
|
)
|
||||||
|
self.assertEqual(comment_user_id(), u"jim")
|
||||||
|
|
||||||
def testCommentUserFullNameStringSubstitution(self):
|
def testCommentUserFullNameStringSubstitution(self):
|
||||||
comment_user_fullname = getAdapter(self.document, IStringSubstitution,
|
comment_user_fullname = getAdapter(
|
||||||
name=u'comment_user_fullname')
|
self.document, IStringSubstitution, name=u"comment_user_fullname"
|
||||||
self.assertEqual(comment_user_fullname(), u'Jim')
|
)
|
||||||
|
self.assertEqual(comment_user_fullname(), u"Jim")
|
||||||
|
|
||||||
def testCommentUserEmailStringSubstitution(self):
|
def testCommentUserEmailStringSubstitution(self):
|
||||||
comment_user_email = getAdapter(self.document, IStringSubstitution,
|
comment_user_email = getAdapter(
|
||||||
name=u'comment_user_email')
|
self.document, IStringSubstitution, name=u"comment_user_email"
|
||||||
self.assertEqual(comment_user_email(), u'jim@example.com')
|
)
|
||||||
|
self.assertEqual(comment_user_email(), u"jim@example.com")
|
||||||
|
|
||||||
|
|
||||||
class ReplyContentRulesTest(unittest.TestCase):
|
class ReplyContentRulesTest(unittest.TestCase):
|
||||||
""" Test custom comments events
|
"""Test custom comments events"""
|
||||||
"""
|
|
||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Setup sandbox
|
# Setup sandbox
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
self.document = self.portal['doc1']
|
self.document = self.portal["doc1"]
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'This is a comment'
|
comment.text = "This is a comment"
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
comment = self.document.restrictedTraverse(
|
comment = self.document.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id),
|
"++conversation++default/{0}".format(new_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'This is a reply'
|
re_comment.text = "This is a reply"
|
||||||
re_comment.author_username = 'julia'
|
re_comment.author_username = "julia"
|
||||||
re_comment.author_name = 'Juliana'
|
re_comment.author_name = "Juliana"
|
||||||
re_comment.author_email = 'julia@example.com'
|
re_comment.author_email = "julia@example.com"
|
||||||
|
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
replies.addComment(re_comment)
|
replies.addComment(re_comment)
|
||||||
@ -114,7 +119,7 @@ class ReplyContentRulesTest(unittest.TestCase):
|
|||||||
reply_id = getAdapter(
|
reply_id = getAdapter(
|
||||||
self.document,
|
self.document,
|
||||||
IStringSubstitution,
|
IStringSubstitution,
|
||||||
name=u'comment_id',
|
name=u"comment_id",
|
||||||
)
|
)
|
||||||
self.assertIsInstance(reply_id(), int)
|
self.assertIsInstance(reply_id(), int)
|
||||||
|
|
||||||
@ -122,30 +127,30 @@ class ReplyContentRulesTest(unittest.TestCase):
|
|||||||
reply_text = getAdapter(
|
reply_text = getAdapter(
|
||||||
self.document,
|
self.document,
|
||||||
IStringSubstitution,
|
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):
|
def testReplyUserIdStringSubstitution(self):
|
||||||
reply_user_id = getAdapter(
|
reply_user_id = getAdapter(
|
||||||
self.document,
|
self.document,
|
||||||
IStringSubstitution,
|
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):
|
def testReplyUserFullNameStringSubstitution(self):
|
||||||
reply_user_fullname = getAdapter(
|
reply_user_fullname = getAdapter(
|
||||||
self.document,
|
self.document,
|
||||||
IStringSubstitution,
|
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):
|
def testReplyUserEmailStringSubstitution(self):
|
||||||
reply_user_email = getAdapter(
|
reply_user_email = getAdapter(
|
||||||
self.document,
|
self.document,
|
||||||
IStringSubstitution,
|
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")
|
||||||
|
@ -19,8 +19,8 @@ class RegistryTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.registry = Registry()
|
self.registry = Registry()
|
||||||
self.registry.registerInterface(IDiscussionSettings)
|
self.registry.registerInterface(IDiscussionSettings)
|
||||||
|
|
||||||
@ -31,99 +31,100 @@ class RegistryTest(unittest.TestCase):
|
|||||||
def test_discussion_controlpanel_view(self):
|
def test_discussion_controlpanel_view(self):
|
||||||
view = getMultiAdapter(
|
view = getMultiAdapter(
|
||||||
(self.portal, self.portal.REQUEST),
|
(self.portal, self.portal.REQUEST),
|
||||||
name='discussion-controlpanel',
|
name="discussion-controlpanel",
|
||||||
)
|
)
|
||||||
self.assertTrue(view())
|
self.assertTrue(view())
|
||||||
|
|
||||||
def test_discussion_in_controlpanel(self):
|
def test_discussion_in_controlpanel(self):
|
||||||
# Check if discussion is in the control panel
|
# Check if discussion is in the control panel
|
||||||
self.controlpanel = getToolByName(self.portal, 'portal_controlpanel')
|
self.controlpanel = getToolByName(self.portal, "portal_controlpanel")
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'discussion' in [
|
"discussion"
|
||||||
a.getAction(self)['id']
|
in [a.getAction(self)["id"] for a in self.controlpanel.listActions()],
|
||||||
for a in self.controlpanel.listActions()
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_globally_enabled(self):
|
def test_globally_enabled(self):
|
||||||
# Check globally_enabled record
|
# Check globally_enabled record
|
||||||
self.assertTrue('globally_enabled' in IDiscussionSettings)
|
self.assertTrue("globally_enabled" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.globally_enabled'
|
+ "IDiscussionSettings.globally_enabled"
|
||||||
],
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_anonymous_comments(self):
|
def test_anonymous_comments(self):
|
||||||
# Check anonymous_comments record
|
# Check anonymous_comments record
|
||||||
self.assertTrue('anonymous_comments' in IDiscussionSettings)
|
self.assertTrue("anonymous_comments" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.anonymous_comments'
|
+ "IDiscussionSettings.anonymous_comments"
|
||||||
],
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_moderation_enabled(self):
|
def test_moderation_enabled(self):
|
||||||
# Check globally_enabled record
|
# Check globally_enabled record
|
||||||
self.assertTrue('moderation_enabled' in IDiscussionSettings)
|
self.assertTrue("moderation_enabled" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.moderation_enabled'
|
+ "IDiscussionSettings.moderation_enabled"
|
||||||
],
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_edit_comment_enabled(self):
|
def test_edit_comment_enabled(self):
|
||||||
# Check edit_comment_enabled record
|
# Check edit_comment_enabled record
|
||||||
self.assertTrue('edit_comment_enabled' in IDiscussionSettings)
|
self.assertTrue("edit_comment_enabled" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry['plone.app.discussion.interfaces.' +
|
self.registry[
|
||||||
'IDiscussionSettings.edit_comment_enabled'],
|
"plone.app.discussion.interfaces."
|
||||||
|
+ "IDiscussionSettings.edit_comment_enabled"
|
||||||
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete_own_comment_enabled(self):
|
def test_delete_own_comment_enabled(self):
|
||||||
# Check delete_own_comment_enabled record
|
# 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.assertEqual(
|
||||||
self.registry['plone.app.discussion.interfaces.' +
|
self.registry[
|
||||||
'IDiscussionSettings.delete_own_comment_enabled'],
|
"plone.app.discussion.interfaces."
|
||||||
|
+ "IDiscussionSettings.delete_own_comment_enabled"
|
||||||
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_text_transform(self):
|
def test_text_transform(self):
|
||||||
self.assertTrue('text_transform' in IDiscussionSettings)
|
self.assertTrue("text_transform" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.text_transform'
|
+ "IDiscussionSettings.text_transform"
|
||||||
],
|
],
|
||||||
'text/plain',
|
"text/plain",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_captcha(self):
|
def test_captcha(self):
|
||||||
# Check globally_enabled record
|
# Check globally_enabled record
|
||||||
self.assertTrue('captcha' in IDiscussionSettings)
|
self.assertTrue("captcha" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces." + "IDiscussionSettings.captcha"
|
||||||
'IDiscussionSettings.captcha'
|
|
||||||
],
|
],
|
||||||
'disabled',
|
"disabled",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_show_commenter_image(self):
|
def test_show_commenter_image(self):
|
||||||
# Check show_commenter_image record
|
# Check show_commenter_image record
|
||||||
self.assertTrue('show_commenter_image' in IDiscussionSettings)
|
self.assertTrue("show_commenter_image" in IDiscussionSettings)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.show_commenter_image'
|
+ "IDiscussionSettings.show_commenter_image"
|
||||||
],
|
],
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
@ -131,12 +132,12 @@ class RegistryTest(unittest.TestCase):
|
|||||||
def test_moderator_notification_enabled(self):
|
def test_moderator_notification_enabled(self):
|
||||||
# Check show_commenter_image record
|
# Check show_commenter_image record
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'moderator_notification_enabled' in IDiscussionSettings,
|
"moderator_notification_enabled" in IDiscussionSettings,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.registry[
|
self.registry[
|
||||||
'plone.app.discussion.interfaces.' +
|
"plone.app.discussion.interfaces."
|
||||||
'IDiscussionSettings.moderator_notification_enabled'
|
+ "IDiscussionSettings.moderator_notification_enabled"
|
||||||
],
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
@ -156,8 +157,8 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
self.settings = registry.forInterface(IDiscussionSettings, check=False)
|
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
|
# By default the comment_one_state_workflow without moderation is
|
||||||
# enabled
|
# enabled
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
('comment_one_state_workflow',),
|
("comment_one_state_workflow",),
|
||||||
self.portal.portal_workflow.getChainForPortalType(
|
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
|
# Make sure the comment_review_workflow with moderation enabled is
|
||||||
# enabled
|
# enabled
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
('comment_review_workflow',),
|
("comment_review_workflow",),
|
||||||
self.portal.portal_workflow.getChainForPortalType(
|
self.portal.portal_workflow.getChainForPortalType(
|
||||||
'Discussion Item',
|
"Discussion Item",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# And back
|
# And back
|
||||||
self.settings.moderation_enabled = False
|
self.settings.moderation_enabled = False
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
('comment_one_state_workflow',),
|
("comment_one_state_workflow",),
|
||||||
self.portal.portal_workflow.getChainForPortalType(
|
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
|
# Enable the 'comment_review_workflow' with moderation enabled
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow',),
|
("comment_review_workflow",),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure the moderation_enabled settings has changed
|
# Make sure the moderation_enabled settings has changed
|
||||||
@ -214,15 +215,15 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Enable the 'comment_review_workflow' with moderation enabled
|
# Enable the 'comment_review_workflow' with moderation enabled
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_one_state_workflow',),
|
("comment_one_state_workflow",),
|
||||||
)
|
)
|
||||||
self.settings.moderation_enabled = True
|
self.settings.moderation_enabled = True
|
||||||
|
|
||||||
# Enable a 'custom' discussion workflow
|
# Enable a 'custom' discussion workflow
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('intranet_workflow',),
|
("intranet_workflow",),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setting has not changed. A Custom workflow disables the
|
# Setting has not changed. A Custom workflow disables the
|
||||||
|
@ -27,6 +27,7 @@ import unittest
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from plone.dexterity.interfaces import IDexterityContent
|
from plone.dexterity.interfaces import IDexterityContent
|
||||||
|
|
||||||
DEXTERITY = True
|
DEXTERITY = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DEXTERITY = False
|
DEXTERITY = False
|
||||||
@ -37,15 +38,14 @@ class ConversationTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
interface.alsoProvides(
|
interface.alsoProvides(self.portal.REQUEST, interfaces.IDiscussionLayer)
|
||||||
self.portal.REQUEST, interfaces.IDiscussionLayer)
|
|
||||||
|
|
||||||
self.typetool = self.portal.portal_types
|
self.typetool = self.portal.portal_types
|
||||||
self.portal_discussion = getToolByName(
|
self.portal_discussion = getToolByName(
|
||||||
self.portal,
|
self.portal,
|
||||||
'portal_discussion',
|
"portal_discussion",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
# Allow discussion
|
# Allow discussion
|
||||||
@ -54,7 +54,7 @@ class ConversationTest(unittest.TestCase):
|
|||||||
settings.globally_enabled = True
|
settings.globally_enabled = True
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.portal.doc1, 'publish')
|
workflow.doActionFor(self.portal.doc1, "publish")
|
||||||
|
|
||||||
def test_add_comment(self):
|
def test_add_comment(self):
|
||||||
# Create a conversation. In this case we doesn't assign it to an
|
# 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
|
# Add a comment. Note: in real life, we always create comments via the
|
||||||
# factory to allow different factories to be swapped in
|
# factory to allow different factories to be swapped in
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
new_id = conversation.addComment(comment)
|
new_id = conversation.addComment(comment)
|
||||||
|
|
||||||
@ -81,20 +81,19 @@ class ConversationTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(tuple(conversation.getThreads())), 1)
|
self.assertEqual(len(tuple(conversation.getThreads())), 1)
|
||||||
self.assertEqual(conversation.total_comments(), 1)
|
self.assertEqual(conversation.total_comments(), 1)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date - datetime.utcnow() <
|
conversation.last_comment_date - datetime.utcnow() < timedelta(seconds=1),
|
||||||
timedelta(seconds=1),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_private_comment(self):
|
def test_private_comment(self):
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.author_username = 'nobody'
|
comment.author_username = "nobody"
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
comment.manage_permission('View', roles=tuple())
|
comment.manage_permission("View", roles=tuple())
|
||||||
self.assertEqual(0, conversation.total_comments())
|
self.assertEqual(0, conversation.total_comments())
|
||||||
self.assertEqual(None, conversation.last_comment_date)
|
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))
|
self.assertEqual([], list(conversation.public_commentators))
|
||||||
|
|
||||||
def test_delete_comment(self):
|
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
|
# Add a comment. Note: in real life, we always create comments via the
|
||||||
# factory to allow different factories to be swapped in
|
# factory to allow different factories to be swapped in
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
new_id = conversation.addComment(comment)
|
new_id = conversation.addComment(comment)
|
||||||
|
|
||||||
@ -141,23 +140,23 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# +- Comment 2_1
|
# +- Comment 2_1
|
||||||
|
|
||||||
# Create all comments
|
# Create all comments
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1 = createObject('plone.Comment')
|
comment1_1 = createObject("plone.Comment")
|
||||||
comment1_1.text = 'Comment text'
|
comment1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1_1 = createObject('plone.Comment')
|
comment1_1_1 = createObject("plone.Comment")
|
||||||
comment1_1_1.text = 'Comment text'
|
comment1_1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_2 = createObject('plone.Comment')
|
comment1_2 = createObject("plone.Comment")
|
||||||
comment1_2.text = 'Comment text'
|
comment1_2.text = "Comment text"
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
comment2_1 = createObject('plone.Comment')
|
comment2_1 = createObject("plone.Comment")
|
||||||
comment2_1.text = 'Comment text'
|
comment2_1.text = "Comment text"
|
||||||
|
|
||||||
# Create the nested comment structure
|
# Create the nested comment structure
|
||||||
new_id_1 = conversation.addComment(comment1)
|
new_id_1 = conversation.addComment(comment1)
|
||||||
@ -177,21 +176,24 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
del conversation[new_id_1]
|
del conversation[new_id_1]
|
||||||
|
|
||||||
self.assertEqual([
|
self.assertEqual(
|
||||||
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
[
|
||||||
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
{"comment": comment2, "depth": 0, "id": new_id_2},
|
||||||
], list(conversation.getThreads()))
|
{"comment": comment2_1, "depth": 1, "id": new_id_2_1},
|
||||||
|
],
|
||||||
|
list(conversation.getThreads()),
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_comment_when_content_object_is_deleted(self):
|
def test_delete_comment_when_content_object_is_deleted(self):
|
||||||
# Make sure all comments of a content object are deleted when the
|
# Make sure all comments of a content object are deleted when the
|
||||||
# object itself is deleted.
|
# object itself is deleted.
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
|
|
||||||
# Delete the content object
|
# Delete the content object
|
||||||
self.portal.manage_delObjects(['doc1'])
|
self.portal.manage_delObjects(["doc1"])
|
||||||
|
|
||||||
# Make sure the comment has been deleted as well
|
# Make sure the comment has been deleted as well
|
||||||
self.assertEqual(len(list(conversation.getComments())), 0)
|
self.assertEqual(len(list(conversation.getComments())), 0)
|
||||||
@ -200,8 +202,8 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_comments_enabled_on_doc_in_subfolder(self):
|
def test_comments_enabled_on_doc_in_subfolder(self):
|
||||||
typetool = self.portal.portal_types
|
typetool = self.portal.portal_types
|
||||||
typetool.constructContent('Folder', self.portal, 'folder1')
|
typetool.constructContent("Folder", self.portal, "folder1")
|
||||||
typetool.constructContent('Document', self.portal.folder1, 'doc2')
|
typetool.constructContent("Document", self.portal.folder1, "doc2")
|
||||||
|
|
||||||
folder = self.portal.folder1
|
folder = self.portal.folder1
|
||||||
|
|
||||||
@ -211,13 +213,13 @@ class ConversationTest(unittest.TestCase):
|
|||||||
self.assertFalse(aq_base(folder).allow_discussion)
|
self.assertFalse(aq_base(folder).allow_discussion)
|
||||||
|
|
||||||
doc = self.portal.folder1.doc2
|
doc = self.portal.folder1.doc2
|
||||||
conversation = doc.restrictedTraverse('@@conversation_view')
|
conversation = doc.restrictedTraverse("@@conversation_view")
|
||||||
self.assertEqual(conversation.enabled(), False)
|
self.assertEqual(conversation.enabled(), False)
|
||||||
|
|
||||||
# We have to allow discussion on Document content type, since
|
# We have to allow discussion on Document content type, since
|
||||||
# otherwise allow_discussion will always return False
|
# otherwise allow_discussion will always return False
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'Document')
|
document_fti = getattr(portal_types, "Document")
|
||||||
document_fti.manage_changeProperties(allow_discussion=True)
|
document_fti.manage_changeProperties(allow_discussion=True)
|
||||||
|
|
||||||
self.assertEqual(conversation.enabled(), True)
|
self.assertEqual(conversation.enabled(), True)
|
||||||
@ -225,13 +227,12 @@ class ConversationTest(unittest.TestCase):
|
|||||||
def test_disable_commenting_globally(self):
|
def test_disable_commenting_globally(self):
|
||||||
|
|
||||||
# Create a conversation.
|
# Create a conversation.
|
||||||
conversation = self.portal.doc1.restrictedTraverse(
|
conversation = self.portal.doc1.restrictedTraverse("@@conversation_view")
|
||||||
'@@conversation_view')
|
|
||||||
|
|
||||||
# We have to allow discussion on Document content type, since
|
# We have to allow discussion on Document content type, since
|
||||||
# otherwise allow_discussion will always return False
|
# otherwise allow_discussion will always return False
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'Document')
|
document_fti = getattr(portal_types, "Document")
|
||||||
document_fti.manage_changeProperties(allow_discussion=True)
|
document_fti.manage_changeProperties(allow_discussion=True)
|
||||||
|
|
||||||
# Check if conversation is enabled now
|
# Check if conversation is enabled now
|
||||||
@ -251,14 +252,14 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_allow_discussion_for_news_items(self):
|
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
|
newsitem = self.portal.newsitem
|
||||||
conversation = newsitem.restrictedTraverse('@@conversation_view')
|
conversation = newsitem.restrictedTraverse("@@conversation_view")
|
||||||
|
|
||||||
# We have to allow discussion on Document content type, since
|
# We have to allow discussion on Document content type, since
|
||||||
# otherwise allow_discussion will always return False
|
# otherwise allow_discussion will always return False
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'News Item')
|
document_fti = getattr(portal_types, "News Item")
|
||||||
document_fti.manage_changeProperties(allow_discussion=True)
|
document_fti.manage_changeProperties(allow_discussion=True)
|
||||||
|
|
||||||
# Check if conversation is enabled now
|
# Check if conversation is enabled now
|
||||||
@ -280,23 +281,23 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Create a conversation.
|
# Create a conversation.
|
||||||
conversation = self.portal.doc1.restrictedTraverse(
|
conversation = self.portal.doc1.restrictedTraverse(
|
||||||
'@@conversation_view',
|
"@@conversation_view",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The Document content type is disabled by default
|
# The Document content type is disabled by default
|
||||||
self.assertEqual(conversation.enabled(), False)
|
self.assertEqual(conversation.enabled(), False)
|
||||||
|
|
||||||
# Allow discussion on Document content type
|
# Allow discussion on Document content type
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'Document')
|
document_fti = getattr(portal_types, "Document")
|
||||||
document_fti.manage_changeProperties(allow_discussion=True)
|
document_fti.manage_changeProperties(allow_discussion=True)
|
||||||
|
|
||||||
# Check if conversation is enabled now
|
# Check if conversation is enabled now
|
||||||
self.assertEqual(conversation.enabled(), True)
|
self.assertEqual(conversation.enabled(), True)
|
||||||
|
|
||||||
# Disallow discussion on Document content type
|
# Disallow discussion on Document content type
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'Document')
|
document_fti = getattr(portal_types, "Document")
|
||||||
document_fti.manage_changeProperties(allow_discussion=False)
|
document_fti.manage_changeProperties(allow_discussion=False)
|
||||||
|
|
||||||
# Check if conversation is enabled now
|
# Check if conversation is enabled now
|
||||||
@ -308,17 +309,17 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# plone.app.contenttypes does not have this restriction any longer.
|
# plone.app.contenttypes does not have this restriction any longer.
|
||||||
|
|
||||||
# Create a folder
|
# 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
|
# 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
|
# Allow discussion for the folder
|
||||||
self.portal.f1.allow_discussion = True
|
self.portal.f1.allow_discussion = True
|
||||||
|
|
||||||
# Allow discussion on Folder content type
|
# Allow discussion on Folder content type
|
||||||
portal_types = getToolByName(self.portal, 'portal_types')
|
portal_types = getToolByName(self.portal, "portal_types")
|
||||||
document_fti = getattr(portal_types, 'Folder')
|
document_fti = getattr(portal_types, "Folder")
|
||||||
document_fti.manage_changeProperties(allow_discussion=True)
|
document_fti.manage_changeProperties(allow_discussion=True)
|
||||||
|
|
||||||
self.assertTrue(conversation.enabled())
|
self.assertTrue(conversation.enabled())
|
||||||
@ -328,7 +329,7 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Create a conversation.
|
# Create a conversation.
|
||||||
conversation = self.portal.doc1.restrictedTraverse(
|
conversation = self.portal.doc1.restrictedTraverse(
|
||||||
'@@conversation_view',
|
"@@conversation_view",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Discussion is disallowed by default
|
# 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
|
# Add a comment. Note: in real life, we always create comments via the
|
||||||
# factory to allow different factories to be swapped in
|
# factory to allow different factories to be swapped in
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
new_id1 = conversation.addComment(comment1)
|
new_id1 = conversation.addComment(comment1)
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
new_id2 = conversation.addComment(comment2)
|
new_id2 = conversation.addComment(comment2)
|
||||||
|
|
||||||
@ -410,14 +411,14 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# comments via the factory to allow different factories to be
|
# comments via the factory to allow different factories to be
|
||||||
# swapped in
|
# swapped in
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
comment3 = createObject('plone.Comment')
|
comment3 = createObject("plone.Comment")
|
||||||
comment3.text = 'Comment text'
|
comment3.text = "Comment text"
|
||||||
|
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
conversation.addComment(comment2)
|
conversation.addComment(comment2)
|
||||||
@ -439,49 +440,49 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# Note: in real life, we always create
|
# Note: in real life, we always create
|
||||||
# comments via the factory to allow different factories to be
|
# comments via the factory to allow different factories to be
|
||||||
# swapped in
|
# swapped in
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.author_username = 'Jim'
|
comment1.author_username = "Jim"
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.author_username = 'Joe'
|
comment2.author_username = "Joe"
|
||||||
conversation.addComment(comment2)
|
conversation.addComment(comment2)
|
||||||
|
|
||||||
comment3 = createObject('plone.Comment')
|
comment3 = createObject("plone.Comment")
|
||||||
comment3.text = 'Comment text'
|
comment3.text = "Comment text"
|
||||||
comment3.author_username = 'Jack'
|
comment3.author_username = "Jack"
|
||||||
new_comment3_id = conversation.addComment(comment3)
|
new_comment3_id = conversation.addComment(comment3)
|
||||||
|
|
||||||
comment4 = createObject('plone.Comment')
|
comment4 = createObject("plone.Comment")
|
||||||
comment4.text = 'Comment text'
|
comment4.text = "Comment text"
|
||||||
comment4.author_username = 'Jack'
|
comment4.author_username = "Jack"
|
||||||
new_comment4_id = conversation.addComment(comment4)
|
new_comment4_id = conversation.addComment(comment4)
|
||||||
|
|
||||||
# check if all commentators are in the commentators list
|
# check if all commentators are in the commentators list
|
||||||
self.assertEqual(conversation.total_comments(), 4)
|
self.assertEqual(conversation.total_comments(), 4)
|
||||||
self.assertTrue('Jim' in conversation.commentators)
|
self.assertTrue("Jim" in conversation.commentators)
|
||||||
self.assertTrue('Joe' in conversation.commentators)
|
self.assertTrue("Joe" in conversation.commentators)
|
||||||
self.assertTrue('Jack' in conversation.commentators)
|
self.assertTrue("Jack" in conversation.commentators)
|
||||||
|
|
||||||
# remove the comment from Jack
|
# remove the comment from Jack
|
||||||
del conversation[new_comment3_id]
|
del conversation[new_comment3_id]
|
||||||
|
|
||||||
# check if Jack is still in the commentators list (since
|
# check if Jack is still in the commentators list (since
|
||||||
# he had added two comments)
|
# he had added two comments)
|
||||||
self.assertTrue('Jim' in conversation.commentators)
|
self.assertTrue("Jim" in conversation.commentators)
|
||||||
self.assertTrue('Joe' in conversation.commentators)
|
self.assertTrue("Joe" in conversation.commentators)
|
||||||
self.assertTrue('Jack' in conversation.commentators)
|
self.assertTrue("Jack" in conversation.commentators)
|
||||||
self.assertEqual(conversation.total_comments(), 3)
|
self.assertEqual(conversation.total_comments(), 3)
|
||||||
|
|
||||||
# remove the second comment from Jack
|
# remove the second comment from Jack
|
||||||
del conversation[new_comment4_id]
|
del conversation[new_comment4_id]
|
||||||
|
|
||||||
# check if Jack has been removed from the commentators list
|
# check if Jack has been removed from the commentators list
|
||||||
self.assertTrue('Jim' in conversation.commentators)
|
self.assertTrue("Jim" in conversation.commentators)
|
||||||
self.assertTrue('Joe' in conversation.commentators)
|
self.assertTrue("Joe" in conversation.commentators)
|
||||||
self.assertFalse('Jack' in conversation.commentators)
|
self.assertFalse("Jack" in conversation.commentators)
|
||||||
self.assertEqual(conversation.total_comments(), 2)
|
self.assertEqual(conversation.total_comments(), 2)
|
||||||
|
|
||||||
def test_last_comment_date(self):
|
def test_last_comment_date(self):
|
||||||
@ -496,29 +497,29 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# Note: in real life, we always create
|
# Note: in real life, we always create
|
||||||
# comments via the factory to allow different factories to be
|
# comments via the factory to allow different factories to be
|
||||||
# swapped in
|
# swapped in
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.creation_date = datetime.utcnow() - timedelta(4)
|
comment1.creation_date = datetime.utcnow() - timedelta(4)
|
||||||
conversation.addComment(comment1)
|
conversation.addComment(comment1)
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.creation_date = datetime.utcnow() - timedelta(2)
|
comment2.creation_date = datetime.utcnow() - timedelta(2)
|
||||||
new_comment2_id = conversation.addComment(comment2)
|
new_comment2_id = conversation.addComment(comment2)
|
||||||
|
|
||||||
comment3 = createObject('plone.Comment')
|
comment3 = createObject("plone.Comment")
|
||||||
comment3.text = 'Comment text'
|
comment3.text = "Comment text"
|
||||||
comment3.creation_date = datetime.utcnow() - timedelta(1)
|
comment3.creation_date = datetime.utcnow() - timedelta(1)
|
||||||
new_comment3_id = conversation.addComment(comment3)
|
new_comment3_id = conversation.addComment(comment3)
|
||||||
|
|
||||||
# check if the latest comment is exactly one day old
|
# check if the latest comment is exactly one day old
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date < datetime.utcnow() -
|
conversation.last_comment_date
|
||||||
timedelta(hours=23, minutes=59, seconds=59),
|
< datetime.utcnow() - timedelta(hours=23, minutes=59, seconds=59),
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date >
|
conversation.last_comment_date
|
||||||
datetime.utcnow() - timedelta(days=1, seconds=1),
|
> datetime.utcnow() - timedelta(days=1, seconds=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove the latest comment
|
# remove the latest comment
|
||||||
@ -527,12 +528,12 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# check if the latest comment has been updated
|
# check if the latest comment has been updated
|
||||||
# the latest comment should be exactly two days old
|
# the latest comment should be exactly two days old
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date < datetime.utcnow() -
|
conversation.last_comment_date
|
||||||
timedelta(days=1, hours=23, minutes=59, seconds=59),
|
< datetime.utcnow() - timedelta(days=1, hours=23, minutes=59, seconds=59),
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date > datetime.utcnow() -
|
conversation.last_comment_date
|
||||||
timedelta(days=2, seconds=1),
|
> datetime.utcnow() - timedelta(days=2, seconds=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove the latest comment again
|
# remove the latest comment again
|
||||||
@ -541,12 +542,12 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# check if the latest comment has been updated
|
# check if the latest comment has been updated
|
||||||
# the latest comment should be exactly four days old
|
# the latest comment should be exactly four days old
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date < datetime.utcnow() -
|
conversation.last_comment_date
|
||||||
timedelta(days=3, hours=23, minutes=59, seconds=59),
|
< datetime.utcnow() - timedelta(days=3, hours=23, minutes=59, seconds=59),
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
conversation.last_comment_date > datetime.utcnow() -
|
conversation.last_comment_date
|
||||||
timedelta(days=4, seconds=2),
|
> datetime.utcnow() - timedelta(days=4, seconds=2),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_comments_full(self):
|
def test_get_comments_full(self):
|
||||||
@ -574,23 +575,23 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# +- Comment 2_1
|
# +- Comment 2_1
|
||||||
|
|
||||||
# Create all comments
|
# Create all comments
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1 = createObject('plone.Comment')
|
comment1_1 = createObject("plone.Comment")
|
||||||
comment1_1.text = 'Comment text'
|
comment1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1_1 = createObject('plone.Comment')
|
comment1_1_1 = createObject("plone.Comment")
|
||||||
comment1_1_1.text = 'Comment text'
|
comment1_1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_2 = createObject('plone.Comment')
|
comment1_2 = createObject("plone.Comment")
|
||||||
comment1_2.text = 'Comment text'
|
comment1_2.text = "Comment text"
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
comment2_1 = createObject('plone.Comment')
|
comment2_1 = createObject("plone.Comment")
|
||||||
comment2_1.text = 'Comment text'
|
comment2_1.text = "Comment text"
|
||||||
|
|
||||||
# Create the nested comment structure
|
# Create the nested comment structure
|
||||||
new_id_1 = conversation.addComment(comment1)
|
new_id_1 = conversation.addComment(comment1)
|
||||||
@ -610,14 +611,17 @@ class ConversationTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Get threads
|
# Get threads
|
||||||
|
|
||||||
self.assertEqual([
|
self.assertEqual(
|
||||||
{'comment': comment1, 'depth': 0, 'id': new_id_1},
|
[
|
||||||
{'comment': comment1_1, 'depth': 1, 'id': new_id_1_1},
|
{"comment": comment1, "depth": 0, "id": new_id_1},
|
||||||
{'comment': comment1_1_1, 'depth': 2, 'id': new_id_1_1_1},
|
{"comment": comment1_1, "depth": 1, "id": new_id_1_1},
|
||||||
{'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
|
{"comment": comment1_1_1, "depth": 2, "id": new_id_1_1_1},
|
||||||
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
{"comment": comment1_2, "depth": 1, "id": new_id_1_2},
|
||||||
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
{"comment": comment2, "depth": 0, "id": new_id_2},
|
||||||
], list(conversation.getThreads()))
|
{"comment": comment2_1, "depth": 1, "id": new_id_2_1},
|
||||||
|
],
|
||||||
|
list(conversation.getThreads()),
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_threads_batched(self):
|
def test_get_threads_batched(self):
|
||||||
# TODO: test start, size, root and depth arguments to getThreads() # noqa T000
|
# 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
|
# make sure we can traverse to conversations and get a URL and path
|
||||||
|
|
||||||
conversation = self.portal.doc1.restrictedTraverse(
|
conversation = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default',
|
"++conversation++default",
|
||||||
)
|
)
|
||||||
self.assertTrue(IConversation.providedBy(conversation))
|
self.assertTrue(IConversation.providedBy(conversation))
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
('', 'plone', 'doc1', '++conversation++default'),
|
("", "plone", "doc1", "++conversation++default"),
|
||||||
conversation.getPhysicalPath(),
|
conversation.getPhysicalPath(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'http://nohost/plone/doc1/++conversation++default',
|
"http://nohost/plone/doc1/++conversation++default",
|
||||||
conversation.absolute_url(),
|
conversation.absolute_url(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -646,7 +650,7 @@ class ConversationTest(unittest.TestCase):
|
|||||||
# can't be converted to int
|
# can't be converted to int
|
||||||
|
|
||||||
conversation = self.portal.doc1.restrictedTraverse(
|
conversation = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/ThisCantBeRight',
|
"++conversation++default/ThisCantBeRight",
|
||||||
)
|
)
|
||||||
self.assertEqual(conversation, None)
|
self.assertEqual(conversation, None)
|
||||||
|
|
||||||
@ -660,17 +664,16 @@ class ConversationTest(unittest.TestCase):
|
|||||||
self.assertTrue(conversation.__parent__)
|
self.assertTrue(conversation.__parent__)
|
||||||
self.assertTrue(aq_parent(conversation))
|
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):
|
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):
|
def test_no_comment(self):
|
||||||
IConversation(self.portal.doc1)
|
IConversation(self.portal.doc1)
|
||||||
# Make sure no conversation has been created
|
# Make sure no conversation has been created
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'plone.app.discussion:conversation' not in
|
"plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1),
|
||||||
IAnnotations(self.portal.doc1),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -679,8 +682,8 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
interface.alsoProvides(
|
interface.alsoProvides(
|
||||||
self.portal.REQUEST,
|
self.portal.REQUEST,
|
||||||
interfaces.IDiscussionLayer,
|
interfaces.IDiscussionLayer,
|
||||||
@ -693,7 +696,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _makeOne(self, *args, **kw):
|
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):
|
def _globally_enable_discussion(self, value):
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -701,7 +704,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
|
|||||||
settings.globally_enabled = value
|
settings.globally_enabled = value
|
||||||
|
|
||||||
def _enable_discussion_on_portal_type(self, portal_type, allow_discussion):
|
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 = getattr(portal_types, portal_type)
|
||||||
document_fti.manage_changeProperties(allow_discussion=allow_discussion)
|
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):
|
def test_conversation_needs_to_be_enabled_globally_and_for_type(self):
|
||||||
if DEXTERITY:
|
if DEXTERITY:
|
||||||
self._globally_enable_discussion(True)
|
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)
|
conversation = self._makeOne(self.portal.doc1)
|
||||||
self.assertTrue(conversation.enabled())
|
self.assertTrue(conversation.enabled())
|
||||||
|
|
||||||
def test_disable_discussion(self):
|
def test_disable_discussion(self):
|
||||||
if DEXTERITY:
|
if DEXTERITY:
|
||||||
self._globally_enable_discussion(True)
|
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
|
self.portal.doc1.allow_discussion = False
|
||||||
conversation = self._makeOne(self.portal.doc1)
|
conversation = self._makeOne(self.portal.doc1)
|
||||||
self.assertFalse(conversation.enabled())
|
self.assertFalse(conversation.enabled())
|
||||||
@ -734,7 +737,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase):
|
|||||||
def test_enable_discussion(self):
|
def test_enable_discussion(self):
|
||||||
if DEXTERITY:
|
if DEXTERITY:
|
||||||
self._globally_enable_discussion(True)
|
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
|
self.portal.doc1.allow_discussion = True
|
||||||
conversation = self._makeOne(self.portal.doc1)
|
conversation = self._makeOne(self.portal.doc1)
|
||||||
self.assertTrue(conversation.enabled())
|
self.assertTrue(conversation.enabled())
|
||||||
@ -747,11 +750,11 @@ class RepliesTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.portal.doc1, 'publish')
|
workflow.doActionFor(self.portal.doc1, "publish")
|
||||||
|
|
||||||
def test_add_comment(self):
|
def test_add_comment(self):
|
||||||
# Add comments to a ConversationReplies adapter
|
# Add comments to a ConversationReplies adapter
|
||||||
@ -762,8 +765,8 @@ class RepliesTest(unittest.TestCase):
|
|||||||
|
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
|
|
||||||
@ -789,8 +792,8 @@ class RepliesTest(unittest.TestCase):
|
|||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
# Add a comment.
|
# Add a comment.
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
|
|
||||||
@ -828,39 +831,39 @@ class RepliesTest(unittest.TestCase):
|
|||||||
# +- Comment 2_1
|
# +- Comment 2_1
|
||||||
|
|
||||||
# Create all comments
|
# Create all comments
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1 = createObject('plone.Comment')
|
comment1_1 = createObject("plone.Comment")
|
||||||
comment1_1.text = 'Comment text'
|
comment1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_1_1 = createObject('plone.Comment')
|
comment1_1_1 = createObject("plone.Comment")
|
||||||
comment1_1_1.text = 'Comment text'
|
comment1_1_1.text = "Comment text"
|
||||||
|
|
||||||
comment1_2 = createObject('plone.Comment')
|
comment1_2 = createObject("plone.Comment")
|
||||||
comment1_2.text = 'Comment text'
|
comment1_2.text = "Comment text"
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
|
|
||||||
comment2_1 = createObject('plone.Comment')
|
comment2_1 = createObject("plone.Comment")
|
||||||
comment2_1.text = 'Comment text'
|
comment2_1.text = "Comment text"
|
||||||
|
|
||||||
# Create the nested comment structure
|
# Create the nested comment structure
|
||||||
new_id_1 = replies.addComment(comment1)
|
new_id_1 = replies.addComment(comment1)
|
||||||
comment1 = self.portal.doc1.restrictedTraverse(
|
comment1 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id_1),
|
"++conversation++default/{0}".format(new_id_1),
|
||||||
)
|
)
|
||||||
replies_to_comment1 = IReplies(comment1)
|
replies_to_comment1 = IReplies(comment1)
|
||||||
new_id_2 = replies.addComment(comment2)
|
new_id_2 = replies.addComment(comment2)
|
||||||
comment2 = self.portal.doc1.restrictedTraverse(
|
comment2 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id_2),
|
"++conversation++default/{0}".format(new_id_2),
|
||||||
)
|
)
|
||||||
replies_to_comment2 = IReplies(comment2)
|
replies_to_comment2 = IReplies(comment2)
|
||||||
|
|
||||||
new_id_1_1 = replies_to_comment1.addComment(comment1_1)
|
new_id_1_1 = replies_to_comment1.addComment(comment1_1)
|
||||||
comment1_1 = self.portal.doc1.restrictedTraverse(
|
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 = IReplies(comment1_1)
|
||||||
replies_to_comment1_1.addComment(comment1_1_1)
|
replies_to_comment1_1.addComment(comment1_1_1)
|
||||||
|
@ -21,8 +21,8 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
class EventsRegistry(object):
|
class EventsRegistry(object):
|
||||||
""" Fake registry to be used while testing discussion events
|
"""Fake registry to be used while testing discussion events"""
|
||||||
"""
|
|
||||||
commentAdded = False
|
commentAdded = False
|
||||||
commentModified = False
|
commentModified = False
|
||||||
commentRemoved = False
|
commentRemoved = False
|
||||||
@ -30,6 +30,7 @@ class EventsRegistry(object):
|
|||||||
replyModified = False
|
replyModified = False
|
||||||
replyRemoved = False
|
replyRemoved = False
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Fake event handlers
|
# Fake event handlers
|
||||||
#
|
#
|
||||||
@ -65,19 +66,19 @@ def reply_removed(doc, evt):
|
|||||||
|
|
||||||
|
|
||||||
class CommentEventsTest(unittest.TestCase):
|
class CommentEventsTest(unittest.TestCase):
|
||||||
""" Test custom comments events
|
"""Test custom comments events"""
|
||||||
"""
|
|
||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
# Setup sandbox
|
# Setup sandbox
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
self.registry = EventsRegistry
|
self.registry = EventsRegistry
|
||||||
|
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.document = self.portal['doc1']
|
self.document = self.portal["doc1"]
|
||||||
|
|
||||||
#
|
#
|
||||||
# Subscribers
|
# Subscribers
|
||||||
@ -106,23 +107,23 @@ class CommentEventsTest(unittest.TestCase):
|
|||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
"""
|
"""
|
||||||
zcml.load_config('configure.zcml', Products.Five)
|
zcml.load_config("configure.zcml", Products.Five)
|
||||||
zcml.load_string(configure)
|
zcml.load_string(configure)
|
||||||
|
|
||||||
def test_addEvent(self):
|
def test_addEvent(self):
|
||||||
self.assertFalse(self.registry.commentAdded)
|
self.assertFalse(self.registry.commentAdded)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
conversation.addComment(comment)
|
conversation.addComment(comment)
|
||||||
self.assertTrue(self.registry.commentAdded)
|
self.assertTrue(self.registry.commentAdded)
|
||||||
|
|
||||||
def test_modifyEvent(self):
|
def test_modifyEvent(self):
|
||||||
self.assertFalse(self.registry.commentModified)
|
self.assertFalse(self.registry.commentModified)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
new_id = conversation.addComment(comment)
|
new_id = conversation.addComment(comment)
|
||||||
comment = self.document.restrictedTraverse(
|
comment = self.document.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id),
|
"++conversation++default/{0}".format(new_id),
|
||||||
)
|
)
|
||||||
comment.text = "foo"
|
comment.text = "foo"
|
||||||
notify(ObjectModifiedEvent(comment))
|
notify(ObjectModifiedEvent(comment))
|
||||||
@ -130,7 +131,7 @@ class CommentEventsTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_removedEvent(self):
|
def test_removedEvent(self):
|
||||||
self.assertFalse(self.registry.commentRemoved)
|
self.assertFalse(self.registry.commentRemoved)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
cid = conversation.addComment(comment)
|
cid = conversation.addComment(comment)
|
||||||
del conversation[cid]
|
del conversation[cid]
|
||||||
@ -138,17 +139,17 @@ class CommentEventsTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class RepliesEventsTest(unittest.TestCase):
|
class RepliesEventsTest(unittest.TestCase):
|
||||||
""" Test custom replies events
|
"""Test custom replies events"""
|
||||||
"""
|
|
||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
self.registry = EventsRegistry
|
self.registry = EventsRegistry
|
||||||
|
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.document = self.portal['doc1']
|
self.document = self.portal["doc1"]
|
||||||
|
|
||||||
#
|
#
|
||||||
# Subscribers
|
# Subscribers
|
||||||
@ -177,7 +178,7 @@ class RepliesEventsTest(unittest.TestCase):
|
|||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
"""
|
"""
|
||||||
zcml.load_config('configure.zcml', Products.Five)
|
zcml.load_config("configure.zcml", Products.Five)
|
||||||
zcml.load_string(configure)
|
zcml.load_string(configure)
|
||||||
|
|
||||||
def test_addEvent(self):
|
def test_addEvent(self):
|
||||||
@ -186,15 +187,15 @@ class RepliesEventsTest(unittest.TestCase):
|
|||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
comment = self.document.restrictedTraverse(
|
comment = self.document.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id),
|
"++conversation++default/{0}".format(new_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
|
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
replies.addComment(re_comment)
|
replies.addComment(re_comment)
|
||||||
@ -206,14 +207,14 @@ class RepliesEventsTest(unittest.TestCase):
|
|||||||
|
|
||||||
conversation = IConversation(self.document)
|
conversation = IConversation(self.document)
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment_id = replies.addComment(comment)
|
comment_id = replies.addComment(comment)
|
||||||
comment = self.document.restrictedTraverse(
|
comment = self.document.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(comment_id),
|
"++conversation++default/{0}".format(comment_id),
|
||||||
)
|
)
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
new_id = replies.addComment(re_comment)
|
new_id = replies.addComment(re_comment)
|
||||||
reply = replies[new_id]
|
reply = replies[new_id]
|
||||||
@ -227,15 +228,15 @@ class RepliesEventsTest(unittest.TestCase):
|
|||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
replies = IReplies(conversation)
|
replies = IReplies(conversation)
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
new_id = replies.addComment(comment)
|
new_id = replies.addComment(comment)
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
comment = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id),
|
"++conversation++default/{0}".format(new_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
re_comment = createObject('plone.Comment')
|
re_comment = createObject("plone.Comment")
|
||||||
re_comment.text = 'Comment text'
|
re_comment.text = "Comment text"
|
||||||
replies = IReplies(comment)
|
replies = IReplies(comment)
|
||||||
new_re_id = replies.addComment(re_comment)
|
new_re_id = replies.addComment(re_comment)
|
||||||
|
|
||||||
|
@ -12,29 +12,29 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
optionflags = (
|
optionflags = (
|
||||||
doctest.ELLIPSIS |
|
doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE
|
||||||
doctest.NORMALIZE_WHITESPACE |
|
|
||||||
doctest.REPORT_ONLY_FIRST_FAILURE
|
|
||||||
)
|
)
|
||||||
normal_testfiles = [
|
normal_testfiles = [
|
||||||
'functional_test_comments.txt',
|
"functional_test_comments.txt",
|
||||||
'functional_test_comment_review_workflow.txt',
|
"functional_test_comment_review_workflow.txt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
suite.addTests([
|
suite.addTests(
|
||||||
|
[
|
||||||
layered(
|
layered(
|
||||||
doctest.DocFileSuite(
|
doctest.DocFileSuite(
|
||||||
test,
|
test,
|
||||||
optionflags=optionflags,
|
optionflags=optionflags,
|
||||||
globs={
|
globs={
|
||||||
'pprint': pprint.pprint,
|
"pprint": pprint.pprint,
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
layer=PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING,
|
layer=PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING,
|
||||||
)
|
)
|
||||||
for test in normal_testfiles
|
for test in normal_testfiles
|
||||||
])
|
]
|
||||||
|
)
|
||||||
return suite
|
return suite
|
||||||
|
@ -28,41 +28,40 @@ sed diam voluptua. At [...]"""
|
|||||||
|
|
||||||
|
|
||||||
class ConversationIndexersTest(unittest.TestCase):
|
class ConversationIndexersTest(unittest.TestCase):
|
||||||
"""Conversation Indexer Tests
|
"""Conversation Indexer Tests"""
|
||||||
"""
|
|
||||||
|
|
||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.portal.doc1, 'publish')
|
workflow.doActionFor(self.portal.doc1, "publish")
|
||||||
|
|
||||||
# Create a conversation.
|
# Create a conversation.
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.text = 'Comment Text'
|
comment1.text = "Comment Text"
|
||||||
comment1.creator = 'jim'
|
comment1.creator = "jim"
|
||||||
comment1.author_username = 'Jim'
|
comment1.author_username = "Jim"
|
||||||
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
||||||
comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12)
|
comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12)
|
||||||
self.new_id1 = conversation.addComment(comment1)
|
self.new_id1 = conversation.addComment(comment1)
|
||||||
|
|
||||||
comment2 = createObject('plone.Comment')
|
comment2 = createObject("plone.Comment")
|
||||||
comment2.text = 'Comment Text'
|
comment2.text = "Comment Text"
|
||||||
comment2.creator = 'emma'
|
comment2.creator = "emma"
|
||||||
comment2.author_username = 'Emma'
|
comment2.author_username = "Emma"
|
||||||
comment2.creation_date = datetime(2007, 12, 13, 4, 18, 12)
|
comment2.creation_date = datetime(2007, 12, 13, 4, 18, 12)
|
||||||
comment2.modification_date = datetime(2007, 12, 13, 4, 18, 12)
|
comment2.modification_date = datetime(2007, 12, 13, 4, 18, 12)
|
||||||
self.new_id2 = conversation.addComment(comment2)
|
self.new_id2 = conversation.addComment(comment2)
|
||||||
|
|
||||||
comment3 = createObject('plone.Comment')
|
comment3 = createObject("plone.Comment")
|
||||||
comment3.text = 'Comment Text'
|
comment3.text = "Comment Text"
|
||||||
comment3.creator = 'lukas'
|
comment3.creator = "lukas"
|
||||||
comment3.author_username = 'Lukas'
|
comment3.author_username = "Lukas"
|
||||||
comment3.creation_date = datetime(2009, 4, 12, 11, 12, 12)
|
comment3.creation_date = datetime(2009, 4, 12, 11, 12, 12)
|
||||||
comment3.modification_date = datetime(2009, 4, 12, 11, 12, 12)
|
comment3.modification_date = datetime(2009, 4, 12, 11, 12, 12)
|
||||||
self.new_id3 = conversation.addComment(comment3)
|
self.new_id3 = conversation.addComment(comment3)
|
||||||
@ -70,10 +69,12 @@ class ConversationIndexersTest(unittest.TestCase):
|
|||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
|
|
||||||
def test_conversation_total_comments(self):
|
def test_conversation_total_comments(self):
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(
|
||||||
|
isinstance(
|
||||||
catalog.total_comments,
|
catalog.total_comments,
|
||||||
DelegatingIndexerFactory,
|
DelegatingIndexerFactory,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 3)
|
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 3)
|
||||||
del self.conversation[self.new_id1]
|
del self.conversation[self.new_id1]
|
||||||
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 2)
|
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)
|
self.assertEqual(catalog.total_comments(self.portal.doc1)(), 0)
|
||||||
|
|
||||||
def test_conversation_last_comment_date(self):
|
def test_conversation_last_comment_date(self):
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(
|
||||||
|
isinstance(
|
||||||
catalog.last_comment_date,
|
catalog.last_comment_date,
|
||||||
DelegatingIndexerFactory,
|
DelegatingIndexerFactory,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.last_comment_date(self.portal.doc1)(),
|
catalog.last_comment_date(self.portal.doc1)(),
|
||||||
datetime(2009, 4, 12, 11, 12, 12),
|
datetime(2009, 4, 12, 11, 12, 12),
|
||||||
@ -112,8 +115,8 @@ class CommentIndexersTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
|
|
||||||
# Create a conversation. In this case we doesn't assign it to an
|
# Create a conversation. In this case we doesn't assign it to an
|
||||||
# object, as we just want to check the Conversation object API.
|
# 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
|
# Add a comment. Note: in real life, we always create comments via the
|
||||||
# factory to allow different factories to be swapped in
|
# factory to allow different factories to be swapped in
|
||||||
|
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Lorem ipsum dolor sit amet.'
|
comment.text = "Lorem ipsum dolor sit amet."
|
||||||
comment.creator = 'jim'
|
comment.creator = "jim"
|
||||||
comment.author_name = 'Jim'
|
comment.author_name = "Jim"
|
||||||
comment.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
comment.creation_date = datetime(2006, 9, 17, 14, 18, 12)
|
||||||
comment.modification_date = datetime(2008, 3, 12, 7, 32, 52)
|
comment.modification_date = datetime(2008, 3, 12, 7, 32, 52)
|
||||||
|
|
||||||
@ -134,60 +137,61 @@ class CommentIndexersTest(unittest.TestCase):
|
|||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
|
|
||||||
def test_title(self):
|
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))
|
self.assertTrue(isinstance(catalog.title, DelegatingIndexerFactory))
|
||||||
|
|
||||||
def test_description(self):
|
def test_description(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.description(self.comment)(),
|
catalog.description(self.comment)(),
|
||||||
'Lorem ipsum dolor sit amet.',
|
"Lorem ipsum dolor sit amet.",
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(isinstance(catalog.description, DelegatingIndexerFactory))
|
||||||
isinstance(catalog.description, DelegatingIndexerFactory))
|
|
||||||
|
|
||||||
def test_description_long(self):
|
def test_description_long(self):
|
||||||
# Create a 50 word comment and make sure the description returns
|
# Create a 50 word comment and make sure the description returns
|
||||||
# only the first 25 words
|
# only the first 25 words
|
||||||
comment_long = createObject('plone.Comment')
|
comment_long = createObject("plone.Comment")
|
||||||
comment_long.title = 'Long Comment'
|
comment_long.title = "Long Comment"
|
||||||
comment_long.text = LONG_TEXT
|
comment_long.text = LONG_TEXT
|
||||||
|
|
||||||
self.conversation.addComment(comment_long)
|
self.conversation.addComment(comment_long)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.description(comment_long)(),
|
catalog.description(comment_long)(),
|
||||||
LONG_TEXT_CUT.replace('\n', ' '),
|
LONG_TEXT_CUT.replace("\n", " "),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_dates(self):
|
def test_dates(self):
|
||||||
# Test if created, modified, effective etc. are set correctly
|
# Test if created, modified, effective etc. are set correctly
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.created(self.comment)(),
|
catalog.created(self.comment)(),
|
||||||
DateTime(2006, 9, 17, 14, 18, 12, 'GMT'),
|
DateTime(2006, 9, 17, 14, 18, 12, "GMT"),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.effective(self.comment)(),
|
catalog.effective(self.comment)(),
|
||||||
DateTime(2006, 9, 17, 14, 18, 12, 'GMT'),
|
DateTime(2006, 9, 17, 14, 18, 12, "GMT"),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.modified(self.comment)(),
|
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):
|
def test_searchable_text(self):
|
||||||
# Test if searchable text is a concatenation of title and comment text
|
# Test if searchable text is a concatenation of title and comment text
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
catalog.searchable_text(self.comment)(),
|
catalog.searchable_text(self.comment)(),
|
||||||
('Lorem ipsum dolor sit amet.'),
|
("Lorem ipsum dolor sit amet."),
|
||||||
)
|
)
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(
|
||||||
|
isinstance(
|
||||||
catalog.searchable_text,
|
catalog.searchable_text,
|
||||||
DelegatingIndexerFactory,
|
DelegatingIndexerFactory,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_creator(self):
|
def test_creator(self):
|
||||||
self.assertEqual(catalog.creator(self.comment)(), ('jim'))
|
self.assertEqual(catalog.creator(self.comment)(), ("jim"))
|
||||||
|
|
||||||
def test_in_response_to(self):
|
def test_in_response_to(self):
|
||||||
# make sure in_response_to returns the title or id of the content
|
# make sure in_response_to returns the title or id of the content
|
||||||
# object the comment was added to
|
# 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")
|
||||||
|
@ -23,59 +23,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = self.layer['app']
|
self.app = self.layer["app"]
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.wf = getToolByName(self.portal,
|
self.wf = getToolByName(self.portal, "portal_workflow", None)
|
||||||
'portal_workflow',
|
|
||||||
None)
|
|
||||||
self.context = self.portal
|
self.context = self.portal
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
'comment_review_workflow',
|
"comment_review_workflow",
|
||||||
)
|
)
|
||||||
self.wf_tool = self.portal.portal_workflow
|
self.wf_tool = self.portal.portal_workflow
|
||||||
# Add a conversation with three comments
|
# Add a conversation with three comments
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.title = 'Comment 1'
|
comment1.title = "Comment 1"
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.Creator = 'Jim'
|
comment1.Creator = "Jim"
|
||||||
new_id_1 = conversation.addComment(comment1)
|
new_id_1 = conversation.addComment(comment1)
|
||||||
self.comment1 = self.portal.doc1.restrictedTraverse(
|
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 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.Creator = 'Joe'
|
comment2.Creator = "Joe"
|
||||||
new_id_2 = conversation.addComment(comment2)
|
new_id_2 = conversation.addComment(comment2)
|
||||||
self.comment2 = self.portal.doc1.restrictedTraverse(
|
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 = createObject("plone.Comment")
|
||||||
comment3.title = 'Comment 3'
|
comment3.title = "Comment 3"
|
||||||
comment3.text = 'Comment text'
|
comment3.text = "Comment text"
|
||||||
comment3.Creator = 'Emma'
|
comment3.Creator = "Emma"
|
||||||
new_id_3 = conversation.addComment(comment3)
|
new_id_3 = conversation.addComment(comment3)
|
||||||
self.comment3 = self.portal.doc1.restrictedTraverse(
|
self.comment3 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id_3),
|
"++conversation++default/{0}".format(new_id_3),
|
||||||
)
|
)
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
|
|
||||||
def test_default_bulkaction(self):
|
def test_default_bulkaction(self):
|
||||||
# Make sure no error is raised when no bulk actions has been supplied
|
# Make sure no error is raised when no bulk actions has been supplied
|
||||||
self.request.set('form.select.BulkAction', '-1')
|
self.request.set("form.select.BulkAction", "-1")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
|
||||||
|
|
||||||
view = BulkActionsView(self.portal, self.request)
|
view = BulkActionsView(self.portal, self.request)
|
||||||
|
|
||||||
self.assertFalse(view())
|
self.assertFalse(view())
|
||||||
|
|
||||||
def test_publish(self):
|
def test_publish(self):
|
||||||
self.request.set('form.select.BulkAction', 'publish')
|
self.request.set("form.select.BulkAction", "publish")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
|
||||||
view = BulkActionsView(self.portal, self.request)
|
view = BulkActionsView(self.portal, self.request)
|
||||||
|
|
||||||
view()
|
view()
|
||||||
@ -83,16 +81,16 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
# Count published comments
|
# Count published comments
|
||||||
published_comments = 0
|
published_comments = 0
|
||||||
for r in self.conversation.getThreads():
|
for r in self.conversation.getThreads():
|
||||||
comment_obj = r['comment']
|
comment_obj = r["comment"]
|
||||||
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
|
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
|
||||||
if workflow_status == 'published':
|
if workflow_status == "published":
|
||||||
published_comments += 1
|
published_comments += 1
|
||||||
# Make sure the comment has been published
|
# Make sure the comment has been published
|
||||||
self.assertEqual(published_comments, 1)
|
self.assertEqual(published_comments, 1)
|
||||||
|
|
||||||
def test_mark_as_spam(self):
|
def test_mark_as_spam(self):
|
||||||
self.request.set('form.select.BulkAction', 'mark_as_spam')
|
self.request.set("form.select.BulkAction", "mark_as_spam")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
|
||||||
|
|
||||||
view = BulkActionsView(self.portal, self.request)
|
view = BulkActionsView(self.portal, self.request)
|
||||||
|
|
||||||
@ -101,9 +99,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
# Count spam comments
|
# Count spam comments
|
||||||
spam_comments = 0
|
spam_comments = 0
|
||||||
for r in self.conversation.getThreads():
|
for r in self.conversation.getThreads():
|
||||||
comment_obj = r['comment']
|
comment_obj = r["comment"]
|
||||||
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
|
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
|
||||||
if workflow_status == 'spam':
|
if workflow_status == "spam":
|
||||||
spam_comments += 1
|
spam_comments += 1
|
||||||
# Make sure the comment has been marked as spam
|
# Make sure the comment has been marked as spam
|
||||||
self.assertEqual(spam_comments, 1)
|
self.assertEqual(spam_comments, 1)
|
||||||
@ -112,9 +110,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
# Initially we have three comments
|
# Initially we have three comments
|
||||||
self.assertEqual(len(self.conversation.objectIds()), 3)
|
self.assertEqual(len(self.conversation.objectIds()), 3)
|
||||||
# Delete two comments with bulk actions
|
# Delete two comments with bulk actions
|
||||||
self.request.set('form.select.BulkAction', 'delete')
|
self.request.set("form.select.BulkAction", "delete")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath()),
|
self.request.set(
|
||||||
'/'.join(self.comment3.getPhysicalPath())])
|
"paths",
|
||||||
|
[
|
||||||
|
"/".join(self.comment1.getPhysicalPath()),
|
||||||
|
"/".join(self.comment3.getPhysicalPath()),
|
||||||
|
],
|
||||||
|
)
|
||||||
view = BulkActionsView(self.app, self.request)
|
view = BulkActionsView(self.app, self.request)
|
||||||
|
|
||||||
view()
|
view()
|
||||||
|
@ -23,21 +23,19 @@ class ModerationViewTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = self.layer['app']
|
self.app = self.layer["app"]
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal_discussion = getToolByName(self.portal,
|
self.portal_discussion = getToolByName(self.portal, "portal_discussion", None)
|
||||||
'portal_discussion',
|
self.membership_tool = getToolByName(self.portal, "portal_membership")
|
||||||
None)
|
|
||||||
self.membership_tool = getToolByName(self.portal,
|
|
||||||
'portal_membership')
|
|
||||||
self.memberdata = self.portal.portal_memberdata
|
self.memberdata = self.portal.portal_memberdata
|
||||||
request = self.app.REQUEST
|
request = self.app.REQUEST
|
||||||
context = getattr(self.portal, 'doc1')
|
context = getattr(self.portal, "doc1")
|
||||||
self.view = View(context, request)
|
self.view = View(context, request)
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',), 'comment_review_workflow')
|
("Discussion Item",), "comment_review_workflow"
|
||||||
|
)
|
||||||
self.wf_tool = self.portal.portal_workflow
|
self.wf_tool = self.portal.portal_workflow
|
||||||
|
|
||||||
def test_moderation_enabled(self):
|
def test_moderation_enabled(self):
|
||||||
@ -45,15 +43,17 @@ class ModerationViewTest(unittest.TestCase):
|
|||||||
workflow implements a 'pending' state.
|
workflow implements a 'pending' state.
|
||||||
"""
|
"""
|
||||||
# If workflow is not set, enabled must return False
|
# 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)
|
self.assertEqual(self.view.moderation_enabled(), False)
|
||||||
# The comment_one_state_workflow does not have a 'pending' state
|
# The comment_one_state_workflow does not have a 'pending' state
|
||||||
self.wf_tool.setChainForPortalTypes(('Discussion Item',),
|
self.wf_tool.setChainForPortalTypes(
|
||||||
('comment_one_state_workflow,'))
|
("Discussion Item",), ("comment_one_state_workflow,")
|
||||||
|
)
|
||||||
self.assertEqual(self.view.moderation_enabled(), False)
|
self.assertEqual(self.view.moderation_enabled(), False)
|
||||||
# The comment_review_workflow does have a 'pending' state
|
# The comment_review_workflow does have a 'pending' state
|
||||||
self.wf_tool.setChainForPortalTypes(('Discussion Item',),
|
self.wf_tool.setChainForPortalTypes(
|
||||||
('comment_review_workflow,'))
|
("Discussion Item",), ("comment_review_workflow,")
|
||||||
|
)
|
||||||
self.assertEqual(self.view.moderation_enabled(), True)
|
self.assertEqual(self.view.moderation_enabled(), True)
|
||||||
|
|
||||||
|
|
||||||
@ -62,59 +62,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = self.layer['app']
|
self.app = self.layer["app"]
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.wf = getToolByName(self.portal,
|
self.wf = getToolByName(self.portal, "portal_workflow", None)
|
||||||
'portal_workflow',
|
|
||||||
None)
|
|
||||||
self.context = self.portal
|
self.context = self.portal
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
'comment_review_workflow',
|
"comment_review_workflow",
|
||||||
)
|
)
|
||||||
self.wf_tool = self.portal.portal_workflow
|
self.wf_tool = self.portal.portal_workflow
|
||||||
# Add a conversation with three comments
|
# Add a conversation with three comments
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
comment1 = createObject('plone.Comment')
|
comment1 = createObject("plone.Comment")
|
||||||
comment1.title = 'Comment 1'
|
comment1.title = "Comment 1"
|
||||||
comment1.text = 'Comment text'
|
comment1.text = "Comment text"
|
||||||
comment1.Creator = 'Jim'
|
comment1.Creator = "Jim"
|
||||||
new_id_1 = conversation.addComment(comment1)
|
new_id_1 = conversation.addComment(comment1)
|
||||||
self.comment1 = self.portal.doc1.restrictedTraverse(
|
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 = createObject("plone.Comment")
|
||||||
comment2.title = 'Comment 2'
|
comment2.title = "Comment 2"
|
||||||
comment2.text = 'Comment text'
|
comment2.text = "Comment text"
|
||||||
comment2.Creator = 'Joe'
|
comment2.Creator = "Joe"
|
||||||
new_id_2 = conversation.addComment(comment2)
|
new_id_2 = conversation.addComment(comment2)
|
||||||
self.comment2 = self.portal.doc1.restrictedTraverse(
|
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 = createObject("plone.Comment")
|
||||||
comment3.title = 'Comment 3'
|
comment3.title = "Comment 3"
|
||||||
comment3.text = 'Comment text'
|
comment3.text = "Comment text"
|
||||||
comment3.Creator = 'Emma'
|
comment3.Creator = "Emma"
|
||||||
new_id_3 = conversation.addComment(comment3)
|
new_id_3 = conversation.addComment(comment3)
|
||||||
self.comment3 = self.portal.doc1.restrictedTraverse(
|
self.comment3 = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(new_id_3),
|
"++conversation++default/{0}".format(new_id_3),
|
||||||
)
|
)
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
|
|
||||||
def test_default_bulkaction(self):
|
def test_default_bulkaction(self):
|
||||||
# Make sure no error is raised when no bulk actions has been supplied
|
# Make sure no error is raised when no bulk actions has been supplied
|
||||||
self.request.set('form.select.BulkAction', '-1')
|
self.request.set("form.select.BulkAction", "-1")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
|
||||||
|
|
||||||
view = BulkActionsView(self.portal, self.request)
|
view = BulkActionsView(self.portal, self.request)
|
||||||
|
|
||||||
self.assertFalse(view())
|
self.assertFalse(view())
|
||||||
|
|
||||||
def test_publish(self):
|
def test_publish(self):
|
||||||
self.request.set('form.select.BulkAction', 'publish')
|
self.request.set("form.select.BulkAction", "publish")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())])
|
self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())])
|
||||||
view = BulkActionsView(self.portal, self.request)
|
view = BulkActionsView(self.portal, self.request)
|
||||||
|
|
||||||
view()
|
view()
|
||||||
@ -122,9 +120,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
# Count published comments
|
# Count published comments
|
||||||
published_comments = 0
|
published_comments = 0
|
||||||
for r in self.conversation.getThreads():
|
for r in self.conversation.getThreads():
|
||||||
comment_obj = r['comment']
|
comment_obj = r["comment"]
|
||||||
workflow_status = self.wf.getInfoFor(comment_obj, 'review_state')
|
workflow_status = self.wf.getInfoFor(comment_obj, "review_state")
|
||||||
if workflow_status == 'published':
|
if workflow_status == "published":
|
||||||
published_comments += 1
|
published_comments += 1
|
||||||
# Make sure the comment has been published
|
# Make sure the comment has been published
|
||||||
self.assertEqual(published_comments, 1)
|
self.assertEqual(published_comments, 1)
|
||||||
@ -133,9 +131,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase):
|
|||||||
# Initially we have three comments
|
# Initially we have three comments
|
||||||
self.assertEqual(len(self.conversation.objectIds()), 3)
|
self.assertEqual(len(self.conversation.objectIds()), 3)
|
||||||
# Delete two comments with bulk actions
|
# Delete two comments with bulk actions
|
||||||
self.request.set('form.select.BulkAction', 'delete')
|
self.request.set("form.select.BulkAction", "delete")
|
||||||
self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath()),
|
self.request.set(
|
||||||
'/'.join(self.comment3.getPhysicalPath())])
|
"paths",
|
||||||
|
[
|
||||||
|
"/".join(self.comment1.getPhysicalPath()),
|
||||||
|
"/".join(self.comment3.getPhysicalPath()),
|
||||||
|
],
|
||||||
|
)
|
||||||
view = BulkActionsView(self.app, self.request)
|
view = BulkActionsView(self.app, self.request)
|
||||||
|
|
||||||
view()
|
view()
|
||||||
@ -153,41 +156,41 @@ class RedirectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Update settings.
|
# Update settings.
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
self.request = self.layer['request']
|
self.request = self.layer["request"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
# applyProfile(self.portal, 'plone.app.discussion:default')
|
# applyProfile(self.portal, 'plone.app.discussion:default')
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings)
|
settings = registry.forInterface(IDiscussionSettings)
|
||||||
settings.globally_enabled = True
|
settings.globally_enabled = True
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow',),
|
("comment_review_workflow",),
|
||||||
)
|
)
|
||||||
# Create page plus comment.
|
# Create page plus comment.
|
||||||
self.portal.invokeFactory(
|
self.portal.invokeFactory(
|
||||||
id='page',
|
id="page",
|
||||||
title='Page 1',
|
title="Page 1",
|
||||||
type_name='Document',
|
type_name="Document",
|
||||||
)
|
)
|
||||||
self.page = self.portal.page
|
self.page = self.portal.page
|
||||||
self.conversation = IConversation(self.page)
|
self.conversation = IConversation(self.page)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
self.comment_id = self.conversation.addComment(comment)
|
self.comment_id = self.conversation.addComment(comment)
|
||||||
self.comment = list(self.conversation.getComments())[0]
|
self.comment = list(self.conversation.getComments())[0]
|
||||||
|
|
||||||
def test_regression(self):
|
def test_regression(self):
|
||||||
page_url = self.page.absolute_url()
|
page_url = self.page.absolute_url()
|
||||||
self.request['HTTP_REFERER'] = page_url
|
self.request["HTTP_REFERER"] = page_url
|
||||||
for Klass in (DeleteComment, CommentTransition):
|
for Klass in (DeleteComment, CommentTransition):
|
||||||
view = Klass(self.comment, self.request)
|
view = Klass(self.comment, self.request)
|
||||||
view.__parent__ = self.comment
|
view.__parent__ = self.comment
|
||||||
self.assertEqual(page_url, view())
|
self.assertEqual(page_url, view())
|
||||||
|
|
||||||
def test_valid_next_url(self):
|
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):
|
for Klass in (DeleteComment, CommentTransition):
|
||||||
view = Klass(self.comment, self.request)
|
view = Klass(self.comment, self.request)
|
||||||
view.__parent__ = self.comment
|
view.__parent__ = self.comment
|
||||||
self.assertNotEqual('http://attacker.com', view())
|
self.assertNotEqual("http://attacker.com", view())
|
||||||
|
@ -23,45 +23,46 @@ class TestUserNotificationUnit(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
# Set up a mock mailhost
|
# Set up a mock mailhost
|
||||||
self.portal._original_MailHost = self.portal.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 = getSiteManager(context=self.portal)
|
||||||
sm.unregisterUtility(provided=IMailHost)
|
sm.unregisterUtility(provided=IMailHost)
|
||||||
sm.registerUtility(mailhost, provided=IMailHost)
|
sm.registerUtility(mailhost, provided=IMailHost)
|
||||||
# We need to fake a valid mail setup
|
# We need to fake a valid mail setup
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
mail_settings.email_from_address = 'portal@plone.test'
|
mail_settings.email_from_address = "portal@plone.test"
|
||||||
self.mailhost = self.portal.MailHost
|
self.mailhost = self.portal.MailHost
|
||||||
# Enable user notification setting
|
# Enable user notification setting
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry['plone.app.discussion.interfaces.IDiscussionSettings' +
|
registry[
|
||||||
'.user_notification_enabled'] = True
|
"plone.app.discussion.interfaces.IDiscussionSettings"
|
||||||
|
+ ".user_notification_enabled"
|
||||||
|
] = True
|
||||||
# Archetypes content types store data as utf-8 encoded strings
|
# Archetypes content types store data as utf-8 encoded strings
|
||||||
# The missing u in front of a string is therefor not missing
|
# 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)
|
self.conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
def beforeTearDown(self):
|
def beforeTearDown(self):
|
||||||
self.portal.MailHost = self.portal._original_MailHost
|
self.portal.MailHost = self.portal._original_MailHost
|
||||||
sm = getSiteManager(context=self.portal)
|
sm = getSiteManager(context=self.portal)
|
||||||
sm.unregisterUtility(provided=IMailHost)
|
sm.unregisterUtility(provided=IMailHost)
|
||||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
sm.registerUtility(aq_base(self.portal._original_MailHost), provided=IMailHost)
|
||||||
provided=IMailHost)
|
|
||||||
|
|
||||||
def test_notify_user(self):
|
def test_notify_user(self):
|
||||||
# Add a comment with user notification enabled. Add another comment
|
# Add a comment with user notification enabled. Add another comment
|
||||||
# and make sure an email is send to the user of the first comment.
|
# and make sure an email is send to the user of the first comment.
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
comment_id = self.conversation.addComment(comment)
|
comment_id = self.conversation.addComment(comment)
|
||||||
|
|
||||||
@ -69,52 +70,46 @@ class TestUserNotificationUnit(unittest.TestCase):
|
|||||||
self.assertTrue(self.mailhost.messages[0])
|
self.assertTrue(self.mailhost.messages[0])
|
||||||
msg = self.mailhost.messages[0]
|
msg = self.mailhost.messages[0]
|
||||||
msg = msg.decode("utf-8")
|
msg = msg.decode("utf-8")
|
||||||
self.assertIn('To: john@plone.test', msg)
|
self.assertIn("To: john@plone.test", msg)
|
||||||
self.assertIn('From: portal@plone.test', msg)
|
self.assertIn("From: portal@plone.test", msg)
|
||||||
# We expect the headers to be properly header encoded (7-bit):
|
# We expect the headers to be properly header encoded (7-bit):
|
||||||
self.assertIn(
|
self.assertIn("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=", msg)
|
||||||
'Subject: =?utf-8?q?A_comment_has_been_posted=2E?=',
|
|
||||||
msg)
|
|
||||||
# The output should be encoded in a reasonable manner
|
# The output should be encoded in a reasonable manner
|
||||||
# (in this case quoted-printable).
|
# (in this case quoted-printable).
|
||||||
# Depending on which Python version and which Products.MailHost version,
|
# Depending on which Python version and which Products.MailHost version,
|
||||||
# you may get lines separated by '\n' or '\r\n' in here.
|
# you may get lines separated by '\n' or '\r\n' in here.
|
||||||
msg = msg.replace('\r\n', '\n')
|
msg = msg.replace("\r\n", "\n")
|
||||||
self.assertIn(
|
self.assertIn('A comment on "K=C3=B6lle Alaaf" has been posted here:', msg)
|
||||||
'A comment on "K=C3=B6lle Alaaf" has been posted here:',
|
self.assertIn("http://nohost/plone/d=\noc1/view#{0}".format(comment_id), msg)
|
||||||
msg)
|
self.assertIn("Comment text", msg)
|
||||||
self.assertIn(
|
self.assertNotIn("Approve comment", msg)
|
||||||
'http://nohost/plone/d=\noc1/view#{0}'.format(comment_id),
|
self.assertNotIn("Delete comment", msg)
|
||||||
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):
|
def test_do_not_notify_user_when_notification_is_disabled(self):
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry[
|
registry[
|
||||||
'plone.app.discussion.interfaces.IDiscussionSettings.' +
|
"plone.app.discussion.interfaces.IDiscussionSettings."
|
||||||
'user_notification_enabled'
|
+ "user_notification_enabled"
|
||||||
] = False
|
] = False
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
|
|
||||||
self.assertEqual(len(self.mailhost.messages), 0)
|
self.assertEqual(len(self.mailhost.messages), 0)
|
||||||
|
|
||||||
def test_do_not_notify_user_when_email_address_is_given(self):
|
def test_do_not_notify_user_when_email_address_is_given(self):
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
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
|
# Set sender mail address to none and make sure no email is send to
|
||||||
# the moderator.
|
# the moderator.
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
mail_settings.email_from_address = None
|
mail_settings.email_from_address = None
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
self.assertEqual(len(self.mailhost.messages), 0)
|
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
|
# When a user has added two comments in a conversation and has
|
||||||
# both times requested email notification, do not send him two
|
# both times requested email notification, do not send him two
|
||||||
# emails when another comment has been added.
|
# emails when another comment has been added.
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.user_notification = True
|
comment.user_notification = True
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
|
|
||||||
@ -165,48 +160,47 @@ class TestModeratorNotificationUnit(unittest.TestCase):
|
|||||||
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
# Set up a mock mailhost
|
# Set up a mock mailhost
|
||||||
self.portal._original_MailHost = self.portal.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 = getSiteManager(context=self.portal)
|
||||||
sm.unregisterUtility(provided=IMailHost)
|
sm.unregisterUtility(provided=IMailHost)
|
||||||
sm.registerUtility(mailhost, provided=IMailHost)
|
sm.registerUtility(mailhost, provided=IMailHost)
|
||||||
# We need to fake a valid mail setup
|
# We need to fake a valid mail setup
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
mail_settings.email_from_address = 'portal@plone.test'
|
mail_settings.email_from_address = "portal@plone.test"
|
||||||
self.mailhost = self.portal.MailHost
|
self.mailhost = self.portal.MailHost
|
||||||
# Enable comment moderation
|
# Enable comment moderation
|
||||||
self.portal.portal_types['Document'].allow_discussion = True
|
self.portal.portal_types["Document"].allow_discussion = True
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow',),
|
("comment_review_workflow",),
|
||||||
)
|
)
|
||||||
# Enable moderator notification setting
|
# Enable moderator notification setting
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry[
|
registry[
|
||||||
'plone.app.discussion.interfaces.IDiscussionSettings.' +
|
"plone.app.discussion.interfaces.IDiscussionSettings."
|
||||||
'moderator_notification_enabled'
|
+ "moderator_notification_enabled"
|
||||||
] = True
|
] = True
|
||||||
# Archetypes content types store data as utf-8 encoded strings
|
# Archetypes content types store data as utf-8 encoded strings
|
||||||
# The missing u in front of a string is therefor not missing
|
# 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)
|
self.conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
def beforeTearDown(self):
|
def beforeTearDown(self):
|
||||||
self.portal.MailHost = self.portal._original_MailHost
|
self.portal.MailHost = self.portal._original_MailHost
|
||||||
sm = getSiteManager(context=self.portal)
|
sm = getSiteManager(context=self.portal)
|
||||||
sm.unregisterUtility(provided=IMailHost)
|
sm.unregisterUtility(provided=IMailHost)
|
||||||
sm.registerUtility(aq_base(self.portal._original_MailHost),
|
sm.registerUtility(aq_base(self.portal._original_MailHost), provided=IMailHost)
|
||||||
provided=IMailHost)
|
|
||||||
|
|
||||||
def test_notify_moderator(self):
|
def test_notify_moderator(self):
|
||||||
"""Add a comment and make sure an email is send to the moderator."""
|
"""Add a comment and make sure an email is send to the moderator."""
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment.author_email = 'john@plone.test'
|
comment.author_email = "john@plone.test"
|
||||||
|
|
||||||
comment_id = self.conversation.addComment(comment)
|
comment_id = self.conversation.addComment(comment)
|
||||||
|
|
||||||
@ -214,54 +208,41 @@ class TestModeratorNotificationUnit(unittest.TestCase):
|
|||||||
self.assertTrue(self.mailhost.messages[0])
|
self.assertTrue(self.mailhost.messages[0])
|
||||||
msg = self.mailhost.messages[0]
|
msg = self.mailhost.messages[0]
|
||||||
msg = msg.decode("utf-8")
|
msg = msg.decode("utf-8")
|
||||||
self.assertTrue('To: portal@plone.test' in msg)
|
self.assertTrue("To: portal@plone.test" in msg)
|
||||||
self.assertTrue('From: portal@plone.test' in msg)
|
self.assertTrue("From: portal@plone.test" in msg)
|
||||||
# We expect the headers to be properly header encoded (7-bit):
|
# We expect the headers to be properly header encoded (7-bit):
|
||||||
self.assertTrue(
|
self.assertTrue("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=" in msg)
|
||||||
'Subject: =?utf-8?q?A_comment_has_been_posted=2E?='
|
|
||||||
in msg)
|
|
||||||
# The output should be encoded in a reasonable manner
|
# The output should be encoded in a reasonable manner
|
||||||
# (in this case quoted-printable):
|
# (in this case quoted-printable):
|
||||||
self.assertTrue(
|
self.assertTrue('A comment on "K=C3=B6lle Alaaf" has been posted' in msg)
|
||||||
'A comment on "K=C3=B6lle Alaaf" has been posted'
|
self.assertIn("http://nohost/plone/doc1/view#{0}".format(comment_id), msg)
|
||||||
in msg
|
self.assertIn(comment.author_email, msg)
|
||||||
)
|
self.assertIn(comment.text, 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):
|
def test_notify_moderator_specific_address(self):
|
||||||
# A moderator email address can be specified in the control panel.
|
# A moderator email address can be specified in the control panel.
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry['plone.app.discussion.interfaces.IDiscussionSettings' +
|
registry[
|
||||||
'.moderator_email'] = 'test@example.com'
|
"plone.app.discussion.interfaces.IDiscussionSettings" + ".moderator_email"
|
||||||
comment = createObject('plone.Comment')
|
] = "test@example.com"
|
||||||
comment.text = 'Comment text'
|
comment = createObject("plone.Comment")
|
||||||
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
|
|
||||||
self.assertEqual(len(self.mailhost.messages), 1)
|
self.assertEqual(len(self.mailhost.messages), 1)
|
||||||
msg = self.mailhost.messages[0]
|
msg = self.mailhost.messages[0]
|
||||||
msg = msg.decode("utf-8")
|
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):
|
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
|
# Set sender mail address to nonw and make sure no email is send to the
|
||||||
# moderator.
|
# moderator.
|
||||||
registry = getUtility(IRegistry)
|
registry = getUtility(IRegistry)
|
||||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||||
mail_settings.email_from_address = None
|
mail_settings.email_from_address = None
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
|
|
||||||
@ -271,10 +252,12 @@ class TestModeratorNotificationUnit(unittest.TestCase):
|
|||||||
# Disable moderator notification setting and make sure no email is send
|
# Disable moderator notification setting and make sure no email is send
|
||||||
# to the moderator.
|
# to the moderator.
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
registry['plone.app.discussion.interfaces.IDiscussionSettings.' +
|
registry[
|
||||||
'moderator_notification_enabled'] = False
|
"plone.app.discussion.interfaces.IDiscussionSettings."
|
||||||
comment = createObject('plone.Comment')
|
+ "moderator_notification_enabled"
|
||||||
comment.text = 'Comment text'
|
] = False
|
||||||
|
comment = createObject("plone.Comment")
|
||||||
|
comment.text = "Comment text"
|
||||||
|
|
||||||
self.conversation.addComment(comment)
|
self.conversation.addComment(comment)
|
||||||
|
|
||||||
|
@ -11,19 +11,21 @@ import unittest
|
|||||||
def test_suite():
|
def test_suite():
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
current_dir = os.path.abspath(os.path.dirname(__file__))
|
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 = [
|
robot_tests = [
|
||||||
os.path.join('robot', doc) for doc in
|
os.path.join("robot", doc)
|
||||||
os.listdir(robot_dir) if doc.endswith('.robot') and
|
for doc in os.listdir(robot_dir)
|
||||||
doc.startswith('test_')
|
if doc.endswith(".robot") and doc.startswith("test_")
|
||||||
]
|
]
|
||||||
for robot_test in robot_tests:
|
for robot_test in robot_tests:
|
||||||
robottestsuite = robotsuite.RobotTestSuite(robot_test)
|
robottestsuite = robotsuite.RobotTestSuite(robot_test)
|
||||||
robottestsuite.level = ROBOT_TEST_LEVEL
|
robottestsuite.level = ROBOT_TEST_LEVEL
|
||||||
suite.addTests([
|
suite.addTests(
|
||||||
|
[
|
||||||
layered(
|
layered(
|
||||||
robottestsuite,
|
robottestsuite,
|
||||||
layer=PLONE_APP_DISCUSSION_ROBOT_TESTING,
|
layer=PLONE_APP_DISCUSSION_ROBOT_TESTING,
|
||||||
),
|
),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
return suite
|
return suite
|
||||||
|
@ -21,48 +21,51 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowSetupTest(unittest.TestCase):
|
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
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal.invokeFactory('Folder', 'test-folder')
|
self.portal.invokeFactory("Folder", "test-folder")
|
||||||
self.folder = self.portal['test-folder']
|
self.folder = self.portal["test-folder"]
|
||||||
self.portal.portal_types['Document'].allow_discussion = True
|
self.portal.portal_types["Document"].allow_discussion = True
|
||||||
self.folder.invokeFactory('Document', 'doc1')
|
self.folder.invokeFactory("Document", "doc1")
|
||||||
self.doc = self.folder.doc1
|
self.doc = self.folder.doc1
|
||||||
|
|
||||||
def test_workflows_installed(self):
|
def test_workflows_installed(self):
|
||||||
"""Make sure both comment workflows have been installed properly.
|
"""Make sure both comment workflows have been installed properly."""
|
||||||
"""
|
self.assertTrue(
|
||||||
self.assertTrue('comment_one_state_workflow' in
|
"comment_one_state_workflow" in self.portal.portal_workflow.objectIds()
|
||||||
self.portal.portal_workflow.objectIds())
|
)
|
||||||
self.assertTrue('comment_review_workflow' in
|
self.assertTrue(
|
||||||
self.portal.portal_workflow.objectIds())
|
"comment_review_workflow" in self.portal.portal_workflow.objectIds()
|
||||||
|
)
|
||||||
|
|
||||||
def test_default_workflow(self):
|
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(
|
self.assertEqual(
|
||||||
('comment_one_state_workflow',),
|
("comment_one_state_workflow",),
|
||||||
self.portal.portal_workflow.getChainForPortalType(
|
self.portal.portal_workflow.getChainForPortalType(
|
||||||
'Discussion Item',
|
"Discussion Item",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_review_comments_permission(self):
|
def test_review_comments_permission(self):
|
||||||
# 'Review comments' in self.portal.permissionsOfRole('Admin')
|
# 'Review comments' in self.portal.permissionsOfRole('Admin')
|
||||||
|
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
|
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
|
||||||
self.assertTrue(self.portal.portal_membership.checkPermission(
|
self.assertTrue(
|
||||||
'Review comments', self.folder), self.folder)
|
self.portal.portal_membership.checkPermission(
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Member'])
|
"Review comments", self.folder
|
||||||
|
),
|
||||||
|
self.folder,
|
||||||
|
)
|
||||||
|
setRoles(self.portal, TEST_USER_ID, ["Member"])
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
self.portal.portal_membership.checkPermission(
|
self.portal.portal_membership.checkPermission(
|
||||||
'Review comments',
|
"Review comments",
|
||||||
self.folder,
|
self.folder,
|
||||||
),
|
),
|
||||||
self.folder,
|
self.folder,
|
||||||
@ -73,14 +76,13 @@ class WorkflowSetupTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class PermissionsSetupTest(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
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
mtool = self.portal.portal_membership
|
mtool = self.portal.portal_membership
|
||||||
self.checkPermission = mtool.checkPermission
|
self.checkPermission = mtool.checkPermission
|
||||||
|
|
||||||
@ -90,14 +92,14 @@ class PermissionsSetupTest(unittest.TestCase):
|
|||||||
plone.app.discussion assigns this permission to 'Authenticated' as
|
plone.app.discussion assigns this permission to 'Authenticated' as
|
||||||
well to emulate the behavior of the old commenting system.
|
well to emulate the behavior of the old commenting system.
|
||||||
"""
|
"""
|
||||||
ReplyToItemPerm = 'Reply to item'
|
ReplyToItemPerm = "Reply to item"
|
||||||
# should be allowed as Member
|
# should be allowed as Member
|
||||||
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
|
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
|
||||||
# should be allowed as Authenticated
|
# 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))
|
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
|
||||||
# should be allowed as Manager
|
# 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))
|
self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal))
|
||||||
# should not be allowed as anonymous
|
# should not be allowed as anonymous
|
||||||
logout()
|
logout()
|
||||||
@ -105,70 +107,66 @@ class PermissionsSetupTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class CommentOneStateWorkflowTest(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
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal.invokeFactory('Folder', 'test-folder')
|
self.portal.invokeFactory("Folder", "test-folder")
|
||||||
self.folder = self.portal['test-folder']
|
self.folder = self.portal["test-folder"]
|
||||||
self.catalog = self.portal.portal_catalog
|
self.catalog = self.portal.portal_catalog
|
||||||
self.workflow = self.portal.portal_workflow
|
self.workflow = self.portal.portal_workflow
|
||||||
self.folder.invokeFactory('Document', 'doc1')
|
self.folder.invokeFactory("Document", "doc1")
|
||||||
self.doc = self.folder.doc1
|
self.doc = self.folder.doc1
|
||||||
|
|
||||||
# Add a comment
|
# Add a comment
|
||||||
conversation = IConversation(self.folder.doc1)
|
conversation = IConversation(self.folder.doc1)
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
cid = conversation.addComment(comment)
|
cid = conversation.addComment(comment)
|
||||||
|
|
||||||
self.comment = self.folder.doc1.restrictedTraverse(
|
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("member", "secret", ["Member"], [])
|
||||||
self.portal.acl_users._doAddUser(
|
self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], [])
|
||||||
'reviewer', 'secret', ['Reviewer'], [])
|
self.portal.acl_users._doAddUser("manager", "secret", ["Manager"], [])
|
||||||
self.portal.acl_users._doAddUser('manager', 'secret', ['Manager'], [])
|
self.portal.acl_users._doAddUser("editor", " secret", ["Editor"], [])
|
||||||
self.portal.acl_users._doAddUser('editor', ' secret', ['Editor'], [])
|
self.portal.acl_users._doAddUser("reader", "secret", ["Reader"], [])
|
||||||
self.portal.acl_users._doAddUser('reader', 'secret', ['Reader'], [])
|
|
||||||
|
|
||||||
def test_initial_workflow_state(self):
|
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.assertEqual(
|
||||||
self.workflow.getInfoFor(self.doc, 'review_state'),
|
self.workflow.getInfoFor(self.doc, "review_state"),
|
||||||
'private',
|
"private",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_view_comments(self):
|
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
|
# Owner is allowed
|
||||||
# self.login(default_user)
|
# self.login(default_user)
|
||||||
# self.assertTrue(checkPerm(View, self.doc))
|
# self.assertTrue(checkPerm(View, self.doc))
|
||||||
# Member is allowed
|
# Member is allowed
|
||||||
login(self.portal, TEST_USER_NAME)
|
login(self.portal, TEST_USER_NAME)
|
||||||
workflow = self.portal.portal_workflow
|
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))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
# Reviewer is allowed
|
# Reviewer is allowed
|
||||||
login(self.portal, 'reviewer')
|
login(self.portal, "reviewer")
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
# Anonymous is allowed
|
# Anonymous is allowed
|
||||||
logout()
|
logout()
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
# Editor is allowed
|
# Editor is allowed
|
||||||
login(self.portal, 'editor')
|
login(self.portal, "editor")
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
# Reader is allowed
|
# Reader is allowed
|
||||||
login(self.portal, 'reader')
|
login(self.portal, "reader")
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
|
|
||||||
def test_comment_on_private_content_not_visible_to_world(self):
|
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
|
from plone.app.discussion.upgrades import upgrade_comment_workflows
|
||||||
|
|
||||||
# Fake permission according to earlier one_comment_workflow.
|
# Fake permission according to earlier one_comment_workflow.
|
||||||
self.comment._View_Permission = ('Anonymous',)
|
self.comment._View_Permission = ("Anonymous",)
|
||||||
# Anonymous can see the comment.
|
# Anonymous can see the comment.
|
||||||
logout()
|
logout()
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
@ -188,8 +186,8 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
|
|||||||
upgrade_comment_workflows(self.portal.portal_setup)
|
upgrade_comment_workflows(self.portal.portal_setup)
|
||||||
# The workflow chain is still what we want.
|
# The workflow chain is still what we want.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.portal.portal_workflow.getChainFor('Discussion Item'),
|
self.portal.portal_workflow.getChainFor("Discussion Item"),
|
||||||
('comment_one_state_workflow',),
|
("comment_one_state_workflow",),
|
||||||
)
|
)
|
||||||
# A Manager can still see the comment.
|
# A Manager can still see the comment.
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
@ -199,112 +197,112 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class CommentReviewWorkflowTest(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
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.portal = self.layer['portal']
|
self.portal = self.layer["portal"]
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
||||||
self.portal.invokeFactory('Folder', 'test-folder')
|
self.portal.invokeFactory("Folder", "test-folder")
|
||||||
self.folder = self.portal['test-folder']
|
self.folder = self.portal["test-folder"]
|
||||||
|
|
||||||
# Allow discussion on the Document content type
|
# 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
|
# Set workflow for Discussion item to review workflow
|
||||||
self.portal.portal_workflow.setChainForPortalTypes(
|
self.portal.portal_workflow.setChainForPortalTypes(
|
||||||
('Discussion Item',),
|
("Discussion Item",),
|
||||||
('comment_review_workflow',),
|
("comment_review_workflow",),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a conversation for this Document
|
# Create a conversation for this Document
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
|
||||||
# Add a comment.
|
# Add a comment.
|
||||||
comment = createObject('plone.Comment')
|
comment = createObject("plone.Comment")
|
||||||
comment.text = 'Comment text'
|
comment.text = "Comment text"
|
||||||
comment_id = conversation.addComment(comment)
|
comment_id = conversation.addComment(comment)
|
||||||
comment = self.portal.doc1.restrictedTraverse(
|
comment = self.portal.doc1.restrictedTraverse(
|
||||||
'++conversation++default/{0}'.format(comment_id),
|
"++conversation++default/{0}".format(comment_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversation = conversation
|
self.conversation = conversation
|
||||||
self.comment_id = comment_id
|
self.comment_id = comment_id
|
||||||
self.comment = comment
|
self.comment = comment
|
||||||
|
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Reviewer'])
|
setRoles(self.portal, TEST_USER_ID, ["Reviewer"])
|
||||||
alsoProvides(self.portal.REQUEST, IDiscussionLayer)
|
alsoProvides(self.portal.REQUEST, IDiscussionLayer)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
self.portal.REQUEST.form["comment_id"] = self.comment_id
|
||||||
view = self.comment.restrictedTraverse('@@moderate-delete-comment')
|
view = self.comment.restrictedTraverse("@@moderate-delete-comment")
|
||||||
view()
|
view()
|
||||||
self.assertFalse(self.comment_id in self.conversation.objectIds())
|
self.assertFalse(self.comment_id in self.conversation.objectIds())
|
||||||
|
|
||||||
def test_delete_as_anonymous(self):
|
def test_delete_as_anonymous(self):
|
||||||
# Make sure that anonymous users can not delete comments
|
# Make sure that anonymous users can not delete comments
|
||||||
logout()
|
logout()
|
||||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
self.portal.REQUEST.form["comment_id"] = self.comment_id
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
self.comment.restrictedTraverse,
|
self.comment.restrictedTraverse,
|
||||||
'@@moderate-delete-comment',
|
"@@moderate-delete-comment",
|
||||||
)
|
)
|
||||||
self.assertTrue(self.comment_id in self.conversation.objectIds())
|
self.assertTrue(self.comment_id in self.conversation.objectIds())
|
||||||
|
|
||||||
def test_delete_as_user(self):
|
def test_delete_as_user(self):
|
||||||
# Make sure that members can not delete comments
|
# Make sure that members can not delete comments
|
||||||
logout()
|
logout()
|
||||||
setRoles(self.portal, TEST_USER_ID, ['Member'])
|
setRoles(self.portal, TEST_USER_ID, ["Member"])
|
||||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
self.portal.REQUEST.form["comment_id"] = self.comment_id
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
self.comment.restrictedTraverse,
|
self.comment.restrictedTraverse,
|
||||||
'@@moderate-delete-comment',
|
"@@moderate-delete-comment",
|
||||||
)
|
)
|
||||||
self.assertTrue(self.comment_id in self.conversation.objectIds())
|
self.assertTrue(self.comment_id in self.conversation.objectIds())
|
||||||
|
|
||||||
def test_publish(self):
|
def test_publish(self):
|
||||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
self.portal.REQUEST.form["comment_id"] = self.comment_id
|
||||||
self.portal.REQUEST.form['workflow_action'] = 'publish'
|
self.portal.REQUEST.form["workflow_action"] = "publish"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'pending',
|
"pending",
|
||||||
self.portal.portal_workflow.getInfoFor(
|
self.portal.portal_workflow.getInfoFor(
|
||||||
self.comment,
|
self.comment,
|
||||||
'review_state',
|
"review_state",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
view = self.comment.restrictedTraverse('@@transmit-comment')
|
view = self.comment.restrictedTraverse("@@transmit-comment")
|
||||||
view()
|
view()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'published',
|
"published",
|
||||||
self.portal.portal_workflow.getInfoFor(
|
self.portal.portal_workflow.getInfoFor(
|
||||||
self.comment,
|
self.comment,
|
||||||
'review_state',
|
"review_state",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_publish_as_anonymous(self):
|
def test_publish_as_anonymous(self):
|
||||||
logout()
|
logout()
|
||||||
self.portal.REQUEST.form['comment_id'] = self.comment_id
|
self.portal.REQUEST.form["comment_id"] = self.comment_id
|
||||||
self.portal.REQUEST.form['workflow_action'] = 'publish'
|
self.portal.REQUEST.form["workflow_action"] = "publish"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'pending', self.portal.portal_workflow.getInfoFor(
|
"pending",
|
||||||
|
self.portal.portal_workflow.getInfoFor(
|
||||||
self.comment,
|
self.comment,
|
||||||
'review_state',
|
"review_state",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
self.comment.restrictedTraverse,
|
self.comment.restrictedTraverse,
|
||||||
'@@transmit-comment',
|
"@@transmit-comment",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'pending',
|
"pending",
|
||||||
self.portal.portal_workflow.getInfoFor(
|
self.portal.portal_workflow.getInfoFor(
|
||||||
self.comment,
|
self.comment,
|
||||||
'review_state',
|
"review_state",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -315,7 +313,7 @@ class CommentReviewWorkflowTest(unittest.TestCase):
|
|||||||
# publish comment and check again
|
# publish comment and check again
|
||||||
login(self.portal, TEST_USER_NAME)
|
login(self.portal, TEST_USER_NAME)
|
||||||
workflow = self.portal.portal_workflow
|
workflow = self.portal.portal_workflow
|
||||||
workflow.doActionFor(self.comment, 'publish')
|
workflow.doActionFor(self.comment, "publish")
|
||||||
|
|
||||||
logout()
|
logout()
|
||||||
self.assertFalse(checkPerm(View, self.comment))
|
self.assertFalse(checkPerm(View, self.comment))
|
||||||
@ -324,7 +322,7 @@ class CommentReviewWorkflowTest(unittest.TestCase):
|
|||||||
from plone.app.discussion.upgrades import upgrade_comment_workflows
|
from plone.app.discussion.upgrades import upgrade_comment_workflows
|
||||||
|
|
||||||
# Fake permission according to earlier comment_review_workflow.
|
# Fake permission according to earlier comment_review_workflow.
|
||||||
self.comment._View_Permission = ('Anonymous',)
|
self.comment._View_Permission = ("Anonymous",)
|
||||||
# Anonymous can see the comment.
|
# Anonymous can see the comment.
|
||||||
logout()
|
logout()
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
@ -333,8 +331,9 @@ class CommentReviewWorkflowTest(unittest.TestCase):
|
|||||||
upgrade_comment_workflows(self.portal.portal_setup)
|
upgrade_comment_workflows(self.portal.portal_setup)
|
||||||
# The workflow chain is still what we want.
|
# The workflow chain is still what we want.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.portal.portal_workflow.getChainFor('Discussion Item'),
|
self.portal.portal_workflow.getChainFor("Discussion Item"),
|
||||||
('comment_review_workflow',))
|
("comment_review_workflow",),
|
||||||
|
)
|
||||||
# A Manager can still see the comment.
|
# A Manager can still see the comment.
|
||||||
self.assertTrue(checkPerm(View, self.comment))
|
self.assertTrue(checkPerm(View, self.comment))
|
||||||
# Anonymous cannot see the comment.
|
# Anonymous cannot see the comment.
|
||||||
|
@ -17,62 +17,60 @@ from zope.component import queryUtility
|
|||||||
@interface.implementer(ICommentingTool)
|
@interface.implementer(ICommentingTool)
|
||||||
class CommentingTool(UniqueObject, SimpleItem):
|
class CommentingTool(UniqueObject, SimpleItem):
|
||||||
|
|
||||||
meta_type = 'plone.app.discussion tool'
|
meta_type = "plone.app.discussion tool"
|
||||||
id = 'portal_discussion'
|
id = "portal_discussion"
|
||||||
|
|
||||||
def reindexObject(self, object):
|
def reindexObject(self, object):
|
||||||
# Reindex in catalog.
|
# Reindex in catalog.
|
||||||
catalog = getToolByName(self, 'portal_catalog')
|
catalog = getToolByName(self, "portal_catalog")
|
||||||
return catalog.reindexObject(object)
|
return catalog.reindexObject(object)
|
||||||
|
|
||||||
indexObject = reindexObject
|
indexObject = reindexObject
|
||||||
|
|
||||||
def unindexObject(self, object):
|
def unindexObject(self, object):
|
||||||
# Remove from catalog.
|
# Remove from catalog.
|
||||||
catalog = getToolByName(self, 'portal_catalog')
|
catalog = getToolByName(self, "portal_catalog")
|
||||||
return catalog.unindexObject(object)
|
return catalog.unindexObject(object)
|
||||||
|
|
||||||
def uniqueValuesFor(self, name):
|
def uniqueValuesFor(self, name):
|
||||||
# return unique values for FieldIndex name
|
# return unique values for FieldIndex name
|
||||||
catalog = getToolByName(self, 'portal_catalog')
|
catalog = getToolByName(self, "portal_catalog")
|
||||||
return catalog.uniqueValuesFor(name)
|
return catalog.uniqueValuesFor(name)
|
||||||
|
|
||||||
def searchResults(self, REQUEST=None, **kw):
|
def searchResults(self, REQUEST=None, **kw):
|
||||||
# Calls ZCatalog.searchResults with extra arguments that
|
# Calls ZCatalog.searchResults with extra arguments that
|
||||||
# limit the results to what the user is allowed to see.
|
# 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__]
|
object_provides = [IComment.__identifier__]
|
||||||
|
|
||||||
if 'object_provides' in kw:
|
if "object_provides" in kw:
|
||||||
kw_provides = kw['object_provides']
|
kw_provides = kw["object_provides"]
|
||||||
if isinstance(str, kw_provides):
|
if isinstance(str, kw_provides):
|
||||||
object_provides.append(kw_provides)
|
object_provides.append(kw_provides)
|
||||||
else:
|
else:
|
||||||
object_provides.extend(kw_provides)
|
object_provides.extend(kw_provides)
|
||||||
|
|
||||||
if REQUEST is not None and 'object_provides' in REQUEST.form:
|
if REQUEST is not None and "object_provides" in REQUEST.form:
|
||||||
rq_provides = REQUEST.form['object_provides']
|
rq_provides = REQUEST.form["object_provides"]
|
||||||
del REQUEST.form['object_provides']
|
del REQUEST.form["object_provides"]
|
||||||
if isinstance(str, rq_provides):
|
if isinstance(str, rq_provides):
|
||||||
object_provides.append(rq_provides)
|
object_provides.append(rq_provides)
|
||||||
else:
|
else:
|
||||||
object_provides.extend(rq_provides)
|
object_provides.extend(rq_provides)
|
||||||
|
|
||||||
kw['object_provides'] = object_provides
|
kw["object_provides"] = object_provides
|
||||||
return catalog.searchResults(REQUEST, **kw)
|
return catalog.searchResults(REQUEST, **kw)
|
||||||
|
|
||||||
|
|
||||||
def index_object(obj, event):
|
def index_object(obj, event):
|
||||||
"""Index the object when added to the conversation
|
"""Index the object when added to the conversation"""
|
||||||
"""
|
|
||||||
tool = queryUtility(ICommentingTool)
|
tool = queryUtility(ICommentingTool)
|
||||||
if tool is not None:
|
if tool is not None:
|
||||||
tool.indexObject(obj)
|
tool.indexObject(obj)
|
||||||
|
|
||||||
|
|
||||||
def unindex_object(obj, event):
|
def unindex_object(obj, event):
|
||||||
"""Unindex the object when removed
|
"""Unindex the object when removed"""
|
||||||
"""
|
|
||||||
tool = queryUtility(ICommentingTool)
|
tool = queryUtility(ICommentingTool)
|
||||||
if tool is not None:
|
if tool is not None:
|
||||||
tool.unindexObject(obj)
|
tool.unindexObject(obj)
|
||||||
|
@ -7,8 +7,8 @@ from zope.component import getUtility
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
default_profile = 'profile-plone.app.discussion:default'
|
default_profile = "profile-plone.app.discussion:default"
|
||||||
logger = logging.getLogger('plone.app.discussion')
|
logger = logging.getLogger("plone.app.discussion")
|
||||||
|
|
||||||
|
|
||||||
def update_registry(context):
|
def update_registry(context):
|
||||||
@ -17,7 +17,7 @@ def update_registry(context):
|
|||||||
|
|
||||||
|
|
||||||
def update_rolemap(context):
|
def update_rolemap(context):
|
||||||
context.runImportStepFromProfile(default_profile, 'rolemap')
|
context.runImportStepFromProfile(default_profile, "rolemap")
|
||||||
|
|
||||||
|
|
||||||
def upgrade_comment_workflows_retain_current_workflow(context):
|
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.
|
# 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
|
# If it was anything else, we should restore this. So get the original
|
||||||
# chain.
|
# chain.
|
||||||
portal_type = 'Discussion Item'
|
portal_type = "Discussion Item"
|
||||||
wf_tool = getToolByName(context, 'portal_workflow')
|
wf_tool = getToolByName(context, "portal_workflow")
|
||||||
orig_chain = list(wf_tool.getChainFor(portal_type))
|
orig_chain = list(wf_tool.getChainFor(portal_type))
|
||||||
|
|
||||||
# Run the workflow step. This sets the chain to
|
# Run the workflow step. This sets the chain to
|
||||||
# comment_one_state_workflow.
|
# comment_one_state_workflow.
|
||||||
context.runImportStepFromProfile(default_profile, 'workflow')
|
context.runImportStepFromProfile(default_profile, "workflow")
|
||||||
|
|
||||||
# Restore original workflow chain if needed.
|
# Restore original workflow chain if needed.
|
||||||
old_workflow = 'one_state_workflow'
|
old_workflow = "one_state_workflow"
|
||||||
if old_workflow not in orig_chain:
|
if old_workflow not in orig_chain:
|
||||||
# Restore the chain. Probably comment_review_workflow.
|
# Restore the chain. Probably comment_review_workflow.
|
||||||
wf_tool.setChainForPortalTypes([portal_type], orig_chain)
|
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:
|
if old_workflow in orig_chain:
|
||||||
# Replace with new one.
|
# Replace with new one.
|
||||||
idx = orig_chain.index(old_workflow)
|
idx = orig_chain.index(old_workflow)
|
||||||
orig_chain[idx] = 'comment_one_state_workflow'
|
orig_chain[idx] = "comment_one_state_workflow"
|
||||||
# Restore the chain.
|
# Restore the chain.
|
||||||
wf_tool.setChainForPortalTypes([portal_type], orig_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):
|
def upgrade_comment_workflows_apply_rolemapping(context):
|
||||||
# Now go over the comments, update their role mappings, and reindex the
|
# Now go over the comments, update their role mappings, and reindex the
|
||||||
# allowedRolesAndUsers index.
|
# allowedRolesAndUsers index.
|
||||||
portal_type = 'Discussion Item'
|
portal_type = "Discussion Item"
|
||||||
catalog = getToolByName(context, 'portal_catalog')
|
catalog = getToolByName(context, "portal_catalog")
|
||||||
wf_tool = getToolByName(context, 'portal_workflow')
|
wf_tool = getToolByName(context, "portal_workflow")
|
||||||
new_chain = list(wf_tool.getChainFor(portal_type))
|
new_chain = list(wf_tool.getChainFor(portal_type))
|
||||||
workflows = [wf_tool.getWorkflowById(wf_id) for wf_id in new_chain]
|
workflows = [wf_tool.getWorkflowById(wf_id) for wf_id in new_chain]
|
||||||
for brain in catalog.unrestrictedSearchResults(portal_type=portal_type):
|
for brain in catalog.unrestrictedSearchResults(portal_type=portal_type):
|
||||||
@ -63,7 +63,7 @@ def upgrade_comment_workflows_apply_rolemapping(context):
|
|||||||
wf.updateRoleMappingsFor(comment)
|
wf.updateRoleMappingsFor(comment)
|
||||||
comment.reindexObjectSecurity()
|
comment.reindexObjectSecurity()
|
||||||
except (AttributeError, KeyError):
|
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):
|
def upgrade_comment_workflows(context):
|
||||||
@ -72,7 +72,7 @@ def upgrade_comment_workflows(context):
|
|||||||
|
|
||||||
|
|
||||||
def add_js_to_plone_legacy(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):
|
def extend_review_workflow(context):
|
||||||
|
@ -7,6 +7,7 @@ from zope.schema.vocabulary import SimpleVocabulary
|
|||||||
HAS_CAPTCHA = False
|
HAS_CAPTCHA = False
|
||||||
try:
|
try:
|
||||||
import plone.formwidget.captcha # noqa
|
import plone.formwidget.captcha # noqa
|
||||||
|
|
||||||
HAS_CAPTCHA = True # pragma: no cover
|
HAS_CAPTCHA = True # pragma: no cover
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -14,6 +15,7 @@ except ImportError:
|
|||||||
HAS_RECAPTCHA = False
|
HAS_RECAPTCHA = False
|
||||||
try:
|
try:
|
||||||
import plone.formwidget.recaptcha # noqa
|
import plone.formwidget.recaptcha # noqa
|
||||||
|
|
||||||
HAS_RECAPTCHA = True # pragma: no cover
|
HAS_RECAPTCHA = True # pragma: no cover
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -21,6 +23,7 @@ except ImportError:
|
|||||||
HAS_AKISMET = False
|
HAS_AKISMET = False
|
||||||
try:
|
try:
|
||||||
import collective.akismet # noqa
|
import collective.akismet # noqa
|
||||||
|
|
||||||
HAS_AKISMET = True # pragma: no cover
|
HAS_AKISMET = True # pragma: no cover
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -28,73 +31,48 @@ except ImportError:
|
|||||||
HAS_NOROBOTS = False
|
HAS_NOROBOTS = False
|
||||||
try:
|
try:
|
||||||
import collective.z3cform.norobots # noqa
|
import collective.z3cform.norobots # noqa
|
||||||
|
|
||||||
HAS_NOROBOTS = True # pragma: no cover
|
HAS_NOROBOTS = True # pragma: no cover
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def captcha_vocabulary(context):
|
def captcha_vocabulary(context):
|
||||||
"""Vocabulary with all available captcha implementations.
|
"""Vocabulary with all available captcha implementations."""
|
||||||
"""
|
|
||||||
terms = []
|
terms = []
|
||||||
terms.append(
|
terms.append(SimpleTerm(value="disabled", token="disabled", title=_(u"Disabled")))
|
||||||
SimpleTerm(
|
|
||||||
value='disabled',
|
|
||||||
token='disabled',
|
|
||||||
title=_(u'Disabled')))
|
|
||||||
|
|
||||||
if HAS_CAPTCHA: # pragma: no cover
|
if HAS_CAPTCHA: # pragma: no cover
|
||||||
terms.append(
|
terms.append(SimpleTerm(value="captcha", token="captcha", title="Captcha"))
|
||||||
SimpleTerm(
|
|
||||||
value='captcha',
|
|
||||||
token='captcha',
|
|
||||||
title='Captcha'))
|
|
||||||
|
|
||||||
if HAS_RECAPTCHA: # pragma: no cover
|
if HAS_RECAPTCHA: # pragma: no cover
|
||||||
terms.append(
|
terms.append(
|
||||||
SimpleTerm(
|
SimpleTerm(value="recaptcha", token="recaptcha", title="ReCaptcha")
|
||||||
value='recaptcha',
|
)
|
||||||
token='recaptcha',
|
|
||||||
title='ReCaptcha'))
|
|
||||||
|
|
||||||
if HAS_AKISMET: # pragma: no cover
|
if HAS_AKISMET: # pragma: no cover
|
||||||
terms.append(
|
terms.append(SimpleTerm(value="akismet", token="akismet", title="Akismet"))
|
||||||
SimpleTerm(
|
|
||||||
value='akismet',
|
|
||||||
token='akismet',
|
|
||||||
title='Akismet'))
|
|
||||||
|
|
||||||
if HAS_NOROBOTS: # pragma: no cover
|
if HAS_NOROBOTS: # pragma: no cover
|
||||||
terms.append(
|
terms.append(SimpleTerm(value="norobots", token="norobots", title="Norobots"))
|
||||||
SimpleTerm(
|
|
||||||
value='norobots',
|
|
||||||
token='norobots',
|
|
||||||
title='Norobots'))
|
|
||||||
return SimpleVocabulary(terms)
|
return SimpleVocabulary(terms)
|
||||||
|
|
||||||
|
|
||||||
def text_transform_vocabulary(context):
|
def text_transform_vocabulary(context):
|
||||||
"""Vocabulary with all available portal_transform transformations.
|
"""Vocabulary with all available portal_transform transformations."""
|
||||||
"""
|
|
||||||
terms = []
|
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(
|
terms.append(
|
||||||
SimpleTerm(
|
SimpleTerm(
|
||||||
value='text/plain',
|
value="text/x-web-markdown", token="text/x-web-markdown", title="Markdown"
|
||||||
token='text/plain',
|
)
|
||||||
title='Plain text'))
|
)
|
||||||
terms.append(
|
terms.append(
|
||||||
SimpleTerm(
|
SimpleTerm(
|
||||||
value='text/html',
|
value="text/x-web-intelligent",
|
||||||
token='text/html',
|
token="text/x-web-intelligent",
|
||||||
title='HTML'))
|
title="Intelligent text",
|
||||||
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'))
|
|
||||||
return SimpleVocabulary(terms)
|
return SimpleVocabulary(terms)
|
||||||
|
74
setup.py
74
setup.py
@ -4,34 +4,34 @@ from setuptools import find_packages
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
version = '4.0.0a7.dev0'
|
version = "4.0.0a7.dev0"
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'setuptools',
|
"setuptools",
|
||||||
'plone.app.layout',
|
"plone.app.layout",
|
||||||
'plone.app.registry',
|
"plone.app.registry",
|
||||||
'plone.app.uuid',
|
"plone.app.uuid",
|
||||||
'plone.app.z3cform',
|
"plone.app.z3cform",
|
||||||
'plone.indexer',
|
"plone.indexer",
|
||||||
'plone.registry',
|
"plone.registry",
|
||||||
'plone.z3cform',
|
"plone.z3cform",
|
||||||
'six',
|
"six",
|
||||||
'ZODB3',
|
"ZODB3",
|
||||||
'zope.interface',
|
"zope.interface",
|
||||||
'zope.component',
|
"zope.component",
|
||||||
'zope.annotation',
|
"zope.annotation",
|
||||||
'zope.event',
|
"zope.event",
|
||||||
'zope.container',
|
"zope.container",
|
||||||
'zope.lifecycleevent',
|
"zope.lifecycleevent",
|
||||||
'zope.site',
|
"zope.site",
|
||||||
'z3c.form>=2.3.3',
|
"z3c.form>=2.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(name='plone.app.discussion',
|
setup(
|
||||||
|
name="plone.app.discussion",
|
||||||
version=version,
|
version=version,
|
||||||
description='Enhanced discussion support for Plone',
|
description="Enhanced discussion support for Plone",
|
||||||
long_description=open('README.rst').read() + '\n' +
|
long_description=open("README.rst").read() + "\n" + open("CHANGES.rst").read(),
|
||||||
open('CHANGES.rst').read(),
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Environment :: Web Environment",
|
"Environment :: Web Environment",
|
||||||
@ -46,28 +46,28 @@ setup(name='plone.app.discussion',
|
|||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
],
|
],
|
||||||
keywords='plone discussion',
|
keywords="plone discussion",
|
||||||
author='Timo Stollenwerk - Plone Foundation',
|
author="Timo Stollenwerk - Plone Foundation",
|
||||||
author_email='plone-developers@lists.sourceforge.net',
|
author_email="plone-developers@lists.sourceforge.net",
|
||||||
url='https://pypi.org/project/plone.app.discussion',
|
url="https://pypi.org/project/plone.app.discussion",
|
||||||
license='GPL',
|
license="GPL",
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['plone', 'plone.app'],
|
namespace_packages=["plone", "plone.app"],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require={
|
extras_require={
|
||||||
'test': [
|
"test": [
|
||||||
'plone.app.testing',
|
"plone.app.testing",
|
||||||
'plone.stringinterp',
|
"plone.stringinterp",
|
||||||
'plone.contentrules',
|
"plone.contentrules",
|
||||||
'plone.app.contentrules',
|
"plone.app.contentrules",
|
||||||
'plone.app.contenttypes[test]',
|
"plone.app.contenttypes[test]",
|
||||||
'plone.app.robotframework',
|
"plone.app.robotframework",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[z3c.autoinclude.plugin]
|
[z3c.autoinclude.plugin]
|
||||||
target = plone
|
target = plone
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user