diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py
index 03a2ce925183a0411dadb68f88224994af71c281..d0c344e905448689c969c3d9871f81192bc00ac1 100644
--- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py
+++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py
@@ -6899,17 +6899,17 @@ class TestBusinessTemplate(BusinessTemplateMixin):
Updating from:
- portal_categories/test_category/modified
- portal_categories/test_category/modified/container_in_which_child_is_added
- portal_categories/test_category/modified/container_in_which_child_is_added/child_kept
- portal_categories/test_category/modified/removed
+ portal_categories/test_category/modified_category
+ portal_categories/test_category/modified_category/container_in_which_child_is_added
+ portal_categories/test_category/modified_category/container_in_which_child_is_added/child_kept
+ portal_categories/test_category/modified_category/removed
to:
- portal_categories/test_category/modified <-- this will be modified
- portal_categories/test_category/modified/container_in_which_child_is_added
- portal_categories/test_category/modified/container_in_which_child_is_added/child_kept
- portal_categories/test_category/modified/container_in_which_child_is_added/added
+ portal_categories/test_category/modified_category <-- this will be modified
+ portal_categories/test_category/modified_category/container_in_which_child_is_added
+ portal_categories/test_category/modified_category/container_in_which_child_is_added/child_kept
+ portal_categories/test_category/modified_category/container_in_which_child_is_added/added
was causing when test_category was added both as a base category and as paths.
@@ -6920,7 +6920,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
portal_categories.manage_delObjects(['test_category'])
base_category = portal_categories.newContent(portal_type='Base Category', id='test_category')
- parent_category = base_category.newContent(portal_type='Category', id='modified')
+ parent_category = base_category.newContent(portal_type='Category', id='modified_category')
parent_category.newContent(portal_type='Category', id='container_in_which_child_is_added')
parent_category.newContent(portal_type='Category', id='removed')
parent_category.container_in_which_child_is_added.newContent(portal_type='Category', id='child_kept')
@@ -6945,7 +6945,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
shutil.rmtree(export_dir)
# Apply the changes and build a second version of business template
- parent_category.setTitle('modified')
+ parent_category.setTitle('modified_category')
parent_category.container_in_which_child_is_added.newContent(portal_type='Category', id='added')
parent_category.manage_delObjects(['removed'])
@@ -6959,7 +6959,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
self.tic()
new_business_template_version_1.install()
self.tic()
- portal_categories.test_category.modified.container_in_which_child_is_added.setTitle(
+ portal_categories.test_category.modified_category.container_in_which_child_is_added.setTitle(
'This path should not be reinstalled during update'
)
self.tic()
@@ -6971,7 +6971,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
self.tic()
self.assertEqual(
'This path should not be reinstalled during update',
- portal_categories.test_category.modified.container_in_which_child_is_added.getTitle())
+ portal_categories.test_category.modified_category.container_in_which_child_is_added.getTitle())
def test_update_business_template_with_template_keep_path_list_catalog_method(self):
"""Tests for `template_keep_path_list` feature for the special case of catalog methods
diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py
index 6a3bb620b255f48cfa4630ffe76885a091d08946..255b8db2bcccc1e44761c632240b5607c8702923 100644
--- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py
+++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py
@@ -576,7 +576,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
if i.getId() not in ('portal_uidhandler',) and
0 != i.getUid() != i.getProperty('uid')])
- def test_site_manager_and_translation_migration(self):
+ def test_04_site_manager_and_translation_migration(self):
from zope.site.hooks import getSite, setSite
from zope.component import queryUtility
from zope.i18n.interfaces import ITranslationDomain
@@ -594,25 +594,27 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
self.assertEqual(queryUtility(ITranslationDomain, 'ui'), None)
# now let's simulate a site just migrated from Zope 2.8 that's being
# accessed for the first time:
- old_site = getSite()
- try:
+ from Products.ERP5 import ERP5Site
+ if 1: # BBB
setSite()
# Sites from Zope2.8 don't have a site_manager yet.
del self.portal._components
+ self.assertIsNotNone(ERP5Site._missing_tools_registered)
+ ERP5Site._missing_tools_registered = None
+ self.commit()
# check that we can't get any translation utility
self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'), None)
# Now simulate first access. Default behaviour from
# ObjectManager is to raise a ComponentLookupError here:
+
setSite(self.portal)
+ self.commit()
+ self.assertIsNotNone(ERP5Site._missing_tools_registered)
# This should have automatically reconstructed the i18n utility
# registrations:
self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'),
erp5_ui_catalog)
self.assertEqual(queryUtility(ITranslationDomain, 'ui'), erp5_ui_catalog)
- finally:
- # clean everything up, we don't want to mess the test environment
- self.abort()
- setSite(old_site)
def test_BasicAuthenticateDesactivated(self):
"""Make sure Unauthorized error does not lead to Basic auth popup in browser"""
diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py
index 517377c83fd97bd09dfc0278adc13a42eca19b44..371241a22d4a3b49df0732f68873173422232221 100644
--- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py
+++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py
@@ -52,6 +52,7 @@ from AccessControl.ZopeGuards import guarded_getattr, guarded_hasattr
from Products.ERP5Type.tests.utils import createZODBPythonScript
from Products.ERP5Type.tests.utils import removeZODBPythonScript
from Products.ERP5Type import Permissions
+from DateTime import DateTime
class PropertySheetTestCase(ERP5TypeTestCase):
"""Base test case class for property sheets tests.
@@ -2478,8 +2479,9 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
portal = self.getPortalObject()
folder = self.getOrganisationModule()
obj = folder.newContent(portal_type='Organisation')
- self.assertNotEquals(obj.getCreationDate(), portal.CreationDate())
- self.assertNotEquals(obj.getCreationDate(), folder.getCreationDate())
+ self.assertIsInstance(portal.creation_date, DateTime)
+ self.assertLess(portal.creation_date, obj.getCreationDate())
+ self.assertIsNone(folder.getCreationDate())
def test_copyWithoutModificationRight(self):
"""
diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py
index 8329ac300b3215a19c3c6afd609cc03e24647b1f..cafec033a5a88c4bab8aa1f59e8277fe1c26820a 100644
--- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py
+++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py
@@ -638,9 +638,6 @@ class TestProxyField(ERP5TypeTestCase):
self.container = Folder('container').__of__(self.portal)
self.container.manage_addProduct['ERP5Form'].addERP5Form('Base_viewProxyFieldLibrary', 'Proxys')
self.container.manage_addProduct['ERP5Form'].addERP5Form('Base_view', 'View')
- from Products.CMFCore.tests.base.utils import _setUpDefaultTraversable
- _setUpDefaultTraversable()
-
def addField(self, form, id_, title, field_type):
form.manage_addField(id_, title, field_type)
diff --git a/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py b/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py
index 0fdc074e8cb0aaf10a7fac25c0ee6566e7552526..0fa3ce91d22bcd4bc2e638ea40e22e18572f5c34 100644
--- a/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py
+++ b/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py
@@ -17,6 +17,8 @@ Save, download or return generated PDF Document
# doc_pdf_file pdf content to store
# doc_aggregate_list not applicable (only used for events)
+from io import BytesIO
+
if doc_save:
dms_module = getattr(context, 'document_module', None)
if dms_module is not None:
@@ -31,7 +33,7 @@ if doc_save:
)
document.edit(
source_reference=''.join([doc_reference, '.pdf']),
- file=doc_pdf_file
+ file=BytesIO(doc_pdf_file)
)
document.setContentType("application/pdf")
diff --git a/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_export.xml b/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_export.xml
similarity index 100%
rename from bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_export.xml
rename to bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_export.xml
diff --git a/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_import.xml b/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_import.xml
similarity index 100%
rename from bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_import.xml
rename to bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_import.xml
diff --git a/bt5/erp5_csv_style/bt/template_action_path_list b/bt5/erp5_csv_style/bt/template_action_path_list
index 4a6477999a630ef06025973d24becb6e6c5690b2..371e5950e7812c5313677d5a5ea9e35efe0939a9 100644
--- a/bt5/erp5_csv_style/bt/template_action_path_list
+++ b/bt5/erp5_csv_style/bt/template_action_path_list
@@ -1,2 +1,2 @@
-portal_properties | csv_export
-portal_properties | csv_import
\ No newline at end of file
+portal_actions | csv_export
+portal_actions | csv_import
\ No newline at end of file
diff --git a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py
index 51ffc7c1e4a7e6063e72516ed879078b3cfa0a30..8d2698e18d6c3521af70f52b5932dff75ae3f0d9 100644
--- a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py
+++ b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py
@@ -672,10 +672,9 @@ class TestDocument(TestDocumentMixin):
for document_type in portal.getPortalDocumentTypeList():
module = portal.getDefaultModule(document_type)
obj = module.newContent(portal_type=document_type)
- self.assertNotEquals(obj.getCreationDate(),
- module.getCreationDate())
- self.assertNotEquals(obj.getCreationDate(),
- portal.CreationDate())
+ self.assertIsInstance(portal.creation_date, DateTime)
+ self.assertLess(portal.creation_date, obj.getCreationDate())
+ self.assertIsNone(module.getCreationDate())
def test_06_ProcessingStateOfAClonedDocument(self):
"""
@@ -2865,7 +2864,7 @@ class TestDocumentWithSecurity(TestDocumentMixin):
reference='Foo_001',
title='Foo_OO1')
f = makeFileUpload('Foo_001.odt')
- text_document.edit(file=f.read())
+ text_document.edit(file=f)
f.close()
self.tic()
@@ -2967,4 +2966,4 @@ def test_suite():
return suite
-# vim: syntax=python shiftwidth=2
\ No newline at end of file
+# vim: syntax=python shiftwidth=2
diff --git a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py
index ea84e7aa07da915226972d29a22f98dc85cca0d1..fbe605bfa521151c2722bc39b0265dfdcb11b577 100644
--- a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py
+++ b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py
@@ -186,12 +186,14 @@ class TestWebDavSupport(ERP5TypeTestCase):
"""
iso_text_content = text_content.decode('utf-8').encode('iso-8859-1')
path = web_page_module.getPath()
- response = self.publish('%s/%s' % (path, filename),
- request_method='PUT',
- stdin=StringIO(iso_text_content),
- basic=self.authentication)
- self.assertEqual(response.getStatus(), httplib.NO_CONTENT)
- self.assertEqual(web_page_module[filename].getData(), iso_text_content)
+ for _ in xrange(2): # Run twice to check the code that compares
+ # old & new data when setting file attribute.
+ response = self.publish('%s/%s' % (path, filename),
+ request_method='PUT',
+ stdin=StringIO(iso_text_content),
+ basic=self.authentication)
+ self.assertEqual(response.getStatus(), httplib.NO_CONTENT)
+ self.assertEqual(web_page_module[filename].getData(), iso_text_content)
# Convert to base format and run conversion into utf-8
self.tic()
# Content-Type header is replaced if sonversion encoding succeed
diff --git a/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py b/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py
index fd7bd45009f23b9356c2c6ac4972b113f5b6fa1c..487f6ea1dbceba5d7b17d66fbbb7d5cdef2d34b6 100644
--- a/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py
+++ b/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py
@@ -34,6 +34,7 @@ import random
import tempfile
from xml.dom.minidom import getDOMImplementation
from App.config import getConfiguration
+from Products.CMFCore.ActionsTool import ActionsTool
from Products.ERP5.Document.BusinessTemplate import \
BusinessTemplateMissingDependency
@@ -51,7 +52,6 @@ class TestTemplateTool(ERP5TypeTestCase):
return ('erp5_core_proxy_field_legacy',
'erp5_full_text_mroonga_catalog',
'erp5_base',
- 'erp5_csv_style',
'erp5_crm',
'erp5_forge')
@@ -140,7 +140,7 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEqual(test_web.getTitle(), 'test_web')
self.assertEqual(len(test_web.getRevision()), 28)
- def test_updateBusinessTemplateFromUrl_simple(self):
+ def test_00_updateBusinessTemplateFromUrl_simple(self):
"""
Test updateBusinessTemplateFromUrl method
@@ -148,13 +148,42 @@ class TestTemplateTool(ERP5TypeTestCase):
the new bt5 is not installed, only imported.
"""
self._svn_setup_ssl()
+ global PropertiesTool
+ class PropertiesTool(ActionsTool):
+ id = 'portal_properties'
+ cls = PropertiesTool
+
+ # Assign a fake properties tool to the portal
+ tool = PropertiesTool()
+ self.portal._setObject(tool.id, tool, set_owner=False, suppress_events=True)
+ del tool
+ self.commit()
+
template_tool = self.portal.portal_templates
+
+ url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style'
+ template_tool.updateBusinessTemplateFromUrl(url)
old_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style')
# fake different revision
old_bt.setRevision('')
- url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style'
- template_tool.updateBusinessTemplateFromUrl(url)
- new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style')
+
+ # Break the properties tool
+ self.assertIs(self.portal.portal_properties.__class__, cls)
+ self.commit()
+ self.portal._p_jar.cacheMinimize()
+ del PropertiesTool
+ self.assertIsNot(self.portal.portal_properties.__class__, cls)
+
+ # Remove portal.portal_properties
+ from Products.ERP5Type.dynamic.portal_type_class import \
+ _bootstrapped, synchronizeDynamicModules
+ _bootstrapped.remove(self.portal.id)
+ synchronizeDynamicModules(self.portal, force=True)
+
+ # The bt from this repo
+ url = self._getBTPathAndIdList(('erp5_csv_style',))[0][0]
+
+ new_bt = template_tool.updateBusinessTemplateFromUrl(url)
self.assertNotEquals(old_bt, new_bt)
self.assertEqual('erp5_csv_style', new_bt.getTitle())
diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py
index 85f8b6d2052d0a2d2a36bad687668196ccfc113b..8dae64b4693eac711d8395593d02a958d3c976b6 100644
--- a/product/ERP5/Document/BusinessTemplate.py
+++ b/product/ERP5/Document/BusinessTemplate.py
@@ -1869,6 +1869,7 @@ class ToolTemplateItem(PathTemplateItem):
def install(self, context, trashbin, **kw):
""" When we install a tool that is a type provider not
registered on types tool, register it into the type provider.
+ We also need to register the tool on the site manager
"""
PathTemplateItem.install(self, context, trashbin, **kw)
portal = context.getPortalObject()
@@ -1879,6 +1880,8 @@ class ToolTemplateItem(PathTemplateItem):
type_container_id not in types_tool.type_provider_list):
types_tool.type_provider_list = tuple(types_tool.type_provider_list) + \
(type_container_id,)
+ tool_id_list = list(set(self._objects.keys()) & set(portal._registry_tool_id_list))
+ portal._registerTools(tool_id_list)
def uninstall(self, context, **kw):
""" When we uninstall a tool, unregister it from the type provider. """
@@ -2121,7 +2124,7 @@ class RegisteredSkinSelectionTemplateItem(BaseTemplateItem):
update_dict = kw.get('object_to_update')
force = kw.get('force')
portal = context.getPortalObject()
- skin_tool = getToolByName(portal, 'portal_skins')
+ skin_tool = portal.portal_skins
for skin_folder_id in self._objects.keys():
@@ -2163,7 +2166,7 @@ class RegisteredSkinSelectionTemplateItem(BaseTemplateItem):
def uninstall(self, context, **kw):
portal = context.getPortalObject()
- skin_tool = getToolByName(portal, 'portal_skins')
+ skin_tool = portal.portal_skins
object_path = kw.get('object_path')
for skin_folder_id in (object_path,) if object_path else self._objects:
skin_selection_list = self._objects[skin_folder_id]
diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py
index 0a750092d59a52df0792704eb4561f6e6714af6d..89e7a77ff35929ec9e793f645774f0ab19ab9188 100644
--- a/product/ERP5/ERP5Site.py
+++ b/product/ERP5/ERP5Site.py
@@ -15,6 +15,8 @@
Portal class
"""
+from DateTime import DateTime
+from six.moves import map
import thread, threading
from weakref import ref as weakref
from OFS.Application import Application, AppInitializer
@@ -25,7 +27,7 @@ from Products.SiteErrorLog.SiteErrorLog import manage_addErrorLog
from ZPublisher import BeforeTraverse
from ZPublisher.BaseRequest import RequestContainer
from AccessControl import ClassSecurityInfo
-from Products.CMFDefault.Portal import CMFSite
+from Products.CMFCore.PortalObject import PortalObjectBase
from Products.ERP5Type import Permissions
from Products.ERP5Type.Core.Folder import FolderMixIn
from Acquisition import aq_base
@@ -36,7 +38,8 @@ from Products.ERP5Type.ERP5Type import ERP5TypeInformation
from Products.ERP5Type.patches.CMFCoreSkinnable import SKINDATA, skinResolve
from Products.CMFActivity.Errors import ActivityPendingError
import ERP5Defaults
-from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
+from Products.ERP5Type.TransactionalVariable import \
+ getTransactionalVariable, TransactionalResource
from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules
from Products.ERP5Type.mixin.response_header_generator import ResponseHeaderGenerator
@@ -227,8 +230,9 @@ class _site(threading.local):
getSite, setSite = _site()
+_missing_tools_registered = None
-class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
+class ERP5Site(ResponseHeaderGenerator, FolderMixIn, PortalObjectBase, CacheCookieMixin):
"""
The *only* function this class should have is to help in the setup
of a new ERP5. It should not assist in the functionality at all.
@@ -269,6 +273,15 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
+ def __init__(self, id):
+ PortalObjectBase.__init__(self, id)
+ self.creation_date = DateTime()
+
+ security.declarePrivate('reindexObject')
+ def reindexObject(self, idxs=[]):
+ """from Products.CMFDefault.Portal"""
+ pass
+
security.declarePublic('isSubtreeIndexable')
def isSubtreeIndexable(self):
"""
@@ -328,7 +341,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
from Products.Localizer.MessageCatalog import (
message_catalog_alias_sources
)
- sm = self.getSiteManager()
+ sm = self._components
for message_catalog in self.Localizer.objectValues():
sm.registerUtility(message_catalog,
provided=ITranslationDomain,
@@ -338,21 +351,31 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
provided=ITranslationDomain,
name=alias)
- def _doInitialSiteManagerMigration(self):
- self._createInitialSiteManager()
- # Now that we have a sitemanager, se can do things that require
- # one. Including setting up ZTK style utilities and adapters. We
- # can even call setSite(self), as long as we roll back that later,
- # since we are actually in the middle of a setSite() call.
- from zope.site.hooks import getSite, setSite
- old_site = getSite()
- try:
- setSite(self)
- # setSite(self) is not really necessary for the migration below, but
- # could be needed by other migrations to be added here.
- self._doTranslationDomainRegistration()
- finally:
- setSite(old_site)
+ _registry_tool_id_list = 'caching_policy_manager',
+ def _registerMissingTools(self):
+ tool_id_list = ("portal_skins", "portal_types", "portal_membership",
+ "portal_url", "portal_workflow")
+ if (None in map(self.get, tool_id_list) or not
+ TransactionalResource.registerOnce(__name__, 'site_manager', self.id)):
+ return
+ self._registerTools(tool_id_list + self._registry_tool_id_list)
+ def markRegistered(txn):
+ global _missing_tools_registered
+ _missing_tools_registered = self.id
+ TransactionalResource(tpc_finish=markRegistered)
+
+ def _registerTools(self, tool_id_list):
+ from Products.CMFCore import interfaces, utils
+ sm = self._components
+ for tool_id in tool_id_list:
+ tool = self.get(tool_id, None)
+ if tool:
+ tool_interface = utils._tool_interface_registry.get(tool_id)
+ if tool_interface is not None:
+ # Note: already registered tools will be either:
+ # - updated
+ # - registered again after being unregistered
+ sm.registerUtility(aq_base(tool), tool_interface)
# backward compatibility auto-migration
def getSiteManager(self):
@@ -369,14 +392,29 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
# OFS.ObjectManager.ObjectManager.getSiteManager(), and is exactly
# as cheap as it is on the case that self._components is already
# set.
+
+ if self.id == _missing_tools_registered:
+ return self._components # fast path
_components = self._components
- if _components is not None:
- return _components
- # This method below can take as (reasonably) long as it pleases
- # since it will not be run ever again
- self._doInitialSiteManagerMigration()
- assert self._components is not None, 'Migration Failed!'
- return self._components
+ if _components is None:
+ # only create _components
+ self._createInitialSiteManager()
+ _components = self._components
+ # Now that we have a sitemanager, se can do things that require
+ # one. Including setting up ZTK style utilities and adapters. We
+ # can even call setSite(self), as long as we roll back that later,
+ # since we are actually in the middle of a setSite() call.
+ from zope.site.hooks import getSite, setSite
+ old_site = getSite()
+ try:
+ setSite(self)
+ self._doTranslationDomainRegistration()
+ self._registerMissingTools()
+ finally:
+ setSite(old_site)
+ else:
+ self._registerMissingTools()
+ return _components
security.declareProtected(Permissions.View, 'view')
def view(self):
@@ -387,7 +425,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
return self.index_html()
def __of__(self, parent):
- self = CMFSite.__of__(self, parent)
+ self = PortalObjectBase.__of__(self, parent)
# Use a transactional variable for performance reason,
# since ERP5Site.__of__ is called quite often.
tv = getTransactionalVariable()
@@ -471,7 +509,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
path_item_list=path_item_list,
new_id=new_id)
# Rename the object
- return CMFSite.manage_renameObject(self, id=id, new_id=new_id,
+ return PortalObjectBase.manage_renameObject(self, id=id, new_id=new_id,
REQUEST=REQUEST)
@@ -589,7 +627,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
# _getProperty is missing, but since there are no protected properties
# on an ERP5 Site, we can just use getProperty instead.
- _getProperty = CMFSite.getProperty
+ _getProperty = PortalObjectBase.getProperty
security.declareProtected(Permissions.AccessContentsInformation, 'getUid')
def getUid(self):
@@ -703,7 +741,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin):
email_from_address, email_from_name,
validate_email
):
- CMFSite.setupDefaultProperties(self, p, title, description,
+ PortalObjectBase.setupDefaultProperties(self, p, title, description,
email_from_address, email_from_name,
validate_email)
@@ -1863,7 +1901,7 @@ factory_type_information = () # No original CMF portal_types installed by defaul
class PortalGenerator:
- klass = CMFSite
+ klass = PortalObjectBase
def setupTools(self, p):
"""Set up initial tools"""
@@ -1872,29 +1910,13 @@ class PortalGenerator:
addCMFCoreTool('CMF Actions Tool', None)
addCMFCoreTool('CMF Catalog', None)
addCMFCoreTool('CMF Member Data Tool', None)
+ addCMFCoreTool('CMF Membership Tool', None)
+ addCMFCoreTool('CMF Registration Tool', None)
addCMFCoreTool('CMF Skins Tool', None)
addCMFCoreTool('CMF Undo Tool', None)
addCMFCoreTool('CMF URL Tool', None)
addCMFCoreTool('CMF Workflow Tool', None)
- addCMFDefaultTool = p.manage_addProduct['CMFDefault'].manage_addTool
- addCMFDefaultTool('Default Discussion Tool', None)
- addCMFDefaultTool('Default Membership Tool', None)
- addCMFDefaultTool('Default Registration Tool', None)
- addCMFDefaultTool('Default Properties Tool', None)
- addCMFDefaultTool('Default Metadata Tool', None)
- addCMFDefaultTool('Default Syndication Tool', None)
-
- # try to install CMFUid without raising exceptions if not available
- try:
- addCMFUidTool = p.manage_addProduct['CMFUid'].manage_addTool
- except AttributeError:
- pass
- else:
- addCMFUidTool('Unique Id Annotation Tool', None)
- addCMFUidTool('Unique Id Generator Tool', None)
- addCMFUidTool('Unique Id Handler Tool', None)
-
def setupMailHost(self, p):
p.manage_addProduct['MailHost'].manage_addMailHost(
'MailHost', smtp_host='localhost')
@@ -2194,8 +2216,6 @@ class ERP5Generator(PortalGenerator):
'manage_members'))
# actions tool
removeActionsFromTool(p.portal_actions, ('folderContents',))
- # properties tool
- removeActionsFromTool(p.portal_properties, ('configPortal',))
# remove unused action providers
for i in ('portal_registration', 'portal_discussion', 'portal_syndication'):
p.portal_actions.deleteActionProvider(i)
@@ -2206,35 +2226,14 @@ class ERP5Generator(PortalGenerator):
"""
pass
- # this lists only the skin layers of Products.CMFDefault we are actually
- # interested in.
- CMFDEFAULT_FOLDER_LIST = ['Images']
- def addCMFDefaultDirectoryViews(self, p):
- """Semi-manually create DirectoryViews since CMFDefault 2.X no longer
- registers the "skins" directory, only its subdirectories, making it
- unusable with Products.CMFCore.DirectoryView.addDirectoryViews."""
- from Products.CMFCore.DirectoryView import createDirectoryView, _generateKey
- import Products.CMFDefault
-
- ps = p.portal_skins
- # get the layer directories actually present
- for cmfdefault_skin_layer in self.CMFDEFAULT_FOLDER_LIST:
- reg_key = _generateKey(Products.CMFDefault.__name__,
- 'skins/' + cmfdefault_skin_layer)
- createDirectoryView(ps, reg_key)
-
def setupDefaultSkins(self, p):
ps = p.portal_skins
- self.addCMFDefaultDirectoryViews(p)
ps.manage_addProduct['OFSP'].manage_addFolder(id='external_method')
ps.manage_addProduct['OFSP'].manage_addFolder(id='custom')
# Set the 'custom' layer a high priority, so it remains the first
# layer when installing new business templates.
ps['custom'].manage_addProperty("business_template_skin_layer_priority", 100.0, "float")
- skin_folder_list = [ 'custom'
- , 'external_method'
- ] + self.CMFDEFAULT_FOLDER_LIST
- skin_folders = ', '.join(skin_folder_list)
+ skin_folders = ', '.join(('custom', 'external_method'))
ps.addSkinSelection( 'View'
, skin_folders
, make_default = 1
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py
index 0b99670ca9917c635d5511f781e74c77175e3d9d..c48c145f5743da3b6406c3f02c696947e115605b 100644
--- a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py
@@ -34,8 +34,9 @@ from erp5.component.document.Document import Document, VALID_TEXT_FORMAT_LIST
from erp5.component.document.Document import VALID_IMAGE_FORMAT_LIST
from erp5.component.document.Document import ConversionError
from Products.ERP5Type.Base import Base, removeIContentishInterface
-from Products.CMFDefault.File import File as CMFFile
+from OFS.Image import File as OFS_File
from Products.ERP5Type.Utils import deprecated
+from cStringIO import StringIO
def _unpackData(data):
"""
@@ -46,7 +47,7 @@ def _unpackData(data):
_MARKER = object()
-class File(Document, CMFFile):
+class File(Document, OFS_File):
"""
A File can contain raw data which can be uploaded and downloaded.
It is the root class of Image, OOoDocument (ERP5OOo product),
@@ -110,8 +111,14 @@ class File(Document, CMFFile):
filename = kw.get('filename')
if filename:
self._setFilename(filename)
- if self._isNotEmpty(file_object):
- self._setFile(file_object, precondition=precondition)
+ if file_object is not None:
+ # XXX: Rather than doing nothing if empty, consider changing:
+ # - _update_image_info to clear metadata
+ # - interactions to do nothing (or else?)
+ file_object.seek(0, 2)
+ if file_object.tell():
+ file_object.seek(0)
+ self._setFile(file_object)
Base._edit(self, **kw)
security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
@@ -138,11 +145,18 @@ class File(Document, CMFFile):
return None
def _setFile(self, data, precondition=None):
- if data is not None and self.hasData() and \
- str(data.read()) == str(self.getData()):
- # Same data as previous, no need to change it's content
+ if data is None:
return
- CMFFile._edit(self, precondition=precondition, file=data)
+ if self.hasData():
+ if str(data.read()) == str(self.getData()):
+ # Same data as previous, no need to change its content
+ return
+ else:
+ data.seek(0, 2)
+
+ if data.tell():
+ data.seek(0)
+ self.manage_upload(data)
security.declareProtected(Permissions.ModifyPortalContent,'setFile')
def setFile(self, data, precondition=None):
@@ -176,11 +190,16 @@ class File(Document, CMFFile):
return str(data)
# DAV Support
- PUT = CMFFile.PUT
+ security.declareProtected(Permissions.ModifyPortalContent, 'PUT')
+ def PUT(self, REQUEST, RESPONSE):
+ """from Products.CMFDefault.File"""
+ OFS_File.PUT(self, REQUEST, RESPONSE)
+ self.reindexObject()
+
security.declareProtected(Permissions.FTPAccess, 'manage_FTPstat',
'manage_FTPlist')
- manage_FTPlist = CMFFile.manage_FTPlist
- manage_FTPstat = CMFFile.manage_FTPstat
+ manage_FTPlist = OFS_File.manage_FTPlist
+ manage_FTPstat = OFS_File.manage_FTPstat
security.declareProtected(Permissions.AccessContentsInformation, 'getMimeTypeAndContent')
def getMimeTypeAndContent(self):
diff --git a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py
index 8bc2d6fd1e215c62fb85b3d937669ee795a04588..1e66ee17a66379f523207b380df0d59ca08dfa7d 100644
--- a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py
@@ -23,6 +23,7 @@ from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type import Permissions
from Products.CMFCore.PortalContent import ResourceLockedError
from zExceptions import Forbidden
+from cStringIO import StringIO
security = ModuleSecurityInfo(__name__)
@@ -76,7 +77,7 @@ class TextContent:
headers = self.parseHeadersFromText(body)
content_type = REQUEST.get_header('Content-Type', '')
headers.setdefault('content_type', content_type)
- headers['file'] = body
+ headers['file'] = StringIO(body)
self._edit(**headers)
except ResourceLockedError:
transaction.abort()
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py
index d66fba1a1581353d71fc33bc0baab3f526a0c483..3cdd44270ab6d93a4a8902afbc3090209c994e7a 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py
@@ -85,7 +85,6 @@ print reindex(
'portal_classes',
'portal_preferences',
'portal_simulation',
- 'portal_uidhandler',
) and
'inventory' not in x.id
],
diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py
index 198dcef624bc85966a648b718aca393c6be9cc03..9b708accb31c8f2089fc6e6977e3fb80117b8dd9 100644
--- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py
+++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py
@@ -1,31 +1,29 @@
"Modified version for ERP5 to append the default action (/view) in the URL."
-from Products.CMFCore.utils import getToolByName
-ptool = getToolByName(script, 'portal_properties')
-utool = getToolByName(script, 'portal_url')
+portal = context.getPortalObject()
+
+utool = portal.portal_url
portal_url = utool()
-result = []
-param = int(context.REQUEST.get('ignore_layout', 0)) and '?ignore_layout:int=1' or ''
+param = '?ignore_layout:int=1' if int(portal.REQUEST.get('ignore_layout', 0)) else ''
if include_root:
- result.append( { 'id' : 'root'
- , 'title' : ptool.title()
- , 'url' : '%s/view%s' % (portal_url, param)
- }
- )
-
-relative = utool.getRelativeContentPath(context)
-portal = utool.getPortalObject()
+ result = [{
+ 'id' : 'root',
+ 'title' : portal.title,
+ 'url' : '%s/view%s' % (portal_url, param),
+ }]
+else:
+ result = []
obj = portal
now = []
-for name in relative:
+for name in utool.getRelativeContentPath(context):
obj = obj.restrictedTraverse(name)
now.append(name)
title = (
getattr(obj, "getCompactTranslatedTitle", lambda: None)() or
obj.getTitle() or obj.getId()
)
- if not name == 'talkback':
+ if name != 'talkback':
result.append( { 'id' : name
, 'title' : title
, 'url' : '%s/%s/view%s' % (portal_url, '/'.join(now), param)
diff --git a/product/ERP5OOo/tests/testFormPrintoutAsODG.py b/product/ERP5OOo/tests/testFormPrintoutAsODG.py
index cea68a5736992142a24de5a7cd2b88def96786b3..c084fc634059ded0a60f6f8f7c051be7ddf936b3 100644
--- a/product/ERP5OOo/tests/testFormPrintoutAsODG.py
+++ b/product/ERP5OOo/tests/testFormPrintoutAsODG.py
@@ -69,7 +69,6 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin):
addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile
if custom._getOb('Foo_getODGStyleSheet', None) is None:
addStyleSheet(id='Foo_getODGStyleSheet', file=foo_file, title='',
- precondition='',
content_type='application/vnd.oasis.opendocument.graphics')
erp5OOo = custom.manage_addProduct['ERP5OOo']
diff --git a/product/ERP5OOo/tests/testFormPrintoutAsODT.py b/product/ERP5OOo/tests/testFormPrintoutAsODT.py
index 63016ef9e6faed83e6da6e39b0bd1d4d28d48f87..083f64e1dd7931cbb62066860bafb0a940133d6f 100644
--- a/product/ERP5OOo/tests/testFormPrintoutAsODT.py
+++ b/product/ERP5OOo/tests/testFormPrintoutAsODT.py
@@ -84,23 +84,22 @@ class TestFormPrintoutAsODT(TestFormPrintoutMixin):
addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile
if custom._getOb('Foo_getODTStyleSheet', None) is None:
addStyleSheet(id='Foo_getODTStyleSheet', file=foo_file, title='',
- precondition='', content_type = 'application/vnd.oasis.opendocument.text')
+ content_type = 'application/vnd.oasis.opendocument.text')
if custom._getOb('Foo2_getODTStyleSheet', None) is None:
addStyleSheet(id='Foo2_getODTStyleSheet', file=foo2_file, title='',
- precondition='', content_type = 'application/vnd.oasis.opendocument.text')
+ content_type = 'application/vnd.oasis.opendocument.text')
if custom._getOb('Foo3_getODTStyleSheet', None) is None:
addStyleSheet(id='Foo3_getODTStyleSheet', file=foo3_file, title='',
- precondition='', content_type = 'application/vnd.oasis.opendocument.text')
+ content_type = 'application/vnd.oasis.opendocument.text')
if custom._getOb('Foo4_getODTStyleSheet', None) is None:
addStyleSheet(id='Foo4_getODTStyleSheet', file=foo4_file, title='',
- precondition='', content_type = 'application/vnd.oasis.opendocument.text')
+ content_type = 'application/vnd.oasis.opendocument.text')
if custom._getOb('Foo5_getODTStyleSheet', None) is None:
addStyleSheet(id='Foo5_getODTStyleSheet', file=foo5_file, title='',
- precondition='', content_type = 'application/vnd.oasis.opendocument.text')
+ content_type = 'application/vnd.oasis.opendocument.text')
if custom._getOb('Foo_getVariableODTStyleSheet', None) is None:
addStyleSheet(id='Foo_getVariableODTStyleSheet',
file=variable_file_object, title='',
- precondition='',
content_type='application/vnd.oasis.opendocument.text')
erp5OOo = custom.manage_addProduct['ERP5OOo']
diff --git a/product/ERP5OOo/tests/testOOoBatchMode.py b/product/ERP5OOo/tests/testOOoBatchMode.py
index fb3c41ef6ddb92ceea8a75b8e073e753e327324e..a9bdb97f678760aad491e538d8b3455218e35cf1 100644
--- a/product/ERP5OOo/tests/testOOoBatchMode.py
+++ b/product/ERP5OOo/tests/testOOoBatchMode.py
@@ -66,7 +66,7 @@ class TestOoodResponse(ERP5TypeTestCase):
custom = portal_skins.custom
addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile
addStyleSheet(id='Base_getODTStyleSheet', file=import_file, title='',
- precondition='', content_type='application/vnd.oasis.opendocument.text')
+ content_type='application/vnd.oasis.opendocument.text')
addOOoTemplate = custom.manage_addProduct['ERP5OOo'].addOOoTemplate
addOOoTemplate(id='ERP5Site_viewNothingAsOdt', title='')
portal_skins.changeSkin(skinname=None)
diff --git a/product/ERP5OOo/tests/testOOoDynamicStyle.py b/product/ERP5OOo/tests/testOOoDynamicStyle.py
index 85a5cd1d9967c60e6c32a71f5dcc8e7d7906e025..b3ab7579f9b7b08acc5a780973b99b3ed92004b4 100644
--- a/product/ERP5OOo/tests/testOOoDynamicStyle.py
+++ b/product/ERP5OOo/tests/testOOoDynamicStyle.py
@@ -76,10 +76,10 @@ class TestOooDynamicStyle(ERP5TypeTestCase):
addStyleSheet = self.getPortal().manage_addProduct['OFSP'].manage_addFile
if getattr(self.getPortal(), 'Test_getODTStyleSheet_en', None) is None:
addStyleSheet(id='Test_getODTStyleSheet_en', file=en_file, title='',
- precondition='', content_type=self.content_type_writer)
+ content_type=self.content_type_writer)
if getattr(self.getPortal(), 'Test_getODTStyleSheet_ja', None) is None:
addStyleSheet(id='Test_getODTStyleSheet_ja', file=ja_file, title='',
- precondition='', content_type=self.content_type_writer)
+ content_type=self.content_type_writer)
if getattr(self.getPortal(), 'Base_getODTStyleSheetByLanguage', None) is None:
script_body = """
current_language = context.Localizer.get_selected_language()
diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py
index 293bfadd4483befb751e4d7934a3b3629438092c..f137c418586233d387de18ed9edd1a0b9f2b66da 100644
--- a/product/ERP5Type/Base.py
+++ b/product/ERP5Type/Base.py
@@ -81,7 +81,6 @@ from CopySupport import CopyContainer, CopyError,\
from Errors import DeferredCatalogError, UnsupportedWorkflowMethod
from Products.CMFActivity.ActiveObject import ActiveObject
from Products.ERP5Type.Accessor.Accessor import Accessor as Method
-from Products.ERP5Type.Accessor.TypeDefinition import asDate
from Products.ERP5Type.Message import Message
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod, super_user
@@ -3229,9 +3228,8 @@ class Base(
if history and 'time' in history[0])
except ValueError:
pass
- if getattr(aq_base(self), 'CreationDate', None) is not None:
- return asDate(self.CreationDate())
- return None # JPS-XXX - try to find a way to return a creation date instead of None
+ if getattr(aq_base(self), 'creation_date', None):
+ return self.creation_date.toZone(DateTime().timezone())
security.declareProtected(Permissions.AccessContentsInformation, 'getModificationDate')
def getModificationDate(self):
@@ -3260,7 +3258,13 @@ class Base(
# Return a copy of history time, to prevent modification
return DateTime(max_date)
if self._p_serial:
- return DateTime(TimeStamp(self._p_serial).timeTime())
+ return DateTime(self._p_mtime)
+
+ security.declareProtected(Permissions.AccessContentsInformation, 'modified')
+ def modified(self):
+ warnings.warn('modified is a deprecated alias to getModificationDate.',
+ DeprecationWarning)
+ return self.getModificationDate()
# Layout management
security.declareProtected(Permissions.AccessContentsInformation, 'getApplicableLayout')
diff --git a/product/ERP5Type/ZopePatch.py b/product/ERP5Type/ZopePatch.py
index d1a1061cb897c5a9d393c4e2134979f0e8137633..02c049c64bcb39dd9ad8e6dd5d868bb05d09275e 100644
--- a/product/ERP5Type/ZopePatch.py
+++ b/product/ERP5Type/ZopePatch.py
@@ -34,6 +34,7 @@ from Products.ERP5Type.patches import DCWorkflow
from Products.ERP5Type.patches import Worklists
from Products.ERP5Type.patches import BTreeFolder2
from Products.ERP5Type.patches import WorkflowTool
+from Products.ERP5Type.patches import DynamicType
from Products.ERP5Type.patches import XMLExportImport
from Products.ERP5Type.patches import ppml
from Products.ERP5Type.patches import Expression
diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py
index f5623e4af4a0ebd710ecbcf5fe6575d1a895f7de..00ff7d12070412cdae7ebaf63cd4be9f075e5dc7 100644
--- a/product/ERP5Type/__init__.py
+++ b/product/ERP5Type/__init__.py
@@ -189,3 +189,18 @@ import zExceptions
ModuleSecurityInfo('zExceptions').declarePublic(*filter(
lambda x: Exception in getattr(getattr(zExceptions, x), '__mro__', ()),
dir(zExceptions)))
+
+# BBB : allow load of fomer Products.CMFDefault.MembershipTool
+# that has been replaced by Products.CMFCore.MembershipTool
+try:
+ from Products.CMFDefault.MembershipTool import MembershipTool
+except ImportError:
+ import sys, imp
+ m = 'Products.CMFDefault'
+ sys.modules[m] = imp.new_module(m)
+ m += ".MembershipTool"
+ sys.modules[m] = m = imp.new_module(m)
+ from Products.CMFCore.MembershipTool import MembershipTool
+ m.MembershipTool = MembershipTool
+ del m
+
diff --git a/product/ERP5Type/dynamic/portal_type_class.py b/product/ERP5Type/dynamic/portal_type_class.py
index 57bbcc4b9d545a87191da0e9aabc1fe39a833ae3..e0c36e493f9eea25f464e2a2fabca20ccad335fa 100644
--- a/product/ERP5Type/dynamic/portal_type_class.py
+++ b/product/ERP5Type/dynamic/portal_type_class.py
@@ -468,6 +468,15 @@ def synchronizeDynamicModules(context, force=False):
except AttributeError:
pass # no Activity Tool yet
+ for tool_id in ("portal_properties", "portal_uidannotation",
+ "portal_uidgenerator", "portal_uidhandler"):
+ if portal.hasObject(tool_id):
+ portal._delObject(tool_id, suppress_events=True)
+ migrate = True
+ if tool_id == 'portal_properties':
+ portal.portal_skins.erp5_xhtml_style.breadcrumbs.write(
+ 'return []')
+
if migrate:
portal.migrateToPortalTypeClass()
portal.portal_skins.changeSkin(None)
diff --git a/product/ERP5Type/patches/CMFCoreSkinnable.py b/product/ERP5Type/patches/CMFCoreSkinnable.py
index cdbba4af1ada0d9302ba031fe755b82b11175902..72fd4a74433398c9bdb7d4f70edd5e0cbf5140d8 100644
--- a/product/ERP5Type/patches/CMFCoreSkinnable.py
+++ b/product/ERP5Type/patches/CMFCoreSkinnable.py
@@ -141,11 +141,9 @@ def CMFCoreSkinnableSkinnableObjectManager_changeSkin(self, skinname, REQUEST=No
Patched not to call getSkin.
'''
if skinname is None:
- sfn = self.getSkinsFolderName()
- if sfn is not None:
- sf = getattr(self, sfn, None)
- if sf is not None:
- skinname = sf.getDefaultSkin()
+ sf = getattr(self, "portal_skins", None)
+ if sf is not None:
+ skinname = sf.getDefaultSkin()
tid = get_ident()
SKINDATA[tid] = (
None,
diff --git a/product/ERP5Type/patches/CMFCoreUtils.py b/product/ERP5Type/patches/CMFCoreUtils.py
index 6ac86a93d661c481a41a1d376c041852d77b9826..172d6094d4a24d5e00e6bb378910c3bdda1bac96 100644
--- a/product/ERP5Type/patches/CMFCoreUtils.py
+++ b/product/ERP5Type/patches/CMFCoreUtils.py
@@ -13,24 +13,25 @@
##############################################################################
from Acquisition import aq_parent
-from Products.CMFCore.utils import getToolByName, SUBTEMPLATE
+from Products.CMFCore.utils import SUBTEMPLATE
+from zope.component import queryUtility
+from Products.CMFCore.interfaces import ICachingPolicyManager
# patch _setCacheHeaders so that existing headers are not overridden
def _setCacheHeaders(obj, extra_context):
"""Set cache headers according to cache policy manager for the obj."""
REQUEST = getattr(obj, 'REQUEST', None)
-
if REQUEST is not None:
call_count = getattr(REQUEST, SUBTEMPLATE, 1) - 1
setattr(REQUEST, SUBTEMPLATE, call_count)
if call_count != 0:
- return
+ return
# cleanup
delattr(REQUEST, SUBTEMPLATE)
content = aq_parent(obj)
- manager = getToolByName(obj, 'caching_policy_manager', None)
+ manager = queryUtility(ICachingPolicyManager)
if manager is None:
return
diff --git a/product/ERP5Type/patches/CookieCrumbler.py b/product/ERP5Type/patches/CookieCrumbler.py
index df547957805fcc99194c68e5e88ac1b25606fce1..583a374d75e27659e66f21630c2dd364bcf604ce 100644
--- a/product/ERP5Type/patches/CookieCrumbler.py
+++ b/product/ERP5Type/patches/CookieCrumbler.py
@@ -30,6 +30,7 @@ from App.class_init import InitializeClass
from Products.CMFCore.CookieCrumbler import CookieCrumbler
from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled
from urllib import quote, unquote
+from zExceptions import Redirect
from ZPublisher.HTTPRequest import HTTPRequest
ATTEMPT_NONE = 0 # No attempt at authentication
@@ -47,6 +48,10 @@ class PatchedCookieCrumbler(CookieCrumbler):
security = ClassSecurityInfo()
+CookieCrumbler.auto_login_page = 'login_form'
+CookieCrumbler.unauth_page = ''
+CookieCrumbler.logout_page = 'logged_out'
+
def getLoginURL(self):
'''
Redirects to the login page.
@@ -176,14 +181,175 @@ def modifyRequest(self, req, resp):
CookieCrumbler.modifyRequest = modifyRequest
-def credentialsChanged(self, user, name, pw):
- ac = standard_b64encode('%s:%s' % (name, pw))
- method = self.getCookieMethod( 'setAuthCookie'
- , self.defaultSetAuthCookie )
- resp = self.REQUEST['RESPONSE']
- method( resp, self.auth_cookie, quote( ac ) )
+def credentialsChanged(self, user, name, pw, request=None):
+ """
+ Updates cookie credentials if user details are changed.
+ """
+ if request is None:
+ request = getRequest() # BBB for Membershiptool
+ reponse = request['RESPONSE']
+ #
+ # We don't want new lines, so use base64.standard_b64encode instead of
+ # base64.encodestring
+ ac = standard_b64encode('%s:%s' % (name, pw)).rstrip()
+ #
+ method = self.getCookieMethod('setAuthCookie',
+ self.defaultSetAuthCookie)
+ method(reponse, self.auth_cookie, quote(ac))
CookieCrumbler.credentialsChanged = credentialsChanged
+# The following patches are to keep the original behaviour of automatic
+# redirection to login page. Recent CMF uses a view that is implemented
+# in CMFDefault (UnauthorizedView, on zExceptions.Unauthorized).
+
+class ResponseCleanup:
+ def __init__(self, resp):
+ self.resp = resp
+
+ def __del__(self):
+ # Free the references.
+ #
+ # No errors of any sort may propagate, and we don't care *what*
+ # they are, even to log them.
+ try:
+ del self.resp.unauthorized
+ except Exception:
+ pass
+ try:
+ del self.resp._unauthorized
+ except Exception:
+ pass
+ try:
+ del self.resp
+ except Exception:
+ pass
+
+
+if 1:
+ def __call__(self, container, req):
+ '''The __before_publishing_traverse__ hook.'''
+ resp = req['RESPONSE']
+ try:
+ attempt = self.modifyRequest(req, resp)
+ except CookieCrumblerDisabled:
+ return
+ #
+ if req.get('disable_cookie_login__', 0):
+ return
+
+ if (self.unauth_page or
+ attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE):
+ # Modify the "unauthorized" response.
+ req._hold(ResponseCleanup(resp))
+ resp.unauthorized = self.unauthorized
+ resp._unauthorized = self._unauthorized
+ #
+ if attempt != ATTEMPT_NONE:
+ # Trying to log in or resume a session
+ if self.cache_header_value:
+ # we don't want caches to cache the resulting page
+ resp.setHeader('Cache-Control', self.cache_header_value)
+ # demystify this in the response.
+ resp.setHeader('X-Cache-Control-Hdr-Modified-By',
+ 'CookieCrumbler')
+ phys_path = self.getPhysicalPath()
+ #
+ if self.logout_page:
+ # Cookies are in use.
+ page = getattr(container, self.logout_page, None)
+ if page is not None:
+ # Provide a logout page.
+ req._logout_path = phys_path + ('logout',)
+ req._credentials_changed_path = (
+ phys_path + ('credentialsChanged',))
+ #
+
+ def _cleanupResponse(self):
+ # XXX: this method violates the rules for tools/utilities:
+ # it depends on self.REQUEST
+ resp = self.REQUEST['RESPONSE']
+ # No errors of any sort may propagate, and we don't care *what*
+ # they are, even to log them.
+ try: del resp.unauthorized
+ except Exception: pass
+ try: del resp._unauthorized
+ except Exception: pass
+ return resp
+
+ security.declarePrivate('unauthorized')
+ def unauthorized(self):
+ resp = self._cleanupResponse()
+ # If we set the auth cookie before, delete it now.
+ if resp.cookies.has_key(self.auth_cookie):
+ del resp.cookies[self.auth_cookie]
+ # Redirect if desired.
+ url = self.getUnauthorizedURL()
+ if url is not None:
+ raise Redirect, url
+ # Fall through to the standard unauthorized() call.
+ resp.unauthorized()
+
+ def _unauthorized(self):
+ resp = self._cleanupResponse()
+ # If we set the auth cookie before, delete it now.
+ if resp.cookies.has_key(self.auth_cookie):
+ del resp.cookies[self.auth_cookie]
+ # Redirect if desired.
+ url = self.getUnauthorizedURL()
+ if url is not None:
+ resp.redirect(url, lock=1)
+ # We don't need to raise an exception.
+ return
+ # Fall through to the standard _unauthorized() call.
+ resp._unauthorized()
+
+ security.declarePublic('getUnauthorizedURL')
+ def getUnauthorizedURL(self):
+ '''
+ Redirects to the login page.
+ '''
+ # XXX: this method violates the rules for tools/utilities:
+ # it depends on self.REQUEST
+ req = self.REQUEST
+ resp = req['RESPONSE']
+ attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE)
+ if attempt == ATTEMPT_NONE:
+ # An anonymous user was denied access to something.
+ page_id = self.auto_login_page
+ retry = ''
+ elif attempt == ATTEMPT_LOGIN:
+ # The login attempt failed. Try again.
+ page_id = self.auto_login_page
+ retry = '1'
+ else:
+ # An authenticated user was denied access to something.
+ page_id = self.unauth_page
+ retry = ''
+ if page_id:
+ page = self.restrictedTraverse(page_id, None)
+ if page is not None:
+ came_from = req.get('came_from', None)
+ if came_from is None:
+ came_from = req.get('ACTUAL_URL')
+ query = req.get('QUERY_STRING')
+ if query:
+ # Include the query string in came_from
+ if not query.startswith('?'):
+ query = '?' + query
+ came_from = came_from + query
+ url = '%s?came_from=%s&retry=%s&disable_cookie_login__=1' % (
+ page.absolute_url(), quote(came_from), retry)
+ return url
+ return None
+
+ CookieCrumbler.__call__ = __call__
+ CookieCrumbler._cleanupResponse = _cleanupResponse
+ CookieCrumbler.unauthorized = unauthorized
+ CookieCrumbler._unauthorized = _unauthorized
+ CookieCrumbler.getUnauthorizedURL = getUnauthorizedURL
+
+###
+
CookieCrumbler.security = security
InitializeClass(CookieCrumbler)
diff --git a/product/ERP5Type/patches/DynamicType.py b/product/ERP5Type/patches/DynamicType.py
new file mode 100644
index 0000000000000000000000000000000000000000..6eb6a880211387f167c229d05de7f9a7bdb57f6f
--- /dev/null
+++ b/product/ERP5Type/patches/DynamicType.py
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+from Products.CMFCore.DynamicType import DynamicType
+
+def getTypeInfo(self):
+ """ Get the TypeInformation object specified by the portal type.
+ """
+ #
+ tool = getattr(self.getPortalObject(), "portal_types", None)
+ #
+ if tool is None:
+ return None
+ return tool.getTypeInfo(self) # Can return None.
+
+DynamicType.getTypeInfo = getTypeInfo
diff --git a/product/ERP5Type/patches/Restricted.py b/product/ERP5Type/patches/Restricted.py
index 3e11978ce43f312cf7fc1a2bc8cd51e387f3bd52..9ef0a6f43f5622463cc11479269e11bdad4fa3a5 100644
--- a/product/ERP5Type/patches/Restricted.py
+++ b/product/ERP5Type/patches/Restricted.py
@@ -265,7 +265,9 @@ allow_module('cStringIO')
import cStringIO
allow_type(cStringIO.InputType)
allow_type(cStringIO.OutputType)
-
+allow_module('io')
+import io
+allow_type(io.BytesIO)
ModuleSecurityInfo('cgi').declarePublic('escape', 'parse_header')
allow_module('datetime')
diff --git a/product/ERP5Type/patches/WorkflowTool.py b/product/ERP5Type/patches/WorkflowTool.py
index d27e374781530f52fd8d1e343e9deab514257b06..0363b80db17e5ca02f590cfe481622afde1f4bce 100644
--- a/product/ERP5Type/patches/WorkflowTool.py
+++ b/product/ERP5Type/patches/WorkflowTool.py
@@ -951,4 +951,17 @@ def canDoActionFor(self, ob, action, wf_id=None, guard_kw={}):
WorkflowTool.canDoActionFor = canDoActionFor
+security.declarePrivate('_listTypeInfo')
+def _listTypeInfo(self):
+ """ List the portal types which are available.
+ """
+ #
+ ttool = getattr(self.getPortalObject(), "portal_types", None)
+ #
+ if ttool is not None:
+ return ttool.listTypeInfo()
+ return ()
+
+WorkflowTool._listTypeInfo = _listTypeInfo
+
InitializeClass(WorkflowTool)
diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py
index 61ace0aac7a4aa6ed1a7c24443e9cb6fba44c988..92f9ee387cd0517ea724e50968f26a2437a45590 100644
--- a/product/ERP5Type/tests/ERP5TypeTestCase.py
+++ b/product/ERP5Type/tests/ERP5TypeTestCase.py
@@ -839,6 +839,10 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase):
PAS._extractUserIds = orig_extractUserIds
# Restore security manager
setSecurityManager(sm)
+ # Restore site removed by closing of request
+ setSite(self.portal)
+
+
# Make sure that the skin cache does not have objects that were
# loaded with the connection used by the requested url.