Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Alexander Emmerich
erp5
Commits
2d8b3838
Commit
2d8b3838
authored
Mar 12, 2012
by
Arnaud Fontaine
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Complete comments in source code of ZODB Components.
parent
fe42a522
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
351 additions
and
70 deletions
+351
-70
product/ERP5/Document/BusinessTemplate.py
product/ERP5/Document/BusinessTemplate.py
+41
-5
product/ERP5/ERP5Site.py
product/ERP5/ERP5Site.py
+7
-3
product/ERP5Type/Core/DocumentComponent.py
product/ERP5Type/Core/DocumentComponent.py
+5
-2
product/ERP5Type/Core/ExtensionComponent.py
product/ERP5Type/Core/ExtensionComponent.py
+4
-2
product/ERP5Type/Core/TestComponent.py
product/ERP5Type/Core/TestComponent.py
+5
-2
product/ERP5Type/Permissions.py
product/ERP5Type/Permissions.py
+3
-0
product/ERP5Type/Tool/ComponentTool.py
product/ERP5Type/Tool/ComponentTool.py
+21
-9
product/ERP5Type/Tool/TypesTool.py
product/ERP5Type/Tool/TypesTool.py
+2
-1
product/ERP5Type/dynamic/component_package.py
product/ERP5Type/dynamic/component_package.py
+51
-18
product/ERP5Type/dynamic/dynamic_module.py
product/ERP5Type/dynamic/dynamic_module.py
+8
-3
product/ERP5Type/mixin/component.py
product/ERP5Type/mixin/component.py
+39
-14
product/ERP5Type/patches/ExternalMethod.py
product/ERP5Type/patches/ExternalMethod.py
+22
-0
product/ERP5Type/patches/User.py
product/ERP5Type/patches/User.py
+1
-1
product/ERP5Type/tests/testDynamicClassGeneration.py
product/ERP5Type/tests/testDynamicClassGeneration.py
+142
-10
No files found.
product/ERP5/Document/BusinessTemplate.py
View file @
2d8b3838
...
@@ -3437,8 +3437,8 @@ class ModuleTemplateItem(BaseTemplateItem):
...
@@ -3437,8 +3437,8 @@ class ModuleTemplateItem(BaseTemplateItem):
# Do not remove any module for safety.
# Do not remove any module for safety.
pass
pass
# XXX-arnau: when everything has been migrated to Components,
everything in
# XXX-arnau: when everything has been migrated to Components,
this class
#
this class should be mov
ed to DocumentTemplateItem
#
should be renam
ed to DocumentTemplateItem
class
FilesystemDocumentTemplateItem
(
BaseTemplateItem
):
class
FilesystemDocumentTemplateItem
(
BaseTemplateItem
):
local_file_reader_name
=
staticmethod
(
readLocalDocument
)
local_file_reader_name
=
staticmethod
(
readLocalDocument
)
local_file_writer_name
=
staticmethod
(
writeLocalDocument
)
local_file_writer_name
=
staticmethod
(
writeLocalDocument
)
...
@@ -3667,6 +3667,10 @@ class FilesystemToZodbTemplateItem(FilesystemDocumentTemplateItem,
...
@@ -3667,6 +3667,10 @@ class FilesystemToZodbTemplateItem(FilesystemDocumentTemplateItem,
preinstall
=
_filesystemCompatibilityWrapper
(
'preinstall'
,
'_objects'
)
preinstall
=
_filesystemCompatibilityWrapper
(
'preinstall'
,
'_objects'
)
def
_importFile
(
self
,
file_name
,
*
args
,
**
kw
):
def
_importFile
(
self
,
file_name
,
*
args
,
**
kw
):
"""
Import file by calling the appropriate base class according to the file
name extensions
"""
if
file_name
.
endswith
(
'.xml'
):
if
file_name
.
endswith
(
'.xml'
):
return
ObjectTemplateItem
.
_importFile
(
self
,
file_name
,
*
args
,
**
kw
)
return
ObjectTemplateItem
.
_importFile
(
self
,
file_name
,
*
args
,
**
kw
)
else
:
else
:
...
@@ -3899,10 +3903,18 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
...
@@ -3899,10 +3903,18 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
The migration is performed in two steps:
The migration is performed in two steps:
1/ Copy the Business Template to be migrated;
1/ Copy the Business Template to be migrated;
2/ Run the migration script which will update properly the Document IDs in
the Business Template.
Upon import or export, two files will be created:
2/ Run the migration script which will update properly the Document IDs in
- XML file: contains metadata
the Business Template.
- Python file: contains the source code itself
This allows to keep Git history and having readable source code instead of
being crippled into an XML file
"""
"""
_tool_id
=
'portal_components'
_tool_id
=
'portal_components'
...
@@ -3918,6 +3930,12 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
...
@@ -3918,6 +3930,12 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
class_id
+
".py"
)
class_id
+
".py"
)
def
_importFile
(
self
,
file_name
,
file_obj
):
def
_importFile
(
self
,
file_name
,
file_obj
):
"""
Upon import, only consider XML file for ZODB Components (as the Python
source file will be read and set to text_content property on the new
object when the XML will be processed) and for backward compatibility,
handle non-migrated Document as well
"""
if
file_name
.
endswith
(
'.py'
):
if
file_name
.
endswith
(
'.py'
):
# If portal_components/XXX.py, then ignore it as it will be handled when
# If portal_components/XXX.py, then ignore it as it will be handled when
# the .xml file will be processed
# the .xml file will be processed
...
@@ -3942,6 +3960,10 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
...
@@ -3942,6 +3960,10 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
LOG
(
'Business Template'
,
0
,
'Skipping file "%s"'
%
file_name
)
LOG
(
'Business Template'
,
0
,
'Skipping file "%s"'
%
file_name
)
def
export
(
self
,
context
,
bta
,
**
kw
):
def
export
(
self
,
context
,
bta
,
**
kw
):
"""
Export a Document as two files for ZODB Components, one for metadata
(.xml) and the other for the Python source code (.py)
"""
path
=
self
.
__class__
.
__name__
+
'/'
path
=
self
.
__class__
.
__name__
+
'/'
for
key
,
obj
in
self
.
_objects
.
iteritems
():
for
key
,
obj
in
self
.
_objects
.
iteritems
():
# Back compatibility with filesystem Documents
# Back compatibility with filesystem Documents
...
@@ -3964,6 +3986,10 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
...
@@ -3964,6 +3986,10 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
bta
.
addObject
(
f
,
key
,
path
=
path
)
bta
.
addObject
(
f
,
key
,
path
=
path
)
def
getTemplateIdList
(
self
):
def
getTemplateIdList
(
self
):
"""
Getter for Document property on the Business Template, must be overriden
in children classes (e.g. ExtensionDocumentTemplateItem for example)
"""
return
self
.
getTemplateDocumentIdList
()
return
self
.
getTemplateDocumentIdList
()
def
build
(
self
,
context
,
**
kw
):
def
build
(
self
,
context
,
**
kw
):
...
@@ -4025,6 +4051,11 @@ class ExtensionTemplateItem(DocumentTemplateItem):
...
@@ -4025,6 +4051,11 @@ class ExtensionTemplateItem(DocumentTemplateItem):
return
self
.
getTemplateExtensionIdList
()
return
self
.
getTemplateExtensionIdList
()
class
TestTemplateItem
(
DocumentTemplateItem
):
class
TestTemplateItem
(
DocumentTemplateItem
):
"""
Live Tests are now stored in ZODB rather than on the filesystem. However,
some Business Templates may still have filesystem Live Tests which need to
be migrated to the ZODB.
"""
local_file_reader_name
=
staticmethod
(
readLocalTest
)
local_file_reader_name
=
staticmethod
(
readLocalTest
)
local_file_writer_name
=
staticmethod
(
writeLocalTest
)
local_file_writer_name
=
staticmethod
(
writeLocalTest
)
# Test needs no import
# Test needs no import
...
@@ -5912,6 +5943,11 @@ Business Template is a set of definitions, such as skins, portal types and categ
...
@@ -5912,6 +5943,11 @@ Business Template is a set of definitions, such as skins, portal types and categ
component_portal_type_dict
,
component_portal_type_dict
,
erase_existing
=
False
,
erase_existing
=
False
,
**
kw
):
**
kw
):
"""
Migrate the given components from filesystem to ZODB by calling the
appropriate importFromFilesystem according to the destination Portal
Type and then update the Business Template property with migrated IDs
"""
if
not
component_portal_type_dict
:
if
not
component_portal_type_dict
:
return
{}
return
{}
...
...
product/ERP5/ERP5Site.py
View file @
2d8b3838
...
@@ -447,16 +447,19 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
...
@@ -447,16 +447,19 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
'getVersionPriorityList'
)
'getVersionPriorityList'
)
def
getVersionPriorityList
(
self
):
def
getVersionPriorityList
(
self
):
"""
"""
Return the Component version priorities defined on the site
Return the Component version priorities defined on the site in descending
order. Whatever happens, a version must always be returned otherwise it
may render the site unusable when all Products will have been migrated
"""
"""
# Whatever happens, a version must always be returned otherwise it may
# render the site unusable when all Products will have been migrated
return
self
.
_version_priority_list
or
(
'erp5 | 0.0'
,)
return
self
.
_version_priority_list
or
(
'erp5 | 0.0'
,)
security
.
declareProtected
(
Permissions
.
ModifyPortalContent
,
security
.
declareProtected
(
Permissions
.
ModifyPortalContent
,
'setVersionPriorityList'
)
'setVersionPriorityList'
)
def
setVersionPriorityList
(
self
,
version_priority_tuple
):
def
setVersionPriorityList
(
self
,
version_priority_tuple
):
"""
"""
Set Version Priority List and make sure that erp5 version is always
defined whatever the given value is
XXX-arnau: must be written through an interaction workflow when ERP5Site
XXX-arnau: must be written through an interaction workflow when ERP5Site
will become a real ERP5 object...
will become a real ERP5 object...
"""
"""
...
@@ -479,6 +482,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
...
@@ -479,6 +482,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
except
AttributeError
:
except
AttributeError
:
pass
pass
# Make sure that reset is not performed when creating a new site
if
not
getattr
(
self
,
'_v_bootstrapping'
,
False
):
if
not
getattr
(
self
,
'_v_bootstrapping'
,
False
):
self
.
portal_components
.
resetOnceAtTransactionBoundary
()
self
.
portal_components
.
resetOnceAtTransactionBoundary
()
...
...
product/ERP5Type/Core/DocumentComponent.py
View file @
2d8b3838
...
@@ -35,13 +35,16 @@ import zope.interface
...
@@ -35,13 +35,16 @@ import zope.interface
from
Products.ERP5Type.interfaces.component
import
IComponent
from
Products.ERP5Type.interfaces.component
import
IComponent
class
DocumentComponent
(
ComponentMixin
):
class
DocumentComponent
(
ComponentMixin
):
# CMF Type Definition
"""
ZODB Component for Documents in bt5 only for now (which used to be installed
in INSTANCE_HOME/Document) but this will also be used later on for Documents
in Products
"""
meta_type
=
'ERP5 Document Component'
meta_type
=
'ERP5 Document Component'
portal_type
=
'Document Component'
portal_type
=
'Document Component'
zope
.
interface
.
implements
(
IComponent
)
zope
.
interface
.
implements
(
IComponent
)
# Declarative security
security
=
ClassSecurityInfo
()
security
=
ClassSecurityInfo
()
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
...
...
product/ERP5Type/Core/ExtensionComponent.py
View file @
2d8b3838
...
@@ -35,13 +35,15 @@ import zope.interface
...
@@ -35,13 +35,15 @@ import zope.interface
from
Products.ERP5Type.interfaces.component
import
IComponent
from
Products.ERP5Type.interfaces.component
import
IComponent
class
ExtensionComponent
(
ComponentMixin
):
class
ExtensionComponent
(
ComponentMixin
):
# CMF Type Definition
"""
ZODB Component for Extensions previously defined in the bt5 and installed in
INSTANCE_HOME/Extensions
"""
meta_type
=
'ERP5 Extension Component'
meta_type
=
'ERP5 Extension Component'
portal_type
=
'Extension Component'
portal_type
=
'Extension Component'
zope
.
interface
.
implements
(
IComponent
)
zope
.
interface
.
implements
(
IComponent
)
# Declarative security
security
=
ClassSecurityInfo
()
security
=
ClassSecurityInfo
()
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
...
...
product/ERP5Type/Core/TestComponent.py
View file @
2d8b3838
...
@@ -35,13 +35,16 @@ import zope.interface
...
@@ -35,13 +35,16 @@ import zope.interface
from
Products.ERP5Type.interfaces.component
import
IComponent
from
Products.ERP5Type.interfaces.component
import
IComponent
class
TestComponent
(
ComponentMixin
):
class
TestComponent
(
ComponentMixin
):
# CMF Type Definition
"""
ZODB Component for Live Tests only (previously defined in the bt5 and
installed in INSTANCE_HOME/tests) as other kind of Tests should be
deprecated at some point
"""
meta_type
=
'ERP5 Test Component'
meta_type
=
'ERP5 Test Component'
portal_type
=
'Test Component'
portal_type
=
'Test Component'
zope
.
interface
.
implements
(
IComponent
)
zope
.
interface
.
implements
(
IComponent
)
# Declarative security
security
=
ClassSecurityInfo
()
security
=
ClassSecurityInfo
()
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
...
...
product/ERP5Type/Permissions.py
View file @
2d8b3838
...
@@ -146,4 +146,7 @@ AddERP5Content = AddPortalContent # Since we put come CPS content in ERP5 docume
...
@@ -146,4 +146,7 @@ AddERP5Content = AddPortalContent # Since we put come CPS content in ERP5 docume
# Source Code Management - this is the highest possible permission
# Source Code Management - this is the highest possible permission
ManageExtensions
=
"Manage extensions"
ManageExtensions
=
"Manage extensions"
# Permission for resetting ZODB Components, this is required to allow a
# Manager to reset Dynamic Classes but not to modify the source code of
# Components
ResetDynamicClasses
=
"Reset dynamic classes"
ResetDynamicClasses
=
"Reset dynamic classes"
product/ERP5Type/Tool/ComponentTool.py
View file @
2d8b3838
...
@@ -64,33 +64,45 @@ class ComponentTool(BaseTool):
...
@@ -64,33 +64,45 @@ class ComponentTool(BaseTool):
security
.
declareProtected
(
Permissions
.
ResetDynamicClasses
,
'reset'
)
security
.
declareProtected
(
Permissions
.
ResetDynamicClasses
,
'reset'
)
def
reset
(
self
,
force
=
False
,
reset_portal_type
=
False
):
def
reset
(
self
,
force
=
False
,
reset_portal_type
=
False
):
"""
"""
XXX-arnau: global reset
Reset all ZODB Component packages. A cache cookie is used to check whether
the reset is necessary when force is not specified. This allows to make
sure that all ZEO clients get reset (checked in __of__ on ERP5Site) when
one given ZEO client gets reset when Component(s) are modified or
invalidated.
Also, as resetting ZODB Components Package usually implies to reset Portal
Type as Classes (because the former are used as bases), perform the reset
by default.
XXX-arnau: for now, this is a global reset but it might be improved in the
future if required...
"""
"""
portal
=
self
.
getPortalObject
()
portal
=
self
.
getPortalObject
()
# XXX-arnau: copy/paste from portal_type_class, but is this really
# necessary as even for Portal Type classes, synchronizeDynamicModules
# seems to always called with force=True?
global
last_sync
global
last_sync
if
force
:
if
force
:
#
h
ard invalidation to force sync between nodes
#
H
ard invalidation to force sync between nodes
portal
.
newCacheCookie
(
'component_packages'
)
portal
.
newCacheCookie
(
'component_packages'
)
last_sync
=
portal
.
getCacheCookie
(
'component_packages'
)
last_sync
=
portal
.
getCacheCookie
(
'component_packages'
)
else
:
else
:
cookie
=
portal
.
getCacheCookie
(
'component_packages'
)
cookie
=
portal
.
getCacheCookie
(
'component_packages'
)
if
cookie
==
last_sync
:
if
cookie
==
last_sync
:
return
False
return
False
last_sync
=
cookie
last_sync
=
cookie
LOG
(
"ERP5Type.Tool.ComponentTool"
,
INFO
,
"Resetting Components"
)
LOG
(
"ERP5Type.Tool.ComponentTool"
,
INFO
,
"Resetting Components"
)
type_tool
=
portal
.
portal_types
type_tool
=
portal
.
portal_types
# One Component Package per allowed Portal Types on Component Tool
allowed_content_type_list
=
type_tool
.
getTypeInfo
(
allowed_content_type_list
=
type_tool
.
getTypeInfo
(
self
.
getPortalType
()).
getTypeAllowedContentTypeList
()
self
.
getPortalType
()).
getTypeAllowedContentTypeList
()
import
erp5.component
import
erp5.component
# Make sure that it is not possible to load Components or load Portal Type
# class when Components are reset through aq_method_lock
with
Base
.
aq_method_lock
:
with
Base
.
aq_method_lock
:
for
content_type
in
allowed_content_type_list
:
for
content_type
in
allowed_content_type_list
:
package_name
=
content_type
.
split
(
' '
)[
0
].
lower
()
package_name
=
content_type
.
split
(
' '
)[
0
].
lower
()
...
@@ -112,10 +124,10 @@ class ComponentTool(BaseTool):
...
@@ -112,10 +124,10 @@ class ComponentTool(BaseTool):
'resetOnceAtTransactionBoundary'
)
'resetOnceAtTransactionBoundary'
)
def
resetOnceAtTransactionBoundary
(
self
):
def
resetOnceAtTransactionBoundary
(
self
):
"""
"""
Schedule a single reset at the end of the transaction
, only once. The
Schedule a single reset at the end of the transaction
. The idea behind
idea behind this is that a reset is (very) costly and that we want to do
this is that a reset is (very) costly and that we want to do it as little
it as little often as possible. Moreover, doing it twice in a transaction
often as possible. Moreover, doing it twice in a transaction is useless
is useless
(but still twice as costly).
(but still twice as costly).
"""
"""
tv
=
getTransactionalVariable
()
tv
=
getTransactionalVariable
()
key
=
'ComponentTool.resetOnceAtTransactionBoundary'
key
=
'ComponentTool.resetOnceAtTransactionBoundary'
...
...
product/ERP5Type/Tool/TypesTool.py
View file @
2d8b3838
...
@@ -201,7 +201,8 @@ class TypesTool(TypeProvider):
...
@@ -201,7 +201,8 @@ class TypesTool(TypeProvider):
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getDocumentTypeList'
)
security
.
declareProtected
(
Permissions
.
AccessContentsInformation
,
'getDocumentTypeList'
)
def
getDocumentTypeList
(
self
):
def
getDocumentTypeList
(
self
):
"""
"""
Return a list of Document types that can be used as Base classes
Return a list of Document types (including filesystem and ZODB Component
Documents) that can be used as Base classes
"""
"""
from
Products.ERP5Type
import
document_class_registry
from
Products.ERP5Type
import
document_class_registry
document_type_set
=
set
(
document_class_registry
)
document_type_set
=
set
(
document_class_registry
)
...
...
product/ERP5Type/dynamic/component_package.py
View file @
2d8b3838
...
@@ -86,29 +86,25 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -86,29 +86,25 @@ class ComponentDynamicPackage(ModuleType):
"""
"""
Create the component registry, this is very similar to
Create the component registry, this is very similar to
Products.ERP5Type.document_class_registry and avoids checking whether a
Products.ERP5Type.document_class_registry and avoids checking whether a
Component exists at each call at the expense to increase startup
Component exists at each call at the expense of being slower when being
time. Moreover, it allows to handle reference easily.
re-generated after a reset. Moreover, it allows to handle reference
easily.
XXX-arnau: handle different versions of a Component, perhaps something
like erp5.component.extension.VERSION.REFERENCE perhaps but there should
be a a way to specify priorities such as portal_skins maybe?
"""
"""
if
not
self
.
__registry_dict
:
if
not
self
.
__registry_dict
:
portal
=
getSite
()
portal
=
getSite
()
try
:
try
:
component_tool
=
portal
.
portal_components
component_tool
=
portal
.
portal_components
#
XXX-arnau: When installing ERP5 site, erp5_core_components has not
#
When installing ERP5 site, erp5_core_components has not been installed
#
been installed
yet, thus this will obviously failed...
# yet, thus this will obviously failed...
except
AttributeError
:
except
AttributeError
:
return
{}
return
{}
version_priority_set
=
set
(
portal
.
getVersionPriorityNameList
())
version_priority_set
=
set
(
portal
.
getVersionPriorityNameList
())
# objectValues should not be used for a large number of objects, but
# objectValues should not be used for a large number of objects, but
# this is only done at startup or upon reset, moreover using the Catalog
# this is only done upon reset, moreover using the Catalog is too risky
# is too risky as it lags behind and depends upon objects being
# as it lags behind and depends upon objects being reindexed
# reindexed
with
Base
.
aq_method_lock
:
with
Base
.
aq_method_lock
:
for
component
in
component_tool
.
objectValues
(
portal_type
=
self
.
_portal_type
):
for
component
in
component_tool
.
objectValues
(
portal_type
=
self
.
_portal_type
):
# Only consider modified or validated states as state transition will
# Only consider modified or validated states as state transition will
...
@@ -138,6 +134,16 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -138,6 +134,16 @@ class ComponentDynamicPackage(ModuleType):
module
.
__file__
[
1
:
-
1
]).
getTextContent
(
validated_only
=
True
)
module
.
__file__
[
1
:
-
1
]).
getTextContent
(
validated_only
=
True
)
def
find_module
(
self
,
fullname
,
path
=
None
):
def
find_module
(
self
,
fullname
,
path
=
None
):
"""
PEP-302 Finder which determines which packages and modules will be handled
by this class. It must be done carefully to avoid handling packages and
modules the Loader (load_module()) will not be handled later as the latter
would raise ImportError...
As per PEP-302, returns None if this Finder cannot handle the given name,
perhaps because the Finder of another Component Package could do it or
because this is a filesystem module...
"""
# Ignore imports with a path which are filesystem-only and any
# Ignore imports with a path which are filesystem-only and any
# absolute imports which does not start with this package prefix,
# absolute imports which does not start with this package prefix,
# None there means that "normal" sys.path will be used
# None there means that "normal" sys.path will be used
...
@@ -162,7 +168,7 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -162,7 +168,7 @@ class ComponentDynamicPackage(ModuleType):
except
KeyError
:
except
KeyError
:
return
None
return
None
# Skip
components not available
, otherwise Products for example could be
# Skip
unavailable components
, otherwise Products for example could be
# wrongly considered as importable and thus the actual filesystem class
# wrongly considered as importable and thus the actual filesystem class
# ignored
# ignored
elif
(
name
not
in
self
.
_registry_dict
and
elif
(
name
not
in
self
.
_registry_dict
and
...
@@ -172,6 +178,13 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -172,6 +178,13 @@ class ComponentDynamicPackage(ModuleType):
return
self
return
self
def
_getVersionPackage
(
self
,
version
):
def
_getVersionPackage
(
self
,
version
):
"""
Get the version package (NAMESPACE.VERSION_version) for the given version
and create it if it does not already exist
"""
# Version are appended with '_version' to distinguish them from top-level
# Component modules (Component checkConsistency() forbids Component name
# ending with _version)
version
+=
'_version'
version
+=
'_version'
version_package
=
getattr
(
self
,
version
,
None
)
version_package
=
getattr
(
self
,
version
,
None
)
if
version_package
is
None
:
if
version_package
is
None
:
...
@@ -185,12 +198,24 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -185,12 +198,24 @@ class ComponentDynamicPackage(ModuleType):
def
__load_module
(
self
,
fullname
):
def
__load_module
(
self
,
fullname
):
"""
"""
Load a module with given fullname (see PEP 302) if it's not
Load a module with given fullname (see PEP 302) if it's not already in
already in sys.modules. It is assumed that imports are filtered
sys.modules. It is assumed that imports are filtered properly in
properly in find_module().
find_module().
Also, when the top-level Component module is requested
(erp5.component.XXX.COMPONENT_NAME), the Component with the highest
version priority will be loaded into the Version package
(erp5.component.XXX.VERSION_version.COMPONENT_NAME. Therefore, the
top-level Component module will just be an alias of the versioned one.
As per PEP-302, raise an ImportError if the Loader could not load the
module for any reason...
"""
"""
site
=
getSite
()
site
=
getSite
()
name
=
fullname
[
len
(
self
.
_namespace_prefix
):]
name
=
fullname
[
len
(
self
.
_namespace_prefix
):]
# if only Version package (erp5.component.XXX.VERSION_version) is
# requested to be loaded, then create it if necessary
if
name
.
endswith
(
'_version'
):
if
name
.
endswith
(
'_version'
):
version
=
name
[:
-
self
.
__version_suffix_len
]
version
=
name
[:
-
self
.
__version_suffix_len
]
return
(
version
in
site
.
getVersionPriorityNameList
()
and
return
(
version
in
site
.
getVersionPriorityNameList
()
and
...
@@ -198,6 +223,8 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -198,6 +223,8 @@ class ComponentDynamicPackage(ModuleType):
module_fullname_alias
=
None
module_fullname_alias
=
None
version_package_name
=
name
[:
-
self
.
__version_suffix_len
]
version_package_name
=
name
[:
-
self
.
__version_suffix_len
]
# If a specific version of the Component has been requested
if
'.'
in
name
:
if
'.'
in
name
:
try
:
try
:
version
,
name
=
name
.
split
(
'.'
)
version
,
name
=
name
.
split
(
'.'
)
...
@@ -212,6 +239,7 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -212,6 +239,7 @@ class ComponentDynamicPackage(ModuleType):
raise
ImportError
(
"%s: version %s of Component %s could not be found"
%
\
raise
ImportError
(
"%s: version %s of Component %s could not be found"
%
\
(
fullname
,
version
,
name
))
(
fullname
,
version
,
name
))
# Otherwise, find the Component with the highest version priority
else
:
else
:
try
:
try
:
component_version_dict
=
self
.
_registry_dict
[
name
]
component_version_dict
=
self
.
_registry_dict
[
name
]
...
@@ -219,6 +247,7 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -219,6 +247,7 @@ class ComponentDynamicPackage(ModuleType):
raise
ImportError
(
"%s: Component %s could not be found"
%
(
fullname
,
raise
ImportError
(
"%s: Component %s could not be found"
%
(
fullname
,
name
))
name
))
# Version priority name list is ordered in descending order
for
version
in
site
.
getVersionPriorityNameList
():
for
version
in
site
.
getVersionPriorityNameList
():
component
=
component_version_dict
.
get
(
version
)
component
=
component_version_dict
.
get
(
version
)
if
component
is
not
None
:
if
component
is
not
None
:
...
@@ -227,6 +256,8 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -227,6 +256,8 @@ class ComponentDynamicPackage(ModuleType):
raise
ImportError
(
"%s: no version of Component %s in Site priority"
%
\
raise
ImportError
(
"%s: no version of Component %s in Site priority"
%
\
(
fullname
,
name
))
(
fullname
,
name
))
# Check whether this module has already been loaded before for a
# specific version, if so, just add it to the upper level
try
:
try
:
module
=
getattr
(
getattr
(
self
,
version
+
'_version'
),
name
)
module
=
getattr
(
getattr
(
self
,
version
+
'_version'
),
name
)
except
AttributeError
:
except
AttributeError
:
...
@@ -264,6 +295,8 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -264,6 +295,8 @@ class ComponentDynamicPackage(ModuleType):
module
.
__loader__
=
self
module
.
__loader__
=
self
module
.
__name__
=
module_fullname
module
.
__name__
=
module_fullname
# Add the newly created module to the Version package and add it as an
# alias to the top-level package as well
setattr
(
self
.
_getVersionPackage
(
version
),
name
,
module
)
setattr
(
self
.
_getVersionPackage
(
version
),
name
,
module
)
if
module_fullname_alias
:
if
module_fullname_alias
:
setattr
(
self
,
name
,
module
)
setattr
(
self
,
name
,
module
)
...
@@ -287,7 +320,7 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -287,7 +320,7 @@ class ComponentDynamicPackage(ModuleType):
if
sub_package
:
if
sub_package
:
package
=
sub_package
package
=
sub_package
else
:
else
:
# Clear the Component registry
# Clear the Component registry
only once
self
.
__registry_dict
.
clear
()
self
.
__registry_dict
.
clear
()
package
=
self
package
=
self
...
@@ -306,7 +339,8 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -306,7 +339,8 @@ class ComponentDynamicPackage(ModuleType):
# the meantime
# the meantime
del
sys
.
modules
[
module_name
]
del
sys
.
modules
[
module_name
]
# Delete linecache data
# Delete linecache data to get updated source code (__file__ attribute
# (<ComponentID>) is used as linecache key)
import
linecache
import
linecache
try
:
try
:
del
linecache
.
cache
[
getattr
(
package
,
name
).
__file__
]
del
linecache
.
cache
[
getattr
(
package
,
name
).
__file__
]
...
@@ -314,5 +348,4 @@ class ComponentDynamicPackage(ModuleType):
...
@@ -314,5 +348,4 @@ class ComponentDynamicPackage(ModuleType):
except
(
AttributeError
,
KeyError
):
except
(
AttributeError
,
KeyError
):
pass
pass
# And finally remove the module
delattr
(
package
,
name
)
delattr
(
package
,
name
)
product/ERP5Type/dynamic/dynamic_module.py
View file @
2d8b3838
...
@@ -86,9 +86,14 @@ def initializeDynamicModules():
...
@@ -86,9 +86,14 @@ def initializeDynamicModules():
erp5.accessor_holder.portal_type
erp5.accessor_holder.portal_type
holds accessors holders of Portal Types
holds accessors holders of Portal Types
erp5.component:
erp5.component:
holds component modules
holds ZODB Component packages
erp5.component.document:
holds Document modules previously found in bt5 in $INSTANCE_HOME/Document
erp5.component.extension:
erp5.component.extension:
holds extension classes previously found in bt5 in instancehome/Extensions
holds Extension modules previously found in bt5 in
$INSTANCE_HOME/Extensions
erp5.component.test:
holds Live Test modules previously found in bt5 in $INSTANCE_HOME/test
"""
"""
erp5
=
ModuleType
(
"erp5"
)
erp5
=
ModuleType
(
"erp5"
)
sys
.
modules
[
"erp5"
]
=
erp5
sys
.
modules
[
"erp5"
]
=
erp5
...
@@ -121,7 +126,7 @@ def initializeDynamicModules():
...
@@ -121,7 +126,7 @@ def initializeDynamicModules():
erp5
.
temp_portal_type
=
registerDynamicModule
(
'erp5.temp_portal_type'
,
erp5
.
temp_portal_type
=
registerDynamicModule
(
'erp5.temp_portal_type'
,
loadTempPortalTypeClass
)
loadTempPortalTypeClass
)
# Components
#
ZODB
Components
erp5
.
component
=
ModuleType
(
"erp5.component"
)
erp5
.
component
=
ModuleType
(
"erp5.component"
)
sys
.
modules
[
"erp5.component"
]
=
erp5
.
component
sys
.
modules
[
"erp5.component"
]
=
erp5
.
component
...
...
product/ERP5Type/mixin/component.py
View file @
2d8b3838
...
@@ -116,6 +116,22 @@ class RecordablePropertyMetaClass(ExtensionClass):
...
@@ -116,6 +116,22 @@ class RecordablePropertyMetaClass(ExtensionClass):
class
ComponentMixin
(
PropertyRecordableMixin
,
Base
):
class
ComponentMixin
(
PropertyRecordableMixin
,
Base
):
"""
"""
Mixin used for all ZODB Components. Most of the code is generic, thus actual
ZODB Components should have almost nothing to defined...
From a security point of view, only Developer Role defined on Component Tool
can manage Components (as exec is used and anything potentially damaging
could be done on the filesystem), while only Manager or Developer Roles can
reset Component Packages (see ERP5Type.Permissions). All the permissions are
defined on Component Tool itself and newly created Components just inherits
permissions defined on the former.
The Developer Role is not a typical Role as only users defined in Zope
configuration can be added to this Role (which is displayed in the list of
available Roles in ZMI). This is achieved by two monkey patches
(ERP5Type.patches.{User,PropertiedUser}) and modifications in
ERP5Security.ERP5UserFactory.
XXX-arnau: add tests to ERP5 itself to make sure all securities are defined
XXX-arnau: add tests to ERP5 itself to make sure all securities are defined
properly everywhere (see naming convention test)
properly everywhere (see naming convention test)
"""
"""
...
@@ -157,7 +173,15 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -157,7 +173,15 @@ class ComponentMixin(PropertyRecordableMixin, Base):
security
.
declareProtected
(
Permissions
.
ModifyPortalContent
,
'checkConsistency'
)
security
.
declareProtected
(
Permissions
.
ModifyPortalContent
,
'checkConsistency'
)
def
checkConsistency
(
self
,
*
args
,
**
kw
):
def
checkConsistency
(
self
,
*
args
,
**
kw
):
"""
"""
XXX-arnau: should probably be in a separate Constraint class?
Check the consistency of the Component upon validate or when being
modified after being validated.
Some keywords are forbidden for reference and version. As Version package
always ends with '_version', reference is checked more carefully to avoid
clashing with existing method names (such as the ones required for PEP
302).
XXX-arnau: separate Constraint class?
"""
"""
error_list
=
super
(
ComponentMixin
,
self
).
checkConsistency
(
*
args
,
**
kw
)
error_list
=
super
(
ComponentMixin
,
self
).
checkConsistency
(
*
args
,
**
kw
)
object_relative_url
=
self
.
getRelativeUrl
()
object_relative_url
=
self
.
getRelativeUrl
()
...
@@ -201,6 +225,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -201,6 +225,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
else
:
else
:
message
=
None
message
=
None
try
:
try
:
# Check for any error in the source code by trying to load it
self
.
load
({},
text_content
=
text_content
)
self
.
load
({},
text_content
=
text_content
)
except
SyntaxError
,
e
:
except
SyntaxError
,
e
:
mapping
=
dict
(
error_message
=
str
(
e
),
mapping
=
dict
(
error_message
=
str
(
e
),
...
@@ -226,10 +251,11 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -226,10 +251,11 @@ class ComponentMixin(PropertyRecordableMixin, Base):
'checkConsistencyAndValidate'
)
'checkConsistencyAndValidate'
)
def
checkConsistencyAndValidate
(
self
):
def
checkConsistencyAndValidate
(
self
):
"""
"""
When a Component is in validated or modified validation state and
When a Component is in validated or modified validation state and it is
it is modified, modified state is set then this checks whether the
modified, modified state is set then this checks whether the Component can
Component can be validated again if checkConsistency returns no
be validated again if checkConsistency returns no error. Otherwise, it
error
stays in modified state and previously validated values are used for
reference, version and text_content
"""
"""
error_list
=
self
.
checkConsistency
()
error_list
=
self
.
checkConsistency
()
if
error_list
:
if
error_list
:
...
@@ -249,7 +275,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -249,7 +275,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
def
hasErrorMessageList
(
self
):
def
hasErrorMessageList
(
self
):
"""
"""
Check whether there are error messages, useful to display errors in the UI
Check whether there are error messages, useful to display errors in the UI
without calling getErrorMessageList()
which
translates error messages
without calling getErrorMessageList()
as it
translates error messages
"""
"""
workflow
=
self
.
workflow_history
[
'component_validation_workflow'
][
-
1
]
workflow
=
self
.
workflow_history
[
'component_validation_workflow'
][
-
1
]
return
bool
(
workflow
[
'error_message'
])
return
bool
(
workflow
[
'error_message'
])
...
@@ -274,10 +300,8 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -274,10 +300,8 @@ class ComponentMixin(PropertyRecordableMixin, Base):
__dict__ is given rather than creating an empty dict and returning it.
__dict__ is given rather than creating an empty dict and returning it.
Initially, namespace_dict default parameter value was an empty dict to
Initially, namespace_dict default parameter value was an empty dict to
allow checking the source code before validate, but this introduces a bug
allow checking the source code before validate, but this is completely
when namespace_dict was not given because the first call would exec
wrong as the object reference is kept accross each call
directly into function namespace_dict default parameter, thus the second
call would have namespace_dict default value to the previous call.
"""
"""
if
text_content
is
None
:
if
text_content
is
None
:
text_content
=
self
.
getTextContent
(
validated_only
=
validated_only
)
text_content
=
self
.
getTextContent
(
validated_only
=
validated_only
)
...
@@ -312,7 +336,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -312,7 +336,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
Get source for FTP/Webdav. The default implementation of GET for Webdav,
Get source for FTP/Webdav. The default implementation of GET for Webdav,
available in webdav.Resource, calls manage_FTPget
available in webdav.Resource, calls manage_FTPget
XXX-arnau: encoding?
XXX-arnau: encoding
issue
?
"""
"""
return
self
.
getTextContent
()
return
self
.
getTextContent
()
...
@@ -322,8 +346,9 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -322,8 +346,9 @@ class ComponentMixin(PropertyRecordableMixin, Base):
def
importFromFilesystem
(
cls
,
context
,
reference
,
version
,
def
importFromFilesystem
(
cls
,
context
,
reference
,
version
,
erase_existing
=
False
):
erase_existing
=
False
):
"""
"""
Import a Component from the filesystem into ZODB after checking that the
Import a Component from the filesystem into ZODB and validate it so it can
source code is valid
be loaded straightaway provided validate() does not raise any error of
course
"""
"""
object_id
=
'%s.%s.%s'
%
(
cls
.
_getDynamicModuleNamespace
(),
version
,
object_id
=
'%s.%s.%s'
%
(
cls
.
_getDynamicModuleNamespace
(),
version
,
reference
)
reference
)
...
@@ -358,7 +383,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
...
@@ -358,7 +383,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
# straightaway as there should be no error
# straightaway as there should be no error
new_component
.
validate
()
new_component
.
validate
()
#
XXX-arnau: is it really safe?
#
Remove now useless Component on filesystem
os
.
remove
(
path
)
os
.
remove
(
path
)
return
new_component
return
new_component
product/ERP5Type/patches/ExternalMethod.py
View file @
2d8b3838
##############################################################################
#
# Copyright (c) 2002 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.ExternalMethod.ExternalMethod
import
ExternalMethod
from
Products.ExternalMethod.ExternalMethod
import
ExternalMethod
from
App.Extensions
import
FuncCode
,
getObject
from
App.Extensions
import
FuncCode
,
getObject
def
getFunction
(
self
,
reload
=
False
,
f
=
None
):
def
getFunction
(
self
,
reload
=
False
,
f
=
None
):
"""
Patch to get ZODB Component Extension function if available, otherwise
fallback on filesystem Extension
"""
if
f
is
None
:
if
f
is
None
:
import
erp5.component.extension
import
erp5.component.extension
try
:
try
:
...
@@ -21,6 +38,11 @@ ExternalMethod.getFunction = getFunction
...
@@ -21,6 +38,11 @@ ExternalMethod.getFunction = getFunction
ExternalMethod__call__
=
ExternalMethod
.
__call__
ExternalMethod__call__
=
ExternalMethod
.
__call__
def
__call__
(
self
,
*
args
,
**
kw
):
def
__call__
(
self
,
*
args
,
**
kw
):
"""
Patch to call ZODB Component Extension, by trying first to import ZODB
Component Extension if available, otherwise fallback on filesystem
Extension
"""
try
:
try
:
f
=
getattr
(
__import__
(
'erp5.component.extension.'
+
self
.
_module
,
f
=
getattr
(
__import__
(
'erp5.component.extension.'
+
self
.
_module
,
fromlist
=
[
'erp5.component.extension'
],
fromlist
=
[
'erp5.component.extension'
],
...
...
product/ERP5Type/patches/User.py
View file @
2d8b3838
...
@@ -94,7 +94,7 @@ def getRolesInContext(self, object):
...
@@ -94,7 +94,7 @@ def getRolesInContext(self, object):
continue
continue
break
break
# Patched: Developer role should n
ot n
ever be available as local role
# Patched: Developer role should never be available as local role
local
.
pop
(
'Developer'
,
None
)
local
.
pop
(
'Developer'
,
None
)
roles
=
list
(
roles
)
+
local
.
keys
()
roles
=
list
(
roles
)
+
local
.
keys
()
return
roles
return
roles
...
...
product/ERP5Type/tests/testDynamicClassGeneration.py
View file @
2d8b3838
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment