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
Ekaterina
erp5
Commits
3425583d
Commit
3425583d
authored
Nov 09, 2019
by
Jérome Perrin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
python_support: new business template to act as a language server for python
parent
de2a0603
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1372 additions
and
0 deletions
+1372
-0
bt5/erp5_python_support/PortalTypeTemplateItem/portal_types/Python%20Support%20Tool.xml
...TypeTemplateItem/portal_types/Python%20Support%20Tool.xml
+58
-0
bt5/erp5_python_support/TestTemplateItem/portal_components/test.erp5.testPythonSupport.py
...lateItem/portal_components/test.erp5.testPythonSupport.py
+599
-0
bt5/erp5_python_support/TestTemplateItem/portal_components/test.erp5.testPythonSupport.xml
...ateItem/portal_components/test.erp5.testPythonSupport.xml
+127
-0
bt5/erp5_python_support/ToolComponentTemplateItem/portal_components/tool.erp5.PythonSupportTool.py
...lateItem/portal_components/tool.erp5.PythonSupportTool.py
+435
-0
bt5/erp5_python_support/ToolComponentTemplateItem/portal_components/tool.erp5.PythonSupportTool.xml
...ateItem/portal_components/tool.erp5.PythonSupportTool.xml
+127
-0
bt5/erp5_python_support/ToolTemplateItem/portal_python_support.xml
...python_support/ToolTemplateItem/portal_python_support.xml
+20
-0
bt5/erp5_python_support/bt/template_format_version
bt5/erp5_python_support/bt/template_format_version
+1
-0
bt5/erp5_python_support/bt/template_portal_type_id_list
bt5/erp5_python_support/bt/template_portal_type_id_list
+1
-0
bt5/erp5_python_support/bt/template_test_id_list
bt5/erp5_python_support/bt/template_test_id_list
+1
-0
bt5/erp5_python_support/bt/template_tool_component_id_list
bt5/erp5_python_support/bt/template_tool_component_id_list
+1
-0
bt5/erp5_python_support/bt/template_tool_id_list
bt5/erp5_python_support/bt/template_tool_id_list
+1
-0
bt5/erp5_python_support/bt/title
bt5/erp5_python_support/bt/title
+1
-0
No files found.
bt5/erp5_python_support/PortalTypeTemplateItem/portal_types/Python%20Support%20Tool.xml
0 → 100644
View file @
3425583d
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Base Type"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
content_icon
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<string>
Provide intellisense for python.
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Python Support Tool
</string>
</value>
</item>
<item>
<key>
<string>
init_script
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
permission
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Base Type
</string>
</value>
</item>
<item>
<key>
<string>
type_class
</string>
</key>
<value>
<string>
PythonSupportTool
</string>
</value>
</item>
<item>
<key>
<string>
type_interface
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
type_mixin
</string>
</key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_python_support/TestTemplateItem/portal_components/test.erp5.testPythonSupport.py
0 → 100644
View file @
3425583d
##############################################################################
#
# Copyright (c) 2002-2019 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from
Products.ERP5Type.tests.ERP5TypeTestCase
import
ERP5TypeTestCase
import
re
import
typing
from
typing
import
List
# pylint: disable=unused-import
import
enum
import
textwrap
from
collections
import
namedtuple
# TODO: just import !
# https://microsoft.github.io/monaco-editor/api/classes/monaco.position.html
# lineNumber and column start at 1
Position
=
namedtuple
(
'Position'
,
'lineNumber column'
)
Completion
=
namedtuple
(
'Completion'
,
'text description'
)
# TODO
# with ReferenceCompletion, we can use regexs
ReferenceCompletion
=
namedtuple
(
'ReferenceCompletion'
,
'text description'
)
# /TODO
if
typing
.
TYPE_CHECKING
:
xScriptType
=
typing
.
Union
[
typing
.
Literal
[
'Python (Script)'
]]
import
erp5.portal_type
# pylint: disable=import-error,unused-import
class
ScriptType
(
enum
.
Enum
):
Component
=
0
SkinFolderPythonScript
=
1
WorkflowPythonScript
=
1
# XXX workaround missing completions from unittest.TestCase
import
unittest
class
XERP5TypeTestCase
(
ERP5TypeTestCase
,
unittest
.
TestCase
):
pass
class
PythonSupportTestCase
(
XERP5TypeTestCase
):
"""TestCase for python support
"""
def
assertCompletionIn
(
self
,
completion
,
completion_list
):
# type: (ReferenceCompletion, List[Completion]) -> None
"""check that `completion` is in `completion_list`
"""
self
.
fail
(
'TODO'
)
def
assertCompletionNotIn
(
self
,
completion
,
completion_list
):
# type: (ReferenceCompletion, List[Completion]) -> None
"""check that `completion` is not in `completion_list`
"""
self
.
fail
(
'TODO'
)
class
TestCompleteFromScript
(
PythonSupportTestCase
):
"""Test completions from within a python scripts
Check that magic of python scripts with context and params
is properly emulated.
"""
script_name
=
'Base_example'
script_params
=
''
def
getCompletionList
(
self
,
code
,
position
=
None
):
# type: (str, Position) -> List[Completion]
return
self
.
portal
.
portal_python_support
.
getCompletionList
(
code
,
position
)
def
test_portal_tools
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
context.getPortalObject().'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"person_module"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"portal_types"
),
completion_list
)
def
test_base_method
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
context.getP'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getPortalType"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getParentValue"
),
completion_list
)
def
test_context_name
(
self
):
self
.
script_name
=
'Person_example'
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
context.getFir'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
def
test_params_type_comment
(
self
):
self
.
script_params
=
'person'
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
# type: (erp5.portal_type.Person) -> str
person.getFir'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
class
TestCompleteWithScript
(
PythonSupportTestCase
):
"""Check that python scripts are offered as completions items like methods on objects.
"""
def
afterSetUp
(
self
):
super
(
TestCompleteWithScript
,
self
).
afterSetUp
()
# sanity check that the scripts we are asserting with really exist
self
.
assertTrue
(
hasattr
(
self
.
portal
,
'Account_getFormattedTitle'
))
self
.
assertTrue
(
hasattr
(
self
.
portal
,
'Person_getAge'
))
self
.
assertTrue
(
hasattr
(
self
.
portal
,
'Base_edit'
))
self
.
assertTrue
(
hasattr
(
self
.
portal
,
'ERP5Site_getSearchResultList'
))
def
test_context
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"Person_getAge"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"ERP5Site_getSearchResultList"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"Base_edit"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"Account_getFormattedTitle"
),
completion_list
)
def
test_docstring
(
self
):
# create python script with docstring
# check docstring from completion contain this docstring + a link to /manage_main on the script
self
.
fail
(
'TODO'
)
def
test_docstring_plus_type_comment
(
self
):
# create python script with docstring and type comment for parameters
# check docstring from completion contain this docstring + a link to /manage_main on the script
self
.
fail
(
'TODO'
)
def
test_no_docstring
(
self
):
# create python script with no docstring
# check docstring from completion contain a link to /manage_main on the script
self
.
fail
(
'TODO'
)
def
test_typevar_in_type_comment
(
self
):
# create a Base_x python script with a type comment returning content same portal_type,
# like Base_createCloneDocument
# type: (X,) -> X
self
.
fail
(
'TODO'
)
class
TestCompletePortalType
(
PythonSupportTestCase
):
def
test_getattr
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module.person.getFi'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
def
test_getitem
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module[person].getFi'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
def
test_newContent_return_value
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module.newContent().getFi'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
def
test_newContent_portal_type_return_value
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.newContent(portal_type="Bank Account").get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.newContent(portal_type="Address").get'''
))
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
def
test_searchFolder_return_value
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module.searchFolder()[0].'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getObject"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module.searchFolder()[0].getObject().'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getFirstName"
),
completion_list
)
def
test_searchFolder_portal_type_return_value
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.searchFolder(portal_type="Bank Account")[0].get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getObject"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.searchFolder(portal_type="Bank Account")[0].getObject().get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.searchFolder(portal_type="Address")[0].getObject().get'''
))
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
def
test_getPortalType_docstring
(
self
):
# getPortalType docstring has a full description of the portal type and a link
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getPortalType'''
))
self
.
assertCompletionIn
(
Completion
(
description
=
re
.
compile
(
".*Persons capture the contact information.*"
)),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
description
=
re
.
compile
(
".*portal_types/Person.*"
)),
completion_list
)
def
test_getPortalType_literal
(
self
):
# jedi knows what literal getPortalType returns
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
this = "a"
if person.getPortalType() = "Person":
this = []
this.'''
))
# jedi understood that `this` is list in this case
self
.
assertCompletionIn
(
Completion
(
text
=
"append"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"capitalize"
),
completion_list
)
def
test_getParentValue
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getParentValue().getPortalType'''
))
self
.
assertCompletionIn
(
Completion
(
description
=
re
.
compile
(
".*Person Module.*"
)),
completion_list
)
def
test_workflow_state_getter
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getValidationState"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getTranslatedValidationStateTitle"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getSimulationState"
),
completion_list
)
def
test_workflow_method
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.validat'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"validate"
),
completion_list
)
def
test_edit_argument
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.edit(fir'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"first_name"
),
completion_list
)
def
test_new_content_argument
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person_module = None # type: erp5.portal_type.PersonModule
person_module.newContent(fir'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"first_name"
),
completion_list
)
class
TestCompletePropertySheet
(
PythonSupportTestCase
):
def
test_content_property
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getDefaultAddre'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getDefaultAddressStreetAddress"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getDefaultAddressCity"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getDefaultAddressText"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getDefaultAddressRegionTitle"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.setDefaultAddre'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"setDefaultAddressStreetAddress"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"setDefaultAddressCity"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"setDefaultAddressText"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"setDefaultAddressRegionValue"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"setDefaultAddressRegionTitle"
),
completion_list
)
def
test_category
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getRegion"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getRegionTitle"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getRegionValue"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"getDefaultRegionTitle"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.set'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"setRegion"
),
completion_list
)
# XXX include this accessor ?
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getRegionTitle"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"setRegionValue"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"setDefaultRegionTitle"
),
completion_list
)
def
test_category_value_getter_portal_type
(
self
):
# filtered for a portal type
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getRegionValue(portal_type="Bank Account").'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
# a list of portal types
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getRegionValue(portal_type=("Bank Account", "Address")).'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
# not filter assume any portal type
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
person = None # type: erp5.portal_type.Person
person.getRegionValue().'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
class
TestCompleteERP5Site
(
PythonSupportTestCase
):
def
test_base_method
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.res'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"restrictedTraverse"
),
completion_list
)
def
test_content
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.acl'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"acl_users"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.acl_users.getUs'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getUserById"
),
completion_list
)
def
test_portal_itself
(
self
):
# non regression for bug, when completing on portal. this cause:
# AttributeError: 'CompiledObject' object has no attribute 'py__get__'
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getTitle"
),
completion_list
)
class
TestCompleteCatalogTool
(
PythonSupportTestCase
):
def
test_brain
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog.searchResults().'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getObject"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"title"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"path"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getTitle"
),
completion_list
)
def
test_portal_type
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog.searchResults(
portal_type='Bank Account').getObject().getBank'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getBankAccountHolderName"
),
completion_list
)
def
test_arguments
(
self
):
# catalog columns
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog(titl'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"title"
),
completion_list
)
# related keys
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog(tra'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"translated_simulation_state_title"
),
completion_list
)
# category dynamic related keys
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog(grou'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"group_uid"
),
completion_list
)
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog(default_grou'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"default_group_uid"
),
completion_list
)
# scriptable keys
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_catalog(full'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"full_text"
),
completion_list
)
class
TestCompleteSimulationTool
(
PythonSupportTestCase
):
def
test_inventory_list_brain
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_simulation.getInventoryList()[0].'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getObject"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"section_title"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"section_uid"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"quantity"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"total_price"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getQuantity"
),
completion_list
)
def
test_movement_history_list_brain
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_simulation.getMovementHistoryList()[0].'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getObject"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"section_title"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"section_uid"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"quantity"
),
completion_list
)
self
.
assertCompletionIn
(
Completion
(
text
=
"total_price"
),
completion_list
)
self
.
assertCompletionNotIn
(
Completion
(
text
=
"getQuantity"
),
completion_list
)
def
test_brain_date_is_date_time
(
self
):
for
method
in
(
'getInventoryList'
,
'getMovementHistoryList'
,):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_simulation.{}()[0].date.'''
).
format
(
method
))
self
.
assertCompletionIn
(
Completion
(
text
=
"year"
),
completion_list
)
def
test_brain_node_value_is_node
(
self
):
for
method
in
(
'getInventoryList'
,
'getMovementHistoryList'
,):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_simulation.{}()[0].node_value.'''
).
format
(
method
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getTitle"
),
completion_list
)
def
test_portal_type
(
self
):
for
method
in
(
'getInventoryList'
,
'getMovementHistoryList'
,):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_simulation.{}(
portal_type='Sale Order Line'
)[0].getObject().getPortalType'''
).
format
(
method
))
self
.
assertCompletionIn
(
Completion
(
description
=
re
.
compile
(
"Sale Order Line"
)),
completion_list
)
class
TestCompletePreferenceTool
(
PythonSupportTestCase
):
def
test_preference_tool_preference_getter
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_preferrences.get'''
))
self
.
assertCompletionIn
(
Completion
(
text
=
"getPreferredClientRoleList"
),
completion_list
)
def
test_preference_tool_preference_setter
(
self
):
completion_list
=
self
.
getCompletionList
(
textwrap
.
dedent
(
'''
portal = None # type: erp5.portal_type.ERP5Site
portal.portal_preferrences.set'''
))
self
.
assertCompletionNotIn
(
Completion
(
text
=
"setPreferredClientRoleList"
),
completion_list
)
\ No newline at end of file
bt5/erp5_python_support/TestTemplateItem/portal_components/test.erp5.testPythonSupport.xml
0 → 100644
View file @
3425583d
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Test Component"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_recorded_property_dict
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAI=
</string>
</persistent>
</value>
</item>
<item>
<key>
<string>
default_reference
</string>
</key>
<value>
<string>
testPythonSupport
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
test.erp5.testPythonSupport
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Test Component
</string>
</value>
</item>
<item>
<key>
<string>
sid
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
text_content_error_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
text_content_warning_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
version
</string>
</key>
<value>
<string>
erp5
</string>
</value>
</item>
<item>
<key>
<string>
workflow_history
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAM=
</string>
</persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"2"
aka=
"AAAAAAAAAAI="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"3"
aka=
"AAAAAAAAAAM="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary>
<item>
<key>
<string>
component_validation_workflow
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAQ=
</string>
</persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.Workflow"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_log
</string>
</key>
<value>
<list>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<string>
validate
</string>
</value>
</item>
<item>
<key>
<string>
validation_state
</string>
</key>
<value>
<string>
validated
</string>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_python_support/ToolComponentTemplateItem/portal_components/tool.erp5.PythonSupportTool.py
0 → 100644
View file @
3425583d
import
sys
from
collections
import
namedtuple
import
logging
import
json
import
functools
import
textwrap
import
enum
from
typing
import
Union
,
List
,
Literal
,
Dict
,
NamedTuple
,
TYPE_CHECKING
# pylint: disable=unused-import
from
AccessControl
import
ClassSecurityInfo
from
Products.ERP5Type.Tool.BaseTool
import
BaseTool
from
Products.ERP5Type
import
Permissions
import
jedi
logger
=
logging
.
getLogger
(
__name__
)
def
loadJson
(
data
):
"""Load json in objects (and not dictionaries like json.loads does by default).
"""
return
json
.
loads
(
data
,
object_hook
=
lambda
d
:
namedtuple
(
'Unknown'
,
d
.
keys
())(
*
d
.
values
())
)
def
dumpsJson
(
data
):
"""symetric of loadJson, dumps to json, with support of simple objects.
"""
def
flatten
(
obj
):
if
hasattr
(
obj
,
'_asdict'
):
# namedtuple
obj
=
obj
.
_asdict
()
if
isinstance
(
obj
,
dict
):
return
{
k
:
flatten
(
v
)
for
k
,
v
in
obj
.
items
()}
if
isinstance
(
obj
,
(
list
,
tuple
)):
return
[
flatten
(
x
)
for
x
in
obj
]
if
hasattr
(
obj
,
'__dict__'
):
obj
=
obj
.
__dict__
return
obj
return
json
.
dumps
(
flatten
(
data
))
def
json_serialized
(
f
):
"""Transparently deserialize `data` parameter and serialize the returned value to/as json.
"""
@
functools
.
wraps
(
f
)
def
wrapper
(
self
,
data
):
return
dumpsJson
(
f
(
self
,
loadJson
(
data
)))
return
wrapper
Position
=
namedtuple
(
'Position'
,
'lineNumber, column'
)
"""Position in the editor, same as monaco, ie. indexed from 1
"""
if
TYPE_CHECKING
:
import
erp5.portal_type
# pylint: disable=import-error,unused-import
# XXX "Context" is bad name
class
Context
:
code
=
None
# type: str
class
PythonScriptContext
(
Context
):
script_name
=
None
# type: str
bound_names
=
None
# type: List[str]
params
=
None
# type: str
class
CompletionContext
(
Context
):
position
=
None
# type: Position
class
PythonScriptCompletionContext
(
CompletionContext
,
PythonScriptContext
):
"""completion for portal_skins's Script (Python)
"""
CompletionKind
=
Union
[
Literal
[
'Method'
],
Literal
[
'Function'
],
Literal
[
'Constructor'
],
Literal
[
'Field'
],
Literal
[
'Variable'
],
Literal
[
'Class'
],
Literal
[
'Struct'
],
Literal
[
'Interface'
],
Literal
[
'Module'
],
Literal
[
'Property'
],
Literal
[
'Event'
],
Literal
[
'Operator'
],
Literal
[
'Unit'
],
Literal
[
'Value'
],
Literal
[
'Constant'
],
Literal
[
'Enum'
],
Literal
[
'EnumMember'
],
Literal
[
'Keyword'
],
Literal
[
'Text'
],
Literal
[
'Color'
],
Literal
[
'File'
],
Literal
[
'Reference'
],
Literal
[
'Customcolor'
],
Literal
[
'Folder'
],
Literal
[
'TypeParameter'
],
Literal
[
'Snippet'
],
]
class
CompletionKind
(
enum
.
Enum
):
# pylint: disable=function-redefined
Method
=
'Method'
Function
=
'Function'
Constructor
=
'Constructor'
Field
=
'Field'
Variable
=
'Variable'
Class
=
'Class'
Struct
=
'Struct'
Interface
=
'Interface'
Module
=
'Module'
Property
=
'Property'
Event
=
'Event'
Operator
=
'Operator'
Unit
=
'Unit'
Value
=
'Value'
Constant
=
'Constant'
Enum
=
'Enum'
EnumMember
=
'EnumMember'
Keyword
=
'Keyword'
Text
=
'Text'
Color
=
'Color'
File
=
'File'
Reference
=
'Reference'
Customcolor
=
'Customcolor'
Folder
=
'Folder'
TypeParameter
=
'TypeParameter'
Snippet
=
'Snippet'
# https://microsoft.github.io/monaco-editor/api/interfaces/monaco.imarkdownstring.html
class
IMarkdownString
(
NamedTuple
(
'IMarkdownString'
,
((
'value'
,
str
),))):
value
=
None
# type: str
class
CompletionItem
(
NamedTuple
(
'CompletionItem'
,
(
(
'label'
,
str
),
(
'kind'
,
CompletionKind
),
(
'detail'
,
str
),
(
'documentation'
,
Union
[
str
,
IMarkdownString
]),
(
'sortText'
,
str
),
(
'insertText'
,
str
),
))):
"""A completion item represents a text snippet that is proposed to complete text that is being typed.
https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitem.html
"""
label
=
None
# type: str
kind
=
None
# type: CompletionKind
detail
=
None
# type: str
documentation
=
None
# type: Union[str, IMarkdownString]
sortText
=
None
# type: str
insertText
=
None
# type: str
logger
=
logging
.
getLogger
(
__name__
)
class
PythonSupportTool
(
BaseTool
):
"""Tool to support code editors.
"""
portal_type
=
'Python Support Tool'
meta_type
=
'ERP5 {}'
.
format
(
portal_type
)
id
=
'portal_python_support'
security
=
ClassSecurityInfo
()
security
.
declareObjectProtected
(
Permissions
.
ManagePortal
)
def
_getCode
(
self
,
completion_context
):
# type: (PythonScriptCompletionContext) -> str
return
''
def
getStubPath
(
self
):
"""The path where stubs are generated.
"""
return
"/tmp/stubs/"
def
_convertCompletion
(
self
,
completion
):
# type: (jedi.api.classes.Completion,) -> CompletionItem
"""Convert a completion from jedi format to the format used by text editors.
"""
# map jedi type to the name of monaco.languages.CompletionItemKind
# This mapping and this method are copied/inspired by jedi integration in python-language-server
# https://github.com/palantir/python-language-server/blob/19b10c47988df504872a4fe07c421b0555b3127e/pyls/plugins/jedi_completion.py
# python-language-server is Copyright 2017 Palantir Technologies, Inc. and distributed under MIT License.
# https://github.com/palantir/python-language-server/blob/19b10c47988df504872a4fe07c421b0555b3127e/LICENSE
_TYPE_MAP
=
{
'none'
:
CompletionKind
.
Value
,
'type'
:
CompletionKind
.
Class
,
'tuple'
:
CompletionKind
.
Class
,
'dict'
:
CompletionKind
.
Class
,
'dictionary'
:
CompletionKind
.
Class
,
'function'
:
CompletionKind
.
Function
,
'lambda'
:
CompletionKind
.
Function
,
'generator'
:
CompletionKind
.
Function
,
'class'
:
CompletionKind
.
Class
,
'instance'
:
CompletionKind
.
Reference
,
'method'
:
CompletionKind
.
Method
,
'builtin'
:
CompletionKind
.
Class
,
'builtinfunction'
:
CompletionKind
.
Function
,
'module'
:
CompletionKind
.
Module
,
'file'
:
CompletionKind
.
File
,
'xrange'
:
CompletionKind
.
Class
,
'slice'
:
CompletionKind
.
Class
,
'traceback'
:
CompletionKind
.
Class
,
'frame'
:
CompletionKind
.
Class
,
'buffer'
:
CompletionKind
.
Class
,
'dictproxy'
:
CompletionKind
.
Class
,
'funcdef'
:
CompletionKind
.
Function
,
'property'
:
CompletionKind
.
Property
,
'import'
:
CompletionKind
.
Module
,
'keyword'
:
CompletionKind
.
Keyword
,
'constant'
:
CompletionKind
.
Variable
,
'variable'
:
CompletionKind
.
Variable
,
'value'
:
CompletionKind
.
Value
,
'param'
:
CompletionKind
.
Variable
,
'statement'
:
CompletionKind
.
Keyword
,
}
# type: Dict[str, CompletionKind]
def
_label
(
definition
):
if
definition
.
type
in
(
'function'
,
'method'
)
and
hasattr
(
definition
,
'params'
):
params
=
', '
.
join
([
param
.
name
for
param
in
definition
.
params
])
return
'{}({})'
.
format
(
definition
.
name
,
params
)
return
definition
.
name
def
_detail
(
definition
):
try
:
return
definition
.
parent
().
full_name
or
''
except
AttributeError
:
return
definition
.
full_name
or
''
def
_sort_text
(
definition
):
""" Ensure builtins appear at the bottom.
Description is of format <type>: <module>.<item>
"""
# If its 'hidden', put it next last
prefix
=
'z{}'
if
definition
.
name
.
startswith
(
'_'
)
else
'a{}'
return
prefix
.
format
(
definition
.
name
)
def
_format_docstring
(
completion
):
# type: (jedi.api.classes.Completion,) -> Union[str, IMarkdownString]
# XXX we could check based on completion.module_path() python's stdlib tend to be rst
# but for now, we assume everything is markdown
return
IMarkdownString
(
completion
.
docstring
())
return
{
'label'
:
_label
(
completion
),
'kind'
:
_TYPE_MAP
.
get
(
completion
.
type
),
'detail'
:
_detail
(
completion
),
'documentation'
:
_format_docstring
(
completion
),
'sortText'
:
_sort_text
(
completion
),
'insertText'
:
completion
.
name
}
@
json_serialized
def
getCompletions
(
self
,
completion_context
):
# type: (Union[CompletionContext, PythonScriptCompletionContext],) -> List[CompletionItem]
"""Returns completions.
"""
script
=
JediController
(
self
,
# fixPythonScriptContext not here !
fixPythonScriptContext
(
completion_context
,
self
.
getPortalObject
())
).
getScript
()
return
[
self
.
_convertCompletion
(
c
)
for
c
in
script
.
completions
()]
@
json_serialized
def
getCodeLens
(
self
,
completion_context
):
# type: (Union[CompletionContext, PythonScriptCompletionContext],) -> List[CompletionItem]
"""Returns code lens.
"""
return
[]
def
fixPythonScriptContext
(
context
,
portal
):
# type: (Union[CompletionContext, PythonScriptCompletionContext], erp5.portal_type.ERP5Site) -> CompletionContext
"""Normalize completion context for python scripts
ie. make a function with params and adjust the line number.
"""
if
not
getattr
(
context
,
"bound_names"
):
return
context
def
_guessParameterType
(
name
,
context_type
=
None
):
"""guess the type of python script parameters based on naming conventions.
"""
# TODO: `state_change` arguments for workflow scripts
name
=
name
.
split
(
'='
)[
0
]
# support also assigned names (like REQUEST=None in params)
if
name
==
'context'
and
context_type
:
return
context_type
if
name
in
(
'context'
,
'container'
,):
return
'erp5.portal_type.ERP5Site'
if
name
==
'script'
:
return
'Products.PythonScripts.PythonScript.PythonScript'
if
name
==
'REQUEST'
:
return
'ZPublisher.HTTPRequest.HTTPRequest'
if
name
==
'RESPONSE'
:
return
'ZPublisher.HTTPRequest.HTTPResponse'
return
'str'
# assume string by default
signature_parts
=
context
.
bound_names
+
(
[
context
.
params
]
if
context
.
params
else
[]
)
# guess type of `context`
context_type
=
None
if
'_'
in
context
:
context_type
=
context
.
split
(
'_'
)[
0
]
if
context_type
not
in
[
ti
.
replace
(
' '
,
''
)
# XXX "python identifier"
for
ti
in
portal
.
portal_types
.
objectIds
()
]
+
[
'ERP5Site'
,]:
logger
.
debug
(
"context_type %s has no portal type, using ERP5Site"
,
context_type
)
context_type
=
None
type_comment
=
" # type: ({}) -> None"
.
format
(
', '
.
join
(
[
_guessParameterType
(
part
,
context_type
)
for
part
in
signature_parts
]
)
)
def
indent
(
text
):
return
''
.
join
((
" "
+
line
)
for
line
in
text
.
splitlines
(
True
))
context
.
code
=
textwrap
.
dedent
(
'''import erp5.portal_type;
import Products.ERP5Type.Core.Folder;
import ZPublisher.HTTPRequest;
import Products.PythonScripts.PythonScript
def {script_name}({signature}):
{type_comment}
{body}
pass
'''
).
format
(
script_name
=
context
.
script_name
,
signature
=
', '
.
join
(
signature_parts
),
type_comment
=
type_comment
,
body
=
indent
(
context
.
code
)
)
context
.
position
.
lineNumber
+=
6
# imports, fonction header + type comment
context
.
position
.
column
+=
2
# re-indentation
return
context
class
JediController
(
object
):
"""Controls jedi.
"""
def
__init__
(
self
,
tool
,
context
):
# type: (PythonSupportTool, CompletionContext) -> None
if
not
self
.
_isEnabled
():
self
.
_patchBuildoutSupport
()
self
.
_enablePlugin
()
self
.
_sys_path
=
[
tool
.
getStubPath
()]
+
sys
.
path
self
.
_context
=
context
def
_isEnabled
(
self
):
"""is our plugin already enabled ?
"""
return
False
def
_enablePlugin
(
self
):
"""Enable our ERP5 jedi plugin.
"""
def
_patchBuildoutSupport
(
self
):
"""Patch jedi to disable buggy buildout.cfg support.
"""
# monkey patch to disable buggy sys.path addition based on buildout.
# https://github.com/davidhalter/jedi/issues/1325
# rdiff-backup also seem to trigger a bug, but it's generally super slow and not correct for us.
try
:
# in jedi 0.15.1 it's here
from
jedi.evaluate
import
sys_path
as
jedi_inference_sys_path
# pylint: disable=import-error,unused-import,no-name-in-module
except
ImportError
:
# but it's beeing moved. Next release (0.15.2) will be here
# https://github.com/davidhalter/jedi/commit/3b4f2924648eafb9660caac9030b20beb50a83bb
from
jedi.inference
import
sys_path
as
jedi_inference_sys_path
# pylint: disable=import-error,unused-import,no-name-in-module
_
=
jedi_inference_sys_path
.
discover_buildout_paths
# make sure we found it here
def
dont_discover_buildout_paths
(
*
args
,
**
kw
):
return
set
()
jedi_inference_sys_path
.
discover_buildout_paths
=
dont_discover_buildout_paths
from
jedi.api
import
project
as
jedi_api_project
jedi_api_project
.
discover_buildout_paths
=
dont_discover_buildout_paths
def
getScript
(
self
):
# type: () -> jedi.Script
"""Returns a jedi.Script for this code.
"""
# TODO: lock ! (and not only here)
context
=
self
.
_context
return
jedi
.
Script
(
context
.
code
,
context
.
position
.
lineNumber
,
context
.
position
.
column
-
1
,
context
.
script_name
,
self
.
_sys_path
)
@
staticmethod
def
jedi_execute
(
callback
,
context
,
arguments
):
# type: (Callable[[Any], Any], jedi.Context, Any) -> Any
"""jedi plugin `execute`
XXX
"""
return
"jedi executed"
class
PythonCodeGenerator
(
object
):
"""Generator python code for static analysis.
"""
# make sure PythonSupportTool is first, this is needed for dynamic components.
__all__
=
(
'PythonSupportTool'
,
'json_serialized'
,
'PythonCodeGenerator'
,
'Position'
)
bt5/erp5_python_support/ToolComponentTemplateItem/portal_components/tool.erp5.PythonSupportTool.xml
0 → 100644
View file @
3425583d
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Tool Component"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_recorded_property_dict
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAI=
</string>
</persistent>
</value>
</item>
<item>
<key>
<string>
default_reference
</string>
</key>
<value>
<string>
PythonSupportTool
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
tool.erp5.PythonSupportTool
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Tool Component
</string>
</value>
</item>
<item>
<key>
<string>
sid
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
text_content_error_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
text_content_warning_message
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
version
</string>
</key>
<value>
<string>
erp5
</string>
</value>
</item>
<item>
<key>
<string>
workflow_history
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAM=
</string>
</persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"2"
aka=
"AAAAAAAAAAI="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"3"
aka=
"AAAAAAAAAAM="
>
<pickle>
<global
name=
"PersistentMapping"
module=
"Persistence.mapping"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
data
</string>
</key>
<value>
<dictionary>
<item>
<key>
<string>
component_validation_workflow
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAQ=
</string>
</persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.Workflow"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_log
</string>
</key>
<value>
<list>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<string>
validate
</string>
</value>
</item>
<item>
<key>
<string>
validation_state
</string>
</key>
<value>
<string>
validated
</string>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_python_support/ToolTemplateItem/portal_python_support.xml
0 → 100644
View file @
3425583d
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Python Support Tool"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
portal_python_support
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Python Support Tool
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_python_support/bt/template_format_version
0 → 100644
View file @
3425583d
1
\ No newline at end of file
bt5/erp5_python_support/bt/template_portal_type_id_list
0 → 100644
View file @
3425583d
Python Support Tool
\ No newline at end of file
bt5/erp5_python_support/bt/template_test_id_list
0 → 100644
View file @
3425583d
test.erp5.testPythonSupport
\ No newline at end of file
bt5/erp5_python_support/bt/template_tool_component_id_list
0 → 100644
View file @
3425583d
tool.erp5.PythonSupportTool
\ No newline at end of file
bt5/erp5_python_support/bt/template_tool_id_list
0 → 100644
View file @
3425583d
portal_python_support
\ No newline at end of file
bt5/erp5_python_support/bt/title
0 → 100644
View file @
3425583d
erp5_python_support
\ No newline at end of file
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