diff --git a/docs/source/conf.py b/docs/source/conf.py index f5d59b8..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. @@ -47,8 +46,8 @@ source_suffix = ".txt" 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 @@ -185,8 +184,8 @@ latex_documents = [ ( "index", "ploneappdiscussion.tex", - u"plone.app.discussion Documentation", - u"Timo Stollenwerk", + "plone.app.discussion Documentation", + "Timo Stollenwerk", "manual", ), ] diff --git a/plone/__init__.py b/plone/__init__.py index 03d08ff..5284146 100644 --- a/plone/__init__.py +++ b/plone/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- __import__("pkg_resources").declare_namespace(__name__) diff --git a/plone/app/__init__.py b/plone/app/__init__.py index 03d08ff..5284146 100644 --- a/plone/app/__init__.py +++ b/plone/app/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- __import__("pkg_resources").declare_namespace(__name__) diff --git a/plone/app/discussion/__init__.py b/plone/app/discussion/__init__.py index 88b2d0d..8efe8b3 100644 --- a/plone/app/discussion/__init__.py +++ b/plone/app/discussion/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from zope.i18nmessageid import MessageFactory diff --git a/plone/app/discussion/browser/captcha.py b/plone/app/discussion/browser/captcha.py index 3c21fc5..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 @@ -23,7 +22,7 @@ from zope.publisher.interfaces.browser import IDefaultBrowserLayer class Captcha(Persistent): """Captcha input field.""" - captcha = u"" + captcha = "" Captcha = factory(Captcha) diff --git a/plone/app/discussion/browser/comment.py b/plone/app/discussion/browser/comment.py index bfa1ea7..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 @@ -48,9 +47,9 @@ 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): @@ -58,10 +57,10 @@ class EditCommentForm(CommentForm): ignoreContext = True id = "edit-comment-form" - label = _(u"edit_comment_form_title", default=u"Edit comment") + label = _("edit_comment_form_title", default="Edit comment") def updateWidgets(self): - super(EditCommentForm, self).updateWidgets() + 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. @@ -70,12 +69,12 @@ class EditCommentForm(CommentForm): def _redirect(self, target=""): if not target: portal_state = getMultiAdapter( - (self.context, self.request), name=u"plone_portal_state" + (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 @@ -96,14 +95,14 @@ class EditCommentForm(CommentForm): # Redirect to comment IStatusMessage(self.request).add( - _(u"comment_edit_notification", default="Comment was edited"), type="info" + _("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"), + _("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 324acc8..817ad22 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 @@ -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,7 +63,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): ignoreContext = True # don't use context to get widget data id = None - label = _(u"Add a comment") + label = _("Add a comment") fields = field.Fields(IComment).omit( "portal_type", "__parent__", @@ -79,16 +78,16 @@ class CommentForm(extensible.ExtensibleForm, form.Form): ) def updateFields(self): - super(CommentForm, self).updateFields() + 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["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 @@ -140,7 +139,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): self.widgets["user_notification"].mode = interfaces.HIDDEN_MODE def updateActions(self): - super(CommentForm, self).updateActions() + super().updateActions() self.actions["cancel"].addClass("btn btn-secondary") self.actions["cancel"].addClass("hide") self.actions["comment"].addClass("btn btn-primary") @@ -148,7 +147,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): 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: @@ -219,16 +218,14 @@ class CommentForm(extensible.ExtensibleForm, form.Form): 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) @@ -254,7 +251,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): anon = portal_membership.isAnonymousUser() if captcha_enabled and anonymous_comments and anon: if "captcha" not in data: - data["captcha"] = u"" + data["captcha"] = "" captcha = CaptchaValidator( self.context, self.request, None, ICaptcha["captcha"], None ) @@ -296,7 +293,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # Redirect to comment (inside a content object page) 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. @@ -309,7 +306,7 @@ class CommentsViewlet(ViewletBase): 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() @@ -483,7 +480,7 @@ 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): @@ -523,7 +520,7 @@ class CommentsViewlet(ViewletBase): 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", "")), ) diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py index 8e40a3c..f359c48 100644 --- a/plone/app/discussion/browser/controlpanel.py +++ b/plone/app/discussion/browser/controlpanel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import _ from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.discussion.upgrades import update_registry @@ -32,22 +31,22 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): schema = IDiscussionSettings id = "DiscussionSettingsEditForm" - label = _(u"Discussion settings") + 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() + super().updateFields() self.fields["globally_enabled"].widgetFactory = SingleCheckBoxFieldWidget self.fields["moderation_enabled"].widgetFactory = SingleCheckBoxFieldWidget self.fields["edit_comment_enabled"].widgetFactory = SingleCheckBoxFieldWidget @@ -65,20 +64,20 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): 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") + 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 = _( - u"Moderator Email Notification", + "Moderator Email Notification", ) self.widgets["user_notification_enabled"].label = _( - u"User Email Notification", + "User Email Notification", ) @button.buttonAndHandler(_("Save"), name=None) @@ -88,14 +87,14 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): self.status = self.formErrorsMessage return self.applyChanges(data) - IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info") + IStatusMessage(self.request).addStatusMessage(_("Changes saved"), "info") self.context.REQUEST.RESPONSE.redirect("@@discussion-controlpanel") @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, ), @@ -111,7 +110,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): def __call__(self): self.mailhost_warning() self.custom_comment_workflow_warning() - return super(DiscussionSettingsControlPanel, self).__call__() + return super().__call__() @property def site_url(self): @@ -180,8 +179,8 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): 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.", + "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") @@ -195,8 +194,8 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper): 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.", + "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") diff --git a/plone/app/discussion/browser/conversation.py b/plone/app/discussion/browser/conversation.py index 79622e0..f2356c8 100644 --- a/plone/app/discussion/browser/conversation.py +++ b/plone/app/discussion/browser/conversation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from Acquisition import aq_base from Acquisition import aq_chain from Acquisition import aq_inner @@ -34,7 +33,7 @@ def traverse_parents(context): return None -class ConversationView(object): +class ConversationView: def enabled(self): if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context): return self._enabled_for_dexterity_types() diff --git a/plone/app/discussion/browser/moderation.py b/plone/app/discussion/browser/moderation.py index 5603cd3..1d0ee8f 100644 --- a/plone/app/discussion/browser/moderation.py +++ b/plone/app/discussion/browser/moderation.py @@ -1,4 +1,3 @@ -# coding: utf-8 from AccessControl import getSecurityManager from AccessControl import Unauthorized from Acquisition import aq_inner @@ -51,7 +50,7 @@ class View(BrowserView): pass def __init__(self, context, request): - super(View, self).__init__(context, request) + super().__init__(context, request) self.workflowTool = getToolByName(self.context, "portal_workflow") self.transitions = [] @@ -229,7 +228,7 @@ class DeleteOwnComment(DeleteComment): def __call__(self): if self.can_delete(): - super(DeleteOwnComment, self).__call__() + super().__call__() else: raise Unauthorized("You're not allowed to delete this comment.") @@ -318,7 +317,7 @@ class BulkActionsView(BrowserView): """ def __init__(self, context, request): - super(BulkActionsView, self).__init__(context, request) + super().__init__(context, request) self.workflowTool = getToolByName(context, "portal_workflow") def __call__(self): diff --git a/plone/app/discussion/browser/traversal.py b/plone/app/discussion/browser/traversal.py index 3dd55d4..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 @@ -30,7 +29,7 @@ class ConversationNamespace(object): def traverse(self, name, ignore): if name == "default": - name = u"" + 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 a960538..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,7 +38,7 @@ 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) diff --git a/plone/app/discussion/catalog.py b/plone/app/discussion/catalog.py index 0f9252f..0da596e 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. diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py index 1d376be..4e515b8 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 @@ -44,28 +43,28 @@ 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") @@ -100,10 +99,10 @@ class Comment( comment_id = None # long in_reply_to = None # long - title = u"" + title = "" mime_type = None - text = u"" + text = "" creator = None creation_date = None @@ -137,7 +136,7 @@ class Comment( @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): @@ -162,7 +161,7 @@ class Comment( text = self.text if text is None: return "" - if six.PY2 and isinstance(text, six.text_type): + if six.PY2 and isinstance(text, str): text = text.encode("utf8") transform = transforms.convertTo( targetMimetype, text, context=self, mimetype=sourceMimetype @@ -172,8 +171,8 @@ class Comment( else: logger = logging.getLogger("plone.app.discussion") msg = ( - u'Transform "{0}" => "{1}" not available. Failed to ' - u'transform comment "{2}".' + 'Transform "{0}" => "{1}" not available. Failed to ' + 'transform comment "{2}".' ) logger.error( msg.format( @@ -194,8 +193,8 @@ class Comment( author_name = translate( Message( _( - u"label_anonymous", - default=u"Anonymous", + "label_anonymous", + default="Anonymous", ), ), ) @@ -373,7 +372,7 @@ 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, @@ -441,7 +440,7 @@ 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, @@ -453,8 +452,8 @@ def notify_moderator(obj, event): or translate( Message( _( - u"label_anonymous", - default=u"Anonymous", + "label_anonymous", + default="Anonymous", ), ), ), diff --git a/plone/app/discussion/contentrules.py b/plone/app/discussion/contentrules.py index da324fc..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 _ @@ -8,7 +7,7 @@ try: from plone.stringinterp.adapters import BaseSubstitution except ImportError: - class BaseSubstitution(object): + class BaseSubstitution: """Fallback class if plone.stringinterp is not available""" def __init__(self, context, **kwargs): @@ -32,7 +31,7 @@ class CommentSubstitution(BaseSubstitution): """Comment string substitution""" def __init__(self, context, **kwargs): - super(CommentSubstitution, self).__init__(context, **kwargs) + super().__init__(context, **kwargs) @property def event(self): @@ -48,53 +47,53 @@ class CommentSubstitution(BaseSubstitution): class Id(CommentSubstitution): """Comment id string substitution""" - category = _(u"Comments") - description = _(u"Comment id") + category = _("Comments") + description = _("Comment id") def safe_call(self): """Safe call""" - return getattr(self.comment, "comment_id", u"") + return getattr(self.comment, "comment_id", "") class Text(CommentSubstitution): """Comment text""" - category = _(u"Comments") - description = _(u"Comment text") + category = _("Comments") + description = _("Comment text") def safe_call(self): """Safe call""" - return getattr(self.comment, "text", u"") + return getattr(self.comment, "text", "") class AuthorUserName(CommentSubstitution): """Comment author user name string substitution""" - category = _(u"Comments") - description = _(u"Comment author user name") + category = _("Comments") + description = _("Comment author user name") def safe_call(self): """Safe call""" - return getattr(self.comment, "author_username", u"") + return getattr(self.comment, "author_username", "") class AuthorFullName(CommentSubstitution): """Comment author full name string substitution""" - category = _(u"Comments") - description = _(u"Comment author full name") + category = _("Comments") + description = _("Comment author full name") def safe_call(self): """Safe call""" - return getattr(self.comment, "author_name", u"") + return getattr(self.comment, "author_name", "") class AuthorEmail(CommentSubstitution): """Comment author email string substitution""" - category = _(u"Comments") - description = _(u"Comment author email") + category = _("Comments") + description = _("Comment author email") def safe_call(self): """Safe call""" - return getattr(self.comment, "author_email", u"") + return getattr(self.comment, "author_email", "") diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index 6c28a16..e42cc41 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 @@ -130,8 +129,7 @@ class Conversation(Traversable, Persistent, Explicit): 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) @@ -145,8 +143,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 @@ -276,14 +273,14 @@ class Conversation(Traversable, Persistent, Explicit): 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): + for k, v in self._comments.items(): yield ( k, v.__of__(self), @@ -332,7 +329,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. diff --git a/plone/app/discussion/events.py b/plone/app/discussion/events.py index 8cec3c3..3444bbc 100644 --- a/plone/app/discussion/events.py +++ b/plone/app/discussion/events.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Custom discussion events """ from plone.app.discussion.interfaces import ICommentAddedEvent @@ -15,7 +14,7 @@ from zope.interface import implementer @implementer(IDiscussionEvent) -class DiscussionEvent(object): +class DiscussionEvent: """Custom event""" def __init__(self, context, comment, **kwargs): diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py index 1a211f9..1f35487 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 _ @@ -42,24 +41,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, ) @@ -138,58 +137,58 @@ class IComment(Interface): """ portal_type = schema.ASCIILine( - title=_(u"Portal type"), + 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_name = schema.TextLine(title=_("Name"), required=False) author_email = schema.TextLine( - title=_(u"Email"), + 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 = schema.TextLine(title=_("Captcha"), required=False) class IDiscussionSettings(Interface): @@ -204,26 +203,26 @@ 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, @@ -231,11 +230,11 @@ class IDiscussionSettings(Interface): anonymous_email_enabled = schema.Bool( title=_( - u"label_anonymous_email_enabled", default=u"Enable anonymous email field" + "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, @@ -243,28 +242,28 @@ class IDiscussionSettings(Interface): moderation_enabled = schema.Bool( title=_( - u"label_moderation_enabled", + "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"), + title=_("label_edit_comment_enabled", default="Enable editing of comments"), description=_( - u"help_edit_comment_enabled", - default=u"If selected, supports editing " + "help_edit_comment_enabled", + default="If selected, supports editing " 'of comments for users with the "Edit comments" ' "permission.", ), @@ -274,11 +273,11 @@ class IDiscussionSettings(Interface): delete_own_comment_enabled = schema.Bool( title=_( - u"label_delete_own_comment_enabled", default="Enable deleting own comments" + "label_delete_own_comment_enabled", default="Enable deleting own comments" ), description=_( - u"help_delete_own_comment_enabled", - default=u"If selected, supports deleting " + "help_delete_own_comment_enabled", + default="If selected, supports deleting " "of own comments for users with the " '"Delete own comments" permission.', ), @@ -287,16 +286,16 @@ class IDiscussionSettings(Interface): ) 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", @@ -304,15 +303,15 @@ class IDiscussionSettings(Interface): ) 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", @@ -320,11 +319,11 @@ class IDiscussionSettings(Interface): ) 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, @@ -332,14 +331,14 @@ class IDiscussionSettings(Interface): moderator_notification_enabled = schema.Bool( title=_( - u"label_moderator_notification_enabled", - default=u"Enable moderator email notification", + "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, @@ -347,25 +346,25 @@ class IDiscussionSettings(Interface): 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, diff --git a/plone/app/discussion/subscribers.py b/plone/app/discussion/subscribers.py index 5f20867..6e1b498 100644 --- a/plone/app/discussion/subscribers.py +++ b/plone/app/discussion/subscribers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from Products.CMFCore.utils import getToolByName diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py index c78b643..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 diff --git a/plone/app/discussion/tests/test_catalog.py b/plone/app/discussion/tests/test_catalog.py index 86cbaed..395f6c2 100644 --- a/plone/app/discussion/tests/test_catalog.py +++ b/plone/app/discussion/tests/test_catalog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Test the plone.app.discussion catalog indexes """ from datetime import datetime @@ -98,7 +97,7 @@ class ConversationCatalogTest(unittest.TestCase): 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( @@ -128,7 +127,7 @@ class ConversationCatalogTest(unittest.TestCase): 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( @@ -188,7 +187,7 @@ class ConversationCatalogTest(unittest.TestCase): 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() @@ -283,7 +282,7 @@ class CommentCatalogTest(unittest.TestCase): # 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( @@ -304,7 +303,7 @@ class CommentCatalogTest(unittest.TestCase): # Comment brain comment = self.portal.doc1.restrictedTraverse( - "++conversation++default/{0}".format(cid), + f"++conversation++default/{cid}", ) brains = self.catalog.searchResults( dict( @@ -503,7 +502,7 @@ class CommentCatalogTest(unittest.TestCase): 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), diff --git a/plone/app/discussion/tests/test_comment.py b/plone/app/discussion/tests/test_comment.py index ff9dd27..3d16fea 100644 --- a/plone/app/discussion/tests/test_comment.py +++ b/plone/app/discussion/tests/test_comment.py @@ -1,4 +1,3 @@ -# -*- 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 @@ -64,7 +63,7 @@ class CommentTest(unittest.TestCase): comment1.comment_id = 123 self.assertEqual("123", comment1.id) self.assertEqual("123", comment1.getId()) - self.assertEqual(u"123", comment1.__name__) + self.assertEqual("123", comment1.__name__) def test_uid(self): conversation = IConversation(self.portal.doc1) @@ -111,14 +110,14 @@ class CommentTest(unittest.TestCase): def test_title_special_characters(self): self.portal.invokeFactory( id="doc_sp_chars", - title=u"Document äüö", + title="Document äüö", type_name="Document", ) conversation = IConversation(self.portal.doc_sp_chars) comment1 = createObject("plone.Comment") - comment1.author_name = u"Tarek Ziadé" + 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( @@ -130,7 +129,7 @@ class CommentTest(unittest.TestCase): 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") @@ -174,12 +173,9 @@ class CommentTest(unittest.TestCase): def test_getText_with_non_ascii_characters(self): comment1 = createObject("plone.Comment") - comment1.text = u"Umlaute sind ä, ö und ü." + comment1.text = "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")) + self.assertEqual(comment1.getText(), out.decode("utf8")) def test_getText_doesnt_link(self): comment1 = createObject("plone.Comment") @@ -233,7 +229,7 @@ class CommentTest(unittest.TestCase): 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)) @@ -268,7 +264,7 @@ class CommentTest(unittest.TestCase): 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) @@ -336,7 +332,7 @@ class CommentTest(unittest.TestCase): 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 @@ -381,7 +377,7 @@ class RepliesTest(unittest.TestCase): 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 @@ -418,7 +414,7 @@ class RepliesTest(unittest.TestCase): 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 @@ -454,7 +450,7 @@ class RepliesTest(unittest.TestCase): 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 @@ -463,7 +459,7 @@ class RepliesTest(unittest.TestCase): 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 @@ -472,7 +468,7 @@ class RepliesTest(unittest.TestCase): 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 @@ -481,7 +477,7 @@ class RepliesTest(unittest.TestCase): 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( diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py index 4b25242..020dfc0 100644 --- a/plone/app/discussion/tests/test_comments_viewlet.py +++ b/plone/app/discussion/tests/test_comments_viewlet.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from AccessControl import Unauthorized from datetime import datetime from OFS.Image import Image @@ -81,7 +80,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 @@ -89,7 +88,7 @@ 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 @@ -99,11 +98,11 @@ class TestCommentForm(unittest.TestCase): # 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 @@ -116,7 +115,7 @@ class TestCommentForm(unittest.TestCase): self.assertEqual(len(comments), 1) for comment in comments: - self.assertEqual(comment.text, u"bar") + 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() @@ -144,23 +143,23 @@ 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 @@ -171,10 +170,10 @@ class TestCommentForm(unittest.TestCase): # 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 @@ -182,14 +181,14 @@ class TestCommentForm(unittest.TestCase): self.assertEqual(len(errors), 0) 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.text, "foobar") self.assertEqual(comment.creator, "test_user_1_") self.assertEqual(comment.getOwner().getUserName(), "test-user") @@ -218,16 +217,16 @@ 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() @@ -240,7 +239,7 @@ class TestCommentForm(unittest.TestCase): 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"]) @@ -275,16 +274,16 @@ 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() @@ -297,7 +296,7 @@ class TestCommentForm(unittest.TestCase): 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"]) @@ -337,20 +336,20 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u"comment-form", + 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", + "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 @@ -363,7 +362,7 @@ class TestCommentForm(unittest.TestCase): 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) @@ -387,14 +386,14 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u"comment-form", + 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 @@ -425,12 +424,12 @@ class TestCommentForm(unittest.TestCase): adapts=(Interface, IBrowserRequest), provides=Interface, factory=CommentForm, - name=u"comment-form", + 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 diff --git a/plone/app/discussion/tests/test_contentrules.py b/plone/app/discussion/tests/test_contentrules.py index d23c854..506a538 100644 --- a/plone/app/discussion/tests/test_contentrules.py +++ b/plone/app/discussion/tests/test_contentrules.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import ICommentAddedEvent from plone.app.discussion.interfaces import ICommentRemovedEvent from plone.app.discussion.interfaces import IConversation @@ -56,32 +55,32 @@ 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.document, IStringSubstitution, name="comment_text" ) - self.assertEqual(comment_text(), u"This is a comment") + self.assertEqual(comment_text(), "This is a comment") def testCommentUserIdStringSubstitution(self): comment_user_id = getAdapter( - self.document, IStringSubstitution, name=u"comment_user_id" + self.document, IStringSubstitution, name="comment_user_id" ) - self.assertEqual(comment_user_id(), u"jim") + self.assertEqual(comment_user_id(), "jim") def testCommentUserFullNameStringSubstitution(self): comment_user_fullname = getAdapter( - self.document, IStringSubstitution, name=u"comment_user_fullname" + self.document, IStringSubstitution, name="comment_user_fullname" ) - self.assertEqual(comment_user_fullname(), u"Jim") + self.assertEqual(comment_user_fullname(), "Jim") def testCommentUserEmailStringSubstitution(self): comment_user_email = getAdapter( - self.document, IStringSubstitution, name=u"comment_user_email" + self.document, IStringSubstitution, name="comment_user_email" ) - self.assertEqual(comment_user_email(), u"jim@example.com") + self.assertEqual(comment_user_email(), "jim@example.com") class ReplyContentRulesTest(unittest.TestCase): @@ -103,7 +102,7 @@ class ReplyContentRulesTest(unittest.TestCase): 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") @@ -119,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) @@ -127,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 f9f1cb1..b148547 100644 --- a/plone/app/discussion/tests/test_controlpanel.py +++ b/plone/app/discussion/tests/test_controlpanel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.discussion.testing import ( # noqa PLONE_APP_DISCUSSION_INTEGRATION_TESTING, diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py index c00acd9..c749f53 100644 --- a/plone/app/discussion/tests/test_conversation.py +++ b/plone/app/discussion/tests/test_conversation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from Acquisition import aq_base from Acquisition import aq_parent from datetime import datetime @@ -387,17 +386,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) @@ -852,18 +851,18 @@ class RepliesTest(unittest.TestCase): # 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 d4d6a92..175af1c 100644 --- a/plone/app/discussion/tests/test_events.py +++ b/plone/app/discussion/tests/test_events.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IReplies from plone.app.discussion.testing import ( # noqa @@ -20,7 +19,7 @@ import unittest # -class EventsRegistry(object): +class EventsRegistry: """Fake registry to be used while testing discussion events""" commentAdded = False @@ -123,7 +122,7 @@ class CommentEventsTest(unittest.TestCase): 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)) @@ -191,7 +190,7 @@ class RepliesEventsTest(unittest.TestCase): 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") @@ -211,7 +210,7 @@ class RepliesEventsTest(unittest.TestCase): 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" @@ -232,7 +231,7 @@ class RepliesEventsTest(unittest.TestCase): 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") diff --git a/plone/app/discussion/tests/test_functional.py b/plone/app/discussion/tests/test_functional.py index 23b71c6..f5f1181 100644 --- a/plone/app/discussion/tests/test_functional.py +++ b/plone/app/discussion/tests/test_functional.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Functional Doctests for plone.app.discussion. These test are only triggered when Plone 4 (and plone.testing) is installed. diff --git a/plone/app/discussion/tests/test_indexers.py b/plone/app/discussion/tests/test_indexers.py index 5180179..0581576 100644 --- a/plone/app/discussion/tests/test_indexers.py +++ b/plone/app/discussion/tests/test_indexers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Test for the plone.app.discussion indexers """ from datetime import datetime 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 7f9edb3..2247c6f 100644 --- a/plone/app/discussion/tests/test_moderation_multiple_state_view.py +++ b/plone/app/discussion/tests/test_moderation_multiple_state_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.browser.moderation import BulkActionsView from plone.app.discussion.browser.moderation import CommentTransition from plone.app.discussion.browser.moderation import DeleteComment @@ -42,7 +41,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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" @@ -50,7 +49,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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" @@ -58,7 +57,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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 diff --git a/plone/app/discussion/tests/test_moderation_view.py b/plone/app/discussion/tests/test_moderation_view.py index 59f24ba..5458404 100644 --- a/plone/app/discussion/tests/test_moderation_view.py +++ b/plone/app/discussion/tests/test_moderation_view.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.browser.moderation import BulkActionsView from plone.app.discussion.browser.moderation import CommentTransition from plone.app.discussion.browser.moderation import DeleteComment @@ -81,7 +80,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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" @@ -89,7 +88,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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" @@ -97,7 +96,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): 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 diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py index a0d3d70..fdf326f 100644 --- a/plone/app/discussion/tests/test_notifications.py +++ b/plone/app/discussion/tests/test_notifications.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from Acquisition import aq_base from plone.app.discussion.interfaces import IConversation from plone.app.discussion.testing import ( # noqa @@ -80,7 +79,7 @@ class TestUserNotificationUnit(unittest.TestCase): # 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(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) @@ -215,7 +214,7 @@ class TestModeratorNotificationUnit(unittest.TestCase): # 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(f"http://nohost/plone/doc1/view#{comment_id}", msg) self.assertIn(comment.author_email, msg) self.assertIn(comment.text, msg) diff --git a/plone/app/discussion/tests/test_robot.py b/plone/app/discussion/tests/test_robot.py index 5b3e073..e6487d5 100644 --- a/plone/app/discussion/tests/test_robot.py +++ b/plone/app/discussion/tests/test_robot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from plone.app.discussion.testing import PLONE_APP_DISCUSSION_ROBOT_TESTING from plone.app.testing import ROBOT_TEST_LEVEL from plone.testing import layered diff --git a/plone/app/discussion/tests/test_workflow.py b/plone/app/discussion/tests/test_workflow.py index 9095fc0..5667f00 100644 --- a/plone/app/discussion/tests/test_workflow.py +++ b/plone/app/discussion/tests/test_workflow.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Test plone.app.discussion workflow and permissions. """ from AccessControl import Unauthorized @@ -128,7 +127,7 @@ class CommentOneStateWorkflowTest(unittest.TestCase): 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"], []) @@ -223,7 +222,7 @@ class CommentReviewWorkflowTest(unittest.TestCase): 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 diff --git a/plone/app/discussion/tool.py b/plone/app/discussion/tool.py index a57e9bb..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. diff --git a/plone/app/discussion/upgrades.py b/plone/app/discussion/upgrades.py index c9b19d1..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 @@ -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): diff --git a/plone/app/discussion/vocabularies.py b/plone/app/discussion/vocabularies.py index 58b5716..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 @@ -40,7 +39,7 @@ except ImportError: def captcha_vocabulary(context): """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")) diff --git a/setup.py b/setup.py index ab0134b..fbc4639 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from setuptools import find_packages from setuptools import setup