From 6d43d37ca5c77ce63f102497af23c20b4e0e080e Mon Sep 17 00:00:00 2001 From: vangheem Date: Tue, 28 Jul 2015 10:28:40 -0500 Subject: [PATCH] [fc] Repository: plone.app.theming Branch: refs/heads/master Date: 2015-07-28T15:40:54+02:00 Author: Gagaro (Gagaro) Commit: https://github.com/plone/plone.app.theming/commit/ed7289f3b6534f5d96b3e1e1b978f72b72144b70 fix: copy theme with dot in name Files changed: M src/plone/app/theming/browser/controlpanel.pt Repository: plone.app.theming Branch: refs/heads/master Date: 2015-07-28T10:28:40-05:00 Author: Nathan Van Gheem (vangheem) Commit: https://github.com/plone/plone.app.theming/commit/f53e14ac37a807b377e761173bc40fd9396ee47e Merge pull request #69 from Gagaro/master fix: copy theme with dot in name Files changed: M src/plone/app/theming/browser/controlpanel.pt --- last_commit.txt | 3776 +---------------------------------------------- 1 file changed, 47 insertions(+), 3729 deletions(-) diff --git a/last_commit.txt b/last_commit.txt index 117ce0ae68..fcba904e16 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,3750 +1,68 @@ -Repository: Products.CMFQuickInstallerTool +Repository: plone.app.theming Branch: refs/heads/master -Date: 2015-07-28T13:52:21+02:00 -Author: Jens W. Klein (jensens) -Commit: https://github.com/plone/Products.CMFQuickInstallerTool/commit/37e1f8868976d44c5bead6af99d12f73a8e8d16a +Date: 2015-07-28T15:40:54+02:00 +Author: Gagaro (Gagaro) +Commit: https://github.com/plone/plone.app.theming/commit/ed7289f3b6534f5d96b3e1e1b978f72b72144b70 -cleanup: pep8 et al +fix: copy theme with dot in name Files changed: -M CHANGES.rst -M Products/CMFQuickInstallerTool/InstalledProduct.py -M Products/CMFQuickInstallerTool/QuickInstallerTool.py -M Products/CMFQuickInstallerTool/__init__.py -M Products/CMFQuickInstallerTool/events.py -M Products/CMFQuickInstallerTool/interfaces/installable.py -M Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -M Products/CMFQuickInstallerTool/tests/__init__.py -M Products/CMFQuickInstallerTool/tests/testSetup.py -M Products/CMFQuickInstallerTool/tests/test_install.py -M Products/CMFQuickInstallerTool/utils.py -M Products/__init__.py -M README.rst -M setup.py +M src/plone/app/theming/browser/controlpanel.pt -diff --git a/CHANGES.rst b/CHANGES.rst -index 91e6f85..dc3522b 100644 ---- a/CHANGES.rst -+++ b/CHANGES.rst -@@ -4,7 +4,8 @@ Changelog - 3.0.10 (unreleased) - ------------------- +diff --git a/src/plone/app/theming/browser/controlpanel.pt b/src/plone/app/theming/browser/controlpanel.pt +index 3058cbe..bbd5c96 100644 +--- a/src/plone/app/theming/browser/controlpanel.pt ++++ b/src/plone/app/theming/browser/controlpanel.pt +@@ -186,11 +186,11 @@ + + + Copy + +
++ tal:attributes="id python:'modal-copy-{0}'.format(theme['name'].replace('.', '-'))"> +

Create copy of

--- Nothing changed yet. -+- Cleanup: PEP8, decorators for security+zca, et al. -+ [jensens] - - - 3.0.9 (2015-06-15) -diff --git a/Products/CMFQuickInstallerTool/InstalledProduct.py b/Products/CMFQuickInstallerTool/InstalledProduct.py -index b9fae50..a466fd9 100644 ---- a/Products/CMFQuickInstallerTool/InstalledProduct.py -+++ b/Products/CMFQuickInstallerTool/InstalledProduct.py -@@ -1,24 +1,22 @@ --import logging --from zope.interface import implements --from zope.component import getSiteManager --from zope.component import queryUtility -- -+# -*- coding: utf-8 -*- - from AccessControl import ClassSecurityInfo - from Acquisition import aq_base --from DateTime import DateTime - from App.class_init import InitializeClass -+from DateTime import DateTime - from OFS.SimpleItem import SimpleItem -- --from Products.CMFCore.utils import getToolByName - from Products.CMFCore.permissions import ManagePortal -+from Products.CMFCore.utils import getToolByName -+from Products.CMFQuickInstallerTool.interfaces.portal_quickinstaller import IInstalledProduct # noqa -+from Products.CMFQuickInstallerTool.utils import delObjects -+from Products.CMFQuickInstallerTool.utils import get_install_method -+from Products.CMFQuickInstallerTool.utils import get_method -+from Products.CMFQuickInstallerTool.utils import updatelist - from Products.GenericSetup.utils import _resolveDottedName - from Products.PageTemplates.PageTemplateFile import PageTemplateFile -- --from Products.CMFQuickInstallerTool.interfaces.portal_quickinstaller \ -- import IInstalledProduct --from Products.CMFQuickInstallerTool.utils import get_method --from Products.CMFQuickInstallerTool.utils import get_install_method --from Products.CMFQuickInstallerTool.utils import updatelist, delObjects -+from zope.component import getSiteManager -+from zope.component import queryUtility -+from zope.interface import implementer -+import logging - - logger = logging.getLogger('CMFQuickInstallerTool') - -@@ -28,6 +26,7 @@ - ) - - -+@implementer(IInstalledProduct) - class InstalledProduct(SimpleItem): - """Class storing information about an installed product - -@@ -38,13 +37,12 @@ class InstalledProduct(SimpleItem): - >>> verifyClass(IInstalledProduct, InstalledProduct) - True - """ -- implements(IInstalledProduct) - - meta_type = "Installed Product" - - manage_options = ( - {'label': 'View', 'action': 'manage_installationInfo'}, -- ) + SimpleItem.manage_options -+ ) + SimpleItem.manage_options - - security = ClassSecurityInfo() - -@@ -72,8 +70,7 @@ def __init__(self, id): - for key in DEFAULT_CASCADE: - setattr(self, key, []) - -- security.declareProtected(ManagePortal, 'update') -- -+ @security.protected(ManagePortal) - def update(self, settings, installedversion='', logmsg='', - status='installed', error=False, locked=False, hidden=False, - afterid=None, beforeid=None): -@@ -102,117 +99,97 @@ def update(self, settings, installedversion='', logmsg='', - - self.error = error - -- security.declareProtected(ManagePortal, 'log') -- -+ @security.protected(ManagePortal) - def log(self, logmsg): - """Adds a log to the transcript - """ - self.transcript.insert(0, {'timestamp': DateTime(), 'msg': logmsg}) - -- security.declareProtected(ManagePortal, 'hasError') -- -+ @security.protected(ManagePortal) - def hasError(self): - """Returns if the prod is in error state - """ - return getattr(self, 'error', False) - -- security.declareProtected(ManagePortal, 'isLocked') -- -+ @security.protected(ManagePortal) - def isLocked(self): - """Is the product locked for uninstall - """ - return getattr(self, 'locked', False) - -- security.declareProtected(ManagePortal, 'isHidden') -- -+ @security.protected(ManagePortal) - def isHidden(self): - """Is the product hidden - """ - return getattr(self, 'hidden', False) - -- security.declareProtected(ManagePortal, 'isVisible') -- -+ @security.protected(ManagePortal) - def isVisible(self): - return not self.isHidden() - -- security.declareProtected(ManagePortal, 'isInstalled') -- -+ @security.protected(ManagePortal) - def isInstalled(self): - return self.status == 'installed' - -- security.declareProtected(ManagePortal, 'getStatus') -- -+ @security.protected(ManagePortal) - def getStatus(self): - return self.status - -- security.declareProtected(ManagePortal, 'getTypes') -- -+ @security.protected(ManagePortal) - def getTypes(self): - return self.types - -- security.declareProtected(ManagePortal, 'getSkins') -- -+ @security.protected(ManagePortal) - def getSkins(self): - return self.skins - -- security.declareProtected(ManagePortal, 'getActions') -- -+ @security.protected(ManagePortal) - def getActions(self): - return self.actions - -- security.declareProtected(ManagePortal, 'getPortalObjects') -- -+ @security.protected(ManagePortal) - def getPortalObjects(self): - return self.portalobjects - -- security.declareProtected(ManagePortal, 'getWorkflows') -- -+ @security.protected(ManagePortal) - def getWorkflows(self): - return self.workflows - -- security.declareProtected(ManagePortal, 'getLeftSlots') -- -+ @security.protected(ManagePortal) - def getLeftSlots(self): - if getattr(self, 'leftslots', None) is None: - self.leftslots = [] - return self.leftslots - -- security.declareProtected(ManagePortal, 'getRightSlots') -- -+ @security.protected(ManagePortal) - def getRightSlots(self): - if getattr(self, 'rightslots', None) is None: - self.rightslots = [] - return self.rightslots - -- security.declareProtected(ManagePortal, 'getSlots') -- -+ @security.protected(ManagePortal) - def getSlots(self): - return self.getLeftSlots() + self.getRightSlots() - -- security.declareProtected(ManagePortal, 'getValue') -- -+ @security.protected(ManagePortal) - def getValue(self, name): - return getattr(self, name, []) - -- security.declareProtected(ManagePortal, 'getRegistryPredicates') -- -+ @security.protected(ManagePortal) - def getRegistryPredicates(self): - """Return the custom entries in the content_type_registry - """ - return getattr(self, 'registrypredicates', []) - -- security.declareProtected(ManagePortal, 'getAfterId') -- -+ @security.protected(ManagePortal) - def getAfterId(self): - return self.afterid - -- security.declareProtected(ManagePortal, 'getBeforeId') -- -+ @security.protected(ManagePortal) - def getBeforeId(self): - return self.beforeid - -- security.declareProtected(ManagePortal, 'getTranscriptAsText') -- -+ @security.protected(ManagePortal) - def getTranscriptAsText(self): - if getattr(self, 'transcript', None): - msgs = [t['timestamp'].ISO() + '\n' + str(t['msg']) -@@ -226,8 +203,7 @@ def _getMethod(self, modfunc): - """ - return get_method(self.id, modfunc) - -- security.declareProtected(ManagePortal, 'getInstallMethod') -- -+ @security.protected(ManagePortal) - def getInstallMethod(self): - """ returns the installer method """ - res = get_install_method(self.id) -@@ -237,35 +213,45 @@ def getInstallMethod(self): - else: - return res - -- security.declareProtected(ManagePortal, 'getUninstallMethod') -- -+ @security.protected(ManagePortal) - def getUninstallMethod(self): - """ returns the uninstaller method """ -- return self._getMethod((('Install', 'uninstall'), -- ('Install', 'Uninstall'), -- ('install', 'uninstall'), -- ('install', 'Uninstall'), -- )) -- -- security.declareProtected(ManagePortal, 'getAfterInstallMethod') -- -+ return self._getMethod( -+ ( -+ ('Install', 'uninstall'), -+ ('Install', 'Uninstall'), -+ ('install', 'uninstall'), -+ ('install', 'Uninstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) - def getAfterInstallMethod(self): - """ returns the after installer method """ -- return self._getMethod((('Install', 'afterInstall'), -- ('install', 'afterInstall'), -- )) -- -- security.declareProtected(ManagePortal, 'getBeforeUninstallMethod') -- -+ return self._getMethod( -+ ( -+ ('Install', 'afterInstall'), -+ ('install', 'afterInstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) - def getBeforeUninstallMethod(self): - """ returns the before uninstaller method """ -- return self._getMethod((('Install', 'beforeUninstall'), -- ('install', 'beforeUninstall'), -- )) -- -- security.declareProtected(ManagePortal, 'uninstall') -- -- def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): -+ return self._getMethod( -+ ( -+ ('Install', 'beforeUninstall'), -+ ('install', 'beforeUninstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) -+ def uninstall( -+ self, -+ cascade=default_cascade, -+ reinstall=False, -+ REQUEST=None -+ ): - """Uninstalls the product and removes its dependencies - """ - portal = getToolByName(self, 'portal_url').getPortalObject() -@@ -273,7 +259,9 @@ def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): - # TODO eventually we will land Event system and could remove - # this 'removal_inprogress' hack - if self.isLocked() and getattr(portal, 'removal_inprogress', False): -- raise ValueError('The product is locked and cannot be uninstalled!') -+ raise ValueError( -+ 'The product is locked and cannot be uninstalled!' -+ ) - - res = '' - afterRes = '' -@@ -299,8 +287,12 @@ def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): - - if beforeUninstall: - beforeUninstall = beforeUninstall.__of__(portal) -- beforeRes, cascade = beforeUninstall(portal, reinstall=reinstall, -- product=self, cascade=cascade) -+ beforeRes, cascade = beforeUninstall( -+ portal, -+ reinstall=reinstall, -+ product=self, -+ cascade=cascade -+ ) - - self._cascadeRemove(cascade) - -@@ -323,7 +315,10 @@ def _cascadeRemove(self, cascade): - portal_skins = getToolByName(self, 'portal_skins') - delObjects(portal_skins, getattr(aq_base(self), 'skins', [])) - -- if 'actions' in cascade and len(getattr(aq_base(self), 'actions', [])) > 0: -+ if ( -+ 'actions' in cascade -+ and len(getattr(aq_base(self), 'actions', [])) > 0 -+ ): - portal_actions = getToolByName(self, 'portal_actions') - for info in self.actions: - if isinstance(info, basestring): -@@ -349,26 +344,33 @@ def _cascadeRemove(self, cascade): - - if 'workflows' in cascade: - portal_workflow = getToolByName(self, 'portal_workflow') -- delObjects(portal_workflow, getattr(aq_base(self), 'workflows', [])) -+ delObjects( -+ portal_workflow, -+ getattr(aq_base(self), 'workflows', []) -+ ) - - if 'slots' in cascade: - if self.getLeftSlots(): -- portal.left_slots = [s for s in portal.left_slots -- if s not in self.getLeftSlots()] -+ portal.left_slots = [ -+ s for s in portal.left_slots -+ if s not in self.getLeftSlots() -+ ] - if self.getRightSlots(): -- portal.right_slots = [s for s in portal.right_slots -- if s not in self.getRightSlots()] -+ portal.right_slots = [ -+ s for s in portal.right_slots -+ if s not in self.getRightSlots() -+ ] - - if 'registrypredicates' in cascade: - ctr = getToolByName(self, 'content_type_registry') - ids = [id for id, predicate in ctr.listPredicates()] - predicates = getattr(aq_base(self), 'registrypredicates', []) -- for p in predicates: -- if p in ids: -- ctr.removePredicate(p) -+ for pred in predicates: -+ if pred in ids: -+ ctr.removePredicate(pred) - else: - logger.warning("Failed to delete '%s' from content type " -- "registry" % p) -+ "registry" % pred) - - if 'adapters' in cascade: - adapters = getattr(aq_base(self), 'adapters', []) -@@ -410,8 +412,7 @@ def _cascadeRemove(self, cascade): - if portal_controlpanel is not None: - portal_controlpanel.unregisterApplication(self.id) - -- security.declareProtected(ManagePortal, 'getInstalledVersion') -- -+ @security.protected(ManagePortal) - def getInstalledVersion(self): - """Return the version of the product in the moment of installation - """ -diff --git a/Products/CMFQuickInstallerTool/QuickInstallerTool.py b/Products/CMFQuickInstallerTool/QuickInstallerTool.py -index b8365e3..2602ed3 100644 ---- a/Products/CMFQuickInstallerTool/QuickInstallerTool.py -+++ b/Products/CMFQuickInstallerTool/QuickInstallerTool.py -@@ -1,49 +1,55 @@ --import logging --import os -- --import pkg_resources -- --from zope.component import getSiteManager --from zope.component import getAllUtilitiesRegisteredFor --from zope.interface import implements --from zope.annotation.interfaces import IAnnotatable --from zope.i18nmessageid import MessageFactory -- -+# -*- coding: utf-8 -*- - from AccessControl import ClassSecurityInfo - from AccessControl.requestmethod import postonly --from Acquisition import aq_base, aq_parent, aq_get, aq_inner -- --from Globals import DevelopmentMode -+from Acquisition import aq_base -+from Acquisition import aq_get -+from Acquisition import aq_inner -+from Acquisition import aq_parent - from App.class_init import InitializeClass -+from Globals import DevelopmentMode - from Globals import INSTANCE_HOME --from OFS.SimpleItem import SimpleItem - from OFS.ObjectManager import ObjectManager -- -+from OFS.SimpleItem import SimpleItem - from Products.CMFCore.permissions import ManagePortal --from Products.CMFCore.utils import UniqueObject, getToolByName --from Products.GenericSetup import EXTENSION --from Products.GenericSetup.utils import _getDottedName --from Products.PageTemplates.PageTemplateFile import PageTemplateFile -- -+from Products.CMFCore.utils import getToolByName -+from Products.CMFCore.utils import UniqueObject -+from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - from Products.CMFQuickInstallerTool.interfaces import INonInstallable - from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool --from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - from Products.CMFQuickInstallerTool.utils import get_install_method - from Products.CMFQuickInstallerTool.utils import get_packages --_ = MessageFactory("plone") -+from Products.GenericSetup import EXTENSION -+from Products.GenericSetup.utils import _getDottedName -+from Products.PageTemplates.PageTemplateFile import PageTemplateFile -+from zope.annotation.interfaces import IAnnotatable -+from zope.component import getAllUtilitiesRegisteredFor -+from zope.component import getSiteManager -+from zope.i18nmessageid import MessageFactory -+from zope.interface import implementer -+import logging -+import os -+import pkg_resources -+import warnings - - try: -- # Allow IPloneSiteRoot or ISiteRoot if we have Plone -+ pkg_resources.get_distribution('Products.CMFPlone') -+except pkg_resources.DistributionNotFound: - from Products.CMFPlone.interfaces import IPloneSiteRoot as ISiteRoot -- ISiteRoot # pyflakes --except ImportError: -+else: - from Products.CMFCore.interfaces import ISiteRoot - -+_ = MessageFactory("plone") -+ - logger = logging.getLogger('CMFQuickInstallerTool') - - # By convention the uninstall-profile is called 'uninstall' - UNINSTALL_ID = 'uninstall' - -+INSTALLED_PRODUCTS_HEADER = """ -+ Installed Products -+ ==================== -+ """ -+ - - class AlreadyInstalled(Exception): - """ Would be nice to say what Product was trying to be installed """ -@@ -58,13 +64,14 @@ def addQuickInstallerTool(self, REQUEST=None): - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - - -+@implementer(INonInstallable) - class HiddenProducts(object): -- implements(INonInstallable) - - def getNonInstallableProducts(self): - return ['CMFQuickInstallerTool', 'Products.CMFQuickInstallerTool'] - - -+@implementer(IQuickInstallerTool) - class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - """ - Let's make sure that this implementation actually fulfills the -@@ -74,7 +81,6 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - >>> verifyClass(IQuickInstallerTool, QuickInstallerTool) - True - """ -- implements(IQuickInstallerTool) - - meta_type = 'CMF QuickInstaller Tool' - id = 'portal_quickinstaller' -@@ -82,7 +88,9 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - security = ClassSecurityInfo() - - manage_options = ( -- {'label': 'Install', 'action': 'manage_installProductsForm'}, -+ { -+ 'label': 'Install', -+ 'action': 'manage_installProductsForm'}, - ) + ObjectManager.manage_options - - security.declareProtected(ManagePortal, 'manage_installProductsForm') -@@ -90,8 +98,6 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - 'forms/install_products_form', globals(), - __name__='manage_installProductsForm') - -- security = ClassSecurityInfo() -- - def __init__(self): - self.id = 'portal_quickinstaller' - -@@ -99,63 +105,60 @@ def __init__(self): - def errors(self): - return getattr(self, '_v_errors', {}) - -- security.declareProtected(ManagePortal, 'getInstallProfiles') -+ def _init_errors(self, reset=False): -+ """init or reset the list of broken products -+ """ -+ if not self.errors or reset: -+ self._v_errors = {} - -- def getInstallProfiles(self, productname): -- """ Return the installer profile id -+ def _install_profile_info(self, productname): -+ """list extension profile infos of a given name - """ - portal_setup = getToolByName(self, 'portal_setup') - profiles = portal_setup.listProfileInfo() - - # We are only interested in extension profiles for the product - # TODO Remove the manual Products.* check here. It is still needed. -- profiles = [prof['id'] for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] -- -+ profiles = [ -+ prof for prof in profiles -+ if prof['type'] == EXTENSION -+ and ( -+ prof['product'] == productname -+ or prof['product'] == 'Products.%s' % productname -+ ) -+ ] - return profiles - -- security.declareProtected(ManagePortal, 'getInstallProfile') -+ @security.protected(ManagePortal) -+ def getInstallProfiles(self, productname): -+ """ list all installer profile ids of the given name -+ """ -+ return [prof['id'] for prof in self._install_profile_info(productname)] - -+ @security.protected(ManagePortal) - def getInstallProfile(self, productname): - """ Return the installer profile - """ -- portal_setup = getToolByName(self, 'portal_setup') -- profiles = portal_setup.listProfileInfo() -- -- # We are only interested in extension profiles for the product -- profiles = [prof for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] -+ profiles = self._install_profile_info(productname) - - # XXX Currently QI always uses the first profile - if profiles: - return profiles[0] - return None - -- security.declareProtected(ManagePortal, 'getUninstallProfile') -- -+ @security.protected(ManagePortal) - def getUninstallProfile(self, productname): - """ Return the uninstaller profile id - """ -- portal_setup = getToolByName(self, 'portal_setup') -- profiles = portal_setup.listProfileInfo() -+ profiles = self._install_profile_info(productname) - -- # We are only interested in extension profiles for the product -- profiles = [prof for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] - if profiles: - for profile in profiles: - if profile['id'].split(':')[-1] == UNINSTALL_ID: - return profile - return None - -- security.declareProtected(ManagePortal, 'getInstallMethod') -- -+ @security.protected(ManagePortal) - def getInstallMethod(self, productname): - """ Return the installer method - """ -@@ -165,15 +168,13 @@ def getInstallMethod(self, productname): - 'product %s' % productname) - return res - -- security.declareProtected(ManagePortal, 'getBrokenInstalls') -- -+ @security.protected(ManagePortal) - def getBrokenInstalls(self): - """ Return all the broken installs """ - errs = getattr(self, "_v_errors", {}) - return errs.values() - -- security.declareProtected(ManagePortal, 'isProductInstallable') -- -+ @security.protected(ManagePortal) - def isProductInstallable(self, productname): - """Asks wether a product is installable by trying to get its install - method or an installation profile. -@@ -188,48 +189,60 @@ def isProductInstallable(self, productname): - self.getInstallMethod(productname) - return True - except AttributeError: -- profiles = self.getInstallProfiles(productname) -- if not profiles: -- return False -- setup_tool = getToolByName(self, 'portal_setup') -- try: -- # XXX Currently QI always uses the first profile -- setup_tool.getProfileDependencyChain(profiles[0]) -- except KeyError, e: -- if not getattr(self, "_v_errors", {}): -- self._v_errors = {} -- # Don't show twice the same error: old install and profile -- # oldinstall is test in first in other methods we may have an -- # extra 'Products.' in the namespace -- checkname = productname -- if checkname.startswith('Products.'): -- checkname = checkname[9:] -- else: -- checkname = 'Products.' + checkname -- if checkname in self._v_errors: -- if self._v_errors[checkname]['value'] == e.args[0]: -- return False -- else: -- # A new error is found, register it -- self._v_errors[productname] = dict( -- type=_(u"dependency_missing", default=u"Missing dependency"), -- value=e.args[0], -- productname=productname) -- else: -- self._v_errors[productname] = dict( -- type=_(u"dependency_missing", default=u"Missing dependency"), -- value=e.args[0], -- productname=productname) -- -- return False -+ # this means it has no install method, go on from here -+ pass - -- return True -+ profiles = self.getInstallProfiles(productname) -+ if not profiles: -+ return False - -- security.declareProtected(ManagePortal, 'isProductAvailable') -- isProductAvailable = isProductInstallable -+ setup_tool = getToolByName(self, 'portal_setup') -+ try: -+ # XXX Currently QI always uses the first profile -+ setup_tool.getProfileDependencyChain(profiles[0]) -+ except KeyError, e: -+ self._init_errors() -+ # Don't show twice the same error: old install and profile -+ # oldinstall is test in first in other methods we may have an -+ # extra 'Products.' in the namespace -+ checkname = productname -+ if checkname.startswith('Products.'): -+ checkname = checkname[9:] -+ else: -+ checkname = 'Products.' + checkname -+ if checkname in self.errors: -+ if self.errors[checkname]['value'] == e.args[0]: -+ return False -+ # A new error is found, register it -+ self.errors[productname] = dict( -+ type=_( -+ u"dependency_missing", -+ default=u"Missing dependency" -+ ), -+ value=e.args[0], -+ productname=productname -+ ) -+ else: -+ self.errors[productname] = dict( -+ type=_( -+ u"dependency_missing", -+ default=u"Missing dependency" -+ ), -+ value=e.args[0], -+ productname=productname -+ ) -+ return False -+ return True - -- security.declareProtected(ManagePortal, 'listInstallableProfiles') -+ @security.protected(ManagePortal) -+ def isProductAvailable(self, productname): -+ warnings.warn( -+ 'use instead: isProductInstallable', -+ DeprecationWarning -+ ) -+ return self.isProductInstallable(productname) - -+ @security.protected(ManagePortal) - def listInstallableProfiles(self): - """List candidate products which have a GS profiles. - """ -@@ -237,63 +250,70 @@ def listInstallableProfiles(self): - profiles = portal_setup.listProfileInfo(ISiteRoot) - - # We are only interested in extension profiles -- profiles = [prof['product'] for prof in profiles if -- prof['type'] == EXTENSION] -+ profiles = [ -+ prof['product'] for prof in profiles -+ if prof['type'] == EXTENSION -+ ] - return set(profiles) - -- security.declareProtected(ManagePortal, 'listInstallableProducts') -- -+ @security.protected(ManagePortal) - def listInstallableProducts(self, skipInstalled=True): - """List candidate CMF products for installation -> list of dicts - with keys:(id,title,hasError,status) - """ -- # reset the list of broken products -- if getattr(self, '_v_errors', True): -- self._v_errors = {} -+ self._init_errors(reset=True) - - # Returns full names with Products. prefix for all packages / products - packages = get_packages() - - pids = [] -- for p in packages: -- if not self.isProductInstallable(p): -+ for pkg in packages: -+ if not self.isProductInstallable(pkg): - continue -- if p.startswith('Products.'): -- p = p[9:] -- pids.append(p) -+ if pkg.startswith('Products.'): -+ pkg = pkg[9:] -+ pids.append(pkg) - - # Get product list from the extension profiles - profile_pids = self.listInstallableProfiles() - -- for p in profile_pids: -- if p in pids or p in packages: -+ for pp in profile_pids: -+ if pp in pids or pp in packages: - continue -- if not self.isProductInstallable(p): -+ if not self.isProductInstallable(pp): - continue -- pids.append(p) -+ pids.append(pp) - - if skipInstalled: -- installed = [p['id'] for p in self.listInstalledProducts(showHidden=True)] -+ installed = [ -+ p['id'] for p in self.listInstalledProducts(showHidden=True) -+ ] - pids = [r for r in pids if r not in installed] - - res = [] -- for r in pids: -- p = self._getOb(r, None) -- name = r -- profile = self.getInstallProfile(r) -+ for pid in pids: -+ installed_product = self._getOb(pid, None) -+ name = pid -+ profile = self.getInstallProfile(pid) - if profile: - name = profile['title'] -- if p: -- res.append({'id': r, 'title': name, 'status': p.getStatus(), -- 'hasError': p.hasError()}) -+ record = {'id': pid, 'title': name} -+ if installed_product: -+ record['status'] = installed_product.getStatus() -+ record['error'] = installed_product.hasError() - else: -- res.append({'id': r, 'title': name, 'status': 'new', 'hasError': False}) -- res.sort(lambda x, y: cmp(x.get('title', x.get('id', None)), -- y.get('title', y.get('id', None)))) -+ record['status'] = 'new' -+ record['error'] = False -+ res.append(record) -+ res.sort( -+ lambda x, y: cmp( -+ x.get('title', x.get('id', None)), -+ y.get('title', y.get('id', None)) -+ ) -+ ) - return res - -- security.declareProtected(ManagePortal, 'listInstalledProducts') -- -+ @security.protected(ManagePortal) - def listInstalledProducts(self, showHidden=False): - """Returns a list of products that are installed -> list of - dicts with keys:(id, title, hasError, status, isLocked, isHidden, -@@ -304,35 +324,39 @@ def listInstalledProducts(self, showHidden=False): - pids = [pid for pid in pids if self.isProductInstallable(pid)] - - res = [] -- -- for r in pids: -- p = self._getOb(r, None) -- name = r -- profile = self.getInstallProfile(r) -+ for pid in pids: -+ installed_product = self._getOb(pid, None) -+ name = pid -+ profile = self.getInstallProfile(pid) - if profile: - name = profile['title'] - -- res.append({'id': r, -- 'title': name, -- 'status': p.getStatus(), -- 'hasError': p.hasError(), -- 'isLocked': p.isLocked(), -- 'isHidden': p.isHidden(), -- 'installedVersion': p.getInstalledVersion()}) -- res.sort(lambda x, y: cmp(x.get('title', x.get('id', None)), -- y.get('title', y.get('id', None)))) -+ res.append({ -+ 'id': pid, -+ 'title': name, -+ 'status': installed_product.getStatus(), -+ 'hasError': installed_product.hasError(), -+ 'isLocked': installed_product.isLocked(), -+ 'isHidden': installed_product.isHidden(), -+ 'installedVersion': installed_product.getInstalledVersion() -+ }) -+ res.sort( -+ lambda x, y: cmp( -+ x.get('title', x.get('id', None)), -+ y.get('title', y.get('id', None)) -+ ) -+ ) - return res - -- security.declareProtected(ManagePortal, 'getProductFile') -- -- def getProductFile(self, p, fname='readme.txt'): -+ @security.protected(ManagePortal) -+ def getProductFile(self, product_name, fname='readme.txt'): - """Return the content of a file of the product - case-insensitive, if it does not exist -> None - """ - packages = get_packages() -- prodpath = packages.get(p) -+ prodpath = packages.get(product_name) - if prodpath is None: -- prodpath = packages.get('Products.' + p) -+ prodpath = packages.get('Products.' + product_name) - - if prodpath is None: - return None -@@ -343,23 +367,28 @@ def getProductFile(self, p, fname='readme.txt'): - except OSError: - return None - -- for f in files: -- if f.lower() == fname: -- text = open(os.path.join(prodpath, f)).read() -+ for fil in files: -+ if fil.lower() != fname: -+ continue -+ text = open(os.path.join(prodpath, fil)).read() -+ try: -+ return unicode(text) -+ except UnicodeDecodeError: - try: -- return unicode(text) -+ return unicode(text, 'utf-8') - except UnicodeDecodeError: -- try: -- return unicode(text, 'utf-8') -- except UnicodeDecodeError: -- return unicode(text, 'utf-8', 'replace') -+ return unicode(text, 'utf-8', 'replace') - return None - -- security.declareProtected(ManagePortal, 'getProductReadme') -- getProductReadme = getProductFile -- -- security.declareProtected(ManagePortal, 'getProductDescription') -+ @security.protected(ManagePortal) -+ def getProductReadme(self, product_name, fname='readme.txt'): -+ warnings.warn( -+ 'use instead: getProductFile', -+ DeprecationWarning -+ ) -+ return self.getProductFile(product_name, fname=fname) - -+ @security.protected(ManagePortal) - def getProductDescription(self, p): - """Returns the profile description for a given product. - """ -@@ -368,8 +397,7 @@ def getProductDescription(self, p): - return None - return profile.get('description', None) - -- security.declareProtected(ManagePortal, 'getProductVersion') -- -+ @security.protected(ManagePortal) - def getProductVersion(self, p): - """Return the version string stored in version.txt. - """ -@@ -391,8 +419,7 @@ def getProductVersion(self, p): - res = res.strip() - return res - -- security.declareProtected(ManagePortal, 'snapshotPortal') -- -+ @security.protected(ManagePortal) - def snapshotPortal(self, portal): - portal_types = getToolByName(portal, 'portal_types') - portal_skins = getToolByName(portal, 'portal_skins') -@@ -407,7 +434,9 @@ def snapshotPortal(self, portal): - state['rightslots'] = getattr(portal, 'right_slots', []) - if callable(state['rightslots']): - state['rightslots'] = state['rightslots']() -- state['registrypredicates'] = [pred[0] for pred in type_registry.listPredicates()] -+ state['registrypredicates'] = [ -+ pred[0] for pred in type_registry.listPredicates() -+ ] - - state['types'] = portal_types.objectIds() - state['skins'] = portal_skins.objectIds() -@@ -427,8 +456,7 @@ def snapshotPortal(self, portal): - state['resources_css'] = csstool and csstool.getResourceIds() or [] - return state - -- security.declareProtected(ManagePortal, 'deriveSettingsFromSnapshots') -- -+ @security.protected(ManagePortal) - def deriveSettingsFromSnapshots(self, before, after): - actions = [a for a in (after['actions'] - before['actions'])] - -@@ -444,46 +472,72 @@ def deriveSettingsFromSnapshots(self, before, after): - if reg not in before['utilities']] - - for registration in registrations: -- reg = (_getDottedName(registration.provided), registration.name) -- utilities.append(reg) -+ utilities.append( -+ (_getDottedName(registration.provided), registration.name) -+ ) - - settings = dict( - types=[t for t in after['types'] if t not in before['types']], - skins=[s for s in after['skins'] if s not in before['skins']], - actions=actions, -- workflows=[w for w in after['workflows'] if w not in before['workflows']], -- portalobjects=[a for a in after['portalobjects'] -- if a not in before['portalobjects']], -- leftslots=[s for s in after['leftslots'] if s not in before['leftslots']], -- rightslots=[s for s in after['rightslots'] if s not in before['rightslots']], -+ workflows=[ -+ w for w in after['workflows'] -+ if w not in before['workflows'] -+ ], -+ portalobjects=[ -+ a for a in after['portalobjects'] -+ if a not in before['portalobjects'] -+ ], -+ leftslots=[ -+ s for s in after['leftslots'] -+ if s not in before['leftslots'] -+ ], -+ rightslots=[ -+ s for s in after['rightslots'] -+ if s not in before['rightslots'] -+ ], - adapters=adapters, - utilities=utilities, -- registrypredicates=[s for s in after['registrypredicates'] -- if s not in before['registrypredicates']], -- ) -+ registrypredicates=[ -+ s for s in after['registrypredicates'] -+ if s not in before['registrypredicates'] -+ ], -+ ) - - jstool = getToolByName(self, 'portal_javascripts', None) - if jstool is not None: -- settings['resources_js'] = [r for r in after['resources_js'] if r not in before['resources_js']] -- settings['resources_css'] = [r for r in after['resources_css'] if r not in before['resources_css']] -- -+ settings['resources_js'] = [ -+ r for r in after['resources_js'] -+ if r not in before['resources_js'] -+ ] -+ settings['resources_css'] = [ -+ r for r in after['resources_css'] -+ if r not in before['resources_css'] -+ ] - return settings - -- security.declareProtected(ManagePortal, 'installProduct') -- -- def installProduct(self, p, locked=False, hidden=False, -- swallowExceptions=None, reinstall=False, -- forceProfile=False, omitSnapshots=True, -- profile=None, blacklistedSteps=None): -+ @security.protected(ManagePortal) -+ def installProduct( -+ self, -+ product_name, -+ locked=False, -+ hidden=False, -+ swallowExceptions=None, -+ reinstall=False, -+ forceProfile=False, -+ omitSnapshots=True, -+ profile=None, -+ blacklistedSteps=None -+ ): - """Install a product by name - """ -- __traceback_info__ = (p, ) -+ __traceback_info__ = (product_name, ) - - if profile is not None: - forceProfile = True - -- if self.isProductInstalled(p): -- prod = self._getOb(p) -+ if self.isProductInstalled(product_name): -+ prod = self._getOb(product_name) - msg = ('This product is already installed, ' - 'please uninstall before reinstalling it.') - prod.log(msg) -@@ -496,8 +550,11 @@ def installProduct(self, p, locked=False, hidden=False, - if hasattr(self, "REQUEST"): - reqstorage = IAnnotatable(self.REQUEST, None) - if reqstorage is not None: -- installing = reqstorage.get("Products.CMFQUickInstaller.Installing", set()) -- installing.add(p) -+ installing = reqstorage.get( -+ "Products.CMFQUickInstaller.Installing", -+ set() -+ ) -+ installing.add(product_name) - else: - reqstorage = None - -@@ -509,7 +566,9 @@ def installProduct(self, p, locked=False, hidden=False, - res = '' - - # Create a snapshot before installation -- before_id = portal_setup._mangleTimestampName('qi-before-%s' % p) -+ before_id = portal_setup._mangleTimestampName( -+ 'qi-before-%s' % product_name -+ ) - if not omitSnapshots: - portal_setup.createSnapshot(before_id) - -@@ -517,7 +576,7 @@ def installProduct(self, p, locked=False, hidden=False, - if not forceProfile: - try: - # Install via external method -- install = self.getInstallMethod(p).__of__(portal) -+ install = self.getInstallMethod(product_name).__of__(portal) - except AttributeError: - # No classic install method found - pass -@@ -529,14 +588,15 @@ def installProduct(self, p, locked=False, hidden=False, - res = install(portal) - status = 'installed' - else: -- profiles = self.getInstallProfiles(p) -+ profiles = self.getInstallProfiles(product_name) - if profiles: - if profile is None: - profile = profiles[0] - if len(profiles) > 1: -- logger.log(logging.INFO, -- 'Multiple extension profiles found for product ' -- '%s. Used profile: %s' % (p, profile)) -+ logger.info( -+ 'Multiple extension profiles found for product ' -+ '%s. Used profile: %s' % (product_name, profile) -+ ) - - portal_setup.runAllImportStepsFromProfile( - 'profile-%s' % profile, -@@ -548,10 +608,12 @@ def installProduct(self, p, locked=False, hidden=False, - pass - - if reqstorage is not None: -- installing.remove(p) -+ installing.remove(product_name) - - # Create a snapshot after installation -- after_id = portal_setup._mangleTimestampName('qi-after-%s' % p) -+ after_id = portal_setup._mangleTimestampName( -+ 'qi-after-%s' % product_name -+ ) - if not omitSnapshots: - portal_setup.createSnapshot(after_id) - -@@ -567,15 +629,18 @@ def installProduct(self, p, locked=False, hidden=False, - - rr_css = getToolByName(self, 'portal_css', None) - if rr_css is not None: -- if 'resources_css' in settings and len(settings['resources_css']) > 0: -+ if ( -+ 'resources_css' in settings -+ and len(settings['resources_css']) > 0 -+ ): - rr_css.cookResources() - - msg = str(res) -- version = self.getProductVersion(p) -+ version = self.getProductVersion(product_name) - - # add the product - self.notifyInstalled( -- p, -+ product_name, - settings=settings, - installedversion=version, - logmsg=res, -@@ -584,9 +649,10 @@ def installProduct(self, p, locked=False, hidden=False, - locked=locked, - hidden=hidden, - afterid=after_id, -- beforeid=before_id) -+ beforeid=before_id -+ ) - -- prod = getattr(self, p) -+ prod = getattr(self, product_name) - afterInstall = prod.getAfterInstallMethod() - if afterInstall is not None: - afterInstall = afterInstall.__of__(portal) -@@ -595,60 +661,78 @@ def installProduct(self, p, locked=False, hidden=False, - res = res + '\n' + str(afterRes) - return res - -- security.declareProtected(ManagePortal, 'installProducts') -- -- def installProducts(self, products=None, stoponerror=True, reinstall=False, -- REQUEST=None, forceProfile=False, omitSnapshots=True): -+ @security.protected(ManagePortal) -+ def installProducts( -+ self, -+ products=None, -+ stoponerror=True, -+ reinstall=False, -+ REQUEST=None, -+ forceProfile=False, -+ omitSnapshots=True -+ ): - """ """ - if products is None: - products = [] -- res = """ -- Installed Products -- ==================== -- """ -+ res = INSTALLED_PRODUCTS_HEADER - # return products -- for p in products: -- res += p + ':' -- r = self.installProduct(p, swallowExceptions=not stoponerror, -- reinstall=reinstall, -- forceProfile=forceProfile, -- omitSnapshots=omitSnapshots) -+ for product in products: -+ res += product + ':' -+ step_result = self.installProduct( -+ product, -+ swallowExceptions=not stoponerror, -+ reinstall=reinstall, -+ forceProfile=forceProfile, -+ omitSnapshots=omitSnapshots -+ ) - res += 'ok:\n' -- if r: -- res += str(r) + '\n' -+ if step_result: -+ res += str(step_result) + '\n' - if REQUEST: - REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - - return res - -- security.declareProtected(ManagePortal, 'isProductInstalled') -- -+ @security.protected(ManagePortal) - def isProductInstalled(self, productname): - """Check wether a product is installed (by name) - """ -- o = self._getOb(productname, None) -- return o is not None and o.isInstalled() -- -- security.declareProtected(ManagePortal, 'notifyInstalled') -- -- def notifyInstalled(self, p, locked=True, hidden=False, settings={}, **kw): -+ ob = self._getOb(productname, None) -+ return ob is not None and ob.isInstalled() -+ -+ @security.protected(ManagePortal) -+ def notifyInstalled( -+ self, -+ product_name, -+ locked=True, -+ hidden=False, -+ settings={}, -+ **kw -+ ): - """Marks a product that has been installed - without QuickInstaller as installed - """ -+ if product_name not in self.objectIds(): -+ ip = InstalledProduct(product_name) -+ self._setObject(product_name, ip) - -- if p not in self.objectIds(): -- ip = InstalledProduct(p) -- self._setObject(p, ip) -- -- p = getattr(self, p) -- p.update(settings, locked=locked, hidden=hidden, **kw) -- -- security.declareProtected(ManagePortal, 'uninstallProducts') -- -- def uninstallProducts(self, products=None, -- cascade=InstalledProduct.default_cascade, -- reinstall=False, -- REQUEST=None): -+ installed_product = getattr(self, product_name) -+ installed_product.update( -+ settings, -+ locked=locked, -+ hidden=hidden, -+ **kw -+ ) -+ -+ @postonly -+ @security.protected(ManagePortal) -+ def uninstallProducts( -+ self, -+ products=None, -+ cascade=InstalledProduct.default_cascade, -+ reinstall=False, -+ REQUEST=None -+ ): - """Removes a list of products - """ - if products is None: -@@ -661,10 +745,9 @@ def uninstallProducts(self, products=None, - - if REQUEST: - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) -- uninstallProducts = postonly(uninstallProducts) -- -- security.declareProtected(ManagePortal, 'reinstallProducts') - -+ @postonly -+ @security.protected(ManagePortal) - def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True): - """Reinstalls a list of products, the main difference to - uninstall/install is that it does not remove portal objects -@@ -674,23 +757,27 @@ def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True): - products = [products] - - # only delete everything EXCEPT portalobjects (tools etc) for reinstall -- cascade = [c for c in InstalledProduct.default_cascade -- if c != 'portalobjects'] -+ cascade = [ -+ c for c in InstalledProduct.default_cascade -+ if c != 'portalobjects' -+ ] - self.uninstallProducts(products, cascade, reinstall=True) -- self.installProducts(products, -- stoponerror=True, -- reinstall=True, -- omitSnapshots=omitSnapshots) -+ self.installProducts( -+ products, -+ stoponerror=True, -+ reinstall=True, -+ omitSnapshots=omitSnapshots -+ ) - - if REQUEST: - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - -- reinstallProducts = postonly(reinstallProducts) -- - def getQIElements(self): -- res = ['types', 'skins', 'actions', 'portalobjects', 'workflows', -- 'leftslots', 'rightslots', 'registrypredicates', -- 'resources_js', 'resources_css'] -+ res = [ -+ 'types', 'skins', 'actions', 'portalobjects', 'workflows', -+ 'leftslots', 'rightslots', 'registrypredicates', -+ 'resources_js', 'resources_css' -+ ] - return res - - def getAlreadyRegistered(self): -@@ -706,15 +793,13 @@ def getAlreadyRegistered(self): - v.extend(list(pv)) - return result - -- security.declareProtected(ManagePortal, 'isDevelopmentMode') -- -+ @security.protected(ManagePortal) - def isDevelopmentMode(self): - """Is the Zope server in debug mode? - """ - return not not DevelopmentMode - -- security.declareProtected(ManagePortal, 'getInstanceHome') -- -+ @security.protected(ManagePortal) - def getInstanceHome(self): - """Return location of $INSTANCE_HOME - """ -diff --git a/Products/CMFQuickInstallerTool/__init__.py b/Products/CMFQuickInstallerTool/__init__.py -index d4ef849..2ec784f 100644 ---- a/Products/CMFQuickInstallerTool/__init__.py -+++ b/Products/CMFQuickInstallerTool/__init__.py -@@ -1,17 +1,23 @@ -+# -*- coding: utf-8 -*- - from Products.CMFCore.utils import ToolInit - from Products.CMFQuickInstallerTool.QuickInstallerTool import AlreadyInstalled -+ - # this is probably a shortcut. don't let pyflakes complain - AlreadyInstalled - - - def initialize(context): -- import Products.CMFQuickInstallerTool.QuickInstallerTool -- ToolInit('CMF QuickInstaller Tool', -- tools=(QuickInstallerTool.QuickInstallerTool, ), -- icon='tool.gif').initialize(context) -+ from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool # noqa -+ from Products.CMFQuickInstallerTool.QuickInstallerTool import addQuickInstallerTool # noqa -+ ToolInit( -+ 'CMF QuickInstaller Tool', -+ tools=(QuickInstallerTool, ), -+ icon='tool.gif' -+ ).initialize(context) - - context.registerClass( -- QuickInstallerTool.QuickInstallerTool, -+ QuickInstallerTool, - meta_type="CMFQuickInstallerTool", -- constructors=(QuickInstallerTool.addQuickInstallerTool, ), -- icon='tool.gif') -+ constructors=(addQuickInstallerTool, ), -+ icon='tool.gif' -+ ) -diff --git a/Products/CMFQuickInstallerTool/events.py b/Products/CMFQuickInstallerTool/events.py -index 9b3f90c..94c9dec 100644 ---- a/Products/CMFQuickInstallerTool/events.py -+++ b/Products/CMFQuickInstallerTool/events.py -@@ -1,3 +1,4 @@ -+# -*- coding: utf-8 -*- - from Acquisition import aq_parent - from Products.CMFCore.utils import getToolByName - from Products.GenericSetup.interfaces import IBeforeProfileImportEvent -@@ -14,7 +15,9 @@ def findProductForProfile(context, profile_id, qi): - # Cache installable products list to cut portal creation time - request = getattr(context, 'REQUEST', SorryNoCaching()) - if not getattr(request, '_cachedInstallableProducts', ()): -- request._cachedInstallableProducts = qi.listInstallableProducts(skipInstalled=False) -+ request._cachedInstallableProducts = qi.listInstallableProducts( -+ skipInstalled=False -+ ) - - for product in request._cachedInstallableProducts: - profiles = qi.getInstallProfiles(product["id"]) -@@ -57,7 +60,7 @@ def handleBeforeProfileImportEvent(event): - if product in installing: - return - -- if storage.has_key("Products.CMFQuickInstallerTool.Events"): -+ if "Products.CMFQuickInstallerTool.Events" in storage: - data = storage["Products.CMFQuickInstallerTool.Events"] - else: - data = storage["Products.CMFQuickInstallerTool.Events"] = {} -@@ -94,10 +97,11 @@ def handleProfileImportedEvent(event): - settings = qi.deriveSettingsFromSnapshots(info["snapshot"], after) - version = qi.getProductVersion(info["product"]) - qi.notifyInstalled( -- info["product"], -- locked=False, -- logmsg="Installed via setup tool", -- settings=settings, -- installedversion=version, -- status='installed', -- error=False) -+ info["product"], -+ locked=False, -+ logmsg="Installed via setup tool", -+ settings=settings, -+ installedversion=version, -+ status='installed', -+ error=False, -+ ) -diff --git a/Products/CMFQuickInstallerTool/interfaces/installable.py b/Products/CMFQuickInstallerTool/interfaces/installable.py -index ab8a267..6a485c4 100644 ---- a/Products/CMFQuickInstallerTool/interfaces/installable.py -+++ b/Products/CMFQuickInstallerTool/interfaces/installable.py -@@ -1,3 +1,4 @@ -+# -*- coding: utf-8 -*- - from zope.interface import Interface - - -diff --git a/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py b/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -index da82fec..a487da5 100644 ---- a/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -+++ b/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -@@ -1,4 +1,6 @@ --from zope.interface import Interface, Attribute -+# -*- coding: utf-8 -*- -+from zope.interface import Attribute -+from zope.interface import Interface - - - class IQuickInstallerTool(Interface): -@@ -22,7 +24,7 @@ def isProductAvailable(productname): - ''' is the product directory present (to check if it has been deleted - from the Filesystem ''' - -- def installProduct(p, locked=False, hidden=False, -+ def installProduct(productname, locked=False, hidden=False, - swallowExceptions=False, forceProfile=False, - blacklistedSteps=None): - ''' installs a product by name -@@ -39,20 +41,20 @@ def installProducts(products=None, stoponerror=False, REQUEST=None, - forceProfile=False): - ''' installs the products specified in the products list''' - -- def getProductFile(p, fname='readme.txt'): -+ def getProductFile(productname, fname='readme.txt'): - ''' returns the content of a file of the product case-insensitive, if it - does not exist -> None ''' - -- def getProductReadme(p): -+ def getProductReadme(productname): - ''' returns the readme file of the product case-insensitive ''' - -- def getProductVersion(p): -+ def getProductVersion(productname): - ''' returns the version string stored in version.txt''' - - def isProductInstalled(productname): - ''' checks wether a product is installed (by name) ''' - -- def notifyInstalled(p, locked=True, hidden=False, **kw): -+ def notifyInstalled(productname, locked=True, hidden=False, **kw): - ''' marks a product that has been installed without QuickInstaller - as installed - if locked is set -> the prod cannot be uninstalled -diff --git a/Products/CMFQuickInstallerTool/tests/__init__.py b/Products/CMFQuickInstallerTool/tests/__init__.py -index 77fe2eb..02a25ae 100644 ---- a/Products/CMFQuickInstallerTool/tests/__init__.py -+++ b/Products/CMFQuickInstallerTool/tests/__init__.py -@@ -1 +1,2 @@ -+# -*- coding: utf-8 -*- - """QuickInstaller tests package.""" -diff --git a/Products/CMFQuickInstallerTool/tests/testSetup.py b/Products/CMFQuickInstallerTool/tests/testSetup.py -index 183a67c..ea1def6 100644 ---- a/Products/CMFQuickInstallerTool/tests/testSetup.py -+++ b/Products/CMFQuickInstallerTool/tests/testSetup.py -@@ -1,8 +1,8 @@ -+# -*- coding: utf-8 -*- - # - # Setup tests - # - from plone.app.testing.bbb import PloneTestCase -- - from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - - -diff --git a/Products/CMFQuickInstallerTool/tests/test_install.py b/Products/CMFQuickInstallerTool/tests/test_install.py -index 51f3b94..7db41da 100644 ---- a/Products/CMFQuickInstallerTool/tests/test_install.py -+++ b/Products/CMFQuickInstallerTool/tests/test_install.py -@@ -1,14 +1,13 @@ --import doctest --import unittest -- --import zope.component --from Products.GenericSetup import EXTENSION, profile_registry -+# -*- coding: utf-8 -*- - from plone.app import testing - from plone.testing import layered -- --from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool --from Products.CMFQuickInstallerTool.events import handleBeforeProfileImportEvent -+from Products.CMFQuickInstallerTool.events import handleBeforeProfileImportEvent # noqa - from Products.CMFQuickInstallerTool.events import handleProfileImportedEvent -+from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool # noqa -+from Products.GenericSetup import EXTENSION, profile_registry -+import doctest -+import unittest -+import zope.component - - import pkg_resources - try: -@@ -45,19 +44,23 @@ def setUpZope(self, app, configurationContext): - for_=None) - - def setUpPloneSite(self, portal): -- TEST_PATCHES['orig_isProductInstallable'] = QuickInstallerTool.isProductInstallable -+ TEST_PATCHES['orig_isProductInstallable'] = QuickInstallerTool.isProductInstallable # noqa - - def patched_isProductInstallable(self, productname): -- if 'QITest' in productname or 'CMFQuickInstallerTool' in productname: -+ if ( -+ 'QITest' in productname -+ or 'CMFQuickInstallerTool' in productname -+ ): - return True - return TEST_PATCHES['orig_isProductInstallable'](self, productname) - QuickInstallerTool.isProductInstallable = patched_isProductInstallable - - def tearDownPloneSite(self, portal): -- QuickInstallerTool.isProductInstallable = TEST_PATCHES['orig_isProductInstallable'] -- -- profile_registry.unregisterProfile('test', 'Products.CMFQuickInstallerTool') -- -+ QuickInstallerTool.isProductInstallable = TEST_PATCHES['orig_isProductInstallable'] # noqa -+ profile_registry.unregisterProfile( -+ 'test', -+ 'Products.CMFQuickInstallerTool' -+ ) - sm = zope.component.getSiteManager() - sm.unregisterHandler(handleBeforeProfileImportEvent) - sm.unregisterHandler(handleProfileImportedEvent) -diff --git a/Products/CMFQuickInstallerTool/utils.py b/Products/CMFQuickInstallerTool/utils.py -index d0140e9..dca114f 100644 ---- a/Products/CMFQuickInstallerTool/utils.py -+++ b/Products/CMFQuickInstallerTool/utils.py -@@ -1,16 +1,15 @@ --import logging --import os --import os.path -- -+# -*- coding: utf-8 -*- - from Acquisition import aq_base - from OFS.Application import get_products -+from OFS.metaconfigure import get_registered_packages -+from Products.CMFCore.interfaces import IContentish -+from Products.CMFCore.interfaces import IFolderish - from Products.ExternalMethod.ExternalMethod import ExternalMethod - from zExceptions import BadRequest - from zExceptions import NotFound --from Products.CMFCore.interfaces import IContentish --from Products.CMFCore.interfaces import IFolderish -- --from OFS.metaconfigure import get_registered_packages -+import logging -+import os -+import os.path - - logger = logging.getLogger('CMFQuickInstallerTool') - -@@ -23,14 +22,10 @@ - ]) - - --def updatelist(a, b, c=None): -+def updatelist(a, b, c=[]): - for l in b: -- if l not in a: -- if c is None: -- a.append(l) -- else: -- if l not in c: -- a.append(l) -+ if l not in a and l not in c: -+ a.append(l) - - - def delObjects(cont, ids): -@@ -70,10 +65,12 @@ def get_packages(): - - - def get_install_method(productname): -- modfunc = (('Install', 'install'), -- ('Install', 'Install'), -- ('install', 'install'), -- ('install', 'Install')) -+ modfunc = ( -+ ('Install', 'install'), -+ ('Install', 'Install'), -+ ('install', 'install'), -+ ('install', 'Install') -+ ) - return get_method(productname, modfunc) - - -diff --git a/Products/__init__.py b/Products/__init__.py -index f48ad10..68c04af 100644 ---- a/Products/__init__.py -+++ b/Products/__init__.py -@@ -1,6 +1,2 @@ --# 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__) -+# -*- coding: utf-8 -*- -+__import__('pkg_resources').declare_namespace(__name__) -diff --git a/README.rst b/README.rst -index 03e1fc1..7f2801d 100644 ---- a/README.rst -+++ b/README.rst -@@ -4,27 +4,22 @@ Products.CMFQuickInstallerTool - Features - -------- - --CMFQuickInstallerTool is a facility for comfortable activation/deactivation of --CMF compliant products inside a CMF site. -+CMFQuickInstallerTool is a facility for comfortable activation/deactivation of CMF compliant products inside a Zope/CMF site. - --Therefore it has to be installed as a tool inside a CMF portal, where it stores --the information about the installed products. -+Therefore it has to be installed as a tool inside a CMF portal, -+where it stores the information about the installed products. - --The requirements for a product to be installable with QuickInstallerTool are --quite simple (almost all existing CMF products fulfill them):: -+The requirements for a product to be installable with QuickInstallerTool are quite simple -+(almost all existing CMF products fulfill them): - -- External Product: The product has to implement an external -- method 'install' in a python module 'Install.py' -- in its Extensions directory. -+- the product has to implement an external method ``install`` in a python module ``Install.py`` in its ``Extensions`` directory (old style). - -- OR -+OR - -- The product ships with a GenericSetup extension profile -- and has no install method. It can still use an uninstall -- method for custom uninstallation tasks though. -+- The addon/product ships with a GenericSetup extension profile (but has no install method as above). -+ If there are multiple profiles the alphabetically first wins. - --Products can be uninstalled and QuickInstallerTool removes the following items --a product creates during install: -+Products can be uninstalled and QuickInstallerTool removes the following items a product creates during install: - - - portal actions, - - portal skins, -@@ -34,33 +29,29 @@ a product creates during install: - - left and right slots (also checks them only for the portal), - - resource registry entries - --Attention: -- --QuickInstallerTool just tracks which objects are ADDED, but not what is changed --or deleted. -+.. note:: -+ QuickInstallerTool just tracks which objects are **added**, but not what is changed or deleted. - - Usage - ----- - --In the ZMI click on portal_quickinstaller. The management screen allows you to --select products for installation and uninstallation. You can browse into the --installed products and see what was created and the logs of the install process. -+In the ZMI click on portal_quickinstaller. -+The management screen allows you to select products for installation and uninstallation. -+You can browse into the installed products and see what was created and the logs of the install process. - - Customized uninstall - -------------------- - --In order to use a customize uninstall, the following --requirements must be met:: -+In order to use a customize uninstall, the following requirements must be met: -+ -+- the product has to implement an external method ``uninstall`` in a python module ``Install.py`` in its ``Extensions`` directory. -+ Please note that the customized uninstall method is invoked before (and in addition to) the standard removal of objects. - -- External Product: The product has to implement an external -- method 'uninstall in a python module 'Install.py' -- in its Extensions directory. -+OR - --Please note that the customized uninstall method is invoked before (and in --addition to) the standard removal of objects. -+- the addon/product has to ship with a GenericSetup extension profile postfixed with ``uninstall``. -+ That will be run on uninstall only if there is no external method ``uninstall``. - --Alternatively you can register a profile 'uninstall'. That will be run on --uninstall if ther is no method 'uninstall'. - - Install: - -------- -@@ -75,9 +66,6 @@ Uninstall: - Reinstall - --------- - --Reinstalling a product invokes uninstall() and install(). If you have special --code which should work differently on reinstall than uninstall/install you can --add a second argument to the install or uninstall method named 'reinstall' which --is true only for a reinstallation. In most cases you shouldn't react differently --when reinstalling! -- -+Reinstalling a product invokes uninstall() and install(). -+If you have special code which should work differently on reinstall than uninstall/install you can add a second argument to the install or uninstall method named 'reinstall' which is true only for a reinstallation. -+In most cases you shouldn't react differently when reinstalling! -diff --git a/setup.py b/setup.py -index 469f41f..3b94ed2 100644 ---- a/setup.py -+++ b/setup.py -@@ -1,48 +1,53 @@ --from setuptools import setup, find_packages -+# -*- coding: utf-8 -*- -+from setuptools import find_packages -+from setuptools import setup - - version = '3.0.10.dev0' -+long_description = open("README.rst").read() -+long_description += '\n' -+long_description += open("CHANGES.rst").read() - --setup(name='Products.CMFQuickInstallerTool', -- version=version, -- description="CMFQuickInstallerTool is a facility for comfortable " -- "activation/deactivation of CMF compliant products.", -- long_description=open("README.rst").read() + "\n" + \ -- open("CHANGES.rst").read(), -- classifiers=[ -+setup( -+ name='Products.CMFQuickInstallerTool', -+ version=version, -+ description="A facility for comfortable activation/deactivation of CMF " -+ "compliant add ons for Zope.", -+ long_description=long_description, -+ classifiers=[ - "Framework :: Plone", - "Framework :: Plone :: 5.0", - "Framework :: Zope2", - "License :: OSI Approved :: GNU General Public License (GPL)", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", -- ], -- keywords='Zope CMF Plone quickinstall install activation', -- author='Philipp Auersperg', -- author_email='plone-developers@lists.sourceforge.net', -- maintainer='Hanno Schlichting', -- maintainer_email='hannosch@plone.org', -- url='http://pypi.python.org/pypi/Products.CMFQuickInstallerTool', -- license='GPL', -- packages=find_packages(exclude=['ez_setup']), -- namespace_packages=['Products'], -- include_package_data=True, -- zip_safe=False, -- extras_require=dict( -- test=[ -- 'zope.testing', -- 'plone.app.testing', -- ] -- ), -- install_requires=[ -- 'setuptools', -- 'zope.annotation', -- 'zope.component', -- 'zope.i18nmessageid', -- 'zope.interface', -- 'Products.CMFCore', -- 'Products.GenericSetup', -- 'Acquisition', -- 'DateTime', -- 'Zope2', -- ], -+ ], -+ keywords='Zope CMF Plone quickinstall install activation', -+ author='Philipp Auersperg', -+ author_email='plone-developers@lists.sourceforge.net', -+ maintainer='Hanno Schlichting', -+ maintainer_email='hannosch@plone.org', -+ url='http://pypi.python.org/pypi/Products.CMFQuickInstallerTool', -+ license='GPL', -+ packages=find_packages(exclude=['ez_setup']), -+ namespace_packages=['Products'], -+ include_package_data=True, -+ zip_safe=False, -+ extras_require=dict( -+ test=[ -+ 'zope.testing', -+ 'plone.app.testing', -+ ] -+ ), -+ install_requires=[ -+ 'setuptools', -+ 'zope.annotation', -+ 'zope.component', -+ 'zope.i18nmessageid', -+ 'zope.interface', -+ 'Products.CMFCore', -+ 'Products.GenericSetup', -+ 'Acquisition', -+ 'DateTime', -+ 'Zope2', -+ ], - ) -Repository: Products.CMFQuickInstallerTool +Repository: plone.app.theming Branch: refs/heads/master -Date: 2015-07-28T15:45:34+02:00 -Author: agitator (agitator) -Commit: https://github.com/plone/Products.CMFQuickInstallerTool/commit/5375de238e91435134f5f5d2dc12855f682cd8dc +Date: 2015-07-28T10:28:40-05:00 +Author: Nathan Van Gheem (vangheem) +Commit: https://github.com/plone/plone.app.theming/commit/f53e14ac37a807b377e761173bc40fd9396ee47e -Merge pull request #11 from plone/jensens-cleanup +Merge pull request #69 from Gagaro/master -cleanup: pep8 et al +fix: copy theme with dot in name Files changed: -M CHANGES.rst -M Products/CMFQuickInstallerTool/InstalledProduct.py -M Products/CMFQuickInstallerTool/QuickInstallerTool.py -M Products/CMFQuickInstallerTool/__init__.py -M Products/CMFQuickInstallerTool/events.py -M Products/CMFQuickInstallerTool/interfaces/installable.py -M Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -M Products/CMFQuickInstallerTool/tests/__init__.py -M Products/CMFQuickInstallerTool/tests/testSetup.py -M Products/CMFQuickInstallerTool/tests/test_install.py -M Products/CMFQuickInstallerTool/utils.py -M Products/__init__.py -M README.rst -M setup.py +M src/plone/app/theming/browser/controlpanel.pt -diff --git a/CHANGES.rst b/CHANGES.rst -index 91e6f85..dc3522b 100644 ---- a/CHANGES.rst -+++ b/CHANGES.rst -@@ -4,7 +4,8 @@ Changelog - 3.0.10 (unreleased) - ------------------- - --- Nothing changed yet. -+- Cleanup: PEP8, decorators for security+zca, et al. -+ [jensens] - - - 3.0.9 (2015-06-15) -diff --git a/Products/CMFQuickInstallerTool/InstalledProduct.py b/Products/CMFQuickInstallerTool/InstalledProduct.py -index b9fae50..a466fd9 100644 ---- a/Products/CMFQuickInstallerTool/InstalledProduct.py -+++ b/Products/CMFQuickInstallerTool/InstalledProduct.py -@@ -1,24 +1,22 @@ --import logging --from zope.interface import implements --from zope.component import getSiteManager --from zope.component import queryUtility -- -+# -*- coding: utf-8 -*- - from AccessControl import ClassSecurityInfo - from Acquisition import aq_base --from DateTime import DateTime - from App.class_init import InitializeClass -+from DateTime import DateTime - from OFS.SimpleItem import SimpleItem -- --from Products.CMFCore.utils import getToolByName - from Products.CMFCore.permissions import ManagePortal -+from Products.CMFCore.utils import getToolByName -+from Products.CMFQuickInstallerTool.interfaces.portal_quickinstaller import IInstalledProduct # noqa -+from Products.CMFQuickInstallerTool.utils import delObjects -+from Products.CMFQuickInstallerTool.utils import get_install_method -+from Products.CMFQuickInstallerTool.utils import get_method -+from Products.CMFQuickInstallerTool.utils import updatelist - from Products.GenericSetup.utils import _resolveDottedName - from Products.PageTemplates.PageTemplateFile import PageTemplateFile -- --from Products.CMFQuickInstallerTool.interfaces.portal_quickinstaller \ -- import IInstalledProduct --from Products.CMFQuickInstallerTool.utils import get_method --from Products.CMFQuickInstallerTool.utils import get_install_method --from Products.CMFQuickInstallerTool.utils import updatelist, delObjects -+from zope.component import getSiteManager -+from zope.component import queryUtility -+from zope.interface import implementer -+import logging - - logger = logging.getLogger('CMFQuickInstallerTool') - -@@ -28,6 +26,7 @@ - ) - - -+@implementer(IInstalledProduct) - class InstalledProduct(SimpleItem): - """Class storing information about an installed product - -@@ -38,13 +37,12 @@ class InstalledProduct(SimpleItem): - >>> verifyClass(IInstalledProduct, InstalledProduct) - True - """ -- implements(IInstalledProduct) - - meta_type = "Installed Product" - - manage_options = ( - {'label': 'View', 'action': 'manage_installationInfo'}, -- ) + SimpleItem.manage_options -+ ) + SimpleItem.manage_options - - security = ClassSecurityInfo() - -@@ -72,8 +70,7 @@ def __init__(self, id): - for key in DEFAULT_CASCADE: - setattr(self, key, []) - -- security.declareProtected(ManagePortal, 'update') -- -+ @security.protected(ManagePortal) - def update(self, settings, installedversion='', logmsg='', - status='installed', error=False, locked=False, hidden=False, - afterid=None, beforeid=None): -@@ -102,117 +99,97 @@ def update(self, settings, installedversion='', logmsg='', - - self.error = error - -- security.declareProtected(ManagePortal, 'log') -- -+ @security.protected(ManagePortal) - def log(self, logmsg): - """Adds a log to the transcript - """ - self.transcript.insert(0, {'timestamp': DateTime(), 'msg': logmsg}) - -- security.declareProtected(ManagePortal, 'hasError') -- -+ @security.protected(ManagePortal) - def hasError(self): - """Returns if the prod is in error state - """ - return getattr(self, 'error', False) - -- security.declareProtected(ManagePortal, 'isLocked') -- -+ @security.protected(ManagePortal) - def isLocked(self): - """Is the product locked for uninstall - """ - return getattr(self, 'locked', False) - -- security.declareProtected(ManagePortal, 'isHidden') -- -+ @security.protected(ManagePortal) - def isHidden(self): - """Is the product hidden - """ - return getattr(self, 'hidden', False) - -- security.declareProtected(ManagePortal, 'isVisible') -- -+ @security.protected(ManagePortal) - def isVisible(self): - return not self.isHidden() - -- security.declareProtected(ManagePortal, 'isInstalled') -- -+ @security.protected(ManagePortal) - def isInstalled(self): - return self.status == 'installed' - -- security.declareProtected(ManagePortal, 'getStatus') -- -+ @security.protected(ManagePortal) - def getStatus(self): - return self.status - -- security.declareProtected(ManagePortal, 'getTypes') -- -+ @security.protected(ManagePortal) - def getTypes(self): - return self.types - -- security.declareProtected(ManagePortal, 'getSkins') -- -+ @security.protected(ManagePortal) - def getSkins(self): - return self.skins - -- security.declareProtected(ManagePortal, 'getActions') -- -+ @security.protected(ManagePortal) - def getActions(self): - return self.actions - -- security.declareProtected(ManagePortal, 'getPortalObjects') -- -+ @security.protected(ManagePortal) - def getPortalObjects(self): - return self.portalobjects - -- security.declareProtected(ManagePortal, 'getWorkflows') -- -+ @security.protected(ManagePortal) - def getWorkflows(self): - return self.workflows - -- security.declareProtected(ManagePortal, 'getLeftSlots') -- -+ @security.protected(ManagePortal) - def getLeftSlots(self): - if getattr(self, 'leftslots', None) is None: - self.leftslots = [] - return self.leftslots - -- security.declareProtected(ManagePortal, 'getRightSlots') -- -+ @security.protected(ManagePortal) - def getRightSlots(self): - if getattr(self, 'rightslots', None) is None: - self.rightslots = [] - return self.rightslots - -- security.declareProtected(ManagePortal, 'getSlots') -- -+ @security.protected(ManagePortal) - def getSlots(self): - return self.getLeftSlots() + self.getRightSlots() - -- security.declareProtected(ManagePortal, 'getValue') -- -+ @security.protected(ManagePortal) - def getValue(self, name): - return getattr(self, name, []) - -- security.declareProtected(ManagePortal, 'getRegistryPredicates') -- -+ @security.protected(ManagePortal) - def getRegistryPredicates(self): - """Return the custom entries in the content_type_registry - """ - return getattr(self, 'registrypredicates', []) - -- security.declareProtected(ManagePortal, 'getAfterId') -- -+ @security.protected(ManagePortal) - def getAfterId(self): - return self.afterid - -- security.declareProtected(ManagePortal, 'getBeforeId') -- -+ @security.protected(ManagePortal) - def getBeforeId(self): - return self.beforeid - -- security.declareProtected(ManagePortal, 'getTranscriptAsText') -- -+ @security.protected(ManagePortal) - def getTranscriptAsText(self): - if getattr(self, 'transcript', None): - msgs = [t['timestamp'].ISO() + '\n' + str(t['msg']) -@@ -226,8 +203,7 @@ def _getMethod(self, modfunc): - """ - return get_method(self.id, modfunc) - -- security.declareProtected(ManagePortal, 'getInstallMethod') -- -+ @security.protected(ManagePortal) - def getInstallMethod(self): - """ returns the installer method """ - res = get_install_method(self.id) -@@ -237,35 +213,45 @@ def getInstallMethod(self): - else: - return res - -- security.declareProtected(ManagePortal, 'getUninstallMethod') -- -+ @security.protected(ManagePortal) - def getUninstallMethod(self): - """ returns the uninstaller method """ -- return self._getMethod((('Install', 'uninstall'), -- ('Install', 'Uninstall'), -- ('install', 'uninstall'), -- ('install', 'Uninstall'), -- )) -- -- security.declareProtected(ManagePortal, 'getAfterInstallMethod') -- -+ return self._getMethod( -+ ( -+ ('Install', 'uninstall'), -+ ('Install', 'Uninstall'), -+ ('install', 'uninstall'), -+ ('install', 'Uninstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) - def getAfterInstallMethod(self): - """ returns the after installer method """ -- return self._getMethod((('Install', 'afterInstall'), -- ('install', 'afterInstall'), -- )) -- -- security.declareProtected(ManagePortal, 'getBeforeUninstallMethod') -- -+ return self._getMethod( -+ ( -+ ('Install', 'afterInstall'), -+ ('install', 'afterInstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) - def getBeforeUninstallMethod(self): - """ returns the before uninstaller method """ -- return self._getMethod((('Install', 'beforeUninstall'), -- ('install', 'beforeUninstall'), -- )) -- -- security.declareProtected(ManagePortal, 'uninstall') -- -- def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): -+ return self._getMethod( -+ ( -+ ('Install', 'beforeUninstall'), -+ ('install', 'beforeUninstall'), -+ ) -+ ) -+ -+ @security.protected(ManagePortal) -+ def uninstall( -+ self, -+ cascade=default_cascade, -+ reinstall=False, -+ REQUEST=None -+ ): - """Uninstalls the product and removes its dependencies - """ - portal = getToolByName(self, 'portal_url').getPortalObject() -@@ -273,7 +259,9 @@ def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): - # TODO eventually we will land Event system and could remove - # this 'removal_inprogress' hack - if self.isLocked() and getattr(portal, 'removal_inprogress', False): -- raise ValueError('The product is locked and cannot be uninstalled!') -+ raise ValueError( -+ 'The product is locked and cannot be uninstalled!' -+ ) - - res = '' - afterRes = '' -@@ -299,8 +287,12 @@ def uninstall(self, cascade=default_cascade, reinstall=False, REQUEST=None): - - if beforeUninstall: - beforeUninstall = beforeUninstall.__of__(portal) -- beforeRes, cascade = beforeUninstall(portal, reinstall=reinstall, -- product=self, cascade=cascade) -+ beforeRes, cascade = beforeUninstall( -+ portal, -+ reinstall=reinstall, -+ product=self, -+ cascade=cascade -+ ) - - self._cascadeRemove(cascade) - -@@ -323,7 +315,10 @@ def _cascadeRemove(self, cascade): - portal_skins = getToolByName(self, 'portal_skins') - delObjects(portal_skins, getattr(aq_base(self), 'skins', [])) - -- if 'actions' in cascade and len(getattr(aq_base(self), 'actions', [])) > 0: -+ if ( -+ 'actions' in cascade -+ and len(getattr(aq_base(self), 'actions', [])) > 0 -+ ): - portal_actions = getToolByName(self, 'portal_actions') - for info in self.actions: - if isinstance(info, basestring): -@@ -349,26 +344,33 @@ def _cascadeRemove(self, cascade): - - if 'workflows' in cascade: - portal_workflow = getToolByName(self, 'portal_workflow') -- delObjects(portal_workflow, getattr(aq_base(self), 'workflows', [])) -+ delObjects( -+ portal_workflow, -+ getattr(aq_base(self), 'workflows', []) -+ ) - - if 'slots' in cascade: - if self.getLeftSlots(): -- portal.left_slots = [s for s in portal.left_slots -- if s not in self.getLeftSlots()] -+ portal.left_slots = [ -+ s for s in portal.left_slots -+ if s not in self.getLeftSlots() -+ ] - if self.getRightSlots(): -- portal.right_slots = [s for s in portal.right_slots -- if s not in self.getRightSlots()] -+ portal.right_slots = [ -+ s for s in portal.right_slots -+ if s not in self.getRightSlots() -+ ] - - if 'registrypredicates' in cascade: - ctr = getToolByName(self, 'content_type_registry') - ids = [id for id, predicate in ctr.listPredicates()] - predicates = getattr(aq_base(self), 'registrypredicates', []) -- for p in predicates: -- if p in ids: -- ctr.removePredicate(p) -+ for pred in predicates: -+ if pred in ids: -+ ctr.removePredicate(pred) - else: - logger.warning("Failed to delete '%s' from content type " -- "registry" % p) -+ "registry" % pred) - - if 'adapters' in cascade: - adapters = getattr(aq_base(self), 'adapters', []) -@@ -410,8 +412,7 @@ def _cascadeRemove(self, cascade): - if portal_controlpanel is not None: - portal_controlpanel.unregisterApplication(self.id) - -- security.declareProtected(ManagePortal, 'getInstalledVersion') -- -+ @security.protected(ManagePortal) - def getInstalledVersion(self): - """Return the version of the product in the moment of installation - """ -diff --git a/Products/CMFQuickInstallerTool/QuickInstallerTool.py b/Products/CMFQuickInstallerTool/QuickInstallerTool.py -index b8365e3..2602ed3 100644 ---- a/Products/CMFQuickInstallerTool/QuickInstallerTool.py -+++ b/Products/CMFQuickInstallerTool/QuickInstallerTool.py -@@ -1,49 +1,55 @@ --import logging --import os -- --import pkg_resources -- --from zope.component import getSiteManager --from zope.component import getAllUtilitiesRegisteredFor --from zope.interface import implements --from zope.annotation.interfaces import IAnnotatable --from zope.i18nmessageid import MessageFactory -- -+# -*- coding: utf-8 -*- - from AccessControl import ClassSecurityInfo - from AccessControl.requestmethod import postonly --from Acquisition import aq_base, aq_parent, aq_get, aq_inner -- --from Globals import DevelopmentMode -+from Acquisition import aq_base -+from Acquisition import aq_get -+from Acquisition import aq_inner -+from Acquisition import aq_parent - from App.class_init import InitializeClass -+from Globals import DevelopmentMode - from Globals import INSTANCE_HOME --from OFS.SimpleItem import SimpleItem - from OFS.ObjectManager import ObjectManager -- -+from OFS.SimpleItem import SimpleItem - from Products.CMFCore.permissions import ManagePortal --from Products.CMFCore.utils import UniqueObject, getToolByName --from Products.GenericSetup import EXTENSION --from Products.GenericSetup.utils import _getDottedName --from Products.PageTemplates.PageTemplateFile import PageTemplateFile -- -+from Products.CMFCore.utils import getToolByName -+from Products.CMFCore.utils import UniqueObject -+from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - from Products.CMFQuickInstallerTool.interfaces import INonInstallable - from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool --from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - from Products.CMFQuickInstallerTool.utils import get_install_method - from Products.CMFQuickInstallerTool.utils import get_packages --_ = MessageFactory("plone") -+from Products.GenericSetup import EXTENSION -+from Products.GenericSetup.utils import _getDottedName -+from Products.PageTemplates.PageTemplateFile import PageTemplateFile -+from zope.annotation.interfaces import IAnnotatable -+from zope.component import getAllUtilitiesRegisteredFor -+from zope.component import getSiteManager -+from zope.i18nmessageid import MessageFactory -+from zope.interface import implementer -+import logging -+import os -+import pkg_resources -+import warnings - - try: -- # Allow IPloneSiteRoot or ISiteRoot if we have Plone -+ pkg_resources.get_distribution('Products.CMFPlone') -+except pkg_resources.DistributionNotFound: - from Products.CMFPlone.interfaces import IPloneSiteRoot as ISiteRoot -- ISiteRoot # pyflakes --except ImportError: -+else: - from Products.CMFCore.interfaces import ISiteRoot - -+_ = MessageFactory("plone") -+ - logger = logging.getLogger('CMFQuickInstallerTool') - - # By convention the uninstall-profile is called 'uninstall' - UNINSTALL_ID = 'uninstall' - -+INSTALLED_PRODUCTS_HEADER = """ -+ Installed Products -+ ==================== -+ """ -+ - - class AlreadyInstalled(Exception): - """ Would be nice to say what Product was trying to be installed """ -@@ -58,13 +64,14 @@ def addQuickInstallerTool(self, REQUEST=None): - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - - -+@implementer(INonInstallable) - class HiddenProducts(object): -- implements(INonInstallable) - - def getNonInstallableProducts(self): - return ['CMFQuickInstallerTool', 'Products.CMFQuickInstallerTool'] - - -+@implementer(IQuickInstallerTool) - class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - """ - Let's make sure that this implementation actually fulfills the -@@ -74,7 +81,6 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - >>> verifyClass(IQuickInstallerTool, QuickInstallerTool) - True - """ -- implements(IQuickInstallerTool) - - meta_type = 'CMF QuickInstaller Tool' - id = 'portal_quickinstaller' -@@ -82,7 +88,9 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - security = ClassSecurityInfo() - - manage_options = ( -- {'label': 'Install', 'action': 'manage_installProductsForm'}, -+ { -+ 'label': 'Install', -+ 'action': 'manage_installProductsForm'}, - ) + ObjectManager.manage_options - - security.declareProtected(ManagePortal, 'manage_installProductsForm') -@@ -90,8 +98,6 @@ class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem): - 'forms/install_products_form', globals(), - __name__='manage_installProductsForm') - -- security = ClassSecurityInfo() -- - def __init__(self): - self.id = 'portal_quickinstaller' - -@@ -99,63 +105,60 @@ def __init__(self): - def errors(self): - return getattr(self, '_v_errors', {}) - -- security.declareProtected(ManagePortal, 'getInstallProfiles') -+ def _init_errors(self, reset=False): -+ """init or reset the list of broken products -+ """ -+ if not self.errors or reset: -+ self._v_errors = {} - -- def getInstallProfiles(self, productname): -- """ Return the installer profile id -+ def _install_profile_info(self, productname): -+ """list extension profile infos of a given name - """ - portal_setup = getToolByName(self, 'portal_setup') - profiles = portal_setup.listProfileInfo() - - # We are only interested in extension profiles for the product - # TODO Remove the manual Products.* check here. It is still needed. -- profiles = [prof['id'] for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] -- -+ profiles = [ -+ prof for prof in profiles -+ if prof['type'] == EXTENSION -+ and ( -+ prof['product'] == productname -+ or prof['product'] == 'Products.%s' % productname -+ ) -+ ] - return profiles - -- security.declareProtected(ManagePortal, 'getInstallProfile') -+ @security.protected(ManagePortal) -+ def getInstallProfiles(self, productname): -+ """ list all installer profile ids of the given name -+ """ -+ return [prof['id'] for prof in self._install_profile_info(productname)] - -+ @security.protected(ManagePortal) - def getInstallProfile(self, productname): - """ Return the installer profile - """ -- portal_setup = getToolByName(self, 'portal_setup') -- profiles = portal_setup.listProfileInfo() -- -- # We are only interested in extension profiles for the product -- profiles = [prof for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] -+ profiles = self._install_profile_info(productname) - - # XXX Currently QI always uses the first profile - if profiles: - return profiles[0] - return None - -- security.declareProtected(ManagePortal, 'getUninstallProfile') -- -+ @security.protected(ManagePortal) - def getUninstallProfile(self, productname): - """ Return the uninstaller profile id - """ -- portal_setup = getToolByName(self, 'portal_setup') -- profiles = portal_setup.listProfileInfo() -+ profiles = self._install_profile_info(productname) - -- # We are only interested in extension profiles for the product -- profiles = [prof for prof in profiles if -- prof['type'] == EXTENSION and -- (prof['product'] == productname or -- prof['product'] == 'Products.%s' % productname)] - if profiles: - for profile in profiles: - if profile['id'].split(':')[-1] == UNINSTALL_ID: - return profile - return None - -- security.declareProtected(ManagePortal, 'getInstallMethod') -- -+ @security.protected(ManagePortal) - def getInstallMethod(self, productname): - """ Return the installer method - """ -@@ -165,15 +168,13 @@ def getInstallMethod(self, productname): - 'product %s' % productname) - return res - -- security.declareProtected(ManagePortal, 'getBrokenInstalls') -- -+ @security.protected(ManagePortal) - def getBrokenInstalls(self): - """ Return all the broken installs """ - errs = getattr(self, "_v_errors", {}) - return errs.values() - -- security.declareProtected(ManagePortal, 'isProductInstallable') -- -+ @security.protected(ManagePortal) - def isProductInstallable(self, productname): - """Asks wether a product is installable by trying to get its install - method or an installation profile. -@@ -188,48 +189,60 @@ def isProductInstallable(self, productname): - self.getInstallMethod(productname) - return True - except AttributeError: -- profiles = self.getInstallProfiles(productname) -- if not profiles: -- return False -- setup_tool = getToolByName(self, 'portal_setup') -- try: -- # XXX Currently QI always uses the first profile -- setup_tool.getProfileDependencyChain(profiles[0]) -- except KeyError, e: -- if not getattr(self, "_v_errors", {}): -- self._v_errors = {} -- # Don't show twice the same error: old install and profile -- # oldinstall is test in first in other methods we may have an -- # extra 'Products.' in the namespace -- checkname = productname -- if checkname.startswith('Products.'): -- checkname = checkname[9:] -- else: -- checkname = 'Products.' + checkname -- if checkname in self._v_errors: -- if self._v_errors[checkname]['value'] == e.args[0]: -- return False -- else: -- # A new error is found, register it -- self._v_errors[productname] = dict( -- type=_(u"dependency_missing", default=u"Missing dependency"), -- value=e.args[0], -- productname=productname) -- else: -- self._v_errors[productname] = dict( -- type=_(u"dependency_missing", default=u"Missing dependency"), -- value=e.args[0], -- productname=productname) -- -- return False -+ # this means it has no install method, go on from here -+ pass - -- return True -+ profiles = self.getInstallProfiles(productname) -+ if not profiles: -+ return False - -- security.declareProtected(ManagePortal, 'isProductAvailable') -- isProductAvailable = isProductInstallable -+ setup_tool = getToolByName(self, 'portal_setup') -+ try: -+ # XXX Currently QI always uses the first profile -+ setup_tool.getProfileDependencyChain(profiles[0]) -+ except KeyError, e: -+ self._init_errors() -+ # Don't show twice the same error: old install and profile -+ # oldinstall is test in first in other methods we may have an -+ # extra 'Products.' in the namespace -+ checkname = productname -+ if checkname.startswith('Products.'): -+ checkname = checkname[9:] -+ else: -+ checkname = 'Products.' + checkname -+ if checkname in self.errors: -+ if self.errors[checkname]['value'] == e.args[0]: -+ return False -+ # A new error is found, register it -+ self.errors[productname] = dict( -+ type=_( -+ u"dependency_missing", -+ default=u"Missing dependency" -+ ), -+ value=e.args[0], -+ productname=productname -+ ) -+ else: -+ self.errors[productname] = dict( -+ type=_( -+ u"dependency_missing", -+ default=u"Missing dependency" -+ ), -+ value=e.args[0], -+ productname=productname -+ ) -+ return False -+ return True - -- security.declareProtected(ManagePortal, 'listInstallableProfiles') -+ @security.protected(ManagePortal) -+ def isProductAvailable(self, productname): -+ warnings.warn( -+ 'use instead: isProductInstallable', -+ DeprecationWarning -+ ) -+ return self.isProductInstallable(productname) - -+ @security.protected(ManagePortal) - def listInstallableProfiles(self): - """List candidate products which have a GS profiles. - """ -@@ -237,63 +250,70 @@ def listInstallableProfiles(self): - profiles = portal_setup.listProfileInfo(ISiteRoot) - - # We are only interested in extension profiles -- profiles = [prof['product'] for prof in profiles if -- prof['type'] == EXTENSION] -+ profiles = [ -+ prof['product'] for prof in profiles -+ if prof['type'] == EXTENSION -+ ] - return set(profiles) - -- security.declareProtected(ManagePortal, 'listInstallableProducts') -- -+ @security.protected(ManagePortal) - def listInstallableProducts(self, skipInstalled=True): - """List candidate CMF products for installation -> list of dicts - with keys:(id,title,hasError,status) - """ -- # reset the list of broken products -- if getattr(self, '_v_errors', True): -- self._v_errors = {} -+ self._init_errors(reset=True) - - # Returns full names with Products. prefix for all packages / products - packages = get_packages() - - pids = [] -- for p in packages: -- if not self.isProductInstallable(p): -+ for pkg in packages: -+ if not self.isProductInstallable(pkg): - continue -- if p.startswith('Products.'): -- p = p[9:] -- pids.append(p) -+ if pkg.startswith('Products.'): -+ pkg = pkg[9:] -+ pids.append(pkg) - - # Get product list from the extension profiles - profile_pids = self.listInstallableProfiles() - -- for p in profile_pids: -- if p in pids or p in packages: -+ for pp in profile_pids: -+ if pp in pids or pp in packages: - continue -- if not self.isProductInstallable(p): -+ if not self.isProductInstallable(pp): - continue -- pids.append(p) -+ pids.append(pp) - - if skipInstalled: -- installed = [p['id'] for p in self.listInstalledProducts(showHidden=True)] -+ installed = [ -+ p['id'] for p in self.listInstalledProducts(showHidden=True) -+ ] - pids = [r for r in pids if r not in installed] - - res = [] -- for r in pids: -- p = self._getOb(r, None) -- name = r -- profile = self.getInstallProfile(r) -+ for pid in pids: -+ installed_product = self._getOb(pid, None) -+ name = pid -+ profile = self.getInstallProfile(pid) - if profile: - name = profile['title'] -- if p: -- res.append({'id': r, 'title': name, 'status': p.getStatus(), -- 'hasError': p.hasError()}) -+ record = {'id': pid, 'title': name} -+ if installed_product: -+ record['status'] = installed_product.getStatus() -+ record['error'] = installed_product.hasError() - else: -- res.append({'id': r, 'title': name, 'status': 'new', 'hasError': False}) -- res.sort(lambda x, y: cmp(x.get('title', x.get('id', None)), -- y.get('title', y.get('id', None)))) -+ record['status'] = 'new' -+ record['error'] = False -+ res.append(record) -+ res.sort( -+ lambda x, y: cmp( -+ x.get('title', x.get('id', None)), -+ y.get('title', y.get('id', None)) -+ ) -+ ) - return res - -- security.declareProtected(ManagePortal, 'listInstalledProducts') -- -+ @security.protected(ManagePortal) - def listInstalledProducts(self, showHidden=False): - """Returns a list of products that are installed -> list of - dicts with keys:(id, title, hasError, status, isLocked, isHidden, -@@ -304,35 +324,39 @@ def listInstalledProducts(self, showHidden=False): - pids = [pid for pid in pids if self.isProductInstallable(pid)] - - res = [] -- -- for r in pids: -- p = self._getOb(r, None) -- name = r -- profile = self.getInstallProfile(r) -+ for pid in pids: -+ installed_product = self._getOb(pid, None) -+ name = pid -+ profile = self.getInstallProfile(pid) - if profile: - name = profile['title'] - -- res.append({'id': r, -- 'title': name, -- 'status': p.getStatus(), -- 'hasError': p.hasError(), -- 'isLocked': p.isLocked(), -- 'isHidden': p.isHidden(), -- 'installedVersion': p.getInstalledVersion()}) -- res.sort(lambda x, y: cmp(x.get('title', x.get('id', None)), -- y.get('title', y.get('id', None)))) -+ res.append({ -+ 'id': pid, -+ 'title': name, -+ 'status': installed_product.getStatus(), -+ 'hasError': installed_product.hasError(), -+ 'isLocked': installed_product.isLocked(), -+ 'isHidden': installed_product.isHidden(), -+ 'installedVersion': installed_product.getInstalledVersion() -+ }) -+ res.sort( -+ lambda x, y: cmp( -+ x.get('title', x.get('id', None)), -+ y.get('title', y.get('id', None)) -+ ) -+ ) - return res - -- security.declareProtected(ManagePortal, 'getProductFile') -- -- def getProductFile(self, p, fname='readme.txt'): -+ @security.protected(ManagePortal) -+ def getProductFile(self, product_name, fname='readme.txt'): - """Return the content of a file of the product - case-insensitive, if it does not exist -> None - """ - packages = get_packages() -- prodpath = packages.get(p) -+ prodpath = packages.get(product_name) - if prodpath is None: -- prodpath = packages.get('Products.' + p) -+ prodpath = packages.get('Products.' + product_name) - - if prodpath is None: - return None -@@ -343,23 +367,28 @@ def getProductFile(self, p, fname='readme.txt'): - except OSError: - return None - -- for f in files: -- if f.lower() == fname: -- text = open(os.path.join(prodpath, f)).read() -+ for fil in files: -+ if fil.lower() != fname: -+ continue -+ text = open(os.path.join(prodpath, fil)).read() -+ try: -+ return unicode(text) -+ except UnicodeDecodeError: - try: -- return unicode(text) -+ return unicode(text, 'utf-8') - except UnicodeDecodeError: -- try: -- return unicode(text, 'utf-8') -- except UnicodeDecodeError: -- return unicode(text, 'utf-8', 'replace') -+ return unicode(text, 'utf-8', 'replace') - return None - -- security.declareProtected(ManagePortal, 'getProductReadme') -- getProductReadme = getProductFile -- -- security.declareProtected(ManagePortal, 'getProductDescription') -+ @security.protected(ManagePortal) -+ def getProductReadme(self, product_name, fname='readme.txt'): -+ warnings.warn( -+ 'use instead: getProductFile', -+ DeprecationWarning -+ ) -+ return self.getProductFile(product_name, fname=fname) - -+ @security.protected(ManagePortal) - def getProductDescription(self, p): - """Returns the profile description for a given product. - """ -@@ -368,8 +397,7 @@ def getProductDescription(self, p): - return None - return profile.get('description', None) - -- security.declareProtected(ManagePortal, 'getProductVersion') -- -+ @security.protected(ManagePortal) - def getProductVersion(self, p): - """Return the version string stored in version.txt. - """ -@@ -391,8 +419,7 @@ def getProductVersion(self, p): - res = res.strip() - return res - -- security.declareProtected(ManagePortal, 'snapshotPortal') -- -+ @security.protected(ManagePortal) - def snapshotPortal(self, portal): - portal_types = getToolByName(portal, 'portal_types') - portal_skins = getToolByName(portal, 'portal_skins') -@@ -407,7 +434,9 @@ def snapshotPortal(self, portal): - state['rightslots'] = getattr(portal, 'right_slots', []) - if callable(state['rightslots']): - state['rightslots'] = state['rightslots']() -- state['registrypredicates'] = [pred[0] for pred in type_registry.listPredicates()] -+ state['registrypredicates'] = [ -+ pred[0] for pred in type_registry.listPredicates() -+ ] - - state['types'] = portal_types.objectIds() - state['skins'] = portal_skins.objectIds() -@@ -427,8 +456,7 @@ def snapshotPortal(self, portal): - state['resources_css'] = csstool and csstool.getResourceIds() or [] - return state - -- security.declareProtected(ManagePortal, 'deriveSettingsFromSnapshots') -- -+ @security.protected(ManagePortal) - def deriveSettingsFromSnapshots(self, before, after): - actions = [a for a in (after['actions'] - before['actions'])] - -@@ -444,46 +472,72 @@ def deriveSettingsFromSnapshots(self, before, after): - if reg not in before['utilities']] - - for registration in registrations: -- reg = (_getDottedName(registration.provided), registration.name) -- utilities.append(reg) -+ utilities.append( -+ (_getDottedName(registration.provided), registration.name) -+ ) - - settings = dict( - types=[t for t in after['types'] if t not in before['types']], - skins=[s for s in after['skins'] if s not in before['skins']], - actions=actions, -- workflows=[w for w in after['workflows'] if w not in before['workflows']], -- portalobjects=[a for a in after['portalobjects'] -- if a not in before['portalobjects']], -- leftslots=[s for s in after['leftslots'] if s not in before['leftslots']], -- rightslots=[s for s in after['rightslots'] if s not in before['rightslots']], -+ workflows=[ -+ w for w in after['workflows'] -+ if w not in before['workflows'] -+ ], -+ portalobjects=[ -+ a for a in after['portalobjects'] -+ if a not in before['portalobjects'] -+ ], -+ leftslots=[ -+ s for s in after['leftslots'] -+ if s not in before['leftslots'] -+ ], -+ rightslots=[ -+ s for s in after['rightslots'] -+ if s not in before['rightslots'] -+ ], - adapters=adapters, - utilities=utilities, -- registrypredicates=[s for s in after['registrypredicates'] -- if s not in before['registrypredicates']], -- ) -+ registrypredicates=[ -+ s for s in after['registrypredicates'] -+ if s not in before['registrypredicates'] -+ ], -+ ) - - jstool = getToolByName(self, 'portal_javascripts', None) - if jstool is not None: -- settings['resources_js'] = [r for r in after['resources_js'] if r not in before['resources_js']] -- settings['resources_css'] = [r for r in after['resources_css'] if r not in before['resources_css']] -- -+ settings['resources_js'] = [ -+ r for r in after['resources_js'] -+ if r not in before['resources_js'] -+ ] -+ settings['resources_css'] = [ -+ r for r in after['resources_css'] -+ if r not in before['resources_css'] -+ ] - return settings - -- security.declareProtected(ManagePortal, 'installProduct') -- -- def installProduct(self, p, locked=False, hidden=False, -- swallowExceptions=None, reinstall=False, -- forceProfile=False, omitSnapshots=True, -- profile=None, blacklistedSteps=None): -+ @security.protected(ManagePortal) -+ def installProduct( -+ self, -+ product_name, -+ locked=False, -+ hidden=False, -+ swallowExceptions=None, -+ reinstall=False, -+ forceProfile=False, -+ omitSnapshots=True, -+ profile=None, -+ blacklistedSteps=None -+ ): - """Install a product by name - """ -- __traceback_info__ = (p, ) -+ __traceback_info__ = (product_name, ) - - if profile is not None: - forceProfile = True - -- if self.isProductInstalled(p): -- prod = self._getOb(p) -+ if self.isProductInstalled(product_name): -+ prod = self._getOb(product_name) - msg = ('This product is already installed, ' - 'please uninstall before reinstalling it.') - prod.log(msg) -@@ -496,8 +550,11 @@ def installProduct(self, p, locked=False, hidden=False, - if hasattr(self, "REQUEST"): - reqstorage = IAnnotatable(self.REQUEST, None) - if reqstorage is not None: -- installing = reqstorage.get("Products.CMFQUickInstaller.Installing", set()) -- installing.add(p) -+ installing = reqstorage.get( -+ "Products.CMFQUickInstaller.Installing", -+ set() -+ ) -+ installing.add(product_name) - else: - reqstorage = None - -@@ -509,7 +566,9 @@ def installProduct(self, p, locked=False, hidden=False, - res = '' - - # Create a snapshot before installation -- before_id = portal_setup._mangleTimestampName('qi-before-%s' % p) -+ before_id = portal_setup._mangleTimestampName( -+ 'qi-before-%s' % product_name -+ ) - if not omitSnapshots: - portal_setup.createSnapshot(before_id) - -@@ -517,7 +576,7 @@ def installProduct(self, p, locked=False, hidden=False, - if not forceProfile: - try: - # Install via external method -- install = self.getInstallMethod(p).__of__(portal) -+ install = self.getInstallMethod(product_name).__of__(portal) - except AttributeError: - # No classic install method found - pass -@@ -529,14 +588,15 @@ def installProduct(self, p, locked=False, hidden=False, - res = install(portal) - status = 'installed' - else: -- profiles = self.getInstallProfiles(p) -+ profiles = self.getInstallProfiles(product_name) - if profiles: - if profile is None: - profile = profiles[0] - if len(profiles) > 1: -- logger.log(logging.INFO, -- 'Multiple extension profiles found for product ' -- '%s. Used profile: %s' % (p, profile)) -+ logger.info( -+ 'Multiple extension profiles found for product ' -+ '%s. Used profile: %s' % (product_name, profile) -+ ) - - portal_setup.runAllImportStepsFromProfile( - 'profile-%s' % profile, -@@ -548,10 +608,12 @@ def installProduct(self, p, locked=False, hidden=False, - pass - - if reqstorage is not None: -- installing.remove(p) -+ installing.remove(product_name) - - # Create a snapshot after installation -- after_id = portal_setup._mangleTimestampName('qi-after-%s' % p) -+ after_id = portal_setup._mangleTimestampName( -+ 'qi-after-%s' % product_name -+ ) - if not omitSnapshots: - portal_setup.createSnapshot(after_id) - -@@ -567,15 +629,18 @@ def installProduct(self, p, locked=False, hidden=False, - - rr_css = getToolByName(self, 'portal_css', None) - if rr_css is not None: -- if 'resources_css' in settings and len(settings['resources_css']) > 0: -+ if ( -+ 'resources_css' in settings -+ and len(settings['resources_css']) > 0 -+ ): - rr_css.cookResources() - - msg = str(res) -- version = self.getProductVersion(p) -+ version = self.getProductVersion(product_name) - - # add the product - self.notifyInstalled( -- p, -+ product_name, - settings=settings, - installedversion=version, - logmsg=res, -@@ -584,9 +649,10 @@ def installProduct(self, p, locked=False, hidden=False, - locked=locked, - hidden=hidden, - afterid=after_id, -- beforeid=before_id) -+ beforeid=before_id -+ ) - -- prod = getattr(self, p) -+ prod = getattr(self, product_name) - afterInstall = prod.getAfterInstallMethod() - if afterInstall is not None: - afterInstall = afterInstall.__of__(portal) -@@ -595,60 +661,78 @@ def installProduct(self, p, locked=False, hidden=False, - res = res + '\n' + str(afterRes) - return res - -- security.declareProtected(ManagePortal, 'installProducts') -- -- def installProducts(self, products=None, stoponerror=True, reinstall=False, -- REQUEST=None, forceProfile=False, omitSnapshots=True): -+ @security.protected(ManagePortal) -+ def installProducts( -+ self, -+ products=None, -+ stoponerror=True, -+ reinstall=False, -+ REQUEST=None, -+ forceProfile=False, -+ omitSnapshots=True -+ ): - """ """ - if products is None: - products = [] -- res = """ -- Installed Products -- ==================== -- """ -+ res = INSTALLED_PRODUCTS_HEADER - # return products -- for p in products: -- res += p + ':' -- r = self.installProduct(p, swallowExceptions=not stoponerror, -- reinstall=reinstall, -- forceProfile=forceProfile, -- omitSnapshots=omitSnapshots) -+ for product in products: -+ res += product + ':' -+ step_result = self.installProduct( -+ product, -+ swallowExceptions=not stoponerror, -+ reinstall=reinstall, -+ forceProfile=forceProfile, -+ omitSnapshots=omitSnapshots -+ ) - res += 'ok:\n' -- if r: -- res += str(r) + '\n' -+ if step_result: -+ res += str(step_result) + '\n' - if REQUEST: - REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - - return res - -- security.declareProtected(ManagePortal, 'isProductInstalled') -- -+ @security.protected(ManagePortal) - def isProductInstalled(self, productname): - """Check wether a product is installed (by name) - """ -- o = self._getOb(productname, None) -- return o is not None and o.isInstalled() -- -- security.declareProtected(ManagePortal, 'notifyInstalled') -- -- def notifyInstalled(self, p, locked=True, hidden=False, settings={}, **kw): -+ ob = self._getOb(productname, None) -+ return ob is not None and ob.isInstalled() -+ -+ @security.protected(ManagePortal) -+ def notifyInstalled( -+ self, -+ product_name, -+ locked=True, -+ hidden=False, -+ settings={}, -+ **kw -+ ): - """Marks a product that has been installed - without QuickInstaller as installed - """ -+ if product_name not in self.objectIds(): -+ ip = InstalledProduct(product_name) -+ self._setObject(product_name, ip) - -- if p not in self.objectIds(): -- ip = InstalledProduct(p) -- self._setObject(p, ip) -- -- p = getattr(self, p) -- p.update(settings, locked=locked, hidden=hidden, **kw) -- -- security.declareProtected(ManagePortal, 'uninstallProducts') -- -- def uninstallProducts(self, products=None, -- cascade=InstalledProduct.default_cascade, -- reinstall=False, -- REQUEST=None): -+ installed_product = getattr(self, product_name) -+ installed_product.update( -+ settings, -+ locked=locked, -+ hidden=hidden, -+ **kw -+ ) -+ -+ @postonly -+ @security.protected(ManagePortal) -+ def uninstallProducts( -+ self, -+ products=None, -+ cascade=InstalledProduct.default_cascade, -+ reinstall=False, -+ REQUEST=None -+ ): - """Removes a list of products - """ - if products is None: -@@ -661,10 +745,9 @@ def uninstallProducts(self, products=None, - - if REQUEST: - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) -- uninstallProducts = postonly(uninstallProducts) -- -- security.declareProtected(ManagePortal, 'reinstallProducts') - -+ @postonly -+ @security.protected(ManagePortal) - def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True): - """Reinstalls a list of products, the main difference to - uninstall/install is that it does not remove portal objects -@@ -674,23 +757,27 @@ def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True): - products = [products] - - # only delete everything EXCEPT portalobjects (tools etc) for reinstall -- cascade = [c for c in InstalledProduct.default_cascade -- if c != 'portalobjects'] -+ cascade = [ -+ c for c in InstalledProduct.default_cascade -+ if c != 'portalobjects' -+ ] - self.uninstallProducts(products, cascade, reinstall=True) -- self.installProducts(products, -- stoponerror=True, -- reinstall=True, -- omitSnapshots=omitSnapshots) -+ self.installProducts( -+ products, -+ stoponerror=True, -+ reinstall=True, -+ omitSnapshots=omitSnapshots -+ ) - - if REQUEST: - return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER']) - -- reinstallProducts = postonly(reinstallProducts) -- - def getQIElements(self): -- res = ['types', 'skins', 'actions', 'portalobjects', 'workflows', -- 'leftslots', 'rightslots', 'registrypredicates', -- 'resources_js', 'resources_css'] -+ res = [ -+ 'types', 'skins', 'actions', 'portalobjects', 'workflows', -+ 'leftslots', 'rightslots', 'registrypredicates', -+ 'resources_js', 'resources_css' -+ ] - return res - - def getAlreadyRegistered(self): -@@ -706,15 +793,13 @@ def getAlreadyRegistered(self): - v.extend(list(pv)) - return result - -- security.declareProtected(ManagePortal, 'isDevelopmentMode') -- -+ @security.protected(ManagePortal) - def isDevelopmentMode(self): - """Is the Zope server in debug mode? - """ - return not not DevelopmentMode - -- security.declareProtected(ManagePortal, 'getInstanceHome') -- -+ @security.protected(ManagePortal) - def getInstanceHome(self): - """Return location of $INSTANCE_HOME - """ -diff --git a/Products/CMFQuickInstallerTool/__init__.py b/Products/CMFQuickInstallerTool/__init__.py -index d4ef849..2ec784f 100644 ---- a/Products/CMFQuickInstallerTool/__init__.py -+++ b/Products/CMFQuickInstallerTool/__init__.py -@@ -1,17 +1,23 @@ -+# -*- coding: utf-8 -*- - from Products.CMFCore.utils import ToolInit - from Products.CMFQuickInstallerTool.QuickInstallerTool import AlreadyInstalled -+ - # this is probably a shortcut. don't let pyflakes complain - AlreadyInstalled - - - def initialize(context): -- import Products.CMFQuickInstallerTool.QuickInstallerTool -- ToolInit('CMF QuickInstaller Tool', -- tools=(QuickInstallerTool.QuickInstallerTool, ), -- icon='tool.gif').initialize(context) -+ from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool # noqa -+ from Products.CMFQuickInstallerTool.QuickInstallerTool import addQuickInstallerTool # noqa -+ ToolInit( -+ 'CMF QuickInstaller Tool', -+ tools=(QuickInstallerTool, ), -+ icon='tool.gif' -+ ).initialize(context) - - context.registerClass( -- QuickInstallerTool.QuickInstallerTool, -+ QuickInstallerTool, - meta_type="CMFQuickInstallerTool", -- constructors=(QuickInstallerTool.addQuickInstallerTool, ), -- icon='tool.gif') -+ constructors=(addQuickInstallerTool, ), -+ icon='tool.gif' -+ ) -diff --git a/Products/CMFQuickInstallerTool/events.py b/Products/CMFQuickInstallerTool/events.py -index 9b3f90c..94c9dec 100644 ---- a/Products/CMFQuickInstallerTool/events.py -+++ b/Products/CMFQuickInstallerTool/events.py -@@ -1,3 +1,4 @@ -+# -*- coding: utf-8 -*- - from Acquisition import aq_parent - from Products.CMFCore.utils import getToolByName - from Products.GenericSetup.interfaces import IBeforeProfileImportEvent -@@ -14,7 +15,9 @@ def findProductForProfile(context, profile_id, qi): - # Cache installable products list to cut portal creation time - request = getattr(context, 'REQUEST', SorryNoCaching()) - if not getattr(request, '_cachedInstallableProducts', ()): -- request._cachedInstallableProducts = qi.listInstallableProducts(skipInstalled=False) -+ request._cachedInstallableProducts = qi.listInstallableProducts( -+ skipInstalled=False -+ ) - - for product in request._cachedInstallableProducts: - profiles = qi.getInstallProfiles(product["id"]) -@@ -57,7 +60,7 @@ def handleBeforeProfileImportEvent(event): - if product in installing: - return - -- if storage.has_key("Products.CMFQuickInstallerTool.Events"): -+ if "Products.CMFQuickInstallerTool.Events" in storage: - data = storage["Products.CMFQuickInstallerTool.Events"] - else: - data = storage["Products.CMFQuickInstallerTool.Events"] = {} -@@ -94,10 +97,11 @@ def handleProfileImportedEvent(event): - settings = qi.deriveSettingsFromSnapshots(info["snapshot"], after) - version = qi.getProductVersion(info["product"]) - qi.notifyInstalled( -- info["product"], -- locked=False, -- logmsg="Installed via setup tool", -- settings=settings, -- installedversion=version, -- status='installed', -- error=False) -+ info["product"], -+ locked=False, -+ logmsg="Installed via setup tool", -+ settings=settings, -+ installedversion=version, -+ status='installed', -+ error=False, -+ ) -diff --git a/Products/CMFQuickInstallerTool/interfaces/installable.py b/Products/CMFQuickInstallerTool/interfaces/installable.py -index ab8a267..6a485c4 100644 ---- a/Products/CMFQuickInstallerTool/interfaces/installable.py -+++ b/Products/CMFQuickInstallerTool/interfaces/installable.py -@@ -1,3 +1,4 @@ -+# -*- coding: utf-8 -*- - from zope.interface import Interface - - -diff --git a/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py b/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -index da82fec..a487da5 100644 ---- a/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -+++ b/Products/CMFQuickInstallerTool/interfaces/portal_quickinstaller.py -@@ -1,4 +1,6 @@ --from zope.interface import Interface, Attribute -+# -*- coding: utf-8 -*- -+from zope.interface import Attribute -+from zope.interface import Interface - - - class IQuickInstallerTool(Interface): -@@ -22,7 +24,7 @@ def isProductAvailable(productname): - ''' is the product directory present (to check if it has been deleted - from the Filesystem ''' - -- def installProduct(p, locked=False, hidden=False, -+ def installProduct(productname, locked=False, hidden=False, - swallowExceptions=False, forceProfile=False, - blacklistedSteps=None): - ''' installs a product by name -@@ -39,20 +41,20 @@ def installProducts(products=None, stoponerror=False, REQUEST=None, - forceProfile=False): - ''' installs the products specified in the products list''' - -- def getProductFile(p, fname='readme.txt'): -+ def getProductFile(productname, fname='readme.txt'): - ''' returns the content of a file of the product case-insensitive, if it - does not exist -> None ''' - -- def getProductReadme(p): -+ def getProductReadme(productname): - ''' returns the readme file of the product case-insensitive ''' - -- def getProductVersion(p): -+ def getProductVersion(productname): - ''' returns the version string stored in version.txt''' - - def isProductInstalled(productname): - ''' checks wether a product is installed (by name) ''' - -- def notifyInstalled(p, locked=True, hidden=False, **kw): -+ def notifyInstalled(productname, locked=True, hidden=False, **kw): - ''' marks a product that has been installed without QuickInstaller - as installed - if locked is set -> the prod cannot be uninstalled -diff --git a/Products/CMFQuickInstallerTool/tests/__init__.py b/Products/CMFQuickInstallerTool/tests/__init__.py -index 77fe2eb..02a25ae 100644 ---- a/Products/CMFQuickInstallerTool/tests/__init__.py -+++ b/Products/CMFQuickInstallerTool/tests/__init__.py -@@ -1 +1,2 @@ -+# -*- coding: utf-8 -*- - """QuickInstaller tests package.""" -diff --git a/Products/CMFQuickInstallerTool/tests/testSetup.py b/Products/CMFQuickInstallerTool/tests/testSetup.py -index 183a67c..ea1def6 100644 ---- a/Products/CMFQuickInstallerTool/tests/testSetup.py -+++ b/Products/CMFQuickInstallerTool/tests/testSetup.py -@@ -1,8 +1,8 @@ -+# -*- coding: utf-8 -*- - # - # Setup tests - # - from plone.app.testing.bbb import PloneTestCase -- - from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct - - -diff --git a/Products/CMFQuickInstallerTool/tests/test_install.py b/Products/CMFQuickInstallerTool/tests/test_install.py -index 51f3b94..7db41da 100644 ---- a/Products/CMFQuickInstallerTool/tests/test_install.py -+++ b/Products/CMFQuickInstallerTool/tests/test_install.py -@@ -1,14 +1,13 @@ --import doctest --import unittest -- --import zope.component --from Products.GenericSetup import EXTENSION, profile_registry -+# -*- coding: utf-8 -*- - from plone.app import testing - from plone.testing import layered -- --from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool --from Products.CMFQuickInstallerTool.events import handleBeforeProfileImportEvent -+from Products.CMFQuickInstallerTool.events import handleBeforeProfileImportEvent # noqa - from Products.CMFQuickInstallerTool.events import handleProfileImportedEvent -+from Products.CMFQuickInstallerTool.QuickInstallerTool import QuickInstallerTool # noqa -+from Products.GenericSetup import EXTENSION, profile_registry -+import doctest -+import unittest -+import zope.component - - import pkg_resources - try: -@@ -45,19 +44,23 @@ def setUpZope(self, app, configurationContext): - for_=None) - - def setUpPloneSite(self, portal): -- TEST_PATCHES['orig_isProductInstallable'] = QuickInstallerTool.isProductInstallable -+ TEST_PATCHES['orig_isProductInstallable'] = QuickInstallerTool.isProductInstallable # noqa - - def patched_isProductInstallable(self, productname): -- if 'QITest' in productname or 'CMFQuickInstallerTool' in productname: -+ if ( -+ 'QITest' in productname -+ or 'CMFQuickInstallerTool' in productname -+ ): - return True - return TEST_PATCHES['orig_isProductInstallable'](self, productname) - QuickInstallerTool.isProductInstallable = patched_isProductInstallable - - def tearDownPloneSite(self, portal): -- QuickInstallerTool.isProductInstallable = TEST_PATCHES['orig_isProductInstallable'] -- -- profile_registry.unregisterProfile('test', 'Products.CMFQuickInstallerTool') -- -+ QuickInstallerTool.isProductInstallable = TEST_PATCHES['orig_isProductInstallable'] # noqa -+ profile_registry.unregisterProfile( -+ 'test', -+ 'Products.CMFQuickInstallerTool' -+ ) - sm = zope.component.getSiteManager() - sm.unregisterHandler(handleBeforeProfileImportEvent) - sm.unregisterHandler(handleProfileImportedEvent) -diff --git a/Products/CMFQuickInstallerTool/utils.py b/Products/CMFQuickInstallerTool/utils.py -index d0140e9..dca114f 100644 ---- a/Products/CMFQuickInstallerTool/utils.py -+++ b/Products/CMFQuickInstallerTool/utils.py -@@ -1,16 +1,15 @@ --import logging --import os --import os.path -- -+# -*- coding: utf-8 -*- - from Acquisition import aq_base - from OFS.Application import get_products -+from OFS.metaconfigure import get_registered_packages -+from Products.CMFCore.interfaces import IContentish -+from Products.CMFCore.interfaces import IFolderish - from Products.ExternalMethod.ExternalMethod import ExternalMethod - from zExceptions import BadRequest - from zExceptions import NotFound --from Products.CMFCore.interfaces import IContentish --from Products.CMFCore.interfaces import IFolderish -- --from OFS.metaconfigure import get_registered_packages -+import logging -+import os -+import os.path - - logger = logging.getLogger('CMFQuickInstallerTool') - -@@ -23,14 +22,10 @@ - ]) - - --def updatelist(a, b, c=None): -+def updatelist(a, b, c=[]): - for l in b: -- if l not in a: -- if c is None: -- a.append(l) -- else: -- if l not in c: -- a.append(l) -+ if l not in a and l not in c: -+ a.append(l) - - - def delObjects(cont, ids): -@@ -70,10 +65,12 @@ def get_packages(): - - - def get_install_method(productname): -- modfunc = (('Install', 'install'), -- ('Install', 'Install'), -- ('install', 'install'), -- ('install', 'Install')) -+ modfunc = ( -+ ('Install', 'install'), -+ ('Install', 'Install'), -+ ('install', 'install'), -+ ('install', 'Install') -+ ) - return get_method(productname, modfunc) - - -diff --git a/Products/__init__.py b/Products/__init__.py -index f48ad10..68c04af 100644 ---- a/Products/__init__.py -+++ b/Products/__init__.py -@@ -1,6 +1,2 @@ --# 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__) -+# -*- coding: utf-8 -*- -+__import__('pkg_resources').declare_namespace(__name__) -diff --git a/README.rst b/README.rst -index 03e1fc1..7f2801d 100644 ---- a/README.rst -+++ b/README.rst -@@ -4,27 +4,22 @@ Products.CMFQuickInstallerTool - Features - -------- - --CMFQuickInstallerTool is a facility for comfortable activation/deactivation of --CMF compliant products inside a CMF site. -+CMFQuickInstallerTool is a facility for comfortable activation/deactivation of CMF compliant products inside a Zope/CMF site. - --Therefore it has to be installed as a tool inside a CMF portal, where it stores --the information about the installed products. -+Therefore it has to be installed as a tool inside a CMF portal, -+where it stores the information about the installed products. - --The requirements for a product to be installable with QuickInstallerTool are --quite simple (almost all existing CMF products fulfill them):: -+The requirements for a product to be installable with QuickInstallerTool are quite simple -+(almost all existing CMF products fulfill them): - -- External Product: The product has to implement an external -- method 'install' in a python module 'Install.py' -- in its Extensions directory. -+- the product has to implement an external method ``install`` in a python module ``Install.py`` in its ``Extensions`` directory (old style). - -- OR -+OR - -- The product ships with a GenericSetup extension profile -- and has no install method. It can still use an uninstall -- method for custom uninstallation tasks though. -+- The addon/product ships with a GenericSetup extension profile (but has no install method as above). -+ If there are multiple profiles the alphabetically first wins. - --Products can be uninstalled and QuickInstallerTool removes the following items --a product creates during install: -+Products can be uninstalled and QuickInstallerTool removes the following items a product creates during install: - - - portal actions, - - portal skins, -@@ -34,33 +29,29 @@ a product creates during install: - - left and right slots (also checks them only for the portal), - - resource registry entries - --Attention: -- --QuickInstallerTool just tracks which objects are ADDED, but not what is changed --or deleted. -+.. note:: -+ QuickInstallerTool just tracks which objects are **added**, but not what is changed or deleted. - - Usage - ----- - --In the ZMI click on portal_quickinstaller. The management screen allows you to --select products for installation and uninstallation. You can browse into the --installed products and see what was created and the logs of the install process. -+In the ZMI click on portal_quickinstaller. -+The management screen allows you to select products for installation and uninstallation. -+You can browse into the installed products and see what was created and the logs of the install process. - - Customized uninstall - -------------------- - --In order to use a customize uninstall, the following --requirements must be met:: -+In order to use a customize uninstall, the following requirements must be met: -+ -+- the product has to implement an external method ``uninstall`` in a python module ``Install.py`` in its ``Extensions`` directory. -+ Please note that the customized uninstall method is invoked before (and in addition to) the standard removal of objects. - -- External Product: The product has to implement an external -- method 'uninstall in a python module 'Install.py' -- in its Extensions directory. -+OR - --Please note that the customized uninstall method is invoked before (and in --addition to) the standard removal of objects. -+- the addon/product has to ship with a GenericSetup extension profile postfixed with ``uninstall``. -+ That will be run on uninstall only if there is no external method ``uninstall``. - --Alternatively you can register a profile 'uninstall'. That will be run on --uninstall if ther is no method 'uninstall'. - - Install: - -------- -@@ -75,9 +66,6 @@ Uninstall: - Reinstall - --------- - --Reinstalling a product invokes uninstall() and install(). If you have special --code which should work differently on reinstall than uninstall/install you can --add a second argument to the install or uninstall method named 'reinstall' which --is true only for a reinstallation. In most cases you shouldn't react differently --when reinstalling! -- -+Reinstalling a product invokes uninstall() and install(). -+If you have special code which should work differently on reinstall than uninstall/install you can add a second argument to the install or uninstall method named 'reinstall' which is true only for a reinstallation. -+In most cases you shouldn't react differently when reinstalling! -diff --git a/setup.py b/setup.py -index 469f41f..3b94ed2 100644 ---- a/setup.py -+++ b/setup.py -@@ -1,48 +1,53 @@ --from setuptools import setup, find_packages -+# -*- coding: utf-8 -*- -+from setuptools import find_packages -+from setuptools import setup - - version = '3.0.10.dev0' -+long_description = open("README.rst").read() -+long_description += '\n' -+long_description += open("CHANGES.rst").read() +diff --git a/src/plone/app/theming/browser/controlpanel.pt b/src/plone/app/theming/browser/controlpanel.pt +index 3058cbe..bbd5c96 100644 +--- a/src/plone/app/theming/browser/controlpanel.pt ++++ b/src/plone/app/theming/browser/controlpanel.pt +@@ -186,11 +186,11 @@ + + + Copy + +
++ tal:attributes="id python:'modal-copy-{0}'.format(theme['name'].replace('.', '-'))"> +

Create copy of

--setup(name='Products.CMFQuickInstallerTool', -- version=version, -- description="CMFQuickInstallerTool is a facility for comfortable " -- "activation/deactivation of CMF compliant products.", -- long_description=open("README.rst").read() + "\n" + \ -- open("CHANGES.rst").read(), -- classifiers=[ -+setup( -+ name='Products.CMFQuickInstallerTool', -+ version=version, -+ description="A facility for comfortable activation/deactivation of CMF " -+ "compliant add ons for Zope.", -+ long_description=long_description, -+ classifiers=[ - "Framework :: Plone", - "Framework :: Plone :: 5.0", - "Framework :: Zope2", - "License :: OSI Approved :: GNU General Public License (GPL)", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", -- ], -- keywords='Zope CMF Plone quickinstall install activation', -- author='Philipp Auersperg', -- author_email='plone-developers@lists.sourceforge.net', -- maintainer='Hanno Schlichting', -- maintainer_email='hannosch@plone.org', -- url='http://pypi.python.org/pypi/Products.CMFQuickInstallerTool', -- license='GPL', -- packages=find_packages(exclude=['ez_setup']), -- namespace_packages=['Products'], -- include_package_data=True, -- zip_safe=False, -- extras_require=dict( -- test=[ -- 'zope.testing', -- 'plone.app.testing', -- ] -- ), -- install_requires=[ -- 'setuptools', -- 'zope.annotation', -- 'zope.component', -- 'zope.i18nmessageid', -- 'zope.interface', -- 'Products.CMFCore', -- 'Products.GenericSetup', -- 'Acquisition', -- 'DateTime', -- 'Zope2', -- ], -+ ], -+ keywords='Zope CMF Plone quickinstall install activation', -+ author='Philipp Auersperg', -+ author_email='plone-developers@lists.sourceforge.net', -+ maintainer='Hanno Schlichting', -+ maintainer_email='hannosch@plone.org', -+ url='http://pypi.python.org/pypi/Products.CMFQuickInstallerTool', -+ license='GPL', -+ packages=find_packages(exclude=['ez_setup']), -+ namespace_packages=['Products'], -+ include_package_data=True, -+ zip_safe=False, -+ extras_require=dict( -+ test=[ -+ 'zope.testing', -+ 'plone.app.testing', -+ ] -+ ), -+ install_requires=[ -+ 'setuptools', -+ 'zope.annotation', -+ 'zope.component', -+ 'zope.i18nmessageid', -+ 'zope.interface', -+ 'Products.CMFCore', -+ 'Products.GenericSetup', -+ 'Acquisition', -+ 'DateTime', -+ 'Zope2', -+ ], - )