diff --git a/README.rst b/README.rst index 44f807c..b78fc0d 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,5 @@ -Introduction -============ -Provides ${id} style string interpolation using named adapters to look up -variables. This is meant to provide a trivially simple template system -for clients like plone.app.contentrules. +Provides ``${id}`` style string interpolation using named adapters to look up variables. +This is meant to provide a trivially simple template system for clients like plone.app.contentrules. To interpolate a string in context, just follow the pattern:: @@ -10,24 +7,19 @@ To interpolate a string in context, just follow the pattern:: IStringInterpolator(context)("Here is the title: ${title}") -Substitution of variables that are part of the Dublin Core are -provided with the package. To provide additional substitutions, just -provide a named adapter implementing interfaces.IStringSubstitution -for your context. The adapter name is used for the lookup. +Substitution of variables that are part of the Dublin Core are provided with the package. +To provide additional substitutions, provide a named adapter implementing ``interfaces.IStringSubstitution`` for your context. +The adapter name is used for the lookup. -You can also wrap your context with IContextWrapper adapter if you need to pass -custom messages within your substitutions. +You can also wrap your context with ``IContextWrapper`` adapter if you need to pass custom messages within your substitutions. -Dependencies -============ -Dependencies are all in the CMF* namespace, so this is theoretically useful -outside Plone. It does use the 'plone' identifier for the message factory. Implemented Substitutions ========================= All Content ----------- + - id - parent_id - url @@ -36,12 +28,14 @@ All Content Minimal Dublin Core ------------------- + - title - description - type (content type) Workflow Aware -------------- + - review_state - review_state_title @@ -62,6 +56,7 @@ Dublin Core Catalogable Dublin Core ----------------------- + Everything should be in long local time format - created @@ -71,6 +66,7 @@ Everything should be in long local time format Member / Group Information for roles on content ----------------------------------------------- + - owner_emails - reviewer_emails - manager_emails @@ -79,12 +75,22 @@ Member / Group Information for roles on content Current User Information ------------------------ + - user_fullname - user_id Last Change (workflow or version) Information --------------------------------------------- + - change_comment - change_title - change_type - change_authorid + + +Source Code +=========== + +Contributors please read the document `Process for Plone core's development `_ + +Sources and issue tracker are at the `Plone code repository hosted at Github `_. diff --git a/news/15.breaking b/news/15.breaking new file mode 100644 index 0000000..dc1a8a1 --- /dev/null +++ b/news/15.breaking @@ -0,0 +1,5 @@ +pyupgrade, drop support for Python < 3.8. +isort, black, manual edits. +Fix deprecation warnings for imports from Products.CMFPlone. +Import and depend from plone.base. + diff --git a/plone/__init__.py b/plone/__init__.py index f48ad10..5284146 100644 --- a/plone/__init__.py +++ b/plone/__init__.py @@ -1,6 +1 @@ -# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) +__import__("pkg_resources").declare_namespace(__name__) diff --git a/plone/stringinterp/__init__.py b/plone/stringinterp/__init__.py index eb6d7c9..32d03e1 100644 --- a/plone/stringinterp/__init__.py +++ b/plone/stringinterp/__init__.py @@ -1,4 +1 @@ from .dollarReplace import Interpolator - -from zope.i18nmessageid import MessageFactory -_ = MessageFactory('plone') diff --git a/plone/stringinterp/adapters.py b/plone/stringinterp/adapters.py index db55e43..9209b72 100644 --- a/plone/stringinterp/adapters.py +++ b/plone/stringinterp/adapters.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 """ adapters.py @@ -7,43 +6,38 @@ Copyright (c) 2009 Plone Foundation. """ -from zope.interface import implementer, Interface, alsoProvides -from zope.component import adapter -from zope.i18n import translate - from AccessControl import Unauthorized -from Acquisition import aq_inner, aq_parent, aq_get, Implicit - from AccessControl.interfaces import IRoleManager -from Products.PlonePAS.interfaces.group import IGroupData -from Products.CMFCore.utils import getToolByName -from Products.CMFCore.interfaces import ( - IContentish, IMinimalDublinCore, IWorkflowAware, IDublinCore, - ICatalogableDublinCore) -from Products.CMFCore.WorkflowCore import WorkflowException -from Products.CMFPlone.utils import safe_unicode -from Products.CMFPlone.i18nl10n import ulocalized_time - +from Acquisition import aq_get +from Acquisition import aq_inner +from Acquisition import aq_parent +from Acquisition import Implicit +from plone.base import PloneMessageFactory as _ +from plone.base.i18nl10n import ulocalized_time +from plone.base.utils import safe_text from plone.memoize.request import memoize_diy_request - -from plone.stringinterp import _ -from plone.stringinterp.interfaces import IStringSubstitution from plone.stringinterp.interfaces import IContextWrapper - -# BBB: on Zope 5 the fallback will not work anymore, -# as long as we want compatibility with Zope 4 and earlier -# we need to keep the fallback -try: - from zope.component.hooks import getSite -except: - from zope.site.hooks import getSite - +from plone.stringinterp.interfaces import IStringSubstitution +from Products.CMFCore.interfaces import ICatalogableDublinCore +from Products.CMFCore.interfaces import IContentish +from Products.CMFCore.interfaces import IDublinCore +from Products.CMFCore.interfaces import IMinimalDublinCore +from Products.CMFCore.interfaces import IWorkflowAware +from Products.CMFCore.utils import getToolByName +from Products.CMFCore.WorkflowCore import WorkflowException +from Products.PlonePAS.interfaces.group import IGroupData +from zope.component import adapter +from zope.component.hooks import getSite +from zope.i18n import translate +from zope.interface import alsoProvides +from zope.interface import implementer +from zope.interface import Interface @implementer(IContextWrapper) class ContextWrapper(Implicit): - """ Wrapper for context - """ + """Wrapper for context""" + def __init__(self, context): self.context = context @@ -66,9 +60,9 @@ def __call__(self, **kwargs): @implementer(IStringSubstitution) -class BaseSubstitution(object): - """ Base substitution - """ +class BaseSubstitution: + """Base substitution""" + def __init__(self, context): if IContextWrapper.providedBy(context): self.wrapper = context @@ -82,16 +76,15 @@ def __init__(self, context): # not generating unauth exceptions or returning non-unicode. def __call__(self): try: - return safe_unicode(self.safe_call()) + return safe_text(self.safe_call()) except Unauthorized: - return _(u'Unauthorized') + return _("Unauthorized") @adapter(IContentish) class UrlSubstitution(BaseSubstitution): - - category = _(u'All Content') - description = _(u'URL') + category = _("All Content") + description = _("URL") def safe_call(self): return self.context.absolute_url() @@ -99,9 +92,8 @@ def safe_call(self): @adapter(Interface) class ParentIdSubstitution(BaseSubstitution): - - category = _(u'All Content') - description = _(u"Identifier of the parent content or login of managed user") + category = _("All Content") + description = _("Identifier of the parent content or login of managed user") def safe_call(self): return aq_parent(self.context).getId() @@ -109,9 +101,8 @@ def safe_call(self): @adapter(Interface) class IdSubstitution(BaseSubstitution): - - category = _(u'All Content') - description = _(u"Identifier of the content or login of managed user") + category = _("All Content") + description = _("Identifier of the content or login of managed user") def safe_call(self): return self.context.getId() @@ -119,19 +110,17 @@ def safe_call(self): @adapter(IContentish) class ParentUrlSubstitution(BaseSubstitution): - - category = _(u'All Content') - description = _(u"Folder URL") + category = _("All Content") + description = _("Folder URL") def safe_call(self): - return aq_get(aq_parent(self.context), 'absolute_url')() + return aq_get(aq_parent(self.context), "absolute_url")() @adapter(IMinimalDublinCore) class TitleSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Title') + category = _("Dublin Core") + description = _("Title") def safe_call(self): return self.context.Title() @@ -139,19 +128,17 @@ def safe_call(self): @adapter(IContentish) class ParentTitleSubstitution(BaseSubstitution): - - category = _(u'All Content') - description = _(u'Parent title') + category = _("All Content") + description = _("Parent title") def safe_call(self): - return aq_get(aq_parent(self.context), 'Title')() + return aq_get(aq_parent(self.context), "Title")() @adapter(IMinimalDublinCore) class DescriptionSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Description') + category = _("Dublin Core") + description = _("Description") def safe_call(self): return self.context.Description() @@ -159,9 +146,8 @@ def safe_call(self): @adapter(IMinimalDublinCore) class TypeSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Content Type') + category = _("Dublin Core") + description = _("Content Type") def safe_call(self): return translate(self.context.Type(), context=self.context.REQUEST) @@ -169,33 +155,31 @@ def safe_call(self): @adapter(IDublinCore) class CreatorSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Creator Id') + category = _("Dublin Core") + description = _("Creator Id") def safe_call(self): for creator in self.context.listCreators(): return creator - return '' + return "" @adapter(IContentish) class CreatorFullNameSubstitution(CreatorSubstitution): - - category = _(u'Dublin Core') - description = _(u'Creator Full Name') + category = _("Dublin Core") + description = _("Creator Full Name") def safe_call(self): - creator = super(CreatorFullNameSubstitution, self).safe_call() + creator = super().safe_call() if not creator: - return '' + return "" pm = getToolByName(self.context, "portal_membership") member = pm.getMemberById(creator) if not member: return creator - fname = member.getProperty('fullname', None) + fname = member.getProperty("fullname", None) if not fname: return creator return fname @@ -203,39 +187,36 @@ def safe_call(self): @adapter(IDublinCore) class CreatorEmailSubstitution(CreatorSubstitution): - - category = _(u'Dublin Core') - description = _(u'Creator E-Mail') + category = _("Dublin Core") + description = _("Creator E-Mail") def safe_call(self): - creator = super(CreatorEmailSubstitution, self).safe_call() + creator = super().safe_call() if not creator: - return '' + return "" pm = getToolByName(self.context, "portal_membership") member = pm.getMemberById(creator) if not member: - return '' - email = member.getProperty('email', '') + return "" + email = member.getProperty("email", "") if not email: - return '' + return "" return email @adapter(IDublinCore) class CreatorsSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Creators Ids') + category = _("Dublin Core") + description = _("Creators Ids") def safe_call(self): - return ', '.join(self.context.listCreators()) + return ", ".join(self.context.listCreators()) @adapter(IDublinCore) class CreatorsEmailsSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Creators E-Mails') + category = _("Dublin Core") + description = _("Creators E-Mails") def safe_call(self): creators = self.context.listCreators() @@ -245,28 +226,26 @@ def safe_call(self): member = pm.getMemberById(creator) if not member: continue - email = member.getProperty('email', None) + email = member.getProperty("email", None) if not email: continue emails.append(email) - return ', '.join(emails) + return ", ".join(emails) @adapter(IDublinCore) class ContributorsSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Contributors Ids') + category = _("Dublin Core") + description = _("Contributors Ids") def safe_call(self): - return ', '.join(self.context.listContributors()) + return ", ".join(self.context.listContributors()) @adapter(IDublinCore) class ContributorsEmailsSubstitution(BaseSubstitution): - - category = (u'Dublin Core') - description = _(u'Contributors E-Mails') + category = "Dublin Core" + description = _("Contributors E-Mails") def safe_call(self): contributors = self.context.listContributors() @@ -276,102 +255,93 @@ def safe_call(self): member = pm.getMemberById(contributor) if not member: continue - email = member.getProperty('email', None) + email = member.getProperty("email", None) if not email: continue emails.append(email) - return ', '.join(emails) + return ", ".join(emails) @adapter(IDublinCore) class SubjectSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Subject') + category = _("Dublin Core") + description = _("Subject") def safe_call(self): - return ', '.join(self.context.Subject()) + return ", ".join(self.context.Subject()) @adapter(IDublinCore) class FormatSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Format') + category = _("Dublin Core") + description = _("Format") def safe_call(self): - return self.context.Format() + return self.context.Format() @adapter(IDublinCore) class LanguageSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Language') + category = _("Dublin Core") + description = _("Language") def safe_call(self): - return self.context.Language() + return self.context.Language() @adapter(IDublinCore) class IdentifierSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Identifier, actually URL of the content') + category = _("Dublin Core") + description = _("Identifier, actually URL of the content") def safe_call(self): - return self.context.Identifier() + return self.context.Identifier() @adapter(IDublinCore) class RightsSubstitution(BaseSubstitution): - - category = _(u'Dublin Core') - description = _(u'Rights') + category = _("Dublin Core") + description = _("Rights") def safe_call(self): - return self.context.Rights() + return self.context.Rights() @adapter(IWorkflowAware) class ReviewStateSubstitution(BaseSubstitution): - - category = _(u'Workflow') - description = _(u'Review State') + category = _("Workflow") + description = _("Review State") def safe_call(self): - wft = getToolByName(self.context, 'portal_workflow') - return wft.getInfoFor(self.context, 'review_state') + wft = getToolByName(self.context, "portal_workflow") + return wft.getInfoFor(self.context, "review_state") @adapter(IWorkflowAware) class ReviewStateTitleSubstitution(BaseSubstitution): - - category = _(u'Workflow') - description = _(u'Review State Title') + category = _("Workflow") + description = _("Review State Title") def safe_call(self): - wft = getToolByName(self.context, 'portal_workflow') - review_state = wft.getInfoFor(self.context, 'review_state') - return _(wft.getTitleForStateOnType(review_state, - self.context.portal_type)) + wft = getToolByName(self.context, "portal_workflow") + review_state = wft.getInfoFor(self.context, "review_state") + return _(wft.getTitleForStateOnType(review_state, self.context.portal_type)) class DateSubstitution(BaseSubstitution): - def formatDate(self, adate): try: - return safe_unicode( - ulocalized_time(adate, long_format=True, context=self.context)) + return safe_text( + ulocalized_time(adate, long_format=True, context=self.context) + ) except ValueError: - return u'???' + return "???" @adapter(ICatalogableDublinCore) class CreatedSubstitution(DateSubstitution): - - category = _(u'Dublin Core') - description = _(u'Date Created') + category = _("Dublin Core") + description = _("Date Created") def safe_call(self): return self.formatDate(self.context.created()) @@ -379,9 +349,8 @@ def safe_call(self): @adapter(ICatalogableDublinCore) class EffectiveSubstitution(DateSubstitution): - - category = _(u'Dublin Core') - description = _(u'Date Effective') + category = _("Dublin Core") + description = _("Date Effective") def safe_call(self): return self.formatDate(self.context.effective()) @@ -389,9 +358,8 @@ def safe_call(self): @adapter(ICatalogableDublinCore) class ExpiresSubstitution(DateSubstitution): - - category = _(u'Dublin Core') - description = _(u'Date Expires') + category = _("Dublin Core") + description = _("Date Expires") def safe_call(self): return self.formatDate(self.context.expires()) @@ -399,9 +367,8 @@ def safe_call(self): @adapter(ICatalogableDublinCore) class ModifiedSubstitution(DateSubstitution): - - category = _(u'Dublin Core') - description = _(u'Date Modified') + category = _("Dublin Core") + description = _("Date Modified") def safe_call(self): return self.formatDate(self.context.modified()) @@ -410,9 +377,8 @@ def safe_call(self): # A base class for adapters that need member information @adapter(IContentish) class MemberSubstitution(BaseSubstitution): - def __init__(self, context): - super(MemberSubstitution, self).__init__(context) + super().__init__(context) self.mtool = getToolByName(self.context, "portal_membership") self.gtool = getToolByName(self.context, "portal_groups") @@ -441,35 +407,35 @@ def getPropsForIds(self, ids, propname): return self.getPropsForMembers(self.getMembersFromIds(ids), propname) -# A base class for all the role->email list adapters +# A base class for all the role to email list adapters @adapter(IContentish) class MailAddressSubstitution(MemberSubstitution): - def getEmailsForRole(self, role): - portal = getSite() - acl_users = getToolByName(portal, 'acl_users') + acl_users = getToolByName(portal, "acl_users") # get a set of ids of members with the global role - ids = set([p[0] for p in acl_users.portal_role_manager.listAssignedPrincipals(role)]) + ids = {p[0] for p in acl_users.portal_role_manager.listAssignedPrincipals(role)} # union with set of ids of members with the local role - ids |= set([id for id, irole - in acl_users._getAllLocalRoles(self.context).items() - if role in irole]) + ids |= { + id + for id, irole in acl_users._getAllLocalRoles(self.context).items() + if role in irole + } # get members from group or member ids members = _recursiveGetMembersFromIds(portal, ids) # get emails - return u', '.join(self.getPropsForMembers(members, 'email')) + return ", ".join(self.getPropsForMembers(members, "email")) def _recursiveGetMembersFromIds(portal, group_and_user_ids): - """ get members from a list of group and member ids """ + """get members from a list of group and member ids""" - gtool = getToolByName(portal, 'portal_groups') - mtool = getToolByName(portal, 'portal_membership') + gtool = getToolByName(portal, "portal_groups") + mtool = getToolByName(portal, "portal_membership") members = set() seen = set() @@ -488,7 +454,8 @@ def recursiveGetGroupUsers(mtool, gtool, seen, group): seen.add(group_or_user_id) users = users.union( - recursiveGetGroupUsers(mtool, gtool, seen, group_or_user)) + recursiveGetGroupUsers(mtool, gtool, seen, group_or_user) + ) elif group_or_user is not None: # Other group data PAS plugins might not filter no # longer existing group members. @@ -510,110 +477,100 @@ def recursiveGetGroupUsers(mtool, gtool, seen, group): class OwnerEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Owners E-Mails') + category = _("Local Roles") + description = _("Owners E-Mails") def safe_call(self): - return self.getEmailsForRole('Owner') + return self.getEmailsForRole("Owner") class ReviewerEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Reviewers E-Mails') + category = _("Local Roles") + description = _("Reviewers E-Mails") def safe_call(self): - return self.getEmailsForRole('Reviewer') + return self.getEmailsForRole("Reviewer") class ReaderEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Readers E-Mails') + category = _("Local Roles") + description = _("Readers E-Mails") def safe_call(self): - return self.getEmailsForRole('Reader') + return self.getEmailsForRole("Reader") class ContributorEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Contributors E-Mails') + category = _("Local Roles") + description = _("Contributors E-Mails") def safe_call(self): - return self.getEmailsForRole('Contributor') + return self.getEmailsForRole("Contributor") class EditorEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Editors E-Mails') + category = _("Local Roles") + description = _("Editors E-Mails") def safe_call(self): - return self.getEmailsForRole('Editor') + return self.getEmailsForRole("Editor") class ManagerEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Managers E-Mails') + category = _("Local Roles") + description = _("Managers E-Mails") def safe_call(self): - return self.getEmailsForRole('Manager') + return self.getEmailsForRole("Manager") class MemberEmailSubstitution(MailAddressSubstitution): - - category = _(u'Local Roles') - description = _(u'Members E-Mails') + category = _("Local Roles") + description = _("Members E-Mails") def safe_call(self): - return self.getEmailsForRole('Member') + return self.getEmailsForRole("Member") @adapter(IContentish) class UserEmailSubstitution(BaseSubstitution): - - category = _(u'Current User') - description = _(u'E-Mail Address') + category = _("Current User") + description = _("E-Mail Address") def safe_call(self): pm = getToolByName(self.context, "portal_membership") if not pm.isAnonymousUser(): user = pm.getAuthenticatedMember() if user is not None: - email = user.getProperty('email', None) + email = user.getProperty("email", None) if email: return email - return u'' + return "" @adapter(IContentish) class UserFullNameSubstitution(BaseSubstitution): - - category = _(u'Current User') - description = _(u'Full Name') + category = _("Current User") + description = _("Full Name") def safe_call(self): pm = getToolByName(self.context, "portal_membership") if not pm.isAnonymousUser(): user = pm.getAuthenticatedMember() if user is not None: - fname = user.getProperty('fullname', None) + fname = user.getProperty("fullname", None) if fname: return fname else: return user.getId() - return u'' + return "" @adapter(IContentish) class UserIdSubstitution(BaseSubstitution): - - category = _(u'Current User') - description = _(u'Id') + category = _("Current User") + description = _("Id") def safe_call(self): pm = getToolByName(self.context, "portal_membership") @@ -621,7 +578,7 @@ def safe_call(self): user = pm.getAuthenticatedMember() if user is not None: return user.getId() - return u'' + return "" # memoize on the request an expensive function called by the @@ -635,31 +592,33 @@ def _lastChange(request, context): elif not last_revision: return workflow_change - if workflow_change and last_revision and \ - workflow_change.get('time') > last_revision.get('time'): + if ( + workflow_change + and last_revision + and workflow_change.get("time") > last_revision.get("time") + ): return workflow_change return last_revision def _lastWorkflowChange(context): - workflow = getToolByName(context, 'portal_workflow') + workflow = getToolByName(context, "portal_workflow") try: - review_history = workflow.getInfoFor(context, 'review_history') + review_history = workflow.getInfoFor(context, "review_history") except WorkflowException: return {} # filter out automatic transitions. - review_history = [r for r in review_history if r['action']] + review_history = [r for r in review_history if r["action"]] if review_history: r = review_history[-1] - r['type'] = 'workflow' - r['transition_title'] = \ - workflow.getTitleForTransitionOnType( - r['action'], - context.portal_type) - r['actorid'] = r['actor'] + r["type"] = "workflow" + r["transition_title"] = workflow.getTitleForTransitionOnType( + r["action"], context.portal_type + ) + r["actorid"] = r["actor"] else: r = {} @@ -674,17 +633,18 @@ def _lastRevision(context): if history: # history = ImplicitAcquisitionWrapper(history, pa) meta = history.retrieve( - history.getLength(countPurged=False)-1, - countPurged=False, - )['metadata']['sys_metadata'] - return dict(type='versioning', - action=_(u"edit"), - transition_title=_(u"Edit"), - actorid=meta["principal"], - time=meta["timestamp"], - comments=meta['comment'], - review_state=meta["review_state"], - ) + history.getLength(countPurged=False) - 1, + countPurged=False, + )["metadata"]["sys_metadata"] + return dict( + type="versioning", + action=_("edit"), + transition_title=_("Edit"), + actorid=meta["principal"], + time=meta["timestamp"], + comments=meta["comment"], + review_state=meta["review_state"], + ) return {} @@ -692,70 +652,63 @@ def _lastRevision(context): # a base class for substitutions that use # last revision or workflow information class ChangeSubstitution(BaseSubstitution): - def lastChangeMetadata(self, id): - return _lastChange(self.context.REQUEST, self.context).get(id, '') + return _lastChange(self.context.REQUEST, self.context).get(id, "") @adapter(IContentish) class LastChangeCommentSubstitution(ChangeSubstitution): - - category = _(u'History') - description = _(u'Comment') + category = _("History") + description = _("Comment") def safe_call(self): - return self.lastChangeMetadata('comments') + return self.lastChangeMetadata("comments") @adapter(IContentish) class LastChangeTitleSubstitution(ChangeSubstitution): - - category = _(u'History') - description = _(u'Transition title') + category = _("History") + description = _("Transition title") def safe_call(self): - return self.lastChangeMetadata('transition_title') + return self.lastChangeMetadata("transition_title") @adapter(IContentish) class LastChangeTypeSubstitution(ChangeSubstitution): - - category = _(u'History') - description = _(u'Change type') + category = _("History") + description = _("Change type") def safe_call(self): - return self.lastChangeMetadata('type') + return self.lastChangeMetadata("type") @adapter(IContentish) class LastChangeActorIdSubstitution(ChangeSubstitution): - - category = _(u'History') - description = _(u'Change author') + category = _("History") + description = _("Change author") def safe_call(self): - return self.lastChangeMetadata('actorid') + return self.lastChangeMetadata("actorid") class PortalSubstitution(BaseSubstitution): - - category = _(u'Portal') + category = _("Portal") def __init__(self, context): BaseSubstitution.__init__(self, context) - self.portal = getToolByName(self.context, 'portal_url').getPortalObject() + self.portal = getToolByName(self.context, "portal_url").getPortalObject() -class PortalURLSubstitution(PortalSubstitution): - description = _(u'Portal URL') +class PortalURLSubstitution(PortalSubstitution): + description = _("Portal URL") def safe_call(self): return self.portal.absolute_url() class PortalTitleSubstitution(PortalSubstitution): - - description = _(u'Portal title') + description = _("Portal title") def safe_call(self): return self.portal.Title() diff --git a/plone/stringinterp/browser.py b/plone/stringinterp/browser.py index b98801f..d1fcd87 100644 --- a/plone/stringinterp/browser.py +++ b/plone/stringinterp/browser.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 """ interfaces.py @@ -7,12 +6,10 @@ Copyright (c) 2009 Plone Foundation. """ -from zope.component import getSiteManager - -from Products.Five import BrowserView - +from plone.base import PloneMessageFactory as _ from plone.stringinterp.interfaces import IStringSubstitution -from plone.stringinterp import _ +from Products.Five import BrowserView +from zope.component import getSiteManager def find_adapters(reg): @@ -45,10 +42,9 @@ def substitutionList(self): categories = {} for a in find_adapters(getSiteManager()): id = a.name - cat = getattr(a.factory, 'category', _(u'Miscellaneous')) - desc = getattr(a.factory, 'description', u'') - categories.setdefault(cat, []).append( - {'id': id, 'description': desc}) + cat = getattr(a.factory, "category", _("Miscellaneous")) + desc = getattr(a.factory, "description", "") + categories.setdefault(cat, []).append({"id": id, "description": desc}) # rearrange again into a sorted list res = [] @@ -57,7 +53,7 @@ def substitutionList(self): for key in sorted(keys, key=lambda s: s.lower()): acat = categories[key] # sort by id, ignoring case - acat = sorted(acat, key=lambda i: i['id'].lower()) - res.append({'category': key, 'items': acat}) + acat = sorted(acat, key=lambda i: i["id"].lower()) + res.append({"category": key, "items": acat}) return res diff --git a/plone/stringinterp/configure.zcml b/plone/stringinterp/configure.zcml index 3736b7a..4a259ab 100644 --- a/plone/stringinterp/configure.zcml +++ b/plone/stringinterp/configure.zcml @@ -1,322 +1,323 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:browser="http://namespaces.zope.org/browser" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plone/stringinterp/dollarReplace.py b/plone/stringinterp/dollarReplace.py index df97aed..3338698 100644 --- a/plone/stringinterp/dollarReplace.py +++ b/plone/stringinterp/dollarReplace.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 """ dollarReplace.py @@ -7,29 +6,30 @@ Copyright (c) 2009 Plone Foundation. """ -import string - -from zope.interface import implementer -from zope.component import adapts, getAdapter, ComponentLookupError - from AccessControl import Unauthorized - +from plone.stringinterp.interfaces import IStringInterpolator +from plone.stringinterp.interfaces import IStringSubstitution from Products.CMFCore.interfaces import IContentish +from zope.component import adapter +from zope.component import ComponentLookupError +from zope.component import getAdapter +from zope.interface import implementer + +import string -from plone.stringinterp.interfaces import IStringSubstitution, IStringInterpolator +_marker = "_bad_" -_marker = u'_bad_' -class LazyDict(object): - """ cached lookup via adapter """ +class LazyDict: + """cached lookup via adapter""" def __init__(self, context): self.context = context self._cache = {} def __getitem__(self, key): - if key and key[0] not in ['_', '.']: + if key and key[0] not in ["_", "."]: res = self._cache.get(key) if res is None: try: @@ -37,7 +37,7 @@ def __getitem__(self, key): except ComponentLookupError: res = _marker except Unauthorized: - res = u'Unauthorized' + res = "Unauthorized" self._cache[key] = res @@ -48,9 +48,8 @@ def __getitem__(self, key): @implementer(IStringInterpolator) -class Interpolator(object): - adapts(IContentish) - +@adapter(IContentish) +class Interpolator: def __init__(self, context): self._ldict = LazyDict(context) diff --git a/plone/stringinterp/interfaces.py b/plone/stringinterp/interfaces.py index c6e8a1f..dfaad3e 100644 --- a/plone/stringinterp/interfaces.py +++ b/plone/stringinterp/interfaces.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 """ interfaces.py @@ -12,34 +11,34 @@ class IStringSubstitution(Interface): """ - provides callable returning the substitution + provides callable returning the substitution - if you would like your substitution listed - in lists, provide name, description and category - class attributes + if you would like your substitution listed + in lists, provide name, description and category + class attributes """ def __call__(): """ - return substitution + return substitution """ class IStringInterpolator(Interface): """ - provides callable returning - interpolated string + provides callable returning + interpolated string """ def __call__(): """ - return interpolated string + return interpolated string """ class IStringSubstitutionInfo(Interface): """ - provides information on available IStringSubstitution adapters + provides information on available IStringSubstitution adapters """ def substitutionList(): @@ -49,18 +48,18 @@ def substitutionList(): [{'id':subId, 'description':subDescription}, ...]), ... ] """ + class IContextWrapper(Interface): """ - Wrap context in order to be able to provide custom strings - not stored on context + Wrap context in order to be able to provide custom strings + not stored on context - Usage: + Usage: - >>> wrapper = IContextWrapper(obj)(m1='A message', m2="Another one") - >>> notify(CustomEvent(wrapper)) + >>> wrapper = IContextWrapper(obj)(m1='A message', m2="Another one") + >>> notify(CustomEvent(wrapper)) """ def __call__(kwargs): - """" - Return wrapped context - """ + """ " + Return wrapped context""" diff --git a/plone/stringinterp/tests/__init__.py b/plone/stringinterp/tests/__init__.py index 4287ca8..792d600 100644 --- a/plone/stringinterp/tests/__init__.py +++ b/plone/stringinterp/tests/__init__.py @@ -1 +1 @@ -# \ No newline at end of file +# diff --git a/plone/stringinterp/tests/testDocTests.py b/plone/stringinterp/tests/testDocTests.py index 98e185f..79a2481 100644 --- a/plone/stringinterp/tests/testDocTests.py +++ b/plone/stringinterp/tests/testDocTests.py @@ -1,38 +1,29 @@ +from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_INTEGRATION_TESTING from plone.testing import layered -# from plone.app.testing import PLONE_INTEGRATION_TESTING -from plone.app.contenttypes.testing import ( - PLONE_APP_CONTENTTYPES_INTEGRATION_TESTING -) + import doctest -import re -import six import unittest testfiles = ( - 'substitutionTests.txt', - 'moreSubstitutionTests.txt','' - 'wrapperTests.txt', - 'interpolationTests.txt', + "substitutionTests.txt", + "moreSubstitutionTests.txt", + "wrapperTests.txt", + "interpolationTests.txt", ) -class Py23DocChecker(doctest.OutputChecker): - def check_output(self, want, got, optionflags): - if six.PY2: - got = re.sub("u'(.*?)'", "'\\1'", got) - return doctest.OutputChecker.check_output(self, want, got, optionflags) - - def test_suite(): - return unittest.TestSuite([ - layered( - doctest.DocFileSuite( - f, - package='plone.stringinterp.tests', - optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS, - checker=Py23DocChecker(), - ), - layer=PLONE_APP_CONTENTTYPES_INTEGRATION_TESTING, - ) for f in testfiles - ]) + return unittest.TestSuite( + [ + layered( + doctest.DocFileSuite( + test_file, + package="plone.stringinterp.tests", + optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS, + ), + layer=PLONE_APP_CONTENTTYPES_INTEGRATION_TESTING, + ) + for test_file in testfiles + ] + ) diff --git a/setup.cfg b/setup.cfg index 2a9acf1..3293a61 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [bdist_wheel] -universal = 1 +universal = 0 + +[isort] +profile = plone \ No newline at end of file diff --git a/setup.py b/setup.py index 9da3d1c..d2d9eda 100644 --- a/setup.py +++ b/setup.py @@ -1,51 +1,49 @@ -from setuptools import setup, find_packages +from setuptools import find_packages +from setuptools import setup -version = '1.3.4.dev0' + +version = "2.0.0.dev0" setup( - name='plone.stringinterp', + name="plone.stringinterp", version=version, description="Adaptable string interpolation", - long_description=(open("README.rst").read() + "\n" + - open("CHANGES.rst").read()), + long_description=(open("README.rst").read() + "\n" + open("CHANGES.rst").read()), classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Plone", - "Framework :: Plone :: 5.0", - "Framework :: Plone :: 5.1", - "Framework :: Plone :: 5.2", + "Framework :: Plone :: 6.0", "Framework :: Plone :: Core", - "Framework :: Zope2", - "Framework :: Zope :: 4", + "Framework :: Zope :: 5", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], - keywords='Plone Zope Interpolation', - author='Plone Foundation', - author_email='plone-developers@lists.sourceforge.net', - url='https://pypi.org/project/plone.stringinterp', - license='GPL version 2', + keywords="Plone Zope Interpolation", + author="Plone Foundation", + author_email="plone-developers@lists.sourceforge.net", + url="https://github.com/plone/plone.stringinterp", + license="GPL version 2", packages=find_packages(), - namespace_packages=['plone'], + namespace_packages=["plone"], include_package_data=True, zip_safe=False, install_requires=[ - 'setuptools', - 'Products.CMFCore', - 'zope.i18n', - 'plone.memoize', + "setuptools", + "plone.base", + "plone.memoize", + "zope.i18n", ], extras_require={ - 'test': [ - 'plone.app.contenttypes', - 'plone.app.testing', - 'plone.testing', + "test": [ + "plone.app.contenttypes", + "plone.app.testing", + "plone.testing", ], }, entry_points="""