diff --git a/docs/source/conf.py b/docs/source/conf.py index 925d131..b94ce2d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # plone.app.discussion documentation build configuration file, created by # sphinx-quickstart on Thu Mar 18 10:17:15 2010. @@ -24,184 +23,190 @@ import sys # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'repoze.sphinx.autointerface' - ] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "repoze.sphinx.autointerface", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.txt' +source_suffix = ".txt" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'plone.app.discussion' -copyright = u'2010, Timo Stollenwerk - Plone Foundation' +project = "plone.app.discussion" +copyright = "2010, Timo Stollenwerk - Plone Foundation" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '2.0' +version = "2.0" # The full version, including alpha/beta/rc tags. -release = '2.0' +release = "2.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # 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. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # 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. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -#html_theme = 'plone' +# html_theme = 'plone' # 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 # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes'] +html_theme_path = ["_themes"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#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 # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # 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 # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # 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. -#html_split_index = False +# html_split_index = False # 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 # contain a tag referring to it. The value of this option must be the # 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"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'ploneappdiscussiondoc' +htmlhelp_basename = "ploneappdiscussiondoc" # -- Options for LaTeX output -------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # 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 # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'ploneappdiscussion.tex', u'plone.app.discussion Documentation', - u'Timo Stollenwerk', 'manual'), + ( + "index", + "ploneappdiscussion.tex", + "plone.app.discussion Documentation", + "Timo Stollenwerk", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # 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. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"http://docs.python.org/": None} diff --git a/docs/source/howtos/howto_extend_the_comment_form.txt b/docs/source/howtos/howto_extend_the_comment_form.txt index 605e0ef..3afaf6a 100644 --- a/docs/source/howtos/howto_extend_the_comment_form.txt +++ b/docs/source/howtos/howto_extend_the_comment_form.txt @@ -58,13 +58,13 @@ comment form with the "website" field:: # Interface to define the fields we want to add to the comment form. class ICommentExtenderFields(Interface): - website = schema.TextLine(title=u"Website", required=False) + website = schema.TextLine(title="Website", required=False) # Persistent class that implements the ICommentExtenderFields interface @adapter(Comment) class CommentExtenderFields(Persistent): interface.implements(ICommentExtenderFields) - website = u"" + website = "" # CommentExtenderFields factory CommentExtenderFactory = factory(CommentExtenderFields) diff --git a/docs/source/howtos/howto_make_pad_work_with_a_dexterity_content_type.txt b/docs/source/howtos/howto_make_pad_work_with_a_dexterity_content_type.txt index b134648..84610b3 100644 --- a/docs/source/howtos/howto_make_pad_work_with_a_dexterity_content_type.txt +++ b/docs/source/howtos/howto_make_pad_work_with_a_dexterity_content_type.txt @@ -20,8 +20,8 @@ configure.zcml:: Define an interface IMyDexterityContentType groked schema, I added:: allowDiscussion = schema.Bool( - title=_(u"Allow Users to Comment"), - description=_(u"Allow users to commemt on you. Comments + title=_("Allow Users to Comment"), + description=_("Allow users to comment on you. Comments are shown at the end of each page"), required=True, default=True, diff --git a/news/195.breaking b/news/195.breaking new file mode 100644 index 0000000..41d8e37 --- /dev/null +++ b/news/195.breaking @@ -0,0 +1,2 @@ +Code style black & isort. Remove six usage. Use plone.base and move annotation key over to here. +[jensens] diff --git a/plone/__init__.py b/plone/__init__.py index 68c04af..5284146 100644 --- a/plone/__init__.py +++ b/plone/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- -__import__('pkg_resources').declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) diff --git a/plone/app/__init__.py b/plone/app/__init__.py index 68c04af..5284146 100644 --- a/plone/app/__init__.py +++ b/plone/app/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- -__import__('pkg_resources').declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) diff --git a/plone/app/discussion/__init__.py b/plone/app/discussion/__init__.py index 1936adc..8efe8b3 100644 --- a/plone/app/discussion/__init__.py +++ b/plone/app/discussion/__init__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- from zope.i18nmessageid import MessageFactory -_ = MessageFactory('plone') +_ = MessageFactory("plone") diff --git a/plone/app/discussion/browser/captcha.py b/plone/app/discussion/browser/captcha.py index 090fce9..7e10d12 100644 --- a/plone/app/discussion/browser/captcha.py +++ b/plone/app/discussion/browser/captcha.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Captcha validator, see captcha.txt for design notes. from persistent import Persistent from plone.app.discussion.browser.comments import CommentForm @@ -21,9 +20,9 @@ from zope.publisher.interfaces.browser import IDefaultBrowserLayer @adapter(Comment) @interface.implementer(ICaptcha) class Captcha(Persistent): - """Captcha input field. - """ - captcha = u'' + """Captcha input field.""" + + captcha = "" Captcha = factory(Captcha) @@ -47,22 +46,24 @@ class CaptchaExtender(extensible.FormExtender): registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) self.captcha = settings.captcha - portal_membership = getToolByName(self.context, 'portal_membership') + portal_membership = getToolByName(self.context, "portal_membership") self.isAnon = portal_membership.isAnonymousUser() def update(self): - if self.captcha != 'disabled' and self.isAnon: + if self.captcha != "disabled" and self.isAnon: # Add a captcha field if captcha is enabled in the registry - self.add(ICaptcha, prefix='') - if self.captcha == 'captcha': + self.add(ICaptcha, prefix="") + if self.captcha == "captcha": from plone.formwidget.captcha import CaptchaFieldWidget - self.form.fields['captcha'].widgetFactory = CaptchaFieldWidget - elif self.captcha == 'recaptcha': + + self.form.fields["captcha"].widgetFactory = CaptchaFieldWidget + elif self.captcha == "recaptcha": from plone.formwidget.recaptcha import ReCaptchaFieldWidget - self.form.fields['captcha'].widgetFactory = \ - ReCaptchaFieldWidget - elif self.captcha == 'norobots': + + self.form.fields["captcha"].widgetFactory = ReCaptchaFieldWidget + elif self.captcha == "norobots": from collective.z3cform.norobots import NorobotsFieldWidget - self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget + + self.form.fields["captcha"].widgetFactory = NorobotsFieldWidget else: - self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE + self.form.fields["captcha"].mode = interfaces.HIDDEN_MODE diff --git a/plone/app/discussion/browser/comment.py b/plone/app/discussion/browser/comment.py index 41beecc..fc95a55 100644 --- a/plone/app/discussion/browser/comment.py +++ b/plone/app/discussion/browser/comment.py @@ -1,4 +1,3 @@ -# coding: utf-8 from .comments import CommentForm from AccessControl import getSecurityManager from Acquisition import aq_inner @@ -14,7 +13,6 @@ from zope.component import getMultiAdapter from zope.component import getUtility from zope.event import notify from zope.lifecycleevent import ObjectModifiedEvent -from .comments import CommentForm class View(BrowserView): @@ -38,8 +36,7 @@ class View(BrowserView): context = aq_inner(self.context) registry = getUtility(IRegistry) - view_action_types = registry.get( - 'plone.types_use_view_action_in_listings', []) + view_action_types = registry.get("plone.types_use_view_action_in_listings", []) obj = aq_parent(aq_parent(context)) url = obj.absolute_url() @@ -50,33 +47,34 @@ class View(BrowserView): will redirect right to the binary object, bypassing comments. """ if obj.portal_type in view_action_types: - url = '{0}/view'.format(url) + url = f"{url}/view" - self.request.response.redirect('{0}#{1}'.format(url, context.id)) + self.request.response.redirect(f"{url}#{context.id}") class EditCommentForm(CommentForm): """Form to edit an existing comment.""" + ignoreContext = True - id = 'edit-comment-form' - label = _(u'edit_comment_form_title', default=u'Edit comment') + id = "edit-comment-form" + label = _("edit_comment_form_title", default="Edit comment") def updateWidgets(self): - super(EditCommentForm, self).updateWidgets() - self.widgets['text'].value = self.context.text + super().updateWidgets() + self.widgets["text"].value = self.context.text # We have to rename the id, otherwise TinyMCE can't initialize # because there are two textareas with the same id. - self.widgets['text'].id = 'overlay-comment-text' + self.widgets["text"].id = "overlay-comment-text" - def _redirect(self, target=''): + def _redirect(self, target=""): if not target: - portal_state = getMultiAdapter((self.context, self.request), - name=u'plone_portal_state') + portal_state = getMultiAdapter( + (self.context, self.request), name="plone_portal_state" + ) target = portal_state.portal_url() self.request.response.redirect(target) - @button.buttonAndHandler(_(u'label_save', - default=u'Save'), name='comment') + @button.buttonAndHandler(_("label_save", default="Save"), name="comment") def handleComment(self, action): # Validate form @@ -85,32 +83,28 @@ class EditCommentForm(CommentForm): return # Check permissions - can_edit = getSecurityManager().checkPermission( - 'Edit comments', - self.context) - mtool = getToolByName(self.context, 'portal_membership') + can_edit = getSecurityManager().checkPermission("Edit comments", self.context) + mtool = getToolByName(self.context, "portal_membership") if mtool.isAnonymousUser() or not can_edit: return # Update text - self.context.text = data['text'] + self.context.text = data["text"] # Notify that the object has been modified notify(ObjectModifiedEvent(self.context)) # Redirect to comment - IStatusMessage(self.request).add(_(u'comment_edit_notification', - default='Comment was edited'), - type='info') - return self._redirect( - target=self.action.replace('@@edit-comment', '@@view')) + IStatusMessage(self.request).add( + _("comment_edit_notification", default="Comment was edited"), type="info" + ) + return self._redirect(target=self.action.replace("@@edit-comment", "@@view")) - @button.buttonAndHandler(_(u'cancel_form_button', - default=u'Cancel'), name='cancel') + @button.buttonAndHandler(_("cancel_form_button", default="Cancel"), name="cancel") def handle_cancel(self, action): IStatusMessage(self.request).add( - _(u'comment_edit_cancel_notification', - default=u'Edit comment cancelled'), - type='info') + _("comment_edit_cancel_notification", default="Edit comment cancelled"), + type="info", + ) return self._redirect(target=self.context.absolute_url()) diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py index d8ee95f..387cead 100644 --- a/plone/app/discussion/browser/comments.py +++ b/plone/app/discussion/browser/comments.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from AccessControl import getSecurityManager from AccessControl import Unauthorized from Acquisition import aq_inner @@ -12,15 +11,15 @@ from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.discussion.interfaces import IReplies from plone.app.layout.viewlets.common import ViewletBase +from plone.base.utils import safe_text from plone.registry.interfaces import IRegistry from plone.z3cform import z2 from plone.z3cform.fieldsets import extensible from plone.z3cform.interfaces import IWrappedForm from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.utils import safe_unicode from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.statusmessages.interfaces import IStatusMessage -from six.moves.urllib.parse import quote +from urllib.parse import quote from z3c.form import button from z3c.form import field from z3c.form import form @@ -35,28 +34,28 @@ from zope.interface import alsoProvides COMMENT_DESCRIPTION_PLAIN_TEXT = _( - u'comment_description_plain_text', - default=u'You can add a comment by filling out the form below. ' - u'Plain text formatting.', + "comment_description_plain_text", + default="You can add a comment by filling out the form below. " + "Plain text formatting.", ) COMMENT_DESCRIPTION_MARKDOWN = _( - u'comment_description_markdown', - default=u'You can add a comment by filling out the form below. ' - u'Plain text formatting. You can use the Markdown syntax for ' - u'links and images.', + "comment_description_markdown", + default="You can add a comment by filling out the form below. " + "Plain text formatting. You can use the Markdown syntax for " + "links and images.", ) COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _( - u'comment_description_intelligent_text', - default=u'You can add a comment by filling out the form below. ' - u'Plain text formatting. Web and email addresses are ' - u'transformed into clickable links.', + "comment_description_intelligent_text", + default="You can add a comment by filling out the form below. " + "Plain text formatting. Web and email addresses are " + "transformed into clickable links.", ) COMMENT_DESCRIPTION_MODERATION_ENABLED = _( - u'comment_description_moderation_enabled', - default=u'Comments are moderated.', + "comment_description_moderation_enabled", + default="Comments are moderated.", ) @@ -64,30 +63,31 @@ class CommentForm(extensible.ExtensibleForm, form.Form): ignoreContext = True # don't use context to get widget data id = None - label = _(u'Add a comment') - fields = field.Fields(IComment).omit('portal_type', - '__parent__', - '__name__', - 'comment_id', - 'mime_type', - 'creator', - 'creation_date', - 'modification_date', - 'author_username', - 'title') + label = _("Add a comment") + fields = field.Fields(IComment).omit( + "portal_type", + "__parent__", + "__name__", + "comment_id", + "mime_type", + "creator", + "creation_date", + "modification_date", + "author_username", + "title", + ) def updateFields(self): - super(CommentForm, self).updateFields() - self.fields['user_notification'].widgetFactory = \ - SingleCheckBoxFieldWidget + super().updateFields() + self.fields["user_notification"].widgetFactory = SingleCheckBoxFieldWidget def updateWidgets(self): - super(CommentForm, self).updateWidgets() + super().updateWidgets() # Widgets - self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE - self.widgets['text'].addClass('autoresize') - self.widgets['user_notification'].label = _(u'') + self.widgets["in_reply_to"].mode = interfaces.HIDDEN_MODE + self.widgets["text"].addClass("autoresize") + self.widgets["user_notification"].label = _("") # Reset widget field settings to their defaults, which may be changed # further on. Otherwise, the email field might get set to required # when an anonymous user visits, and then remain required when an @@ -97,19 +97,19 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # would have no effect until the instance was restarted. Note that the # widget is new each time, but the field is the same item in memory as # the previous time. - self.widgets['author_email'].field.required = False + self.widgets["author_email"].field.required = False # The widget is new, but its 'required' setting is based on the # previous value on the field, so we need to reset it here. Changing # the field in updateFields does not help. - self.widgets['author_email'].required = False + self.widgets["author_email"].required = False # Rename the id of the text widgets because there can be css-id # clashes with the text field of documents when using and overlay # with TinyMCE. - self.widgets['text'].id = 'form-widgets-comment-text' + self.widgets["text"].id = "form-widgets-comment-text" # Anonymous / Logged-in - mtool = getToolByName(self.context, 'portal_membership') + mtool = getToolByName(self.context, "portal_membership") anon = mtool.isAnonymousUser() registry = queryUtility(IRegistry) @@ -119,56 +119,55 @@ class CommentForm(extensible.ExtensibleForm, form.Form): if settings.anonymous_email_enabled: # according to IDiscussionSettings.anonymous_email_enabled: # 'If selected, anonymous user will have to give their email.' - self.widgets['author_email'].field.required = True - self.widgets['author_email'].required = True + self.widgets["author_email"].field.required = True + self.widgets["author_email"].required = True else: - self.widgets['author_email'].mode = interfaces.HIDDEN_MODE + self.widgets["author_email"].mode = interfaces.HIDDEN_MODE else: - self.widgets['author_name'].mode = interfaces.HIDDEN_MODE - self.widgets['author_email'].mode = interfaces.HIDDEN_MODE + self.widgets["author_name"].mode = interfaces.HIDDEN_MODE + self.widgets["author_email"].mode = interfaces.HIDDEN_MODE member = mtool.getAuthenticatedMember() - member_email = member.getProperty('email') + member_email = member.getProperty("email") # Hide the user_notification checkbox if user notification is disabled # or the user is not logged in. Also check if the user has a valid # email address - member_email_is_empty = member_email == '' + member_email_is_empty = member_email == "" user_notification_disabled = not settings.user_notification_enabled if member_email_is_empty or user_notification_disabled or anon: - self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE + self.widgets["user_notification"].mode = interfaces.HIDDEN_MODE def updateActions(self): - super(CommentForm, self).updateActions() - self.actions['cancel'].addClass('btn btn-secondary') - self.actions['cancel'].addClass('hide') - self.actions['comment'].addClass('btn btn-primary') + super().updateActions() + self.actions["cancel"].addClass("btn btn-secondary") + self.actions["cancel"].addClass("hide") + self.actions["comment"].addClass("btn btn-primary") def get_author(self, data): context = aq_inner(self.context) # some attributes are not always set - author_name = u'' + author_name = "" # Make sure author_name/ author_email is properly encoded - if 'author_name' in data: - author_name = safe_unicode(data['author_name']) - if 'author_email' in data: - author_email = safe_unicode(data['author_email']) + if "author_name" in data: + author_name = safe_text(data["author_name"]) + if "author_email" in data: + author_email = safe_text(data["author_email"]) # Set comment author properties for anonymous users or members - portal_membership = getToolByName(context, 'portal_membership') + portal_membership = getToolByName(context, "portal_membership") anon = portal_membership.isAnonymousUser() - if not anon and getSecurityManager().checkPermission( - 'Reply to item', context): + if not anon and getSecurityManager().checkPermission("Reply to item", context): # Member member = portal_membership.getAuthenticatedMember() - email = safe_unicode(member.getProperty('email')) - fullname = member.getProperty('fullname') - if not fullname or fullname == '': + email = safe_text(member.getProperty("email")) + fullname = member.getProperty("fullname") + if not fullname or fullname == "": fullname = member.getUserName() - fullname = safe_unicode(fullname) + fullname = safe_text(fullname) author_name = fullname - email = safe_unicode(email) + email = safe_text(email) # XXX: according to IComment interface author_email must not be # noqa T000 # set for logged in users, cite: # 'for anonymous comments only, set to None for logged in comments' @@ -179,7 +178,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): def create_comment(self, data): context = aq_inner(self.context) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) @@ -200,42 +199,42 @@ class CommentForm(extensible.ExtensibleForm, form.Form): comment.author_name, comment.author_email = self.get_author(data) # Set comment author properties for anonymous users or members - portal_membership = getToolByName(context, 'portal_membership') + portal_membership = getToolByName(context, "portal_membership") anon = portal_membership.isAnonymousUser() if anon and anonymous_comments: # Anonymous Users comment.user_notification = None elif not anon and getSecurityManager().checkPermission( - 'Reply to item', context): + "Reply to item", context + ): # Member member = portal_membership.getAuthenticatedMember() memberid = member.getId() user = member.getUser() comment.changeOwnership(user, recursive=False) - comment.manage_setLocalRoles(memberid, ['Owner']) + comment.manage_setLocalRoles(memberid, ["Owner"]) comment.creator = memberid comment.author_username = memberid else: # pragma: no cover raise Unauthorized( - u'Anonymous user tries to post a comment, but anonymous ' - u'commenting is disabled. Or user does not have the ' - u"'reply to item' permission.", + "Anonymous user tries to post a comment, but anonymous " + "commenting is disabled. Or user does not have the " + "'reply to item' permission.", ) return comment - @button.buttonAndHandler(_(u'add_comment_button', default=u'Comment'), - name='comment') + @button.buttonAndHandler(_("add_comment_button", default="Comment"), name="comment") def handleComment(self, action): context = aq_inner(self.context) # Check if conversation is enabled on this content object if not self.__parent__.restrictedTraverse( - '@@conversation_view', + "@@conversation_view", ).enabled(): raise Unauthorized( - 'Discussion is not enabled for this content object.', + "Discussion is not enabled for this content object.", ) # Validation form @@ -246,28 +245,26 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # Validate Captcha registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) - portal_membership = getToolByName(self.context, 'portal_membership') - captcha_enabled = settings.captcha != 'disabled' + portal_membership = getToolByName(self.context, "portal_membership") + captcha_enabled = settings.captcha != "disabled" anonymous_comments = settings.anonymous_comments anon = portal_membership.isAnonymousUser() if captcha_enabled and anonymous_comments and anon: - if 'captcha' not in data: - data['captcha'] = u'' - captcha = CaptchaValidator(self.context, - self.request, - None, - ICaptcha['captcha'], - None) - captcha.validate(data['captcha']) + if "captcha" not in data: + data["captcha"] = "" + captcha = CaptchaValidator( + self.context, self.request, None, ICaptcha["captcha"], None + ) + captcha.validate(data["captcha"]) # Create comment comment = self.create_comment(data) # Add comment to conversation conversation = IConversation(self.__parent__) - if data['in_reply_to']: + if data["in_reply_to"]: # Add a reply to an existing comment - conversation_to_reply_to = conversation.get(data['in_reply_to']) + conversation_to_reply_to = conversation.get(data["in_reply_to"]) replies = IReplies(conversation_to_reply_to) comment_id = replies.addComment(comment) else: @@ -279,25 +276,24 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # shown to the user that his/her comment awaits moderation. If the user # has 'review comments' permission, he/she is redirected directly # to the comment. - can_review = getSecurityManager().checkPermission('Review comments', - context) - workflowTool = getToolByName(context, 'portal_workflow') + can_review = getSecurityManager().checkPermission("Review comments", context) + workflowTool = getToolByName(context, "portal_workflow") comment_review_state = workflowTool.getInfoFor( comment, - 'review_state', + "review_state", None, ) - if comment_review_state == 'pending' and not can_review: + if comment_review_state == "pending" and not can_review: # Show info message when comment moderation is enabled IStatusMessage(self.context.REQUEST).addStatusMessage( - _('Your comment awaits moderator approval.'), - type='info') + _("Your comment awaits moderator approval."), type="info" + ) self.request.response.redirect(self.action) else: # Redirect to comment (inside a content object page) - self.request.response.redirect(self.action + '#' + str(comment_id)) + self.request.response.redirect(self.action + "#" + str(comment_id)) - @button.buttonAndHandler(_(u'Cancel')) + @button.buttonAndHandler(_("Cancel")) def handleCancel(self, action): # This method should never be called, it's only there to show # a cancel button that is handled by a jQuery method. @@ -307,15 +303,15 @@ class CommentForm(extensible.ExtensibleForm, form.Form): class CommentsViewlet(ViewletBase): form = CommentForm - index = ViewPageTemplateFile('comments.pt') + index = ViewPageTemplateFile("comments.pt") def update(self): - super(CommentsViewlet, self).update() + super().update() discussion_allowed = self.is_discussion_allowed() anonymous_allowed_or_can_reply = ( - self.is_anonymous() and - self.anonymous_discussion_allowed() or - self.can_reply() + self.is_anonymous() + and self.anonymous_discussion_allowed() + or self.can_reply() ) if discussion_allowed and anonymous_allowed_or_can_reply: z2.switch_on(self, request_layer=IFormLayer) @@ -326,30 +322,29 @@ class CommentsViewlet(ViewletBase): # view methods def can_reply(self): - """Returns true if current user has the 'Reply to item' permission. - """ - return getSecurityManager().checkPermission('Reply to item', - aq_inner(self.context)) + """Returns true if current user has the 'Reply to item' permission.""" + return getSecurityManager().checkPermission( + "Reply to item", aq_inner(self.context) + ) def can_manage(self): """We keep this method for <= 1.0b9 backward compatibility. Since we do - not want any API changes in beta releases. + not want any API changes in beta releases. """ return self.can_review() def can_review(self): - """Returns true if current user has the 'Review comments' permission. - """ - return getSecurityManager().checkPermission('Review comments', - aq_inner(self.context)) + """Returns true if current user has the 'Review comments' permission.""" + return getSecurityManager().checkPermission( + "Review comments", aq_inner(self.context) + ) def can_delete_own(self, comment): """Returns true if the current user can delete the comment. Only comments without replies can be deleted. """ try: - return comment.restrictedTraverse( - '@@delete-own-comment').can_delete() + return comment.restrictedTraverse("@@delete-own-comment").can_delete() except Unauthorized: return False @@ -358,8 +353,7 @@ class CommentsViewlet(ViewletBase): no replies. This is used to prepare hidden form buttons for JS. """ try: - return comment.restrictedTraverse( - '@@delete-own-comment').could_delete() + return comment.restrictedTraverse("@@delete-own-comment").could_delete() except Unauthorized: return False @@ -367,58 +361,63 @@ class CommentsViewlet(ViewletBase): """Returns true if current user has the 'Edit comments' permission. """ - return getSecurityManager().checkPermission('Edit comments', - aq_inner(reply)) + return getSecurityManager().checkPermission("Edit comments", aq_inner(reply)) def can_delete(self, reply): """Returns true if current user has the 'Delete comments' permission. """ - return getSecurityManager().checkPermission('Delete comments', - aq_inner(reply)) + return getSecurityManager().checkPermission("Delete comments", aq_inner(reply)) def is_discussion_allowed(self): context = aq_inner(self.context) - return context.restrictedTraverse('@@conversation_view').enabled() + return context.restrictedTraverse("@@conversation_view").enabled() def comment_transform_message(self): """Returns the description that shows up above the comment text, - dependent on the text_transform setting and the comment moderation - workflow in the discussion control panel. + dependent on the text_transform setting and the comment moderation + workflow in the discussion control panel. """ context = aq_inner(self.context) registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) # text transform setting - if settings.text_transform == 'text/x-web-intelligent': - message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT), - context=self.request) - elif settings.text_transform == 'text/x-web-markdown': - message = translate(Message(COMMENT_DESCRIPTION_MARKDOWN), - context=self.request) + if settings.text_transform == "text/x-web-intelligent": + message = translate( + Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT), context=self.request + ) + elif settings.text_transform == "text/x-web-markdown": + message = translate( + Message(COMMENT_DESCRIPTION_MARKDOWN), context=self.request + ) else: - message = translate(Message(COMMENT_DESCRIPTION_PLAIN_TEXT), - context=self.request) + message = translate( + Message(COMMENT_DESCRIPTION_PLAIN_TEXT), context=self.request + ) # comment workflow - wftool = getToolByName(context, 'portal_workflow', None) - workflow_chain = wftool.getChainForPortalType('Discussion Item') + wftool = getToolByName(context, "portal_workflow", None) + workflow_chain = wftool.getChainForPortalType("Discussion Item") if workflow_chain: comment_workflow = workflow_chain[0] comment_workflow = wftool[comment_workflow] # check if the current workflow implements a pending state. If this # is true comments are moderated - if 'pending' in comment_workflow.states: - message = message + ' ' + \ - translate(Message(COMMENT_DESCRIPTION_MODERATION_ENABLED), - context=self.request) + if "pending" in comment_workflow.states: + message = ( + message + + " " + + translate( + Message(COMMENT_DESCRIPTION_MODERATION_ENABLED), + context=self.request, + ) + ) return message def has_replies(self, workflow_actions=False): - """Returns true if there are replies. - """ + """Returns true if there are replies.""" if self.get_replies(workflow_actions) is not None: try: next(self.get_replies(workflow_actions)) @@ -442,31 +441,32 @@ class CommentsViewlet(ViewletBase): if conversation is None: return iter([]) - wf = getToolByName(context, 'portal_workflow') + wf = getToolByName(context, "portal_workflow") # workflow_actions is only true when user # has 'Manage portal' permission def replies_with_workflow_actions(): # Generator that returns replies dict with workflow actions for r in conversation.getThreads(): - comment_obj = r['comment'] + comment_obj = r["comment"] # list all possible workflow actions actions = [ - a for a in wf.listActionInfos(object=comment_obj) - if a['category'] == 'workflow' and a['allowed'] + a + for a in wf.listActionInfos(object=comment_obj) + if a["category"] == "workflow" and a["allowed"] ] r = r.copy() - r['actions'] = actions + r["actions"] = actions yield r def published_replies(): # Generator that returns replies dict with workflow status. for r in conversation.getThreads(): - comment_obj = r['comment'] - workflow_status = wf.getInfoFor(comment_obj, 'review_state') - if workflow_status == 'published': + comment_obj = r["comment"] + workflow_status = wf.getInfoFor(comment_obj, "review_state") + if workflow_status == "published": r = r.copy() - r['workflow_status'] = workflow_status + r["workflow_status"] = workflow_status yield r # Return all direct replies @@ -480,20 +480,16 @@ class CommentsViewlet(ViewletBase): if username is None: return None else: - return '{0}/author/{1}'.format(self.context.portal_url(), username) + return f"{self.context.portal_url()}/author/{username}" def get_commenter_portrait(self, username=None): if username is None: # return the default user image if no username is given - return 'defaultUser.png' + return "defaultUser.png" else: - portal_membership = getToolByName(self.context, - 'portal_membership', - None) - return portal_membership\ - .getPersonalPortrait(username)\ - .absolute_url() + portal_membership = getToolByName(self.context, "portal_membership", None) + return portal_membership.getPersonalPortrait(username).absolute_url() def anonymous_discussion_allowed(self): # Check if anonymous comments are allowed in the registry @@ -520,20 +516,18 @@ class CommentsViewlet(ViewletBase): return settings.show_commenter_image def is_anonymous(self): - portal_membership = getToolByName(self.context, - 'portal_membership', - None) + portal_membership = getToolByName(self.context, "portal_membership", None) return portal_membership.isAnonymousUser() def login_action(self): - return '{0}/login_form?came_from={1}'.format( + return "{}/login_form?came_from={}".format( self.navigation_root_url, - quote(self.request.get('URL', '')), + quote(self.request.get("URL", "")), ) def format_time(self, time): # We have to transform Python datetime into Zope DateTime # before we can call toLocalizedTime. - util = getToolByName(self.context, 'translation_service') + util = getToolByName(self.context, "translation_service") zope_time = DateTime(time.isoformat()) return util.toLocalizedTime(zope_time, long_format=True) diff --git a/plone/app/discussion/browser/configure.zcml b/plone/app/discussion/browser/configure.zcml index 63f80a4..0892259 100644 --- a/plone/app/discussion/browser/configure.zcml +++ b/plone/app/discussion/browser/configure.zcml @@ -48,7 +48,7 @@ diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py index 272fbb0..b9a3e23 100644 --- a/plone/app/discussion/browser/controlpanel.py +++ b/plone/app/discussion/browser/controlpanel.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- -from plone.app.discussion.interfaces import _ -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.upgrades import update_registry +from ..interfaces import _ +from ..interfaces import IDiscussionSettings +from ..upgrades import update_registry from plone.app.registry.browser import controlpanel +from plone.base.interfaces.controlpanel import IConfigurationChangedEvent +from plone.base.interfaces.controlpanel import IMailSchema from plone.registry.interfaces import IRecordModifiedEvent from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces.controlpanel import IMailSchema from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.statusmessages.interfaces import IStatusMessage from z3c.form import button @@ -16,87 +16,76 @@ from zope.component import getUtility from zope.component import queryUtility from zope.component.hooks import getSite -# try/except was added because Configuration Changed Event was moved inside the -# controlpanel file in the PR #2495 on Products.CMFPlone -try: - from Products.CMFPlone.interfaces.controlpanel import IConfigurationChangedEvent # noqa: E501 -except ImportError: - from Products.CMFPlone.interfaces import IConfigurationChangedEvent - class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): - """Discussion settings form. - """ + """Discussion settings form.""" + schema = IDiscussionSettings - id = 'DiscussionSettingsEditForm' - label = _(u'Discussion settings') + id = "DiscussionSettingsEditForm" + label = _("Discussion settings") description = _( - u'help_discussion_settings_editform', - default=u'Some discussion related settings are not ' - u'located in the Discussion Control Panel.\n' - u'To enable comments for a specific content type, ' - u'go to the Types Control Panel of this type and ' - u'choose "Allow comments".\n' - u'To enable the moderation workflow for comments, ' - u'go to the Types Control Panel, choose ' - u'"Comment" and set workflow to ' - u'"Comment Review Workflow".', + "help_discussion_settings_editform", + default="Some discussion related settings are not " + "located in the Discussion Control Panel.\n" + "To enable comments for a specific content type, " + "go to the Types Control Panel of this type and " + 'choose "Allow comments".\n' + "To enable the moderation workflow for comments, " + "go to the Types Control Panel, choose " + '"Comment" and set workflow to ' + '"Comment Review Workflow".', ) def updateFields(self): - super(DiscussionSettingsEditForm, self).updateFields() - self.fields['globally_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['moderation_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['edit_comment_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['delete_own_comment_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['anonymous_comments'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['show_commenter_image'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['moderator_notification_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget - self.fields['user_notification_enabled'].widgetFactory = \ - SingleCheckBoxFieldWidget + super().updateFields() + self.fields["globally_enabled"].widgetFactory = SingleCheckBoxFieldWidget + self.fields["moderation_enabled"].widgetFactory = SingleCheckBoxFieldWidget + self.fields["edit_comment_enabled"].widgetFactory = SingleCheckBoxFieldWidget + self.fields[ + "delete_own_comment_enabled" + ].widgetFactory = SingleCheckBoxFieldWidget + self.fields["anonymous_comments"].widgetFactory = SingleCheckBoxFieldWidget + self.fields["show_commenter_image"].widgetFactory = SingleCheckBoxFieldWidget + self.fields[ + "moderator_notification_enabled" + ].widgetFactory = SingleCheckBoxFieldWidget + self.fields[ + "user_notification_enabled" + ].widgetFactory = SingleCheckBoxFieldWidget def updateWidgets(self): try: - super(DiscussionSettingsEditForm, self).updateWidgets() + super().updateWidgets() except KeyError: # upgrade profile not visible in prefs_install_products_form # provide auto-upgrade update_registry(self.context) - super(DiscussionSettingsEditForm, self).updateWidgets() - self.widgets['globally_enabled'].label = _(u'Enable Comments') - self.widgets['anonymous_comments'].label = _(u'Anonymous Comments') - self.widgets['show_commenter_image'].label = _(u'Commenter Image') - self.widgets['moderator_notification_enabled'].label = _( - u'Moderator Email Notification', + super().updateWidgets() + self.widgets["globally_enabled"].label = _("Enable Comments") + self.widgets["anonymous_comments"].label = _("Anonymous Comments") + self.widgets["show_commenter_image"].label = _("Commenter Image") + self.widgets["moderator_notification_enabled"].label = _( + "Moderator Email Notification", ) - self.widgets['user_notification_enabled'].label = _( - u'User Email Notification', + self.widgets["user_notification_enabled"].label = _( + "User Email Notification", ) - @button.buttonAndHandler(_('Save'), name=None) + @button.buttonAndHandler(_("Save"), name=None) def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) - IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'), - 'info') - self.context.REQUEST.RESPONSE.redirect('@@discussion-controlpanel') + IStatusMessage(self.request).addStatusMessage(_("Changes saved"), "info") + self.context.REQUEST.RESPONSE.redirect("@@discussion-controlpanel") - @button.buttonAndHandler(_('Cancel'), name='cancel') + @button.buttonAndHandler(_("Cancel"), name="cancel") def handleCancel(self, action): - IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'), - 'info') + IStatusMessage(self.request).addStatusMessage(_("Edit cancelled"), "info") self.request.response.redirect( - '{0}/{1}'.format( + "{}/{}".format( self.context.absolute_url(), self.control_panel_view, ), @@ -104,15 +93,15 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): - """Discussion settings control panel. - """ + """Discussion settings control panel.""" + form = DiscussionSettingsEditForm - index = ViewPageTemplateFile('controlpanel.pt') + index = ViewPageTemplateFile("controlpanel.pt") def __call__(self): self.mailhost_warning() self.custom_comment_workflow_warning() - return super(DiscussionSettingsControlPanel, self).__call__() + return super().__call__() @property def site_url(self): @@ -123,43 +112,44 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): def settings(self): """Compose a string that contains all registry settings that are - needed for the discussion control panel. + needed for the discussion control panel. """ registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) - wftool = getToolByName(self.context, 'portal_workflow', None) - workflow_chain = wftool.getChainForPortalType('Discussion Item') + wftool = getToolByName(self.context, "portal_workflow", None) + workflow_chain = wftool.getChainForPortalType("Discussion Item") output = [] # Globally enabled if settings.globally_enabled: - output.append('globally_enabled') + output.append("globally_enabled") # Comment moderation - one_state_worklow_disabled = \ - 'comment_one_state_workflow' not in workflow_chain - comment_review_workflow_disabled = \ - 'comment_review_workflow' not in workflow_chain + one_state_worklow_disabled = "comment_one_state_workflow" not in workflow_chain + comment_review_workflow_disabled = ( + "comment_review_workflow" not in workflow_chain + ) if one_state_worklow_disabled and comment_review_workflow_disabled: - output.append('moderation_custom') + output.append("moderation_custom") elif settings.moderation_enabled: - output.append('moderation_enabled') + output.append("moderation_enabled") if settings.edit_comment_enabled: - output.append('edit_comment_enabled') + output.append("edit_comment_enabled") if settings.delete_own_comment_enabled: - output.append('delete_own_comment_enabled') + output.append("delete_own_comment_enabled") # Anonymous comments if settings.anonymous_comments: - output.append('anonymous_comments') + output.append("anonymous_comments") # Invalid mail setting - ctrlOverview = getMultiAdapter((self.context, self.request), - name='overview-controlpanel') + ctrlOverview = getMultiAdapter( + (self.context, self.request), name="overview-controlpanel" + ) if ctrlOverview.mailhost_warning(): - output.append('invalid_mail_setup') + output.append("invalid_mail_setup") # Workflow if workflow_chain: @@ -167,69 +157,71 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): output.append(discussion_workflow) # Merge all settings into one string - return ' '.join(output) + return " ".join(output) def mailhost_warning(self): - """Returns true if mailhost is not configured properly. - """ + """Returns true if mailhost is not configured properly.""" # Copied from Products.CMFPlone/controlpanel/browser/overview.py registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') + mail_settings = registry.forInterface(IMailSchema, prefix="plone") mailhost = mail_settings.smtp_host email = mail_settings.email_from_address if mailhost and email: pass else: - message = _(u'discussion_text_no_mailhost_configured', - default=u'You have not configured a mail host or a site \'From\' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.') # noqa: E501 - IStatusMessage(self.request).addStatusMessage(message, 'warning') + message = _( + "discussion_text_no_mailhost_configured", + default="You have not configured a mail host or a site 'From' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.", + ) # noqa: E501 + IStatusMessage(self.request).addStatusMessage(message, "warning") def custom_comment_workflow_warning(self): """Return True if a custom comment workflow is enabled.""" - wftool = getToolByName(self.context, 'portal_workflow', None) - workflow_chain = wftool.getChainForPortalType('Discussion Item') - one_state_workflow_enabled = \ - 'comment_one_state_workflow' in workflow_chain - comment_review_workflow_enabled = \ - 'comment_review_workflow' in workflow_chain + wftool = getToolByName(self.context, "portal_workflow", None) + workflow_chain = wftool.getChainForPortalType("Discussion Item") + one_state_workflow_enabled = "comment_one_state_workflow" in workflow_chain + comment_review_workflow_enabled = "comment_review_workflow" in workflow_chain if one_state_workflow_enabled or comment_review_workflow_enabled: pass else: - message = _(u'discussion_text_custom_comment_workflow', - default=u'You have configured a custom workflow for the \'Discussion Item\' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default \'Discussion Item\' workflows. Go to the Types control panel to choose a workflow for the \'Discussion Item\' type.') # noqa: E501 - IStatusMessage(self.request).addStatusMessage(message, 'warning') + message = _( + "discussion_text_custom_comment_workflow", + default="You have configured a custom workflow for the 'Discussion Item' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default 'Discussion Item' workflows. Go to the Types control panel to choose a workflow for the 'Discussion Item' type.", + ) # noqa: E501 + IStatusMessage(self.request).addStatusMessage(message, "warning") def notify_configuration_changed(event): - """Event subscriber that is called every time the configuration changed. - """ + """Event subscriber that is called every time the configuration changed.""" portal = getSite() - wftool = getToolByName(portal, 'portal_workflow', None) + wftool = getToolByName(portal, "portal_workflow", None) if IRecordModifiedEvent.providedBy(event): # Discussion control panel setting changed - if event.record.fieldName == 'moderation_enabled': + if event.record.fieldName == "moderation_enabled": # Moderation enabled has changed if event.record.value is True: # Enable moderation workflow - wftool.setChainForPortalTypes(('Discussion Item',), - 'comment_review_workflow') + wftool.setChainForPortalTypes( + ("Discussion Item",), "comment_review_workflow" + ) else: # Disable moderation workflow - wftool.setChainForPortalTypes(('Discussion Item',), - 'comment_one_state_workflow') + wftool.setChainForPortalTypes( + ("Discussion Item",), "comment_one_state_workflow" + ) if IConfigurationChangedEvent.providedBy(event): # Types control panel setting changed - if 'workflow' in event.data: + if "workflow" in event.data: registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) - workflow_chain = wftool.getChainForPortalType('Discussion Item') + workflow_chain = wftool.getChainForPortalType("Discussion Item") if workflow_chain: workflow = workflow_chain[0] - if workflow == 'comment_one_state_workflow': + if workflow == "comment_one_state_workflow": settings.moderation_enabled = False - elif workflow == 'comment_review_workflow': + elif workflow == "comment_review_workflow": settings.moderation_enabled = True else: # Custom workflow diff --git a/plone/app/discussion/browser/conversation.py b/plone/app/discussion/browser/conversation.py index 0017da4..ac0c932 100644 --- a/plone/app/discussion/browser/conversation.py +++ b/plone/app/discussion/browser/conversation.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- +from ..interfaces import IDiscussionSettings from Acquisition import aq_base from Acquisition import aq_chain from Acquisition import aq_inner -from plone.app.discussion.interfaces import IDiscussionSettings +from plone.base.interfaces import INonStructuralFolder +from plone.base.interfaces import IPloneSiteRoot +from plone.base.utils import safe_hasattr from plone.registry.interfaces import IRegistry from Products.CMFCore.interfaces import IFolderish from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces import INonStructuralFolder -from Products.CMFPlone.interfaces import IPloneSiteRoot -from Products.CMFPlone.utils import safe_hasattr from zope.component import queryUtility try: from plone.dexterity.interfaces import IDexterityContent + DEXTERITY_INSTALLED = True except ImportError: DEXTERITY_INSTALLED = False @@ -26,15 +26,14 @@ def traverse_parents(context): if not IPloneSiteRoot.providedBy(obj): obj_is_folderish = IFolderish.providedBy(obj) obj_is_stuctural = not INonStructuralFolder.providedBy(obj) - if (obj_is_folderish and obj_is_stuctural): - flag = getattr(obj, 'allow_discussion', None) + if obj_is_folderish and obj_is_stuctural: + flag = getattr(obj, "allow_discussion", None) if flag is not None: return flag return None -class ConversationView(object): - +class ConversationView: def enabled(self): if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context): return self._enabled_for_dexterity_types() @@ -42,7 +41,7 @@ class ConversationView(object): return self._enabled_for_archetypes() 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 discussion is enabled on a specific content object: @@ -82,7 +81,7 @@ class ConversationView(object): return False # If discussion is disabled for the object, bail out - obj_flag = getattr(aq_base(context), 'allow_discussion', None) + obj_flag = getattr(aq_base(context), "allow_discussion", None) if obj_flag is False: return False @@ -91,16 +90,16 @@ class ConversationView(object): folder_allow_discussion = traverse_parents(context) if folder_allow_discussion: - if not getattr(self, 'allow_discussion', None): + if not getattr(self, "allow_discussion", None): return True else: if obj_flag: return True # Check if discussion is allowed on the content type - portal_types = getToolByName(self, 'portal_types') + portal_types = getToolByName(self, "portal_types") document_fti = getattr(portal_types, context.portal_type) - if not document_fti.getProperty('allow_discussion'): + if not document_fti.getProperty("allow_discussion"): # If discussion is not allowed on the content type, # check if 'allow discussion' is overridden on the content object. if not obj_flag: @@ -109,7 +108,7 @@ class ConversationView(object): return True 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 discussion is enable on a specific content object: @@ -134,11 +133,11 @@ class ConversationView(object): return False # Check if discussion is allowed on the content object - if safe_hasattr(context, 'allow_discussion'): + if safe_hasattr(context, "allow_discussion"): if context.allow_discussion is not None: return context.allow_discussion # Check if discussion is allowed on the content type - portal_types = getToolByName(self, 'portal_types') + portal_types = getToolByName(self, "portal_types") document_fti = getattr(portal_types, context.portal_type) - return document_fti.getProperty('allow_discussion') + return document_fti.getProperty("allow_discussion") diff --git a/plone/app/discussion/browser/moderation.py b/plone/app/discussion/browser/moderation.py index 99d6216..1d0ee8f 100644 --- a/plone/app/discussion/browser/moderation.py +++ b/plone/app/discussion/browser/moderation.py @@ -1,11 +1,10 @@ -# coding: utf-8 from AccessControl import getSecurityManager from AccessControl import Unauthorized from Acquisition import aq_inner from Acquisition import aq_parent +from plone.app.discussion.events import CommentDeletedEvent from plone.app.discussion.events import CommentPublishedEvent from plone.app.discussion.events import CommentTransitionEvent -from plone.app.discussion.events import CommentDeletedEvent from plone.app.discussion.interfaces import _ from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IReplies @@ -18,21 +17,20 @@ from zope.event import notify # Translations for generated values in buttons # States -_('comment_pending', default='pending') +_("comment_pending", default="pending") # _('comment_approved', default='published') -_('comment_published', default='published') -_('comment_rejected', default='rejected') -_('comment_spam', default='marked as spam') +_("comment_published", default="published") +_("comment_rejected", default="rejected") +_("comment_spam", default="marked as spam") # Transitions -_('Recall') -_('Approve') -_('Reject') -_('Spam') +_("Recall") +_("Approve") +_("Reject") +_("Spam") PMF = _ class TranslationHelper(BrowserView): - def translate(self, text=""): return _(text) @@ -44,22 +42,21 @@ class TranslationHelper(BrowserView): class View(BrowserView): """Show comment moderation view.""" - template = ViewPageTemplateFile('moderation.pt') + template = ViewPageTemplateFile("moderation.pt") try: - template.id = '@@moderate-comments' + template.id = "@@moderate-comments" except AttributeError: # id is not writeable in Zope 2.12 pass def __init__(self, context, request): - super(View, self).__init__(context, request) - self.workflowTool = getToolByName(self.context, 'portal_workflow') + super().__init__(context, request) + self.workflowTool = getToolByName(self.context, "portal_workflow") self.transitions = [] def __call__(self): - self.request.set('disable_border', True) - self.request.set('review_state', - self.request.get('review_state', 'pending')) + self.request.set("disable_border", True) + self.request.set("review_state", self.request.get("review_state", "pending")) return self.template() def comments(self): @@ -67,15 +64,19 @@ class View(BrowserView): review_state is string or list of strings. """ - catalog = getToolByName(self.context, 'portal_catalog') - if self.request.review_state == 'all': - return catalog(object_provides=IComment.__identifier__, - sort_on='created', - sort_order='reverse') - return catalog(object_provides=IComment.__identifier__, - review_state=self.request.review_state, - sort_on='created', - sort_order='reverse') + catalog = getToolByName(self.context, "portal_catalog") + if self.request.review_state == "all": + return catalog( + object_provides=IComment.__identifier__, + sort_on="created", + sort_order="reverse", + ) + return catalog( + object_provides=IComment.__identifier__, + review_state=self.request.review_state, + sort_on="created", + sort_order="reverse", + ) def moderation_enabled(self): """Return true if a review workflow is enabled on 'Discussion Item' @@ -84,11 +85,10 @@ class View(BrowserView): A 'review workflow' is characterized by implementing a 'pending' workflow state. """ - workflows = self.workflowTool.getChainForPortalType( - 'Discussion Item') + workflows = self.workflowTool.getChainForPortalType("Discussion Item") if workflows: comment_workflow = self.workflowTool[workflows[0]] - if 'pending' in comment_workflow.states: + if "pending" in comment_workflow.states: return True return False @@ -100,11 +100,10 @@ class View(BrowserView): A 'review multipe state workflow' is characterized by implementing a 'rejected' workflow state and a 'spam' workflow state. """ - workflows = self.workflowTool.getChainForPortalType( - 'Discussion Item') + workflows = self.workflowTool.getChainForPortalType("Discussion Item") if workflows: comment_workflow = self.workflowTool[workflows[0]] - if 'spam' in comment_workflow.states: + if "spam" in comment_workflow.states: return True return False @@ -125,27 +124,26 @@ class View(BrowserView): """ if obj: transitions = [ - a for a in self.workflowTool.listActionInfos(object=obj) - if a['category'] == 'workflow' and a['allowed'] - ] + a + for a in self.workflowTool.listActionInfos(object=obj) + if a["category"] == "workflow" and a["allowed"] + ] return transitions class ModerateCommentsEnabled(BrowserView): - def __call__(self): """Returns true if a 'review workflow' is enabled on 'Discussion Item' - content type. A 'review workflow' is characterized by implementing - a 'pending' workflow state. + content type. A 'review workflow' is characterized by implementing + a 'pending' workflow state. """ context = aq_inner(self.context) - workflowTool = getToolByName(context, 'portal_workflow', None) - comment_workflow = workflowTool.getChainForPortalType( - 'Discussion Item') + workflowTool = getToolByName(context, "portal_workflow", None) + comment_workflow = workflowTool.getChainForPortalType("Discussion Item") if comment_workflow: comment_workflow = comment_workflow[0] comment_workflow = workflowTool[comment_workflow] - if 'pending' in comment_workflow.states: + if "pending" in comment_workflow.states: return True return False @@ -184,13 +182,15 @@ class DeleteComment(BrowserView): content_object.reindexObject() notify(CommentDeletedEvent(self.context, comment)) IStatusMessage(self.context.REQUEST).addStatusMessage( - _('Comment deleted.'), - type='info') + _("Comment deleted."), type="info" + ) came_from = self.context.REQUEST.HTTP_REFERER # if the referrer already has a came_from in it, don't redirect back - if (len(came_from) == 0 or 'came_from=' in came_from or - not getToolByName( - content_object, 'portal_url').isURLInPortal(came_from)): + if ( + len(came_from) == 0 + or "came_from=" in came_from + or not getToolByName(content_object, "portal_url").isURLInPortal(came_from) + ): came_from = content_object.absolute_url() return self.context.REQUEST.RESPONSE.redirect(came_from) @@ -198,8 +198,7 @@ class DeleteComment(BrowserView): """Returns true if current user has the 'Delete comments' permission. """ - return getSecurityManager().checkPermission('Delete comments', - aq_inner(reply)) + return getSecurityManager().checkPermission("Delete comments", aq_inner(reply)) class DeleteOwnComment(DeleteComment): @@ -213,26 +212,23 @@ class DeleteOwnComment(DeleteComment): """ def could_delete(self, comment=None): - """Returns true if the comment could be deleted if it had no replies. - """ + """Returns true if the comment could be deleted if it had no replies.""" sm = getSecurityManager() comment = comment or aq_inner(self.context) userid = sm.getUser().getId() - return ( - sm.checkPermission('Delete own comments', comment) and - 'Owner' in comment.get_local_roles_for_userid(userid) - ) + return sm.checkPermission( + "Delete own comments", comment + ) and "Owner" in comment.get_local_roles_for_userid(userid) def can_delete(self, comment=None): comment = comment or self.context - return ( - len(IReplies(aq_inner(comment))) == 0 and - self.could_delete(comment=comment) + return len(IReplies(aq_inner(comment))) == 0 and self.could_delete( + comment=comment ) def __call__(self): if self.can_delete(): - super(DeleteOwnComment, self).__call__() + super().__call__() else: raise Unauthorized("You're not allowed to delete this comment.") @@ -262,33 +258,37 @@ class CommentTransition(BrowserView): """Call CommentTransition.""" comment = aq_inner(self.context) content_object = aq_parent(aq_parent(comment)) - workflow_action = self.request.form.get('workflow_action', 'publish') - workflowTool = getToolByName(self.context, 'portal_workflow') + workflow_action = self.request.form.get("workflow_action", "publish") + workflowTool = getToolByName(self.context, "portal_workflow") workflowTool.doActionFor(comment, workflow_action) comment.reindexObject() - content_object.reindexObject(idxs=['total_comments']) + content_object.reindexObject(idxs=["total_comments"]) notify(CommentPublishedEvent(self.context, comment)) # for complexer workflows: notify(CommentTransitionEvent(self.context, comment)) - comment_state_translated = '' + comment_state_translated = "" if workflowTool.getWorkflowsFor(comment): - review_state_new = workflowTool.getInfoFor(ob=comment, name='review_state') + review_state_new = workflowTool.getInfoFor(ob=comment, name="review_state") helper = self.context.restrictedTraverse("translationhelper") - comment_state_translated = helper.translate_comment_review_state(review_state_new) + comment_state_translated = helper.translate_comment_review_state( + review_state_new + ) msgid = _( "comment_transmitted", - default='Comment ${comment_state_translated}.', - mapping={"comment_state_translated": comment_state_translated}) + default="Comment ${comment_state_translated}.", + mapping={"comment_state_translated": comment_state_translated}, + ) translated = self.context.translate(msgid) - IStatusMessage(self.request).add(translated, type='info') + IStatusMessage(self.request).add(translated, type="info") came_from = self.context.REQUEST.HTTP_REFERER # if the referrer already has a came_from in it, don't redirect back - if (len(came_from) == 0 - or 'came_from=' in came_from - or not getToolByName( - content_object, 'portal_url').isURLInPortal(came_from)): + if ( + len(came_from) == 0 + or "came_from=" in came_from + or not getToolByName(content_object, "portal_url").isURLInPortal(came_from) + ): came_from = content_object.absolute_url() return self.context.REQUEST.RESPONSE.redirect(came_from) @@ -317,19 +317,19 @@ class BulkActionsView(BrowserView): """ def __init__(self, context, request): - super(BulkActionsView, self).__init__(context, request) - self.workflowTool = getToolByName(context, 'portal_workflow') + super().__init__(context, request) + self.workflowTool = getToolByName(context, "portal_workflow") def __call__(self): """Call BulkActionsView.""" - if 'form.select.BulkAction' in self.request: - bulkaction = self.request.get('form.select.BulkAction') - self.paths = self.request.get('paths') + if "form.select.BulkAction" in self.request: + bulkaction = self.request.get("form.select.BulkAction") + self.paths = self.request.get("paths") if self.paths: - if bulkaction == '-1': + if bulkaction == "-1": # no bulk action was selected pass - elif bulkaction == 'delete': + elif bulkaction == "delete": self.delete() else: self.transmit(bulkaction) @@ -346,13 +346,14 @@ class BulkActionsView(BrowserView): comment = context.restrictedTraverse(path) content_object = aq_parent(aq_parent(comment)) allowed_transitions = [ - transition['id'] for transition in self.workflowTool.listActionInfos(object=comment) - if transition['category'] == 'workflow' and transition['allowed'] - ] + transition["id"] + for transition in self.workflowTool.listActionInfos(object=comment) + if transition["category"] == "workflow" and transition["allowed"] + ] if action in allowed_transitions: self.workflowTool.doActionFor(comment, action) comment.reindexObject() - content_object.reindexObject(idxs=['total_comments']) + content_object.reindexObject(idxs=["total_comments"]) notify(CommentPublishedEvent(content_object, comment)) # for complexer workflows: notify(CommentTransitionEvent(self.context, comment)) @@ -370,5 +371,5 @@ class BulkActionsView(BrowserView): conversation = aq_parent(comment) content_object = aq_parent(conversation) del conversation[comment.id] - content_object.reindexObject(idxs=['total_comments']) + content_object.reindexObject(idxs=["total_comments"]) notify(CommentDeletedEvent(content_object, comment)) diff --git a/plone/app/discussion/browser/traversal.py b/plone/app/discussion/browser/traversal.py index 1e2251c..f626621 100644 --- a/plone/app/discussion/browser/traversal.py +++ b/plone/app/discussion/browser/traversal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Implement the ++comments++ traversal namespace. This should return the IDiscussion container for the context, from which traversal will continue into an actual comment object. @@ -15,7 +14,7 @@ from zope.traversing.interfaces import TraversalError @implementer(ITraversable) @adapter(Interface, IBrowserRequest) -class ConversationNamespace(object): +class ConversationNamespace: """Allow traversal into a conversation via a ++conversation++name namespace. The name is the name of an adapter from context to IConversation. The special name 'default' will be taken as the default @@ -29,8 +28,8 @@ class ConversationNamespace(object): def traverse(self, name, ignore): - if name == 'default': - name = u'' + if name == "default": + name = "" conversation = queryAdapter(self.context, IConversation, name=name) if conversation is None: diff --git a/plone/app/discussion/browser/validator.py b/plone/app/discussion/browser/validator.py index d1582e9..9d28bd9 100644 --- a/plone/app/discussion/browser/validator.py +++ b/plone/app/discussion/browser/validator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Captcha validator, see captcha.txt for design notes. """ from Acquisition import aq_inner @@ -39,16 +38,17 @@ class CaptchaValidator(validator.SimpleFieldValidator): # We adapt the CaptchaValidator class to all form fields (IField) def validate(self, value): - super(CaptchaValidator, self).validate(value) + super().validate(value) registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) - if settings.captcha in ('captcha', 'recaptcha', 'norobots'): - captcha = getMultiAdapter((aq_inner(self.context), self.request), - name=settings.captcha) + if settings.captcha in ("captcha", "recaptcha", "norobots"): + captcha = getMultiAdapter( + (aq_inner(self.context), self.request), name=settings.captcha + ) if not captcha.verify(input=value): - if settings.captcha == 'norobots': + if settings.captcha == "norobots": raise WrongNorobotsAnswer else: raise WrongCaptchaCode @@ -57,5 +57,4 @@ class CaptchaValidator(validator.SimpleFieldValidator): # Register Captcha validator for the Captcha field in the ICaptcha Form -validator.WidgetValidatorDiscriminators(CaptchaValidator, - field=ICaptcha['captcha']) +validator.WidgetValidatorDiscriminators(CaptchaValidator, field=ICaptcha["captcha"]) diff --git a/plone/app/discussion/catalog.py b/plone/app/discussion/catalog.py index 73c5ab2..6413d11 100644 --- a/plone/app/discussion/catalog.py +++ b/plone/app/discussion/catalog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Catalog indexers, using plone.indexer. These will populate standard catalog indexes with values based on the IComment interface. @@ -7,13 +6,12 @@ Also provide event handlers to actually catalog the comments. from DateTime import DateTime from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IConversation +from plone.base.utils import safe_text from plone.indexer import indexer from plone.uuid.interfaces import IUUID from Products.CMFCore.interfaces import IContentish -from Products.CMFPlone.utils import safe_unicode from Products.ZCatalog.interfaces import IZCatalog -import six MAX_DESCRIPTION = 25 @@ -24,7 +22,7 @@ MAX_DESCRIPTION = 25 def total_comments(object): # Total number of comments on a conversation # Indexers won't work on old discussion items - if object.meta_type != 'Discussion Item': + if object.meta_type != "Discussion Item": try: conversation = IConversation(object) return conversation.total_comments() @@ -38,7 +36,7 @@ def total_comments(object): def last_comment_date(object): # Date of the latest comment on a conversation # Indexers won't work on old discussion items - if object.meta_type != 'Discussion Item': + if object.meta_type != "Discussion Item": try: conversation = IConversation(object) return conversation.last_comment_date @@ -52,7 +50,7 @@ def last_comment_date(object): def commentators(object): # List of commentators on a conversation # Indexers won't work on old discussion items - if object.meta_type != 'Discussion Item': + if object.meta_type != "Discussion Item": try: conversation = IConversation(object) return conversation.public_commentators @@ -61,6 +59,7 @@ def commentators(object): # implemented an adapter for it pass + # Comment Indexers @@ -73,26 +72,24 @@ def title(object): def creator(object): if not object.creator: return - value = safe_unicode(object.creator) - if six.PY2: - return value.encode('utf8') + value = safe_text(object.creator) return value @indexer(IComment) def description(object): # Return the first 25 words of the comment text and append ' [...]' - text = ' '.join( - object.getText(targetMimetype='text/plain').split()[:MAX_DESCRIPTION], + text = " ".join( + object.getText(targetMimetype="text/plain").split()[:MAX_DESCRIPTION], ) if len(object.getText().split()) > 25: - text += ' [...]' + text += " [...]" return text @indexer(IComment) def searchable_text(object): - return object.getText(targetMimetype='text/plain') + return object.getText(targetMimetype="text/plain") @indexer(IComment) @@ -106,42 +103,42 @@ def in_response_to(object): def effective(object): # the catalog index needs Zope DateTime instead of Python datetime return DateTime( - object.creation_date.year, - object.creation_date.month, - object.creation_date.day, - object.creation_date.hour, - object.creation_date.minute, - object.creation_date.second, - 'GMT', - ) + object.creation_date.year, + object.creation_date.month, + object.creation_date.day, + object.creation_date.hour, + object.creation_date.minute, + object.creation_date.second, + "GMT", + ) @indexer(IComment) def created(object): # the catalog index needs Zope DateTime instead of Python datetime return DateTime( - object.creation_date.year, - object.creation_date.month, - object.creation_date.day, - object.creation_date.hour, - object.creation_date.minute, - object.creation_date.second, - 'GMT', - ) + object.creation_date.year, + object.creation_date.month, + object.creation_date.day, + object.creation_date.hour, + object.creation_date.minute, + object.creation_date.second, + "GMT", + ) @indexer(IComment) def modified(object): # the catalog index needs Zope DateTime instead of Python datetime return DateTime( - object.modification_date.year, - object.modification_date.month, - object.modification_date.day, - object.modification_date.hour, - object.modification_date.minute, - object.modification_date.second, - 'GMT', - ) + object.modification_date.year, + object.modification_date.month, + object.modification_date.day, + object.modification_date.hour, + object.modification_date.minute, + object.modification_date.second, + "GMT", + ) # Override the conversation indexers for comments diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py index cc1099f..438e726 100644 --- a/plone/app/discussion/comment.py +++ b/plone/app/discussion/comment.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """The default comment class and factory. """ from AccessControl import ClassSecurityInfo @@ -21,14 +20,14 @@ from plone.app.discussion.events import ReplyRemovedEvent from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IDiscussionSettings +from plone.base.interfaces.controlpanel import IMailSchema +from plone.base.utils import safe_text from plone.registry.interfaces import IRegistry from Products.CMFCore import permissions from Products.CMFCore.CMFCatalogAware import CatalogAware from Products.CMFCore.CMFCatalogAware import WorkflowAware from Products.CMFCore.DynamicType import DynamicType from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces.controlpanel import IMailSchema -from Products.CMFPlone.utils import safe_unicode from smtplib import SMTPException from zope.annotation.interfaces import IAnnotatable from zope.component import getUtility @@ -40,61 +39,69 @@ from zope.i18nmessageid import Message from zope.interface import implementer import logging -import six COMMENT_TITLE = _( - u'comment_title', - default=u'${author_name} on ${content}', - ) + "comment_title", + default="${author_name} on ${content}", +) MAIL_NOTIFICATION_MESSAGE = _( - u'mail_notification_message', - default=u'A comment on "${title}" ' - u'has been posted here: ${link}\n\n' - u'---\n' - u'${text}\n' - u'---\n', - ) + "mail_notification_message", + default='A comment on "${title}" ' + "has been posted here: ${link}\n\n" + "---\n" + "${text}\n" + "---\n", +) MAIL_NOTIFICATION_MESSAGE_MODERATOR = _( - u'mail_notification_message_moderator2', - default=u'A comment on "${title}" ' - u'has been posted by ${commentator}\n' - u'here: ${link}\n\n' - u'---\n\n' - u'${text}\n\n' - u'---\n\n' - u'Log in to moderate.\n\n', - ) + "mail_notification_message_moderator2", + default='A comment on "${title}" ' + "has been posted by ${commentator}\n" + "here: ${link}\n\n" + "---\n\n" + "${text}\n\n" + "---\n\n" + "Log in to moderate.\n\n", +) -logger = logging.getLogger('plone.app.discussion') +logger = logging.getLogger("plone.app.discussion") @implementer(IComment) -class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, - RoleManager, Owned, Implicit, Persistent): +class Comment( + CatalogAware, + WorkflowAware, + DynamicType, + Traversable, + RoleManager, + Owned, + Implicit, + Persistent, +): """A comment. This object attempts to be as lightweight as possible. We implement a number of standard methods instead of subclassing, to have total control over what goes into the object. """ + security = ClassSecurityInfo() - meta_type = portal_type = 'Discussion Item' + meta_type = portal_type = "Discussion Item" # This needs to be kept in sync with types/Discussion_Item.xml title - fti_title = 'Comment' + fti_title = "Comment" __parent__ = None comment_id = None # long in_reply_to = None # long - title = u'' + title = "" mime_type = None - text = u'' + text = "" creator = None creation_date = None @@ -113,19 +120,22 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, def __init__(self): self.creation_date = self.modification_date = datetime.utcnow() - self.mime_type = 'text/plain' + self.mime_type = "text/plain" user = getSecurityManager().getUser() if user and user.getId(): aclpath = [x for x in user.getPhysicalPath() if x] - self._owner = (aclpath, user.getId(),) + self._owner = ( + aclpath, + user.getId(), + ) self.__ac_local_roles__ = { - user.getId(): ['Owner'], + user.getId(): ["Owner"], } @property def __name__(self): - return self.comment_id and six.text_type(self.comment_id) or None + return self.comment_id and str(self.comment_id) or None @property def id(self): @@ -137,32 +147,30 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, def getText(self, targetMimetype=None): """The body text of a comment.""" - transforms = getToolByName(self, 'portal_transforms') + transforms = getToolByName(self, "portal_transforms") if targetMimetype is None: - targetMimetype = 'text/x-html-safe' + targetMimetype = "text/x-html-safe" - sourceMimetype = getattr(self, 'mime_type', None) + sourceMimetype = getattr(self, "mime_type", None) if sourceMimetype is None: registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) sourceMimetype = settings.text_transform text = self.text if text is None: - return '' - if six.PY2 and isinstance(text, six.text_type): - text = text.encode('utf8') + return "" transform = transforms.convertTo( - targetMimetype, - text, - context=self, - mimetype=sourceMimetype) + targetMimetype, text, context=self, mimetype=sourceMimetype + ) if transform: return transform.getData() else: - logger = logging.getLogger('plone.app.discussion') - msg = u'Transform "{0}" => "{1}" not available. Failed to ' \ - u'transform comment "{2}".' + logger = logging.getLogger("plone.app.discussion") + msg = ( + 'Transform "{0}" => "{1}" not available. Failed to ' + 'transform comment "{2}".' + ) logger.error( msg.format( sourceMimetype, @@ -182,8 +190,8 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, author_name = translate( Message( _( - u'label_anonymous', - default=u'Anonymous', + "label_anonymous", + default="Anonymous", ), ), ) @@ -194,9 +202,14 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, # conversation, the parent of the conversation is the content object). content = aq_base(self.__parent__.__parent__) title = translate( - Message(COMMENT_TITLE, - mapping={'author_name': safe_unicode(author_name), - 'content': safe_unicode(content.Title())})) + Message( + COMMENT_TITLE, + mapping={ + "author_name": safe_text(author_name), + "content": safe_text(content.Title()), + }, + ) + ) return title def Creator(self): @@ -224,25 +237,23 @@ CommentFactory = Factory(Comment) def notify_workflow(obj, event): - """Tell the workflow tool when a comment is added - """ - tool = getToolByName(obj, 'portal_workflow', None) + """Tell the workflow tool when a comment is added""" + tool = getToolByName(obj, "portal_workflow", None) if tool is not None: tool.notifyCreated(obj) def notify_content_object(obj, event): - """Tell the content object when a comment is added - """ + """Tell the content object when a comment is added""" content_obj = aq_parent(aq_parent(obj)) - content_obj.reindexObject(idxs=('total_comments', - 'last_comment_date', - 'commentators')) + content_obj.reindexObject( + idxs=("total_comments", "last_comment_date", "commentators") + ) def notify_content_object_deleted(obj, event): """Remove all comments of a content object when the content object has been - deleted. + deleted. """ if IAnnotatable.providedBy(obj): conversation = IConversation(obj) @@ -251,40 +262,40 @@ def notify_content_object_deleted(obj, event): def notify_comment_added(obj, event): - """ Notify custom discussion events when a comment is added or replied - """ + """Notify custom discussion events when a comment is added or replied""" conversation = aq_parent(obj) context = aq_parent(conversation) - if getattr(obj, 'in_reply_to', None): + if getattr(obj, "in_reply_to", None): return notify(ReplyAddedEvent(context, obj)) return notify(CommentAddedEvent(context, obj)) def notify_comment_modified(obj, event): - """ Notify custom discussion events when a comment, or a reply, is modified - """ + """Notify custom discussion events when a comment, or a reply, is modified""" conversation = aq_parent(obj) context = aq_parent(conversation) - if getattr(obj, 'in_reply_to', None): + if getattr(obj, "in_reply_to", None): return notify(ReplyModifiedEvent(context, obj)) return notify(CommentModifiedEvent(context, obj)) def notify_comment_removed(obj, event): - """ Notify custom discussion events when a comment or reply is removed - """ + """Notify custom discussion events when a comment or reply is removed""" conversation = aq_parent(obj) context = aq_parent(conversation) - if getattr(obj, 'in_reply_to', None): + if getattr(obj, "in_reply_to", None): return notify(ReplyRemovedEvent(context, obj)) return notify(CommentRemovedEvent(context, obj)) def notify_content_object_moved(obj, event): - """Update all comments of a content object that has been moved. - """ - if event.oldParent is None or event.newParent is None \ - or event.oldName is None or event.newName is None: + """Update all comments of a content object that has been moved.""" + if ( + event.oldParent is None + or event.newParent is None + or event.oldName is None + or event.newName is None + ): return # This method is also called for sublocations of moved objects. We @@ -293,21 +304,19 @@ def notify_content_object_moved(obj, event): # in the object hierarchy. The object is already moved at this point. so # obj.getPhysicalPath retruns the new path get the part of the path that # was moved. - moved_path = obj.getPhysicalPath()[ - len(event.newParent.getPhysicalPath()) + 1: - ] + moved_path = obj.getPhysicalPath()[len(event.newParent.getPhysicalPath()) + 1 :] # Remove comments at the old location from catalog - catalog = getToolByName(obj, 'portal_catalog') - old_path = '/'.join( - event.oldParent.getPhysicalPath() + - (event.oldName,) + - moved_path, + catalog = getToolByName(obj, "portal_catalog") + old_path = "/".join( + event.oldParent.getPhysicalPath() + (event.oldName,) + moved_path, + ) + brains = catalog.searchResults( + dict( + path={"query": old_path}, + portal_type="Discussion Item", + ) ) - brains = catalog.searchResults(dict( - path={'query': old_path}, - portal_type='Discussion Item', - )) for brain in brains: catalog.uncatalog_object(brain.getPath()) # Reindex comment at the new location @@ -320,11 +329,11 @@ def notify_content_object_moved(obj, event): def notify_user(obj, event): """Tell users when a comment has been added. - This method composes and sends emails to all users that have added a - comment to this conversation and enabled user notification. + This method composes and sends emails to all users that have added a + comment to this conversation and enabled user notification. - This requires the user_notification setting to be enabled in the - discussion control panel. + This requires the user_notification setting to be enabled in the + discussion control panel. """ # Check if user notification is enabled @@ -334,9 +343,9 @@ def notify_user(obj, event): return # Get informations that are necessary to send an email - mail_host = getToolByName(obj, 'MailHost') + mail_host = getToolByName(obj, "MailHost") registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') + mail_settings = registry.forInterface(IMailSchema, prefix="plone") sender = mail_settings.email_from_address # Check if a sender address is available @@ -360,15 +369,14 @@ def notify_user(obj, event): if not emails: return - subject = translate(_(u'A comment has been posted.'), - context=obj.REQUEST) + subject = translate(_("A comment has been posted."), context=obj.REQUEST) message = translate( Message( MAIL_NOTIFICATION_MESSAGE, mapping={ - 'title': safe_unicode(content_object.title), - 'link': content_object.absolute_url() + '/view#' + obj.id, - 'text': obj.text, + "title": safe_text(content_object.title), + "link": content_object.absolute_url() + "/view#" + obj.id, + "text": obj.text, }, ), context=obj.REQUEST, @@ -381,12 +389,11 @@ def notify_user(obj, event): email, sender, subject, - charset='utf-8', + charset="utf-8", ) except SMTPException: logger.error( - 'SMTP exception while trying to send an ' + - 'email from %s to %s', + "SMTP exception while trying to send an " + "email from %s to %s", sender, email, ) @@ -395,15 +402,15 @@ def notify_user(obj, event): def notify_moderator(obj, event): """Tell the moderator when a comment needs attention. - This method sends an email to the moderator if comment moderation a new - comment has been added that needs to be approved. + This method sends an email to the moderator if comment moderation a new + comment has been added that needs to be approved. - The moderator_notification setting has to be enabled in the discussion - control panel. + The moderator_notification setting has to be enabled in the discussion + control panel. - Configure the moderator e-mail address in the discussion control panel. - If no moderator is configured but moderator notifications are turned on, - the site admin email (from the mail control panel) will be used. + Configure the moderator e-mail address in the discussion control panel. + If no moderator is configured but moderator notifications are turned on, + the site admin email (from the mail control panel) will be used. """ # Check if moderator notification is enabled registry = queryUtility(IRegistry) @@ -412,9 +419,9 @@ def notify_moderator(obj, event): return # Get informations that are necessary to send an email - mail_host = getToolByName(obj, 'MailHost') + mail_host = getToolByName(obj, "MailHost") registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') + mail_settings = registry.forInterface(IMailSchema, prefix="plone") sender = mail_settings.email_from_address if settings.moderator_email: @@ -430,22 +437,23 @@ def notify_moderator(obj, event): content_object = aq_parent(conversation) # Compose email - subject = translate(_(u'A comment has been posted.'), context=obj.REQUEST) + subject = translate(_("A comment has been posted."), context=obj.REQUEST) message = translate( Message( MAIL_NOTIFICATION_MESSAGE_MODERATOR, mapping={ - 'title': safe_unicode(content_object.title), - 'link': content_object.absolute_url() + '/view#' + obj.id, - 'text': obj.text, - 'commentator': obj.author_email or translate( - Message( - _( - u'label_anonymous', - default=u'Anonymous', - ), + "title": safe_text(content_object.title), + "link": content_object.absolute_url() + "/view#" + obj.id, + "text": obj.text, + "commentator": obj.author_email + or translate( + Message( + _( + "label_anonymous", + default="Anonymous", ), - ) + ), + ), }, ), context=obj.REQUEST, @@ -453,12 +461,12 @@ def notify_moderator(obj, event): # Send email try: - mail_host.send(message, mto, sender, subject, charset='utf-8') + mail_host.send(message, mto, sender, subject, charset="utf-8") except SMTPException as e: logger.error( - 'SMTP exception (%s) while trying to send an ' + - 'email notification to the comment moderator ' + - '(from %s to %s, message: %s)', + "SMTP exception (%s) while trying to send an " + + "email notification to the comment moderator " + + "(from %s to %s, message: %s)", e, sender, mto, diff --git a/plone/app/discussion/configure.zcml b/plone/app/discussion/configure.zcml index aabd851..fb72cee 100644 --- a/plone/app/discussion/configure.zcml +++ b/plone/app/discussion/configure.zcml @@ -39,7 +39,7 @@ description="Commenting infrastructure for Plone" directory="profiles/default" provides="Products.GenericSetup.interfaces.EXTENSION" - for="Products.CMFPlone.interfaces.IPloneSiteRoot" + for="plone.base.interfaces.IPloneSiteRoot" /> diff --git a/plone/app/discussion/contentrules.py b/plone/app/discussion/contentrules.py index c8f602a..bc752b2 100644 --- a/plone/app/discussion/contentrules.py +++ b/plone/app/discussion/contentrules.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Content rules handlers """ from plone.app.discussion import _ @@ -7,101 +6,94 @@ from plone.app.discussion import _ try: from plone.stringinterp.adapters import BaseSubstitution except ImportError: - class BaseSubstitution(object): - """ Fallback class if plone.stringinterp is not available - """ + + class BaseSubstitution: + """Fallback class if plone.stringinterp is not available""" def __init__(self, context, **kwargs): self.context = context + try: from plone.app.contentrules.handlers import execute except ImportError: + def execute(context, event): return False def execute_comment(event): - """ Execute comment content rules - """ + """Execute comment content rules""" execute(event.object, event) class CommentSubstitution(BaseSubstitution): - """ Comment string substitution - """ + """Comment string substitution""" def __init__(self, context, **kwargs): - super(CommentSubstitution, self).__init__(context, **kwargs) + super().__init__(context, **kwargs) @property def event(self): - """ event that triggered the content rule - """ - return self.context.REQUEST.get('event') + """event that triggered the content rule""" + return self.context.REQUEST.get("event") @property def comment(self): - """ Get changed inline comment - """ + """Get changed inline comment""" return self.event.comment class Id(CommentSubstitution): - """ Comment id string substitution - """ - category = _(u'Comments') - description = _(u'Comment id') + """Comment id string substitution""" + + category = _("Comments") + description = _("Comment id") def safe_call(self): - """ Safe call - """ - return getattr(self.comment, 'comment_id', u'') + """Safe call""" + return getattr(self.comment, "comment_id", "") class Text(CommentSubstitution): - """ Comment text - """ - category = _(u'Comments') - description = _(u'Comment text') + """Comment text""" + + category = _("Comments") + description = _("Comment text") def safe_call(self): - """ Safe call - """ - return getattr(self.comment, 'text', u'') + """Safe call""" + return getattr(self.comment, "text", "") class AuthorUserName(CommentSubstitution): - """ Comment author user name string substitution - """ - category = _(u'Comments') - description = _(u'Comment author user name') + """Comment author user name string substitution""" + + category = _("Comments") + description = _("Comment author user name") def safe_call(self): - """ Safe call - """ - return getattr(self.comment, 'author_username', u'') + """Safe call""" + return getattr(self.comment, "author_username", "") class AuthorFullName(CommentSubstitution): - """ Comment author full name string substitution - """ - category = _(u'Comments') - description = _(u'Comment author full name') + """Comment author full name string substitution""" + + category = _("Comments") + description = _("Comment author full name") def safe_call(self): - """ Safe call - """ - return getattr(self.comment, 'author_name', u'') + """Safe call""" + return getattr(self.comment, "author_name", "") class AuthorEmail(CommentSubstitution): - """ Comment author email string substitution - """ - category = _(u'Comments') - description = _(u'Comment author email') + """Comment author email string substitution""" + + category = _("Comments") + description = _("Comment author email") def safe_call(self): - """ Safe call - """ - return getattr(self.comment, 'author_email', u'') + """Safe call""" + return getattr(self.comment, "author_email", "") diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index ae13301..1cdb266 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """The conversation and replies adapters The conversation is responsible for storing all comments. It provides a @@ -10,6 +9,10 @@ manipulate the same data structures, but provide an API for finding and manipulating the comments directly in reply to a particular comment or at the top level of the conversation. """ +from .comment import Comment +from .interfaces import DISCUSSION_ANNOTATION_KEY as ANNOTATION_KEY +from .interfaces import IConversation +from .interfaces import IReplies from AccessControl.SpecialUsers import nobody as user_nobody from Acquisition import aq_base from Acquisition import aq_inner @@ -22,11 +25,7 @@ from OFS.event import ObjectWillBeAddedEvent from OFS.event import ObjectWillBeRemovedEvent from OFS.Traversable import Traversable from persistent import Persistent -from plone.app.discussion.comment import Comment -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IReplies -from Products.CMFPlone import DISCUSSION_ANNOTATION_KEY as ANNOTATION_KEY -from Products.CMFPlone.interfaces import IHideFromBreadcrumbs +from plone.base.interfaces import IHideFromBreadcrumbs from zope.annotation.interfaces import IAnnotatable from zope.annotation.interfaces import IAnnotations from zope.component import adapter @@ -37,7 +36,6 @@ from zope.lifecycleevent import ObjectAddedEvent from zope.lifecycleevent import ObjectCreatedEvent from zope.lifecycleevent import ObjectRemovedEvent -import six import time @@ -51,7 +49,7 @@ class Conversation(Traversable, Persistent, Explicit): __allow_access_to_unprotected_subobjects__ = True - def __init__(self, id='++conversation++default'): + def __init__(self, id="++conversation++default"): self.id = id # username -> count of comments; key is removed when count reaches 0 @@ -72,12 +70,11 @@ class Conversation(Traversable, Persistent, Explicit): def enabled(self): parent = aq_inner(self.__parent__) - return parent.restrictedTraverse('@@conversation_view').enabled() + return parent.restrictedTraverse("@@conversation_view").enabled() def total_comments(self): public_comments = [ - x for x in self.values() - if user_nobody.has_permission('View', x) + x for x in self.values() if user_nobody.has_permission("View", x) ] return len(public_comments) @@ -88,7 +85,7 @@ class Conversation(Traversable, Persistent, Explicit): comment_keys = self._comments.keys() for comment_key in reversed(comment_keys): comment = self._comments[comment_key] - if user_nobody.has_permission('View', comment): + if user_nobody.has_permission("View", comment): return comment.creation_date return None @@ -100,7 +97,7 @@ class Conversation(Traversable, Persistent, Explicit): def public_commentators(self): retval = set() for comment in self._comments.values(): - if not user_nobody.has_permission('View', comment): + if not user_nobody.has_permission("View", comment): continue retval.add(comment.author_username) return tuple(retval) @@ -109,8 +106,7 @@ class Conversation(Traversable, Persistent, Explicit): return self._comments.keys() def getComments(self, start=0, size=None): - """Get unthreaded comments - """ + """Get unthreaded comments""" count = 0 for comment in self._comments.values(min=start): # Yield the acquisition wrapped comment @@ -121,20 +117,18 @@ class Conversation(Traversable, Persistent, Explicit): return def getThreads(self, start=0, size=None, root=0, depth=None): - """Get threaded comments - """ + """Get threaded comments""" def recurse(comment_id, d=0): # Yield the current comment before we look for its children - yield {'id': comment_id, 'comment': self[comment_id], 'depth': d} + yield {"id": comment_id, "comment": self[comment_id], "depth": d} # Recurse if there are children and we are not out of our depth if depth is None or d + 1 < depth: children = self._children.get(comment_id, None) if children is not None: for child_id in children: - for value in recurse(child_id, d + 1): - yield value + yield from recurse(child_id, d + 1) # Find top level threads comments = self._children.get(root, None) @@ -148,8 +142,7 @@ class Conversation(Traversable, Persistent, Explicit): return # Let the closure recurse - for value in recurse(comment_id): - yield value + yield from recurse(comment_id) def addComment(self, comment): """Add a new comment. The parent id should have been set already. The @@ -209,8 +202,7 @@ class Conversation(Traversable, Persistent, Explicit): return int(key) in self._comments def __getitem__(self, key): - """Get an item by its int key - """ + """Get an item by its int key""" try: comment_id = int(key) except ValueError: @@ -218,8 +210,7 @@ class Conversation(Traversable, Persistent, Explicit): return self._comments[comment_id].__of__(self) def __delitem__(self, key, suppress_container_modified=False): - """Delete an item by its int key - """ + """Delete an item by its int key""" key = int(key) @@ -269,21 +260,30 @@ class Conversation(Traversable, Persistent, Explicit): return self._comments.keys() def items(self): - return [(i[0], i[1].__of__(self),) for i in self._comments.items()] + return [ + ( + i[0], + i[1].__of__(self), + ) + for i in self._comments.items() + ] def values(self): return [v.__of__(self) for v in self._comments.values()] def iterkeys(self): - return six.iterkeys(self._comments) + return self._comments.keys() def itervalues(self): - for v in six.itervalues(self._comments): + for v in self._comments.values(): yield v.__of__(self) def iteritems(self): - for k, v in six.iteritems(self._comments): - yield (k, v.__of__(self),) + for k, v in self._comments.items(): + yield ( + k, + v.__of__(self), + ) def allowedContentTypes(self): return [] @@ -309,6 +309,7 @@ try: except ImportError: pass else: + @implementer(IConversation) # pragma: no cover @adapter(IAnnotatable) # pragma: no cover def conversationCanonicalAdapterFactory(content): # pragma: no cover @@ -327,7 +328,7 @@ else: @implementer(IReplies) @adapter(Conversation) # relies on implementation details -class ConversationReplies(object): +class ConversationReplies: """An IReplies adapter for conversations. This makes it easy to work with top-level comments. @@ -350,16 +351,14 @@ class ConversationReplies(object): return int(key) in self.children def __getitem__(self, key): - """Get an item by its int key - """ + """Get an item by its int key""" key = int(key) if key not in self.children: raise KeyError(key) return self.conversation[key] def __delitem__(self, key): - """Delete an item by its int key - """ + """Delete an item by its int key""" key = int(key) if key not in self.children: raise KeyError(key) @@ -392,7 +391,10 @@ class ConversationReplies(object): def iteritems(self): for key in self.children: - yield (key, self.conversation[key],) + yield ( + key, + self.conversation[key], + ) @property def children(self): @@ -418,11 +420,12 @@ class CommentReplies(ConversationReplies): self.conversation = aq_parent(self.comment) conversation_has_no_children = not hasattr( self.conversation, - '_children', + "_children", ) if self.conversation is None or conversation_has_no_children: - raise TypeError("This adapter doesn't know what to do with the " - 'parent conversation') + raise TypeError( + "This adapter doesn't know what to do with the " "parent conversation" + ) self.comment_id = self.comment.comment_id diff --git a/plone/app/discussion/events.py b/plone/app/discussion/events.py index e2ecc02..3444bbc 100644 --- a/plone/app/discussion/events.py +++ b/plone/app/discussion/events.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- """ Custom discussion events """ from plone.app.discussion.interfaces import ICommentAddedEvent -from plone.app.discussion.interfaces import ICommentModifiedEvent -from plone.app.discussion.interfaces import ICommentRemovedEvent -from plone.app.discussion.interfaces import IDiscussionEvent from plone.app.discussion.interfaces import ICommentDeletedEvent +from plone.app.discussion.interfaces import ICommentModifiedEvent from plone.app.discussion.interfaces import ICommentPublishedEvent +from plone.app.discussion.interfaces import ICommentRemovedEvent from plone.app.discussion.interfaces import ICommentTransitionEvent +from plone.app.discussion.interfaces import IDiscussionEvent from plone.app.discussion.interfaces import IReplyAddedEvent from plone.app.discussion.interfaces import IReplyModifiedEvent from plone.app.discussion.interfaces import IReplyRemovedEvent @@ -15,9 +14,8 @@ from zope.interface import implementer @implementer(IDiscussionEvent) -class DiscussionEvent(object): - """ Custom event - """ +class DiscussionEvent: + """Custom event""" def __init__(self, context, comment, **kwargs): self.object = context @@ -28,55 +26,47 @@ class DiscussionEvent(object): # Add event to the request to be able to access comment attributes # in content-rules dynamic strings request = context.REQUEST - request.set('event', self) + request.set("event", self) @implementer(ICommentAddedEvent) class CommentAddedEvent(DiscussionEvent): - """ Event to be triggered when a Comment is added - """ + """Event to be triggered when a Comment is added""" @implementer(ICommentModifiedEvent) class CommentModifiedEvent(DiscussionEvent): - """ Event to be triggered when a Comment is modified - """ + """Event to be triggered when a Comment is modified""" @implementer(ICommentRemovedEvent) class CommentRemovedEvent(DiscussionEvent): - """ Event to be triggered when a Comment is removed - """ + """Event to be triggered when a Comment is removed""" @implementer(IReplyAddedEvent) class ReplyAddedEvent(DiscussionEvent): - """ Event to be triggered when a Comment reply is added - """ + """Event to be triggered when a Comment reply is added""" @implementer(IReplyModifiedEvent) class ReplyModifiedEvent(DiscussionEvent): - """ Event to be triggered when a Comment reply is modified - """ + """Event to be triggered when a Comment reply is modified""" @implementer(IReplyRemovedEvent) class ReplyRemovedEvent(DiscussionEvent): - """ Event to be triggered when a Comment reply is removed - """ + """Event to be triggered when a Comment reply is removed""" @implementer(ICommentDeletedEvent) class CommentDeletedEvent(DiscussionEvent): - """ Event to be triggered when a Comment is deleted - """ + """Event to be triggered when a Comment is deleted""" @implementer(ICommentPublishedEvent) class CommentPublishedEvent(DiscussionEvent): - """ Event to be triggered when a Comment is publicated - """ + """Event to be triggered when a Comment is publicated""" @implementer(ICommentTransitionEvent) diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py index 76a0cee..e53628d 100644 --- a/plone/app/discussion/interfaces.py +++ b/plone/app/discussion/interfaces.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Interfaces for plone.app.discussion """ from plone.app.discussion import _ @@ -12,11 +11,14 @@ from zope.interface.common.mapping import IIterableMapping from zope.interface.interfaces import IObjectEvent +DISCUSSION_ANNOTATION_KEY = "plone.app.discussion:conversation" + + def isEmail(value): portal = getUtility(ISiteRoot) - reg_tool = getToolByName(portal, 'portal_registration') + reg_tool = getToolByName(portal, "portal_registration") if not (value and reg_tool.isValidEmail(value)): - raise Invalid(_('Invalid email address.')) + raise Invalid(_("Invalid email address.")) return True @@ -42,25 +44,24 @@ class IConversation(IIterableMapping): """ total_comments = schema.Int( - title=_(u'Total number of public comments on this item'), + title=_("Total number of public comments on this item"), min=0, readonly=True, ) last_comment_date = schema.Date( - title=_(u'Date of the most recent public comment'), + title=_("Date of the most recent public comment"), readonly=True, ) commentators = schema.Set( - title=_(u'The set of unique commentators (usernames)'), + title=_("The set of unique commentators (usernames)"), readonly=True, ) public_commentators = schema.Set( title=_( - u'The set of unique commentators (usernames) ' - u'of published_comments', + "The set of unique commentators (usernames) " "of published_comments", ), readonly=True, ) @@ -72,8 +73,7 @@ class IConversation(IIterableMapping): """ def __delitem__(key): - """Delete the comment with the given key. The key is a long id. - """ + """Delete the comment with the given key. The key is a long id.""" def getComments(start=0, size=None): """Return an iterator of comment objects for rendering. @@ -130,8 +130,7 @@ class IReplies(IIterableMapping): """ def __delitem__(key): - """Delete the comment with the given key. The key is a long id. - """ + """Delete the comment with the given key. The key is a long id.""" class IComment(Interface): @@ -141,61 +140,58 @@ class IComment(Interface): """ portal_type = schema.ASCIILine( - title=_(u'Portal type'), - default='Discussion Item', + title=_("Portal type"), + default="Discussion Item", ) - __parent__ = schema.Object( - title=_(u'Conversation'), schema=Interface) + __parent__ = schema.Object(title=_("Conversation"), schema=Interface) - __name__ = schema.TextLine(title=_(u'Name')) + __name__ = schema.TextLine(title=_("Name")) - comment_id = schema.Int( - title=_(u'A comment id unique to this conversation')) + comment_id = schema.Int(title=_("A comment id unique to this conversation")) in_reply_to = schema.Int( - title=_(u'Id of comment this comment is in reply to'), + title=_("Id of comment this comment is in reply to"), required=False, ) # for logged in comments - set to None for anonymous - author_username = schema.TextLine(title=_(u'Name'), required=False) + author_username = schema.TextLine(title=_("Name"), required=False) # for anonymous comments only, set to None for logged in comments - author_name = schema.TextLine(title=_(u'Name'), required=False) - author_email = schema.TextLine(title=_(u'Email'), - required=False, - constraint=isEmail, - ) + author_name = schema.TextLine(title=_("Name"), required=False) + author_email = schema.TextLine( + title=_("Email"), + required=False, + constraint=isEmail, + ) - title = schema.TextLine(title=_(u'label_subject', - default=u'Subject')) + title = schema.TextLine(title=_("label_subject", default="Subject")) - mime_type = schema.ASCIILine(title=_(u'MIME type'), default='text/plain') + mime_type = schema.ASCIILine(title=_("MIME type"), default="text/plain") text = schema.Text( title=_( - u'label_comment', - default=u'Comment', + "label_comment", + default="Comment", ), ) user_notification = schema.Bool( title=_( - u'Notify me of new comments via email.', + "Notify me of new comments via email.", ), required=False, ) - creator = schema.TextLine(title=_(u'Username of the commenter')) - creation_date = schema.Date(title=_(u'Creation date')) - modification_date = schema.Date(title=_(u'Modification date')) + creator = schema.TextLine(title=_("Username of the commenter")) + creation_date = schema.Date(title=_("Creation date")) + modification_date = schema.Date(title=_("Modification date")) class ICaptcha(Interface): - """Captcha/ReCaptcha text field to extend the existing comment form. - """ - captcha = schema.TextLine(title=_(u'Captcha'), - required=False) + """Captcha/ReCaptcha text field to extend the existing comment form.""" + + captcha = schema.TextLine(title=_("Captcha"), required=False) class IDiscussionSettings(Interface): @@ -210,40 +206,38 @@ class IDiscussionSettings(Interface): # - Search control panel: Show comments in search results globally_enabled = schema.Bool( - title=_(u'label_globally_enabled', - default=u'Globally enable comments'), + title=_("label_globally_enabled", default="Globally enable comments"), description=_( - u'help_globally_enabled', - default=u'If selected, users are able to post comments on the ' - u'site. However, you will still need to enable comments ' - u'for specific content types, folders or content ' - u'objects before users will be able to post comments.', + "help_globally_enabled", + default="If selected, users are able to post comments on the " + "site. However, you will still need to enable comments " + "for specific content types, folders or content " + "objects before users will be able to post comments.", ), required=False, default=False, ) anonymous_comments = schema.Bool( - title=_(u'label_anonymous_comments', - default='Enable anonymous comments'), + title=_("label_anonymous_comments", default="Enable anonymous comments"), description=_( - u'help_anonymous_comments', - default=u'If selected, anonymous users are able to post ' - u'comments without logging in. It is highly ' - u'recommended to use a captcha solution to prevent ' - u'spam if this setting is enabled.', + "help_anonymous_comments", + default="If selected, anonymous users are able to post " + "comments without logging in. It is highly " + "recommended to use a captcha solution to prevent " + "spam if this setting is enabled.", ), required=False, default=False, ) anonymous_email_enabled = schema.Bool( - title=_(u'label_anonymous_email_enabled', - default=u'Enable anonymous email field'), + title=_( + "label_anonymous_email_enabled", default="Enable anonymous email field" + ), description=_( - u'help_anonymous_email_enabled', - default=u'If selected, anonymous user will have to ' - u'give their email.', + "help_anonymous_email_enabled", + default="If selected, anonymous user will have to " "give their email.", ), required=False, default=False, @@ -251,130 +245,137 @@ class IDiscussionSettings(Interface): moderation_enabled = schema.Bool( title=_( - u'label_moderation_enabled', - default='Enable comment moderation', + "label_moderation_enabled", + default="Enable comment moderation", ), description=_( - u'help_moderation_enabled', - default=u'If selected, comments will enter a "Pending" state ' - u'in which they are invisible to the public. A user ' - u'with the "Review comments" permission ("Reviewer" ' - u'or "Manager") can approve comments to make them ' - u'visible to the public. If you want to enable a ' - u'custom comment workflow, you have to go to the ' - u'types control panel.', + "help_moderation_enabled", + default='If selected, comments will enter a "Pending" state ' + "in which they are invisible to the public. A user " + 'with the "Review comments" permission ("Reviewer" ' + 'or "Manager") can approve comments to make them ' + "visible to the public. If you want to enable a " + "custom comment workflow, you have to go to the " + "types control panel.", ), required=False, default=False, ) edit_comment_enabled = schema.Bool( - title=_(u'label_edit_comment_enabled', - default='Enable editing of comments'), - description=_(u'help_edit_comment_enabled', - default=u'If selected, supports editing ' - 'of comments for users with the "Edit comments" ' - 'permission.'), + title=_("label_edit_comment_enabled", default="Enable editing of comments"), + description=_( + "help_edit_comment_enabled", + default="If selected, supports editing " + 'of comments for users with the "Edit comments" ' + "permission.", + ), required=False, default=False, ) delete_own_comment_enabled = schema.Bool( - title=_(u'label_delete_own_comment_enabled', - default='Enable deleting own comments'), - description=_(u'help_delete_own_comment_enabled', - default=u'If selected, supports deleting ' - 'of own comments for users with the ' - '"Delete own comments" permission.'), + title=_( + "label_delete_own_comment_enabled", default="Enable deleting own comments" + ), + description=_( + "help_delete_own_comment_enabled", + default="If selected, supports deleting " + "of own comments for users with the " + '"Delete own comments" permission.', + ), required=False, default=False, ) text_transform = schema.Choice( - title=_(u'label_text_transform', - default='Comment text transform'), + title=_("label_text_transform", default="Comment text transform"), description=_( - u'help_text_transform', - default=u'Use this setting to choose if the comment text ' - u'should be transformed in any way. You can choose ' - u'between "Plain text" and "Intelligent text". ' - u'"Intelligent text" converts plain text into HTML ' - u'where line breaks and indentation is preserved, ' - u'and web and email addresses are made into ' - u'clickable links.'), + "help_text_transform", + default="Use this setting to choose if the comment text " + "should be transformed in any way. You can choose " + 'between "Plain text" and "Intelligent text". ' + '"Intelligent text" converts plain text into HTML ' + "where line breaks and indentation is preserved, " + "and web and email addresses are made into " + "clickable links.", + ), required=True, - default='text/plain', - vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary', + default="text/plain", + vocabulary="plone.app.discussion.vocabularies.TextTransformVocabulary", ) captcha = schema.Choice( - title=_(u'label_captcha', - default='Captcha'), + title=_("label_captcha", default="Captcha"), description=_( - u'help_captcha', - default=u'Use this setting to enable or disable Captcha ' - u'validation for comments. Install ' - u'plone.formwidget.captcha, ' - u'plone.formwidget.recaptcha, collective.akismet, or ' - u'collective.z3cform.norobots if there are no options ' - u'available.'), + "help_captcha", + default="Use this setting to enable or disable Captcha " + "validation for comments. Install " + "plone.formwidget.captcha, " + "plone.formwidget.recaptcha, collective.akismet, or " + "collective.z3cform.norobots if there are no options " + "available.", + ), required=True, - default='disabled', - vocabulary='plone.app.discussion.vocabularies.CaptchaVocabulary', + default="disabled", + vocabulary="plone.app.discussion.vocabularies.CaptchaVocabulary", ) show_commenter_image = schema.Bool( - title=_(u'label_show_commenter_image', - default=u'Show commenter image'), + title=_("label_show_commenter_image", default="Show commenter image"), description=_( - u'help_show_commenter_image', - default=u'If selected, an image of the user is shown next to ' - u'the comment.'), + "help_show_commenter_image", + default="If selected, an image of the user is shown next to " + "the comment.", + ), required=False, default=True, ) moderator_notification_enabled = schema.Bool( - title=_(u'label_moderator_notification_enabled', - default=u'Enable moderator email notification'), + title=_( + "label_moderator_notification_enabled", + default="Enable moderator email notification", + ), description=_( - u'help_moderator_notification_enabled', - default=u'If selected, the moderator is notified if a comment ' - u'needs attention. The moderator email address can ' - u'be set below.'), + "help_moderator_notification_enabled", + default="If selected, the moderator is notified if a comment " + "needs attention. The moderator email address can " + "be set below.", + ), required=False, default=False, ) moderator_email = schema.ASCIILine( title=_( - u'label_moderator_email', - default=u'Moderator Email Address', + "label_moderator_email", + default="Moderator Email Address", ), description=_( - u'help_moderator_email', - default=u'Address to which moderator notifications ' - u'will be sent.'), + "help_moderator_email", + default="Address to which moderator notifications " "will be sent.", + ), required=False, ) user_notification_enabled = schema.Bool( title=_( - u'label_user_notification_enabled', - default=u'Enable user email notification', + "label_user_notification_enabled", + default="Enable user email notification", ), description=_( - u'help_user_notification_enabled', - default=u'If selected, users can choose to be notified ' - u'of new comments by email.'), + "help_user_notification_enabled", + default="If selected, users can choose to be notified " + "of new comments by email.", + ), required=False, default=False, ) class IDiscussionLayer(Interface): - """Request marker installed via browserlayer.xml. - """ + """Request marker installed via browserlayer.xml.""" class ICommentingTool(Interface): @@ -384,54 +385,46 @@ class ICommentingTool(Interface): of Plone that had a portal_discussion tool. """ + # # Custom events # class IDiscussionEvent(IObjectEvent): - """ Discussion custom event - """ + """Discussion custom event""" class ICommentAddedEvent(IDiscussionEvent): - """ Comment added - """ + """Comment added""" class ICommentModifiedEvent(IDiscussionEvent): - """ Comment modified - """ + """Comment modified""" class ICommentRemovedEvent(IDiscussionEvent): - """ Comment removed - """ + """Comment removed""" class IReplyAddedEvent(IDiscussionEvent): - """ Comment reply added - """ + """Comment reply added""" class IReplyModifiedEvent(IDiscussionEvent): - """ Comment reply modified - """ + """Comment reply modified""" class IReplyRemovedEvent(IDiscussionEvent): - """ Comment reply removed - """ + """Comment reply removed""" class ICommentPublishedEvent(IDiscussionEvent): - """ Notify user on comment publication - """ + """Notify user on comment publication""" class ICommentDeletedEvent(IDiscussionEvent): - """ Notify user on comment delete - """ + """Notify user on comment delete""" class ICommentTransitionEvent(IDiscussionEvent): diff --git a/plone/app/discussion/subscribers.py b/plone/app/discussion/subscribers.py index 0234c97..6e1b498 100644 --- a/plone/app/discussion/subscribers.py +++ b/plone/app/discussion/subscribers.py @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- from Products.CMFCore.utils import getToolByName def index_object(obj, event): - """Index the object when it is added/modified to the conversation. - """ + """Index the object when it is added/modified to the conversation.""" obj.indexObject() def unindex_object(obj, event): - """Unindex the object when it is removed from the conversation. - """ + """Unindex the object when it is removed from the conversation.""" obj.unindexObject() diff --git a/plone/app/discussion/subscribers.zcml b/plone/app/discussion/subscribers.zcml index 85e74e2..a797741 100644 --- a/plone/app/discussion/subscribers.zcml +++ b/plone/app/discussion/subscribers.zcml @@ -72,7 +72,7 @@ diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py index 5a4060b..01ea305 100644 --- a/plone/app/discussion/testing.py +++ b/plone/app/discussion/testing.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.robotframework.testing import REMOTE_LIBRARY_ROBOT_TESTING @@ -15,40 +14,43 @@ from zope.component import queryUtility try: import plone.app.collection # noqa - COLLECTION_TYPE = 'Collection' + + COLLECTION_TYPE = "Collection" except ImportError: - COLLECTION_TYPE = 'Topic' + COLLECTION_TYPE = "Topic" class PloneAppDiscussion(PloneSandboxLayer): defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,) - USER_NAME = 'johndoe' - USER_PASSWORD = 'secret' - MEMBER_NAME = 'janedoe' - MEMBER_PASSWORD = 'secret' - USER_WITH_FULLNAME_NAME = 'jim' - USER_WITH_FULLNAME_FULLNAME = 'Jim Fulton' - USER_WITH_FULLNAME_PASSWORD = 'secret' - MANAGER_USER_NAME = 'manager' - MANAGER_USER_PASSWORD = 'secret' - REVIEWER_NAME = 'reviewer' - REVIEWER_PASSWORD = 'secret' + USER_NAME = "johndoe" + USER_PASSWORD = "secret" + MEMBER_NAME = "janedoe" + MEMBER_PASSWORD = "secret" + USER_WITH_FULLNAME_NAME = "jim" + USER_WITH_FULLNAME_FULLNAME = "Jim Fulton" + USER_WITH_FULLNAME_PASSWORD = "secret" + MANAGER_USER_NAME = "manager" + MANAGER_USER_PASSWORD = "secret" + REVIEWER_NAME = "reviewer" + REVIEWER_PASSWORD = "secret" def setUpZope(self, app, configurationContext): # Load ZCML import plone.app.discussion - self.loadZCML(package=plone.app.discussion, - context=configurationContext, - ) + + self.loadZCML( + package=plone.app.discussion, + context=configurationContext, + ) def setUpPloneSite(self, portal): # Install into Plone site using portal_setup - applyProfile(portal, 'plone.app.discussion:default') + applyProfile(portal, "plone.app.discussion:default") # Creates some users - acl_users = getToolByName(portal, 'acl_users') + acl_users = getToolByName(portal, "acl_users") acl_users.userFolderAddUser( self.USER_NAME, self.USER_PASSWORD, @@ -58,41 +60,42 @@ class PloneAppDiscussion(PloneSandboxLayer): acl_users.userFolderAddUser( self.MEMBER_NAME, self.MEMBER_PASSWORD, - ['Member'], + ["Member"], [], ) acl_users.userFolderAddUser( self.USER_WITH_FULLNAME_NAME, self.USER_WITH_FULLNAME_PASSWORD, - ['Member'], + ["Member"], [], ) acl_users.userFolderAddUser( self.REVIEWER_NAME, self.REVIEWER_PASSWORD, - ['Member'], + ["Member"], [], ) - mtool = getToolByName(portal, 'portal_membership', None) - gtool = getToolByName(portal, 'portal_groups', None) - gtool.addPrincipalToGroup(self.REVIEWER_NAME, 'Reviewers') - mtool.addMember('jim', 'Jim', ['Member'], []) - mtool.getMemberById('jim').setMemberProperties( - {'fullname': 'Jim Fult\xc3\xb8rn'}) + mtool = getToolByName(portal, "portal_membership", None) + gtool = getToolByName(portal, "portal_groups", None) + gtool.addPrincipalToGroup(self.REVIEWER_NAME, "Reviewers") + mtool.addMember("jim", "Jim", ["Member"], []) + mtool.getMemberById("jim").setMemberProperties( + {"fullname": "Jim Fult\xc3\xb8rn"} + ) acl_users.userFolderAddUser( self.MANAGER_USER_NAME, self.MANAGER_USER_PASSWORD, - ['Manager'], + ["Manager"], [], ) # Add a document - setRoles(portal, TEST_USER_ID, ['Manager']) + setRoles(portal, TEST_USER_ID, ["Manager"]) portal.invokeFactory( - id='doc1', - title='Document 1', - type_name='Document', + id="doc1", + title="Document 1", + type_name="Document", ) @@ -112,12 +115,12 @@ class PloneAppDiscussionRobot(PloneAppDiscussion): PLONE_APP_DISCUSSION_ROBOT_FIXTURE = PloneAppDiscussionRobot() PLONE_APP_DISCUSSION_FIXTURE = PloneAppDiscussion() PLONE_APP_DISCUSSION_INTEGRATION_TESTING = IntegrationTesting( - bases=(PLONE_APP_DISCUSSION_FIXTURE,), - name='PloneAppDiscussion:Integration') + bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Integration" +) PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING = FunctionalTesting( - bases=(PLONE_APP_DISCUSSION_FIXTURE,), - name='PloneAppDiscussion:Functional') + bases=(PLONE_APP_DISCUSSION_FIXTURE,), name="PloneAppDiscussion:Functional" +) PLONE_APP_DISCUSSION_ROBOT_TESTING = FunctionalTesting( bases=(PLONE_APP_DISCUSSION_ROBOT_FIXTURE,), - name='PloneAppDiscussion:Robot', + name="PloneAppDiscussion:Robot", ) diff --git a/plone/app/discussion/tests/functional_test_comment_review_workflow.txt b/plone/app/discussion/tests/functional_test_comment_review_workflow.txt index 84d384f..099ce9f 100644 --- a/plone/app/discussion/tests/functional_test_comment_review_workflow.txt +++ b/plone/app/discussion/tests/functional_test_comment_review_workflow.txt @@ -252,7 +252,7 @@ Now we can post an anonymous comment. >>> unprivileged_browser.open(urldoc) >>> unprivileged_browser.getControl(name='form.widgets.text').value = "This is an anonymous comment" - >>> unprivileged_browser.getControl(name='form.widgets.author_name').value = u'John' + >>> unprivileged_browser.getControl(name='form.widgets.author_name').value = 'John' >>> unprivileged_browser.getControl(name='form.widgets.author_email').value = 'john@acme.com' >>> unprivileged_browser.getControl(name='form.buttons.comment').click() diff --git a/plone/app/discussion/tests/test_catalog.py b/plone/app/discussion/tests/test_catalog.py index 98abd9f..395f6c2 100644 --- a/plone/app/discussion/tests/test_catalog.py +++ b/plone/app/discussion/tests/test_catalog.py @@ -1,9 +1,10 @@ -# -*- coding: utf-8 -*- """Test the plone.app.discussion catalog indexes """ from datetime import datetime from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from plone.app.discussion.testing import ( # noqa + PLONE_APP_DISCUSSION_INTEGRATION_TESTING, +) from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from Products.CMFCore.utils import getToolByName @@ -21,33 +22,29 @@ class CatalogSetupTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] + self.portal = self.layer["portal"] def test_catalog_installed(self): self.assertTrue( - 'total_comments' in - self.portal.portal_catalog.indexes(), + "total_comments" in self.portal.portal_catalog.indexes(), ) self.assertTrue( - 'commentators' in - self.portal.portal_catalog.indexes(), + "commentators" in self.portal.portal_catalog.indexes(), ) self.assertTrue( - 'total_comments' in - self.portal.portal_catalog.schema(), + "total_comments" in self.portal.portal_catalog.schema(), ) self.assertTrue( - 'in_response_to' in - self.portal.portal_catalog.schema(), + "in_response_to" in self.portal.portal_catalog.schema(), ) def test_collection_criteria_installed(self): - if 'portal_atct' not in self.portal: + if "portal_atct" not in self.portal: return try: - self.portal.portal_atct.getIndex('commentators') - self.portal.portal_atct.getIndex('total_comments') - self.portal.portal_atct.getMetadata('total_comments') + self.portal.portal_atct.getIndex("commentators") + self.portal.portal_atct.getIndex("total_comments") + self.portal.portal_atct.getMetadata("total_comments") except AttributeError: self.fail() @@ -57,19 +54,19 @@ class ConversationCatalogTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") - self.catalog = getToolByName(self.portal, 'portal_catalog') + self.catalog = getToolByName(self.portal, "portal_catalog") conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.title = 'Comment 1' - comment1.text = 'Comment text' - comment1.creator = 'jim' - comment1.author_username = 'Jim' + comment1 = createObject("plone.Comment") + comment1.title = "Comment 1" + comment1.text = "Comment text" + comment1.creator = "jim" + comment1.author_username = "Jim" comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12) comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12) @@ -79,9 +76,9 @@ class ConversationCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) self.conversation = conversation @@ -91,54 +88,54 @@ class ConversationCatalogTest(unittest.TestCase): self.new_comment1_id = new_comment1_id def test_total_comments(self): - self.assertTrue('total_comments' in self.doc1_brain) + self.assertTrue("total_comments" in self.doc1_brain) self.assertEqual(self.doc1_brain.total_comments, 1) - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" new_comment2_id = self.conversation.addComment(comment2) comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment2_id), + f"++conversation++default/{new_comment2_id}", ) comment2.reindexObject() brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] self.assertEqual(doc1_brain.total_comments, 2) def test_last_comment_date(self): - self.assertTrue('last_comment_date' in self.doc1_brain) + self.assertTrue("last_comment_date" in self.doc1_brain) self.assertEqual( self.doc1_brain.last_comment_date, datetime(2006, 9, 17, 14, 18, 12), ) # Add another comment and check if last comment date is updated. - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" comment2.creation_date = datetime(2009, 9, 17, 14, 18, 12) comment2.modification_date = datetime(2009, 9, 17, 14, 18, 12) new_comment2_id = self.conversation.addComment(comment2) comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment2_id), + f"++conversation++default/{new_comment2_id}", ) comment2.reindexObject() brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] @@ -153,9 +150,9 @@ class ConversationCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] @@ -169,44 +166,44 @@ class ConversationCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] self.assertEqual(doc1_brain.last_comment_date, None) def test_commentators(self): - self.assertTrue('commentators' in self.doc1_brain) - self.assertEqual(self.doc1_brain.commentators, ('Jim',)) + self.assertTrue("commentators" in self.doc1_brain) + self.assertEqual(self.doc1_brain.commentators, ("Jim",)) # add another comment with another author - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' - comment2.creator = 'emma' - comment2.author_username = 'Emma' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" + comment2.creator = "emma" + comment2.author_username = "Emma" new_comment2_id = self.conversation.addComment(comment2) comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment2_id), + f"++conversation++default/{new_comment2_id}", ) comment2.reindexObject() brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] self.assertEqual( sorted(doc1_brain.commentators), - sorted(('Jim', 'Emma')), + sorted(("Jim", "Emma")), ) # remove one comments @@ -214,22 +211,22 @@ class ConversationCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] - self.assertEqual(doc1_brain.commentators, ('Jim',)) + self.assertEqual(doc1_brain.commentators, ("Jim",)) # remove all comments del self.conversation[self.new_comment1_id] brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] @@ -239,9 +236,9 @@ class ConversationCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Discussion Item', + portal_type="Discussion Item", ), ) comment1_brain = brains[0] @@ -250,14 +247,14 @@ class ConversationCatalogTest(unittest.TestCase): self.assertEqual(comment1_brain.total_comments, None) def test_dont_index_private_commentators(self): - self.comment1.manage_permission('View', roles=tuple()) + self.comment1.manage_permission("View", roles=tuple()) self.portal.doc1.reindexObject() brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) doc1_brain = brains[0] @@ -269,70 +266,70 @@ class CommentCatalogTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.catalog = getToolByName(self.portal, 'portal_catalog') + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.catalog = getToolByName(self.portal, "portal_catalog") conversation = IConversation(self.portal.doc1) self.conversation = conversation - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' - comment1.creator = 'jim' - comment1.author_name = 'Jim' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" + comment1.creator = "jim" + comment1.author_name = "Jim" new_comment1_id = conversation.addComment(comment1) self.comment_id = new_comment1_id # Comment brain self.comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment1_id), + f"++conversation++default/{new_comment1_id}", ) brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.comment.getPhysicalPath()), + "query": "/".join(self.comment.getPhysicalPath()), }, ), ) self.comment_brain = brains[0] def test_title(self): - self.assertEqual(self.comment_brain.Title, 'Jim on Document 1') + self.assertEqual(self.comment_brain.Title, "Jim on Document 1") def test_no_name_title(self): - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" cid = self.conversation.addComment(comment) # Comment brain comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(cid), + f"++conversation++default/{cid}", ) brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(comment.getPhysicalPath()), + "query": "/".join(comment.getPhysicalPath()), }, ), ) comment_brain = brains[0] - self.assertEqual(comment_brain.Title, 'Anonymous on Document 1') + self.assertEqual(comment_brain.Title, "Anonymous on Document 1") def test_type(self): - self.assertEqual(self.comment_brain.portal_type, 'Discussion Item') - self.assertEqual(self.comment_brain.Type, 'Comment') + self.assertEqual(self.comment_brain.portal_type, "Discussion Item") + self.assertEqual(self.comment_brain.Type, "Comment") def test_review_state(self): - self.assertEqual(self.comment_brain.review_state, 'published') + self.assertEqual(self.comment_brain.review_state, "published") def test_creator(self): - self.assertEqual(self.comment_brain.Creator, 'jim') + self.assertEqual(self.comment_brain.Creator, "jim") def test_in_response_to(self): """Make sure in_response_to returns the title or id of the content - object the comment was added to. + object the comment was added to. """ - self.assertEqual(self.comment_brain.in_response_to, 'Document 1') + self.assertEqual(self.comment_brain.in_response_to, "Document 1") def test_add_comment(self): self.assertTrue(self.comment_brain) @@ -344,7 +341,7 @@ class CommentCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.comment.getPhysicalPath()), + "query": "/".join(self.comment.getPhysicalPath()), }, ), ) @@ -352,54 +349,54 @@ class CommentCatalogTest(unittest.TestCase): def test_reindex_comment(self): # Make sure a comment is reindexed on the catalog when is modified - self.comment.text = 'Another text' + self.comment.text = "Another text" notify(ObjectModifiedEvent(self.comment)) - brains = self.catalog.searchResults(SearchableText='Another text') + brains = self.catalog.searchResults(SearchableText="Another text") self.assertEqual(len(brains), 1) def test_remove_comments_when_content_object_is_removed(self): """Make sure all comments are removed from the catalog, if the content - object is removed. + object is removed. """ - brains = self.catalog.searchResults({'portal_type': 'Discussion Item'}) + brains = self.catalog.searchResults({"portal_type": "Discussion Item"}) self.assertEqual(len(brains), 1) - self.portal.manage_delObjects(['doc1']) - brains = self.catalog.searchResults({'portal_type': 'Discussion Item'}) + self.portal.manage_delObjects(["doc1"]) + brains = self.catalog.searchResults({"portal_type": "Discussion Item"}) self.assertEqual(len(brains), 0) def test_move_comments_when_content_object_is_moved(self): # Create two folders and a content object with a comment self.portal.invokeFactory( - id='folder1', - title='Folder 1', - type_name='Folder', + id="folder1", + title="Folder 1", + type_name="Folder", ) self.portal.invokeFactory( - id='folder2', - title='Folder 2', - type_name='Folder', + id="folder2", + title="Folder 2", + type_name="Folder", ) self.portal.folder1.invokeFactory( - id='moveme', - title='Move Me', - type_name='Document', + id="moveme", + title="Move Me", + type_name="Document", ) conversation = IConversation(self.portal.folder1.moveme) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") comment_id = conversation.addComment(comment) # We need to commit here so that _p_jar isn't None and move will work transaction.savepoint(optimistic=True) # Move moveme from folder1 to folder2 - cp = self.portal.folder1.manage_cutObjects(ids=('moveme',)) + cp = self.portal.folder1.manage_cutObjects(ids=("moveme",)) self.portal.folder2.manage_pasteObjects(cp) # Make sure no old comment brains are brains = self.catalog.searchResults( dict( - portal_type='Discussion Item', + portal_type="Discussion Item", path={ - 'query': '/'.join(self.portal.folder1.getPhysicalPath()), + "query": "/".join(self.portal.folder1.getPhysicalPath()), }, ), ) @@ -407,61 +404,60 @@ class CommentCatalogTest(unittest.TestCase): brains = self.catalog.searchResults( dict( - portal_type='Discussion Item', + portal_type="Discussion Item", path={ - 'query': '/'.join(self.portal.folder2.getPhysicalPath()), + "query": "/".join(self.portal.folder2.getPhysicalPath()), }, ), ) self.assertEqual(len(brains), 1) self.assertEqual( brains[0].getPath(), - '/plone/folder2/moveme/++conversation++default/' + - str(comment_id), + "/plone/folder2/moveme/++conversation++default/" + str(comment_id), ) def test_move_upper_level_folder(self): # create a folder with a nested structure self.portal.invokeFactory( - id='sourcefolder', - title='Source Folder', - type_name='Folder', + id="sourcefolder", + title="Source Folder", + type_name="Folder", ) self.portal.sourcefolder.invokeFactory( - id='moveme', - title='Move Me', - type_name='Folder', + id="moveme", + title="Move Me", + type_name="Folder", ) self.portal.sourcefolder.moveme.invokeFactory( - id='mydocument', - title='My Document', - type_name='Folder', + id="mydocument", + title="My Document", + type_name="Folder", ) self.portal.invokeFactory( - id='targetfolder', - title='Target Folder', - type_name='Folder', + id="targetfolder", + title="Target Folder", + type_name="Folder", ) # create comment on my-document conversation = IConversation( self.portal.sourcefolder.moveme.mydocument, ) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") comment_id = conversation.addComment(comment) # We need to commit here so that _p_jar isn't None and move will work transaction.savepoint(optimistic=True) # Move moveme from folder1 to folder2 - cp = self.portal.sourcefolder.manage_cutObjects(ids=('moveme',)) + cp = self.portal.sourcefolder.manage_cutObjects(ids=("moveme",)) self.portal.targetfolder.manage_pasteObjects(cp) # Make sure no old comment brains are left brains = self.catalog.searchResults( dict( - portal_type='Discussion Item', - path={'query': '/plone/sourcefolder/moveme'}, + portal_type="Discussion Item", + path={"query": "/plone/sourcefolder/moveme"}, ), ) self.assertEqual(len(brains), 0) @@ -469,49 +465,47 @@ class CommentCatalogTest(unittest.TestCase): # make sure comments are correctly index on the target brains = self.catalog.searchResults( dict( - portal_type='Discussion Item', - path={'query': '/plone/targetfolder/moveme'}, + portal_type="Discussion Item", + path={"query": "/plone/targetfolder/moveme"}, ), ) self.assertEqual(len(brains), 1) self.assertEqual( brains[0].getPath(), - '/plone/targetfolder/moveme/mydocument/++conversation++default/' + - str(comment_id), + "/plone/targetfolder/moveme/mydocument/++conversation++default/" + + str(comment_id), ) def test_update_comments_when_content_object_is_renamed(self): # We need to commit here so that _p_jar isn't None and move will work transaction.savepoint(optimistic=True) - self.portal.manage_renameObject('doc1', 'doc2') + self.portal.manage_renameObject("doc1", "doc2") brains = self.catalog.searchResults( - portal_type='Discussion Item', + portal_type="Discussion Item", ) self.assertEqual(len(brains), 1) self.assertEqual( brains[0].getPath(), - '/plone/doc2/++conversation++default/' + - str(self.comment_id), + "/plone/doc2/++conversation++default/" + str(self.comment_id), ) def test_clear_and_rebuild_catalog(self): - brains = self.catalog.searchResults({'portal_type': 'Discussion Item'}) + brains = self.catalog.searchResults({"portal_type": "Discussion Item"}) self.assertTrue(brains) # Clear and rebuild catalog self.catalog.clearFindAndRebuild() # Check if comment is still there - brains = self.catalog.searchResults({'portal_type': 'Discussion Item'}) + brains = self.catalog.searchResults({"portal_type": "Discussion Item"}) self.assertTrue(brains) comment_brain = brains[0] - self.assertEqual(comment_brain.Title, u'Jim on Document 1') + self.assertEqual(comment_brain.Title, "Jim on Document 1") self.assertEqual( comment_brain.getPath(), - '/plone/doc1/++conversation++default/' + - str(self.comment_id), + "/plone/doc1/++conversation++default/" + str(self.comment_id), ) def test_clear_and_rebuild_catalog_for_nested_comments(self): @@ -526,25 +520,25 @@ class CommentCatalogTest(unittest.TestCase): # +- Comment 2 # +- Comment 2_1 - comment1_1 = createObject('plone.Comment') - comment1_1.title = 'Re: Comment 1' - comment1_1.text = 'Comment text' + comment1_1 = createObject("plone.Comment") + comment1_1.title = "Re: Comment 1" + comment1_1.text = "Comment text" - comment1_1_1 = createObject('plone.Comment') - comment1_1_1.title = 'Re: Re: Comment 1' - comment1_1_1.text = 'Comment text' + comment1_1_1 = createObject("plone.Comment") + comment1_1_1.title = "Re: Re: Comment 1" + comment1_1_1.text = "Comment text" - comment1_2 = createObject('plone.Comment') - comment1_2.title = 'Re: Comment 1 (2)' - comment1_2.text = 'Comment text' + comment1_2 = createObject("plone.Comment") + comment1_2.title = "Re: Comment 1 (2)" + comment1_2.text = "Comment text" - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" - comment2_1 = createObject('plone.Comment') - comment2_1.title = 'Re: Comment 2' - comment2_1.text = 'Comment text' + comment2_1 = createObject("plone.Comment") + comment2_1.title = "Re: Comment 2" + comment2_1.text = "Comment text" # Create the nested comment structure new_id_1 = self.conversation.addComment(self.comment) @@ -566,7 +560,7 @@ class CommentCatalogTest(unittest.TestCase): self.catalog.clearFindAndRebuild() # Check if comments are still there - brains = self.catalog.searchResults({'portal_type': 'Discussion Item'}) + brains = self.catalog.searchResults({"portal_type": "Discussion Item"}) self.assertTrue(brains) self.assertEqual(len(brains), 6) @@ -576,19 +570,19 @@ class NoConversationCatalogTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) - self.catalog = getToolByName(self.portal, 'portal_catalog') + self.catalog = getToolByName(self.portal, "portal_catalog") conversation = IConversation(self.portal.doc1) brains = self.catalog.searchResults( dict( path={ - 'query': '/'.join(self.portal.doc1.getPhysicalPath()), + "query": "/".join(self.portal.doc1.getPhysicalPath()), }, - portal_type='Document', + portal_type="Document", ), ) self.conversation = conversation @@ -596,11 +590,10 @@ class NoConversationCatalogTest(unittest.TestCase): self.doc1_brain = brains[0] def test_total_comments(self): - self.assertTrue('total_comments' in self.doc1_brain) + self.assertTrue("total_comments" in self.doc1_brain) self.assertEqual(self.doc1_brain.total_comments, 0) # Make sure no conversation has been created self.assertTrue( - 'plone.app.discussion:conversation' not in - IAnnotations(self.portal.doc1), + "plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1), ) diff --git a/plone/app/discussion/tests/test_comment.py b/plone/app/discussion/tests/test_comment.py index f5f726c..17bcb64 100644 --- a/plone/app/discussion/tests/test_comment.py +++ b/plone/app/discussion/tests/test_comment.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.browser.comment import View from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IReplies -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from Products.CMFCore.utils import getToolByName @@ -12,11 +11,10 @@ from zope.component import getMultiAdapter import datetime import logging -import six import unittest -logger = logging.getLogger('plone.app.discussion.tests') +logger = logging.getLogger("plone.app.discussion.tests") logger.addHandler(logging.StreamHandler()) @@ -25,30 +23,30 @@ class CommentTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - self.request = self.layer['request'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.catalog = getToolByName(self.portal, 'portal_catalog') - self.document_brain = self.catalog.searchResults( - portal_type='Document')[0] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.catalog = getToolByName(self.portal, "portal_catalog") + self.document_brain = self.catalog.searchResults(portal_type="Document")[0] def test_factory(self): - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") self.assertTrue(IComment.providedBy(comment1)) def test_UTCDates(self): - utc_to_local_diff = \ - datetime.datetime.now() - datetime.datetime.utcnow() + utc_to_local_diff = datetime.datetime.now() - datetime.datetime.utcnow() utc_to_local_diff = abs(utc_to_local_diff.seconds) if utc_to_local_diff < 60: - logger.warning('Your computer is living in a timezone where local ' - 'time equals utc time. Some potential errors can ' - 'get hidden by that') - comment1 = createObject('plone.Comment') + logger.warning( + "Your computer is living in a timezone where local " + "time equals utc time. Some potential errors can " + "get hidden by that" + ) + comment1 = createObject("plone.Comment") local_utc = datetime.datetime.utcnow() for date in (comment1.creation_date, comment1.modification_date): difference = abs(date - local_utc) @@ -58,171 +56,163 @@ class CommentTest(unittest.TestCase): self.assertFalse(difference // 10) def test_id(self): - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") comment1.comment_id = 123 - self.assertEqual('123', comment1.id) - self.assertEqual('123', comment1.getId()) - self.assertEqual(u'123', comment1.__name__) + self.assertEqual("123", comment1.id) + self.assertEqual("123", comment1.getId()) + self.assertEqual("123", comment1.__name__) def test_uid(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") conversation.addComment(comment1) comment_brain = self.catalog.searchResults( - portal_type='Discussion Item', + portal_type="Discussion Item", )[0] self.assertTrue(comment_brain.UID) def test_uid_is_unique(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") conversation.addComment(comment1) - comment2 = createObject('plone.Comment') + comment2 = createObject("plone.Comment") conversation.addComment(comment2) brains = self.catalog.searchResults( - portal_type='Discussion Item', + portal_type="Discussion Item", ) self.assertNotEqual(brains[0].UID, brains[1].UID) def test_comment_uid_differs_from_content_uid(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") conversation.addComment(comment1) comment_brain = self.catalog.searchResults( - portal_type='Discussion Item', + portal_type="Discussion Item", )[0] self.assertNotEqual(self.document_brain.UID, comment_brain.UID) def test_title(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.author_name = 'Jim Fulton' + comment1 = createObject("plone.Comment") + comment1.author_name = "Jim Fulton" conversation.addComment(comment1) - self.assertEqual('Jim Fulton on Document 1', comment1.Title()) + self.assertEqual("Jim Fulton on Document 1", comment1.Title()) def test_no_name_title(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") conversation.addComment(comment1) - self.assertEqual('Anonymous on Document 1', comment1.Title()) + self.assertEqual("Anonymous on Document 1", comment1.Title()) def test_title_special_characters(self): self.portal.invokeFactory( - id='doc_sp_chars', - title=u'Document äüö', - type_name='Document', + id="doc_sp_chars", + title="Document äüö", + type_name="Document", ) conversation = IConversation(self.portal.doc_sp_chars) - comment1 = createObject('plone.Comment') - comment1.author_name = u'Tarek Ziadé' + comment1 = createObject("plone.Comment") + comment1.author_name = "Tarek Ziadé" conversation.addComment(comment1) - self.assertEqual(u'Tarek Ziadé on Document äüö', comment1.Title()) + self.assertEqual("Tarek Ziadé on Document äüö", comment1.Title()) def test_title_special_characters_utf8(self): self.portal.invokeFactory( - id='doc_sp_chars_utf8', - title='Document ëïû', - type_name='Document', + id="doc_sp_chars_utf8", + title="Document ëïû", + type_name="Document", ) conversation = IConversation(self.portal.doc_sp_chars_utf8) - comment1 = createObject('plone.Comment') - comment1.author_name = 'Hüüb Bôûmä' + comment1 = createObject("plone.Comment") + comment1.author_name = "Hüüb Bôûmä" conversation.addComment(comment1) - self.assertEqual(u'Hüüb Bôûmä on Document ëïû', comment1.Title()) + self.assertEqual("Hüüb Bôûmä on Document ëïû", comment1.Title()) def test_creator(self): - comment1 = createObject('plone.Comment') - comment1.creator = 'jim' - self.assertEqual('jim', comment1.Creator()) + comment1 = createObject("plone.Comment") + comment1.creator = "jim" + self.assertEqual("jim", comment1.Creator()) def test_creator_author_name(self): - comment1 = createObject('plone.Comment') - comment1.author_name = 'joey' - self.assertEqual('joey', comment1.Creator()) + comment1 = createObject("plone.Comment") + comment1.author_name = "joey" + self.assertEqual("joey", comment1.Creator()) def test_owner(self): - comment1 = createObject('plone.Comment') - self.assertEqual((['plone', 'acl_users'], TEST_USER_ID), - comment1.getOwnerTuple()) + comment1 = createObject("plone.Comment") + self.assertEqual( + (["plone", "acl_users"], TEST_USER_ID), comment1.getOwnerTuple() + ) def test_type(self): - comment1 = createObject('plone.Comment') - self.assertEqual(comment1.Type(), 'Comment') + comment1 = createObject("plone.Comment") + self.assertEqual(comment1.Type(), "Comment") def test_mime_type(self): - comment1 = createObject('plone.Comment') - self.assertEqual(comment1.mime_type, 'text/plain') + comment1 = createObject("plone.Comment") + self.assertEqual(comment1.mime_type, "text/plain") def test_getText(self): - comment1 = createObject('plone.Comment') - comment1.text = 'First paragraph\n\nSecond_paragraph' + comment1 = createObject("plone.Comment") + comment1.text = "First paragraph\n\nSecond_paragraph" self.assertEqual( - ''.join(comment1.getText().split()), - '

Firstparagraph

Second_paragraph

', + "".join(comment1.getText().split()), + "

Firstparagraph

Second_paragraph

", ) def test_getText_escapes_HTML(self): - comment1 = createObject('plone.Comment') - comment1.text = 'Got HTML?' + comment1 = createObject("plone.Comment") + comment1.text = "Got HTML?" self.assertEqual( comment1.getText(), - '

<b>Got HTML?</b>

', + "

<b>Got HTML?</b>

", ) def test_getText_with_non_ascii_characters(self): - comment1 = createObject('plone.Comment') - comment1.text = u'Umlaute sind ä, ö und ü.' - out = b'

Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.

' - if six.PY2: - self.assertEqual( - comment1.getText(), - out) - else: - self.assertEqual( - comment1.getText(), - out.decode('utf8')) + comment1 = createObject("plone.Comment") + comment1.text = "Umlaute sind ä, ö und ü." + out = b"

Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.

" + self.assertEqual(comment1.getText(), out.decode("utf8")) def test_getText_doesnt_link(self): - comment1 = createObject('plone.Comment') - comment1.text = 'Go to http://www.plone.org' + comment1 = createObject("plone.Comment") + comment1.text = "Go to http://www.plone.org" self.assertEqual( comment1.getText(), - '

Go to http://www.plone.org

', + "

Go to http://www.plone.org

", ) def test_getText_uses_comment_mime_type(self): - comment1 = createObject('plone.Comment') - comment1.text = 'Go to http://www.plone.org' - comment1.mime_type = 'text/x-web-intelligent' + comment1 = createObject("plone.Comment") + comment1.text = "Go to http://www.plone.org" + comment1.mime_type = "text/x-web-intelligent" self.assertEqual( comment1.getText(), - 'Go to http://www.plone.org', + 'Go to http://www.plone.org', ) def test_getText_uses_comment_mime_type_html(self): - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") comment1.text = 'Go to plone.org' - comment1.mime_type = 'text/html' + comment1.mime_type = "text/html" self.assertEqual( comment1.getText(), 'Go to plone.org', ) def test_getText_w_custom_targetMimetype(self): - comment1 = createObject('plone.Comment') - comment1.text = 'para' - self.assertEqual(comment1.getText(targetMimetype='text/plain'), 'para') + comment1 = createObject("plone.Comment") + comment1.text = "para" + self.assertEqual(comment1.getText(targetMimetype="text/plain"), "para") def test_getText_invalid_transformation_raises_error(self): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.mime_type = 'text/x-html-safe' - comment1.text = 'para' + comment1 = createObject("plone.Comment") + comment1.mime_type = "text/x-html-safe" + comment1.text = "para" conversation.addComment(comment1) - self.assertEqual( - comment1.getText(targetMimetype='text/html'), - 'para') + self.assertEqual(comment1.getText(targetMimetype="text/html"), "para") def test_traversal(self): # make sure comments are traversable, have an id, absolute_url and @@ -230,26 +220,29 @@ class CommentTest(unittest.TestCase): conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" new_comment1_id = conversation.addComment(comment1) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment1_id), + f"++conversation++default/{new_comment1_id}", ) self.assertTrue(IComment.providedBy(comment)) self.assertEqual( ( - '', 'plone', 'doc1', '++conversation++default', + "", + "plone", + "doc1", + "++conversation++default", str(new_comment1_id), ), comment.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default/' + - str(new_comment1_id), comment.absolute_url(), + "http://nohost/plone/doc1/++conversation++default/" + str(new_comment1_id), + comment.absolute_url(), ) def test_view_blob_types(self): @@ -258,68 +251,67 @@ class CommentTest(unittest.TestCase): version of the url with a /view in it. """ self.portal.invokeFactory( - id='image1', - title='Image', - type_name='Image', + id="image1", + title="Image", + type_name="Image", ) conversation = IConversation(self.portal.image1) - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" new_comment1_id = conversation.addComment(comment1) comment = self.portal.image1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment1_id), + f"++conversation++default/{new_comment1_id}", ) view = View(comment, self.request) View.__call__(view) response = self.request.response - self.assertIn('/view', response.headers['location']) + self.assertIn("/view", response.headers["location"]) def test_workflow(self): - """Basic test for the 'comment_review_workflow' - """ + """Basic test for the 'comment_review_workflow'""" self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow,'), + ("Discussion Item",), + ("comment_review_workflow,"), ) conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") new_comment1_id = conversation.addComment(comment1) comment = conversation[new_comment1_id] # Make sure comments use the 'comment_review_workflow' chain = self.portal.portal_workflow.getChainFor(comment) - self.assertEqual(('comment_review_workflow',), chain) + self.assertEqual(("comment_review_workflow",), chain) # Ensure the initial state was entered and recorded self.assertEqual( 1, - len(comment.workflow_history['comment_review_workflow']), + len(comment.workflow_history["comment_review_workflow"]), ) self.assertEqual( None, - comment.workflow_history['comment_review_workflow'][0]['action'], + comment.workflow_history["comment_review_workflow"][0]["action"], ) self.assertEqual( - 'pending', - self.portal.portal_workflow.getInfoFor(comment, 'review_state'), + "pending", + self.portal.portal_workflow.getInfoFor(comment, "review_state"), ) def test_fti(self): # test that we can look up an FTI for Discussion Item self.assertIn( - 'Discussion Item', + "Discussion Item", self.portal.portal_types.objectIds(), ) - comment1 = createObject('plone.Comment') + comment1 = createObject("plone.Comment") fti = self.portal.portal_types.getTypeInfo(comment1) - self.assertEqual('Discussion Item', fti.getTypeInfo(comment1).getId()) + self.assertEqual("Discussion Item", fti.getTypeInfo(comment1).getId()) def test_view(self): # make sure that the comment view is there and redirects to the right @@ -330,21 +322,21 @@ class CommentTest(unittest.TestCase): conversation = IConversation(self.portal.doc1) # Create a comment - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" # Add comment to the conversation new_comment1_id = conversation.addComment(comment1) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_comment1_id), + f"++conversation++default/{new_comment1_id}", ) # make sure the view is there self.assertTrue( getMultiAdapter( (comment, self.request), - name='view', + name="view", ), ) @@ -362,11 +354,11 @@ class RepliesTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") def test_add_comment(self): # Add comments to a CommentReplies adapter @@ -378,16 +370,16 @@ class RepliesTest(unittest.TestCase): # Add a comment to the conversation replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) # Add a reply to the CommentReplies adapter of the first comment - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) @@ -415,16 +407,16 @@ class RepliesTest(unittest.TestCase): # Add a comment to the conversation replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) # Add a reply to the CommentReplies adapter of the first comment - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) @@ -446,83 +438,86 @@ class RepliesTest(unittest.TestCase): # physical path conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" conversation.addComment(comment1) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = conversation.addComment(comment) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) # Add a reply to the CommentReplies adapter of the first comment - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) new_re_id = replies.addComment(re_comment) re_comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_re_id), + f"++conversation++default/{new_re_id}", ) # Add a reply to the reply - re_re_comment = createObject('plone.Comment') - re_re_comment.text = 'Comment text' + re_re_comment = createObject("plone.Comment") + re_re_comment.text = "Comment text" replies = IReplies(re_comment) new_re_re_id = replies.addComment(re_re_comment) re_re_comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_re_re_id), + f"++conversation++default/{new_re_re_id}", ) # Add a reply to the replies reply - re_re_re_comment = createObject('plone.Comment') - re_re_re_comment.text = 'Comment text' + re_re_re_comment = createObject("plone.Comment") + re_re_re_comment.text = "Comment text" replies = IReplies(re_re_comment) new_re_re_re_id = replies.addComment(re_re_re_comment) re_re_re_comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_re_re_re_id), + f"++conversation++default/{new_re_re_re_id}", ) self.assertEqual( - ('', 'plone', 'doc1', '++conversation++default', str(new_id)), + ("", "plone", "doc1", "++conversation++default", str(new_id)), comment.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default/' + - str(new_id), comment.absolute_url(), + "http://nohost/plone/doc1/++conversation++default/" + str(new_id), + comment.absolute_url(), ) self.assertEqual( - ('', 'plone', 'doc1', '++conversation++default', str(new_re_id)), + ("", "plone", "doc1", "++conversation++default", str(new_re_id)), re_comment.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default/' + - str(new_re_id), + "http://nohost/plone/doc1/++conversation++default/" + str(new_re_id), re_comment.absolute_url(), ) self.assertEqual( ( - '', 'plone', 'doc1', '++conversation++default', + "", + "plone", + "doc1", + "++conversation++default", str(new_re_re_id), ), re_re_comment.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default/' + - str(new_re_re_id), + "http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_id), re_re_comment.absolute_url(), ) self.assertEqual( ( - '', 'plone', 'doc1', '++conversation++default', + "", + "plone", + "doc1", + "++conversation++default", str(new_re_re_re_id), ), re_re_re_comment.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default/' + - str(new_re_re_re_id), + "http://nohost/plone/doc1/++conversation++default/" + str(new_re_re_re_id), re_re_re_comment.absolute_url(), ) diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py index 06b3231..54a18bd 100644 --- a/plone/app/discussion/tests/test_comments_viewlet.py +++ b/plone/app/discussion/tests/test_comments_viewlet.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- +from .. import interfaces +from ..browser.comment import EditCommentForm +from ..browser.comments import CommentForm +from ..browser.comments import CommentsViewlet +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from AccessControl import Unauthorized from datetime import datetime from OFS.Image import Image -from plone.app.discussion import interfaces -from plone.app.discussion.browser.comment import EditCommentForm -from plone.app.discussion.browser.comments import CommentForm -from plone.app.discussion.browser.comments import CommentsViewlet -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa from plone.app.testing import login from plone.app.testing import logout from plone.app.testing import setRoles @@ -16,7 +15,6 @@ from plone.app.testing import TEST_USER_ID from plone.app.testing import TEST_USER_NAME from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.tests import dummy from z3c.form.interfaces import IFormLayer from zope import interface from zope.annotation.interfaces import IAttributeAnnotatable @@ -28,33 +26,67 @@ from zope.interface import alsoProvides from zope.interface import Interface from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IBrowserRequest +from ZPublisher.HTTPRequest import FileUpload +import io import time import unittest +TEXT = b"file data" + + +class DummyFile(FileUpload): + """Dummy upload object + Used to fake uploaded files. + """ + + __allow_access_to_unprotected_subobjects__ = 1 + filename = "dummy.txt" + data = TEXT + headers = {} + + def __init__(self, filename=None, data=None, headers=None): + if filename is not None: + self.filename = filename + if data is not None: + self.data = data + if headers is not None: + self.headers = headers + self.file = io.BytesIO(self.data) + + def seek(self, *args): + pass + + def tell(self, *args): + return 1 + + def read(self, *args): + return self.data + + class TestCommentForm(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal.invokeFactory('Folder', 'test-folder') - self.folder = self.portal['test-folder'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal.invokeFactory("Folder", "test-folder") + self.folder = self.portal["test-folder"] interface.alsoProvides( self.portal.REQUEST, interfaces.IDiscussionLayer, ) - wftool = getToolByName(self.portal, 'portal_workflow') - wftool.doActionFor(self.portal.doc1, action='publish') + wftool = getToolByName(self.portal, "portal_workflow") + wftool.doActionFor(self.portal.doc1, action="publish") self.portal.doc1.allow_discussion = True - self.membershipTool = getToolByName(self.folder, 'portal_membership') + self.membershipTool = getToolByName(self.folder, "portal_membership") self.memberdata = self.portal.portal_memberdata - self.context = getattr(self.portal, 'doc1') + self.context = getattr(self.portal, "doc1") # Allow discussion registry = queryUtility(IRegistry) @@ -62,8 +94,7 @@ class TestCommentForm(unittest.TestCase): settings.globally_enabled = True def test_add_comment(self): - """Post a comment as logged-in user. - """ + """Post a comment as logged-in user.""" # Allow discussion self.portal.doc1.allow_discussion = True @@ -80,7 +111,7 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u'comment-form', + name="comment-form", ) # The form should return an error if the comment text field is empty @@ -88,46 +119,45 @@ class TestCommentForm(unittest.TestCase): commentForm = getMultiAdapter( (self.context, request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 1) - self.assertFalse(commentForm.handleComment(commentForm, 'foo')) + self.assertFalse(commentForm.handleComment(commentForm, "foo")) # The form is submitted successfully, if the required text field is # filled out - request = make_request(form={'form.widgets.text': u'bar'}) + request = make_request(form={"form.widgets.text": "bar"}) commentForm = getMultiAdapter( (self.context, request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(commentForm.handleComment(commentForm, 'foo')) + self.assertFalse(commentForm.handleComment(commentForm, "foo")) comments = IConversation(commentForm.context).getComments() comments = [comment for comment in comments] # consume iterator self.assertEqual(len(comments), 1) for comment in comments: - self.assertEqual(comment.text, u'bar') - self.assertEqual(comment.creator, 'test_user_1_') - self.assertEqual(comment.getOwner().getUserName(), 'test-user') + self.assertEqual(comment.text, "bar") + self.assertEqual(comment.creator, "test_user_1_") + self.assertEqual(comment.getOwner().getUserName(), "test-user") local_roles = comment.get_local_roles() self.assertEqual(len(local_roles), 1) userid, roles = local_roles[0] - self.assertEqual(userid, 'test_user_1_') + self.assertEqual(userid, "test_user_1_") self.assertEqual(len(roles), 1) - self.assertEqual(roles[0], 'Owner') + self.assertEqual(roles[0], "Owner") def test_edit_comment(self): - """Edit a comment as logged-in user. - """ + """Edit a comment as logged-in user.""" # Allow discussion self.portal.doc1.allow_discussion = True @@ -144,65 +174,64 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u'comment-form', + name="comment-form", ) provideAdapter( adapts=(Interface, IBrowserRequest), provides=Interface, factory=EditCommentForm, - name=u'edit-comment-form', + name="edit-comment-form", ) # The form is submitted successfully, if the required text field is # filled out - request = make_request(form={'form.widgets.text': u'bar'}) + request = make_request(form={"form.widgets.text": "bar"}) commentForm = getMultiAdapter( (self.context, request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(commentForm.handleComment(commentForm, 'foo')) + self.assertFalse(commentForm.handleComment(commentForm, "foo")) # Edit the last comment conversation = IConversation(self.context) comment = [x for x in conversation.getComments()][-1] - request = make_request(form={'form.widgets.text': u'foobar'}) + request = make_request(form={"form.widgets.text": "foobar"}) editForm = getMultiAdapter( (comment, request), - name=u'edit-comment-form', + name="edit-comment-form", ) editForm.update() data, errors = editForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(editForm.handleComment(editForm, 'foo')) + self.assertFalse(editForm.handleComment(editForm, "foo")) comment = [x for x in conversation.getComments()][-1] - self.assertEqual(comment.text, u'foobar') + self.assertEqual(comment.text, "foobar") comments = IConversation(commentForm.context).getComments() comments = [c for c in comments] # consume iterator self.assertEqual(len(comments), 1) for comment in comments: - self.assertEqual(comment.text, u'foobar') - self.assertEqual(comment.creator, 'test_user_1_') + self.assertEqual(comment.text, "foobar") + self.assertEqual(comment.creator, "test_user_1_") - self.assertEqual(comment.getOwner().getUserName(), 'test-user') + self.assertEqual(comment.getOwner().getUserName(), "test-user") local_roles = comment.get_local_roles() self.assertEqual(len(local_roles), 1) userid, roles = local_roles[0] - self.assertEqual(userid, 'test_user_1_') + self.assertEqual(userid, "test_user_1_") self.assertEqual(len(roles), 1) - self.assertEqual(roles[0], 'Owner') + self.assertEqual(roles[0], "Owner") def test_delete_comment(self): - """Delete a comment as logged-in user. - """ + """Delete a comment as logged-in user.""" # Allow discussion self.portal.doc1.allow_discussion = True @@ -219,48 +248,47 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u'comment-form', + name="comment-form", ) # The form is submitted successfully, if the required text field is # filled out - form_request = make_request(form={'form.widgets.text': u'bar'}) + form_request = make_request(form={"form.widgets.text": "bar"}) commentForm = getMultiAdapter( (self.context, form_request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(commentForm.handleComment(commentForm, 'foo')) + self.assertFalse(commentForm.handleComment(commentForm, "foo")) # Delete the last comment conversation = IConversation(self.context) comment = [x for x in conversation.getComments()][-1] deleteView = getMultiAdapter( (comment, self.request), - name=u'moderate-delete-comment', + name="moderate-delete-comment", ) # try to delete last comment without 'Delete comments' permission - setRoles(self.portal, TEST_USER_ID, ['Member']) + setRoles(self.portal, TEST_USER_ID, ["Member"]) self.assertRaises( Unauthorized, comment.restrictedTraverse, - '@@moderate-delete-comment', + "@@moderate-delete-comment", ) deleteView() self.assertEqual(1, len([x for x in conversation.getComments()])) # try to delete last comment with 'Delete comments' permission - setRoles(self.portal, TEST_USER_ID, ['Reviewer']) + setRoles(self.portal, TEST_USER_ID, ["Reviewer"]) deleteView() self.assertEqual(0, len([x for x in conversation.getComments()])) - setRoles(self.portal, TEST_USER_ID, ['Manager']) + setRoles(self.portal, TEST_USER_ID, ["Manager"]) def test_delete_own_comment(self): - """Delete own comment as logged-in user. - """ + """Delete own comment as logged-in user.""" # Allow discussion self.portal.doc1.allow_discussion = True @@ -277,42 +305,42 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u'comment-form', + name="comment-form", ) # The form is submitted successfully, if the required text field is # filled out - form_request = make_request(form={'form.widgets.text': u'bar'}) + form_request = make_request(form={"form.widgets.text": "bar"}) commentForm = getMultiAdapter( (self.context, form_request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(commentForm.handleComment(commentForm, 'foo')) + self.assertFalse(commentForm.handleComment(commentForm, "foo")) # Delete the last comment conversation = IConversation(self.context) comment = [x for x in conversation.getComments()][-1] deleteView = getMultiAdapter( (comment, self.request), - name=u'delete-own-comment', + name="delete-own-comment", ) # try to delete last comment with johndoe - setRoles(self.portal, 'johndoe', ['Member']) - login(self.portal, 'johndoe') + setRoles(self.portal, "johndoe", ["Member"]) + login(self.portal, "johndoe") self.assertRaises( Unauthorized, comment.restrictedTraverse, - '@@delete-own-comment', + "@@delete-own-comment", ) self.assertEqual(1, len([x for x in conversation.getComments()])) # try to delete last comment with the same user that created it login(self.portal, TEST_USER_NAME) - setRoles(self.portal, TEST_USER_ID, ['Member']) + setRoles(self.portal, TEST_USER_ID, ["Member"]) deleteView() self.assertEqual(0, len([x for x in conversation.getComments()])) @@ -335,40 +363,43 @@ class TestCommentForm(unittest.TestCase): alsoProvides(request, IAttributeAnnotatable) return request - provideAdapter(adapts=(Interface, IBrowserRequest), - provides=Interface, - factory=CommentForm, - name=u'comment-form') + provideAdapter( + adapts=(Interface, IBrowserRequest), + provides=Interface, + factory=CommentForm, + name="comment-form", + ) # Post an anonymous comment and provide a name - request = make_request(form={ - 'form.widgets.name': u'john doe', - 'form.widgets.text': u'bar', - }) + request = make_request( + form={ + "form.widgets.name": "john doe", + "form.widgets.text": "bar", + } + ) commentForm = getMultiAdapter( (self.context, request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 self.assertEqual(len(errors), 0) - self.assertFalse(commentForm.handleComment(commentForm, 'action')) + self.assertFalse(commentForm.handleComment(commentForm, "action")) comments = IConversation(commentForm.context).getComments() comments = [comment for comment in comments] # consume itertor self.assertEqual(len(comments), 1) for comment in IConversation(commentForm.context).getComments(): - self.assertEqual(comment.text, u'bar') + self.assertEqual(comment.text, "bar") self.assertIsNone(comment.creator) roles = comment.get_local_roles() self.assertEqual(len(roles), 0) def test_can_not_add_comments_if_discussion_is_not_allowed(self): - """Make sure that comments can't be posted if discussion is disabled. - """ + """Make sure that comments can't be posted if discussion is disabled.""" # Disable discussion registry = queryUtility(IRegistry) @@ -382,16 +413,18 @@ class TestCommentForm(unittest.TestCase): alsoProvides(request, IAttributeAnnotatable) return request - provideAdapter(adapts=(Interface, IBrowserRequest), - provides=Interface, - factory=CommentForm, - name=u'comment-form') + provideAdapter( + adapts=(Interface, IBrowserRequest), + provides=Interface, + factory=CommentForm, + name="comment-form", + ) - request = make_request(form={'form.widgets.text': u'bar'}) + request = make_request(form={"form.widgets.text": "bar"}) commentForm = getMultiAdapter( (self.context, request), - name=u'comment-form', + name="comment-form", ) commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 @@ -400,14 +433,11 @@ class TestCommentForm(unittest.TestCase): # allowed self.assertEqual(len(errors), 0) - self.assertRaises(Unauthorized, - commentForm.handleComment, - commentForm, - 'foo') + self.assertRaises(Unauthorized, commentForm.handleComment, commentForm, "foo") def test_anonymous_can_not_add_comments_if_discussion_is_not_allowed(self): """Make sure that anonymous users can't post comments if anonymous - comments are disabled. + comments are disabled. """ # Anonymous comments are disabled by default @@ -421,15 +451,16 @@ class TestCommentForm(unittest.TestCase): alsoProvides(request, IAttributeAnnotatable) return request - provideAdapter(adapts=(Interface, IBrowserRequest), - provides=Interface, - factory=CommentForm, - name=u'comment-form') + provideAdapter( + adapts=(Interface, IBrowserRequest), + provides=Interface, + factory=CommentForm, + name="comment-form", + ) - request = make_request(form={'form.widgets.text': u'bar'}) + request = make_request(form={"form.widgets.text": "bar"}) - commentForm = getMultiAdapter((self.context, request), - name=u'comment-form') + commentForm = getMultiAdapter((self.context, request), name="comment-form") commentForm.update() data, errors = commentForm.extractData() # pylint: disable-msg=W0612 @@ -438,7 +469,7 @@ class TestCommentForm(unittest.TestCase): Unauthorized, commentForm.handleComment, commentForm, - 'foo', + "foo", ) @@ -447,22 +478,22 @@ class TestCommentsViewlet(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal.invokeFactory('Folder', 'test-folder') - self.folder = self.portal['test-folder'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal.invokeFactory("Folder", "test-folder") + self.folder = self.portal["test-folder"] interface.alsoProvides( self.request, interfaces.IDiscussionLayer, ) - self.workflowTool = getToolByName(self.portal, 'portal_workflow') - self.workflowTool.setDefaultChain('comment_one_state_workflow') + self.workflowTool = getToolByName(self.portal, "portal_workflow") + self.workflowTool.setDefaultChain("comment_one_state_workflow") - self.membershipTool = getToolByName(self.folder, 'portal_membership') + self.membershipTool = getToolByName(self.folder, "portal_membership") self.memberdata = self.portal.portal_memberdata - context = getattr(self.portal, 'doc1') + context = getattr(self.portal, "doc1") self.viewlet = CommentsViewlet(context, self.request, None, None) # Allow discussion @@ -484,15 +515,14 @@ class TestCommentsViewlet(unittest.TestCase): # Anonymous has no 'can review' permission self.assertFalse(self.viewlet.can_review()) # The reviewer role has the 'Review comments' permission - self.portal.acl_users._doAddUser( - 'reviewer', 'secret', ['Reviewer'], []) - login(self.portal, 'reviewer') + self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], []) + login(self.portal, "reviewer") self.assertTrue(self.viewlet.can_review()) def test_can_manage(self): """We keep this method for backward compatibility. This method has been - removed in version 1.0b9 and added again in 1.0b11 because we don't - do API changes in beta releases. + removed in version 1.0b9 and added again in 1.0b11 because we don't + do API changes in beta releases. """ # Portal owner has 'can review' permission self.assertTrue(self.viewlet.can_manage()) @@ -500,9 +530,8 @@ class TestCommentsViewlet(unittest.TestCase): # Anonymous has no 'can review' permission self.assertFalse(self.viewlet.can_manage()) # The reviewer role has the 'Review comments' permission - self.portal.acl_users._doAddUser( - 'reviewer', 'secret', ['Reviewer'], []) - login(self.portal, 'reviewer') + self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], []) + login(self.portal, "reviewer") self.assertTrue(self.viewlet.can_manage()) def test_is_discussion_allowed(self): @@ -519,46 +548,48 @@ class TestCommentsViewlet(unittest.TestCase): self.assertTrue(self.viewlet.comment_transform_message()) self.assertEqual( self.viewlet.comment_transform_message(), - 'You can add a comment by filling out the form below. Plain ' + - 'text formatting.') + "You can add a comment by filling out the form below. Plain " + + "text formatting.", + ) # Set text transform to intelligent text registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) - settings.text_transform = 'text/x-web-intelligent' + settings.text_transform = "text/x-web-intelligent" # Make sure the comment description changes accordingly self.assertEqual( self.viewlet.comment_transform_message(), - 'You can add a comment by filling out the form below. ' + - 'Plain text formatting. Web and email addresses are transformed ' + - 'into clickable links.', + "You can add a comment by filling out the form below. " + + "Plain text formatting. Web and email addresses are transformed " + + "into clickable links.", ) # Enable moderation workflow self.workflowTool.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow,')) + ("Discussion Item",), ("comment_review_workflow,") + ) # Make sure the comment description shows that comments are moderated self.assertEqual( self.viewlet.comment_transform_message(), - 'You can add a comment by filling out the form below. ' + - 'Plain text formatting. Web and email addresses are transformed ' + - 'into clickable links. Comments are moderated.') + "You can add a comment by filling out the form below. " + + "Plain text formatting. Web and email addresses are transformed " + + "into clickable links. Comments are moderated.", + ) def test_has_replies(self): self.assertEqual(self.viewlet.has_replies(), False) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" conversation = IConversation(self.portal.doc1) conversation.addComment(comment) self.assertEqual(self.viewlet.has_replies(), True) def test_get_replies(self): self.assertFalse(self.viewlet.get_replies()) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" conversation = IConversation(self.portal.doc1) conversation.addComment(comment) conversation.addComment(comment) @@ -571,7 +602,7 @@ class TestCommentsViewlet(unittest.TestCase): next(replies) def test_get_replies_on_non_annotatable_object(self): - context = self.portal.MailHost # the mail host is not annotatable + context = self.portal.MailHost # the mail host is not annotatable viewlet = CommentsViewlet(context, self.request, None, None) replies = viewlet.get_replies() self.assertEqual(len(tuple(replies)), 0) @@ -581,8 +612,8 @@ class TestCommentsViewlet(unittest.TestCase): def test_get_replies_with_workflow_actions(self): self.assertFalse(self.viewlet.get_replies(workflow_actions=True)) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" conversation = IConversation(self.portal.doc1) c1 = conversation.addComment(comment) self.assertEqual( @@ -591,32 +622,34 @@ class TestCommentsViewlet(unittest.TestCase): ) # Enable moderation workflow self.workflowTool.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow,'), + ("Discussion Item",), + ("comment_review_workflow,"), ) # Check if workflow actions are available reply = next(self.viewlet.get_replies(workflow_actions=True)) - self.assertTrue('actions' in reply) + self.assertTrue("actions" in reply) self.assertEqual( - reply['actions'][0]['id'], - 'mark_as_spam', + reply["actions"][0]["id"], + "mark_as_spam", + ) + expected_url = ( + "http://nohost/plone/doc1/++conversation++default/{0}" + "/content_status_modify?workflow_action=mark_as_spam" ) - expected_url = 'http://nohost/plone/doc1/++conversation++default/{0}' \ - '/content_status_modify?workflow_action=mark_as_spam' self.assertEqual( - reply['actions'][0]['url'], + reply["actions"][0]["url"], expected_url.format(int(c1)), ) def test_get_commenter_home_url(self): - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" IConversation(self.portal.doc1) - portal_membership = getToolByName(self.portal, 'portal_membership') + portal_membership = getToolByName(self.portal, "portal_membership") m = portal_membership.getAuthenticatedMember() self.assertEqual( self.viewlet.get_commenter_home_url(m.getUserName()), - 'http://nohost/plone/author/test-user', + "http://nohost/plone/author/test-user", ) def test_get_commenter_home_url_is_none(self): @@ -625,72 +658,77 @@ class TestCommentsViewlet(unittest.TestCase): def test_get_commenter_portrait(self): # Add a user with a member image - self.membershipTool.addMember('jim', 'Jim', ['Member'], []) - self.memberdata._setPortrait(Image( - id='jim', - file=dummy.File(), - title='', - ), 'jim') - self.assertEqual( - self.memberdata._getPortrait('jim').getId(), - 'jim', + self.membershipTool.addMember("jim", "Jim", ["Member"], []) + self.memberdata._setPortrait( + Image( + id="jim", + file=DummyFile(), + title="", + ), + "jim", ) self.assertEqual( - self.memberdata._getPortrait('jim').meta_type, - 'Image', + self.memberdata._getPortrait("jim").getId(), + "jim", + ) + self.assertEqual( + self.memberdata._getPortrait("jim").meta_type, + "Image", ) # Add a conversation with a comment conversation = IConversation(self.portal.doc1) - comment = createObject('plone.Comment') - comment.text = 'Comment text' - comment.Creator = 'Jim' - comment.author_username = 'jim' + comment = createObject("plone.Comment") + comment.text = "Comment text" + comment.Creator = "Jim" + comment.author_username = "jim" conversation.addComment(comment) # Call get_commenter_portrait method of the viewlet self.viewlet.update() - portrait_url = self.viewlet.get_commenter_portrait('jim') + portrait_url = self.viewlet.get_commenter_portrait("jim") # Check if the correct member image URL is returned self.assertEqual( portrait_url, - 'http://nohost/plone/portal_memberdata/portraits/jim', + "http://nohost/plone/portal_memberdata/portraits/jim", ) def test_get_commenter_portrait_is_none(self): self.assertTrue( - self.viewlet.get_commenter_portrait() in ( - 'defaultUser.png', - 'defaultUser.gif', + self.viewlet.get_commenter_portrait() + in ( + "defaultUser.png", + "defaultUser.gif", ), ) def test_get_commenter_portrait_without_userimage(self): # Create a user without a user image - self.membershipTool.addMember('jim', 'Jim', ['Member'], []) + self.membershipTool.addMember("jim", "Jim", ["Member"], []) # Add a conversation with a comment conversation = IConversation(self.portal.doc1) - comment = createObject('plone.Comment') - comment.text = 'Comment text' - comment.Creator = 'Jim' - comment.author_username = 'jim' + comment = createObject("plone.Comment") + comment.text = "Comment text" + comment.Creator = "Jim" + comment.author_username = "jim" conversation.addComment(comment) # Call get_commenter_portrait method of the viewlet self.viewlet.update() - portrait_url = self.viewlet.get_commenter_portrait('jim') + portrait_url = self.viewlet.get_commenter_portrait("jim") # Check if the correct default member image URL is returned. # Note that Products.PlonePAS 4.0.5 and later have .png and # earlier versions have .gif. self.assertTrue( - portrait_url in ( - 'http://nohost/plone/defaultUser.png', - 'http://nohost/plone/defaultUser.gif', + portrait_url + in ( + "http://nohost/plone/defaultUser.png", + "http://nohost/plone/defaultUser.gif", ), ) @@ -700,8 +738,8 @@ class TestCommentsViewlet(unittest.TestCase): # Allow anonymous discussion registry = queryUtility(IRegistry) registry[ - 'plone.app.discussion.interfaces.IDiscussionSettings.' + - 'anonymous_comments' + "plone.app.discussion.interfaces.IDiscussionSettings." + + "anonymous_comments" ] = True # Test if anonymous discussion is allowed for the viewlet self.assertTrue(self.viewlet.anonymous_discussion_allowed()) @@ -710,8 +748,8 @@ class TestCommentsViewlet(unittest.TestCase): self.assertTrue(self.viewlet.show_commenter_image()) registry = queryUtility(IRegistry) registry[ - 'plone.app.discussion.interfaces.IDiscussionSettings.' + - 'show_commenter_image' + "plone.app.discussion.interfaces.IDiscussionSettings." + + "show_commenter_image" ] = False self.assertFalse(self.viewlet.show_commenter_image()) @@ -724,7 +762,7 @@ class TestCommentsViewlet(unittest.TestCase): self.viewlet.update() self.assertEqual( self.viewlet.login_action(), - 'http://nohost/plone/login_form?came_from=http%3A//nohost', + "http://nohost/plone/login_form?came_from=http%3A//nohost", ) def test_format_time(self): @@ -737,9 +775,8 @@ class TestCommentsViewlet(unittest.TestCase): # a correct utc time that can be used to make datetime set the utc # time of the local time given above. That way, the time for the # example below is correct within each time zone, independent of DST - python_time = datetime( - *time.gmtime(time.mktime(python_time.timetuple()))[:7]) + python_time = datetime(*time.gmtime(time.mktime(python_time.timetuple()))[:7]) localized_time = self.viewlet.format_time(python_time) self.assertTrue( - localized_time in ['Feb 01, 2009 11:32 PM', '2009-02-01 23:32'], + localized_time in ["Feb 01, 2009 11:32 PM", "2009-02-01 23:32"], ) diff --git a/plone/app/discussion/tests/test_contentrules.py b/plone/app/discussion/tests/test_contentrules.py index c05aede..506a538 100644 --- a/plone/app/discussion/tests/test_contentrules.py +++ b/plone/app/discussion/tests/test_contentrules.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import ICommentAddedEvent from plone.app.discussion.interfaces import ICommentRemovedEvent from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IReplies from plone.app.discussion.interfaces import IReplyAddedEvent from plone.app.discussion.interfaces import IReplyRemovedEvent -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from plone.app.discussion.testing import ( # noqa + PLONE_APP_DISCUSSION_INTEGRATION_TESTING, +) from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.contentrules.rule.interfaces import IRuleEventType @@ -17,31 +18,33 @@ import unittest class CommentContentRulesTest(unittest.TestCase): - """ Test custom comments events - """ + """Test custom comments events""" + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): # Setup sandbox - self.portal = self.layer['portal'] - self.request = self.layer['request'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] # Setup current user properties member = self.portal.portal_membership.getMemberById(TEST_USER_ID) - member.setMemberProperties({ - 'fullname': 'X Manager', - 'email': 'xmanager@example.com', - }) + member.setMemberProperties( + { + "fullname": "X Manager", + "email": "xmanager@example.com", + } + ) - setRoles(self.portal, TEST_USER_ID, ['Manager']) + setRoles(self.portal, TEST_USER_ID, ["Manager"]) - self.document = self.portal['doc1'] + self.document = self.portal["doc1"] - comment = createObject('plone.Comment') - comment.text = 'This is a comment' - comment.author_username = 'jim' - comment.author_name = 'Jim' - comment.author_email = 'jim@example.com' + comment = createObject("plone.Comment") + comment.text = "This is a comment" + comment.author_username = "jim" + comment.author_name = "Jim" + comment.author_email = "jim@example.com" conversation = IConversation(self.document) conversation.addComment(comment) @@ -52,58 +55,61 @@ class CommentContentRulesTest(unittest.TestCase): self.assertTrue(IRuleEventType.providedBy(IReplyRemovedEvent)) def testCommentIdStringSubstitution(self): - comment_id = getAdapter(self.document, IStringSubstitution, - name=u'comment_id') + comment_id = getAdapter(self.document, IStringSubstitution, name="comment_id") self.assertIsInstance(comment_id(), int) def testCommentTextStringSubstitution(self): - comment_text = getAdapter(self.document, IStringSubstitution, - name=u'comment_text') - self.assertEqual(comment_text(), u'This is a comment') + comment_text = getAdapter( + self.document, IStringSubstitution, name="comment_text" + ) + self.assertEqual(comment_text(), "This is a comment") def testCommentUserIdStringSubstitution(self): - comment_user_id = getAdapter(self.document, IStringSubstitution, - name=u'comment_user_id') - self.assertEqual(comment_user_id(), u'jim') + comment_user_id = getAdapter( + self.document, IStringSubstitution, name="comment_user_id" + ) + self.assertEqual(comment_user_id(), "jim") def testCommentUserFullNameStringSubstitution(self): - comment_user_fullname = getAdapter(self.document, IStringSubstitution, - name=u'comment_user_fullname') - self.assertEqual(comment_user_fullname(), u'Jim') + comment_user_fullname = getAdapter( + self.document, IStringSubstitution, name="comment_user_fullname" + ) + self.assertEqual(comment_user_fullname(), "Jim") def testCommentUserEmailStringSubstitution(self): - comment_user_email = getAdapter(self.document, IStringSubstitution, - name=u'comment_user_email') - self.assertEqual(comment_user_email(), u'jim@example.com') + comment_user_email = getAdapter( + self.document, IStringSubstitution, name="comment_user_email" + ) + self.assertEqual(comment_user_email(), "jim@example.com") class ReplyContentRulesTest(unittest.TestCase): - """ Test custom comments events - """ + """Test custom comments events""" + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): # Setup sandbox - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) - self.document = self.portal['doc1'] + self.document = self.portal["doc1"] conversation = IConversation(self.document) replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'This is a comment' + comment = createObject("plone.Comment") + comment.text = "This is a comment" new_id = replies.addComment(comment) comment = self.document.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) - re_comment = createObject('plone.Comment') - re_comment.text = 'This is a reply' - re_comment.author_username = 'julia' - re_comment.author_name = 'Juliana' - re_comment.author_email = 'julia@example.com' + re_comment = createObject("plone.Comment") + re_comment.text = "This is a reply" + re_comment.author_username = "julia" + re_comment.author_name = "Juliana" + re_comment.author_email = "julia@example.com" replies = IReplies(comment) replies.addComment(re_comment) @@ -112,7 +118,7 @@ class ReplyContentRulesTest(unittest.TestCase): reply_id = getAdapter( self.document, IStringSubstitution, - name=u'comment_id', + name="comment_id", ) self.assertIsInstance(reply_id(), int) @@ -120,30 +126,30 @@ class ReplyContentRulesTest(unittest.TestCase): reply_text = getAdapter( self.document, IStringSubstitution, - name=u'comment_text', + name="comment_text", ) - self.assertEqual(reply_text(), u'This is a reply') + self.assertEqual(reply_text(), "This is a reply") def testReplyUserIdStringSubstitution(self): reply_user_id = getAdapter( self.document, IStringSubstitution, - name=u'comment_user_id', + name="comment_user_id", ) - self.assertEqual(reply_user_id(), u'julia') + self.assertEqual(reply_user_id(), "julia") def testReplyUserFullNameStringSubstitution(self): reply_user_fullname = getAdapter( self.document, IStringSubstitution, - name=u'comment_user_fullname', + name="comment_user_fullname", ) - self.assertEqual(reply_user_fullname(), u'Juliana') + self.assertEqual(reply_user_fullname(), "Juliana") def testReplyUserEmailStringSubstitution(self): reply_user_email = getAdapter( self.document, IStringSubstitution, - name=u'comment_user_email', + name="comment_user_email", ) - self.assertEqual(reply_user_email(), u'julia@example.com') + self.assertEqual(reply_user_email(), "julia@example.com") diff --git a/plone/app/discussion/tests/test_controlpanel.py b/plone/app/discussion/tests/test_controlpanel.py index 4ad3018..b148547 100644 --- a/plone/app/discussion/tests/test_controlpanel.py +++ b/plone/app/discussion/tests/test_controlpanel.py @@ -1,6 +1,7 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from plone.app.discussion.testing import ( # noqa + PLONE_APP_DISCUSSION_INTEGRATION_TESTING, +) from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.registry import Registry @@ -17,8 +18,8 @@ class RegistryTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.registry = Registry() self.registry.registerInterface(IDiscussionSettings) @@ -29,99 +30,100 @@ class RegistryTest(unittest.TestCase): def test_discussion_controlpanel_view(self): view = getMultiAdapter( (self.portal, self.portal.REQUEST), - name='discussion-controlpanel', + name="discussion-controlpanel", ) self.assertTrue(view()) def test_discussion_in_controlpanel(self): # Check if discussion is in the control panel - self.controlpanel = getToolByName(self.portal, 'portal_controlpanel') + self.controlpanel = getToolByName(self.portal, "portal_controlpanel") self.assertTrue( - 'discussion' in [ - a.getAction(self)['id'] - for a in self.controlpanel.listActions() - ], + "discussion" + in [a.getAction(self)["id"] for a in self.controlpanel.listActions()], ) def test_globally_enabled(self): # Check globally_enabled record - self.assertTrue('globally_enabled' in IDiscussionSettings) + self.assertTrue("globally_enabled" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.globally_enabled' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.globally_enabled" ], False, ) def test_anonymous_comments(self): # Check anonymous_comments record - self.assertTrue('anonymous_comments' in IDiscussionSettings) + self.assertTrue("anonymous_comments" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.anonymous_comments' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.anonymous_comments" ], False, ) def test_moderation_enabled(self): # Check globally_enabled record - self.assertTrue('moderation_enabled' in IDiscussionSettings) + self.assertTrue("moderation_enabled" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.moderation_enabled' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.moderation_enabled" ], False, ) def test_edit_comment_enabled(self): # Check edit_comment_enabled record - self.assertTrue('edit_comment_enabled' in IDiscussionSettings) + self.assertTrue("edit_comment_enabled" in IDiscussionSettings) self.assertEqual( - self.registry['plone.app.discussion.interfaces.' + - 'IDiscussionSettings.edit_comment_enabled'], + self.registry[ + "plone.app.discussion.interfaces." + + "IDiscussionSettings.edit_comment_enabled" + ], False, ) def test_delete_own_comment_enabled(self): # Check delete_own_comment_enabled record - self.assertTrue('delete_own_comment_enabled' in IDiscussionSettings) + self.assertTrue("delete_own_comment_enabled" in IDiscussionSettings) self.assertEqual( - self.registry['plone.app.discussion.interfaces.' + - 'IDiscussionSettings.delete_own_comment_enabled'], + self.registry[ + "plone.app.discussion.interfaces." + + "IDiscussionSettings.delete_own_comment_enabled" + ], False, ) def test_text_transform(self): - self.assertTrue('text_transform' in IDiscussionSettings) + self.assertTrue("text_transform" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.text_transform' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.text_transform" ], - 'text/plain', + "text/plain", ) def test_captcha(self): # Check globally_enabled record - self.assertTrue('captcha' in IDiscussionSettings) + self.assertTrue("captcha" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.captcha' + "plone.app.discussion.interfaces." + "IDiscussionSettings.captcha" ], - 'disabled', + "disabled", ) def test_show_commenter_image(self): # Check show_commenter_image record - self.assertTrue('show_commenter_image' in IDiscussionSettings) + self.assertTrue("show_commenter_image" in IDiscussionSettings) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.show_commenter_image' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.show_commenter_image" ], True, ) @@ -129,12 +131,12 @@ class RegistryTest(unittest.TestCase): def test_moderator_notification_enabled(self): # Check show_commenter_image record self.assertTrue( - 'moderator_notification_enabled' in IDiscussionSettings, + "moderator_notification_enabled" in IDiscussionSettings, ) self.assertEqual( self.registry[ - 'plone.app.discussion.interfaces.' + - 'IDiscussionSettings.moderator_notification_enabled' + "plone.app.discussion.interfaces." + + "IDiscussionSettings.moderator_notification_enabled" ], False, ) @@ -154,22 +156,22 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) registry = queryUtility(IRegistry) self.settings = registry.forInterface(IDiscussionSettings, check=False) def test_moderation_enabled_in_discussion_control_panel_changed(self): """Make sure the 'Discussion Item' workflow is changed properly, when - the 'comment_moderation' setting in the discussion control panel - changes. + the 'comment_moderation' setting in the discussion control panel + changes. """ # By default the comment_one_state_workflow without moderation is # enabled self.assertEqual( - ('comment_one_state_workflow',), + ("comment_one_state_workflow",), self.portal.portal_workflow.getChainForPortalType( - 'Discussion Item', + "Discussion Item", ), ) @@ -179,32 +181,32 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase): # Make sure the comment_review_workflow with moderation enabled is # enabled self.assertEqual( - ('comment_review_workflow',), + ("comment_review_workflow",), self.portal.portal_workflow.getChainForPortalType( - 'Discussion Item', + "Discussion Item", ), ) # And back self.settings.moderation_enabled = False self.assertEqual( - ('comment_one_state_workflow',), + ("comment_one_state_workflow",), self.portal.portal_workflow.getChainForPortalType( - 'Discussion Item', + "Discussion Item", ), ) def test_change_workflow_in_types_control_panel(self): """Make sure the setting in the discussion control panel is changed - accordingly, when the workflow for the 'Discussion Item' changed in - the types control panel. + accordingly, when the workflow for the 'Discussion Item' changed in + the types control panel. """ # By default, moderation is disabled self.settings.moderation_enabled = False # Enable the 'comment_review_workflow' with moderation enabled self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow',), + ("Discussion Item",), + ("comment_review_workflow",), ) # Make sure the moderation_enabled settings has changed @@ -212,15 +214,15 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase): # Enable the 'comment_review_workflow' with moderation enabled self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_one_state_workflow',), + ("Discussion Item",), + ("comment_one_state_workflow",), ) self.settings.moderation_enabled = True # Enable a 'custom' discussion workflow self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('intranet_workflow',), + ("Discussion Item",), + ("intranet_workflow",), ) # Setting has not changed. A Custom workflow disables the diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py index fcf528c..febce28 100644 --- a/plone/app/discussion/tests/test_conversation.py +++ b/plone/app/discussion/tests/test_conversation.py @@ -1,17 +1,17 @@ -# -*- coding: utf-8 -*- +from ..interfaces import IComment +from ..interfaces import IConversation +from ..interfaces import IDiscussionLayer +from ..interfaces import IDiscussionSettings +from ..interfaces import IReplies +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from Acquisition import aq_base from Acquisition import aq_parent from datetime import datetime from datetime import timedelta -from plone.app.discussion import interfaces -from plone.app.discussion.interfaces import IComment -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.interfaces import IReplies -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.app.vocabularies.types import BAD_TYPES +from plone.dexterity.interfaces import IDexterityContent from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from zope import interface @@ -19,31 +19,22 @@ from zope.annotation.interfaces import IAnnotations from zope.component import createObject from zope.component import queryUtility -import six import unittest -try: - from plone.dexterity.interfaces import IDexterityContent - DEXTERITY = True -except ImportError: - DEXTERITY = False - - class ConversationTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - interface.alsoProvides( - self.portal.REQUEST, interfaces.IDiscussionLayer) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + interface.alsoProvides(self.portal.REQUEST, IDiscussionLayer) self.typetool = self.portal.portal_types self.portal_discussion = getToolByName( self.portal, - 'portal_discussion', + "portal_discussion", None, ) # Allow discussion @@ -52,7 +43,7 @@ class ConversationTest(unittest.TestCase): settings.globally_enabled = True workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") def test_add_comment(self): # Create a conversation. In this case we doesn't assign it to an @@ -62,8 +53,8 @@ class ConversationTest(unittest.TestCase): # Add a comment. Note: in real life, we always create comments via the # factory to allow different factories to be swapped in - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = conversation.addComment(comment) @@ -79,20 +70,19 @@ class ConversationTest(unittest.TestCase): self.assertEqual(len(tuple(conversation.getThreads())), 1) self.assertEqual(conversation.total_comments(), 1) self.assertTrue( - conversation.last_comment_date - datetime.utcnow() < - timedelta(seconds=1), + conversation.last_comment_date - datetime.utcnow() < timedelta(seconds=1), ) def test_private_comment(self): conversation = IConversation(self.portal.doc1) - comment = createObject('plone.Comment') - comment.author_username = 'nobody' + comment = createObject("plone.Comment") + comment.author_username = "nobody" conversation.addComment(comment) - comment.manage_permission('View', roles=tuple()) + comment.manage_permission("View", roles=tuple()) self.assertEqual(0, conversation.total_comments()) self.assertEqual(None, conversation.last_comment_date) - self.assertEqual(['nobody'], list(conversation.commentators)) + self.assertEqual(["nobody"], list(conversation.commentators)) self.assertEqual([], list(conversation.public_commentators)) def test_delete_comment(self): @@ -103,8 +93,8 @@ class ConversationTest(unittest.TestCase): # Add a comment. Note: in real life, we always create comments via the # factory to allow different factories to be swapped in - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = conversation.addComment(comment) @@ -139,23 +129,23 @@ class ConversationTest(unittest.TestCase): # +- Comment 2_1 # Create all comments - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" - comment1_1 = createObject('plone.Comment') - comment1_1.text = 'Comment text' + comment1_1 = createObject("plone.Comment") + comment1_1.text = "Comment text" - comment1_1_1 = createObject('plone.Comment') - comment1_1_1.text = 'Comment text' + comment1_1_1 = createObject("plone.Comment") + comment1_1_1.text = "Comment text" - comment1_2 = createObject('plone.Comment') - comment1_2.text = 'Comment text' + comment1_2 = createObject("plone.Comment") + comment1_2.text = "Comment text" - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" - comment2_1 = createObject('plone.Comment') - comment2_1.text = 'Comment text' + comment2_1 = createObject("plone.Comment") + comment2_1.text = "Comment text" # Create the nested comment structure new_id_1 = conversation.addComment(comment1) @@ -175,21 +165,24 @@ class ConversationTest(unittest.TestCase): del conversation[new_id_1] - self.assertEqual([ - {'comment': comment2, 'depth': 0, 'id': new_id_2}, - {'comment': comment2_1, 'depth': 1, 'id': new_id_2_1}, - ], list(conversation.getThreads())) + self.assertEqual( + [ + {"comment": comment2, "depth": 0, "id": new_id_2}, + {"comment": comment2_1, "depth": 1, "id": new_id_2_1}, + ], + list(conversation.getThreads()), + ) def test_delete_comment_when_content_object_is_deleted(self): # Make sure all comments of a content object are deleted when the # object itself is deleted. conversation = IConversation(self.portal.doc1) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" conversation.addComment(comment) # Delete the content object - self.portal.manage_delObjects(['doc1']) + self.portal.manage_delObjects(["doc1"]) # Make sure the comment has been deleted as well self.assertEqual(len(list(conversation.getComments())), 0) @@ -198,8 +191,8 @@ class ConversationTest(unittest.TestCase): def test_comments_enabled_on_doc_in_subfolder(self): typetool = self.portal.portal_types - typetool.constructContent('Folder', self.portal, 'folder1') - typetool.constructContent('Document', self.portal.folder1, 'doc2') + typetool.constructContent("Folder", self.portal, "folder1") + typetool.constructContent("Document", self.portal.folder1, "doc2") folder = self.portal.folder1 @@ -209,13 +202,13 @@ class ConversationTest(unittest.TestCase): self.assertFalse(aq_base(folder).allow_discussion) doc = self.portal.folder1.doc2 - conversation = doc.restrictedTraverse('@@conversation_view') + conversation = doc.restrictedTraverse("@@conversation_view") self.assertEqual(conversation.enabled(), False) # We have to allow discussion on Document content type, since # otherwise allow_discussion will always return False - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'Document') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "Document") document_fti.manage_changeProperties(allow_discussion=True) self.assertEqual(conversation.enabled(), True) @@ -223,13 +216,12 @@ class ConversationTest(unittest.TestCase): def test_disable_commenting_globally(self): # Create a conversation. - conversation = self.portal.doc1.restrictedTraverse( - '@@conversation_view') + conversation = self.portal.doc1.restrictedTraverse("@@conversation_view") # We have to allow discussion on Document content type, since # otherwise allow_discussion will always return False - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'Document') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "Document") document_fti.manage_changeProperties(allow_discussion=True) # Check if conversation is enabled now @@ -249,14 +241,14 @@ class ConversationTest(unittest.TestCase): def test_allow_discussion_for_news_items(self): - self.typetool.constructContent('News Item', self.portal, 'newsitem') + self.typetool.constructContent("News Item", self.portal, "newsitem") newsitem = self.portal.newsitem - conversation = newsitem.restrictedTraverse('@@conversation_view') + conversation = newsitem.restrictedTraverse("@@conversation_view") # We have to allow discussion on Document content type, since # otherwise allow_discussion will always return False - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'News Item') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "News Item") document_fti.manage_changeProperties(allow_discussion=True) # Check if conversation is enabled now @@ -278,23 +270,23 @@ class ConversationTest(unittest.TestCase): # Create a conversation. conversation = self.portal.doc1.restrictedTraverse( - '@@conversation_view', + "@@conversation_view", ) # The Document content type is disabled by default self.assertEqual(conversation.enabled(), False) # Allow discussion on Document content type - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'Document') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "Document") document_fti.manage_changeProperties(allow_discussion=True) # Check if conversation is enabled now self.assertEqual(conversation.enabled(), True) # Disallow discussion on Document content type - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'Document') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "Document") document_fti.manage_changeProperties(allow_discussion=False) # Check if conversation is enabled now @@ -306,17 +298,17 @@ class ConversationTest(unittest.TestCase): # plone.app.contenttypes does not have this restriction any longer. # Create a folder - self.typetool.constructContent('Folder', self.portal, 'f1') + self.typetool.constructContent("Folder", self.portal, "f1") # Usually we don't create a conversation on a folder - conversation = self.portal.f1.restrictedTraverse('@@conversation_view') + conversation = self.portal.f1.restrictedTraverse("@@conversation_view") # Allow discussion for the folder self.portal.f1.allow_discussion = True # Allow discussion on Folder content type - portal_types = getToolByName(self.portal, 'portal_types') - document_fti = getattr(portal_types, 'Folder') + portal_types = getToolByName(self.portal, "portal_types") + document_fti = getattr(portal_types, "Folder") document_fti.manage_changeProperties(allow_discussion=True) self.assertTrue(conversation.enabled()) @@ -326,7 +318,7 @@ class ConversationTest(unittest.TestCase): # Create a conversation. conversation = self.portal.doc1.restrictedTraverse( - '@@conversation_view', + "@@conversation_view", ) # Discussion is disallowed by default @@ -351,13 +343,13 @@ class ConversationTest(unittest.TestCase): # Add a comment. Note: in real life, we always create comments via the # factory to allow different factories to be swapped in - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" new_id1 = conversation.addComment(comment1) - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" new_id2 = conversation.addComment(comment2) @@ -384,17 +376,17 @@ class ConversationTest(unittest.TestCase): self.assertTrue(comment2 in conversation.values()) # check if comment ids are in iterkeys - self.assertTrue(new_id1 in six.iterkeys(conversation)) - self.assertTrue(new_id2 in six.iterkeys(conversation)) - self.assertFalse(123 in six.iterkeys(conversation)) + self.assertTrue(new_id1 in conversation.keys()) + self.assertTrue(new_id2 in conversation.keys()) + self.assertFalse(123 in conversation.keys()) # check if comment objects are in itervalues - self.assertTrue(comment1 in six.itervalues(conversation)) - self.assertTrue(comment2 in six.itervalues(conversation)) + self.assertTrue(comment1 in conversation.values()) + self.assertTrue(comment2 in conversation.values()) # check if iteritems returns (key, comment object) pairs - self.assertTrue((new_id1, comment1) in six.iteritems(conversation)) - self.assertTrue((new_id2, comment2) in six.iteritems(conversation)) + self.assertTrue((new_id1, comment1) in conversation.items()) + self.assertTrue((new_id2, comment2) in conversation.items()) # TODO test acquisition wrapping # noqa T000 # self.assertTrue(aq_base(aq_parent(comment1)) is conversation) @@ -408,14 +400,14 @@ class ConversationTest(unittest.TestCase): # comments via the factory to allow different factories to be # swapped in - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" - comment3 = createObject('plone.Comment') - comment3.text = 'Comment text' + comment3 = createObject("plone.Comment") + comment3.text = "Comment text" conversation.addComment(comment1) conversation.addComment(comment2) @@ -437,49 +429,49 @@ class ConversationTest(unittest.TestCase): # Note: in real life, we always create # comments via the factory to allow different factories to be # swapped in - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' - comment1.author_username = 'Jim' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" + comment1.author_username = "Jim" conversation.addComment(comment1) - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' - comment2.author_username = 'Joe' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" + comment2.author_username = "Joe" conversation.addComment(comment2) - comment3 = createObject('plone.Comment') - comment3.text = 'Comment text' - comment3.author_username = 'Jack' + comment3 = createObject("plone.Comment") + comment3.text = "Comment text" + comment3.author_username = "Jack" new_comment3_id = conversation.addComment(comment3) - comment4 = createObject('plone.Comment') - comment4.text = 'Comment text' - comment4.author_username = 'Jack' + comment4 = createObject("plone.Comment") + comment4.text = "Comment text" + comment4.author_username = "Jack" new_comment4_id = conversation.addComment(comment4) # check if all commentators are in the commentators list self.assertEqual(conversation.total_comments(), 4) - self.assertTrue('Jim' in conversation.commentators) - self.assertTrue('Joe' in conversation.commentators) - self.assertTrue('Jack' in conversation.commentators) + self.assertTrue("Jim" in conversation.commentators) + self.assertTrue("Joe" in conversation.commentators) + self.assertTrue("Jack" in conversation.commentators) # remove the comment from Jack del conversation[new_comment3_id] # check if Jack is still in the commentators list (since # he had added two comments) - self.assertTrue('Jim' in conversation.commentators) - self.assertTrue('Joe' in conversation.commentators) - self.assertTrue('Jack' in conversation.commentators) + self.assertTrue("Jim" in conversation.commentators) + self.assertTrue("Joe" in conversation.commentators) + self.assertTrue("Jack" in conversation.commentators) self.assertEqual(conversation.total_comments(), 3) # remove the second comment from Jack del conversation[new_comment4_id] # check if Jack has been removed from the commentators list - self.assertTrue('Jim' in conversation.commentators) - self.assertTrue('Joe' in conversation.commentators) - self.assertFalse('Jack' in conversation.commentators) + self.assertTrue("Jim" in conversation.commentators) + self.assertTrue("Joe" in conversation.commentators) + self.assertFalse("Jack" in conversation.commentators) self.assertEqual(conversation.total_comments(), 2) def test_last_comment_date(self): @@ -494,29 +486,29 @@ class ConversationTest(unittest.TestCase): # Note: in real life, we always create # comments via the factory to allow different factories to be # swapped in - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" comment1.creation_date = datetime.utcnow() - timedelta(4) conversation.addComment(comment1) - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" comment2.creation_date = datetime.utcnow() - timedelta(2) new_comment2_id = conversation.addComment(comment2) - comment3 = createObject('plone.Comment') - comment3.text = 'Comment text' + comment3 = createObject("plone.Comment") + comment3.text = "Comment text" comment3.creation_date = datetime.utcnow() - timedelta(1) new_comment3_id = conversation.addComment(comment3) # check if the latest comment is exactly one day old self.assertTrue( - conversation.last_comment_date < datetime.utcnow() - - timedelta(hours=23, minutes=59, seconds=59), + conversation.last_comment_date + < datetime.utcnow() - timedelta(hours=23, minutes=59, seconds=59), ) self.assertTrue( - conversation.last_comment_date > - datetime.utcnow() - timedelta(days=1, seconds=1), + conversation.last_comment_date + > datetime.utcnow() - timedelta(days=1, seconds=1), ) # remove the latest comment @@ -525,12 +517,12 @@ class ConversationTest(unittest.TestCase): # check if the latest comment has been updated # the latest comment should be exactly two days old self.assertTrue( - conversation.last_comment_date < datetime.utcnow() - - timedelta(days=1, hours=23, minutes=59, seconds=59), + conversation.last_comment_date + < datetime.utcnow() - timedelta(days=1, hours=23, minutes=59, seconds=59), ) self.assertTrue( - conversation.last_comment_date > datetime.utcnow() - - timedelta(days=2, seconds=1), + conversation.last_comment_date + > datetime.utcnow() - timedelta(days=2, seconds=1), ) # remove the latest comment again @@ -539,12 +531,12 @@ class ConversationTest(unittest.TestCase): # check if the latest comment has been updated # the latest comment should be exactly four days old self.assertTrue( - conversation.last_comment_date < datetime.utcnow() - - timedelta(days=3, hours=23, minutes=59, seconds=59), + conversation.last_comment_date + < datetime.utcnow() - timedelta(days=3, hours=23, minutes=59, seconds=59), ) self.assertTrue( - conversation.last_comment_date > datetime.utcnow() - - timedelta(days=4, seconds=2), + conversation.last_comment_date + > datetime.utcnow() - timedelta(days=4, seconds=2), ) def test_get_comments_full(self): @@ -572,23 +564,23 @@ class ConversationTest(unittest.TestCase): # +- Comment 2_1 # Create all comments - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" - comment1_1 = createObject('plone.Comment') - comment1_1.text = 'Comment text' + comment1_1 = createObject("plone.Comment") + comment1_1.text = "Comment text" - comment1_1_1 = createObject('plone.Comment') - comment1_1_1.text = 'Comment text' + comment1_1_1 = createObject("plone.Comment") + comment1_1_1.text = "Comment text" - comment1_2 = createObject('plone.Comment') - comment1_2.text = 'Comment text' + comment1_2 = createObject("plone.Comment") + comment1_2.text = "Comment text" - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" - comment2_1 = createObject('plone.Comment') - comment2_1.text = 'Comment text' + comment2_1 = createObject("plone.Comment") + comment2_1.text = "Comment text" # Create the nested comment structure new_id_1 = conversation.addComment(comment1) @@ -608,14 +600,17 @@ class ConversationTest(unittest.TestCase): # Get threads - self.assertEqual([ - {'comment': comment1, 'depth': 0, 'id': new_id_1}, - {'comment': comment1_1, 'depth': 1, 'id': new_id_1_1}, - {'comment': comment1_1_1, 'depth': 2, 'id': new_id_1_1_1}, - {'comment': comment1_2, 'depth': 1, 'id': new_id_1_2}, - {'comment': comment2, 'depth': 0, 'id': new_id_2}, - {'comment': comment2_1, 'depth': 1, 'id': new_id_2_1}, - ], list(conversation.getThreads())) + self.assertEqual( + [ + {"comment": comment1, "depth": 0, "id": new_id_1}, + {"comment": comment1_1, "depth": 1, "id": new_id_1_1}, + {"comment": comment1_1_1, "depth": 2, "id": new_id_1_1_1}, + {"comment": comment1_2, "depth": 1, "id": new_id_1_2}, + {"comment": comment2, "depth": 0, "id": new_id_2}, + {"comment": comment2_1, "depth": 1, "id": new_id_2_1}, + ], + list(conversation.getThreads()), + ) def test_get_threads_batched(self): # TODO: test start, size, root and depth arguments to getThreads() # noqa T000 @@ -626,16 +621,16 @@ class ConversationTest(unittest.TestCase): # make sure we can traverse to conversations and get a URL and path conversation = self.portal.doc1.restrictedTraverse( - '++conversation++default', + "++conversation++default", ) self.assertTrue(IConversation.providedBy(conversation)) self.assertEqual( - ('', 'plone', 'doc1', '++conversation++default'), + ("", "plone", "doc1", "++conversation++default"), conversation.getPhysicalPath(), ) self.assertEqual( - 'http://nohost/plone/doc1/++conversation++default', + "http://nohost/plone/doc1/++conversation++default", conversation.absolute_url(), ) @@ -644,7 +639,7 @@ class ConversationTest(unittest.TestCase): # can't be converted to int conversation = self.portal.doc1.restrictedTraverse( - '++conversation++default/ThisCantBeRight', + "++conversation++default/ThisCantBeRight", ) self.assertEqual(conversation, None) @@ -658,17 +653,16 @@ class ConversationTest(unittest.TestCase): self.assertTrue(conversation.__parent__) self.assertTrue(aq_parent(conversation)) - self.assertEqual(conversation.__parent__.getId(), 'doc1') + self.assertEqual(conversation.__parent__.getId(), "doc1") def test_discussion_item_not_in_bad_types(self): - self.assertFalse('Discussion Item' in BAD_TYPES) + self.assertFalse("Discussion Item" in BAD_TYPES) def test_no_comment(self): IConversation(self.portal.doc1) # Make sure no conversation has been created self.assertTrue( - 'plone.app.discussion:conversation' not in - IAnnotations(self.portal.doc1), + "plone.app.discussion:conversation" not in IAnnotations(self.portal.doc1), ) @@ -677,21 +671,20 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) interface.alsoProvides( self.portal.REQUEST, - interfaces.IDiscussionLayer, + IDiscussionLayer, ) - if DEXTERITY: - interface.alsoProvides( - self.portal.doc1, - IDexterityContent, - ) + interface.alsoProvides( + self.portal.doc1, + IDexterityContent, + ) def _makeOne(self, *args, **kw): - return self.portal.doc1.restrictedTraverse('@@conversation_view') + return self.portal.doc1.restrictedTraverse("@@conversation_view") def _globally_enable_discussion(self, value): registry = queryUtility(IRegistry) @@ -699,43 +692,38 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase): settings.globally_enabled = value def _enable_discussion_on_portal_type(self, portal_type, allow_discussion): - portal_types = getToolByName(self.portal, 'portal_types') + portal_types = getToolByName(self.portal, "portal_types") document_fti = getattr(portal_types, portal_type) document_fti.manage_changeProperties(allow_discussion=allow_discussion) def test_conversation_is_not_enabled_by_default(self): - if DEXTERITY: - conversation = self._makeOne(self.portal.doc1) - self.assertFalse(conversation.enabled()) + conversation = self._makeOne(self.portal.doc1) + self.assertFalse(conversation.enabled()) def test_conversation_is_not_enabled_by_default_on_portal_type(self): - if DEXTERITY: - self._globally_enable_discussion(True) - conversation = self._makeOne(self.portal.doc1) - self.assertFalse(conversation.enabled()) + self._globally_enable_discussion(True) + conversation = self._makeOne(self.portal.doc1) + self.assertFalse(conversation.enabled()) def test_conversation_needs_to_be_enabled_globally_and_for_type(self): - if DEXTERITY: - self._globally_enable_discussion(True) - self._enable_discussion_on_portal_type('Document', True) - conversation = self._makeOne(self.portal.doc1) - self.assertTrue(conversation.enabled()) + self._globally_enable_discussion(True) + self._enable_discussion_on_portal_type("Document", True) + conversation = self._makeOne(self.portal.doc1) + self.assertTrue(conversation.enabled()) def test_disable_discussion(self): - if DEXTERITY: - self._globally_enable_discussion(True) - self._enable_discussion_on_portal_type('Document', True) - self.portal.doc1.allow_discussion = False - conversation = self._makeOne(self.portal.doc1) - self.assertFalse(conversation.enabled()) + self._globally_enable_discussion(True) + self._enable_discussion_on_portal_type("Document", True) + self.portal.doc1.allow_discussion = False + conversation = self._makeOne(self.portal.doc1) + self.assertFalse(conversation.enabled()) def test_enable_discussion(self): - if DEXTERITY: - self._globally_enable_discussion(True) - self._enable_discussion_on_portal_type('Document', True) - self.portal.doc1.allow_discussion = True - conversation = self._makeOne(self.portal.doc1) - self.assertTrue(conversation.enabled()) + self._globally_enable_discussion(True) + self._enable_discussion_on_portal_type("Document", True) + self.portal.doc1.allow_discussion = True + conversation = self._makeOne(self.portal.doc1) + self.assertTrue(conversation.enabled()) class RepliesTest(unittest.TestCase): @@ -745,11 +733,11 @@ class RepliesTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") def test_add_comment(self): # Add comments to a ConversationReplies adapter @@ -760,8 +748,8 @@ class RepliesTest(unittest.TestCase): replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) @@ -787,8 +775,8 @@ class RepliesTest(unittest.TestCase): replies = IReplies(conversation) # Add a comment. - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) @@ -826,39 +814,39 @@ class RepliesTest(unittest.TestCase): # +- Comment 2_1 # Create all comments - comment1 = createObject('plone.Comment') - comment1.text = 'Comment text' + comment1 = createObject("plone.Comment") + comment1.text = "Comment text" - comment1_1 = createObject('plone.Comment') - comment1_1.text = 'Comment text' + comment1_1 = createObject("plone.Comment") + comment1_1.text = "Comment text" - comment1_1_1 = createObject('plone.Comment') - comment1_1_1.text = 'Comment text' + comment1_1_1 = createObject("plone.Comment") + comment1_1_1.text = "Comment text" - comment1_2 = createObject('plone.Comment') - comment1_2.text = 'Comment text' + comment1_2 = createObject("plone.Comment") + comment1_2.text = "Comment text" - comment2 = createObject('plone.Comment') - comment2.text = 'Comment text' + comment2 = createObject("plone.Comment") + comment2.text = "Comment text" - comment2_1 = createObject('plone.Comment') - comment2_1.text = 'Comment text' + comment2_1 = createObject("plone.Comment") + comment2_1.text = "Comment text" # Create the nested comment structure new_id_1 = replies.addComment(comment1) comment1 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_1), + f"++conversation++default/{new_id_1}", ) replies_to_comment1 = IReplies(comment1) new_id_2 = replies.addComment(comment2) comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_2), + f"++conversation++default/{new_id_2}", ) replies_to_comment2 = IReplies(comment2) new_id_1_1 = replies_to_comment1.addComment(comment1_1) comment1_1 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_1_1), + f"++conversation++default/{new_id_1_1}", ) replies_to_comment1_1 = IReplies(comment1_1) replies_to_comment1_1.addComment(comment1_1_1) diff --git a/plone/app/discussion/tests/test_events.py b/plone/app/discussion/tests/test_events.py index 3742c76..a6cc1cd 100644 --- a/plone/app/discussion/tests/test_events.py +++ b/plone/app/discussion/tests/test_events.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IReplies -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from ..interfaces import IConversation +from ..interfaces import IReplies +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from Zope2.App import zcml @@ -18,9 +17,9 @@ import unittest # -class EventsRegistry(object): - """ Fake registry to be used while testing discussion events - """ +class EventsRegistry: + """Fake registry to be used while testing discussion events""" + commentAdded = False commentModified = False commentRemoved = False @@ -28,6 +27,7 @@ class EventsRegistry(object): replyModified = False replyRemoved = False + # # Fake event handlers # @@ -63,19 +63,19 @@ def reply_removed(doc, evt): class CommentEventsTest(unittest.TestCase): - """ Test custom comments events - """ + """Test custom comments events""" + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): # Setup sandbox - self.portal = self.layer['portal'] - self.request = self.layer['request'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] self.registry = EventsRegistry - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.document = self.portal['doc1'] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.document = self.portal["doc1"] # # Subscribers @@ -104,23 +104,23 @@ class CommentEventsTest(unittest.TestCase): """ - zcml.load_config('configure.zcml', Products.Five) + zcml.load_config("configure.zcml", Products.Five) zcml.load_string(configure) def test_addEvent(self): self.assertFalse(self.registry.commentAdded) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") conversation = IConversation(self.document) conversation.addComment(comment) self.assertTrue(self.registry.commentAdded) def test_modifyEvent(self): self.assertFalse(self.registry.commentModified) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") conversation = IConversation(self.document) new_id = conversation.addComment(comment) comment = self.document.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) comment.text = "foo" notify(ObjectModifiedEvent(comment)) @@ -128,7 +128,7 @@ class CommentEventsTest(unittest.TestCase): def test_removedEvent(self): self.assertFalse(self.registry.commentRemoved) - comment = createObject('plone.Comment') + comment = createObject("plone.Comment") conversation = IConversation(self.document) cid = conversation.addComment(comment) del conversation[cid] @@ -136,17 +136,17 @@ class CommentEventsTest(unittest.TestCase): class RepliesEventsTest(unittest.TestCase): - """ Test custom replies events - """ + """Test custom replies events""" + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - self.request = self.layer['request'] + self.portal = self.layer["portal"] + self.request = self.layer["request"] self.registry = EventsRegistry - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.document = self.portal['doc1'] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.document = self.portal["doc1"] # # Subscribers @@ -175,7 +175,7 @@ class RepliesEventsTest(unittest.TestCase): """ - zcml.load_config('configure.zcml', Products.Five) + zcml.load_config("configure.zcml", Products.Five) zcml.load_string(configure) def test_addEvent(self): @@ -184,15 +184,15 @@ class RepliesEventsTest(unittest.TestCase): conversation = IConversation(self.document) replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) comment = self.document.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) replies.addComment(re_comment) @@ -204,14 +204,14 @@ class RepliesEventsTest(unittest.TestCase): conversation = IConversation(self.document) replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment_id = replies.addComment(comment) comment = self.document.restrictedTraverse( - '++conversation++default/{0}'.format(comment_id), + f"++conversation++default/{comment_id}", ) - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) new_id = replies.addComment(re_comment) reply = replies[new_id] @@ -225,15 +225,15 @@ class RepliesEventsTest(unittest.TestCase): conversation = IConversation(self.portal.doc1) replies = IReplies(conversation) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id), + f"++conversation++default/{new_id}", ) - re_comment = createObject('plone.Comment') - re_comment.text = 'Comment text' + re_comment = createObject("plone.Comment") + re_comment.text = "Comment text" replies = IReplies(comment) new_re_id = replies.addComment(re_comment) diff --git a/plone/app/discussion/tests/test_functional.py b/plone/app/discussion/tests/test_functional.py index 14db437..995016e 100644 --- a/plone/app/discussion/tests/test_functional.py +++ b/plone/app/discussion/tests/test_functional.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- """Functional Doctests for plone.app.discussion. These test are only triggered when Plone 4 (and plone.testing) is installed. """ -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING # noqa +from ..testing import PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING # noqa from plone.testing import layered import doctest @@ -12,29 +11,29 @@ import unittest optionflags = ( - doctest.ELLIPSIS | - doctest.NORMALIZE_WHITESPACE | - doctest.REPORT_ONLY_FIRST_FAILURE + doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE ) normal_testfiles = [ - 'functional_test_comments.txt', - 'functional_test_comment_review_workflow.txt', + "functional_test_comments.txt", + "functional_test_comment_review_workflow.txt", ] def test_suite(): suite = unittest.TestSuite() - suite.addTests([ - layered( - doctest.DocFileSuite( - test, - optionflags=optionflags, - globs={ - 'pprint': pprint.pprint, - } - ), - layer=PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING, - ) - for test in normal_testfiles - ]) + suite.addTests( + [ + layered( + doctest.DocFileSuite( + test, + optionflags=optionflags, + globs={ + "pprint": pprint.pprint, + }, + ), + layer=PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING, + ) + for test in normal_testfiles + ] + ) return suite diff --git a/plone/app/discussion/tests/test_indexers.py b/plone/app/discussion/tests/test_indexers.py index 56efebc..b1b30f1 100644 --- a/plone/app/discussion/tests/test_indexers.py +++ b/plone/app/discussion/tests/test_indexers.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- """Test for the plone.app.discussion indexers """ -from DateTime import DateTime +from .. import catalog +from ..interfaces import IConversation +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa from datetime import datetime -from plone.app.discussion import catalog -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from DateTime import DateTime from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.indexer.delegate import DelegatingIndexerFactory @@ -26,41 +25,40 @@ sed diam voluptua. At [...]""" class ConversationIndexersTest(unittest.TestCase): - """Conversation Indexer Tests - """ + """Conversation Indexer Tests""" layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) workflow = self.portal.portal_workflow - workflow.doActionFor(self.portal.doc1, 'publish') + workflow.doActionFor(self.portal.doc1, "publish") # Create a conversation. conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.text = 'Comment Text' - comment1.creator = 'jim' - comment1.author_username = 'Jim' + comment1 = createObject("plone.Comment") + comment1.text = "Comment Text" + comment1.creator = "jim" + comment1.author_username = "Jim" comment1.creation_date = datetime(2006, 9, 17, 14, 18, 12) comment1.modification_date = datetime(2006, 9, 17, 14, 18, 12) self.new_id1 = conversation.addComment(comment1) - comment2 = createObject('plone.Comment') - comment2.text = 'Comment Text' - comment2.creator = 'emma' - comment2.author_username = 'Emma' + comment2 = createObject("plone.Comment") + comment2.text = "Comment Text" + comment2.creator = "emma" + comment2.author_username = "Emma" comment2.creation_date = datetime(2007, 12, 13, 4, 18, 12) comment2.modification_date = datetime(2007, 12, 13, 4, 18, 12) self.new_id2 = conversation.addComment(comment2) - comment3 = createObject('plone.Comment') - comment3.text = 'Comment Text' - comment3.creator = 'lukas' - comment3.author_username = 'Lukas' + comment3 = createObject("plone.Comment") + comment3.text = "Comment Text" + comment3.creator = "lukas" + comment3.author_username = "Lukas" comment3.creation_date = datetime(2009, 4, 12, 11, 12, 12) comment3.modification_date = datetime(2009, 4, 12, 11, 12, 12) self.new_id3 = conversation.addComment(comment3) @@ -68,10 +66,12 @@ class ConversationIndexersTest(unittest.TestCase): self.conversation = conversation def test_conversation_total_comments(self): - self.assertTrue(isinstance( - catalog.total_comments, - DelegatingIndexerFactory, - )) + self.assertTrue( + isinstance( + catalog.total_comments, + DelegatingIndexerFactory, + ) + ) self.assertEqual(catalog.total_comments(self.portal.doc1)(), 3) del self.conversation[self.new_id1] self.assertEqual(catalog.total_comments(self.portal.doc1)(), 2) @@ -80,10 +80,12 @@ class ConversationIndexersTest(unittest.TestCase): self.assertEqual(catalog.total_comments(self.portal.doc1)(), 0) def test_conversation_last_comment_date(self): - self.assertTrue(isinstance( - catalog.last_comment_date, - DelegatingIndexerFactory, - )) + self.assertTrue( + isinstance( + catalog.last_comment_date, + DelegatingIndexerFactory, + ) + ) self.assertEqual( catalog.last_comment_date(self.portal.doc1)(), datetime(2009, 4, 12, 11, 12, 12), @@ -110,8 +112,8 @@ class CommentIndexersTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) # Create a conversation. In this case we doesn't assign it to an # object, as we just want to check the Conversation object API. @@ -120,10 +122,10 @@ class CommentIndexersTest(unittest.TestCase): # Add a comment. Note: in real life, we always create comments via the # factory to allow different factories to be swapped in - comment = createObject('plone.Comment') - comment.text = 'Lorem ipsum dolor sit amet.' - comment.creator = 'jim' - comment.author_name = 'Jim' + comment = createObject("plone.Comment") + comment.text = "Lorem ipsum dolor sit amet." + comment.creator = "jim" + comment.author_name = "Jim" comment.creation_date = datetime(2006, 9, 17, 14, 18, 12) comment.modification_date = datetime(2008, 3, 12, 7, 32, 52) @@ -132,60 +134,61 @@ class CommentIndexersTest(unittest.TestCase): self.conversation = conversation def test_title(self): - self.assertEqual(catalog.title(self.comment)(), 'Jim on Document 1') + self.assertEqual(catalog.title(self.comment)(), "Jim on Document 1") self.assertTrue(isinstance(catalog.title, DelegatingIndexerFactory)) def test_description(self): self.assertEqual( catalog.description(self.comment)(), - 'Lorem ipsum dolor sit amet.', + "Lorem ipsum dolor sit amet.", ) - self.assertTrue( - isinstance(catalog.description, DelegatingIndexerFactory)) + self.assertTrue(isinstance(catalog.description, DelegatingIndexerFactory)) def test_description_long(self): # Create a 50 word comment and make sure the description returns # only the first 25 words - comment_long = createObject('plone.Comment') - comment_long.title = 'Long Comment' + comment_long = createObject("plone.Comment") + comment_long.title = "Long Comment" comment_long.text = LONG_TEXT self.conversation.addComment(comment_long) self.assertEqual( catalog.description(comment_long)(), - LONG_TEXT_CUT.replace('\n', ' '), + LONG_TEXT_CUT.replace("\n", " "), ) def test_dates(self): # Test if created, modified, effective etc. are set correctly self.assertEqual( catalog.created(self.comment)(), - DateTime(2006, 9, 17, 14, 18, 12, 'GMT'), + DateTime(2006, 9, 17, 14, 18, 12, "GMT"), ) self.assertEqual( catalog.effective(self.comment)(), - DateTime(2006, 9, 17, 14, 18, 12, 'GMT'), + DateTime(2006, 9, 17, 14, 18, 12, "GMT"), ) self.assertEqual( catalog.modified(self.comment)(), - DateTime(2008, 3, 12, 7, 32, 52, 'GMT'), + DateTime(2008, 3, 12, 7, 32, 52, "GMT"), ) def test_searchable_text(self): # Test if searchable text is a concatenation of title and comment text self.assertEqual( catalog.searchable_text(self.comment)(), - ('Lorem ipsum dolor sit amet.'), + ("Lorem ipsum dolor sit amet."), + ) + self.assertTrue( + isinstance( + catalog.searchable_text, + DelegatingIndexerFactory, + ) ) - self.assertTrue(isinstance( - catalog.searchable_text, - DelegatingIndexerFactory, - )) def test_creator(self): - self.assertEqual(catalog.creator(self.comment)(), ('jim')) + self.assertEqual(catalog.creator(self.comment)(), ("jim")) def test_in_response_to(self): # make sure in_response_to returns the title or id of the content # object the comment was added to - self.assertEqual(catalog.in_response_to(self.comment)(), 'Document 1') + self.assertEqual(catalog.in_response_to(self.comment)(), "Document 1") diff --git a/plone/app/discussion/tests/test_moderation_multiple_state_view.py b/plone/app/discussion/tests/test_moderation_multiple_state_view.py index 72b1db0..6a12a72 100644 --- a/plone/app/discussion/tests/test_moderation_multiple_state_view.py +++ b/plone/app/discussion/tests/test_moderation_multiple_state_view.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- -from plone.app.discussion.browser.moderation import BulkActionsView -from plone.app.discussion.browser.moderation import DeleteComment -from plone.app.discussion.browser.moderation import CommentTransition -from plone.app.discussion.browser.moderation import View -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from ..browser.moderation import BulkActionsView +from ..browser.moderation import CommentTransition +from ..browser.moderation import DeleteComment +from ..browser.moderation import View +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.registry.interfaces import IRegistry @@ -21,59 +20,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.app = self.layer['app'] - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.wf = getToolByName(self.portal, - 'portal_workflow', - None) + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.wf = getToolByName(self.portal, "portal_workflow", None) self.context = self.portal self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - 'comment_review_workflow', + ("Discussion Item",), + "comment_review_workflow", ) self.wf_tool = self.portal.portal_workflow # Add a conversation with three comments conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.title = 'Comment 1' - comment1.text = 'Comment text' - comment1.Creator = 'Jim' + comment1 = createObject("plone.Comment") + comment1.title = "Comment 1" + comment1.text = "Comment text" + comment1.Creator = "Jim" new_id_1 = conversation.addComment(comment1) self.comment1 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_1), + f"++conversation++default/{new_id_1}", ) - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' - comment2.Creator = 'Joe' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" + comment2.Creator = "Joe" new_id_2 = conversation.addComment(comment2) self.comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_2), + f"++conversation++default/{new_id_2}", ) - comment3 = createObject('plone.Comment') - comment3.title = 'Comment 3' - comment3.text = 'Comment text' - comment3.Creator = 'Emma' + comment3 = createObject("plone.Comment") + comment3.title = "Comment 3" + comment3.text = "Comment text" + comment3.Creator = "Emma" new_id_3 = conversation.addComment(comment3) self.comment3 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_3), + f"++conversation++default/{new_id_3}", ) self.conversation = conversation def test_default_bulkaction(self): # Make sure no error is raised when no bulk actions has been supplied - self.request.set('form.select.BulkAction', '-1') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "-1") + self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())]) view = BulkActionsView(self.portal, self.request) self.assertFalse(view()) def test_publish(self): - self.request.set('form.select.BulkAction', 'publish') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "publish") + self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())]) view = BulkActionsView(self.portal, self.request) view() @@ -81,16 +78,16 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Count published comments published_comments = 0 for r in self.conversation.getThreads(): - comment_obj = r['comment'] - workflow_status = self.wf.getInfoFor(comment_obj, 'review_state') - if workflow_status == 'published': + comment_obj = r["comment"] + workflow_status = self.wf.getInfoFor(comment_obj, "review_state") + if workflow_status == "published": published_comments += 1 # Make sure the comment has been published self.assertEqual(published_comments, 1) def test_mark_as_spam(self): - self.request.set('form.select.BulkAction', 'mark_as_spam') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "mark_as_spam") + self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())]) view = BulkActionsView(self.portal, self.request) @@ -99,9 +96,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Count spam comments spam_comments = 0 for r in self.conversation.getThreads(): - comment_obj = r['comment'] - workflow_status = self.wf.getInfoFor(comment_obj, 'review_state') - if workflow_status == 'spam': + comment_obj = r["comment"] + workflow_status = self.wf.getInfoFor(comment_obj, "review_state") + if workflow_status == "spam": spam_comments += 1 # Make sure the comment has been marked as spam self.assertEqual(spam_comments, 1) @@ -110,9 +107,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Initially we have three comments self.assertEqual(len(self.conversation.objectIds()), 3) # Delete two comments with bulk actions - self.request.set('form.select.BulkAction', 'delete') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath()), - '/'.join(self.comment3.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "delete") + self.request.set( + "paths", + [ + "/".join(self.comment1.getPhysicalPath()), + "/".join(self.comment3.getPhysicalPath()), + ], + ) view = BulkActionsView(self.app, self.request) view() diff --git a/plone/app/discussion/tests/test_moderation_view.py b/plone/app/discussion/tests/test_moderation_view.py index 76eddb3..639eaa3 100644 --- a/plone/app/discussion/tests/test_moderation_view.py +++ b/plone/app/discussion/tests/test_moderation_view.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- -from plone.app.discussion.browser.moderation import BulkActionsView -from plone.app.discussion.browser.moderation import DeleteComment -from plone.app.discussion.browser.moderation import CommentTransition -from plone.app.discussion.browser.moderation import View -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from ..browser.moderation import BulkActionsView +from ..browser.moderation import CommentTransition +from ..browser.moderation import DeleteComment +from ..browser.moderation import View +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.registry.interfaces import IRegistry @@ -21,37 +20,37 @@ class ModerationViewTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.app = self.layer['app'] - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal_discussion = getToolByName(self.portal, - 'portal_discussion', - None) - self.membership_tool = getToolByName(self.portal, - 'portal_membership') + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal_discussion = getToolByName(self.portal, "portal_discussion", None) + self.membership_tool = getToolByName(self.portal, "portal_membership") self.memberdata = self.portal.portal_memberdata request = self.app.REQUEST - context = getattr(self.portal, 'doc1') + context = getattr(self.portal, "doc1") self.view = View(context, request) self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), 'comment_review_workflow') + ("Discussion Item",), "comment_review_workflow" + ) self.wf_tool = self.portal.portal_workflow def test_moderation_enabled(self): """Make sure that moderation_enabled returns true if the comment - workflow implements a 'pending' state. + workflow implements a 'pending' state. """ # If workflow is not set, enabled must return False - self.wf_tool.setChainForPortalTypes(('Discussion Item',), ()) + self.wf_tool.setChainForPortalTypes(("Discussion Item",), ()) self.assertEqual(self.view.moderation_enabled(), False) # The comment_one_state_workflow does not have a 'pending' state - self.wf_tool.setChainForPortalTypes(('Discussion Item',), - ('comment_one_state_workflow,')) + self.wf_tool.setChainForPortalTypes( + ("Discussion Item",), ("comment_one_state_workflow,") + ) self.assertEqual(self.view.moderation_enabled(), False) # The comment_review_workflow does have a 'pending' state - self.wf_tool.setChainForPortalTypes(('Discussion Item',), - ('comment_review_workflow,')) + self.wf_tool.setChainForPortalTypes( + ("Discussion Item",), ("comment_review_workflow,") + ) self.assertEqual(self.view.moderation_enabled(), True) @@ -60,59 +59,57 @@ class ModerationBulkActionsViewTest(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.app = self.layer['app'] - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.wf = getToolByName(self.portal, - 'portal_workflow', - None) + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.wf = getToolByName(self.portal, "portal_workflow", None) self.context = self.portal self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - 'comment_review_workflow', + ("Discussion Item",), + "comment_review_workflow", ) self.wf_tool = self.portal.portal_workflow # Add a conversation with three comments conversation = IConversation(self.portal.doc1) - comment1 = createObject('plone.Comment') - comment1.title = 'Comment 1' - comment1.text = 'Comment text' - comment1.Creator = 'Jim' + comment1 = createObject("plone.Comment") + comment1.title = "Comment 1" + comment1.text = "Comment text" + comment1.Creator = "Jim" new_id_1 = conversation.addComment(comment1) self.comment1 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_1), + f"++conversation++default/{new_id_1}", ) - comment2 = createObject('plone.Comment') - comment2.title = 'Comment 2' - comment2.text = 'Comment text' - comment2.Creator = 'Joe' + comment2 = createObject("plone.Comment") + comment2.title = "Comment 2" + comment2.text = "Comment text" + comment2.Creator = "Joe" new_id_2 = conversation.addComment(comment2) self.comment2 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_2), + f"++conversation++default/{new_id_2}", ) - comment3 = createObject('plone.Comment') - comment3.title = 'Comment 3' - comment3.text = 'Comment text' - comment3.Creator = 'Emma' + comment3 = createObject("plone.Comment") + comment3.title = "Comment 3" + comment3.text = "Comment text" + comment3.Creator = "Emma" new_id_3 = conversation.addComment(comment3) self.comment3 = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(new_id_3), + f"++conversation++default/{new_id_3}", ) self.conversation = conversation def test_default_bulkaction(self): # Make sure no error is raised when no bulk actions has been supplied - self.request.set('form.select.BulkAction', '-1') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "-1") + self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())]) view = BulkActionsView(self.portal, self.request) self.assertFalse(view()) def test_publish(self): - self.request.set('form.select.BulkAction', 'publish') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "publish") + self.request.set("paths", ["/".join(self.comment1.getPhysicalPath())]) view = BulkActionsView(self.portal, self.request) view() @@ -120,9 +117,9 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Count published comments published_comments = 0 for r in self.conversation.getThreads(): - comment_obj = r['comment'] - workflow_status = self.wf.getInfoFor(comment_obj, 'review_state') - if workflow_status == 'published': + comment_obj = r["comment"] + workflow_status = self.wf.getInfoFor(comment_obj, "review_state") + if workflow_status == "published": published_comments += 1 # Make sure the comment has been published self.assertEqual(published_comments, 1) @@ -131,9 +128,14 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Initially we have three comments self.assertEqual(len(self.conversation.objectIds()), 3) # Delete two comments with bulk actions - self.request.set('form.select.BulkAction', 'delete') - self.request.set('paths', ['/'.join(self.comment1.getPhysicalPath()), - '/'.join(self.comment3.getPhysicalPath())]) + self.request.set("form.select.BulkAction", "delete") + self.request.set( + "paths", + [ + "/".join(self.comment1.getPhysicalPath()), + "/".join(self.comment3.getPhysicalPath()), + ], + ) view = BulkActionsView(self.app, self.request) view() @@ -151,41 +153,41 @@ class RedirectionTest(unittest.TestCase): def setUp(self): # Update settings. - self.portal = self.layer['portal'] - self.request = self.layer['request'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + self.request = self.layer["request"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) # applyProfile(self.portal, 'plone.app.discussion:default') registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings) settings.globally_enabled = True self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow',), + ("Discussion Item",), + ("comment_review_workflow",), ) # Create page plus comment. self.portal.invokeFactory( - id='page', - title='Page 1', - type_name='Document', + id="page", + title="Page 1", + type_name="Document", ) self.page = self.portal.page self.conversation = IConversation(self.page) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" self.comment_id = self.conversation.addComment(comment) self.comment = list(self.conversation.getComments())[0] def test_regression(self): page_url = self.page.absolute_url() - self.request['HTTP_REFERER'] = page_url + self.request["HTTP_REFERER"] = page_url for Klass in (DeleteComment, CommentTransition): view = Klass(self.comment, self.request) view.__parent__ = self.comment self.assertEqual(page_url, view()) def test_valid_next_url(self): - self.request['HTTP_REFERER'] = 'http://attacker.com' + self.request["HTTP_REFERER"] = "http://attacker.com" for Klass in (DeleteComment, CommentTransition): view = Klass(self.comment, self.request) view.__parent__ = self.comment - self.assertNotEqual('http://attacker.com', view()) + self.assertNotEqual("http://attacker.com", view()) diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py index 13f9f6d..305c90e 100644 --- a/plone/app/discussion/tests/test_notifications.py +++ b/plone/app/discussion/tests/test_notifications.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- +from ..interfaces import IConversation +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from Acquisition import aq_base -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa +from persistent.list import PersistentList from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID +from plone.base.interfaces import IMailSchema from plone.registry.interfaces import IRegistry -from Products.CMFPlone.interfaces import IMailSchema -from Products.CMFPlone.tests.utils import MockMailHost from Products.MailHost.interfaces import IMailHost +from Products.MailHost.MailHost import _mungeHeaders +from Products.MailHost.MailHost import MailBase from zope.component import createObject from zope.component import getSiteManager from zope.component import getUtility @@ -16,50 +17,85 @@ from zope.component import queryUtility import unittest +class MockMailHost(MailBase): + """A MailHost that collects messages instead of sending them.""" + + def __init__(self, id): + self.reset() + + def reset(self): + self.messages = PersistentList() + + def _send(self, mfrom, mto, messageText, immediate=False): + """Send the message""" + self.messages.append(messageText) + + def send( + self, + messageText, + mto=None, + mfrom=None, + subject=None, + encode=None, + immediate=False, + charset=None, + msg_type=None, + ): + """send *messageText* modified by the other parameters. + + *messageText* can either be an ``email.message.Message`` + or a string. + Note that Products.MailHost 4.10 had changes here. + """ + msg, mto, mfrom = _mungeHeaders( + messageText, mto, mfrom, subject, charset, msg_type, encode + ) + self.messages.append(msg) + + class TestUserNotificationUnit(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) # Set up a mock mailhost self.portal._original_MailHost = self.portal.MailHost - self.portal.MailHost = mailhost = MockMailHost('MailHost') + self.portal.MailHost = mailhost = MockMailHost("MailHost") sm = getSiteManager(context=self.portal) sm.unregisterUtility(provided=IMailHost) sm.registerUtility(mailhost, provided=IMailHost) # We need to fake a valid mail setup registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') - mail_settings.email_from_address = 'portal@plone.test' + mail_settings = registry.forInterface(IMailSchema, prefix="plone") + mail_settings.email_from_address = "portal@plone.test" self.mailhost = self.portal.MailHost # Enable user notification setting registry = queryUtility(IRegistry) - registry['plone.app.discussion.interfaces.IDiscussionSettings' + - '.user_notification_enabled'] = True - # Archetypes content types store data as utf-8 encoded strings - # The missing u in front of a string is therefor not missing - self.portal.doc1.title = 'Kölle Alaaf' # What is 'Fasching'? + registry[ + "plone.app.discussion.interfaces.IDiscussionSettings" + + ".user_notification_enabled" + ] = True + self.portal.doc1.title = "Kölle Alaaf" # What is 'Fasching'? self.conversation = IConversation(self.portal.doc1) def beforeTearDown(self): self.portal.MailHost = self.portal._original_MailHost sm = getSiteManager(context=self.portal) sm.unregisterUtility(provided=IMailHost) - sm.registerUtility(aq_base(self.portal._original_MailHost), - provided=IMailHost) + sm.registerUtility(aq_base(self.portal._original_MailHost), provided=IMailHost) def test_notify_user(self): # Add a comment with user notification enabled. Add another comment # and make sure an email is send to the user of the first comment. - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True - comment.author_email = 'john@plone.test' + comment.author_email = "john@plone.test" self.conversation.addComment(comment) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment_id = self.conversation.addComment(comment) @@ -67,52 +103,46 @@ class TestUserNotificationUnit(unittest.TestCase): self.assertTrue(self.mailhost.messages[0]) msg = self.mailhost.messages[0] msg = msg.decode("utf-8") - self.assertIn('To: john@plone.test', msg) - self.assertIn('From: portal@plone.test', msg) + self.assertIn("To: john@plone.test", msg) + self.assertIn("From: portal@plone.test", msg) # We expect the headers to be properly header encoded (7-bit): - self.assertIn( - 'Subject: =?utf-8?q?A_comment_has_been_posted=2E?=', - msg) + self.assertIn("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=", msg) # The output should be encoded in a reasonable manner # (in this case quoted-printable). # Depending on which Python version and which Products.MailHost version, # you may get lines separated by '\n' or '\r\n' in here. - msg = msg.replace('\r\n', '\n') - self.assertIn( - 'A comment on "K=C3=B6lle Alaaf" has been posted here:', - msg) - self.assertIn( - 'http://nohost/plone/d=\noc1/view#{0}'.format(comment_id), - msg) - self.assertIn('Comment text', msg) - self.assertNotIn('Approve comment', msg) - self.assertNotIn('Delete comment', msg) + msg = msg.replace("\r\n", "\n") + self.assertIn('A comment on "K=C3=B6lle Alaaf" has been posted here:', msg) + self.assertIn(f"http://nohost/plone/d=\noc1/view#{comment_id}", msg) + self.assertIn("Comment text", msg) + self.assertNotIn("Approve comment", msg) + self.assertNotIn("Delete comment", msg) def test_do_not_notify_user_when_notification_is_disabled(self): registry = queryUtility(IRegistry) registry[ - 'plone.app.discussion.interfaces.IDiscussionSettings.' + - 'user_notification_enabled' + "plone.app.discussion.interfaces.IDiscussionSettings." + + "user_notification_enabled" ] = False - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True - comment.author_email = 'john@plone.test' + comment.author_email = "john@plone.test" self.conversation.addComment(comment) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) self.assertEqual(len(self.mailhost.messages), 0) def test_do_not_notify_user_when_email_address_is_given(self): - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True self.conversation.addComment(comment) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) @@ -122,15 +152,15 @@ class TestUserNotificationUnit(unittest.TestCase): # Set sender mail address to none and make sure no email is send to # the moderator. registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') + mail_settings = registry.forInterface(IMailSchema, prefix="plone") mail_settings.email_from_address = None - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True - comment.author_email = 'john@plone.test' + comment.author_email = "john@plone.test" self.conversation.addComment(comment) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) self.assertEqual(len(self.mailhost.messages), 0) @@ -139,15 +169,15 @@ class TestUserNotificationUnit(unittest.TestCase): # When a user has added two comments in a conversation and has # both times requested email notification, do not send him two # emails when another comment has been added. - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True - comment.author_email = 'john@plone.test' + comment.author_email = "john@plone.test" self.conversation.addComment(comment) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment.user_notification = True - comment.author_email = 'john@plone.test' + comment.author_email = "john@plone.test" self.conversation.addComment(comment) @@ -163,48 +193,45 @@ class TestModeratorNotificationUnit(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) # Set up a mock mailhost self.portal._original_MailHost = self.portal.MailHost - self.portal.MailHost = mailhost = MockMailHost('MailHost') + self.portal.MailHost = mailhost = MockMailHost("MailHost") sm = getSiteManager(context=self.portal) sm.unregisterUtility(provided=IMailHost) sm.registerUtility(mailhost, provided=IMailHost) # We need to fake a valid mail setup registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') - mail_settings.email_from_address = 'portal@plone.test' + mail_settings = registry.forInterface(IMailSchema, prefix="plone") + mail_settings.email_from_address = "portal@plone.test" self.mailhost = self.portal.MailHost # Enable comment moderation - self.portal.portal_types['Document'].allow_discussion = True + self.portal.portal_types["Document"].allow_discussion = True self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow',), + ("Discussion Item",), + ("comment_review_workflow",), ) # Enable moderator notification setting registry = queryUtility(IRegistry) registry[ - 'plone.app.discussion.interfaces.IDiscussionSettings.' + - 'moderator_notification_enabled' + "plone.app.discussion.interfaces.IDiscussionSettings." + + "moderator_notification_enabled" ] = True - # Archetypes content types store data as utf-8 encoded strings - # The missing u in front of a string is therefor not missing - self.portal.doc1.title = 'Kölle Alaaf' # What is 'Fasching'? + self.portal.doc1.title = "Kölle Alaaf" # What is 'Fasching'? self.conversation = IConversation(self.portal.doc1) def beforeTearDown(self): self.portal.MailHost = self.portal._original_MailHost sm = getSiteManager(context=self.portal) sm.unregisterUtility(provided=IMailHost) - sm.registerUtility(aq_base(self.portal._original_MailHost), - provided=IMailHost) + sm.registerUtility(aq_base(self.portal._original_MailHost), provided=IMailHost) def test_notify_moderator(self): """Add a comment and make sure an email is send to the moderator.""" - comment = createObject('plone.Comment') - comment.text = 'Comment text' - comment.author_email = 'john@plone.test' + comment = createObject("plone.Comment") + comment.text = "Comment text" + comment.author_email = "john@plone.test" comment_id = self.conversation.addComment(comment) @@ -212,54 +239,41 @@ class TestModeratorNotificationUnit(unittest.TestCase): self.assertTrue(self.mailhost.messages[0]) msg = self.mailhost.messages[0] msg = msg.decode("utf-8") - self.assertTrue('To: portal@plone.test' in msg) - self.assertTrue('From: portal@plone.test' in msg) + self.assertTrue("To: portal@plone.test" in msg) + self.assertTrue("From: portal@plone.test" in msg) # We expect the headers to be properly header encoded (7-bit): - self.assertTrue( - 'Subject: =?utf-8?q?A_comment_has_been_posted=2E?=' - in msg) + self.assertTrue("Subject: =?utf-8?q?A_comment_has_been_posted=2E?=" in msg) # The output should be encoded in a reasonable manner # (in this case quoted-printable): - self.assertTrue( - 'A comment on "K=C3=B6lle Alaaf" has been posted' - in msg - ) - self.assertIn( - 'http://nohost/plone/doc1/view#{0}'.format(comment_id), - msg - ) - self.assertIn( - comment.author_email, - msg - ) - self.assertIn( - comment.text, - msg - ) + self.assertTrue('A comment on "K=C3=B6lle Alaaf" has been posted' in msg) + self.assertIn(f"http://nohost/plone/doc1/view#{comment_id}", msg) + self.assertIn(comment.author_email, msg) + self.assertIn(comment.text, msg) def test_notify_moderator_specific_address(self): # A moderator email address can be specified in the control panel. registry = queryUtility(IRegistry) - registry['plone.app.discussion.interfaces.IDiscussionSettings' + - '.moderator_email'] = 'test@example.com' - comment = createObject('plone.Comment') - comment.text = 'Comment text' + registry[ + "plone.app.discussion.interfaces.IDiscussionSettings" + ".moderator_email" + ] = "test@example.com" + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) self.assertEqual(len(self.mailhost.messages), 1) msg = self.mailhost.messages[0] msg = msg.decode("utf-8") - self.assertTrue('To: test@example.com' in msg) + self.assertTrue("To: test@example.com" in msg) def test_do_not_notify_moderator_when_no_sender_is_available(self): # Set sender mail address to nonw and make sure no email is send to the # moderator. registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix='plone') + mail_settings = registry.forInterface(IMailSchema, prefix="plone") mail_settings.email_from_address = None - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) @@ -269,10 +283,12 @@ class TestModeratorNotificationUnit(unittest.TestCase): # Disable moderator notification setting and make sure no email is send # to the moderator. registry = queryUtility(IRegistry) - registry['plone.app.discussion.interfaces.IDiscussionSettings.' + - 'moderator_notification_enabled'] = False - comment = createObject('plone.Comment') - comment.text = 'Comment text' + registry[ + "plone.app.discussion.interfaces.IDiscussionSettings." + + "moderator_notification_enabled" + ] = False + comment = createObject("plone.Comment") + comment.text = "Comment text" self.conversation.addComment(comment) diff --git a/plone/app/discussion/tests/test_robot.py b/plone/app/discussion/tests/test_robot.py index 202b43b..c626689 100644 --- a/plone/app/discussion/tests/test_robot.py +++ b/plone/app/discussion/tests/test_robot.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_ROBOT_TESTING +from ..testing import PLONE_APP_DISCUSSION_ROBOT_TESTING from plone.app.testing import ROBOT_TEST_LEVEL from plone.testing import layered @@ -11,19 +10,21 @@ import unittest def test_suite(): suite = unittest.TestSuite() current_dir = os.path.abspath(os.path.dirname(__file__)) - robot_dir = os.path.join(current_dir, 'robot') + robot_dir = os.path.join(current_dir, "robot") robot_tests = [ - os.path.join('robot', doc) for doc in - os.listdir(robot_dir) if doc.endswith('.robot') and - doc.startswith('test_') + os.path.join("robot", doc) + for doc in os.listdir(robot_dir) + if doc.endswith(".robot") and doc.startswith("test_") ] for robot_test in robot_tests: robottestsuite = robotsuite.RobotTestSuite(robot_test) robottestsuite.level = ROBOT_TEST_LEVEL - suite.addTests([ - layered( - robottestsuite, - layer=PLONE_APP_DISCUSSION_ROBOT_TESTING, - ), - ]) + suite.addTests( + [ + layered( + robottestsuite, + layer=PLONE_APP_DISCUSSION_ROBOT_TESTING, + ), + ] + ) return suite diff --git a/plone/app/discussion/tests/test_workflow.py b/plone/app/discussion/tests/test_workflow.py index 9f959fc..ece7d8a 100644 --- a/plone/app/discussion/tests/test_workflow.py +++ b/plone/app/discussion/tests/test_workflow.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- """Test plone.app.discussion workflow and permissions. """ +from ..interfaces import IConversation +from ..interfaces import IDiscussionLayer +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from AccessControl import Unauthorized -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionLayer -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa from plone.app.testing import login from plone.app.testing import logout from plone.app.testing import setRoles @@ -19,48 +18,51 @@ import unittest class WorkflowSetupTest(unittest.TestCase): - """Make sure the workflows are set up properly. - """ + """Make sure the workflows are set up properly.""" layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal.invokeFactory('Folder', 'test-folder') - self.folder = self.portal['test-folder'] - self.portal.portal_types['Document'].allow_discussion = True - self.folder.invokeFactory('Document', 'doc1') + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal.invokeFactory("Folder", "test-folder") + self.folder = self.portal["test-folder"] + self.portal.portal_types["Document"].allow_discussion = True + self.folder.invokeFactory("Document", "doc1") self.doc = self.folder.doc1 def test_workflows_installed(self): - """Make sure both comment workflows have been installed properly. - """ - self.assertTrue('comment_one_state_workflow' in - self.portal.portal_workflow.objectIds()) - self.assertTrue('comment_review_workflow' in - self.portal.portal_workflow.objectIds()) + """Make sure both comment workflows have been installed properly.""" + self.assertTrue( + "comment_one_state_workflow" in self.portal.portal_workflow.objectIds() + ) + self.assertTrue( + "comment_review_workflow" in self.portal.portal_workflow.objectIds() + ) def test_default_workflow(self): - """Make sure one_state_workflow is the default workflow. - """ + """Make sure one_state_workflow is the default workflow.""" self.assertEqual( - ('comment_one_state_workflow',), + ("comment_one_state_workflow",), self.portal.portal_workflow.getChainForPortalType( - 'Discussion Item', + "Discussion Item", ), ) def test_review_comments_permission(self): # 'Review comments' in self.portal.permissionsOfRole('Admin') - setRoles(self.portal, TEST_USER_ID, ['Reviewer']) - self.assertTrue(self.portal.portal_membership.checkPermission( - 'Review comments', self.folder), self.folder) - setRoles(self.portal, TEST_USER_ID, ['Member']) + setRoles(self.portal, TEST_USER_ID, ["Reviewer"]) + self.assertTrue( + self.portal.portal_membership.checkPermission( + "Review comments", self.folder + ), + self.folder, + ) + setRoles(self.portal, TEST_USER_ID, ["Member"]) self.assertFalse( self.portal.portal_membership.checkPermission( - 'Review comments', + "Review comments", self.folder, ), self.folder, @@ -71,31 +73,30 @@ class WorkflowSetupTest(unittest.TestCase): class PermissionsSetupTest(unittest.TestCase): - """Make sure the permissions are set up properly. - """ + """Make sure the permissions are set up properly.""" layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) mtool = self.portal.portal_membership self.checkPermission = mtool.checkPermission def test_reply_to_item_permission_assigned(self): """Make sure the 'Reply to item' permission is properly assigned. - By default this permission is assigned to 'Member' and 'Manager'. - plone.app.discussion assigns this permission to 'Authenticated' as - well to emulate the behavior of the old commenting system. + By default this permission is assigned to 'Member' and 'Manager'. + plone.app.discussion assigns this permission to 'Authenticated' as + well to emulate the behavior of the old commenting system. """ - ReplyToItemPerm = 'Reply to item' + ReplyToItemPerm = "Reply to item" # should be allowed as Member self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal)) # should be allowed as Authenticated - setRoles(self.portal, TEST_USER_ID, ['Authenticated']) + setRoles(self.portal, TEST_USER_ID, ["Authenticated"]) self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal)) # should be allowed as Manager - setRoles(self.portal, TEST_USER_ID, ['Manager']) + setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.assertTrue(self.checkPermission(ReplyToItemPerm, self.portal)) # should not be allowed as anonymous logout() @@ -103,70 +104,66 @@ class PermissionsSetupTest(unittest.TestCase): class CommentOneStateWorkflowTest(unittest.TestCase): - """Test the comment_one_state_workflow that ships with plone.app.discussion. - """ + """Test the comment_one_state_workflow that ships with plone.app.discussion.""" layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal.invokeFactory('Folder', 'test-folder') - self.folder = self.portal['test-folder'] + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal.invokeFactory("Folder", "test-folder") + self.folder = self.portal["test-folder"] self.catalog = self.portal.portal_catalog self.workflow = self.portal.portal_workflow - self.folder.invokeFactory('Document', 'doc1') + self.folder.invokeFactory("Document", "doc1") self.doc = self.folder.doc1 # Add a comment conversation = IConversation(self.folder.doc1) - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" cid = conversation.addComment(comment) self.comment = self.folder.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(cid), + f"++conversation++default/{cid}", ) - self.portal.acl_users._doAddUser('member', 'secret', ['Member'], []) - self.portal.acl_users._doAddUser( - 'reviewer', 'secret', ['Reviewer'], []) - self.portal.acl_users._doAddUser('manager', 'secret', ['Manager'], []) - self.portal.acl_users._doAddUser('editor', ' secret', ['Editor'], []) - self.portal.acl_users._doAddUser('reader', 'secret', ['Reader'], []) + self.portal.acl_users._doAddUser("member", "secret", ["Member"], []) + self.portal.acl_users._doAddUser("reviewer", "secret", ["Reviewer"], []) + self.portal.acl_users._doAddUser("manager", "secret", ["Manager"], []) + self.portal.acl_users._doAddUser("editor", " secret", ["Editor"], []) + self.portal.acl_users._doAddUser("reader", "secret", ["Reader"], []) def test_initial_workflow_state(self): - """Make sure the initial workflow state of a comment is 'private'. - """ + """Make sure the initial workflow state of a comment is 'private'.""" self.assertEqual( - self.workflow.getInfoFor(self.doc, 'review_state'), - 'private', + self.workflow.getInfoFor(self.doc, "review_state"), + "private", ) def test_view_comments(self): - """Make sure published comments can be viewed by everyone. - """ + """Make sure published comments can be viewed by everyone.""" # Owner is allowed # self.login(default_user) # self.assertTrue(checkPerm(View, self.doc)) # Member is allowed login(self.portal, TEST_USER_NAME) workflow = self.portal.portal_workflow - workflow.doActionFor(self.doc, 'publish') + workflow.doActionFor(self.doc, "publish") - login(self.portal, 'member') + login(self.portal, "member") self.assertTrue(checkPerm(View, self.comment)) # Reviewer is allowed - login(self.portal, 'reviewer') + login(self.portal, "reviewer") self.assertTrue(checkPerm(View, self.comment)) # Anonymous is allowed logout() self.assertTrue(checkPerm(View, self.comment)) # Editor is allowed - login(self.portal, 'editor') + login(self.portal, "editor") self.assertTrue(checkPerm(View, self.comment)) # Reader is allowed - login(self.portal, 'reader') + login(self.portal, "reader") self.assertTrue(checkPerm(View, self.comment)) def test_comment_on_private_content_not_visible_to_world(self): @@ -175,8 +172,9 @@ class CommentOneStateWorkflowTest(unittest.TestCase): def test_migration(self): from plone.app.discussion.upgrades import upgrade_comment_workflows + # Fake permission according to earlier one_comment_workflow. - self.comment._View_Permission = ('Anonymous',) + self.comment._View_Permission = ("Anonymous",) # Anonymous can see the comment. logout() self.assertTrue(checkPerm(View, self.comment)) @@ -185,8 +183,8 @@ class CommentOneStateWorkflowTest(unittest.TestCase): upgrade_comment_workflows(self.portal.portal_setup) # The workflow chain is still what we want. self.assertEqual( - self.portal.portal_workflow.getChainFor('Discussion Item'), - ('comment_one_state_workflow',), + self.portal.portal_workflow.getChainFor("Discussion Item"), + ("comment_one_state_workflow",), ) # A Manager can still see the comment. self.assertTrue(checkPerm(View, self.comment)) @@ -196,112 +194,112 @@ class CommentOneStateWorkflowTest(unittest.TestCase): class CommentReviewWorkflowTest(unittest.TestCase): - """Test the comment_review_workflow that ships with plone.app.discussion. - """ + """Test the comment_review_workflow that ships with plone.app.discussion.""" layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING def setUp(self): - self.portal = self.layer['portal'] - setRoles(self.portal, TEST_USER_ID, ['Manager']) - self.portal.invokeFactory('Folder', 'test-folder') - self.folder = self.portal['test-folder'] + self.portal = self.layer["portal"] + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.portal.invokeFactory("Folder", "test-folder") + self.folder = self.portal["test-folder"] # Allow discussion on the Document content type - self.portal.portal_types['Document'].allow_discussion = True + self.portal.portal_types["Document"].allow_discussion = True # Set workflow for Discussion item to review workflow self.portal.portal_workflow.setChainForPortalTypes( - ('Discussion Item',), - ('comment_review_workflow',), + ("Discussion Item",), + ("comment_review_workflow",), ) # Create a conversation for this Document conversation = IConversation(self.portal.doc1) # Add a comment. - comment = createObject('plone.Comment') - comment.text = 'Comment text' + comment = createObject("plone.Comment") + comment.text = "Comment text" comment_id = conversation.addComment(comment) comment = self.portal.doc1.restrictedTraverse( - '++conversation++default/{0}'.format(comment_id), + f"++conversation++default/{comment_id}", ) self.conversation = conversation self.comment_id = comment_id self.comment = comment - setRoles(self.portal, TEST_USER_ID, ['Reviewer']) + setRoles(self.portal, TEST_USER_ID, ["Reviewer"]) alsoProvides(self.portal.REQUEST, IDiscussionLayer) def test_delete(self): - self.portal.REQUEST.form['comment_id'] = self.comment_id - view = self.comment.restrictedTraverse('@@moderate-delete-comment') + self.portal.REQUEST.form["comment_id"] = self.comment_id + view = self.comment.restrictedTraverse("@@moderate-delete-comment") view() self.assertFalse(self.comment_id in self.conversation.objectIds()) def test_delete_as_anonymous(self): # Make sure that anonymous users can not delete comments logout() - self.portal.REQUEST.form['comment_id'] = self.comment_id + self.portal.REQUEST.form["comment_id"] = self.comment_id self.assertRaises( Unauthorized, self.comment.restrictedTraverse, - '@@moderate-delete-comment', + "@@moderate-delete-comment", ) self.assertTrue(self.comment_id in self.conversation.objectIds()) def test_delete_as_user(self): # Make sure that members can not delete comments logout() - setRoles(self.portal, TEST_USER_ID, ['Member']) - self.portal.REQUEST.form['comment_id'] = self.comment_id + setRoles(self.portal, TEST_USER_ID, ["Member"]) + self.portal.REQUEST.form["comment_id"] = self.comment_id self.assertRaises( Unauthorized, self.comment.restrictedTraverse, - '@@moderate-delete-comment', + "@@moderate-delete-comment", ) self.assertTrue(self.comment_id in self.conversation.objectIds()) def test_publish(self): - self.portal.REQUEST.form['comment_id'] = self.comment_id - self.portal.REQUEST.form['workflow_action'] = 'publish' + self.portal.REQUEST.form["comment_id"] = self.comment_id + self.portal.REQUEST.form["workflow_action"] = "publish" self.assertEqual( - 'pending', + "pending", self.portal.portal_workflow.getInfoFor( self.comment, - 'review_state', + "review_state", ), ) - view = self.comment.restrictedTraverse('@@transmit-comment') + view = self.comment.restrictedTraverse("@@transmit-comment") view() self.assertEqual( - 'published', + "published", self.portal.portal_workflow.getInfoFor( self.comment, - 'review_state', + "review_state", ), ) def test_publish_as_anonymous(self): logout() - self.portal.REQUEST.form['comment_id'] = self.comment_id - self.portal.REQUEST.form['workflow_action'] = 'publish' + self.portal.REQUEST.form["comment_id"] = self.comment_id + self.portal.REQUEST.form["workflow_action"] = "publish" self.assertEqual( - 'pending', self.portal.portal_workflow.getInfoFor( + "pending", + self.portal.portal_workflow.getInfoFor( self.comment, - 'review_state', + "review_state", ), ) self.assertRaises( Unauthorized, self.comment.restrictedTraverse, - '@@transmit-comment', + "@@transmit-comment", ) self.assertEqual( - 'pending', + "pending", self.portal.portal_workflow.getInfoFor( self.comment, - 'review_state', + "review_state", ), ) @@ -312,15 +310,16 @@ class CommentReviewWorkflowTest(unittest.TestCase): # publish comment and check again login(self.portal, TEST_USER_NAME) workflow = self.portal.portal_workflow - workflow.doActionFor(self.comment, 'publish') + workflow.doActionFor(self.comment, "publish") logout() self.assertFalse(checkPerm(View, self.comment)) def test_migration(self): from plone.app.discussion.upgrades import upgrade_comment_workflows + # Fake permission according to earlier comment_review_workflow. - self.comment._View_Permission = ('Anonymous',) + self.comment._View_Permission = ("Anonymous",) # Anonymous can see the comment. logout() self.assertTrue(checkPerm(View, self.comment)) @@ -329,8 +328,9 @@ class CommentReviewWorkflowTest(unittest.TestCase): upgrade_comment_workflows(self.portal.portal_setup) # The workflow chain is still what we want. self.assertEqual( - self.portal.portal_workflow.getChainFor('Discussion Item'), - ('comment_review_workflow',)) + self.portal.portal_workflow.getChainFor("Discussion Item"), + ("comment_review_workflow",), + ) # A Manager can still see the comment. self.assertTrue(checkPerm(View, self.comment)) # Anonymous cannot see the comment. diff --git a/plone/app/discussion/tool.py b/plone/app/discussion/tool.py index a10ae3c..50f9361 100644 --- a/plone/app/discussion/tool.py +++ b/plone/app/discussion/tool.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """The portal_discussion tool, usually accessed via queryUtility(ICommentingTool). The default implementation delegates to the standard portal_catalog for indexing comments. @@ -17,62 +16,60 @@ from zope.component import queryUtility @interface.implementer(ICommentingTool) class CommentingTool(UniqueObject, SimpleItem): - meta_type = 'plone.app.discussion tool' - id = 'portal_discussion' + meta_type = "plone.app.discussion tool" + id = "portal_discussion" def reindexObject(self, object): # Reindex in catalog. - catalog = getToolByName(self, 'portal_catalog') + catalog = getToolByName(self, "portal_catalog") return catalog.reindexObject(object) indexObject = reindexObject def unindexObject(self, object): # Remove from catalog. - catalog = getToolByName(self, 'portal_catalog') + catalog = getToolByName(self, "portal_catalog") return catalog.unindexObject(object) def uniqueValuesFor(self, name): # return unique values for FieldIndex name - catalog = getToolByName(self, 'portal_catalog') + catalog = getToolByName(self, "portal_catalog") return catalog.uniqueValuesFor(name) def searchResults(self, REQUEST=None, **kw): # Calls ZCatalog.searchResults with extra arguments that # limit the results to what the user is allowed to see. - catalog = getToolByName(self, 'portal_catalog') + catalog = getToolByName(self, "portal_catalog") object_provides = [IComment.__identifier__] - if 'object_provides' in kw: - kw_provides = kw['object_provides'] + if "object_provides" in kw: + kw_provides = kw["object_provides"] if isinstance(str, kw_provides): object_provides.append(kw_provides) else: object_provides.extend(kw_provides) - if REQUEST is not None and 'object_provides' in REQUEST.form: - rq_provides = REQUEST.form['object_provides'] - del REQUEST.form['object_provides'] + if REQUEST is not None and "object_provides" in REQUEST.form: + rq_provides = REQUEST.form["object_provides"] + del REQUEST.form["object_provides"] if isinstance(str, rq_provides): object_provides.append(rq_provides) else: object_provides.extend(rq_provides) - kw['object_provides'] = object_provides + kw["object_provides"] = object_provides return catalog.searchResults(REQUEST, **kw) def index_object(obj, event): - """Index the object when added to the conversation - """ + """Index the object when added to the conversation""" tool = queryUtility(ICommentingTool) if tool is not None: tool.indexObject(obj) def unindex_object(obj, event): - """Unindex the object when removed - """ + """Unindex the object when removed""" tool = queryUtility(ICommentingTool) if tool is not None: tool.unindexObject(obj) diff --git a/plone/app/discussion/upgrades.py b/plone/app/discussion/upgrades.py index 784184e..40cdc8f 100644 --- a/plone/app/discussion/upgrades.py +++ b/plone/app/discussion/upgrades.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import IDiscussionSettings from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName @@ -7,8 +6,8 @@ from zope.component import getUtility import logging -default_profile = 'profile-plone.app.discussion:default' -logger = logging.getLogger('plone.app.discussion') +default_profile = "profile-plone.app.discussion:default" +logger = logging.getLogger("plone.app.discussion") def update_registry(context): @@ -17,7 +16,7 @@ def update_registry(context): def update_rolemap(context): - context.runImportStepFromProfile(default_profile, 'rolemap') + context.runImportStepFromProfile(default_profile, "rolemap") def upgrade_comment_workflows_retain_current_workflow(context): @@ -25,16 +24,16 @@ def upgrade_comment_workflows_retain_current_workflow(context): # import step will change it to comment_one_state_workflow. This is good. # If it was anything else, we should restore this. So get the original # chain. - portal_type = 'Discussion Item' - wf_tool = getToolByName(context, 'portal_workflow') + portal_type = "Discussion Item" + wf_tool = getToolByName(context, "portal_workflow") orig_chain = list(wf_tool.getChainFor(portal_type)) # Run the workflow step. This sets the chain to # comment_one_state_workflow. - context.runImportStepFromProfile(default_profile, 'workflow') + context.runImportStepFromProfile(default_profile, "workflow") # Restore original workflow chain if needed. - old_workflow = 'one_state_workflow' + old_workflow = "one_state_workflow" if old_workflow not in orig_chain: # Restore the chain. Probably comment_review_workflow. wf_tool.setChainForPortalTypes([portal_type], orig_chain) @@ -43,7 +42,7 @@ def upgrade_comment_workflows_retain_current_workflow(context): if old_workflow in orig_chain: # Replace with new one. idx = orig_chain.index(old_workflow) - orig_chain[idx] = 'comment_one_state_workflow' + orig_chain[idx] = "comment_one_state_workflow" # Restore the chain. wf_tool.setChainForPortalTypes([portal_type], orig_chain) @@ -51,9 +50,9 @@ def upgrade_comment_workflows_retain_current_workflow(context): def upgrade_comment_workflows_apply_rolemapping(context): # Now go over the comments, update their role mappings, and reindex the # allowedRolesAndUsers index. - portal_type = 'Discussion Item' - catalog = getToolByName(context, 'portal_catalog') - wf_tool = getToolByName(context, 'portal_workflow') + portal_type = "Discussion Item" + catalog = getToolByName(context, "portal_catalog") + wf_tool = getToolByName(context, "portal_workflow") new_chain = list(wf_tool.getChainFor(portal_type)) workflows = [wf_tool.getWorkflowById(wf_id) for wf_id in new_chain] for brain in catalog.unrestrictedSearchResults(portal_type=portal_type): @@ -63,7 +62,7 @@ def upgrade_comment_workflows_apply_rolemapping(context): wf.updateRoleMappingsFor(comment) comment.reindexObjectSecurity() except (AttributeError, KeyError): - logger.info('Could not reindex comment {0}'.format(brain.getURL())) + logger.info(f"Could not reindex comment {brain.getURL()}") def upgrade_comment_workflows(context): @@ -72,7 +71,7 @@ def upgrade_comment_workflows(context): def add_js_to_plone_legacy(context): - context.runImportStepFromProfile(default_profile, 'plone.app.registry') + context.runImportStepFromProfile(default_profile, "plone.app.registry") def extend_review_workflow(context): diff --git a/plone/app/discussion/vocabularies.py b/plone/app/discussion/vocabularies.py index b158866..290448d 100644 --- a/plone/app/discussion/vocabularies.py +++ b/plone/app/discussion/vocabularies.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import _ from zope.schema.vocabulary import SimpleTerm from zope.schema.vocabulary import SimpleVocabulary @@ -7,6 +6,7 @@ from zope.schema.vocabulary import SimpleVocabulary HAS_CAPTCHA = False try: import plone.formwidget.captcha # noqa + HAS_CAPTCHA = True # pragma: no cover except ImportError: pass @@ -14,6 +14,7 @@ except ImportError: HAS_RECAPTCHA = False try: import plone.formwidget.recaptcha # noqa + HAS_RECAPTCHA = True # pragma: no cover except ImportError: pass @@ -21,6 +22,7 @@ except ImportError: HAS_AKISMET = False try: import collective.akismet # noqa + HAS_AKISMET = True # pragma: no cover except ImportError: pass @@ -28,73 +30,48 @@ except ImportError: HAS_NOROBOTS = False try: import collective.z3cform.norobots # noqa + HAS_NOROBOTS = True # pragma: no cover except ImportError: pass def captcha_vocabulary(context): - """Vocabulary with all available captcha implementations. - """ + """Vocabulary with all available captcha implementations.""" terms = [] - terms.append( - SimpleTerm( - value='disabled', - token='disabled', - title=_(u'Disabled'))) + terms.append(SimpleTerm(value="disabled", token="disabled", title=_("Disabled"))) if HAS_CAPTCHA: # pragma: no cover - terms.append( - SimpleTerm( - value='captcha', - token='captcha', - title='Captcha')) + terms.append(SimpleTerm(value="captcha", token="captcha", title="Captcha")) if HAS_RECAPTCHA: # pragma: no cover terms.append( - SimpleTerm( - value='recaptcha', - token='recaptcha', - title='ReCaptcha')) + SimpleTerm(value="recaptcha", token="recaptcha", title="ReCaptcha") + ) if HAS_AKISMET: # pragma: no cover - terms.append( - SimpleTerm( - value='akismet', - token='akismet', - title='Akismet')) + terms.append(SimpleTerm(value="akismet", token="akismet", title="Akismet")) if HAS_NOROBOTS: # pragma: no cover - terms.append( - SimpleTerm( - value='norobots', - token='norobots', - title='Norobots')) + terms.append(SimpleTerm(value="norobots", token="norobots", title="Norobots")) return SimpleVocabulary(terms) def text_transform_vocabulary(context): - """Vocabulary with all available portal_transform transformations. - """ + """Vocabulary with all available portal_transform transformations.""" terms = [] + terms.append(SimpleTerm(value="text/plain", token="text/plain", title="Plain text")) + terms.append(SimpleTerm(value="text/html", token="text/html", title="HTML")) terms.append( SimpleTerm( - value='text/plain', - token='text/plain', - title='Plain text')) + value="text/x-web-markdown", token="text/x-web-markdown", title="Markdown" + ) + ) terms.append( SimpleTerm( - value='text/html', - token='text/html', - title='HTML')) - terms.append( - SimpleTerm( - value='text/x-web-markdown', - token='text/x-web-markdown', - title='Markdown')) - terms.append( - SimpleTerm( - value='text/x-web-intelligent', - token='text/x-web-intelligent', - title='Intelligent text')) + value="text/x-web-intelligent", + token="text/x-web-intelligent", + title="Intelligent text", + ) + ) return SimpleVocabulary(terms) diff --git a/setup.cfg b/setup.cfg index 5a698fc..e726c4c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,3 +11,10 @@ universal = 1 [zest.releaser] create-wheel = yes + +[isort] +# black compatible Plone isort rules: +profile = black +force_alphabetical_sort = True +force_single_line = True +lines_after_imports = 2 diff --git a/setup.py b/setup.py index 893470a..3b2c629 100644 --- a/setup.py +++ b/setup.py @@ -1,73 +1,62 @@ -# encoding: utf-8 - from setuptools import find_packages from setuptools import setup -version = '4.0.0a7.dev0' +version = "4.0.0a7.dev0" install_requires = [ - 'setuptools', - 'plone.app.layout', - 'plone.app.registry', - 'plone.app.uuid', - 'plone.app.z3cform', - 'plone.indexer', - 'plone.registry', - 'plone.z3cform', - 'six', - 'ZODB3', - 'zope.interface', - 'zope.component', - 'zope.annotation', - 'zope.event', - 'zope.container', - 'zope.lifecycleevent', - 'zope.site', - 'z3c.form>=2.3.3', + "setuptools", + "plone.app.layout", + "plone.app.registry", + "plone.app.uuid", + "plone.app.z3cform", + "plone.base", + "plone.indexer", + "plone.z3cform", + "z3c.form>=2.3.3", ] -setup(name='plone.app.discussion', - version=version, - description='Enhanced discussion support for Plone', - long_description=open('README.rst').read() + '\n' + - open('CHANGES.rst').read(), - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Web Environment", - "Framework :: Plone", - "Framework :: Plone :: 6.0", - "Framework :: Plone :: Core", - "Framework :: Zope :: 5", - "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - ], - keywords='plone discussion', - author='Timo Stollenwerk - Plone Foundation', - author_email='plone-developers@lists.sourceforge.net', - url='https://pypi.org/project/plone.app.discussion', - license='GPL', - packages=find_packages(), - namespace_packages=['plone', 'plone.app'], - include_package_data=True, - zip_safe=False, - install_requires=install_requires, - extras_require={ - 'test': [ - 'plone.app.testing', - 'plone.stringinterp', - 'plone.contentrules', - 'plone.app.contentrules', - 'plone.app.contenttypes[test]', - 'plone.app.robotframework', - ], - }, - entry_points=""" +setup( + name="plone.app.discussion", + version=version, + description="Enhanced discussion support for Plone", + long_description=open("README.rst").read() + "\n" + open("CHANGES.rst").read(), + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Plone", + "Framework :: Plone :: 6.0", + "Framework :: Plone :: Core", + "Framework :: Zope :: 5", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + ], + keywords="plone discussion", + author="Timo Stollenwerk - Plone Foundation", + author_email="plone-developers@lists.sourceforge.net", + url="https://pypi.org/project/plone.app.discussion", + license="GPL", + packages=find_packages(), + namespace_packages=["plone", "plone.app"], + include_package_data=True, + zip_safe=False, + install_requires=install_requires, + extras_require={ + "test": [ + "plone.app.testing", + "plone.stringinterp", + "plone.contentrules", + "plone.app.contentrules", + "plone.app.contenttypes[test]", + "plone.app.robotframework", + ], + }, + entry_points=""" [z3c.autoinclude.plugin] target = plone """, - ) +)