From 16100b46f39f576b7421114fd8af08c7df77805c Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 15:49:07 +0100 Subject: [PATCH 01/56] erp5_business_package: Introduction of Commit Tool and its content types --- .../Business%20Commit/content_view.xml | 85 + .../Business%20Commit/install.xml | 100 + .../Business%20Commit/rebuild_subobjects.xml | 100 + .../Business%20Item/content_view.xml | 85 + .../Business%20Item/diff_view.xml | 85 + .../Business%20Patch%20Item/content_view.xml | 85 + .../Business%20Patch%20Item/diff_view.xml | 85 + .../content_view.xml | 85 + .../Business%20Snapshot/content_view.xml | 85 + .../Business%20Template%20V2/content_view.xml | 85 + .../Commit%20Tool/create_snapshot.xml | 85 + .../portal_types/Commit%20Tool/view.xml | 85 + .../portal_actions/commit_tool.xml | 71 + .../extension.erp5.CommitUtils.py | 47 + .../extension.erp5.CommitUtils.xml | 121 + .../allowed_content_types.xml | 20 + .../base_category_list.xml | 19 + .../property_sheet_list.xml | 8 + .../portal_types/Business%20Commit.xml | 58 + .../portal_types/Business%20Item.xml | 64 + .../portal_types/Business%20Patch%20Item.xml | 58 + .../Business%20Property%20Item.xml | 64 + .../portal_types/Business%20Snapshot.xml | 58 + .../portal_types/Business%20Template%20V2.xml | 62 + .../portal_types/Commit%20Tool.xml | 58 + .../workflow_chain_type.xml | 22 + .../BusinessItemConstraint.xml | 66 + ...business_template_existence_constraint.xml | 86 + .../path_existence_constraint.xml | 80 + .../BusinessTemplateV2.xml | 66 + .../change_log_property.xml | 57 + .../BusinessTemplateV2/copyright_property.xml | 57 + .../dependency_property.xml | 57 + .../BusinessTemplateV2/item_path_property.xml | 36 + .../BusinessTemplateV2/license_property.xml | 57 + .../maintainer_property.xml | 57 + .../BusinessTemplateV2/provision_property.xml | 57 + .../test_dependency_property.xml | 36 + .../portal_skins/erp5_commit.xml | 26 + .../erp5_commit/Base_getLastCommit.py | 14 + .../erp5_commit/Base_getLastCommit.xml | 62 + .../erp5_commit/Base_getLastHistoryDiff.xml | 28 + .../BusinessCommit_getLastCommitTitle.xml | 28 + .../erp5_commit/BusinessCommit_install.py | 3 + .../erp5_commit/BusinessCommit_install.xml | 62 + .../erp5_commit/BusinessCommit_rebuild.py | 5 + .../erp5_commit/BusinessCommit_rebuild.xml | 62 + .../erp5_commit/BusinessCommit_view.xml | 147 ++ .../BusinessCommit_view/listbox.xml | 174 ++ .../listbox_follow_up_title.xml | 89 + .../listbox_item_layer.xml | 112 + .../BusinessCommit_view/listbox_item_path.xml | 98 + .../BusinessCommit_view/listbox_item_sign.xml | 112 + .../BusinessCommit_view/my_creation_date.xml | 89 + .../BusinessCommit_view/my_description.xml | 78 + .../erp5_commit/BusinessCommit_view/my_id.xml | 264 +++ .../my_modification_date.xml | 89 + .../my_predecessor_title.xml | 122 ++ .../my_source_project_title.xml | 89 + .../my_translated_validation_state_title.xml | 272 +++ .../your_business_template_title_list.xml | 126 ++ .../BusinessCommit_viewInstallationDialog.xml | 101 + .../BusinessItem_getObjectViewUrl.py | 3 + .../BusinessItem_getObjectViewUrl.xml | 62 + .../BusinessItem_redirectToZODBPath.py | 3 + .../BusinessItem_redirectToZODBPath.xml | 62 + .../erp5_commit/BusinessItem_view.xml | 137 ++ .../erp5_commit/BusinessItem_view/listbox.xml | 104 + .../BusinessItem_view/my_follow_up_title.xml | 136 ++ .../BusinessItem_view/my_item_layer.xml | 112 + .../BusinessItem_view/my_item_path.xml | 117 + .../BusinessItem_view/my_item_sign.xml | 112 + .../erp5_commit/BusinessPatchItem_view.xml | 137 ++ .../my_dependency_list.xml | 84 + .../my_follow_up_title.xml | 136 ++ .../BusinessPatchItem_view/my_item_layer.xml | 112 + .../BusinessPatchItem_view/my_item_path.xml | 112 + .../BusinessPatchItem_view/my_item_sign.xml | 112 + .../BusinessPatchItem_viewDiff.xml | 112 + .../business_patch_item_view_diff_gadget.xml | 136 ++ .../BusinessPatchItem_viewDiff/my_diff.xml | 99 + .../my_object_link.xml | 122 ++ .../erp5_commit/BusinessPropertyItem_view.xml | 135 ++ .../my_follow_up_title.xml | 136 ++ .../my_item_layer.xml | 117 + .../my_item_path.xml | 117 + .../my_item_sign.xml | 112 + .../erp5_commit/BusinessSnapshot_view.xml | 146 ++ .../BusinessSnapshot_view/listbox.xml | 170 ++ .../listbox_follow_up_title.xml | 89 + .../listbox_item_layer.xml | 88 + .../listbox_item_path.xml | 93 + .../listbox_item_sign.xml | 88 + .../my_creation_date.xml | 89 + .../BusinessSnapshot_view/my_description.xml | 78 + .../BusinessSnapshot_view/my_id.xml | 260 +++ .../my_modification_date.xml | 89 + .../my_similar_title.xml | 111 + .../BusinessSnapshot_view/my_title.xml | 264 +++ .../my_translated_validation_state_title.xml | 272 +++ .../erp5_commit/CommitTool_createSnapshot.py | 11 + .../erp5_commit/CommitTool_createSnapshot.xml | 62 + .../erp5_commit/CommitTool_viewCommitList.xml | 134 ++ .../CommitTool_viewCommitList/listbox.xml | 224 ++ .../listbox_business_template_title_list.xml | 123 ++ .../listbox_path_list.xml | 114 + ...siness_patch_item_view_diff_gadget.css.css | 6 + ...siness_patch_item_view_diff_gadget.css.xml | 28 + ...ness_patch_item_view_diff_gadget.html.html | 17 + ...iness_patch_item_view_diff_gadget.html.xml | 28 + ...business_patch_item_view_diff_gadget.js.js | 39 + ...usiness_patch_item_view_diff_gadget.js.xml | 28 + .../test.erp5.testBusinessPackage.py | 1945 +++++++++++++++++ .../test.erp5.testBusinessPackage.xml | 183 ++ .../test.erp5.testPortalPatchItem.py | 134 ++ .../test.erp5.testPortalPatchItem.xml | 104 + .../ToolTemplateItem/portal_commits.xml | 56 + .../business_commit_interaction_workflow.xml | 46 + .../interactions.xml | 28 + .../BusinessCommit_updateHeadCommitId.xml | 100 + ...Commit_updateTemplateStatustoAvailable.xml | 100 + .../scripts.xml | 28 + .../BusinessCommit_updateHeadCommitId.py | 8 + .../BusinessCommit_updateHeadCommitId.xml | 62 + .../BusinessCommit_updateTemplateStatus.py | 15 + .../BusinessCommit_updateTemplateStatus.xml | 62 + .../variables.xml | 22 + .../worklists.xml | 22 + .../business_commit_validation_workflow.xml | 66 + .../scripts.xml | 28 + .../scripts/BusinessCommit_commit.py | 21 + .../scripts/BusinessCommit_commit.xml | 62 + .../states.xml | 28 + .../states/commited.xml | 91 + .../states/draft.xml | 91 + .../states/pushed.xml | 88 + .../transitions.xml | 28 + .../transitions/commit.xml | 79 + .../transitions/commit_action.xml | 79 + .../transitions/push.xml | 79 + .../transitions/push_action.xml | 79 + .../variables.xml | 28 + .../variables/action.xml | 61 + .../variables/actor.xml | 61 + .../variables/comment.xml | 61 + .../variables/error_message.xml | 48 + .../variables/history.xml | 61 + .../variables/portal_type.xml | 48 + .../variables/time.xml | 61 + .../worklists.xml | 22 + .../business_item_interaction_workflow.xml | 46 + .../interactions.xml | 28 + ...usinessItem_updateFollowUpItemPathList.xml | 100 + .../scripts.xml | 28 + ...BusinessItem_updateFollowUpItemPathList.py | 4 + ...usinessItem_updateFollowUpItemPathList.xml | 62 + .../variables.xml | 22 + .../worklists.xml | 22 + ...ess_property_item_interaction_workflow.xml | 46 + .../interactions.xml | 28 + ...ropertyItem_updateFollowUpItemPathList.xml | 100 + .../scripts.xml | 28 + ...PropertyItem_updateFollowUpItemPathList.py | 4 + ...ropertyItem_updateFollowUpItemPathList.xml | 62 + .../variables.xml | 22 + .../worklists.xml | 22 + .../business_snapshot_validation_workflow.xml | 66 + .../scripts.xml | 22 + .../states.xml | 28 + .../states/draft.xml | 40 + .../states/installed.xml | 94 + .../states/not_installed.xml | 39 + .../states/replaced.xml | 91 + .../transitions.xml | 28 + .../transitions/build.xml | 79 + .../transitions/install.xml | 79 + .../transitions/install_action.xml | 79 + .../transitions/reinstall.xml | 79 + .../transitions/reinstall_action.xml | 79 + .../transitions/replace.xml | 79 + .../variables.xml | 28 + .../variables/action.xml | 61 + .../variables/actor.xml | 61 + .../variables/comment.xml | 61 + .../variables/error_message.xml | 48 + .../variables/history.xml | 61 + .../variables/portal_type.xml | 48 + .../variables/time.xml | 61 + .../worklists.xml | 22 + ...usiness_template_availability_workflow.xml | 66 + .../scripts.xml | 22 + .../states.xml | 28 + .../states/available.xml | 39 + .../states/draft.xml | 39 + .../states/installable.xml | 39 + .../transitions.xml | 28 + .../transitions/exhibit.xml | 79 + .../transitions/exhibit_action.xml | 79 + .../transitions/install.xml | 79 + .../transitions/install_action.xml | 79 + .../transitions/reinstall.xml | 79 + .../transitions/reinstall_action.xml | 79 + .../variables.xml | 28 + .../variables/action.xml | 61 + .../variables/actor.xml | 61 + .../variables/comment.xml | 61 + .../variables/error_message.xml | 48 + .../variables/history.xml | 61 + .../variables/portal_type.xml | 48 + .../variables/time.xml | 61 + .../worklists.xml | 22 + .../erp5_business_package/bt/copyright_list | 1 + .../erp5_business_package/bt/description | 1 + .../erp5_business_package/bt/license | 1 + .../erp5_business_package/bt/maintainer_list | 1 + .../bt/template_action_path_list | 13 + .../bt/template_extension_id_list | 1 + .../bt/template_format_version | 1 + ...late_portal_type_allowed_content_type_list | 10 + .../template_portal_type_base_category_list | 7 + .../bt/template_portal_type_id_list | 7 + .../template_portal_type_property_sheet_list | 2 + .../template_portal_type_workflow_chain_list | 6 + .../bt/template_property_sheet_id_list | 2 + .../bt/template_skin_id_list | 1 + .../bt/template_test_id_list | 2 + .../bt/template_tool_id_list | 1 + .../bt/template_workflow_id_list | 6 + .../bootstrap/erp5_business_package/bt/title | 1 + .../erp5_business_package/bt/version | 1 + 230 files changed, 17415 insertions(+) create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/install.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/rebuild_subobjects.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/diff_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/diff_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Property%20Item/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Snapshot/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Template%20V2/content_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/create_snapshot.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/portal_actions/commit_tool.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeBaseCategoryTemplateItem/base_category_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypePropertySheetTemplateItem/property_sheet_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Commit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Item.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Patch%20Item.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Property%20Item.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Snapshot.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Template%20V2.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Commit%20Tool.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/business_template_existence_constraint.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/path_existence_constraint.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/change_log_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/copyright_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/dependency_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/item_path_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/license_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/maintainer_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/provision_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/test_dependency_property.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastHistoryDiff.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_getLastCommitTitle.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_follow_up_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_layer.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_path.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_sign.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_creation_date.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_description.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_id.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_modification_date.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_predecessor_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_source_project_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_translated_validation_state_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/your_business_template_title_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_viewInstallationDialog.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/listbox.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_follow_up_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_layer.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_path.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_sign.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_dependency_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_follow_up_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_layer.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_path.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_sign.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/business_patch_item_view_diff_gadget.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_diff.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_object_link.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_follow_up_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_layer.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_path.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_sign.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_follow_up_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_layer.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_path.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_sign.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_creation_date.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_description.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_id.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_modification_date.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_similar_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_translated_validation_state_title.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_business_template_title_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_path_list.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.css create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.html create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.js create mode 100644 product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/ToolTemplateItem/portal_commits.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateHeadCommitId.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateTemplateStatustoAvailable.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/commited.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/draft.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/pushed.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/actor.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/comment.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/error_message.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/history.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/portal_type.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/time.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions/BusinessItem_updateFollowUpItemPathList.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions/BusinessPropertyItem_updateFollowUpItemPathList.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.py create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/draft.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/installed.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/not_installed.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/replaced.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/build.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/replace.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/actor.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/comment.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/error_message.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/history.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/portal_type.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/time.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/scripts.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/available.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/draft.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/installable.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall_action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/action.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/actor.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/comment.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/error_message.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/history.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/portal_type.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/time.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/worklists.xml create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/copyright_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/description create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/license create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/maintainer_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_action_path_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_extension_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_format_version create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_allowed_content_type_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_base_category_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_property_sheet_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_workflow_chain_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_property_sheet_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_skin_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_test_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_tool_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/template_workflow_id_list create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/title create mode 100644 product/ERP5/bootstrap/erp5_business_package/bt/version diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/content_view.xml new file mode 100644 index 00000000000..74ac6bbcc59 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessCommit_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/install.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/install.xml new file mode 100644 index 00000000000..7d04ec53a13 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/install.xml @@ -0,0 +1,100 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + AAAAAAAAAAM= + + + + description + + + + + + icon + + + + id + install + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 2.0 + + + title + Install Commit + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessCommit_install + + + + + + + + + + + + text + python: here.getValidationState() in (\'commited\', \'pushed\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/rebuild_subobjects.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/rebuild_subobjects.xml new file mode 100644 index 00000000000..ab06d3d13e0 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Commit/rebuild_subobjects.xml @@ -0,0 +1,100 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + AAAAAAAAAAM= + + + + description + + + + + + icon + + + + id + rebuild_subobjects + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 3.0 + + + title + Rebuild sub-objects + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessCommit_rebuild + + + + + + + + + + + + text + python: here.getValidationState() == \'draft\' + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/content_view.xml new file mode 100644 index 00000000000..2c1975e18b4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessItem_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/diff_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/diff_view.xml new file mode 100644 index 00000000000..419520c804c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Item/diff_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + diff_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 2.0 + + + title + Diff + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessItem_viewDiff + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/content_view.xml new file mode 100644 index 00000000000..ca8359a61d3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessPatchItem_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/diff_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/diff_view.xml new file mode 100644 index 00000000000..2f9f4e0fbf3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Patch%20Item/diff_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + diff_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 2.0 + + + title + Diff + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessPatchItem_viewDiff + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Property%20Item/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Property%20Item/content_view.xml new file mode 100644 index 00000000000..ea4e317dbd1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Property%20Item/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessPropertyItem_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Snapshot/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Snapshot/content_view.xml new file mode 100644 index 00000000000..a697728cd69 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Snapshot/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessSnapshot_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Template%20V2/content_view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Template%20V2/content_view.xml new file mode 100644 index 00000000000..a610e605007 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Business%20Template%20V2/content_view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + content_view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/BusinessManager_view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/create_snapshot.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/create_snapshot.xml new file mode 100644 index 00000000000..c7ed8b4d541 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/create_snapshot.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_action + + + + + category + object_action + + + condition + + + + description + + + + + + icon + + + + id + create_snapshot + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + Generate Snapshot + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/CommitTool_createSnapshot + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/view.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/view.xml new file mode 100644 index 00000000000..13a6253b1a4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/Commit%20Tool/view.xml @@ -0,0 +1,85 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + categories + + + action_type/object_view + + + + + category + object_view + + + condition + + + + description + + + + + + icon + + + + id + view + + + permissions + + + View + + + + + portal_type + Action Information + + + priority + 1.0 + + + title + View + + + visible + 1 + + + + + + + + + + + + text + string:${object_url}/CommitTool_viewCommitList + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/portal_actions/commit_tool.xml b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/portal_actions/commit_tool.xml new file mode 100644 index 00000000000..c1234082af1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ActionTemplateItem/portal_types/portal_actions/commit_tool.xml @@ -0,0 +1,71 @@ + + + + + + + + + + action + + AAAAAAAAAAI= + + + + category + global + + + condition + + + + description + + + + icon + + + + id + commit_tool + + + permissions + + + Manage portal + + + + + priority + 2.5 + + + title + Manage Commits + + + visible + 1 + + + + + + + + + + + + text + string:${portal_url}/portal_commits/view + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.py b/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.py new file mode 100644 index 00000000000..bfb81fdc549 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.py @@ -0,0 +1,47 @@ +############################################################################## +# +# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +def getLatestCommitTitle(commit): + """ + Function to get last commit title. + Returns None if there is no last commit + """ + portal = commit.getPortalObject() + commit_tool = portal.portal_commits + + # Get all commits in commit tool + commit_list = commit_tool.objectValues(portal_type='Business Commit') + # Remove the current created commit from the commit_list + commit_list = [l for l in commit_list if l != commit] + + # Get the commit which was created last + latest_commit = max(commit_list, key=lambda x: x.getCreationDate()) + + if latest_commit is None: + return '' + return latest_commit.getTitle() diff --git a/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.xml b/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.xml new file mode 100644 index 00000000000..a5f8fc99762 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ExtensionTemplateItem/portal_components/extension.erp5.CommitUtils.xml @@ -0,0 +1,121 @@ + + + + + + + + + + _recorded_property_dict + + AAAAAAAAAAI= + + + + default_reference + CommitUtils + + + description + Utility functions to be used with commit tool + + + id + extension.erp5.CommitUtils + + + portal_type + Extension Component + + + sid + + + + + + text_content_error_message + + + + + + text_content_warning_message + + + + + + version + erp5 + + + workflow_history + + AAAAAAAAAAM= + + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + component_validation_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + validate + + + validation_state + validated + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml new file mode 100644 index 00000000000..f646004a848 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeAllowedContentTypeTemplateItem/allowed_content_types.xml @@ -0,0 +1,20 @@ + + + Business Item + Business Patch Item + Business Property Item + + + Business Item + Business Property Item + + + Business Item + Business Patch Item + Business Property Item + + + Business Commit + Business Snapshot + + \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeBaseCategoryTemplateItem/base_category_list.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeBaseCategoryTemplateItem/base_category_list.xml new file mode 100644 index 00000000000..a0eed5f36d4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeBaseCategoryTemplateItem/base_category_list.xml @@ -0,0 +1,19 @@ + + + predecessor + similar + source_project + + + follow_up + + + follow_up + + + follow_up + + + similar + + \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypePropertySheetTemplateItem/property_sheet_list.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypePropertySheetTemplateItem/property_sheet_list.xml new file mode 100644 index 00000000000..5ecfa6987f6 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypePropertySheetTemplateItem/property_sheet_list.xml @@ -0,0 +1,8 @@ + + + BusinessItemConstraint + + + BusinessItemConstraint + + \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Commit.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Commit.xml new file mode 100644 index 00000000000..1a299904409 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Commit.xml @@ -0,0 +1,58 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 1 + + + id + Business Commit + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessCommit + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Item.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Item.xml new file mode 100644 index 00000000000..36bc788f696 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Item.xml @@ -0,0 +1,64 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 0 + + + id + Business Item + + + init_script + + + + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessItem + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Patch%20Item.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Patch%20Item.xml new file mode 100644 index 00000000000..6e70f68dc0d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Patch%20Item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 1 + + + id + Business Patch Item + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessPatchItem + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Property%20Item.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Property%20Item.xml new file mode 100644 index 00000000000..ed9878e97ff --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Property%20Item.xml @@ -0,0 +1,64 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 1 + + + id + Business Property Item + + + init_script + + + + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessPropertyItem + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Snapshot.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Snapshot.xml new file mode 100644 index 00000000000..d8efd616e8c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Snapshot.xml @@ -0,0 +1,58 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 1 + + + id + Business Snapshot + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessSnapshot + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Template%20V2.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Template%20V2.xml new file mode 100644 index 00000000000..6b0ce4b3de3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Business%20Template%20V2.xml @@ -0,0 +1,62 @@ + + + + + + + + + + content_icon + + + + + + description + Simplified version of Business Template + + + filter_content_types + 1 + + + id + Business Template V2 + + + init_script + + + + + + permission + + + + + + portal_type + Base Type + + + type_class + BusinessTemplateV2 + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Commit%20Tool.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Commit%20Tool.xml new file mode 100644 index 00000000000..ae2f2ce1f15 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeTemplateItem/portal_types/Commit%20Tool.xml @@ -0,0 +1,58 @@ + + + + + + + + + + content_icon + + + + + + description + + + + + + filter_content_types + 1 + + + id + Commit Tool + + + permission + + + + + + portal_type + Base Type + + + type_class + CommitTool + + + type_interface + + + + + + type_mixin + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml b/product/ERP5/bootstrap/erp5_business_package/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml new file mode 100644 index 00000000000..662d7fcaa06 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PortalTypeWorkflowChainTemplateItem/workflow_chain_type.xml @@ -0,0 +1,22 @@ + + + Business Commit + business_commit_interaction_workflow, business_commit_validation_workflow + + + Business Item + business_item_interaction_workflow + + + Business Property Item + business_property_item_interaction_workflow + + + Business Snapshot + business_snapshot_validation_workflow + + + Business Template V2 + business_template_availability_workflow + + \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint.xml new file mode 100644 index 00000000000..6e3b8067c14 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint.xml @@ -0,0 +1,66 @@ + + + + + + + + + + _count + + AAAAAAAAAAI= + + + + _mt_index + + AAAAAAAAAAM= + + + + _tree + + AAAAAAAAAAQ= + + + + description + + + + + + id + BusinessItemConstraint + + + portal_type + Property Sheet + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/business_template_existence_constraint.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/business_template_existence_constraint.xml new file mode 100644 index 00000000000..de371829698 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/business_template_existence_constraint.xml @@ -0,0 +1,86 @@ + + + + + + + + + + _identity_criterion + + AAAAAAAAAAI= + + + + _range_criterion + + AAAAAAAAAAM= + + + + constraint_base_category + + + follow_up + + + + + constraint_portal_type + python: (\'Business Template V2\',) + + + description + Follow Up Business Template must be defined + + + id + business_template_existence_constraint + + + message_category_not_set + Follow Up Business Template must be defined + + + portal_type + Category Existence Constraint + + + use_acquisition + 0 + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/path_existence_constraint.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/path_existence_constraint.xml new file mode 100644 index 00000000000..25149405f2f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessItemConstraint/path_existence_constraint.xml @@ -0,0 +1,80 @@ + + + + + + + + + + _identity_criterion + + AAAAAAAAAAI= + + + + _range_criterion + + AAAAAAAAAAM= + + + + constraint_property + + + item_path + + + + + description + + + + + + id + path_existence_constraint + + + message_no_such_property + Path must be defined + + + portal_type + Property Existence Constraint + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2.xml new file mode 100644 index 00000000000..a5b707bf6c3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2.xml @@ -0,0 +1,66 @@ + + + + + + + + + + _count + + AAAAAAAAAAI= + + + + _mt_index + + AAAAAAAAAAM= + + + + _tree + + AAAAAAAAAAQ= + + + + description + + + + + + id + BusinessTemplateV2 + + + portal_type + Property Sheet + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/change_log_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/change_log_property.xml new file mode 100644 index 00000000000..f0cbc40e0a8 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/change_log_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/text + + + + + description + A change log + + + id + change_log_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: \'\' + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/copyright_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/copyright_property.xml new file mode 100644 index 00000000000..a252ad67ca1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/copyright_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/lines + + + + + description + A list of copyright holders + + + id + copyright_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/dependency_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/dependency_property.xml new file mode 100644 index 00000000000..e6548c11580 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/dependency_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/lines + + + + + description + a list of template names required by this template + + + id + dependency_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/item_path_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/item_path_property.xml new file mode 100644 index 00000000000..6607d3c0b7c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/item_path_property.xml @@ -0,0 +1,36 @@ + + + + + + + + + + categories + + + elementary_type/lines + + + + + description + A list of paths used by this template. They can be path of erp5 objects or also of the property. + + + id + item_path_property + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/license_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/license_property.xml new file mode 100644 index 00000000000..893a9cb28ab --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/license_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/text + + + + + description + License + + + id + license_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: \'\' + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/maintainer_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/maintainer_property.xml new file mode 100644 index 00000000000..50802df01b2 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/maintainer_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/lines + + + + + description + A list of maintainers + + + id + maintainer_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/provision_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/provision_property.xml new file mode 100644 index 00000000000..0c5b93d0aa0 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/provision_property.xml @@ -0,0 +1,57 @@ + + + + + + + + + + _local_properties + + + + + id + mode + + + type + string + + + + + + + categories + + + elementary_type/lines + + + + + description + a list of template names provided by this template + + + id + provision_property + + + mode + w + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/test_dependency_property.xml b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/test_dependency_property.xml new file mode 100644 index 00000000000..d9ce685dd8d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/PropertySheetTemplateItem/portal_property_sheets/BusinessTemplateV2/test_dependency_property.xml @@ -0,0 +1,36 @@ + + + + + + + + + + categories + + + elementary_type/lines + + + + + description + a list of template names required by this template to run unit tests from an instance created only for unit tests + + + id + test_dependency_property + + + portal_type + Standard Property + + + property_default + python: () + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit.xml new file mode 100644 index 00000000000..62e2a0f7fa1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit.xml @@ -0,0 +1,26 @@ + + + + + + + + + + _objects + + + + + + id + erp5_commit + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.py new file mode 100644 index 00000000000..6b02838592b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.py @@ -0,0 +1,14 @@ +from operator import attrgetter + +portal = context.getPortalObject() + +commit_tool = portal.portal_commits +# Get all commits in commit tool +commit_list = commit_tool.objectValues(portal_type='Business Commit') + +lastest_commit = max(commit_list, key=attrgetter('creation_date')) + +if latest_commit is None: + return '' + +return latest_commit.title diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.xml new file mode 100644 index 00000000000..66021cc6280 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastCommit.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + + + + id + Base_getLastCommit + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastHistoryDiff.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastHistoryDiff.xml new file mode 100644 index 00000000000..dc1281ebf22 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/Base_getLastHistoryDiff.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _function + getLastHistoryDiff + + + _module + ZODBHistory + + + id + Base_getLastHistoryDiff + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_getLastCommitTitle.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_getLastCommitTitle.xml new file mode 100644 index 00000000000..818ee4ab74d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_getLastCommitTitle.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _function + getLatestCommitTitle + + + _module + CommitUtils + + + id + BusinessCommit_getLastCommitTitle + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.py new file mode 100644 index 00000000000..fb1d7d2da8a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.py @@ -0,0 +1,3 @@ +commit = context + +commit.install() diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.xml new file mode 100644 index 00000000000..e8888da4b0d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_install.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + + + + id + BusinessCommit_install + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.py new file mode 100644 index 00000000000..7808f9d8575 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.py @@ -0,0 +1,5 @@ +item_list = context.objectValues() + +# Re-build the sub-objects +for item in item_list: + item.build(context) diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.xml new file mode 100644 index 00000000000..643c1ff8c71 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_rebuild.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + + + + id + BusinessCommit_rebuild + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view.xml new file mode 100644 index 00000000000..ed00c76bf52 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view.xml @@ -0,0 +1,147 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + listbox + + + + + center + + + my_description + + + + + hidden + + + listbox_item_layer + listbox_item_path + listbox_item_sign + listbox_follow_up_title + + + + + left + + + my_id + my_predecessor_title + my_source_project_title + your_business_template_title_list + + + + + right + + + my_creation_date + my_modification_date + my_translated_validation_state_title + + + + + + + + id + BusinessCommit_view + + + method + POST + + + name + BusinessCommit_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Business Commit Definition + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox.xml new file mode 100644 index 00000000000..71b95a00eeb --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox.xml @@ -0,0 +1,174 @@ + + + + + + + + + + delegated_list + + + columns + editable_columns + lines + list_method + selection_name + sort + title + + + + + id + listbox + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + columns + + + + item_path + Path + + + item_sign + Sign + + + item_layer + Layer + + + portal_type + Portal Type + + + follow_up + Business Manager + + + + + + editable_columns + + + + item_path + Path + + + item_sign + Sign + + + item_layer + Layer + + + + + + field_id + my_view_mode_listbox + + + form_id + Base_viewFieldLibrary + + + lines + 40 + + + list_method + + AAAAAAAAAAI= + + + + selection_name + business_manager_view_selection + + + sort + + + + item_path + Path + + + + + + title + Commit Items + + + + + + + + + + + + + + + method_name + objectValues + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_follow_up_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_follow_up_title.xml new file mode 100644 index 00000000000..981e48f4b39 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_follow_up_title.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + required + title + + + + + id + listbox_follow_up_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_relation_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Business Template + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_layer.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_layer.xml new file mode 100644 index 00000000000..e33f134a0f1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_layer.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + listbox_item_layer + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + 1 + + + field_id + my_integer_field + + + form_id + Base_viewFieldLibrary + + + title + Value + + + + + + + + + + + + + + + _text + python: here.getBusinessPathLayer() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_path.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_path.xml new file mode 100644 index 00000000000..cb90277d223 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_path.xml @@ -0,0 +1,98 @@ + + + + + + + + + + delegated_list + + + height + required + title + + + + + id + listbox_item_path + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + field_id + my_text_area_field + + + form_id + Base_viewFieldLibrary + + + height + 1 + + + required + 1 + + + title + Path + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_sign.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_sign.xml new file mode 100644 index 00000000000..55753457418 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/listbox_item_sign.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + listbox_item_sign + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + 1 + + + field_id + my_checkbox + + + form_id + Base_viewFieldLibrary + + + title + Sign + + + + + + + + + + + + + + + _text + python: cell.getBusinessPathSign() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_creation_date.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_creation_date.xml new file mode 100644 index 00000000000..b453039ec48 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_creation_date.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + editable + title + + + + + id + my_creation_date + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + editable + 0 + + + field_id + my_date_time_field + + + form_id + Base_viewFieldLibrary + + + title + Creation Date + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_description.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_description.xml new file mode 100644 index 00000000000..275352b7691 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_description.xml @@ -0,0 +1,78 @@ + + + + + + + + + + delegated_list + + + + + + id + my_description + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_view_mode_description + + + form_id + Base_viewFieldLibrary + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_id.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_id.xml new file mode 100644 index 00000000000..6f07146ffec --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_id.xml @@ -0,0 +1,264 @@ + + + + + + + + + + id + my_id + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + Identification + + + display_maxwidth + + + + display_width + 30 + + + editable + 0 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 1 + + + title + ID + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_modification_date.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_modification_date.xml new file mode 100644 index 00000000000..d4173e8eb28 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_modification_date.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + editable + title + + + + + id + my_modification_date + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + editable + 0 + + + field_id + my_date_time_field + + + form_id + Base_viewFieldLibrary + + + title + Modification Date + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_predecessor_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_predecessor_title.xml new file mode 100644 index 00000000000..83cb9e8c4b8 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_predecessor_title.xml @@ -0,0 +1,122 @@ + + + + + + + + + + delegated_list + + + catalog_index + first_item + portal_type + title + + + + + id + my_predecessor_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + catalog_index + + + + field_id + + + + first_item + + + + form_id + + + + portal_type + + + + title + + + + + + + values + + + + catalog_index + title + + + field_id + my_relation_field + + + first_item + 1 + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Business Commit + Business Commit + + + + + + title + Predecessor + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_source_project_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_source_project_title.xml new file mode 100644 index 00000000000..7441025ca06 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_source_project_title.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + required + title + + + + + id + my_source_project_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_string_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Source Project + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_translated_validation_state_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_translated_validation_state_title.xml new file mode 100644 index 00000000000..c0cc8eb8b25 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/my_translated_validation_state_title.xml @@ -0,0 +1,272 @@ + + + + + + + + + + id + my_translated_validation_state_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + Draft, Pushed, Committed + + + display_maxwidth + + + + display_width + 20 + + + editable + 0 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 0 + + + title + Validation State + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/your_business_template_title_list.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/your_business_template_title_list.xml new file mode 100644 index 00000000000..709af883b82 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_view/your_business_template_title_list.xml @@ -0,0 +1,126 @@ + + + + + + + + + + delegated_list + + + default + description + editable + title + + + + + id + your_business_template_title_list + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + description + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + description + Business Templates affected by this commit + + + editable + 0 + + + field_id + my_lines_field + + + form_id + Base_viewFieldLibrary + + + title + Business Templates + + + + + + + + + + + + + + + _text + python: list(set([item for sublist in [l.getFollowUpTitleList() for l in here.objectValues()] for item in sublist])) + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_viewInstallationDialog.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_viewInstallationDialog.xml new file mode 100644 index 00000000000..1e8041be378 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessCommit_viewInstallationDialog.xml @@ -0,0 +1,101 @@ + + + + + + + + + + action + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + + + + hidden + + + + + + left + + + + + + right + + + + + + + + + id + BusinessCommit_viewInstallationDialog + + + method + POST + + + name + Base_viewInstallationDialog + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + + + + unicode_mode + 0 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.py new file mode 100644 index 00000000000..476087ce39b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.py @@ -0,0 +1,3 @@ +url = context.id +view_url = url+'/view' +return view_url diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.xml new file mode 100644 index 00000000000..b3fbde43288 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_getObjectViewUrl.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + **kw + + + id + BusinessItem_getObjectViewUrl + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.py new file mode 100644 index 00000000000..4c800f4ea6b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.py @@ -0,0 +1,3 @@ +item_path = context.getProperty('item_path') + +return context.Base_redirect('%s/view' % item_path) diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.xml new file mode 100644 index 00000000000..b37f5bbd6cc --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_redirectToZODBPath.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + + + + id + BusinessItem_redirectToZODBPath + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view.xml new file mode 100644 index 00000000000..74fd2f7e390 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view.xml @@ -0,0 +1,137 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + listbox + + + + + center + + + + + + hidden + + + + + + left + + + my_item_path + my_item_sign + + + + + right + + + my_item_layer + my_follow_up_title + + + + + + + + id + BusinessItem_view + + + method + POST + + + name + BusinessItem_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Business Item Definition + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/listbox.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/listbox.xml new file mode 100644 index 00000000000..0626c3edcc7 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/listbox.xml @@ -0,0 +1,104 @@ + + + + + + + + + + delegated_list + + + columns + title + + + + + id + listbox + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + columns + + + + title + Title + + + id + ID + + + translated_portal_type + Type + + + + + + field_id + my_view_mode_listbox + + + form_id + Base_viewFieldLibrary + + + title + Items + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_follow_up_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_follow_up_title.xml new file mode 100644 index 00000000000..1fe152f6508 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_follow_up_title.xml @@ -0,0 +1,136 @@ + + + + + + + + + + delegated_list + + + catalog_index + description + first_item + portal_type + required + title + + + + + id + my_follow_up_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + catalog_index + + + + field_id + + + + first_item + + + + form_id + + + + portal_type + + + + required + + + + title + + + + + + + values + + + + catalog_index + title + + + description + Business Manager related to the Business Item + + + field_id + my_relation_field + + + first_item + 1 + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Business Template V2 + Business Template V2 + + + + + + required + 1 + + + title + Business Template V2 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_layer.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_layer.xml new file mode 100644 index 00000000000..660a1bf3ac6 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_layer.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_layer + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_integer_field + + + form_id + Base_viewFieldLibrary + + + title + Layer + + + + + + + + + + + + + + + _text + python: here.getBusinessPathLayer() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_path.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_path.xml new file mode 100644 index 00000000000..c6b2b571e43 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_path.xml @@ -0,0 +1,117 @@ + + + + + + + + + + delegated_list + + + default + required + title + + + + + id + my_item_path + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_string_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Path + + + + + + + + + + + + + + + _text + python: here.getBusinessPath() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_sign.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_sign.xml new file mode 100644 index 00000000000..4c6ee5ebd4d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessItem_view/my_item_sign.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_sign + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_checkbox + + + form_id + Base_viewFieldLibrary + + + title + Sign + + + + + + + + + + + + + + + _text + python: here.getBusinessPathSign() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view.xml new file mode 100644 index 00000000000..57e35c35757 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view.xml @@ -0,0 +1,137 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + my_dependency_list + + + + + hidden + + + + + + left + + + my_item_path + my_item_sign + + + + + right + + + my_item_layer + my_follow_up_title + + + + + + + + id + BusinessPatchItem_view + + + method + POST + + + name + BusinessItem_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Business Item Definition + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_dependency_list.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_dependency_list.xml new file mode 100644 index 00000000000..acedb36ae7a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_dependency_list.xml @@ -0,0 +1,84 @@ + + + + + + + + + + delegated_list + + + title + + + + + id + my_dependency_list + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_lines_field + + + form_id + Base_viewFieldLibrary + + + title + Dependencies Title + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_follow_up_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_follow_up_title.xml new file mode 100644 index 00000000000..1fe152f6508 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_follow_up_title.xml @@ -0,0 +1,136 @@ + + + + + + + + + + delegated_list + + + catalog_index + description + first_item + portal_type + required + title + + + + + id + my_follow_up_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + catalog_index + + + + field_id + + + + first_item + + + + form_id + + + + portal_type + + + + required + + + + title + + + + + + + values + + + + catalog_index + title + + + description + Business Manager related to the Business Item + + + field_id + my_relation_field + + + first_item + 1 + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Business Template V2 + Business Template V2 + + + + + + required + 1 + + + title + Business Template V2 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_layer.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_layer.xml new file mode 100644 index 00000000000..d5307312837 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_layer.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_layer + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_integer_field + + + form_id + Base_viewFieldLibrary + + + title + Layer + + + + + + + + + + + + + + + _text + python: here.getProperty(\'item_layer\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_path.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_path.xml new file mode 100644 index 00000000000..14e94807ad1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_path.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_path + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_string_field + + + form_id + Base_viewFieldLibrary + + + title + Path + + + + + + + + + + + + + + + _text + python: here.getProperty(\'item_path\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_sign.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_sign.xml new file mode 100644 index 00000000000..3861e427087 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_view/my_item_sign.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_sign + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_checkbox + + + form_id + Base_viewFieldLibrary + + + title + Sign + + + + + + + + + + + + + + + _text + python: here.getProperty(\'item_sign\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff.xml new file mode 100644 index 00000000000..dab1d15d958 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff.xml @@ -0,0 +1,112 @@ + + + + + + + + + + _objects + + + + + + action + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + + + + hidden + + + my_diff + + + + + left + + + business_patch_item_view_diff_gadget + my_object_link + + + + + right + + + + + + + + + id + BusinessPatchItem_viewDiff + + + method + POST + + + name + BusinessPatchItem_viewDiff + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + View Diff + + + unicode_mode + 0 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/business_patch_item_view_diff_gadget.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/business_patch_item_view_diff_gadget.xml new file mode 100644 index 00000000000..1f097b2aa6a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/business_patch_item_view_diff_gadget.xml @@ -0,0 +1,136 @@ + + + + + + + + + + delegated_list + + + default + gadget_url + title + + + + + id + business_patch_item_view_diff_gadget + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + gadget_url + + AAAAAAAAAAM= + + + + title + + + + + + + values + + + + default + + + + field_id + my_gadget_field + + + form_id + Base_viewFieldLibrary + + + gadget_url + + + + title + Diff + + + + + + + + + + + + + + + _text + python: here.getDiff() + + + + + + + + + + + + _text + python: field.restrictedTraverse(\'business_patch_item_view_diff_gadget.html\').absolute_url() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_diff.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_diff.xml new file mode 100644 index 00000000000..3d603cf06e4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_diff.xml @@ -0,0 +1,99 @@ + + + + + + + + + + delegated_list + + + editable + height + title + width + + + + + id + my_diff + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + editable + 0 + + + field_id + my_text_area_field + + + form_id + Base_viewFieldLibrary + + + height + 100 + + + title + Diff + + + width + 100 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_object_link.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_object_link.xml new file mode 100644 index 00000000000..a6c534f94ed --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPatchItem_viewDiff/my_object_link.xml @@ -0,0 +1,122 @@ + + + + + + + + + + delegated_list + + + default + editable + link_type + title + + + + + id + my_object_link + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + editable + 0 + + + field_id + my_link_field + + + form_id + Base_viewFieldLibrary + + + link_type + relative + + + title + Object Link + + + + + + + + + + + + + + + _text + python: here.item_path.split(\'#\')[0] + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view.xml new file mode 100644 index 00000000000..2c9eed92664 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view.xml @@ -0,0 +1,135 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + + + + center + + + + + + hidden + + + + + + left + + + my_item_path + my_item_sign + + + + + right + + + my_item_layer + my_follow_up_title + + + + + + + + id + BusinessPropertyItem_view + + + method + POST + + + name + BusinessItem_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Business Item Definition + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_follow_up_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_follow_up_title.xml new file mode 100644 index 00000000000..1fe152f6508 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_follow_up_title.xml @@ -0,0 +1,136 @@ + + + + + + + + + + delegated_list + + + catalog_index + description + first_item + portal_type + required + title + + + + + id + my_follow_up_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + catalog_index + + + + field_id + + + + first_item + + + + form_id + + + + portal_type + + + + required + + + + title + + + + + + + values + + + + catalog_index + title + + + description + Business Manager related to the Business Item + + + field_id + my_relation_field + + + first_item + 1 + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Business Template V2 + Business Template V2 + + + + + + required + 1 + + + title + Business Template V2 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_layer.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_layer.xml new file mode 100644 index 00000000000..71475b25b1e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_layer.xml @@ -0,0 +1,117 @@ + + + + + + + + + + delegated_list + + + default + required + title + + + + + id + my_item_layer + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_integer_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Layer + + + + + + + + + + + + + + + _text + python: here.getBusinessPathLayer() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_path.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_path.xml new file mode 100644 index 00000000000..c6b2b571e43 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_path.xml @@ -0,0 +1,117 @@ + + + + + + + + + + delegated_list + + + default + required + title + + + + + id + my_item_path + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_string_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Path + + + + + + + + + + + + + + + _text + python: here.getBusinessPath() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_sign.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_sign.xml new file mode 100644 index 00000000000..4c6ee5ebd4d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessPropertyItem_view/my_item_sign.xml @@ -0,0 +1,112 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + my_item_sign + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + field_id + my_checkbox + + + form_id + Base_viewFieldLibrary + + + title + Sign + + + + + + + + + + + + + + + _text + python: here.getBusinessPathSign() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view.xml new file mode 100644 index 00000000000..16cc857f950 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view.xml @@ -0,0 +1,146 @@ + + + + + + + + + + _objects + + + + + + action + Base_edit + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + listbox + + + + + center + + + my_description + + + + + hidden + + + listbox_item_path + listbox_item_sign + listbox_item_layer + listbox_follow_up_title + + + + + left + + + my_title + my_id + my_similar_title + + + + + right + + + my_creation_date + my_modification_date + my_translated_validation_state_title + + + + + + + + id + BusinessSnapshot_view + + + method + POST + + + name + BusinessSnapshot_view + + + pt + form_view + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Business Snapshot Definition + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox.xml new file mode 100644 index 00000000000..4d914cebb82 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox.xml @@ -0,0 +1,170 @@ + + + + + + + + + + delegated_list + + + columns + editable_columns + lines + list_method + selection_name + sort + title + + + + + id + listbox + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + columns + + + + item_path + Path + + + item_sign + Sign + + + item_layer + Layer + + + portal_type + Portal Type + + + + + + editable_columns + + + + item_path + Path + + + item_sign + Sign + + + item_layer + Layer + + + + + + field_id + my_view_mode_listbox + + + form_id + Base_viewFieldLibrary + + + lines + 40 + + + list_method + + AAAAAAAAAAI= + + + + selection_name + business_manager_view_selection + + + sort + + + + item_path + Path + + + + + + title + Path Items + + + + + + + + + + + + + + + method_name + objectValues + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_follow_up_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_follow_up_title.xml new file mode 100644 index 00000000000..981e48f4b39 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_follow_up_title.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + required + title + + + + + id + listbox_follow_up_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_relation_field + + + form_id + Base_viewFieldLibrary + + + required + 1 + + + title + Business Template + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_layer.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_layer.xml new file mode 100644 index 00000000000..f9f1934dc60 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_layer.xml @@ -0,0 +1,88 @@ + + + + + + + + + + delegated_list + + + title + + + + + id + listbox_item_layer + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + field_id + my_integer_field + + + form_id + Base_viewFieldLibrary + + + title + Value + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_path.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_path.xml new file mode 100644 index 00000000000..8a20eb17834 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_path.xml @@ -0,0 +1,93 @@ + + + + + + + + + + delegated_list + + + height + title + + + + + id + listbox_item_path + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + field_id + my_text_area_field + + + form_id + Base_viewFieldLibrary + + + height + 1 + + + title + Path + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_sign.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_sign.xml new file mode 100644 index 00000000000..48fefb8d6f6 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/listbox_item_sign.xml @@ -0,0 +1,88 @@ + + + + + + + + + + delegated_list + + + title + + + + + id + listbox_item_sign + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + field_id + my_checkbox + + + form_id + Base_viewFieldLibrary + + + title + Sign + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_creation_date.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_creation_date.xml new file mode 100644 index 00000000000..b453039ec48 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_creation_date.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + editable + title + + + + + id + my_creation_date + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + editable + 0 + + + field_id + my_date_time_field + + + form_id + Base_viewFieldLibrary + + + title + Creation Date + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_description.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_description.xml new file mode 100644 index 00000000000..275352b7691 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_description.xml @@ -0,0 +1,78 @@ + + + + + + + + + + delegated_list + + + + + + id + my_description + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + field_id + my_view_mode_description + + + form_id + Base_viewFieldLibrary + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_id.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_id.xml new file mode 100644 index 00000000000..05966633b56 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_id.xml @@ -0,0 +1,260 @@ + + + + + + + + + + id + my_id + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + Identification + + + display_maxwidth + + + + display_width + 30 + + + editable + 1 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + max_length + + + + required + 1 + + + title + ID + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_modification_date.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_modification_date.xml new file mode 100644 index 00000000000..d4173e8eb28 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_modification_date.xml @@ -0,0 +1,89 @@ + + + + + + + + + + delegated_list + + + editable + title + + + + + id + my_modification_date + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + editable + 0 + + + field_id + my_date_time_field + + + form_id + Base_viewFieldLibrary + + + title + Modification Date + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_similar_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_similar_title.xml new file mode 100644 index 00000000000..cc58c54a9a1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_similar_title.xml @@ -0,0 +1,111 @@ + + + + + + + + + + delegated_list + + + catalog_index + description + first_item + portal_type + title + + + + + id + my_similar_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + catalog_index + title + + + description + Commit on which this snapshot is based upon. + + + field_id + my_relation_field + + + first_item + 1 + + + form_id + Base_viewFieldLibrary + + + portal_type + + + + Business Commit + Business Commit + + + + + + title + Equivalent Commit + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_title.xml new file mode 100644 index 00000000000..b2b86ecfcbb --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_title.xml @@ -0,0 +1,264 @@ + + + + + + + + + + id + my_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + The name of the business template, must not change between revision as this is the property used to retrieve old one when upgrading + + + display_maxwidth + + + + display_width + 30 + + + editable + 1 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 1 + + + title + Title + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_translated_validation_state_title.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_translated_validation_state_title.xml new file mode 100644 index 00000000000..52b29d9f243 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/BusinessSnapshot_view/my_translated_validation_state_title.xml @@ -0,0 +1,272 @@ + + + + + + + + + + id + my_translated_validation_state_title + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + required_not_found + Input is required but no input given. + + + too_long + Too much input was given. + + + + + + overrides + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + tales + + + + alternate_name + + + + css_class + + + + default + + + + description + + + + display_maxwidth + + + + display_width + + + + editable + + + + enabled + + + + external_validator + + + + extra + + + + hidden + + + + input_type + + + + max_length + + + + required + + + + title + + + + truncate + + + + unicode + + + + whitespace_preserve + + + + + + + values + + + + alternate_name + + + + css_class + + + + default + + + + description + Draft, Installed, Not Installed + + + display_maxwidth + + + + display_width + 20 + + + editable + 0 + + + enabled + 1 + + + external_validator + + + + extra + + + + hidden + 0 + + + input_type + text + + + max_length + + + + required + 0 + + + title + Validation State + + + truncate + 0 + + + unicode + 0 + + + whitespace_preserve + 0 + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.py b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.py new file mode 100644 index 00000000000..9bf4b7e3677 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.py @@ -0,0 +1,11 @@ +# Check if the conext is Commit Tool +if context.getPortalType() != 'Commit Tool': + return 'context is not commit tool' + +# Get the HEAD commit and create a snapshot based on it +head_commit = context.getHeadCommit() + +# Create a new snapshot based on HEAD commit +snapshot = head_commit.createEquivalentSnapshot() + +return context.Base_redirect('view') diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.xml new file mode 100644 index 00000000000..90b360682ea --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_createSnapshot.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + **kw + + + id + CommitTool_createSnapshot + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList.xml new file mode 100644 index 00000000000..103fb542991 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList.xml @@ -0,0 +1,134 @@ + + + + + + + + + + _objects + + + + + + action + + + + description + + + + edit_order + + + + + + encoding + UTF-8 + + + enctype + + + + group_list + + + left + right + center + bottom + hidden + + + + + groups + + + + bottom + + + listbox + + + + + center + + + + + + hidden + + + listbox_business_template_title_list + listbox_path_list + + + + + left + + + + + + right + + + + + + + + + id + CommitTool_viewCommitList + + + method + POST + + + name + CommitTool_viewCommitList + + + pt + form_list + + + row_length + 4 + + + stored_encoding + UTF-8 + + + title + Commits + + + unicode_mode + 0 + + + update_action + + + + update_action_title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox.xml new file mode 100644 index 00000000000..8def662cf48 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox.xml @@ -0,0 +1,224 @@ + + + + + + + + + + delegated_list + + + all_columns + anchor + columns + editable_columns + lines + list_method + search + search_columns + select + selection_name + sort + title + + + + + id + listbox + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + field_id + + + + form_id + + + + + + + values + + + + all_columns + + + + predecessor_title + Predecessor + + + similar_title + Similar + + + path_list + Paths + + + + + + anchor + 0 + + + columns + + + + id + ID + + + description + Description + + + portal_type + Type + + + creation_date + Date + + + translated_validation_state_title + State + + + business_template_title_list + Business Templates + + + + + + editable_columns + + + + + + field_id + my_view_mode_listbox + + + form_id + Base_viewFieldLibrary + + + lines + 50 + + + list_method + + AAAAAAAAAAI= + + + + search + 1 + + + search_columns + + + + id + ID + + + description + Description + + + portal_type + Type + + + creation_date + Date + + + + + + select + 1 + + + selection_name + + + + sort + + + + creation_date + Date + + + + + + title + Commits + + + + + + + + + + + + + + + method_name + objectValues + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_business_template_title_list.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_business_template_title_list.xml new file mode 100644 index 00000000000..6a82d46febb --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_business_template_title_list.xml @@ -0,0 +1,123 @@ + + + + + + + + + + delegated_list + + + default + editable + title + + + + + id + listbox_business_template_title_list + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + editable + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + + + editable + 0 + + + field_id + my_lines_field + + + form_id + Base_viewFieldLibrary + + + title + Business Templates + + + + + + + + + + + + + + + _text + python: list(set([item for sublist in [l.getFollowUpTitleList() for l in cell.objectValues() if cell.getPortalType() == \'Business Commit\'] for item in sublist])) + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_path_list.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_path_list.xml new file mode 100644 index 00000000000..76f21160d82 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/CommitTool_viewCommitList/listbox_path_list.xml @@ -0,0 +1,114 @@ + + + + + + + + + + delegated_list + + + default + title + + + + + id + listbox_path_list + + + message_values + + + + external_validator_failed + The input failed the external validator. + + + + + + overrides + + + + field_id + + + + form_id + + + + + + + tales + + + + default + + AAAAAAAAAAI= + + + + field_id + + + + form_id + + + + title + + + + + + + values + + + + default + + + + + + field_id + my_lines_field + + + form_id + Base_viewFieldLibrary + + + title + Paths + + + + + + + + + + + + + + + _text + python: cell.getItemPathList() + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.css b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.css new file mode 100644 index 00000000000..7e5f3b6fbf5 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.css @@ -0,0 +1,6 @@ +pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } +.string { color: green; } +.number { color: darkorange; } +.boolean { color: blue; } +.null { color: magenta; } +.key { color: red; } \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.xml new file mode 100644 index 00000000000..bb37083f761 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.css.xml @@ -0,0 +1,28 @@ + + + + + + + + + + __name__ + business_patch_item_view_diff_gadget.css + + + content_type + text/css + + + precondition + + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.html b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.html new file mode 100644 index 00000000000..f73d15d3567 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.xml new file mode 100644 index 00000000000..87d02b1cd25 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.html.xml @@ -0,0 +1,28 @@ + + + + + + + + + + __name__ + business_patch_item_view_diff_gadget.html + + + content_type + text/html + + + precondition + + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.js b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.js new file mode 100644 index 00000000000..44ed5400c18 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.js @@ -0,0 +1,39 @@ +/*global window, rJS, RSVP, Handlebars, jIO, location, console */ +/*jslint nomen: true, maxlen:80, indent:2*/ +(function (rJS, jIO, Handlebars, RSVP, window) { + "use strict"; + var gk = rJS(window); + + function output(inp) { + document.body.appendChild(document.createElement('pre')).innerHTML = inp; + } + + function syntaxHighlight(json) { + json = json.replace(/&/g, '&').replace(//g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '' + match + ''; + }); + } + + rJS(window) + + .declareMethod('render', function (options) { + var patch = options.value; + console.log(patch); + // this.element.innerHTML = output(patch); + this.element.innerHTML = '
'+syntaxHighlight(JSON.stringify(JSON.parse(patch), undefined, 1))+'
'; + }); + +}(rJS, jIO, Handlebars, RSVP, window)); diff --git a/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.xml b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.xml new file mode 100644 index 00000000000..4b7e2408c8c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/SkinTemplateItem/portal_skins/erp5_commit/business_patch_item_view_diff_gadget.js.xml @@ -0,0 +1,28 @@ + + + + + + + + + + __name__ + business_patch_item_view_diff_gadget.js + + + content_type + application/javascript + + + precondition + + + + title + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.py b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.py new file mode 100644 index 00000000000..930a5e9b5c9 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.py @@ -0,0 +1,1945 @@ +############################################################################## +# +# Copyright (c) 2002-2017 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 time +import os +from App.config import getConfiguration +from urllib import pathname2url +from Products.ERP5.Document.BusinessPackage import createInstallationData + +class TestBusinessPackage(ERP5TypeTestCase): + """ + Test class to test that Business Package object can export some paths + and install them on an erp5 site + + Steps: + - Create BusinessPackage object + - Add path list to the object + - Build the package and expect the items mentioned in path list gets + exported in the build step + - Remove the objects mentioned in path_list from the site + - Install the package + - Expected result should be that it installs the objects on the site + """ + + def getTitle(self): + return "TestBusinessPackage" + + def getBusinessTemplateList(self): + """ + Tuple of Business Templates we need to install + """ + return ( + 'erp5_base', + 'erp5_core', + 'erp5_dms', + 'erp5_property_sheets', + 'erp5_business_package', + ) + + def afterSetUp(self): + """ + This is ran before anything, used to set the environment + """ + # here, you can create the categories and objects your test will depend on + #self.export_dir = tempfile.mkdtmp(dir=tests_home) + self.export_dir = '' + self.portal = self.getPortalObject() + + # create dummy portal_type to be used in current live test + #self.portal.portal_type('') + + def beforeTearDown(self): + try: + package_id = self.package.getId() + if package_id: + self.portal.manage_delObjects([package_id,]) + except AttributeError: + pass + + def _createBusinessManager(self, sequence=None, bm_id=None, title=None): + if not bm_id: + bm_id = 'manager_%s' % str(time.time()) + if not title: + title = bm_id + manager = self.portal.portal_templates.newContent( + id=bm_id, + title=title, + portal_type='Business Manager') + self.tic() + return manager + + def _exportBusinessManager(self, manager): + """ + Exports a Business Manager object to the destined path + """ + cfg = getConfiguration() + bm_title = pathname2url(manager.getTitle()) + manager_path = os.path.join(cfg.instancehome, 'tests', '%s' % (bm_title,)) + + # Export package at the package_path + manager.export(path=manager_path, local=True) + + return manager_path + + def _importBusinessManager(self, manager, manager_path, increment): + """ + Imports the package from the path where it had been exported. + + @params: + increment: Used for changing the ID of downloaded Business Manager + """ + + import_manager = self.portal.portal_templates.download( + url='file:'+manager_path, + id=manager.id+str(increment), + ) + + return import_manager + + def _copyBusinessManager(self, manager_id_list): + """ + Copy the manger objects and returns the newly copied manager objects + """ + portal_templates = self.portal.portal_templates + + copy_data = portal_templates.manage_copyObjects(ids=manager_id_list) + result_list = portal_templates.manage_pasteObjects(copy_data) + + new_id_list = [] + for res in result_list: + new_id_list.append(res['new_id']) + + manager_list = [] + for new_id in new_id_list: + manager_list.append(portal_templates._getOb(new_id)) + + return tuple(manager_list) + + def _addFolderInERP5(self, id=None): + """ + """ + if not id: + id = 'test_folder' + test_folder = self.portal.newContent(id=id, + portal_type='Folder', + title='couscous', + ) + return test_folder + + + + # ********** TESTS FOR COMBINING MULTIPLE BUSINESS MANAGERS ************ + ######################################################################### + + def _combineBusinessItemWithDifferentSign(self): + """ + Same path, same value, differernt sign + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + managerB = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + folder_path = test_folder.getRelativeUrl() + + path_item_folder_A = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list_A = [path_item_folder_A] + + managerA._setTemplatePathList(path_item_list_A) + managerA.build() + + path_item_folder_B = '%s | %s | %s' % (folder_path, -1, 1) + path_item_list_B = [path_item_folder_B] + + + managerB._setTemplatePathList(path_item_list_B) + managerB.build() + + bm_list = [managerA, managerB] + combinedBM = portal_templates.combineMultipleBusinessManager(bm_list) + + # ********** TESTS FOR DIFFERENT INSTALLATION USE CASES ************ + ##################################################################### + + def test_useCase_I(self): + """ + Case I: What to test here ? + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + folder_path = test_folder.getRelativeUrl() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', []) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the folder old title to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), + 'couscous') + + def test_useCase_II(self): + """ + Case II: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + folder_path = test_folder.getRelativeUrl() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', []) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Edit title of installed test_folder + test_folder.edit(title='new_couscous') + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), + 'new_couscous') + + def test_useCase_prop_II(self): + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + folder_path = test_folder.getRelativeUrl() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', []) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Edit title of installed test_folder + test_folder.setProperty('short_title', 'new_couscous') + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getProperty('short_title'), + 'new_couscous') + + def test_useCase_III(self): + """ + Case III: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', []) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), + 'couscous') + + def test_useCase_prop_III(self): + """ + Case III: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Unset the property from the object + test_folder.setProperty('short_title', '') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), + 'foo') + + def test_useCase_IV(self): + """ + Case IV: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', []) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', path_item_list) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), + 'couscous') + + def test_useCase_prop_IV(self): + """ + Case IV: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + # Set property on the test_folder object + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), + 'foo') + + def test_useCase_V(self): + """ + Case V: + _ A B: ?? + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + test_folder.edit(title='new_couscous') + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.edit(title='couscous') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder' in + context.exception) + + def test_useCase_prop_V(self): + """ + Case IV: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + test_folder = self._addFolderInERP5() + # Set property on the test_folder object + test_folder.setProperty('short_title', 'new_foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', 'foo') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder#short_title' + in context.exception) + + def test_useCase_VI(self): + """ + Case VI: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([installed_test_folder.getId(),]) + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(folder_path)) + + def test_useCase_prop_VI(self): + """ + Case VI: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Set property at ZODB to empty again + test_folder.setProperty('short_title', '') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Check if there is property installed on the test_folder + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + # Now again edit the property at ZODB + test_folder.setProperty('short_title', '') + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertIn(test_folder.getProperty('short_title'), ('', None)) + + def test_useCase_VII(self): + """ + Case VII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + self.portal.manage_delObjects([test_folder.getId(),]) + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([installed_test_folder.getId(),]) + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # This should install nothing as we prefer user changes + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(folder_path)) + + def test_useCase_prop_VII(self): + """ + Case VII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + self.assertIsNone(test_folder.getProperty('short_title'), '') + + def test_useCase_VIII(self): + """ + Case VIII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list', path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + # Edit the title of the test_folder + installed_test_folder.edit(title='new_couscous') + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([installed_test_folder.getId(),]) + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Object at test_folder removed by user' in + context.exception) + + def test_useCase_prop_VIII(self): + """ + Case VIII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + # Edit the title of the test_folder + test_folder.setProperty('short_title', 'new_foo') + + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Object at test_folder#short_title removed by user' in + context.exception) + + def test_useCase_IX(self): + """ + Case IX: + A A _ : _ + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + # Set empty path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Test that the catalogs don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(folder_path)) + + def test_useCase_prop_IX(self): + """ + Case IX: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + # Set empty path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Test that the catalogs don't exist on site anymore + self.assertIsNone(test_folder.getProperty('short_title'), '') + + def test_useCase_X(self): + """ + Case X: + A A A : A + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Install the updated Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + def test_useCase_prop_X(self): + """ + Case X: + A A A : A + """ + portal_templates = self.portal.portal_templatesd + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Install the updated Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + def test_useCase_XI(self): + """ + Case XI: + Installed ZODB edit Updated Expected ZODB + A A B: B + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + installed_test_folder.edit(title='new_couscous') + # Copy the Business Manager object, updated version of managerA + managerB_new, = self._copyBusinessManager([managerA.id,]) + managerB_new.build() + managerB_new.setStatus('uninstalled') + + installed_test_folder.edit(title='couscous') + + # Install the updated Business Manager + portal_templates.installMultipleBusinessManager([managerB_new,]) + + # Expect the Business Manager to be there + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'new_couscous') + + def test_useCase_prop_XI(self): + """ + Case XI: + Installed ZODB edit Updated Expected ZODB + A A B: B + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + test_folder.setProperty('short_title', 'new_foo') + # Copy the Business Manager object, updated version of managerA + managerB_new, = self._copyBusinessManager([managerA.id,]) + managerB_new.build() + managerB_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', 'foo') + + # Install the updated Business Manager + portal_templates.installMultipleBusinessManager([managerB_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'new_foo') + + def test_useCase_XII(self): + """ + Case XII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + installed_test_folder.edit(title='new_couscous') + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder' in + context.exception) + + def test_useCase_prop_XII(self): + """ + Case XII: + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + test_folder.setProperty('short_title', 'new_foo') + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',[]) + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder#short_title' + in context.exception) + + + def test_useCase_XIII(self): + """ + Case XIII: + A C A : C + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + installed_test_folder.edit(title='new_couscous') + + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'new_couscous') + + def test_useCase_prop_XIII(self): + """ + Case XIII: + A C A : C + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', 'new_foo') + + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'new_foo') + + def test_useCase_XIV(self): + """ + Case XIV: + A C B : ?? + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + + managerA_new.setStatus('uninstalled') + + # Delete the object from ZODB so as we can install the object there + self.portal.manage_delObjects([test_folder.getId(),]) + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), 'couscous') + + installed_test_folder.edit(title='new_couscous') + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + # Change the title at ZODB again + installed_test_folder.edit(title='new_couscous_redefined') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder' in + context.exception) + + def test_useCase_prop_XIV(self): + """ + Case XIV: + A C B : ?? + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + test_folder = self._addFolderInERP5() + test_folder.setProperty('short_title', 'foo') + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + # Add title as the property we want to use in path_item + path_item_folder = '%s#short_title | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerA + managerA.setProperty('template_path_list',path_item_list) + # Copy the Business Manager object + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + #manager_path = self._exportBusinessManager(managerA_new) + + managerA_new.setStatus('uninstalled') + #imported_manager = self._importBusinessManager(managerA_new, manager_path, 1) + + test_folder.setProperty('short_title', '') + # Install the Business Manager + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertEquals(test_folder.getProperty('short_title'), 'foo') + + test_folder.setProperty('short_title', 'new_foo') + # Copy the Business Manager object, updated version of managerA + managerA_new, = self._copyBusinessManager([managerA.id,]) + managerA_new.build() + managerA_new.setStatus('uninstalled') + + test_folder.setProperty('short_title', 'new_foo_redefined') + + with self.assertRaises(ValueError) as context: + portal_templates.installMultipleBusinessManager([managerA_new,]) + + self.assertTrue('Trying to remove changes at ZODB at test_folder#short_title' + in context.exception) + + def test_brokenModuleOnImport(self): + """ + Reproduce the broken object error in case of exporting and importing a + module object with the base type on which it depends on in the same Business + Manager + """ + import transaction + + base_type = self.portal.portal_types.newContent( + id='type_%s' % time.time(), + portal_type='Base Type', + type_class='Folder', + ) + + self.tic() + test_module = self.portal.newContent( + id='module_%s' % time.time(), + portal_type=base_type.id) + + + manager = self._createBusinessManager() + portal_templates = self.portal.portal_templates + + path_item_list = [ + 'portal_types/%s | 1 | 1' % base_type.id, + '%s | 1 | 1' % test_module.id, + ] + + manager.setProperty('template_path_list', path_item_list) + manager.build() + self.tic() + # Export the manager + manager.export('/srv/slapgrid/slappart16/srv/runner/instance/slappart6/tmp') + + self.portal.manage_delObjects(['%s' % test_module.id]) + self.portal.portal_types.manage_delObjects(['%s' % base_type.id]) + + portal_templates._delOb(manager.id) + + transaction.commit() + + # Import the Business Manager + imported_manager = portal_templates._importObjectFromFile('/srv/slapgrid/slappart16/srv/runner/instance/slappart6/tmp/%s.zexp' % manager.id) + + self.tic() + + def test_migrateCatalogBTToBM(self): + """ + Try to export-import-install erp5_mysql_innodb_catalog BT to BM. This + would help to keep track of the erp5_catalog which we use. + """ + portal = self.portal + portal_templates = portal.portal_templates + portal_templates.migrateBTToBM('/srv/slapgrid/slappart99/srv/runner/software/4310f5d661b87ce9e2281dbf4c784ed5/parts/erp5/product/ERP5/bootstrap/erp5_business_package') + + def _globalInstallationOfBusinessTemplate(self): + """ + NOTE: + Keep in mind that the installation is done on copy of built Business Manager + objects only, we are not yet exporting a Business Manager object + + USE CASE: + * 2 bt5: A / B + * B has a path C + * install A and B + * you should have C in ZODB + * modify B to remove path C + * modify A to provide a path C (with a different content to simplify) + * update A and B + * C' should be in ZODB + + EXPECTED RESULT: + Content of C': Something different than C, to be able to check + where C' is path C provided by A + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + managerB = self._createBusinessManager() + test_folder = self._addFolderInERP5() + + # Add catalog to the path list for Business Manager and build the object + folder_path = test_folder.getRelativeUrl() + path_item_folder = '%s | %s | %s' % (folder_path, 1, 1) + path_item_list = [path_item_folder] + + # Set catalog path item as path_item in managerB + managerB.setProperty('template_path_list',path_item_list) + + managerA_new, managerB_new = self._copyBusinessManager([ + managerA.id, + managerB.id, + ]) + + managerA_new.build() + managerB_new.build() + # Change the status of the new Business Manager objects as combined + # installation checks if the Business Manager has status 'uninstalled' + managerA_new.setStatus('uninstalled') + managerB_new.setStatus('uninstalled') + + # Delete the catalog object + self.portal.manage_delObjects([test_folder.getId(),]) + + # Test that the catalog don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(folder_path)) + + # Install both the Business Manager(s) + portal_templates.installMultipleBusinessManager([ + managerA_new, + managerB_new, + ]) + + # XXX: Match the state of manager A and B, nothing extra added + # portal_templates.installMultipleBusinessManager([ + # imported_package_A, + # imported_package_B, + # ]) + + # Test that the catalog exists on ZODB after installation + installed_test_folder = self.portal.restrictedTraverse(folder_path) + self.assertEquals(installed_test_folder.getTitle(), + 'couscous') + + # Add catalog_path to managerA and remove the catalog_path from managerB + managerA.setProperty('template_path_list',path_item_list) + managerB.setProperty('template_path_list',[]) + + managerA_new, managerB_new = self._copyBusinessManager([ + managerA.id, + managerB.id, + ]) + + installed_test_folder.edit(title='new_couscous') + + # Build the new managers so that they do have the Business Item(s) defined + # in them properly + managerA_new.build() + managerB_new.build() + + # Change the status of the new Business Manager objects as combined + # installation checks if the Business Manager has status 'uninstalled' + managerA_new.setStatus('uninstalled') + managerB_new.setStatus('uninstalled') + + # Then we change the title of test catalog again + installed_test_folder.edit(title='new_couscous_change_again') + + # Match the overall state, + # Install both the Business Manager(s) + portal_templates.installMultipleBusinessManager([ + managerA_new, + managerB_new, + ]) + + # Test that the catalog exists on ZODB after installation with the newer + # updated version + test_folder = self.portal.restrictedTraverse(installed_test_folder.getRelativeUrl()) + self.assertEquals(test_folder.getTitle(), "new_couscous") + + # Delete the test folder created at the path if it exists there + try: + self.portal.manage_delObjects([test_folder.getId(),]) + except Exception: + pass + + def _installationOfBusinessManagerViaTemplateTool(self): + """ + We try installing one or multiple Business Manager all via portal_templates, + keeping in mind that any operation done on BM should result in a BM which + can be easlily mapped with OFS. + """ + manager = self._createBusinessManager() + portal_templates = self.portal.portal_templates + + test_catalog_1 = self.portal.portal_catalog.newContent( + portal_type = 'Catalog', + title = 'Test Catalog 1 for Multiple BP5 Installation', + ) + + path_catalog_1 = test_catalog_1.getRelativeUrl() + path_item_catalog_1 = '%s | %s | %s'%(path_catalog_1, 1, 1) + path_item_list = [path_item_catalog_1] + + manager.setProperty('template_path_list',path_item_list) + built_manager = manager.build() + + bm_list = [] + bm_list.append(built_manager) + + self.portal.portal_catalog.manage_delObjects( \ + [test_catalog_1.getId(),]) + + # Test that the catalogs don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(path_catalog_1)) + + portal_templates.installMultipleBusinessManager(bm_list) + + catalog_1 = self.portal.restrictedTraverse(path_catalog_1) + self.assertEquals(catalog_1.getTitle(), \ + 'Test Catalog 1 for Multiple BP5 Installation') + + def reduceBusinessManagerWithTwoConflictingPath(self): + """ + Test the final Business Manager for Business Manager which have same path + at different layer + """ + portal_templates = self.portal.portal_templates + manager_1 = self._createBusinessManager() + manager_2 = self._createBusinessManager() + + test_catalog_1 = self.portal.portal_catalog.newContent( + portal_type = 'Catalog', + title = 'Test Catalog 1 for Multiple BP5 Installation', + ) + + path_catalog_1 = test_catalog_1.getRelativeUrl() + path_item_catalog_1 = '%s | %s | %s'%(path_catalog_1, 1, 1) + path_item_list_1 = [path_item_catalog_1] + + manager_1.setProperty('template_path_list',path_item_list_1) + built_manager_1 = manager_1.build() + + test_catalog_1.edit( + title = 'Test Catalog 2 for Multiple BP5 Installation', + ) + + path_item_catalog_2 = '%s | %s | %s'%(path_catalog_1, 1, 2) + path_item_list_2 = [path_item_catalog_2] + + manager_2._setTemplatePathList(path_item_list_2) + built_manager_2 = manager_2.build() + + self.portal.portal_catalog.manage_delObjects( \ + [test_catalog_1.getId(),]) + + # Test that the catalogs don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(path_catalog_1)) + + bm_list = [] + bm_list.append(built_manager_1) + bm_list.append(built_manager_2) + + portal_templates.installMultipleBusinessManager(bm_list) + + catalog_1 = self.portal.restrictedTraverse(path_catalog_1) + self.assertEquals(catalog_1.getTitle(), \ + 'Test Catalog 2 for Multiple BP5 Installation') + + def _UpdateVersionOfBusinessManager(self): + """ + * install bm A which add one workflow W1 + * install bm B which surcharge workflow W2 + * drop workflow W2 from bm configuration + * update bp5 B: ensure that the ZODB contains W1 + """ + portal_templates = self.portal.portal_templates + managerA = self._createBusinessManager() + managerB = self._createBusinessManager() + + test_catalog_A = self.portal.portal_catalog.newContent( + portal_type = 'Catalog', + title = 'Test Catalog A for Multiple BM Installation', + ) + + # Add catalog to the path list for Business Manager and build the object + path_catalog_A = test_catalog_A.getRelativeUrl() + path_item_catalog_A = '%s | %s | %s'%(path_catalog_A, 1, 1) + path_item_list_A = [path_item_catalog_A] + managerA._setTemplatePathList(path_item_list_A) + + built_manager_A = managerA.build() + + # Delete the catalog object + self.portal.portal_catalog.manage_delObjects( + [test_catalog_A.getId(),]) + + # Test that the catalog don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(path_catalog_A)) + + # Install the Business Manager A + portal_templates.installMultipleBusinessManager([built_manager_A]) + + # Test that the catalog exists + catalog_1 = self.portal.restrictedTraverse(path_catalog_A) + self.assertEquals(catalog_1.getTitle(), \ + 'Test Catalog A for Multiple BM Installation') + + # Create new Business Manager B with some different object + path_document_B = self.portal.document_module.newContent( + portal_type='File', + reference = 'erp5-package.Test.Document', + data='test data', + ) + + path_item_document_B = '%s | %s | %s'%(path_document_B.getRelativeUrl(), 1, 1) + managerB._setTemplatePathList([path_item_document_B]) + + built_manager_B = managerB.build() + + # Delete the document object + self.portal.document_module.manage_delObjects( + [path_document_B.getId(),]) + + # Add an empty path list in managerA + managerA._setTemplatePathList([]) + built_manager_A = managerA.build() + + # Try installing built Business Managers A and B together + bm_list = [] + + bm_list.append(built_manager_A) + bm_list.append(built_manager_B) + portal_templates.installMultipleBusinessManager(bm_list) + + # Check if the catalog still exists + catalog_1 = self.portal.restrictedTraverse(path_catalog_A) + self.assertEquals(catalog_1.getTitle(), \ + 'Test Catalog A for Multiple BM Installation') + + def _differentFileImportAndReinstallOnTwoPackages(self): + """ + Test two Business Templates build and installation of same file. + + Here we will be using Insatallation Tree in Template Tool to install + in the two configurations all together, rather than doing installation + one after another. + + Expected result: If we install same object from 2 different business packages, + then in that case the installation object should compare between the + state of OFS and installation and install accordingly. + """ + + old_package = self._createBusinessPackage() + new_package = self._createBusinessPackage() + + portal_templates = self.portal.portal_templates + + test_catalog_1 = self.portal.portal_catalog.newContent( + portal_type = 'Catalog', + title = 'Test Catalog 1 for Multiple BP5 Installation', + ) + test_catalog_2 = self.portal.portal_catalog.newContent( + portal_type = 'Catalog', + title = 'Test Catalog 2 for Multiple BP5 Installation', + ) + + # Update the property for the above mentioned objects so that we can use + # them in tests + test_catalog_1.edit( + sql_catalog_datetime_search_keys=[ + 'alarm.alarm_date', + 'alarm_date', + 'catalog.creation_date', + 'catalog.grouping_date', + 'catalog.modification_date' + ], + ) + + test_catalog_2.edit( + sql_catalog_datetime_search_keys=[ + 'creation_date', + 'date', + 'delivery.start_date', + 'delivery.start_date_range_max', + 'delivery.start_date_range_min', + ], + ) + + property_list = [ + 'sql_catalog_datetime_search_keys_list', + 'sql_catalog_full_text_search_keys_list', + ] + + path_1 = test_catalog_1.getRelativeUrl() + path_2 = test_catalog_2.getRelativeUrl() + + prop_list_1 = [] + prop_list_2 = [] + for prop_id in property_list: + prop_line_1 = '%s | %s' % (path_1, prop_id) + prop_line_2 = '%s | %s' % (path_2, prop_id) + prop_list_1.append(prop_line_1) + prop_list_2.append(prop_line_2) + + old_package.edit( + template_path_list=[path_1,], + template_object_property_list=prop_list_1, + ) + new_package.edit( + template_path_list=[path_2,], + template_object_property_list=prop_list_2, + ) + + # Build both the packages + old_package_path = self._buildAndExportBusinessPackage(old_package) + new_package_path = self._buildAndExportBusinessPackage(new_package) + import_old_package = self._importBusinessPackage(old_package, old_package_path) + import_new_package = self._importBusinessPackage(new_package, new_package_path) + # Get installation data from the list of packages which we want to install + package_list = [import_old_package, import_new_package] + + # Delete document from site + self.portal.portal_catalog.manage_delObjects( \ + [ + test_catalog_1.getId(), + test_catalog_2.getId(), + ]) + + # Test that the catalogs don't exist on site anymore + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(path_1)) + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(path_2)) + + # Install multiple Business Package all together + portal_templates.installMultipleBusinessPackage(package_list) + + catalog_1 = self.portal.restrictedTraverse(path_1) + catalog_2 = self.portal.restrictedTraverse(path_2) + + self.assertEquals(catalog_1.getTitle(), \ + 'Test Catalog 1 for Multiple BP5 Installation') + + self.assertEquals(catalog_2.getTitle(), \ + 'Test Catalog 2 for Multiple BP5 Installation') + + def _fileImportAndReinstallWithProperty(self): + """ + Test Business Package for Path and ObjectProperty Items together. + Here we export path as well propertie(s) for different objects and check + if we are able to install them back using Business Package + """ + bp_id = 'erp5_mysql_innodb_catalog_%s'%time.time() + package = self._createBusinessPackage(bp_id=bp_id) + catalog_path = 'portal_catalog/erp5_mysql_innodb' + file_path_list = ( + 'portal_catalog/erp5_mysql_innodb', + 'portal_catalog/erp5_mysql_innodb/**', + ) + + #erp5_catalog = self.portal.unrestrictedTraverse(catalog_path) + + property_list = [ + 'sql_catalog_datetime_search_keys_list', + 'sql_catalog_full_text_search_keys_list', + 'sql_catalog_keyword_search_keys_list', + 'sql_catalog_local_role_keys_list', + 'sql_catalog_multivalue_keys_list', + 'sql_catalog_related_keys_list', + 'sql_catalog_request_keys_list', + 'sql_search_result_keys_list', + 'sql_search_tables_list', + 'sql_catalog_role_keys_list', + 'sql_catalog_scriptable_keys_list', + 'sql_catalog_search_keys_list', + 'sql_catalog_security_uid_columns_list', + 'sql_catalog_topic_search_keys_list' + ] + + prop_list = [] + for prop_id in property_list: + prop_line = '%s | %s' % (catalog_path, prop_id) + prop_list.append(prop_line) + + package.edit( + template_path_list=file_path_list, + template_object_property_list=prop_list + ) + + package_path = self._buildAndExportBusinessPackage(package) + import_package = self._importBusinessPackage(package, package_path) + self._installBusinessPackage(import_package) + + def fileImportAndReinstallForDocument(self): + """ + Test Business Package build and install with test document. + + Expected result: Installs the exported object to the path expected on site. + """ + bp_id = 'erp5_mysql_innodb_catalog_%s'%time.time() + package = self._createBusinessPackage(bp_id=bp_id) + document_file = self.portal.document_module.newContent( + portal_type = 'File', + title = 'Test Document', + reference = 'erp5-package.Test.Document', + data = 'test file', + content_type = None) + + file_path = document_file.getRelativeUrl() + property_list = ['%s | title'%file_path,] + package.edit( + template_path_list=file_path, + template_object_property_list=property_list, + ) + + # Build package + package_path = self._buildAndExportBusinessPackage(package) + + # Delete the document + self.portal.document_module.manage_delObjects([document_file.getId(),]) + # Assert that the file is gone + self.assertRaises(KeyError, lambda: self.portal.restrictedTraverse(file_path)) + + import_package = self._importBusinessPackage(package, package_path) + + # Install package + self._installBusinessPackage(import_package) + + # Test if the file is back + self.assertIsNotNone(self.portal.restrictedTraverse(file_path)) + document = self.portal.restrictedTraverse(file_path) + self.assertEquals(document.title, 'Test Document') + + def _AddConflictedFileAtSamePathViaTwoPackages(self): + """ + Test the result of conflict of two files to be installed at same path + by two different Business Packages + """ + old_package = self._createBusinessPackage() + new_package = self._createBusinessPackage() + + document_file = self.portal.document_module.newContent( + portal_type = 'File', + title = 'Test Document', + reference = 'erp5-package.Test.Document.Two.BP', + data = 'test file', + content_type = None) + + file_path = document_file.getRelativeUrl() + old_package.edit(template_path_list=[file_path,]) + + # Build the first package + self._buildAndExportBusinessPackage(old_package) + + # Change something in the document file + document_file.edit(data='Voila, we play with conflict') + new_package.edit(template_path_list=[file_path,]) + + # Build the second package + self._buildAndExportBusinessPackage(new_package) + + # Get installation data from the list of packages which we want to install + package_list = [old_package, new_package] + + final_data, conflicted_data = createInstallationData(package_list) + + # Delete document from site + self.portal.document_module.manage_delObjects([document_file.getId(),]) + + # Assert that the final data is empty and conflicted data contains \ + # two different versions of the file + self.assertFalse(final_data) + self.assertTrue(conflicted_data) + self.assertEquals(len(conflicted_data[file_path]), 2) + + def _checkPathTemplateBuildForFolder(self): + """ + This test should ensure that we are able to use folder as path for Business + Packages without the need to export every path inside it explicitly + + In this test, we take the example to do this first with default catalog, + then with multiple catalogs + """ + + # With single catalog + folder_path = 'portal_catalog/erp5_mysql_innodb/**' + package = self._createBusinessPackage() + package.edit(template_path_list=[folder_path,]) + self._buildAndExportBusinessPackage(package) + + # Check for the presence of catalog objects/paths in the business package + built_package = self.portal._getOb(package.getId()) + path_item = built_package._path_item + + folder = self.portal.unrestrictedTraverse('portal_catalog/erp5_mysql_innodb') + folder_object_id_list = sorted([l for l in folder.objectIds()]) + folder_object_count = len(folder_object_id_list) + + package_object_id_list = sorted([l.getId() for l in path_item._objects.values()]) + package_object_count = len(package_object_id_list) + + self.assertEquals(folder_object_count, package_object_count) + self.assertEquals(folder_object_id_list, package_object_id_list) + + # With multiple catalogs + folder_path_list = [ + 'portal_catalog/erp5_mysql_innodb/**', + 'portal_catalog/erp5_mysql_innodb100/**' + ] + + package.edit(template_path_list=folder_path_list) + + # XXX: Here, we are not exporting the package and its objects, just building + # and saving it inside the package for the tests. + self._buildAndExportBusinessPackage(package) + + # Check for presence of catalog objects from all the catalogs mentioned in + # the folder path list + built_package = self.portal._getOb(package.getId()) + path_item = built_package._path_item + new_folder = self.portal.unrestrictedTraverse('portal_catalog/erp5_mysql_innodb100') + new_folder_id_list = sorted([l for l in new_folder.objectIds()]) + new_folder_object_count = len(new_folder_id_list) + + total_object_count = new_folder_object_count + folder_object_count + package_object_id_list = sorted([l.getId() for l in path_item._objects.values()]) + object_id_list = sorted(folder_object_id_list + new_folder_id_list) + package_object_count = len(package_object_id_list) + self.assertEquals(total_object_count, package_object_count) + self.assertEquals(object_id_list, package_object_id_list) + + def _addObjectPropertyTemplateItemInPackage(self): + """ + Add ObjectPropertyTemplateItem to Business Package with the hash + """ + package = self._createBusinessPackage() + relative_url = 'portal_catalog/erp5_mysql_innodb' + file_path_list = [relative_url] + + portal_catalog = self.portal.portal_catalog + catalog_object = self.portal.unrestrictedTraverse(relative_url) + object_property_list = [] + for property_id in catalog_object.propertyIds(): + object_property_list.append('%s | %s'%(relative_url, property_id)) + + for property_id in portal_catalog.propertyIds(): + object_property_list.append('%s | %s'%(portal_catalog.getRelativeUrl(), property_id)) + + package.edit(template_path_list=file_path_list) + package.edit(template_object_property_list=object_property_list) + + self._buildAndExportBusinessPackage(package) + + # Check for presence of catalog objects from all the catalogs mentioned in + # the folder path list + built_package = self.portal._getOb(package.getId()) + object_property_item = built_package._object_property_item + + property_object_path_list = sorted(object_property_item._objects.keys()) + self.assertIn(relative_url, property_object_path_list) + self.assertIn(portal_catalog.getRelativeUrl(), property_object_path_list) + + property_object_hash_list = sorted(object_property_item._hash.keys()) + self.assertIn(relative_url, property_object_hash_list) + self.assertIn(portal_catalog.getRelativeUrl(), property_object_hash_list) + + def _udpateInstallationStateOnlyForBusinessPackage(self): + """ + Updating Business Package with the changed installation state and trying + to show the diff between the two installation state + """ + pass diff --git a/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.xml b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.xml new file mode 100644 index 00000000000..416030b45a1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testBusinessPackage.xml @@ -0,0 +1,183 @@ + + + + + + + + + + _recorded_property_dict + + AAAAAAAAAAI= + + + + default_reference + testBusinessPackage + + + description + + + + + + id + test.erp5.testBusinessPackage + + + portal_type + Test Component + + + sid + + + + + + text_content_error_message + + + + + + text_content_warning_message + + + W:144, 29: Redefining built-in \'id\' (redefined-builtin) + W:185, 4: Unused variable \'combinedBM\' (unused-variable) + W:203, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:209, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:233, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:241, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:262, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:270, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:292, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:308, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:333, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:350, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:373, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:389, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:411, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:429, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:450, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:467, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:490, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:508, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:538, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:551, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:578, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:596, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:622, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:632, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:663, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:672, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:699, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:713, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:745, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:757, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:788, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:804, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:831, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:845, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:873, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:886, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:917, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:928, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:957, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:971, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1005, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1017, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1046, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1063, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1092, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1107, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1137, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1150, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1182, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1193, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1222, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1237, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1269, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1284, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 1 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1338, 4: Unused variable \'imported_manager\' (unused-variable) + W:1384, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 2 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + W:1423, 4: Possible unbalanced tuple unpacking with sequence defined at line 142: left side has 2 label(s), right side has 0 value(s) (unbalanced-tuple-unpacking) + + + + + version + erp5 + + + workflow_history + + AAAAAAAAAAM= + + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + component_validation_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + validate + + + validation_state + validated + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.py b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.py new file mode 100644 index 00000000000..65848f97494 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.py @@ -0,0 +1,134 @@ +############################################################################## +# +# Copyright (c) 2002-2017 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. +# +############################################################################## + +import time +from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase + +class TestPortalPatch(ERP5TypeTestCase): + """ + Test class to test that Business Package object can export some paths + and install them on an erp5 site + + Steps: + - Create BusinessPackage object + - Add path list to the object + - Build the package and expect the items mentioned in path list gets + exported in the build step + - Remove the objects mentioned in path_list from the site + - Install the package + - Expected result should be that it installs the objects on the site + """ + + def getTitle(self): + return "TestPortalPatch" + + def getBusinessTemplateList(self): + """ + Tuple of Business Templates we need to install + """ + return ( + 'erp5_base', + 'erp5_core', + 'erp5_dms', + 'erp5_property_sheets', + 'erp5_business_package', + ) + + def afterSetUp(self): + """ + This is ran before anything, used to set the environment + """ + # here, you can create the categories and objects your test will depend on + #self.export_dir = tempfile.mkdtmp(dir=tests_home) + self.export_dir = '' + self.portal = self.getPortalObject() + + def beforeTearDown(self): + pass + + def _createBusinessManager(self, sequence=None, bm_id=None, title=None): + if not bm_id: + bm_id = 'manager_%s' % str(time.time()) + if not title: + title = bm_id + manager = self.portal.portal_templates.newContent( + id=bm_id, + title=title, + portal_type='Business Manager') + self.tic() + return manager + + def test_businessPatchItemForWorkflowChain(self): + """ + Test if we are able to use PatchItem for adding Workflow Chain ( priority + doesn't matter ) + """ + portal = self.portal + manager_A = self._createBusinessManager() + manager_A.newContent(portal_type='Business Property Item', + item_path='portal_types/Business Manager#type_workflow_list') + manager_A.build() + + # Change the value for object on ZODB + obj = portal.unrestrictedTraverse('portal_types/Business Manager') + obj.setWorkflowTypeList('type_workflow_list', ['a', 'b', 'c']) + + manager_B = self._createBusinessManager() + + # Create Business Patch Item for the same property_path as we used in + # property_item, and use manager_A as a value in dependency_list + manager_B.newContent(portal_type='Business Patch Item', + path='portal_types/Business Manager#type_workflow_list', + dependency_list=[manager_A,]) + + # Build Business Manager + manager_B.build() + # After build, we expect manager_B to have 2 Business Property Item, one for + # storing new_value(where we do update when we rebuild) and another from + # old_value(which we take from older version of Business Manager) + + # Change the value for object on ZODB to old again + obj.setWorkflowTypeList('type_workflow_list', ['a', 'c']) + + # Get the patch item for the given path + bm_item = manager_B.getBusinessItemByPath( + 'portal_types/Business Manager#type_workflow_list', + patch=True) + + # Get the old and new value + old = bm_item.getOldValue() + new = bm_item.getNewValue() + + # Get Diff Tool + portal_diff = portal.getDiffTool() + # Create patch using the old and new values + patch = portal_diff.diffPortalObject(old, new) + # Get the patch_operation which we have for this patch + patch_operation_list = patch.getPortalPatchOperationList() + for operation in patch_operation_list: + # Select the operation and apply the patch on the object + obj.patch(operation) diff --git a/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.xml b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.xml new file mode 100644 index 00000000000..9233e5c2538 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/TestTemplateItem/portal_components/test.erp5.testPortalPatchItem.xml @@ -0,0 +1,104 @@ + + + + + + + + + + default_reference + testPortalPatchItem + + + description + + + + + + id + test.erp5.testPortalPatchItem + + + portal_type + Test Component + + + sid + + + + + + text_content_error_message + + + + + + text_content_warning_message + + + + + + version + erp5 + + + workflow_history + + AAAAAAAAAAI= + + + + + + + + + + + + + data + + + + component_validation_workflow + + AAAAAAAAAAM= + + + + + + + + + + + + + + + + + + + action + + + + + + validation_state + draft + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/ToolTemplateItem/portal_commits.xml b/product/ERP5/bootstrap/erp5_business_package/ToolTemplateItem/portal_commits.xml new file mode 100644 index 00000000000..8409e552b64 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/ToolTemplateItem/portal_commits.xml @@ -0,0 +1,56 @@ + + + + + + + + + + _count + + AAAAAAAAAAI= + + + + _mt_index + + AAAAAAAAAAM= + + + + _tree + + AAAAAAAAAAQ= + + + + id + portal_commits + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow.xml new file mode 100644 index 00000000000..3bb770c98ec --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow.xml @@ -0,0 +1,46 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + Interaction between Business Commit and the Business Templates affected by this commit + + + groups + + + + + + id + business_commit_interaction_workflow + + + manager_bypass + 0 + + + title + Business Commit Interaction Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions.xml new file mode 100644 index 00000000000..e18bf8cbf77 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + interactions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateHeadCommitId.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateHeadCommitId.xml new file mode 100644 index 00000000000..902bbf9355f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateHeadCommitId.xml @@ -0,0 +1,100 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_name + + + + actbox_url + + + + activate_script_name + + + + + + after_script_name + + + + + + before_commit_script_name + + + BusinessCommit_updateHeadCommitId + + + + + description + + + + guard + + + + + + id + BusinessCommit_updateHeadCommitId + + + method_id + + + commit + + + + + once_per_transaction + 0 + + + portal_type_filter + + + + + + portal_type_group_filter + + + + + + script_name + + + + + + temporary_document_disallowed + 0 + + + title + + + + trigger_type + 2 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateTemplateStatustoAvailable.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateTemplateStatustoAvailable.xml new file mode 100644 index 00000000000..182a353acb1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/interactions/BusinessCommit_updateTemplateStatustoAvailable.xml @@ -0,0 +1,100 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_name + + + + actbox_url + + + + activate_script_name + + + + + + after_script_name + + + + + + before_commit_script_name + + + BusinessCommit_updateTemplateStatus + + + + + description + + + + guard + + + + + + id + BusinessCommit_updateTemplateStatustoAvailable + + + method_id + + + commit + + + + + once_per_transaction + 0 + + + portal_type_filter + + + + + + portal_type_group_filter + + + + + + script_name + + + + + + temporary_document_disallowed + 0 + + + title + + + + trigger_type + 2 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts.xml new file mode 100644 index 00000000000..072c8f6540c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.py b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.py new file mode 100644 index 00000000000..7659dac2e90 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.py @@ -0,0 +1,8 @@ +modified = state_change['object'] +site = context.getPortalObject() +portal_commits = site.portal_commits + +# If the state of modified commit is commited, then update the value of HEAD +# commit ID +if modified.getValidationState() == 'commited': + portal_commits.setHeadCommitId(modified.getId()) diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.xml new file mode 100644 index 00000000000..3720da8ece3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateHeadCommitId.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + state_change + + + id + BusinessCommit_updateHeadCommitId + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.py b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.py new file mode 100644 index 00000000000..a41263ac267 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.py @@ -0,0 +1,15 @@ +modified = state_change['object'] +site = context.getPortalObject() + +if modified.getValidationState() == 'commited': + # Get all Business Template which are affected by this commit + bt_list = list(set([item for sublist + in [l.getFollowUpValueList() + for l in modified.objectValues()] + for item in sublist])) + +# Now update the status of all Business Template to 'available' +for bt in bt_list: + if site.portal_workflow.isTransitionPossible( + bt, 'exhibit'): + bt.exhibit() diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.xml new file mode 100644 index 00000000000..95a6c64ff6d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/scripts/BusinessCommit_updateTemplateStatus.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + state_change + + + id + BusinessCommit_updateTemplateStatus + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/variables.xml new file mode 100644 index 00000000000..6ae03699d19 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/variables.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_interaction_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow.xml new file mode 100644 index 00000000000..bd54d77b563 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow.xml @@ -0,0 +1,66 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + + + + groups + + + + + + id + business_commit_validation_workflow + + + initial_state + draft + + + manager_bypass + 0 + + + permissions + + + Access contents information + View + Add portal content + Modify portal content + Delete objects + + + + + state_var + validation_state + + + title + Business Commit Validation Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts.xml new file mode 100644 index 00000000000..072c8f6540c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.py b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.py new file mode 100644 index 00000000000..601bf2b0a5f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.py @@ -0,0 +1,21 @@ +""" +Validate Business Commit and checks if it has atleast one item +""" + +from Products.DCWorkflow.DCWorkflow import ValidationFailed +from Products.ERP5Type.Message import translateString + +business_commit = state_change['object'] + +business_commit.Base_checkConsistency() +business_item_list = business_commit.objectValues() + +commit_path_list = business_commit.getItemPathList() +# Raise error in case the commit have 2 items which has same path +if len(commit_path_list) != len(set(commit_path_list)): + raise ValidationFailed(translateString('Multiple items have same path !')) + +# Raise error in case there is no Business Item or Business Property Item added +# in the Business Commit +if not business_item_list: + raise ValidationFailed(translateString('Please add item(s) in commit before committing')) diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.xml new file mode 100644 index 00000000000..84031af723a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/scripts/BusinessCommit_commit.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + state_change + + + id + BusinessCommit_commit + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states.xml new file mode 100644 index 00000000000..27ec9069024 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + states + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/commited.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/commited.xml new file mode 100644 index 00000000000..8b9c512a970 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/commited.xml @@ -0,0 +1,91 @@ + + + + + + + + + + description + + + + id + commited + + + permission_roles + + AAAAAAAAAAI= + + + + title + Commited + + + transitions + + + push + push_action + + + + + type_list + + + + + + + + + + + + + + + data + + + + Access contents information + + + + + + Add portal content + + + + + + Delete objects + + + + + + Modify portal content + + + + + + View + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/draft.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/draft.xml new file mode 100644 index 00000000000..5bdde460b07 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/draft.xml @@ -0,0 +1,91 @@ + + + + + + + + + + description + + + + id + draft + + + permission_roles + + AAAAAAAAAAI= + + + + title + Draft + + + transitions + + + commit + commit_action + + + + + type_list + + + + + + + + + + + + + + + data + + + + Access contents information + + + + + + Add portal content + + + + + + Delete objects + + + + + + Modify portal content + + + + + + View + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/pushed.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/pushed.xml new file mode 100644 index 00000000000..6a742cc9b75 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/states/pushed.xml @@ -0,0 +1,88 @@ + + + + + + + + + + description + + + + id + pushed + + + permission_roles + + AAAAAAAAAAI= + + + + title + Pushed + + + transitions + + + + + + type_list + + + + + + + + + + + + + + + data + + + + Access contents information + + + + + + Add portal content + + + + + + Delete objects + + + + + + Modify portal content + + + + + + View + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions.xml new file mode 100644 index 00000000000..aa36144efed --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + transitions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit.xml new file mode 100644 index 00000000000..427f231b933 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + commit + + + new_state_id + commited + + + script_name + + + + title + Commit + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit_action.xml new file mode 100644 index 00000000000..acb5e004d90 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/commit_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Commit + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=commit_action + + + after_script_name + commit + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + commit_action + + + new_state_id + + + + script_name + BusinessCommit_commit + + + title + Commit Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push.xml new file mode 100644 index 00000000000..fcb5ea4ce5d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + push + + + new_state_id + pushed + + + script_name + + + + title + Push + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push_action.xml new file mode 100644 index 00000000000..c18a5a8140f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/transitions/push_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Push + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=push_action + + + after_script_name + push + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + push_action + + + new_state_id + + + + script_name + + + + title + Push Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables.xml new file mode 100644 index 00000000000..bb12bef805f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/action.xml new file mode 100644 index 00000000000..bb5af22d393 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/action.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition id + + + for_catalog + 0 + + + for_status + 1 + + + id + action + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + transition/getId|nothing + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/actor.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/actor.xml new file mode 100644 index 00000000000..fd10331a7a0 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/actor.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Name of the user who performed transition + + + for_catalog + 0 + + + for_status + 1 + + + id + actor + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + user/getIdOrUserName + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/comment.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/comment.xml new file mode 100644 index 00000000000..fda919ee861 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/comment.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Comment about transition + + + for_catalog + 0 + + + for_status + 1 + + + id + comment + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + python:state_change.kwargs.get(\'comment\', \'\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/error_message.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/error_message.xml new file mode 100644 index 00000000000..535863de2a2 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/error_message.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Error message if validation failed + + + for_catalog + 0 + + + for_status + 1 + + + id + error_message + + + info_guard + + + + + + update_always + 1 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/history.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/history.xml new file mode 100644 index 00000000000..44306b76d74 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/history.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Provides access to workflow history + + + for_catalog + 0 + + + for_status + 0 + + + id + history + + + info_guard + + + + + + update_always + 0 + + + + + + + + + + + + text + state_change/getHistory + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/portal_type.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/portal_type.xml new file mode 100644 index 00000000000..89576a7a56a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/portal_type.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Portal type (used as filter for worklists) + + + for_catalog + 1 + + + for_status + 0 + + + id + portal_type + + + info_guard + + + + + + update_always + 0 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/time.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/time.xml new file mode 100644 index 00000000000..0d2d8d7e221 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/variables/time.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition timestamp + + + for_catalog + 0 + + + for_status + 1 + + + id + time + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + state_change/getDateTime + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_commit_validation_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow.xml new file mode 100644 index 00000000000..4a09c8360e1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow.xml @@ -0,0 +1,46 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + + + + groups + + + + + + id + business_item_interaction_workflow + + + manager_bypass + 0 + + + title + Business Item Interaction Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions.xml new file mode 100644 index 00000000000..e18bf8cbf77 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + interactions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions/BusinessItem_updateFollowUpItemPathList.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions/BusinessItem_updateFollowUpItemPathList.xml new file mode 100644 index 00000000000..3a6def628b6 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/interactions/BusinessItem_updateFollowUpItemPathList.xml @@ -0,0 +1,100 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_name + + + + actbox_url + + + + activate_script_name + + + + + + after_script_name + + + + + + before_commit_script_name + + + BusinessItem_updateFollowUpItemPathList + + + + + description + + + + guard + + + + + + id + BusinessItem_updateFollowUpItemPathList + + + method_id + + + edit + + + + + once_per_transaction + 0 + + + portal_type_filter + + + + + + portal_type_group_filter + + + + + + script_name + + + + + + temporary_document_disallowed + 0 + + + title + + + + trigger_type + 2 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts.xml new file mode 100644 index 00000000000..072c8f6540c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.py b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.py new file mode 100644 index 00000000000..e28d2c1b341 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.py @@ -0,0 +1,4 @@ +modified = state_change['object'] + +# Update Follow Up Item Path List +modified.updateFollowUpPathList() diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.xml new file mode 100644 index 00000000000..18370e4d67b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/scripts/BusinessItem_updateFollowUpItemPathList.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + state_change + + + id + BusinessItem_updateFollowUpItemPathList + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/variables.xml new file mode 100644 index 00000000000..6ae03699d19 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/variables.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_item_interaction_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow.xml new file mode 100644 index 00000000000..27f220c1538 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow.xml @@ -0,0 +1,46 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + + + + groups + + + + + + id + business_property_item_interaction_workflow + + + manager_bypass + 0 + + + title + Business Property Item Interaction Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions.xml new file mode 100644 index 00000000000..e18bf8cbf77 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + interactions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions/BusinessPropertyItem_updateFollowUpItemPathList.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions/BusinessPropertyItem_updateFollowUpItemPathList.xml new file mode 100644 index 00000000000..eae54c7ae79 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/interactions/BusinessPropertyItem_updateFollowUpItemPathList.xml @@ -0,0 +1,100 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_name + + + + actbox_url + + + + activate_script_name + + + + + + after_script_name + + + + + + before_commit_script_name + + + BusinessPropertyItem_updateFollowUpItemPathList + + + + + description + + + + guard + + + + + + id + BusinessPropertyItem_updateFollowUpItemPathList + + + method_id + + + edit + + + + + once_per_transaction + 0 + + + portal_type_filter + + + + + + portal_type_group_filter + + + + + + script_name + + + + + + temporary_document_disallowed + 0 + + + title + + + + trigger_type + 2 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts.xml new file mode 100644 index 00000000000..072c8f6540c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.py b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.py new file mode 100644 index 00000000000..e28d2c1b341 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.py @@ -0,0 +1,4 @@ +modified = state_change['object'] + +# Update Follow Up Item Path List +modified.updateFollowUpPathList() diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.xml new file mode 100644 index 00000000000..c28a880d5c4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/scripts/BusinessPropertyItem_updateFollowUpItemPathList.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Script_magic + 3 + + + _bind_names + + + + + + + + + + _asgns + + + + name_container + container + + + name_context + context + + + name_m_self + script + + + name_subpath + traverse_subpath + + + + + + + + + + + _params + state_change + + + id + BusinessPropertyItem_updateFollowUpItemPathList + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/variables.xml new file mode 100644 index 00000000000..6ae03699d19 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/variables.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_property_item_interaction_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow.xml new file mode 100644 index 00000000000..7f3ea13428f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow.xml @@ -0,0 +1,66 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + + + + groups + + + + + + id + business_snapshot_validation_workflow + + + initial_state + draft + + + manager_bypass + 0 + + + permissions + + + Access contents information + View + Add portal content + Delete objects + Modify portal content + + + + + state_var + validation_state + + + title + Business Snapshot Validation Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/scripts.xml new file mode 100644 index 00000000000..a703b14c4ca --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/scripts.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states.xml new file mode 100644 index 00000000000..27ec9069024 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + states + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/draft.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/draft.xml new file mode 100644 index 00000000000..4db77b1d2f7 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/draft.xml @@ -0,0 +1,40 @@ + + + + + + + + + + description + + + + id + draft + + + title + Draft + + + transitions + + + build + install + install_action + + + + + type_list + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/installed.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/installed.xml new file mode 100644 index 00000000000..208f3d81eb3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/installed.xml @@ -0,0 +1,94 @@ + + + + + + + + + + description + + + + id + installed + + + permission_roles + + AAAAAAAAAAI= + + + + title + Installed + + + transitions + + + reinstall + reinstall_action + replace + uninstall + uninstall_action + + + + + type_list + + + + + + + + + + + + + + + data + + + + Access contents information + + + + + + Add portal content + + + + + + Delete objects + + + + + + Modify portal content + + + + + + View + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/not_installed.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/not_installed.xml new file mode 100644 index 00000000000..2c20e95a97e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/not_installed.xml @@ -0,0 +1,39 @@ + + + + + + + + + + description + + + + id + not_installed + + + title + Not Installed + + + transitions + + + install + install_action + + + + + type_list + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/replaced.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/replaced.xml new file mode 100644 index 00000000000..403b70d336c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/states/replaced.xml @@ -0,0 +1,91 @@ + + + + + + + + + + description + + + + id + replaced + + + permission_roles + + AAAAAAAAAAI= + + + + title + Replaced + + + transitions + + + install + install_action + + + + + type_list + + + + + + + + + + + + + + + data + + + + Access contents information + + + + + + Add portal content + + + + + + Delete objects + + + + + + Modify portal content + + + + + + View + + + + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions.xml new file mode 100644 index 00000000000..aa36144efed --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + transitions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/build.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/build.xml new file mode 100644 index 00000000000..e0c4a1619d2 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/build.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + build + + + new_state_id + not_installed + + + script_name + + + + title + Build Snapshot + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install.xml new file mode 100644 index 00000000000..400b0612b68 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + install + + + new_state_id + installed + + + script_name + + + + title + Install Business Snapshot + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Manage Portal + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install_action.xml new file mode 100644 index 00000000000..c328bdc752e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/install_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Install Business Snapshot + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=install_action + + + after_script_name + install + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + install_action + + + new_state_id + + + + script_name + + + + title + Install Business Snapshot Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Manage Portal + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall.xml new file mode 100644 index 00000000000..b489c42981e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + reinstall + + + new_state_id + installed + + + script_name + + + + title + Reinstall Business Snapshot + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Manage portal + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall_action.xml new file mode 100644 index 00000000000..41c2aa411ef --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/reinstall_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Re-install Business Snapshot + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=reinstall_action + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + reinstall_action + + + new_state_id + + + + script_name + + + + title + Reinstall Business Snapshot Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Manage portal + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/replace.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/replace.xml new file mode 100644 index 00000000000..ef5b957a545 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/transitions/replace.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + replace + + + new_state_id + replaced + + + script_name + + + + title + Replace Business Snapshot + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Manage portal + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables.xml new file mode 100644 index 00000000000..bb12bef805f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/action.xml new file mode 100644 index 00000000000..bb5af22d393 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/action.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition id + + + for_catalog + 0 + + + for_status + 1 + + + id + action + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + transition/getId|nothing + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/actor.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/actor.xml new file mode 100644 index 00000000000..fd10331a7a0 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/actor.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Name of the user who performed transition + + + for_catalog + 0 + + + for_status + 1 + + + id + actor + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + user/getIdOrUserName + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/comment.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/comment.xml new file mode 100644 index 00000000000..fda919ee861 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/comment.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Comment about transition + + + for_catalog + 0 + + + for_status + 1 + + + id + comment + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + python:state_change.kwargs.get(\'comment\', \'\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/error_message.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/error_message.xml new file mode 100644 index 00000000000..535863de2a2 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/error_message.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Error message if validation failed + + + for_catalog + 0 + + + for_status + 1 + + + id + error_message + + + info_guard + + + + + + update_always + 1 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/history.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/history.xml new file mode 100644 index 00000000000..44306b76d74 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/history.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Provides access to workflow history + + + for_catalog + 0 + + + for_status + 0 + + + id + history + + + info_guard + + + + + + update_always + 0 + + + + + + + + + + + + text + state_change/getHistory + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/portal_type.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/portal_type.xml new file mode 100644 index 00000000000..89576a7a56a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/portal_type.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Portal type (used as filter for worklists) + + + for_catalog + 1 + + + for_status + 0 + + + id + portal_type + + + info_guard + + + + + + update_always + 0 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/time.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/time.xml new file mode 100644 index 00000000000..0d2d8d7e221 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/variables/time.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition timestamp + + + for_catalog + 0 + + + for_status + 1 + + + id + time + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + state_change/getDateTime + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_snapshot_validation_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow.xml new file mode 100644 index 00000000000..44ff66afea3 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow.xml @@ -0,0 +1,66 @@ + + + + + + + + + + _objects + + + + + + creation_guard + + + + + + description + + + + groups + + + + + + id + business_template_availability_workflow + + + initial_state + draft + + + manager_bypass + 0 + + + permissions + + + Access contents information + View + Add portal content + Modify portal content + Delete objects + + + + + state_var + availability_state + + + title + Business Template Availabilty Workflow + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/scripts.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/scripts.xml new file mode 100644 index 00000000000..a703b14c4ca --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/scripts.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + scripts + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states.xml new file mode 100644 index 00000000000..27ec9069024 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + states + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/available.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/available.xml new file mode 100644 index 00000000000..50fe73afe45 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/available.xml @@ -0,0 +1,39 @@ + + + + + + + + + + description + It denotes that the Business Template is available for installation, but it is not installed currently. + + + id + available + + + title + Available + + + transitions + + + install + install_action + + + + + type_list + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/draft.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/draft.xml new file mode 100644 index 00000000000..333e14c5d2a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/draft.xml @@ -0,0 +1,39 @@ + + + + + + + + + + description + A newly created Business Template which we can modify and attach the changes to one or more Business Commit and make it available. + + + id + draft + + + title + Draft + + + transitions + + + exhibit + exhibit_action + + + + + type_list + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/installable.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/installable.xml new file mode 100644 index 00000000000..db5415a136c --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/states/installable.xml @@ -0,0 +1,39 @@ + + + + + + + + + + description + Installable will be case when either we have an already installed Business Template which we can install again if needed(with or without changes) + + + id + installable + + + title + Installable + + + transitions + + + reinstall + reinstall_action + + + + + type_list + + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions.xml new file mode 100644 index 00000000000..aa36144efed --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + transitions + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit.xml new file mode 100644 index 00000000000..0f45ce95b94 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + exhibit + + + new_state_id + available + + + script_name + + + + title + Exhibit Business Template + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit_action.xml new file mode 100644 index 00000000000..9a68929927a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/exhibit_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Exhibit Business Template V2 + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=exhibit_action + + + after_script_name + exhibit + + + description + Make the Business Template available, i.e, the path items should be committed in a Business Commit. + + + guard + + AAAAAAAAAAI= + + + + id + exhibit_action + + + new_state_id + + + + script_name + + + + title + Exhibit Business Template Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install.xml new file mode 100644 index 00000000000..a4c8c98d777 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + install + + + new_state_id + installable + + + script_name + + + + title + Install Business Template + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install_action.xml new file mode 100644 index 00000000000..58e56f223d4 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/install_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Install Business Template V2 + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=install_action + + + after_script_name + install + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + install_action + + + new_state_id + + + + script_name + + + + title + Install Business Template Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall.xml new file mode 100644 index 00000000000..d0fe70ef5aa --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + + + + actbox_url + + + + after_script_name + reinstall + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + reinstall + + + new_state_id + installable + + + script_name + + + + title + Reinstall Business Template + + + trigger_type + 2 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall_action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall_action.xml new file mode 100644 index 00000000000..a4585f8d9f5 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/transitions/reinstall_action.xml @@ -0,0 +1,79 @@ + + + + + + + + + + actbox_category + workflow + + + actbox_icon + + + + actbox_name + Reinstall Business Template V2 + + + actbox_url + %(content_url)s/Base_viewWorkflowActionDialog?workflow_action=reinstall_action + + + after_script_name + + + + description + + + + guard + + AAAAAAAAAAI= + + + + id + reinstall_action + + + new_state_id + + + + script_name + + + + title + Reinstall Business Template Action + + + trigger_type + 1 + + + + + + + + + + + + permissions + + + Modify portal content + + + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables.xml new file mode 100644 index 00000000000..bb12bef805f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _mapping + + + + + + _objects + + + + + + id + variables + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/action.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/action.xml new file mode 100644 index 00000000000..bb5af22d393 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/action.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition id + + + for_catalog + 0 + + + for_status + 1 + + + id + action + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + transition/getId|nothing + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/actor.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/actor.xml new file mode 100644 index 00000000000..fd10331a7a0 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/actor.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Name of the user who performed transition + + + for_catalog + 0 + + + for_status + 1 + + + id + actor + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + user/getIdOrUserName + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/comment.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/comment.xml new file mode 100644 index 00000000000..fda919ee861 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/comment.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Comment about transition + + + for_catalog + 0 + + + for_status + 1 + + + id + comment + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + python:state_change.kwargs.get(\'comment\', \'\') + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/error_message.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/error_message.xml new file mode 100644 index 00000000000..535863de2a2 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/error_message.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Error message if validation failed + + + for_catalog + 0 + + + for_status + 1 + + + id + error_message + + + info_guard + + + + + + update_always + 1 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/history.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/history.xml new file mode 100644 index 00000000000..44306b76d74 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/history.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Provides access to workflow history + + + for_catalog + 0 + + + for_status + 0 + + + id + history + + + info_guard + + + + + + update_always + 0 + + + + + + + + + + + + text + state_change/getHistory + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/portal_type.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/portal_type.xml new file mode 100644 index 00000000000..89576a7a56a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/portal_type.xml @@ -0,0 +1,48 @@ + + + + + + + + + + default_expr + + + + + + default_value + + + + description + Portal type (used as filter for worklists) + + + for_catalog + 1 + + + for_status + 0 + + + id + portal_type + + + info_guard + + + + + + update_always + 0 + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/time.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/time.xml new file mode 100644 index 00000000000..0d2d8d7e221 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/variables/time.xml @@ -0,0 +1,61 @@ + + + + + + + + + + default_expr + + AAAAAAAAAAI= + + + + default_value + + + + description + Transition timestamp + + + for_catalog + 0 + + + for_status + 1 + + + id + time + + + info_guard + + + + + + update_always + 1 + + + + + + + + + + + + text + state_change/getDateTime + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/worklists.xml b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/worklists.xml new file mode 100644 index 00000000000..c3432aa051e --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/WorkflowTemplateItem/portal_workflow/business_template_availability_workflow/worklists.xml @@ -0,0 +1,22 @@ + + + + + + + + + + _mapping + + + + + + id + worklists + + + + + diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/copyright_list b/product/ERP5/bootstrap/erp5_business_package/bt/copyright_list new file mode 100644 index 00000000000..3b265520a1f --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/copyright_list @@ -0,0 +1 @@ +Copyright (c) 2001-2017 Nexedi SA \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/description b/product/ERP5/bootstrap/erp5_business_package/bt/description new file mode 100644 index 00000000000..36ab867933d --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/description @@ -0,0 +1 @@ +Includes Commit Tool portal_types, skins, component and live tests \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/license b/product/ERP5/bootstrap/erp5_business_package/bt/license new file mode 100644 index 00000000000..3a3e12bcad9 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/license @@ -0,0 +1 @@ +GPL \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/maintainer_list b/product/ERP5/bootstrap/erp5_business_package/bt/maintainer_list new file mode 100644 index 00000000000..cb382916f99 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/maintainer_list @@ -0,0 +1 @@ +ayush \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_action_path_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_action_path_list new file mode 100644 index 00000000000..8577695f961 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_action_path_list @@ -0,0 +1,13 @@ +Business Commit | content_view +Business Commit | install +Business Commit | rebuild_subobjects +Business Item | content_view +Business Item | diff_view +Business Patch Item | content_view +Business Patch Item | diff_view +Business Property Item | content_view +Business Snapshot | content_view +Business Template V2 | content_view +Commit Tool | create_snapshot +Commit Tool | view +portal_actions | commit_tool \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_extension_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_extension_id_list new file mode 100644 index 00000000000..c022056d089 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_extension_id_list @@ -0,0 +1 @@ +extension.erp5.CommitUtils \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_format_version b/product/ERP5/bootstrap/erp5_business_package/bt/template_format_version new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_format_version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_allowed_content_type_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_allowed_content_type_list new file mode 100644 index 00000000000..fac92e702c8 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_allowed_content_type_list @@ -0,0 +1,10 @@ +Business Commit | Business Item +Business Commit | Business Patch Item +Business Commit | Business Property Item +Business Patch Item | Business Item +Business Patch Item | Business Property Item +Business Snapshot | Business Item +Business Snapshot | Business Patch Item +Business Snapshot | Business Property Item +Commit Tool | Business Commit +Commit Tool | Business Snapshot \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_base_category_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_base_category_list new file mode 100644 index 00000000000..5d5c4ae92bf --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_base_category_list @@ -0,0 +1,7 @@ +Business Commit | predecessor +Business Commit | similar +Business Commit | source_project +Business Item | follow_up +Business Patch Item | follow_up +Business Property Item | follow_up +Business Snapshot | similar \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_id_list new file mode 100644 index 00000000000..8a873a105af --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_id_list @@ -0,0 +1,7 @@ +Business Commit +Business Item +Business Patch Item +Business Property Item +Business Snapshot +Business Template V2 +Commit Tool \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_property_sheet_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_property_sheet_list new file mode 100644 index 00000000000..1f5e18bf260 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_property_sheet_list @@ -0,0 +1,2 @@ +Business Item | BusinessItemConstraint +Business Property Item | BusinessItemConstraint \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_workflow_chain_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_workflow_chain_list new file mode 100644 index 00000000000..50a43489416 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_portal_type_workflow_chain_list @@ -0,0 +1,6 @@ +Business Commit | business_commit_interaction_workflow +Business Commit | business_commit_validation_workflow +Business Item | business_item_interaction_workflow +Business Property Item | business_property_item_interaction_workflow +Business Snapshot | business_snapshot_validation_workflow +Business Template V2 | business_template_availability_workflow \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_property_sheet_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_property_sheet_id_list new file mode 100644 index 00000000000..7c9843c2260 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_property_sheet_id_list @@ -0,0 +1,2 @@ +BusinessTemplateV2 +BusinessItemConstraint \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_skin_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_skin_id_list new file mode 100644 index 00000000000..97c45335cac --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_skin_id_list @@ -0,0 +1 @@ +erp5_commit \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_test_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_test_id_list new file mode 100644 index 00000000000..637b1e86b9b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_test_id_list @@ -0,0 +1,2 @@ +test.erp5.testBusinessPackage +test.erp5.testPortalPatchItem \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_tool_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_tool_id_list new file mode 100644 index 00000000000..e4cae5cc3d1 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_tool_id_list @@ -0,0 +1 @@ +portal_commits \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/template_workflow_id_list b/product/ERP5/bootstrap/erp5_business_package/bt/template_workflow_id_list new file mode 100644 index 00000000000..c7711682e0a --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/template_workflow_id_list @@ -0,0 +1,6 @@ +business_commit_interaction_workflow +business_commit_validation_workflow +business_item_interaction_workflow +business_property_item_interaction_workflow +business_snapshot_validation_workflow +business_template_availability_workflow \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/title b/product/ERP5/bootstrap/erp5_business_package/bt/title new file mode 100644 index 00000000000..9c395ab2b79 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/title @@ -0,0 +1 @@ +erp5_business_package \ No newline at end of file diff --git a/product/ERP5/bootstrap/erp5_business_package/bt/version b/product/ERP5/bootstrap/erp5_business_package/bt/version new file mode 100644 index 00000000000..8a9ecc2ea99 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_business_package/bt/version @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file -- 2.30.9 From d0b9f98b710504d50fc643a820af0448be7496dd Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 15:53:12 +0100 Subject: [PATCH 02/56] BusineeTemplate: Add fixes for Business Manager installation to work with BT installation during bootstrap --- product/ERP5/Document/BusinessTemplate.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index b7150c2be90..3b9775892da 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -2531,8 +2531,13 @@ class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem): force = kw.get('force') installed_bt = kw.get('installed_bt') if installed_bt is not None: - previous_portal_type_workflow_chain_list = list(installed_bt\ + try: + previous_portal_type_workflow_chain_list = list(installed_bt\ .getTemplatePortalTypeWorkflowChainList()) + except Exception: + # This will happen in case the `installed_bt` is Business Manager, so + # we won't need to goto the further installation process + return else: previous_portal_type_workflow_chain_list = [] # We now need to setup the list of workflows corresponding to @@ -4308,15 +4313,8 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem): """ if self._is_already_migrated(self._objects.keys()): ObjectTemplateItem.install(self, context, **kw) - # Reset component on the fly, because it is possible that those - # components are required in the middle of the transaction. For example: - # - A method in a component is called while installing. - # - A document component is used in a different business template, - # and those business templates are installed in a single transaction - # by upgrader. - # This reset is called at most 3 times in one business template - # installation. (for Document, Test, Extension) - self.portal_components.reset(force=True) + self.portal_components.reset(force=True, + reset_portal_type_at_transaction_boundary=True) else: FilesystemDocumentTemplateItem.install(self, context, **kw) @@ -5349,7 +5347,10 @@ Business Template is a set of definitions, such as skins, portal types and categ # always created a trash bin because we may to save object already present # but not in a previous business templates apart at creation of a new site if trash_tool is not None and (len(object_to_update) > 0 or len(self.portal_templates) > 2): - trashbin = trash_tool.newTrashBin(self.getTitle(), self) + if self.title == 'erp5_core': + trashbin = None + else: + trashbin = trash_tool.newTrashBin(self.getTitle(), self) else: trashbin = None -- 2.30.9 From 81071a706c2ac27e98d60b4aca9fb5f6cee3a799 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:02:38 +0100 Subject: [PATCH 03/56] Diff Tool: Find Diff betweeen any 2 erp5 objects --- product/ERP5/ERP5Site.py | 1 + product/ERP5/Tool/DiffTool.py | 224 ++++++++++++++++++ product/ERP5/__init__.py | 4 +- product/ERP5Type/Base.py | 4 +- product/ERP5Type/Core/Folder.py | 5 +- .../ERP5Type/interfaces/json_representable.py | 52 ++++ product/ERP5Type/interfaces/patchable.py | 47 ++++ product/ERP5Type/mixin/json_representable.py | 89 +++++++ product/ERP5Type/mixin/patchable.py | 53 +++++ 9 files changed, 476 insertions(+), 3 deletions(-) create mode 100644 product/ERP5/Tool/DiffTool.py create mode 100644 product/ERP5Type/interfaces/json_representable.py create mode 100644 product/ERP5Type/interfaces/patchable.py create mode 100644 product/ERP5Type/mixin/json_representable.py create mode 100644 product/ERP5Type/mixin/patchable.py diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index b3e18f1c76e..c8533dd4ad5 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -1997,6 +1997,7 @@ class ERP5Generator(PortalGenerator): addERP5Tool(p, 'portal_simulation', 'Simulation Tool') addERP5Tool(p, 'portal_deliveries', 'Delivery Tool') addERP5Tool(p, 'portal_orders', 'Order Tool') + addERP5Tool(p, 'portal_diff', 'Diff Tool') def setupTemplateTool(self, p, **kw): """ diff --git a/product/ERP5/Tool/DiffTool.py b/product/ERP5/Tool/DiffTool.py new file mode 100644 index 00000000000..7259e2c366b --- /dev/null +++ b/product/ERP5/Tool/DiffTool.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SARL and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import jsonpatch +from deepdiff import DeepDiff + +from AccessControl import ClassSecurityInfo +from Products.ERP5Type.Globals import InitializeClass +from Products.ERP5Type.Tool.BaseTool import BaseTool +from Products.ERP5Type import Permissions + +class DiffTool(BaseTool): + """ + A portal tool that provides all kinds of utilities to + compare objects. + """ + id = 'portal_diff' + title = 'Diff Tool' + meta_type = 'ERP5 Diff Tool' + portal_type = 'Portal Diff Tool' + allowed_types = () + + # Declarative Security + security = ClassSecurityInfo() + + def diffPortalObject(self, old, new, path=None, patch_format="deepdiff"): + """ + Returns a PortalPatch instance with the appropriate format + original -- original object + new -- new object + path -- optional path to specify which property to diff + patch_format -- optional format (rfc6902 or deepdiff) + """ + return PortalPatch(old, new, patch_format) + + +class PortalPatch: + """ + Provides an abstraction to a patch that + depends on the patch format. + + In the case of deepdiff, the abstraction can + lead to a commutative merge system. + + In the case of rfc6902, the abstraction can not + lead to a commutative merge system but may be + useful to some UI applications. + """ + + def __init__(self, old, new, patch_format="deepdiff"): + """ + Intialises the class from a deepdiff or + a rfc6902 patch. deepdiff is the default. + """ + self.old_value = old + self.new_value = new + self.patch_format = patch_format + + def getPortalPatchOperationList(self): + """ + List all PortalPatchOperation instances in the PortalPatch + """ + patch = self.asDeepDiffPatch() + # In general, we are using `tree` view, so basically for us all operations + # currently are `values_changed` from old to new value or to none + change_list = patch.values() + # Here we can have the change_list as nested list also, for example: + # + # change_list = + # { + # 'iterable_item_removed': set([]), + # 'values_changed': set([, ]) + # } + # We can see here that the values are basically change from one value to + # another, so to get the list of operation(s), we have to flatten all the + # values in one list + flatten_change_list = [item for sublist in change_list for item in sublist] + return flatten_change_list + + def patchPortalObject(self, object): + """ + Apply patch to an object by applying + one by one each PortalPatchItem + """ + pass + + def asDeepDiffPatch(self): + """ + Returns a Json patch with deep diff extensions + """ + # Use try-except as it's easier to ask forgiveness than permission + + # `_asDict` is available only for objects, so in that case, we convert the + # ERP5-fied objects into dict and then work on them. + # In all other cases, we let `deepdiff` do its work on checking the type + try: + src = self.old_value._asDict() + except AttributeError: + src = self.old_value + + try: + dst = self.new_value._asDict() + except AttributeError: + dst = self.new_value + + # For now, we prefer having 'tree' view as it provides us with node level + # where on each node we have value changed(atleast for list and dictionary) + ddiff = DeepDiff(src, dst, view='tree') + return ddiff + + def asStrippedHTML(self): + """ + Returns an HTML representation of the whole patch + that can be embedded + """ + pass + + def asHTML(self): + """ + Returns an HTML representation of the whole patch + that can be displayed in a standalone way + """ + pass + +class PortalPatchOperation: + """ + Provides an abstraction to a patch operation that + depends on the patch format. + + In the case of deepdiff, each operation defines + actually a desired state in a declarative way. + + In the case of rfc6902, each operation is defined + in an imperative manner. + """ + + def patchPortalObject(object, unified_diff_selection=None): + """ + Apply patch to an object + + unified_diff_selection -- a selection of lines in the unified diff + that will be applied + """ + pass + + def getOperation(self): + """ + Returns one of "replace", "add" or "remove" + + (hopefully, this can also be used for deepdiff format) + set_item_added, values_changed, etc. + """ + pass + + def getPath(self): + """ + Returns a path representing the value that is changed + (hopefully, this can also be used for deepdiff format) + """ + pass + + def getOldValue(self): + """ + Returns the old value + """ + pass + + def getNewValue(self): + """ + Returns the new value + """ + pass + + def getUnifiedDiff(self): + """ + Returns a unified diff of the value changed + (this is useful for a text value) or None if + there is no such change. + + (see String difference 2 in deepdiff) + """ + pass + + def asStrippedHTML(self): + """ + Returns an HTML representation of the change + that can be embedded + """ + pass + + def asHTML(self): + """ + Returns an HTML representation that can be displayed + in a standalone way + """ + pass + +InitializeClass(DiffTool) diff --git a/product/ERP5/__init__.py b/product/ERP5/__init__.py index 523ce4ac81b..f05d7eb4e5f 100644 --- a/product/ERP5/__init__.py +++ b/product/ERP5/__init__.py @@ -51,7 +51,8 @@ from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\ GadgetTool, ContributionRegistryTool, IntrospectionTool,\ AcknowledgementTool, SolverTool, SolverProcessTool,\ ConversionTool, RoundingTool, UrlRegistryTool, InterfaceTool,\ - CertificateAuthorityTool, InotifyTool, TaskDistributionTool + CertificateAuthorityTool, InotifyTool, TaskDistributionTool,\ + DiffTool import ERP5Site from Document import PythonScript, SQLMethod object_classes = ( ERP5Site.ERP5Site, @@ -85,6 +86,7 @@ portal_tools = ( CategoryTool.CategoryTool, InotifyTool.InotifyTool, TaskDistributionTool.TaskDistributionTool, InterfaceTool.InterfaceTool, + DiffTool.DiffTool ) content_classes = () content_constructors = () diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 1e4c150ab76..7d2abb58b0b 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -86,6 +86,7 @@ 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 +from Products.ERP5Type.mixin.json_representable import JSONRepresentableMixin from zope.interface import classImplementsOnly, implementedBy @@ -675,7 +676,8 @@ class Base( CopyContainer, ActiveObject, OFS.History.Historical, ERP5PropertyManager, - PropertyTranslatableBuiltInDictMixIn + PropertyTranslatableBuiltInDictMixIn, + JSONRepresentableMixin, ): """ This is the base class for all ERP5 Zope objects. diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py index 1778954894e..7ff7b67ec7f 100644 --- a/product/ERP5Type/Core/Folder.py +++ b/product/ERP5Type/Core/Folder.py @@ -49,6 +49,8 @@ from Products.ERP5Type.Utils import sortValueList from Products.ERP5Type import Permissions from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Accessor import Base as BaseAccessor +from Products.ERP5Type.mixin.json_representable import JSONRepresentableMixin + try: from Products.CMFCore.CMFBTreeFolder import CMFBTreeFolder except ImportError: @@ -562,7 +564,8 @@ HBTREE_HANDLER = 2 InitializeClass(FolderMixIn) -class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn): +class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn, + JSONRepresentableMixin,): """ A Folder is a subclass of Base but not of XMLObject. Folders are not considered as documents and are therefore diff --git a/product/ERP5Type/interfaces/json_representable.py b/product/ERP5Type/interfaces/json_representable.py new file mode 100644 index 00000000000..96c797d55fa --- /dev/null +++ b/product/ERP5Type/interfaces/json_representable.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from zope.interface import Interface + +class IJSONRepresentable(Interface): + """ + An interface for objects that can be converted to JSON + and back to an ERP5 object. + + This can be useful if we want to use JSON as much + as possible in ERP5 in the future and ensure + that certain objects (not all) can be converted to JSON + back and forth + """ + + def asJSON(): + """ + Returns a JSON representation based on + propertysheets and portal properties + """ + + def fromJSON(): + """ + Updates an object based on a JSON representation + """ diff --git a/product/ERP5Type/interfaces/patchable.py b/product/ERP5Type/interfaces/patchable.py new file mode 100644 index 00000000000..2ebd4487da7 --- /dev/null +++ b/product/ERP5Type/interfaces/patchable.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from zope.interface import Interface + +class IPatchable(Interface): + """ + An interface for objects that can be patched. This is useful + to raise an exception for some objects that are not meant to be + supported by BusinessTemplatePatchItem + """ + + def patch(patch, path=None, unified_diff_selection=None): + """ + Apply a PortalPatch or PortalPatchOperation to an object + + unified_diff_selection -- a selection of lines in the unified diff + that will be applied + + path -- optional path in case one wants to patch only some properties + """ diff --git a/product/ERP5Type/mixin/json_representable.py b/product/ERP5Type/mixin/json_representable.py new file mode 100644 index 00000000000..02e7b257c50 --- /dev/null +++ b/product/ERP5Type/mixin/json_representable.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import json +import xmltodict +import zope.interface +from OFS import XMLExportImport +from StringIO import StringIO +from AccessControl import ClassSecurityInfo +from Products.ERP5Type.interfaces.json_representable import IJSONRepresentable +from Products.ERP5Type import Permissions +from Products.ERP5Type.Globals import InitializeClass + +class JSONRepresentableMixin: + """ + An implementation for IJSONRepresentable + """ + + # Declarative Security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + zope.interface.implements(IJSONRepresentable) + + security.declareProtected(Permissions.AccessContentsInformation, 'asJSON') + def asJSON(self): + """ + Generate a JSON representable content for ERP5 object + + Currently we use `XMLExportImport` to first convert the object to its XML + respresentation and then use xmltodict to convert it to dict and JSON + format finally + """ + dict_value = self._asDict() + # Convert the XML to json representation + return json.dumps(dict_value) + + security.declareProtected(Permissions.AccessContentsInformation, 'asDict') + def _asDict(self): + """ + Gets the dict representation of the object + """ + # Use OFS exportXML to first export to xml + f = StringIO() + XMLExportImport.exportXML(self._p_jar, self._p_oid, f) + + # Get the value of exported XML + xml_value = f.getvalue() + return xmltodict.parse(xml_value) + + def fromJSON(self, val): + """ + Updates an object, based on a JSON representation + """ + dict_value = json.loads(val) + + # Convert the dict_value to XML representation + xml_value = xmltodict.unparse(dict_value) + + f = StringIO(xml_value) + return XMLExportImport.importXML(self._p_jar, f) + +InitializeClass(JSONRepresentableMixin) diff --git a/product/ERP5Type/mixin/patchable.py b/product/ERP5Type/mixin/patchable.py new file mode 100644 index 00000000000..75a9840df7b --- /dev/null +++ b/product/ERP5Type/mixin/patchable.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved. +# Ayush Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + + +import zope.interface +from Products.ERP5Type.interfaces.patchable import IPatchable +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.ERP5Type.Globals import InitializeClass + +class PatchableMixin: + """ + An implementation of IPatchable + """ + + zope.interface.implements(IPatchable) + + # Declarative Security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + security.declareProtected(Permissions.AccessContentsInformation, + 'patch') + def patch(patch, path=None, unified_diff_selection=None): + pass + +InitializeClass(PatchableMixin) -- 2.30.9 From 16a9f0c09a08ea16c33731386d64eac89ef0b776 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:03:44 +0100 Subject: [PATCH 04/56] CommitTool: Business Commit Tool to track commits of BT sub-objects --- product/ERP5/Tool/CommitTool.py | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 product/ERP5/Tool/CommitTool.py diff --git a/product/ERP5/Tool/CommitTool.py b/product/ERP5/Tool/CommitTool.py new file mode 100644 index 00000000000..c2cfa06246e --- /dev/null +++ b/product/ERP5/Tool/CommitTool.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from webdav.client import Resource + +from App.config import getConfiguration +import os +import shutil +import sys +import hashlib +import pprint +import transaction + +from Acquisition import Implicit, Explicit +from AccessControl import ClassSecurityInfo +from AccessControl.SecurityInfo import ModuleSecurityInfo +from Products.CMFActivity.ActiveResult import ActiveResult +from Products.PythonScripts.PythonScript import PythonScript +from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping +from Products.ERP5Type.DiffUtils import DiffFile +from Products.ERP5Type.Tool.BaseTool import BaseTool +from Products.ERP5Type.Cache import transactional_cached +from Products.ERP5Type import Permissions, interfaces +from Products.ERP5.Document.BusinessTemplate import BusinessTemplateMissingDependency +from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter +from Products.ERP5.genbt5list import generateInformation +from Acquisition import aq_base +from tempfile import mkstemp, mkdtemp +from Products.ERP5 import _dtmldir +from cStringIO import StringIO +from urllib import pathname2url, urlopen, splittype, urlretrieve +import urllib2 +import re +from xml.dom.minidom import parse +from xml.parsers.expat import ExpatError +import struct +import cPickle +from base64 import b64encode, b64decode +from Products.ERP5Type.Message import translateString +from zLOG import LOG, INFO, WARNING +from base64 import decodestring +from difflib import unified_diff +from operator import attrgetter +import subprocess +import time + +class CommitTool (BaseTool): + """ + CommitTool manages commits of Business Templates. + + CommitTool provides some methods to deal with Business Templates: + - download commits + - install commits + - push commits + - XXX + + ERP5 Business Commit contains: + - ERP5 Business Template Item + - ERP5 Business Template Property Item + - ERP5 Business Template Patch Item + in relation to one or more ERP5 Business Template (follow_up relation from BTI to BTS) + + ERP5 Business Commit has relation to: + - predecessor: previous Business Commit + + ERP5 Business Snapshot has relation to: + - similar: equivalent ERP5 Business Commit + they only serve as a way to optmize installation time by having all objects in one + folder rather than downloading 10 years of commit to get things installed + + ERP5 Business Temaplate Specification defines: + - title + - description + - dependency + - list of paths (of different kinds) to help generate initial ERP5 Business Commit + It is only a spec file with no object inside + + ERP5 Business Snapshot contains: + - all BTI BTPI and BTPI for a single BT at specifc build point + + Bootstrap: + - import zexp file to portal_commits/ + - portal_commits/business_template_index is installed (ERP5 Buiness Snapshot of the erp5_root_index business template) [BOOSTRAP] + - this makes portal_templates/* full of all possibles Business Template that can be installed + - portal_commits/business_template_index --> similar/portal_commits/387897938794876-276376 + + Portal Templates: + - portal_templates/erp5_base (only one) + - portal_templates/erp5_trade (only one) + they containing nothing inside, just their title and description and spec (like a spec file in rpm) + + Installation: + - download all commits that + - are predecessor of 387897938794876-276376 + - that are required to install (erp5_base, erp5_trade, etc.) + RESULT: + - portal_commits/387897938794876-1 (Commit) + - portal_commits/387897938794876-2 (Commit) + - portal_commits/387897938794876-3 (Commit) + - portal_commits/387897938794876-4 (Commit) + - portal_commits/387897938794876-5 (Commit) + - portal_commits/387897938794876-6 (Commit) + - portal_commits/387897938794876-7 (Commit) + - portal_commits/387897938794876-8 (Commit) + - portal_commits/387897938794876-9 (Snapshort of erp5_trade) + - portal_commits/387897938794876-10 (Snapshot of erp5_base) + + """ + id = 'portal_commits' + title = 'Commit Tool' + meta_type = 'ERP5 Commit Tool' + portal_type = 'Commit Tool' + allowed_types = ( + 'ERP5 Business Commit', + 'ERP5 Business Snapshot', + ) + + # This stores information on repositories. + repository_dict = {} + + # Declarative Security + security = ClassSecurityInfo() + + security.declareProtected(Permissions.ManagePortal, 'manage_overview') + manage_overview = DTMLFile('explainCommitTool', _dtmldir) + +InitializeClass(CommitTool) -- 2.30.9 From 0db17646b2072c7632bd7f5ed6cdc1d68d112292 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:18:29 +0100 Subject: [PATCH 05/56] Business Manager: Introduction of Business Manager class and changes in template tool according to it --- product/ERP5/Document/BusinessManager.py | 1484 ++++++++++++++++++++++ product/ERP5/Tool/TemplateTool.py | 1300 ++++++++++++++++++- product/ERP5/__init__.py | 6 +- 3 files changed, 2773 insertions(+), 17 deletions(-) create mode 100644 product/ERP5/Document/BusinessManager.py diff --git a/product/ERP5/Document/BusinessManager.py b/product/ERP5/Document/BusinessManager.py new file mode 100644 index 00000000000..aabc5ff8a71 --- /dev/null +++ b/product/ERP5/Document/BusinessManager.py @@ -0,0 +1,1484 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2017 Nexedi SARL and Contributors. All Rights Reserved. +# Ayush-Tiwari +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability 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 +# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import gc +import os +import posixpath +import transaction +import imghdr +import tarfile +import time +import hashlib +import fnmatch +import re +import threading +import pprint + +from copy import deepcopy +from collections import defaultdict +from cStringIO import StringIO +from OFS.Image import Pdata +from lxml.etree import parse +from urllib import quote, unquote +from OFS import SimpleItem, XMLExportImport +from datetime import datetime +from itertools import chain +from operator import attrgetter +from persistent.list import PersistentList +from AccessControl import ClassSecurityInfo, Unauthorized, getSecurityManager +from Acquisition import Implicit, aq_base, aq_inner, aq_parent +from zLOG import LOG, INFO, WARNING + +from Products.ERP5Type.XMLObject import XMLObject +from Products.ERP5Type.Core.Folder import Folder +from Products.CMFCore.utils import getToolByName +from Products.PythonScripts.PythonScript import PythonScript +from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken +from Products.ERP5Type.Globals import Persistent, PersistentMapping +from Products.ERP5Type import Permissions, PropertySheet, interfaces +from Products.ERP5Type.Globals import InitializeClass +from Products.ERP5Type.TransactionalVariable import getTransactionalVariable +from Products.ERP5Type.patches.ppml import importXML +from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter + +customImporters = { + XMLExportImport.magic: importXML, + } + +CACHE_DATABASE_PATH = None +try: + if int(os.getenv('ERP5_BT5_CACHE', 0)): + from App.config import getConfiguration + import gdbm + instancehome = getConfiguration().instancehome + CACHE_DATABASE_PATH = os.path.join(instancehome, 'bt5cache.db') +except TypeError: + pass +cache_database = threading.local() +_MARKER = [] + +SEPARATELY_EXPORTED_PROPERTY_DICT = { + # For objects whose class name is 'class_name', the 'property_name' + # attribute is removed from the XML export, and the value is exported in a + # separate file, with extension specified by 'extension'. + # 'extension' must be None for auto-detection. + # + # class_name: (extension, unicode_data, property_name), + "Document Component": ("py", 0, "text_content"), + "DTMLDocument": (None, 0, "raw"), + "DTMLMethod": (None, 0, "raw"), + "Extension Component": ("py", 0, "text_content"), + "File": (None, 0, "data"), + "Image": (None, 0, "data"), + "OOoTemplate": ("oot", 1, "_text"), + "PDF": ("pdf", 0, "data"), + "PDFForm": ("pdf", 0, "data"), + "Python Script": ("py", 0, "_body"), + "PythonScript": ("py", 0, "_body"), + "Spreadsheet": (None, 0, "data"), + "SQL": ("sql", 0, "src"), + "SQL Method": ("sql", 0, "src"), + "Test Component": ("py", 0, "text_content"), + "Test Page": (None, 0, "text_content"), + "Web Page": (None, 0, "text_content"), + "Web Script": (None, 0, "text_content"), + "Web Style": (None, 0, "text_content"), + "ZopePageTemplate": ("zpt", 1, "_text"), +} + + +def _delObjectWithoutHook(obj, id): + """OFS.ObjectManager._delObject without calling manage_beforeDelete.""" + ob = obj._getOb(id) + if obj._objects: + obj._objects = tuple([i for i in obj._objects if i['id'] != id]) + obj._delOb(id) + try: + ob._v__object_deleted__ = 1 + except: + pass + + +def _recursiveRemoveUid(obj): + """Recusivly set uid to None, to prevent (un)indexing. + This is used to prevent unindexing real objects when we delete subobjects on + a copy of this object. + """ + if getattr(aq_base(obj), 'uid', _MARKER) is not _MARKER: + obj.uid = None + # Make all objects and sub-object un-indexable + # XXX: Should be moved into another function or change the name and desc + # of this function + obj.isIndexable = ConstantGetter('isIndexable', value=False) + for subobj in obj.objectValues(): + _recursiveRemoveUid(subobj) + + +# New BusinessItem addition function +def manage_addBusinessItem(self, item_path='', item_sign=1, item_layer=0, *args, **kw): + # Create BusinessItem object container + c = BusinessItem(item_path, item_sign, item_layer) + + return c + + +# New BusinessPropertyItem addition function +def manage_addBusinessPropertyItem(self, item_path='', item_sign=1, item_layer=0, *args, **kw): + # Create BusinessPathItem object container + c = BusinessPropertyItem(item_path, item_sign, item_layer) + + return c + +# New BusinessPatchItem addition function +def manage_addBusinessPatchItem(self, item_path='', item_sign=1, item_layer=0, *args, **kw): + # Create BusinessPatchItem object container + c = BusinessPatchItem(item_path, item_sign, item_layer) + + return c + +class BusinessManager(Folder): + + """Business Manager is responsible for saving objects and properties in + an ERP5Site. Everything will be saved just via path""" + + meta_type = 'ERP5 Business Manager' + portal_type = 'Business Manager' + add_permission = Permissions.AddPortalContent + + template_format_version = 3 + + # Factory Type Information + factory_type_information = \ + { 'id' : portal_type + , 'meta_type' : meta_type + , 'icon' : 'file_icon.gif' + , 'product' : 'ERP5Type' + , 'factory' : '' + , 'type_class' : 'BusinessManager' + , 'immediate_view' : 'BusinessManager_view' + , 'allow_discussion' : 1 + , 'allowed_content_types': ('Business Item', + 'Business Property Item', + ) + , 'filter_content_types' : 1 + } + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + # Declarative properties + property_sheets = ( + PropertySheet.Base, + PropertySheet.XMLObject, + PropertySheet.SimpleItem, + PropertySheet.CategoryCore, + PropertySheet.Version, + PropertySheet.BusinessManager, + ) + + def getShortRevision(self): + return None + + def getVersion(self): + # Override the getter because currently we don't migrate the version, and + # this is used to find missing dependency list + return '5.4.7' + + security.declareProtected(Permissions.AccessContentsInformation, + 'getBuildingState') + def getBuildingState(self, default=None, id_only=1): + """ + Returns the current state in building + """ + portal_workflow = getToolByName(self, 'portal_workflow') + wf = portal_workflow.getWorkflowById( + 'business_manager_building_workflow') + return wf._getWorkflowStateOf(self, id_only=id_only ) + + security.declareProtected(Permissions.AccessContentsInformation, + 'getInstallationState') + def getInstallationState(self, default=None, id_only=1): + """ + Returns the current state in installation + """ + portal = self.getPortalObject() + portal_workflow = portal.portal_workflow + wf = portal_workflow.getWorkflowById( + 'business_manager_installation_workflow') + return wf._getWorkflowStateOf(self, id_only=id_only ) + + def changeBuildingStatetoModified(self): + """ + Change building_state to 'modified'. This is needed specifically as we want + to update the building state even if we change any sub-objects(Business Item + or Business Property Item) of Business Manager. + """ + portal_workflow = self.getPortalObject().portal_workflow + wf = portal_workflow._getOb('business_manager_building_workflow') + + wf._executeMetaTransition(self, 'modified') + + def applytoERP5(self, DB): + """Apply the flattened/reduced Business Manager to the DB""" + portal = self.getPortalObject() + pass + + def _clean(self): + """ + Clean built information. + Remove all the built sub-objects from Business Item or Business Property + Item. + """ + for item in self.objectValues(): + if item.getPortalType() == 'Business Item': + # Delete the sub-object + id_list = [l for l in item.objectIds()] + if id_list: + item.manage_delObjects(ids=id_list) + elif item.getPortalType() == 'Business Property Item': + # Add empty property item_property_name, value and type + # XXX: Shouldn't we check if the properties exist and then just delete + # them ? + item.setProperty('item_property_name', '') + item.setProperty('item_property_value', '') + item.setProperty('item_property_type', '') + + security.declareProtected(Permissions.ManagePortal, 'preinstall') + def preinstall(self, check_dependencies=1, **kw): + """ + Preinstall for Business Manager comapres the installation state and returns + the changes in a manner which can keep up compatibilty with the view we use + in Business Template installation. + + This function calls `portal_templates.updateInstallationState` to get the + change_list. + + We don't care of check_dependencies here as this all would be taken care by + `udpateInstallationState` iteself. + """ + change_list = self.aq_parent.compareInstallationState([self]) + modified_object_list = {} + for path in change_list: + type_name = 'Business Item' + if '#' in path[0]: + type_name = 'Business Property Item' + modified_object_list[path[0]] = [path[1], type_name] + return modified_object_list + + security.declareProtected(Permissions.ManagePortal, 'clean') + clean = _clean + + def _setTitle(self, value): + """ + Override required due to bootstrap + """ + self.title = value + + def getPathItemList(self): + return self.objectValues() + + # XXX: Change into property + security.declareProtected(Permissions.ManagePortal, 'getTemplateFormatVersion') + def getTemplateFormatVersion(self): + return self.template_format_version + + # XXX: Change into property + def _setTemplateFormatVersion(self, value): + self.template_format_version = int(value) + + def propertyMap(self): + prop_map = super(BusinessManager, self).propertyMap() + final_prop_map = prop_map+self._properties + return final_prop_map + + def export(self, path=None, **kw): + """ + Export the object as zexp file + """ + if not self.getBuildingState() == 'built': + raise ValueError, 'Manager not built properly' + f = StringIO() + + self._p_jar.exportFile(self._p_oid, f) + + # XXX: Improve naming + name = self.getTitle() + name = posixpath.join(path, name) + # XXX required due to overuse of os.path + name = name.replace('\\', '/').replace(':', '/') + name = quote(name + '.zexp') + obj_path = name.replace('/', os.sep) + + f.seek(0) + obj = f.read() + + object_path = os.path.join(path, obj_path) + path = os.path.dirname(object_path) + os.path.exists(path) or os.makedirs(path) + f = open(object_path, 'wb') + try: + f.write(obj) + finally: + f.close() + + security.declareProtected(Permissions.ManagePortal, 'importFile') + def importFile(self, path, connection=None): + """ + Import Business Manager object and all attribute to current BM itself + """ + if not connection: + connection = self.aq_parent._p_jar + file = open(path, 'rb') + imported_manager = connection.importFile(file) + self.title = imported_manager.title + for obj in imported_manager.objectValues(): + delattr(obj, '__ac_local_roles__') + # XXX: Donot merge this, needed just for migrated erp5_core + try: + self._setObject(obj.id, aq_base(obj)) + except Exception: + pass + obj.isIndexable = ConstantGetter('isIndexable', value=False) + + def __add__(self, other): + """ + Adds the Business Item objects for the given Business Manager objects + """ + # XXX: Still to define + return self + + __radd__ = __add__ + + def __sub__(self, other): + """ + Override subtract to find difference b/w the values in different cases. + """ + # Create the sha list for all path item list available in current object + sha_list = [item.sha for item in self._path_item_list] + # Reverse the sign of Business Item objects for the old Business Manager + # Trying comparing/subtracting ZODB with old installed object + for path_item in other._path_item_list: + if path_item.sha in sha_list: + self._path_item_list = [item for item + in self._path_item_list + if item.sha != path_item.sha] + else: + path_item.sign = -1 + self._path_item_list.append(path_item) + + return self + + __rsub__ = __sub__ + + security.declareProtected(Permissions.ManagePortal, 'storeTemplateData') + def storeTemplateData(self, isBuild=False, **kw): + """ + Store data for objects in the ERP5. + Create Business Item sub-objects after resolving the paths. Also, add + layers to all Business Item objects + """ + portal = self.getPortalObject() + LOG('Business Manager', INFO, 'Storing Manager Data') + + to_delete_id_list = [] + for item in self.objectValues(): + + # Only try to resolve the Business Item objects + if item.getPortalType() != 'Business Item': + continue + + item_path = item.getProperty('item_path') + # Resolve the path and update sub-objects lists + path_list = self._resolvePath(portal, [], item_path.split('/')) + + if len(path_list) == 1 and path_list[0] == item_path: + continue + else: + item_sign = item.getProperty('item_sign') + item_layer = item.getProperty('item_layer') + # Create new Business Item objects with path in path_list and sign and + # layer same as that of the path used for resolving to new paths. + for path in path_list: + path_item = self.newContent(portal_type='Business Item') + path_item.edit( + item_path=path, + item_sign=item_sign, + item_layer=item_layer + ) + # Add Id of BusinessItem to be deleted as we do already have resolved + # path and new sub-objects based on resolved paths + to_delete_id_list.append(item.getId()) + + # Now, delete the original Business Item(s) sub-object as we do have + # Business Item created from resolved paths + self.manage_delObjects(ids=to_delete_id_list) + + def _resolvePath(self, folder, relative_url_list, id_list): + """ + For Business Manager, we expect to resolve the path incase we face + paths which expect to include sub-objects. + For example: 'portal_catalog/erp5_mysql_innodb/**' should only consider + the sub-objects of the object mentioned, and create separate BusinessItem + objects for all of them. + + This method calls itself recursively. + + The folder is the current object which contains sub-objects. + The list of ids are path components. If the list is empty, + the current folder is valid. + """ + if len(id_list) == 0: + return ['/'.join(relative_url_list)] + id = id_list[0] + if re.search('[\*\?\[\]]', id) is None: + # If the id has no meta character, do not have to check all objects. + obj = folder._getOb(id, None) + if obj is None: + raise AttributeError, "Could not resolve '%s' during business template processing." % id + return self._resolvePath(obj, relative_url_list + [id], id_list[1:]) + path_list = [] + for object_id in fnmatch.filter(folder.objectIds(), id): + if object_id != "": + path_list.extend(self._resolvePath( + folder._getOb(object_id), + relative_url_list + [object_id], id_list[1:])) + return path_list + + def getPathList(self): + path_list = [] + for item in self.objectValues(): + path_list.append(item.getProperty('item_path')) + return path_list + + def getPathItemDict(self): + path_item_dict = {} + # TODO: Handle error for BM with multiple items at same path + for item in self.objectValues(): + path_item_dict[item.getProperty('item_path')] = item + return path_item_dict + + def getBusinessItemByPath(self, path): + path_item_dict = self.getPathItemDict() + try: + return path_item_dict[path] + except KeyError: + return + + def build(self, no_action=False, **kw): + """Creates new values for business item from the values from + OFS Database""" + LOG('Business Manager', INFO, 'Building Business Manager') + removable_sub_object_path_list = kw.get('removable_sub_object_path', []) + removable_property_dict = kw.get('removable_property', {}) + # Now, we need to put a check here for returning whih objects should be + # updated during rebuild of BM + checkNeeded = kw.get('checkNeeded', False) + # Build all paths if there is no check required(i.e, its a new build action) + if not checkNeeded: + if not no_action: + self.storeTemplateData(isBuild=True, **kw) + for path_item in self.objectValues(): + kwargs = {} + item_path = path_item.getProperty('item_path') + kwargs['removable_property_list'] = removable_property_dict.get(item_path, []) + kwargs['remove_sub_objects'] = item_path in removable_sub_object_path_list + path_item.build(self, **kwargs) + return self + else: + item_path_list = kw.get('item_path_list', []) + if item_path_list: + for path in item_path_list: + item = self.getBusinessItemByPath(path) + item.build(self) + return self + + def flattenBusinessManager(self): + """ + Flattening a reduced Business Manager with two path p1 and p2 where p1 <> p2: + + flatten([(p1, s1, l1, v1), (p2, s2, l2, v2)]) = [(p1, s1, 0, v1), (p2, s2, 0, v2)] + A reduced Business Manager BT is said to be flattened if and only if: + flatten(BT) = BT + """ + pass + + def reduceBusinessManager(self): + """ + Reduction is a function that takes a Business Manager as input and returns + a smaller Business Manager by taking out values with lower priority layers. + + After taking out BusinessItem(s) with lower priority layer, we also go + through arithmetic in case there are multiple number of BI at the higher layer + + Two path on different layer are reduced as a single path with the highest layer: + + If l1 > l2, + reduce([(p, s, l1, (a, b, c)), (p, s, l2, (d, e))]) = [(p, s, l1, merge(a, b, c))] + + A Business Manager BT is said to be reduced if and only if: + reduce(BT) = BT + """ + path_list = self.getPathList() + + reduced_path_item_list = [] + + # We separate the path list in the ones which are repeated and the ones + # which are unique for the installation + seen_path_list = set() + unique_path_list = [x for x + in path_list + if x not in seen_path_list + and not seen_path_list.add(x)] + + # Create an extra dict for values on path which are repeated in the path list + seen_path_dict = {path: [] for path in seen_path_list} + + for item in self.objectValues(): + if item.getProperty('item_path') in seen_path_list: + # In case the path is repeated keep the path_item in a separate dict + # for further arithmetic + seen_path_dict[item.getProperty('item_path')].append(item) + else: + # If the path is unique, add them in the list of reduced Business Item + reduced_path_item_list.append(item) + + # Reduce the values and get the merged result out of it + for path, path_item_list in seen_path_dict.items(): + + # Create separate list of list items with highest priority + higest_priority_layer = max(path_item_list, key=attrgetter('item_layer')).item_layer + prioritized_path_item = [path_item for path_item + in path_item_list + if path_item.item_layer == higest_priority_layer] + + # Separate the positive and negative sign path_item + if len(prioritized_path_item) > 1: + path_item_list_add = [item for item + in prioritized_path_item + if item.getProperty('item_sign') > 0] + + path_item_list_subtract = [item for item + in prioritized_path_item + if item.getProperty('item_sign') < 0] + + if path_item_list_add: + + combined_added_path_item = reduce(lambda x, y: x+y, path_item_list_add) + if combined_added_path_item.getPortalType() == 'Business Item': + added_value = combined_added_path_item.objectValues()[0] + elif combined_added_path_item.getPortalType() == 'Business Property Item': + added_value = combined_added_path_item.getProperty('item_property_value') + + reduced_path_item_list.append(combined_added_path_item) + + if path_item_list_subtract: + + combined_subtracted_path_item = reduce(lambda x, y: x+y, path_item_list_subtract) + if combined_subtracted_path_item.getPortalType() == 'Business Item': + added_value = combined_subtracted_path_item.objectValues()[0] + elif combined_subtracted_path_item.getPortalType() == 'Business Property Item': + added_value = combined_subtracted_path_item.getProperty('item_property_value') + + reduced_path_item_list.append(combined_subtracted_path_item) + + # XXX: For now, we just care about added items and append them in reduced + # path list. + #if added_value != subtracted_value: + # Append the arithmetically combined path_item objects in the final + # reduced list after removing the intersection + # added_value, subtracted_value = \ + # self._simplifyValueIntersection(added_value, subtracted_value) + + # combined_added_path_item.value = added_value + # combined_subtracted_path_item.value = subtracted_value + + # Append the path_item to the final reduced path_item_list after + # doing required arithmetic on it. Make sure to first append + # subtracted item because while installation, we need to first + # uninstall the old object and then install new object at same path + # reduced_path_item_list.append(combined_subtracted_path_item) + # reduced_path_item_list.append(combined_added_path_item) + + else: + reduced_path_item_list.append(prioritized_path_item[0]) + + id_list = [l for l in self.objectIds()] + for l in id_list: + try: + self._delObject(l) + except Exception: + # XXX: REMOVE/RECHEK BEFORE MERGE + # The reason for doing this horrible workaround is to delete object + # for 'portal_memcached/persistent_memcached_plugin' as it is not getting + # deleted in 1st attempt with failure : + # AttributeError: 'NoneType' object has no attribute 'getUid' + self._delObject(l) + + for item in reduced_path_item_list: + item.isIndexable = ConstantGetter('isIndexable', value=False) + new_id = self.generateNewId() + self._setObject(new_id, aq_base(item), + suppress_events=True) + + def _simplifyValueIntersection(self, added_value, subtracted_value): + """ + Returns values for the Business Item having same path and layer after + removing the intersection of the values + + Parameters: + added_value - Value for the Business Item having sign = +1 + subtracted_value - Value for Busienss Item having sign = -1 + """ + built_in_number_type = (int, long, float, complex) + built_in_container_type = (tuple, list, dict, set) + built_in_type_list = built_in_number_type + built_in_container_type + + # For ERP5 objects, we should return the added and subtracted values as it is + if type(added_value).__name__ not in built_in_type_list and \ + type(subtracted_value).__name__ not in built_in_type_list: + return added_value, subtracted_value + + # For all the values of container type, we remove the intersection + added_value = [x for x in added_value if x not in subtracted_value] + subtracted_value = [x for x in subtracted_value if x not in added_value] + + return added_value, subtracted_value + + def mergeBusinessPatchItem(self, item1, item2): + """ + Merge two BusinessPatchItem at same layer and same sign. + + XXX: This function shouldn't be used at reduction, rather than during + updating the installation_state, because we need to know all the states to + be able to find out which patch needs to be used. + + Cases: + 1. Shouldn't matter if both have same new_value as we give preference to + the final result and try to remove conflict as much as possible. + 2. If both have different old_value and different new_value, merge should + return both of them and let the updateInstallationState process decide + which one to apply and if needed, raise a conflict + """ + pass + +class BusinessItem(XMLObject): + + """Saves the path and values for objects, properties, etc, the + attributes for a path configuration being: + + - item_path (similar to an xpath expression) + Examples of path : + portal_type/Person + portal_type/Person#title + portal_type/Person#property_sheet?ancestor=DublinCore + portal_type/Person#property_sheet?position=2 + - item_sign (+1/-1) + - item_layer (0, 1, 2, 3, etc.) + - item_value (a set of pickable value in python)""" + + add_permission = Permissions.AddPortalContent + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + constructors = (manage_addBusinessItem,) + portal_type = 'Business Item' + meta_type = 'Business Item' + icon = None + isProperty = False + isIndexable = False + + def _edit(self, item_path='', item_sign=1, item_layer=0, *args, **kw): + """ + Overriden function so that we can update attributes for BusinessItem objects + """ + return super(BusinessItem, self)._edit(item_path=item_path, + item_sign=item_sign, + item_layer=item_layer, + **kw) + + def build(self, context, **kw): + """ + Extract value for the given path from the OFS + + Three different situations to extract value: + 1. For paths which point directly to an object in OFS + 2. For paths which point to multiple objects inside a folder + 3. For paths which point to property of an object in OFS : In this case, + we can have URL delimiters like ?, #, = in the path + """ + LOG('Business Manager', INFO, 'Building Business Item') + + # Remove the old sub-objects if exisiting before building + id_list = [l for l in self.objectIds()] + if id_list: + self.manage_delObjects(ids=id_list) + + p = context.getPortalObject() + path = self.getProperty('item_path') + obj = p.unrestrictedTraverse(path) + obj = obj._getCopy(context) + + # We should remove the extra properties of object so that there + # shouldn't be redundancy of the proeprties + removable_property_list = kw.get('removable_property_list', []) + + # We should also add extra parameter to remove sub-objects by removing + # `_tree` for any erp5 object. This way we can have control over adding + # sub-objects as new Business Item objects + remove_sub_objects = kw.get('remove_sub_objects', False) + if remove_sub_objects: + removable_property_list.append('_tree') + keep_workflow_history = False + # For portal_components object, we need validation_history + if self.getProperty('item_path').startswith('portal_components'): + keep_workflow_history = True + obj = self.removeProperties(obj, + 1, + properties=removable_property_list, + keep_workflow_history=keep_workflow_history, + ) + obj = obj.__of__(context) + # XXX: '_recursiveRemoveUid' is not working as expected + _recursiveRemoveUid(obj) + obj = aq_base(obj) + obj.isIndexable = ConstantGetter('isIndexable', value=False) + new_id = self.generateNewId() + self._setObject(new_id, obj, suppress_events=True) + + def _resolvePath(self, folder, relative_url_list, id_list): + """ + We go through 3 types of paths: + + 1. General path we find in erp5 for objects + Ex: portal_type/Person + In this case, we import/export the object on the path + + 2. Path where we consider saving sub-objects also, in that case we create + new BusinessItem for those objects + Ex: portal_catalog/erp5_mysql_innodb/** + This should create BI for the catalog methods sub-objects present in the + erp5_catalog. + + This method calls itself recursively. + + The folder is the current object which contains sub-objects. + The list of ids are path components. If the list is empty, + the current folder is valid. + """ + if len(id_list) == 0: + return ['/'.join(relative_url_list)] + id = id_list[0] + if re.search('[\*\?\[\]]', id) is None: + # If the id has no meta character, do not have to check all objects. + obj = folder._getOb(id, None) + if obj is None: + raise AttributeError, "Could not resolve '%s' during BusinessItem processing." % id + return self._resolvePath(obj, relative_url_list + [id], id_list[1:]) + path_list = [] + for object_id in fnmatch.filter(folder.objectIds(), id): + if object_id != "": + path_list.extend(self._resolvePath( + folder._getOb(object_id), + relative_url_list + [object_id], id_list[1:])) + return path_list + + def install(self, context, *args): + """ + Set the value to the defined path. + """ + # In case the path denotes property, we create separate object for + # ObjectTemplateItem and handle the installation there. + try: + portal = context.getPortalObject() + except AttributeError: + # This is a possibility during bootstrap where the context is not an + # erp5 object but a dynamic class + if args: + portal = args[0] + else: + raise AttributeError('No portal object found') + + path = self.getProperty('item_path') + path_list = path.split('/') + container_path = path_list[:-1] + object_id = path_list[-1] + try: + container = self.unrestrictedResolveValue(portal, container_path) + except KeyError: + # parent object can be set to nothing, in this case just go on + container_url = '/'.join(container_path) + old_obj = container._getOb(object_id, None) + # delete the old object before installing a new object + if old_obj: + container._delObject(object_id) + # Create a new object only if sign is +1 + # If sign is +1, set the new object on the container + if int(self.getProperty('item_sign')) == 1: + # install object + obj = self.objectValues()[0] + obj = obj._getCopy(container) + + # Update the type_provider_list if needed + # XXX: Find a way to fiter and do this check only for tool, so that we + # don't lose on perfomance in checking this everytime + if interfaces.ITypeProvider.providedBy(obj): + type_container_id = obj.id + types_tool = portal.portal_types + if type_container_id not in types_tool.type_provider_list: + types_tool.type_provider_list = tuple(types_tool.type_provider_list) + \ + (type_container_id,) + + # Before making `obj` a sub-object of `container`, we should the acquired + # roles on obj + obj.isIndexable = ConstantGetter('isIndexable', value=False) + delattr(obj, '__ac_local_roles__') + container._setObject(object_id, obj, suppress_events=True) + obj = container._getOb(object_id) + skin_tool = portal.portal_skins + if obj.aq_parent.meta_type == 'CMF Skins Tool': + registerSkinFolder(skin_tool, obj) + + def unrestrictedResolveValue(self, context=None, path='', default=_MARKER, + restricted=0): + """ + Get the value without checking the security. + This method does not acquire the parent. + """ + if isinstance(path, basestring): + stack = path.split('/') + else: + stack = list(path) + stack.reverse() + if stack: + if context is None: + portal = aq_inner(self.getPortalObject()) + container = portal + else: + container = context + + if restricted: + validate = getSecurityManager().validate + + while stack: + key = stack.pop() + try: + value = container[key] + except KeyError: + LOG('BusinessManager', WARNING, + 'Could not access object %s' % (path,)) + if default is _MARKER: + raise + return default + + if restricted: + try: + if not validate(container, container, key, value): + raise Unauthorized('unauthorized access to element %s' % key) + except Unauthorized: + LOG('BusinessManager', WARNING, + 'access to %s is forbidden' % (path,)) + if default is _MARKER: + raise + return default + + container = value + + return value + else: + return context + + def __add__(self, other): + """ + Add the values from the path when the path is same for 2 objects + """ + if self.getProperty('item_path') != other.getProperty('item_path'): + raise ValueError, "BusinessItem are incommensurable, have different path" + elif self.getProperty('item_sign') != other.getProperty('item_sign'): + raise ValueError, "BusinessItem are incommensurable, have different sign" + else: + new_value = self._mergeValue(value_list=[self.objectValues()[0], other.objectValues()[0]]) + # Remove old objects if it exists + old_id_list = [l for l in self.objectIds()] + if old_id_list: + self.manage_delObjects(ids=old_id_list) + # Add the new sub-object + self._setObject(new_value.id, new_value) + return self + + __radd__ = __add__ + + def _mergeValue(self, value_list): + """ + Merge value in value list + + merge(a, b, c) : A monotonic commutative function that depends on the + type of a, b and c: + + if a, b and c are sets, merge = union + if a, b and c are lists, merge = ordered concatenation + if a, b and c are objects, merge = the object created the last + else merge = MAX + """ + builtin_number_type = (int, long, float, complex) + + # Now, consider the type of both values + if all(isinstance(x, builtin_number_type) for x in value_list): + merged_value = max(value_list) + elif all(isinstance(x, set) for x in value_list): + merged_value = set(chain.from_iterable(value_list)) + elif all(isinstance(x, list) for x in value_list): + merged_value = list(set(chain.from_iterable(value_list))) + elif all(isinstance(x, tuple) for x in value_list): + merged_value = tuple(set(chain.from_iterable(value_list))) + else: + # In all other case, check if the values are objects and then take the + # objects created last. + + # XXX: Should we go with creation date or modification_date ?? + # TODO: + # 1. Add check that the values are ERP5 objects + # 2. In case 2 maximum values are created at same time, prefer one with + # higher priority layer + merged_value = max([max(value, key=attrgetter('creation_date')) + for value in value_list], + key=attrgetter('creation_date')) + + return merged_value + + def removeProperties(self, + obj, + export, + properties=[], + keep_workflow_history=False, + keep_workflow_history_last_history_only=False): + """ + Remove unneeded properties for export + """ + obj._p_activate() + klass = obj.__class__ + classname = klass.__name__ + attr_set = {'_dav_writelocks', '_filepath', '_owner', '_related_index', + 'last_id', 'uid', + '__ac_local_roles__', '__ac_local_roles_group_id_dict__'} + if properties: + for prop in properties: + if prop.endswith('_list'): + prop = prop[:-5] + attr_set.add(prop) + if export: + if keep_workflow_history_last_history_only: + self._removeAllButLastWorkflowHistory(obj) + elif not keep_workflow_history: + attr_set.add('workflow_history') + # PythonScript covers both Zope Python scripts + # and ERP5 Python Scripts + if isinstance(obj, PythonScript): + attr_set.update(('func_code', 'func_defaults', '_code', + '_lazy_compilation', 'Python_magic')) + for attr in 'errors', 'warnings', '_proxy_roles': + if not obj.__dict__.get(attr, 1): + delattr(obj, attr) + elif classname in ('File', 'Image'): + attr_set.update(('_EtagSupport__etag', 'size')) + elif classname == 'SQL' and klass.__module__ == 'Products.ZSQLMethods.SQL': + attr_set.update(('_arg', 'template')) + elif interfaces.IIdGenerator.providedBy(obj): + attr_set.update(('last_max_id_dict', 'last_id_dict')) + elif classname == 'Types Tool' and klass.__module__ == 'erp5.portal_type': + attr_set.add('type_provider_list') + + for attr in obj.__dict__.keys(): + if attr in attr_set or attr.startswith('_cache_cookie_'): + try: + delattr(obj, attr) + except AttributeError: + # XXX: Continue in cases where we want to delete some properties which + # are not in attribute list + # Raise an error + continue + + if classname == 'PDFForm': + if not obj.getProperty('business_template_include_content', 1): + obj.deletePdfContent() + return obj + + def getBusinessPath(self): + return self.getProperty('item_path') + + def getBusinessPathSign(self): + return self.getProperty('item_sign', 1) + + def getBusinessPathLayer(self): + return self.getProperty('item_layer', 1) + + def getParentBusinessManager(self): + return self.aq_parent + +class BusinessPropertyItem(XMLObject): + + """Class to deal with path(s) which refer to property of an ERP5 object. + Used to store property name, type and value for a given object and property""" + + add_permission = Permissions.AddPortalContent + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + portal_type = 'Business Property Item' + meta_type = 'Business Property Item' + icon = None + isIndexable = False + isProperty = True + constructors = (manage_addBusinessPropertyItem,) + + def _edit(self, item_path='', item_sign=1, item_layer=0, *args, **kw): + """ + Overriden function so that we can update attributes for BusinessItem objects + """ + return super(BusinessPropertyItem, self)._edit(item_path=item_path, + item_sign=item_sign, + item_layer=item_layer, + **kw) + + def build(self, context, **kw): + p = context.getPortalObject() + path = self.getProperty('item_path') + relative_url, property_id = path.split('#') + obj = p.unrestrictedTraverse(relative_url) + property_value = obj.getProperty(property_id) + property_type = obj.getPropertyType(property_id) + self.setProperty('item_property_name', property_id) + self.setProperty('item_property_type', property_type) + self.setProperty('item_property_value', property_value) + + def install(self, context, *args): + # Get portal object + try: + portal = context.getPortalObject() + except AttributeError: + # This is a possibility during bootstrap where the context is not an + # erp5 object but a dynamic class + if args: + portal = args[0] + else: + raise AttributeError('No portal object found') + + path = self.getProperty('item_path') + relative_url, property_id = path.split('#') + obj = portal.unrestrictedTraverse(relative_url) + property_name = self.getProperty('item_property_name') + property_type = self.getProperty('item_property_type') + property_value = self.getProperty('item_property_value') + # First remove the property from the existing path and keep the default + # empty, and update only if the sign is +1 + obj._delPropValue(property_name) + # Remove the '_list' from the end of property_name. This is required because + # of the way _setProperty is defined where if the type is list_type, it + # explicitly adds '_list' at the end of property_name before trying to call + # the accessor + if property_name.endswith('_list'): + property_name = property_name[:-5] + + # XXX: Explicit addition of property_type for 'type_workflow' property on + # 'ERP5Type' objects. This is required as we have created an explicit + # property outside of property sheet for ERP5Type objects, but at the same + # time, the object(s) also have property sheet for them. Since, there is no + # functions like _setProperty, hasProperty, etc to combine both of them + # we have to end up using explicit mention of 'list' property_value here + # 1. Other option can be to override hasProperty or _setProperty for ERP5Type + # object and write it so that it can handle both _properties attribute as + # well as accessor holders generated from property sheet(s). + # 2. After that we can use hasProperty here for objects to check their + # property_type and then just use the generic '_setProperty' function. + # XXX: For now, we just put this explicitly/hard-code + if property_name == 'type_workflow': + property_type = 'multiple selection' + + if int(self.getProperty('item_sign')) == 1: + obj._setProperty(property_name, property_value, property_type) + + def getBusinessPath(self): + return self.getProperty('item_path') + + def getBusinessPathSign(self): + return self.getProperty('item_sign', 1) + + def getBusinessPathLayer(self): + return self.getProperty('item_layer', 1) + + def getParentBusinessManager(self): + return self.aq_parent + + def getBusinessItemPropertyName(self): + return self.getProperty('item_property_name') + + def getBusinessItemPropertyType(self): + return self.getProperty('item_property_type') + + def getBusinessItemPropertyValue(self): + return self.getProperty('item_property_value') + + def __add__(self, other): + """ + Add the values from the path when the path is same for 2 objects + """ + if self.getProperty('item_path') != other.getProperty('item_path'): + raise ValueError, "BusinessItem are incommensurable, have different path" + elif self.getProperty('item_sign') != other.getProperty('item_sign'): + raise ValueError, "BusinessItem are incommensurable, have different sign" + else: + self.setProperty('item_property_value', self._mergeValue(value_list=[ + self.getProperty('item_property_value'), + other.getProperty('item_property_value')] + )) + return self + + __radd__ = __add__ + + def _mergeValue(self, value_list): + """ + Merge value in value list + + merge(a, b, c) : A monotonic commutative function that depends on the + type of a, b and c: + + if a, b and c are sets, merge = union + if a, b and c are lists, merge = ordered concatenation + if a, b and c are objects, merge = the object created the last + else merge = MAX + """ + builtin_number_type = (int, long, float, complex) + + # Now, consider the type of both values + if all(isinstance(x, builtin_number_type) for x in value_list): + merged_value = max(value_list) + elif all(isinstance(x, set) for x in value_list): + merged_value = set(chain.from_iterable(value_list)) + elif all(isinstance(x, list) for x in value_list): + merged_value = list(set(chain.from_iterable(value_list))) + elif all(isinstance(x, tuple) for x in value_list): + merged_value = tuple(set(chain.from_iterable(value_list))) + else: + # In all other case, check if the values are objects and then take the + # objects created last. + + # XXX: Should we go with creation date or modification_date ?? + # TODO: + # 1. Add check that the values are ERP5 objects + # 2. In case 2 maximum values are created at same time, prefer one with + # higher priority layer + merged_value = max([max(value, key=attrgetter('creation_date')) + for value in value_list], + key=attrgetter('creation_date')) + + return merged_value + +class BusinessPatchItem(XMLObject): + + """ + Business Item for saving patch and diff. This will help us to create a diff or + patch between the old value and current value. + + item_sign -- +1 or -1 + item_path -- extended OFS path (equivalent to deepdiff path) + ex. a/b/c#x/y/z:int/w + item_layer -- layer + old -- old value + new -- new value + dependency_list -- a list of bt5 identifiers useful to rebuild the + BusinesTemplatePatchItem instance + preserved_list -- a list of bt5 identifiers useful to rebuild the + BusinesTemplatePatchItem instance + (XXX: We don't use `preserved_list` for now) + + Business Patch Item can be both PathItem or PropertyItem, but both of them are + quite distinguishable, so we prefer not to use them as base class for it. + """ + # CMF Type definition + portal_type = 'Business Patch Item' + meta_type = 'Business Patch Item' + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + icon = None + isIndexable = False + isProperty = False + constructors = (manage_addBusinessPatchItem,) + allowed_types = ('Business Item', 'Business Property Item',) + + def _edit(self, **kw): + """ + Override _edit to create Business Item and BusinessPropertyItem for old and + new value + """ + dependency_list = kw.get('dependency_list', self.getProperty('dependency_list')) + + # Raise error if no dependency_list, this way we ensure there are no useless + # patch_item objects + if not dependency_list: + raise ValueError('Please add dependency to the Business Patch Item') + + super(BusinessPatchItem, self)._edit(**kw) + + def build(self, context, **kw): + """ + Build should update the old and new value + """ + portal = self.getPortalObject() + portal_templates = portal.portal_templates + + item_path = self.getProperty('item_path') + item_layer = self.getProperty('item_layer') + item_sign = self.getProperty('item_sign') + + # Remove old objects if it exists + old_id_list = [l for l in self.objectIds()] + if old_id_list: + self.manage_delObjects(ids=old_id_list) + + # Get the dependency Business Manager + dependency_list = self.getProperty('dependency_list') + if dependency_list: + dependency_title = dependency_list[0] + dependency_bm = portal_templates.getInstalledBusinessManager(dependency_title) + if not dependency_bm: + raise ValueError('Missing Installed Business Manager for dependecy_list \ + which is required to build') + + # Use item_path to determine if we need to create Business Item or + # Business Property Item for storing old and new values + if '#' in item_path: + # Create new_prop_item and build it from ZODB + new_item = self.newContent(portal_type='Business Property Item', + item_path=item_path, + item_layer=item_layer, + item_sign=item_sign, + id='new_item') + new_item.build(self) + + else: + # Create new_item and build it from ZODB + new_item = self.newContent(portal_type='Business Item', + item_path=item_path, + item_layer=item_layer, + item_sign=item_sign, + id='new_item') + new_item.build(self) + + updated_id = 'old_item' + # Copy old item/property item from the item at similar path in dependency_bm + dependency_item = dependency_bm.getBusinessItemByPath(item_path) + + # Raise if there is no item exisiting in dependency Business Manager + if not dependency_item: + + raise ValueError('No %s exist at path %s in installed version of %s' + % ( new_item.getPortalType(), + item_path, + dependency_title,)) + + cp_data = dependency_bm.manage_copyObjects([dependency_item.getId()]) + new_id = self.manage_pasteObjects(cp_data)[0]['new_id'] + self.manage_renameObject(id=new_id, new_id=updated_id) + + # Get the copied object and update the properties + old_item = self._getOb(updated_id) + old_item.setProperty('item_layer', item_layer) + old_item.setProperty('item_sign', item_sign) + + # Commit the transaction + transaction.commit() + + def getOldValue(self): + """ + Returns old value for the BusinessPatchItem + """ + old_item = self._getOb('old_item')[0] + if old_item.getPortalType() == 'Business Item': + return old_item.objectValues() + else: + return old_item.getProperty('item_property_value') + + def getNewValue(self, build=False): + """ + Returns new value for the given BusinessPatchItem + """ + old_item = self._getOb('new_item') + if old_item.getPortalType() == 'Business Item': + return old_item.objectValues()[0] + else: + return old_item.getProperty('item_property_value') + + def getDiff(self, patch_format='deepdiff'): + """ + Use diff tool to find the diff between two values + + XXX: For now we display the json format of the patched diff + """ + patch = self.getPatchObject(patch_format) + if patch_format == 'deepdiff': + return patch.asDeepDiffPatch().json + else: + # For json-patch object + return patch.asJSONPatch().to_string() + + def getPatchObject(self, patch_format='deepdiff'): + portal = self.getPortalObject() + diff_tool = portal.portal_diff + + old_item = self._getOb('old_item') + new_item = self._getOb('new_item') + + if (old_item.getPortalType() == new_item.getPortalType() == 'Business Item'): + old = old_item.objectValues()[0] + new = new_item.objectValues()[0] + else: + old = old_item.getProperty('item_property_value') + new = new_item.getProperty('item_property_value') + patch = diff_tool.diffPortalObject(old=old, + new=new, + patch_format=patch_format + ) + return patch + + def install(self, context, *args): + """ + Install will call the apply function which puts the new value at the + path mentioned while checking if the old value exists. + """ + # Installation is basically running installation on the new_value + self.new_value.install(context) + + def applyPatch(self): + """ + Apply the new value by removing the old value. Also, show conflict in case + the value at old_installation_state is not same as the old value. + """ + pass + +def registerSkinFolder(skin_tool, skin_folder): + request = skin_tool.REQUEST + # XXX: Getting parameter from request instead of dialog is bad + # XXX: This is even non consistent with rest of parameters selected by user + # (like update_translation or update_catalog) + register_skin_selection = request.get('your_register_skin_selection', 1) + reorder_skin_selection = request.get('your_reorder_skin_selection', 1) + skin_layer_list = request.get('your_skin_layer_list', + skin_tool.getSkinSelections()) + + skin_folder_id = skin_folder.getId() + + try: + skin_selection_list = skin_folder.getProperty( + 'business_template_registered_skin_selections', + skin_tool.getSkinSelections() + ) + except AttributeError: + skin_selection_list = skin_tool.getSkinSelections() + + if isinstance(skin_selection_list, basestring): + skin_selection_list = skin_selection_list.split() + + def skin_sort_key(skin_folder_id): + obj = skin_tool._getOb(skin_folder_id, None) + if obj is None: + return 0, skin_folder_id + return -obj.getProperty('business_template_skin_layer_priority', + obj.meta_type == 'Filesystem Directory View' and -1 or 0), skin_folder_id + + for skin_name in skin_selection_list: + + if (skin_name not in skin_tool.getSkinSelections()) and \ + register_skin_selection: + createSkinSelection(skin_tool, skin_name) + # add newly created skins to list of skins we care for + skin_layer_list.append(skin_name) + + selection = skin_tool.getSkinPath(skin_name) or '' + selection_list = selection.split(',') + if (skin_folder_id not in selection_list): + selection_list.insert(0, skin_folder_id) + if reorder_skin_selection: + # Sort by skin priority and ID + selection_list.sort(key=skin_sort_key) + if (skin_name in skin_layer_list): + skin_tool.manage_skinLayers(skinpath=selection_list, + skinname=skin_name, add_skin=1) + skin_tool.getPortalObject().changeSkin(None) + +def createSkinSelection(skin_tool, skin_name): + # This skin selection does not exist, so we create a new one. + # We'll initialize it with all skin folders, unless: + # - they explictly define a list of + # "business_template_registered_skin_selections", and we + # are not in this list. + # - they are not registered in the default skin selection + skin_path = '' + for skin_folder in skin_tool.objectValues(): + if skin_name in skin_folder.getProperty( + 'business_template_registered_skin_selections', + (skin_name, )): + if skin_folder.getId() in \ + skin_tool.getSkinPath(skin_tool.getDefaultSkin()): + if skin_path: + skin_path = '%s,%s' % (skin_path, skin_folder.getId()) + else: + skin_path= skin_folder.getId() + # add newly created skins to list of skins we care for + skin_tool.addSkinSelection(skin_name, skin_path) + skin_tool.getPortalObject().changeSkin(None) + +def deleteSkinSelection(skin_tool, skin_name): + # Do not delete default skin + if skin_tool.getDefaultSkin() != skin_name: + for skin_folder in skin_tool.objectValues(): + try: + if skin_name in skin_folder.getProperty( + 'business_template_registered_skin_selections', ()): + break + except AttributeError: + pass + else: + skin_tool.manage_skinLayers(chosen=[skin_name], del_skin=1) + skin_tool.getPortalObject().changeSkin(None) + +def unregisterSkinFolderId(skin_tool, skin_folder_id, skin_selection_list): + for skin_selection in skin_selection_list: + selection = skin_tool.getSkinPath(skin_selection) + selection = selection.split(',') + if (skin_folder_id in selection): + selection.remove(skin_folder_id) + skin_tool.manage_skinLayers(skinpath=tuple(selection), + skinname=skin_selection, add_skin=1) + deleteSkinSelection(skin_tool, skin_selection) + skin_tool.getPortalObject().changeSkin(None) diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index d6fd92b5b65..dee59ef228f 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -33,17 +33,22 @@ from App.config import getConfiguration import os import shutil import sys +import hashlib +import pprint +import transaction from Acquisition import Implicit, Explicit from AccessControl import ClassSecurityInfo from AccessControl.SecurityInfo import ModuleSecurityInfo from Products.CMFActivity.ActiveResult import ActiveResult +from Products.PythonScripts.PythonScript import PythonScript from Products.ERP5Type.Globals import InitializeClass, DTMLFile, PersistentMapping from Products.ERP5Type.DiffUtils import DiffFile from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Cache import transactional_cached -from Products.ERP5Type import Permissions +from Products.ERP5Type import Permissions, interfaces from Products.ERP5.Document.BusinessTemplate import BusinessTemplateMissingDependency +from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter from Products.ERP5.genbt5list import generateInformation from Acquisition import aq_base from tempfile import mkstemp, mkdtemp @@ -60,6 +65,8 @@ from base64 import b64encode, b64decode from Products.ERP5Type.Message import translateString from zLOG import LOG, INFO, WARNING from base64 import decodestring +from difflib import unified_diff +from operator import attrgetter import subprocess import time @@ -101,7 +108,10 @@ class TemplateTool (BaseTool): title = 'Template Tool' meta_type = 'ERP5 Template Tool' portal_type = 'Template Tool' - allowed_types = ('ERP5 Business Template', ) + allowed_types = ( + 'ERP5 Business Template', + 'ERP5 Business Manager', + ) # This stores information on repositories. repository_dict = {} @@ -128,7 +138,14 @@ class TemplateTool (BaseTool): # potential danger because business templates may exchange catalog # methods, so the database could be broken temporarily. last_bt = last_time = None - for bt in self.objectValues(portal_type='Business Template'): + for bt in self.objectValues(portal_type=['Business Template', + 'Business Manager']): + if bt.getPortalType() == 'Business Manager': + if bt.getInstallationState() == 'installed' and bt.title == title: + return bt + else: + continue + return None if bt.getTitle() == title or title in bt.getProvisionList(): state = bt.getInstallationState() if state == 'installed': @@ -149,6 +166,22 @@ class TemplateTool (BaseTool): last_time = t return last_bt + security.declareProtected(Permissions.AccessContentsInformation, + 'getInstalledBusinessManager') + def getInstalledBusinessManager(self, title, strict=False, **kw): + """Returns an installed version of business manager of given title. + + Returns None if business manager is not installed or has been uninstalled. + """ + last_bm = None + for bm in self.objectValues(portal_type='Business Manager'): + if bm.getTitle() == title: + state = bm.getInstallationState() + if state == 'installed': + return bm + + return last_bm + security.declareProtected(Permissions.AccessContentsInformation, 'getInstalledBusinessTemplatesList') def getInstalledBusinessTemplatesList(self): @@ -161,7 +194,8 @@ class TemplateTool (BaseTool): """Get the list of installed business templates. """ installed_bts = [] - for bt in self.contentValues(portal_type='Business Template'): + for bt in self.contentValues(portal_type=['Business Template', + 'Business Manager']): if bt.getInstallationState() == 'installed': bt5 = bt if only_title: @@ -276,7 +310,7 @@ class TemplateTool (BaseTool): if RESPONSE is not None: RESPONSE.setHeader('Content-type','tar/x-gzip') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s-%s.bt5' - % (business_template.getTitle(), business_template.getVersion())) + % (business_template.getTitle(), business_template.getVersion())) return export_string.getvalue() finally: export_string.close() @@ -306,6 +340,7 @@ class TemplateTool (BaseTool): self.deleteContent(id) self._importObjectFromFile(StringIO(export_string), id=id) + security.declareProtected( Permissions.ManagePortal, 'manage_download' ) def manage_download(self, url, id=None, REQUEST=None): """The management interface for download. @@ -321,9 +356,14 @@ class TemplateTool (BaseTool): REQUEST.RESPONSE.redirect("%s?portal_status_message=%s" % (ret_url, psm)) - def _download_local(self, path, bt_id): + def _download_local(self, path, bt_id, format_version=1): """Download Business Template from local directory or file """ + if format_version == 3: + bm = self.newContent(bt_id, 'Business Manager') + bm.importFile(path) + return bm + bt = self.newContent(bt_id, 'Business Template') bt.importFile(path) return bt @@ -361,12 +401,17 @@ class TemplateTool (BaseTool): # come from the management interface. if REQUEST is not None: return self.manage_download(url, id=id, REQUEST=REQUEST) - if id is None: id = self.generateNewId() - urltype, path = splittype(url) - if WIN and urltype and '\\' in path: + urltype, name = splittype(url) + # Create a zexp path which would be used for Business Manager files + zexp_path = name + '/' + name.split('/')[-1] + '.zexp' + # Better to expand path as we now use ~software_release for the software + # folder + zexp_path = os.path.expanduser(zexp_path) + + if WIN and urltype and '\\' in name: urltype = None path = url if urltype and urltype != 'file': @@ -377,9 +422,28 @@ class TemplateTool (BaseTool): del bt.uid return self[self._setObject(id, bt)] bt = self._download_url(url, id) + elif os.path.exists(zexp_path): + # If the path exists, we create a Business Manager object after + # downloading it from zexp path + bt = self._download_local(os.path.normpath(zexp_path), id, format_version=3) else: - path = os.path.normpath(os.path.expanduser(path)) - bt = self._download_local(path, id) + template_version_path_list = [ + name+'/bt/template_format_version', + ] + + for path in template_version_path_list: + try: + file = open(os.path.normpath(path)) + except IOError: + continue + try: + format_version = int(file.read()) + file.close() + except UnboundLocalError: + # In case none of the above paths do have template_format_version + format_version = 1 + # XXX: Download only needed in case the file is in directory + bt = self._download_local(os.path.expanduser(os.path.normpath(name)), id, format_version) bt.build(no_action=True) return bt @@ -534,6 +598,413 @@ class TemplateTool (BaseTool): self.activate(activity='SQLQueue').\ importAndReExportBusinessTemplateFromPath(template_path) + security.declareProtected( 'Import/Export objects', 'migrateBTToBM') + def migrateBTToBM(self, template_path, isReduced=False, REQUEST=None, **kw): + """ + Migrate business template repository to Business Manager repo. + Business Manager completely rely only on BusinessItem and to show + the difference between both of them + + So, the steps should be: + 1. Install the business template which is going to be migrated + 2. Create a new Business Manager with random id and title + 3. Add the path, build and export the template + 4. Remove the business template from the directory and add the business + manager there instead + 5. Change the ID and title of the business manager + 6. Export the business manager to the directory, leaving anything in + the installed erp5 unchanged + """ + import_template = self.download(url=template_path) + if import_template.getPortalType() == 'Business Manager': + LOG(import_template.getTitle(),0,'Already migrated') + return + + export_dir = mkdtemp() + + removable_property = {} + removable_sub_object_path = [] + + installed_bt_list = self.getInstalledBusinessTemplatesList() + installed_bt_title_list = [bt.title for bt in installed_bt_list] + + is_installed = False + if import_template.getTitle() not in installed_bt_title_list: + # Install the business template + import_template.install(**kw) + is_installed = True + + # Make list of object paths which needs to be added in the bm5 + # This can be decided by looping over every type of items we do have in + # bt5 and checking if there have been any changes being made to it via this + # bt5 installation or not. + # For ex: + # CatalogTempalteMethodItem, CatalogResultsKeyItem, etc. do make some + # changes in erp5_mysql_innodb(by adding properties, by adding sub-objects), + # so we need to add portal_catalog/erp5_mysql_innodb everytime we find + # a bt5 making changes in any of these items. + + portal_path = self.getPortalObject() + template_path_list = [] + property_path_list = [] + + # For modules, we don't need to create path for the module + module_list = import_template.getTemplateModuleIdList() + for path in module_list: + template_path_list.append(path) + + # For portal_types, we have to add path and subobjects + portal_type_id_list = import_template.getTemplatePortalTypeIdList() + portal_type_path_list = [] + portal_type_workflow_chain_path_list = [] + for id in portal_type_id_list: + portal_type_path_list.append('portal_types/'+id) + # Add type_worklow list separately in path + portal_type_workflow_chain_path_list.append('portal_types/'+id+'#type_workflow_list') + # Remove type_workflow_list from the objects, so that we don't end up in + # conflict + portal_type_path = 'portal_types/' + id + removable_property[portal_type_path] = ['type_workflow_list'] + template_path_list.extend(portal_type_path_list) + template_path_list.extend(portal_type_workflow_chain_path_list) + + # For categories, we create path for category objects as well as the subcategories + category_list = import_template.getTemplateBaseCategoryList() + category_path_list = [] + for base_category in category_list: + category_path_list.append('portal_categories/'+base_category) + #category_path_list.append('portal_categories/'+base_category+'/**') + template_path_list.extend(category_path_list) + + # Adding tools + template_tool_id_list = import_template.getTemplateToolIdList() + tool_id_list = [] + for tool_id in template_tool_id_list: + tool_id_list.append(tool_id) + template_path_list.extend(tool_id_list) + + # Adding business template skin selection property on the portal_tempaltes + template_skin_selection_list = import_template.getTemplateRegisteredSkinSelectionList() + selection_list = [] + for selection in template_skin_selection_list: + skin, selection = selection.split(' | ') + selection_list.append('portal_skins/%s#business_template_registered_skin_selections'%skin) + + # For portal_skins, we export the folder + portal_skin_list = import_template.getTemplateSkinIdList() + portal_skin_path_list = [] + for skin in portal_skin_list: + portal_skin_path_list.append('portal_skins/'+skin) + #portal_skin_path_list.append('portal_skins/'+skin+'/**') + template_path_list.extend(portal_skin_path_list) + + # For workflow chains, + # We have 2 objects in the Business Template design where we deal with + # workflow objects, we deal with the installation separately: + # 1. Workflow_id : We export the whole workflow objects in this case + # 2. Portal Workflow chain: It is already being exported via portal_types + # XXX: CHECK For 2, keeping in mind the migration of workflow would be merged + # before this part where we make workflow_list as property of portal_type + workflow_id_list = import_template.getTemplateWorkflowIdList() + workflow_path_list = [] + for workflow in workflow_id_list: + workflow_path_list.append('portal_workflow/' + workflow) + #workflow_path_list.append('portal_workflow/' + workflow + '/**') + template_path_list.extend(workflow_path_list) + + # For tests in portal components add them with portal_components head + test_id_list = import_template.getTemplateTestIdList() + test_path_list = [] + for path in test_id_list: + test_path_list.append('portal_components/' + path) + template_path_list.extend(test_path_list) + + # For documents in portal components add them with portal_components head + document_id_list = import_template.getTemplateDocumentIdList() + document_path_list = [] + for path in document_id_list: + document_path_list.append('portal_components/' + path) + template_path_list.extend(document_path_list) + + # For extensions in portal components add them with portal_components head + extension_id_list = import_template.getTemplateExtensionIdList() + extension_path_list = [] + for path in extension_id_list: + extension_path_list.append('portal_components/' + path) + template_path_list.extend(extension_path_list) + + # For paths, we add them directly to the path list + path_list = import_template.getTemplatePathList() + for path in path_list: + template_path_list.append(path) + + # Catalog methods would be added as sub objects + catalog_method_item_list = import_template.getTemplateCatalogMethodIdList() + catalog_method_path_list = [] + for method in catalog_method_item_list: + catalog_method_path_list.append('portal_catalog/' + method) + template_path_list.extend(catalog_method_path_list) + + # For catalog objects, we check if there is any catalog object, and then + # add catalog object also in the path if there is + template_catalog_datetime_key = import_template.getTemplateCatalogDatetimeKeyList() + template_catalog_full_text_key = import_template.getTemplateCatalogFullTextKeyList() + template_catalog_keyword_key = import_template.getTemplateCatalogKeywordKeyList() + template_catalog_local_role_key = import_template.getTemplateCatalogLocalRoleKeyList() + template_catalog_multivalue_key = import_template.getTemplateCatalogMultivalueKeyList() + template_catalog_related_key = import_template.getTemplateCatalogRelatedKeyList() + template_catalog_request_key = import_template.getTemplateCatalogRequestKeyList() + template_catalog_result_key = import_template.getTemplateCatalogResultKeyList() + template_catalog_result_table = import_template.getTemplateCatalogResultTableList() + template_catalog_role_key = import_template.getTemplateCatalogRoleKeyList() + template_catalog_scriptable_key = import_template.getTemplateCatalogScriptableKeyList() + template_catalog_search_key = import_template.getTemplateCatalogSearchKeyList() + template_catalog_security_uid_column = import_template.getTemplateCatalogSecurityUidColumnList() + template_catalog_topic_key = import_template.getTemplateCatalogTopicKeyList() + + catalog_property_list = [ + template_catalog_datetime_key, + template_catalog_full_text_key, + template_catalog_keyword_key, + template_catalog_local_role_key, + template_catalog_multivalue_key, + template_catalog_related_key, + template_catalog_request_key, + template_catalog_result_key, + template_catalog_result_table, + template_catalog_role_key, + template_catalog_scriptable_key, + template_catalog_search_key, + template_catalog_security_uid_column, + template_catalog_topic_key, + ] + is_property_added = any(catalog_property_list) + + properties_removed = [ + 'sql_catalog_datetime_search_keys_list', + 'sql_catalog_full_text_search_keys_list', + 'sql_catalog_keyword_search_keys_list', + 'sql_catalog_local_role_keys_list', + 'sql_catalog_multivalue_keys_list', + 'sql_catalog_related_keys_list', + 'sql_catalog_request_keys_list', + 'sql_search_result_keys_list', + 'sql_search_tables_list', + 'sql_catalog_role_keys_list', + 'sql_catalog_scriptable_keys_list', + 'sql_catalog_search_keys_list', + 'sql_catalog_security_uid_columns_list', + 'sql_catalog_topic_search_keys_list', + ] + + if is_property_added: + if catalog_method_path_list: + catalog_path = catalog_method_path_list[0].rsplit('/', 1)[0] + else: + catalog_path = 'portal_catalog/erp5_mysql_innodb' + removable_sub_object_path.append(catalog_path) + removable_property[catalog_path] = properties_removed + for prop in properties_removed: + property_path_list.append('%s#%s' % (catalog_path, prop)) + + # Add these catalog items in the object_property instead of adding + # dummy path item for them + if import_template.getTitle() == 'erp5_mysql_innodb_catalog': + template_path_list.append('portal_catalog/erp5_mysql_innodb') + + # Add portal_property_sheets + property_sheet_id_list = import_template.getTemplatePropertySheetIdList() + property_sheet_path_list = [] + for property_sheet in property_sheet_id_list: + property_sheet_path_list.append('portal_property_sheets/' + property_sheet) + #property_sheet_path_list.append('portal_property_sheets/' + property_sheet + '/**') + template_path_list.extend(property_sheet_path_list) + + # Create new objects for business manager + migrated_bm = self.newContent( + portal_type='Business Manager', + title=import_template.getTitle() + ) + + template_path_list.extend(property_path_list) + template_path_list.extend(selection_list) + template_path_list = self.cleanTemplatePathList(template_path_list) + + # XXX: Add layer=1 and sign=1 for default for all paths + template_path_list = [l + ' | 1 | 1' for l in template_path_list] + + def reduceDependencyList(bt, template_path_list): + """ + Used for recursive udpation of layer for dependency in a BT + """ + dependency_list = bt.getDependencyList() + # XXX: Do not return template_path_list of the new BM incase there is no + # dependency_list, instead look for the latest updated version of + # new_template_path_list + if not dependency_list: + return template_path_list + else: + # Copy of the initial template list to be used to update the layer + new_template_path_list = list(template_path_list) + for item in dependency_list: + dependency = item.split(' ', 1) + if len(dependency) > 1: + version = dependency[1] + if version: + version = version[1:-1] + base_bt = self.getLastestBTOnRepos(dependency[0], version) + else: + try: + base_bt = self.getLastestBTOnRepos(dependency[0]) + except BusinessTemplateIsMeta: + bt_list = self.getProviderList(dependency[0]) + # We explicilty use the Business Template which is used the most + # while dealing with provision list + repository_list = self.getRepositoryList() + if dependency[0] == 'erp5_full_text_catalog': + base_bt = [repository_list[1], 'erp5_full_text_mroonga_catalog'] + if dependency[0] == 'erp5_view_style': + base_bt = [repository_list[0], 'erp5_xhtml_style'] + if dependency[0] == 'erp5_catalog': + base_bt = [repository_list[0], 'erp5_mysql_innodb_catalog'] + # XXX: Create path for the BT(s) here + + # Download the base_bt + base_bt_path = os.path.join(base_bt[0], base_bt[1]) + base_bt = self.download(base_bt_path) + + # Check for the item list and if the BT is Business Manager, + # if BM, then compare and update layer and if not run migration and + # then do it again + if base_bt.getPortalType() != 'Business Manager': + # If the base_bt is not Business Manager, run the migration on the + # base_bt + base_bt = self.migrateBTToBM(base_bt_path, isReduced=True) + + # Check for item path which also exists in base_bt + base_path_list = base_bt.getPathList() + + copy_of_template_path_list = new_template_path_list[:] + # Loop through all the paths in the new_template_path_list and + # check for their existence in base_path_list + for idx, path in enumerate(new_template_path_list): + path_list = path.split(' | ') + item_path = path_list[0] + item_layer = path_list[2] + if item_path in base_path_list: + # TODO: Increase the layer of the path item by +1 and save it + # back at updated_template_path_list + item_layer = int(item_layer) + 1 + updated_path = item_path + ' | 1 | ' + str(item_layer) + copy_of_template_path_list[idx] = updated_path + new_template_path_list = copy_of_template_path_list + + if base_bt.getPortalType() != 'Business Manager': + # Recursively reduce the base Business Templatem no need to do + # this for Business Manager(s) as it had already been migrated + # with taking care of layer + reduceDependencyList(base_bt, new_template_path_list) + + return new_template_path_list + + # Take care about the the dependency_list also and then update the layer + # accordingly for the path(s) that already exists in the dependencies. + template_path_list = reduceDependencyList(import_template, template_path_list) + + # Create new sub-objects instead based on template_path_list + for path in template_path_list: + + path_list = path.split(' | ') + + # Create Business Property Item for objects with property in path + if '#' in path_list[0]: + migrated_bm.newContent( + portal_type='Business Property Item', + item_path=path_list[0], + item_sign=path_list[1], + item_layer=path_list[2], + ) + else: + migrated_bm.newContent( + portal_type='Business Item', + item_path=path_list[0], + item_sign=path_list[1], + item_layer=path_list[2], + ) + + kw['removable_property'] = removable_property + kw['removable_sub_object_path'] = removable_sub_object_path + migrated_bm.build(**kw) + + # Commit transaction to generate all oids before exporting + transaction.commit() + # Export the newly built business manager to the export directory + migrated_bm.export(path=export_dir, local=True) + + if is_installed: + import_template.uninstall() + + if isReduced: + return migrated_bm + + def cleanTemplatePathList(self, path_list): + """ + Remove redundant paths and sub-objects' path if the object path already + exist. + """ + # Split path into list + a2 = [l.split('/') for l in path_list] + # Create new list for paths with ** + a3 = [l for l in a2 if l[-1] in ('**', '*')] + # Create new list for paths without ** + a4 = [l for l in a2 if l[-1] not in ('**', '*')] + # Remove ** from paths in a3 + reserved_id = ('portal_transforms', 'portal_ids') + a3 = [l[:-1] for l in a3 if l[0] not in reserved_id] + [l for l in a3 if l[0] in reserved_id] + # Create new final path list + a2 = a3+a4 + # Join the path list + a2 = [('/').join(l) for l in a2] + # Remove the redundant paths + seen = set() + seen_add = seen.add + # XXX: What about redundant signs with different layers + # Maybe we will end up reducing them + a2 = [x for x in a2 if not (x in seen or seen_add(x))] + + return a2 + + security.declareProtected( 'Import/Export objects', 'migrateBTListToBM') + def migrateBTToBMRequest(self, bt_title_list, REQUEST=None, **kw): + """ + Run migration for BT5 one by one in a given repository. This will be done + via activities. + """ + if REQUEST is None: + REQUEST = getattr(self, 'REQUEST', None) + + if len(bt_title_list) == 0 and REQUEST: + ret_url = self.absolute_url() + REQUEST.RESPONSE.redirect("%s?portal_status_message=%s" + % (ret_url, 'No BT title was given')) + + repository_list = self.getRepositoryList() + for title in bt_title_list: + title = title.rstrip('\n') + title = title.rstrip('\r') + for repository in repository_list: + if title in os.listdir(repository): + template_path = os.path.join(repository, title) + else: + continue + if os.path.isfile(template_path): + LOG(title, 0, 'is file, so it is skipped') + else: + if not os.path.exists((os.path.join(template_path, 'bt'))): + LOG(title, 0, 'has no bt sub-folder, so it is skipped') + else: + self.migrateBTToBM(template_path) + security.declareProtected(Permissions.ManagePortal, 'getFilteredDiff') def getFilteredDiff(self, diff): """ @@ -676,6 +1147,212 @@ class TemplateTool (BaseTool): finally: f.close() + #XXX: Hardcoding 'erp5_core_proxy_field_legacy' BP in the list + bp_dict_1 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_core_proxy_field_legacy', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_core_proxy_field_legacy', + 'version': '5.4.7'} + + bp_dict_2 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_base',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_pdm', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_pdm', + 'version': '5.4.7'} + + bp_dict_3 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_ui_test',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_performance_test', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_performance_test', + 'version': '5.4.7'} + + bp_dict_4 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_ui_test_core',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_ui_test', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_ui_test', + 'version': '5.4.7'} + + bp_dict_5 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_core', 'erp5_xhtml_style',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_ui_test_core', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_ui_test_core', + 'version': '5.4.7'} + + bp_dict_6 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_core (>= 1.0rc12)', + 'erp5_full_text_catalog', + 'erp5_core_proxy_field_legacy',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_base', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_base', + 'version': '5.4.7'} + + bp_dict_7 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_full_text_mroonga_catalog', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': ['erp5_full_text_catalog'], + 'title': 'erp5_full_text_mroonga_catalog', + 'version': '5.4.7'} + + if repository.endswith('/bt5'): + property_dict_list.append(bp_dict_1) + property_dict_list.append(bp_dict_2) + property_dict_list.append(bp_dict_3) + #property_dict_list.append(bp_dict_4) + property_dict_list.append(bp_dict_5) + property_dict_list.append(bp_dict_6) + property_dict_list.append(bp_dict_7) + + bm_dict_1 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_mysql_innodb_catalog', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': ['erp5_catalog'], + 'title': 'erp5_mysql_innodb_catalog', + 'version': '5.4.7'} + + bm_dict_2 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_core'], + 'description': '', + 'force_install': 0, + 'id': 'erp5_xhtml_style', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': ['erp5_view_style'], + 'title': 'erp5_xhtml_style', + 'version': '5.4.7'} + + bm_dict_3 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_mysql_ndb_catalog', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': ['erp5_catalog'], + 'title': 'erp5_mysql_ndb_catalog', + 'version': '5.4.7'} + + bm_dict_4 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_view_style',], + 'description': '', + 'force_install': 0, + 'id': 'erp5_jquery', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_jquery', + 'version': '5.4.7'} + + bm_dict_5 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_property_sheets', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_property_sheets', + 'version': '5.4.7'} + + bm_dict_6 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': ['erp5_catalog (>= 1.1)', + 'erp5_core_proxy_field_legacy', + 'erp5_property_sheets'], + 'description': '', + 'force_install': 0, + 'id': 'erp5_core', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': ['erp5_full_text_mroonga_catalog', + 'erp5_base'], + 'provision_list': ['erp5_auto_logout',], + 'title': 'erp5_core', + 'version': '5.4.7'} + + bm_dict_7 ={ + 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], + 'dependency_list': [], + 'description': '', + 'force_install': 0, + 'id': 'erp5_business_package', + 'license': 'GPL', + 'revision': '', + 'test_dependency_list': [], + 'provision_list': [], + 'title': 'erp5_business_package', + 'version': '5.4.7'} + + if repository.endswith('/bootstrap'): + property_dict_list.append(bm_dict_1) + #property_dict_list.append(bm_dict_2) + property_dict_list.append(bm_dict_3) + property_dict_list.append(bm_dict_4) + property_dict_list.append(bm_dict_5) + property_dict_list.append(bm_dict_6) + #property_dict_list.append(bm_dict_7) + self.repository_dict[repository] = tuple(property_dict_list) if REQUEST is not None: @@ -1141,10 +1818,8 @@ class TemplateTool (BaseTool): template_title_list, with_test_dependency_list=False): available_bt5_list = self.getRepositoryBusinessTemplateList() - template_title_list = set(template_title_list) installed_bt5_title_list = self.getInstalledBusinessTemplateTitleList() - bt5_set = set() for available_bt5 in available_bt5_list: if available_bt5.title in template_title_list: @@ -1341,8 +2016,11 @@ class TemplateTool (BaseTool): if update_catalog is CATALOG_UPDATABLE and install_kw != {}: update_catalog = imported_bt5.isCatalogUpdatable() - imported_bt5.install(object_to_update=install_kw, - update_catalog=update_catalog) + if imported_bt5.getPortalType() == 'Business Manager': + self.updateInstallationState([imported_bt5]) + else: + imported_bt5.install(object_to_update=install_kw, + update_catalog=update_catalog) # Run After script list for after_triggered_bt5_id in after_triggered_bt5_id_list: @@ -1357,6 +2035,598 @@ class TemplateTool (BaseTool): return imported_bt5 + security.declareProtected(Permissions.ManagePortal, + 'installBusinessManager') + def installBusinessManager(self, bm): + """ + Run installation on flattened Business Manager + """ + # Run install on separate Business Item one by one + for path_item in bm._path_item_list: + path_item.install(self) + + bm.setStatus('installed') + + def updateHash(self, item): + """ + Function to update hash of Business Item or Business Property Item + """ + # Check for isProperty attribute + if item.isProperty: + value = item.getProperty('item_property_value') + else: + value_list = item.objectValues() + if value_list: + value = value_list[0] + else: + value = '' + + if value: + item.setProperty('item_sha', self.calculateComparableHash( + value, + item.isProperty, + )) + + def rebuildBusinessManager(self, bm): + """ + Compare the sub-objects in the Business Manager to the previous built + state to give user powet to decide on which item to rebuild. + """ + checkNeeded = True + changed_path_list = [] + + if bm.getBuildingState() not in ['built', 'modified']: + # In case the building_state is not built, we build the BM without + # comparing anything + checkNeeded = False + return checkNeeded, changed_path_list + + portal = self.getPortalObject() + for item in bm.objectValues(): + # Check for the change compared to old building state, i.e, if there is + # some change made at ZODB state(it also count changes made due to + # change while installation of other BM) + path = item.getProperty('item_path') + + try: + if item.isProperty: + # Get the value for Business Property Item + value = item.getProperty('item_property_value') + # Get the value at ZODB + relative_url, property_id = path.split('#') + obj = portal.restrictedTraverse(relative_url) + property_value = obj.getProperty(property_id) + + # If the value at ZODB for the property is none, raise KeyError + # This is important to have compatibility between the way we check + # path as well as property. Otherwise, if we install a new property, + # we are always be getting an Error that there is change made at + # ZODB for this property + if not property_value: + raise KeyError + + obj = property_value + else: + # Get the value of the Business Path Item + value_list = item.objectValues() + if value_list: + value = value_list[0] + else: + # If there is no value, it means the path_item is new, thus no + # need to comapre hash and check anything + changed_path_list.append((path, 'New')) + continue + + # Get the object at ZODB + obj = portal.restrictedTraverse(path) + + # Calculate hash for value at ZODB + obj_sha = self.calculateComparableHash(obj, item.isProperty) + + # Update hash for value at property_value + self.updateHash(item) + item_sha = item.getProperty('item_sha') + + # Compare the hash with the item hash + if obj_sha != item_sha: + changed_path_list.append((path, 'Changed')) + else: + changed_path_list.append((path, 'Unchanged')) + + # KeyError is raised in case the value/object has been deleted at ZODB + except KeyError: + changed_path_list.append((path, 'Deleted')) + + return checkNeeded, changed_path_list + + security.declareProtected(Permissions.ManagePortal, + 'updateInstallationState') + def compareInstallationState(self, bm_list): + """ + Run installation after comparing combined Business Manager status + + Steps: + 1. Create combinedBM for the bm_list + 2. Get the old combinedBM by checking the 'installed' status for it or + by checking timestamp (?? which is better) + CombinedBM: Collection of all Business item(s) whether installed or + uninstalled + 3. Build BM from the filesystem + 4. Compare the combinedBM state to the last combinedBM state + 5. Compare the installation state to the OFS state + 6. If conflict while comaprison at 3, raise the error + 7. In all other case, install the BM List + """ + + # Create old installation state from Installed Business Manager + installed_bm_list = self.getInstalledBusinessManagerList() + combined_installed_path_item = [item for bm + in installed_bm_list + for item in bm.objectValues()] + + # Create BM for old installation state and update its path item list + old_installation_state = self.newContent( + portal_type='Business Manager', + title='Old Installation State', + temp_object=True, + ) + + for item in combined_installed_path_item: + item.isIndexable = ConstantGetter('isIndexable', value=False) + # Better to use new ids so that we don't end up in conflicts + new_id = old_installation_state.generateNewId() + old_installation_state._setObject(new_id, aq_base(item), + suppress_events=True) + + forbidden_bm_title_list = ['Old Installation State',] + for bm in bm_list: + forbidden_bm_title_list.append(bm.title) + + new_installed_bm_list = [l for l + in self.getInstalledBusinessManagerList() + if l.title not in forbidden_bm_title_list] + new_installed_bm_list.extend(bm_list) + + combined_new_path_item = [item for bm + in new_installed_bm_list + for item in bm.objectValues()] + + # Create BM for new installation state and update its path item list + new_installation_state = self.newContent( + portal_type='Business Manager', + title='New Installation State', + temp_object=True, + ) + + for item in combined_new_path_item: + item.isIndexable = ConstantGetter('isIndexable', value=False) + new_id = new_installation_state.generateNewId() + new_installation_state._setObject(new_id, aq_base(item), + suppress_events=True) + + # Create installation process, which have the changes to be made in the + # OFS during installation. Importantly, it should also be a Business Manager + installation_process = self.newContent( + portal_type='Business Manager', + title='Installation Process', + temp_object=True, + ) + + # Reduce both old and new Installation State + old_installation_state.reduceBusinessManager() + new_installation_state.reduceBusinessManager() + + # Get path list for old and new states + old_state_path_list = old_installation_state.getPathList() + new_state_path_list = new_installation_state.getPathList() + + to_install_path_item_list = [] + + # Get the path which has been removed in new installation_state + removed_path_list = [path for path + in old_state_path_list + if path not in new_state_path_list] + + # Add the removed path with negative sign in the to_install_path_item_list + for path in removed_path_list: + old_item = old_installation_state.getBusinessItemByPath(path) + old_item.setProperty('item_sign', '-1') + to_install_path_item_list.append(old_item) + + # Reduce old_installation_state again as changes as new sub-objects maybe + # added to the old_installation_state + old_installation_state.reduceBusinessManager() + + # XXX: At this point, we expect all the Business Manager objects as 'reduced', + # thus all the BusinessItem sub-objects should have single value + # Update hashes of item in old state before installation + for item in old_installation_state.objectValues(): + + # In case of Business Patch Item we need to update hash of both new and + # old value + if item.getPortalType() == 'Business Patch Item': + new_val = item._getOb('new_item') + old_val = item._getOb('old_item') + self.updateHash(new_val) + self.updateHash(old_val) + else: + self.updateHash(item) + + # Path Item List for installation_process should be the difference between + # old and new installation state + for item in new_installation_state.objectValues(): + + # If the path has been removed, then add it with sign = -1 + old_item = old_installation_state.getBusinessItemByPath(item.getProperty('item_path')) + + # In case of Business Patch Item we need to update hash of both new and + # old value + if item.getPortalType() == 'Business Patch Item': + new_val = item._getOb('new_item') + old_val = item._getOb('old_item') + self.updateHash(new_val) + self.updateHash(old_val) + else: + self.updateHash(item) + + if old_item: + to_be_installed_item = item + if old_item.getPortalType() == 'Business Patch Item': + # In case of Business Patch Item, we just need to compare the hash + # of old_item + old_item = old_item._getOb('old_item') + item = item._getOb('old_item') + + # If the old_item exists, we match the hashes and if it differs, then + # add the new item + if old_item.getProperty('item_sha') != item.getProperty('item_sha'): + to_install_path_item_list.append(to_be_installed_item) + + else: + to_install_path_item_list.append(item) + + for item in to_install_path_item_list: + item.isIndexable = ConstantGetter('isIndexable', value=False) + new_id = new_installation_state.generateNewId() + installation_process._setObject(new_id, aq_base(item), + suppress_events=True) + + change_list = self.compareOldStateToOFS(installation_process, old_installation_state) + + if change_list: + change_list = [(l[0].item_path, l[1]) for l in change_list] + + return change_list + + def updateInstallationState(self, bm_list, force=1): + """ + First compare installation state and then install the final value + """ + change_list = self.compareInstallationState(bm_list) + + if force: + to_install_path_list = [l[0] for l in change_list] + to_install_path_list = self.sortPathList(to_install_path_list) + + # Install the path items with bm_list as context + self.installBusinessItemList(bm_list, to_install_path_list) + + installMultipleBusinessManager = updateInstallationState + + def installBusinessItemList(self, manager_list, item_path_list): + """ + Install Business Item/Business Property Item from the current Installation + Process given the change_list which carries the list of paths to be + installed + """ + LOG('INFO', 0, '%s' % [item_path_list]) + + # Create BM for new installation state and update its path item list + new_installation_state = self.newContent( + portal_type='Business Manager', + title='Final Installation State', + temp_object=True, + ) + combined_new_path_item_list = [item for bm + in manager_list + for item in bm.objectValues()] + + for item in combined_new_path_item_list: + item.isIndexable = ConstantGetter('isIndexable', value=False) + new_id = new_installation_state.generateNewId() + new_installation_state._setObject(new_id, aq_base(item), + suppress_events=True) + + for path in item_path_list: + item = new_installation_state.getBusinessItemByPath(path) + if item is None: + raise ValueError("Couldn't find path in current Installation State") + item.install(new_installation_state) + + # Update workflow history of the installed Business Manager(s) + # Get the 'business_manager_installation_workflow' as it is already + # bootstrapped and installed + portal_workflow = self.getPortalObject().portal_workflow + wf = portal_workflow._getOb('business_manager_installation_workflow') + + # Change the installation state for all the BM(s) in manager_list. + for manager in manager_list: + wf._executeMetaTransition(manager, 'installed') + + def calculateComparableHash(self, object, isProperty=False): + """ + Remove some attributes before comparing hashses + and return hash of the comparable object dict, in case the object is + an erp5 object. + + Use shallow copy of the dict of the object at ZODB after removing + attributes which changes at small updation, like workflow_history, + uid, volatile attributes(which starts with _v) + + # XXX: Comparable hash shouldn't be used for BusinessPatchItem as whole. + We can compare the old_value and new_value, but there shouldn't be hash + for the Patch Item. + """ + if isProperty: + obj_dict = object + # Have compatibilty between tuples and list while comparing as we face + # this situation a lot especially for list type properties + if isinstance(obj_dict, list): + obj_dict = tuple(obj_dict) + else: + + klass = object.__class__ + classname = klass.__name__ + obj_dict = object.__dict__.copy() + + # If the dict is empty, do calculate hash of None as it stays same on + # one platform and in any case its impossiblt to move live python + # objects from one seeion to another + if not bool(obj_dict): + return hash(None) + + attr_set = {'_dav_writelocks', '_filepath', '_owner', '_related_index', + 'last_id', 'uid', '_mt_index', '_count', '_tree', + '__ac_local_roles__', '__ac_local_roles_group_id_dict__', + 'workflow_history', 'subject_set_uid_dict', 'security_uid_dict', + 'filter_dict', '_max_uid'} + + attr_set.update(('isIndexable',)) + + if classname in ('File', 'Image'): + attr_set.update(('_EtagSupport__etag', 'size')) + elif classname == 'Types Tool' and klass.__module__ == 'erp5.portal_type': + attr_set.add('type_provider_list') + + for attr in object.__dict__.keys(): + if attr in attr_set or attr.startswith('_cache_cookie_') or attr.startswith('_v'): + try: + del obj_dict[attr] + except AttributeError: + # XXX: Continue in cases where we want to delete some properties which + # are not in attribute list + # Raise an error + continue + + # Special case for configuration instance attributes + if attr in ['_config', '_config_metadata']: + import collections + # Order the dictionary so that comparison can be correct + obj_dict[attr] = collections.OrderedDict(sorted(obj_dict[attr].items())) + if 'valid_tags' in obj_dict[attr]: + try: + obj_dict[attr]['valid_tags'] = collections.OrderedDict(sorted(obj_dict[attr]['valid_tags'].items())) + except AttributeError: + # This can occur in case the valid_tag object is PersistentList + pass + + if 'data' in obj_dict: + try: + obj_dict['data'] = obj_dict.get('data').__dict__ + except AttributeError: + pass + + obj_sha = hash(pprint.pformat(obj_dict)) + return obj_sha + + def sortPathList(self, path_list): + """ + Custom sort for path_list according to the priorities of paths + """ + def comparePath(path): + split_path_list = path.split('/') + # Paths with property item should have the least priority as they should + # be installed after installing the object only + if '#' in path: + return 11 + if len(split_path_list) == 2 and split_path_list[0] in ('portal_types', 'portal_categories'): + return 1 + # portal_transforms objects needs portal_components installed first so + # as to register the modules + if len(split_path_list) == 2 and split_path_list[0] == 'portal_transforms': + return 12 + if len(split_path_list) > 2: + return 10 + if len(split_path_list) == 1: + return 2 + return 5 + + return sorted(path_list, key=comparePath) + + def compareOldStateToOFS(self, installation_process, old_state): + + # Get the paths about which we are concerned about + to_update_path_list = installation_process.getPathList() + portal = self.getPortalObject() + + # List to store what changes will be done to which path. Here we compare + # with all the states (old version, new version and state of object at ZODB) + change_list = [] + + to_update_path_list = self.sortPathList(to_update_path_list) + + for path in to_update_path_list: + try: + # Better to check for status of BusinessPatchItem separately as it + # can contain both BusinessItem as well as BusinessPropertyItem + new_item = installation_process.getBusinessItemByPath(path) + if new_item.getPortalType() == 'Business Patch Item': + patch_item = new_item + # If the value is in ZODB, then compare it to the old_value + if '#' in str(path): + isProperty = True + relative_url, property_id = path.split('#') + obj = portal.restrictedTraverse(relative_url) + property_value = obj.getProperty(property_id) + if not property_value: + raise KeyError + property_type = obj.getPropertyType(property_id) + obj = property_value + else: + # If the path is path on an object and not of a property + isProperty = False + obj = portal.restictedTraverse(path) + + obj_sha = self.calculateComparableHash(obj, isProperty) + + # Get the sha of new_item from the BusinessPatchItem object + new_item_sha = patch_item._getOb('new_item').getProperty('item_sha') + old_item_sha = patch_item._getOb('old_item').getProperty('item_sha') + + if new_item_sha == obj_sha: + # If the new_item in the patch is same as the one at ZODB, do + # nothing + continue + elif old_item_sha == obj_sha: + change_list.append((patch_item._getOb('new_item'), 'Adding')) + else: + change_list.append((patch_item._getOb('new_item'), 'Removing')) + + if '#' in str(path): + isProperty = True + relative_url, property_id = path.split('#') + obj = portal.restrictedTraverse(relative_url) + property_value = obj.getProperty(property_id) + + # If the value at ZODB for the property is none, raise KeyError + # This is important to have compatibility between the way we check + # path as well as property. Otherwise, if we install a new property, + # we are always be getting an Error that there is change made at + # ZODB for this property + if not property_value: + raise KeyError + property_type = obj.getPropertyType(property_id) + obj = property_value + else: + isProperty = False + # XXX: Hardcoding because of problem with 'resource' trying to access + # the resource via acqusition. Should be removed completely before + # merging (DONT PUSH THIS) + if path == 'portal_categories/resource': + path_list = path.split('/') + container_path = path_list[:-1] + object_id = path_list[-1] + container = portal.restrictedTraverse(container_path) + obj = container._getOb(object_id) + else: + obj = portal.restrictedTraverse(path) + + obj_sha = self.calculateComparableHash(obj, isProperty) + + # Get item at old state + old_item = old_state.getBusinessItemByPath(path) + # Check if there is an object at old state at this path + + if old_item: + # Compare hash with ZODB + + if old_item.getProperty('item_sha') == obj_sha: + # No change at ZODB on old item, so get the new item + new_item = installation_process.getBusinessItemByPath(path) + # Compare new item hash with ZODB + + if new_item.getProperty('item_sha') == obj_sha: + if int(new_item.getProperty('item_sign')) == -1: + # If the sign is negative, remove the value from the path + change_list.append((new_item, 'Removing')) + else: + # If same hash, and +1 sign, do nothing + continue + + else: + # Install the new_item + change_list.append((new_item, 'Adding')) + + else: + # Change at ZODB, so get the new item + new_item = installation_process.getBusinessItemByPath(path) + # Compare new item hash with ZODB + + if new_item.getProperty('item_sha') == obj_sha: + # If same hash, do nothing + continue + + else: + # Trying to update change at ZODB + change_list.append((new_item, 'Updating')) + + else: + # Object created at ZODB by the user + # Compare with the new_item + + new_item = installation_process.getBusinessItemByPath(path) + if new_item.getProperty('item_sha') == obj_sha: + # If same hash, do nothing + continue + + else: + # Trying to update change at ZODB + change_list.append((new_item, 'Updating')) + + except (AttributeError, KeyError) as e: + # Get item at old state + old_item = old_state.getBusinessItemByPath(path) + # Check if there is an object at old state at this path + + if old_item: + # This means that the user had removed the object at this path + # Check what the sign is for the new_item + new_item = installation_process.getBusinessItemByPath(path) + # Check sign of new_item + + if int(new_item.getProperty('item_sign')) == 1: + # Object at ZODB has been removed by the user + change_list.append((new_item, 'Adding')) + + else: + # If there is no item at old state, install the new_item + new_item = installation_process.getBusinessItemByPath(path) + # XXX: Hack for not trying to install the sub-objects from zexp, + # This should rather be implemented while exporting the object, + # where we shouldn't export sub-objects in the zexp + if not isProperty: + try: + value = new_item.objectValues()[0] + except IndexError: + continue + # Installing a new item + change_list.append((new_item, 'Adding')) + + return change_list + + def getInstalledBusinessManagerList(self): + bm_list = self.objectValues(portal_type='Business Manager') + installed_bm_list = [bm for bm in bm_list + if bm.getInstallationState() == 'installed'] + return installed_bm_list + + def getInstalledBusinessManagerTitleList(self): + installed_bm_list = self.getInstalledBusinessManagerList() + if not len(installed_bm_list): + return [] + installed_bm_title_list = [bm.title for bm in installed_bm_list] + return installed_bm_title_list + security.declareProtected(Permissions.ManagePortal, 'getBusinessTemplateUrl') def getBusinessTemplateUrl(self, base_url_list, bt5_title): diff --git a/product/ERP5/__init__.py b/product/ERP5/__init__.py index f05d7eb4e5f..eb970b224e0 100644 --- a/product/ERP5/__init__.py +++ b/product/ERP5/__init__.py @@ -54,10 +54,12 @@ from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\ CertificateAuthorityTool, InotifyTool, TaskDistributionTool,\ DiffTool import ERP5Site -from Document import PythonScript, SQLMethod +from Document import PythonScript, BusinessManager object_classes = ( ERP5Site.ERP5Site, PythonScript.PythonScriptThroughZMI, - SQLMethod.SQLMethod, + BusinessManager.BusinessItem, + BusinessManager.BusinessPropertyItem, + BusinessManager.BusinessPatchItem, ) portal_tools = ( CategoryTool.CategoryTool, SimulationTool.SimulationTool, -- 2.30.9 From 5e17819e3376fcbe940b4be1a25232ddab3dfee5 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:25:25 +0100 Subject: [PATCH 06/56] Add _delPropValue which can be used to del property value without deleting the proeprty itself --- product/ERP5Type/Base.py | 7 +++++++ product/ERP5Type/Core/Folder.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 7d2abb58b0b..2affe3e29b3 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -789,6 +789,13 @@ class Base( CopyContainer, pformat(rev1.__dict__), pformat(rev2.__dict__))) + def _delPropValue(self, property_name): + if self.hasProperty(property_name): + value = None + if property_name.endswith('_list'): + value = [] + self._setPropValue(property_name, value) + def _aq_dynamic(self, id): # ahah! disabled, thanks to portal type classes return None diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py index 7ff7b67ec7f..e5214062db9 100644 --- a/product/ERP5Type/Core/Folder.py +++ b/product/ERP5Type/Core/Folder.py @@ -627,6 +627,9 @@ class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn, Title = Base.Title _setPropValue = Base._setPropValue _propertyMap = Base._propertyMap # are there any others XXX ? + # Required while Business Manager installation. Better to rely on properties + # from property sheet than on the attributes + _delPropValue = Base._delPropValue PUT_factory = None # XXX Prevent inheritance from PortalFolderBase description = None -- 2.30.9 From 6343eedaf1ba9fbb56352f3ead07522d3899e0e8 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:26:24 +0100 Subject: [PATCH 07/56] ERP5TypeTestCase: Tests should consider BM while creating ERP5Site --- product/ERP5Type/tests/ERP5TypeTestCase.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py index 9647fc76f9c..f7cb0aed801 100644 --- a/product/ERP5Type/tests/ERP5TypeTestCase.py +++ b/product/ERP5Type/tests/ERP5TypeTestCase.py @@ -1067,6 +1067,12 @@ class ERP5TypeCommandLineTestCase(ERP5TypeTestCaseMixin): if not quiet: ZopeTestCase._print('Adding %s business template ... ' % bt_title) bt = template_tool.download(url) + # If the bt is Business Manager, update the installation state + if bt.getPortalType() == 'Business Manager': + template_tool.updateInstallationState([bt]) + ZopeTestCase._print('(imported in %.3fs) ' % (time.time() - start)) + continue + if not quiet: ZopeTestCase._print('(imported in %.3fs) ' % (time.time() - start)) # For unit test, we accept installing business templates with @@ -1185,6 +1191,7 @@ class ERP5TypeCommandLineTestCase(ERP5TypeTestCaseMixin): self._installBusinessTemplateList(business_template_list, light_install=light_install, quiet=quiet) + self._recreateCatalog() self._updateTranslationTable() self._updateConversionServerConfiguration() @@ -1202,7 +1209,6 @@ class ERP5TypeCommandLineTestCase(ERP5TypeTestCaseMixin): if hot_reindexing: setattr(app,'isIndexable', 1) portal.portal_catalog.manage_hotReindexAll() - portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary() self.tic(not quiet) -- 2.30.9 From 867662e89e65895db9bd7d3ff81a8f4c054cde21 Mon Sep 17 00:00:00 2001 From: Ayush Date: Wed, 22 Nov 2017 16:27:06 +0100 Subject: [PATCH 08/56] Add type_workflow property to ERP5type --- product/ERP5Type/ERP5Type.py | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py index af097c7f984..986d2b36cb6 100644 --- a/product/ERP5Type/ERP5Type.py +++ b/product/ERP5Type/ERP5Type.py @@ -226,6 +226,12 @@ class ERP5TypeInformation(XMLObject, # Declarative properties property_sheets = ( PropertySheet.BaseType, ) + _properties = ( + { 'id' : 'type_workflow', + 'type' : 'multiple selection', + 'mode' : 'w' }, + ) + acquire_local_roles = False property_sheet_list = () base_category_list = () @@ -330,6 +336,72 @@ class ERP5TypeInformation(XMLObject, # Acquisition editing interface # + def getTypeFilterContentType(self): + return + + def getTypeInitScriptId(self): + return + + security.declareProtected(Permissions.ModifyPortalContent, + 'getTypeWorkflowList') + def getTypeWorkflowList(self): + """Getter for 'type_workflow_list' property""" + pw = self.getPortalObject().portal_workflow + cbt = pw._chains_by_type + id = self.getId() + if cbt is not None and cbt.has_key(id): + workflow_list = list(cbt[id]) + else: + workflow_list = None + return workflow_list + + def hasTypeWorkflowList(self): + """ + Always return True. Overriden as general accessor is generated after the + use of this function while installing Business Manager + """ + return True + + security.declareProtected(Permissions.ModifyPortalContent, + 'setTypeWorkflowList') + def _setTypeWorkflowList(self, type_workflow_list): + """Override Setter for 'type_workflow' property""" + # We use setter to update the value for Workflow chain during the + # installation of Business Manager. This way, we would be able to + # modify workflow chain without the need of saving anything in + # type_workflow_list property. + portal = self.getPortalObject() + pw = portal.portal_workflow + cbt = pw._chains_by_type + # Create empty chains dict if it is empty + if cbt is None: + cbt = {} + id = self.getId() + + # If the type_workflow_list is empty, delete the key from workflow chains + if not type_workflow_list: + cbt[id] = [] + pw._chains_by_type = cbt + + # If type_workflow_list is '(Default)', don't do/update anything + elif type_workflow_list[0] != '(Default)': + + # If there is already key existing in cbt, then update it + if cbt is not None and cbt.has_key(id): + workflow_list = list(cbt[id]) + + if not workflow_list: + cbt[id] = sorted(type_workflow_list) + + # If the value in cbt is '(Default)', then update it only after removing + # default value + elif workflow_list[0] != '(Default)': + type_workflow_list = type_workflow_list.extend(workflow_list) + cbt[id] = sorted(type_workflow_list) + + # Update the chains dictionary for portal_workflow + pw._chains_by_type = cbt + security.declarePrivate('_guessMethodAliases') def _guessMethodAliases(self): """ Override this method to disable Method Aliases in ERP5. -- 2.30.9 From f3a1870a834638c7e95125e18b7b7afa5ab785dd Mon Sep 17 00:00:00 2001 From: Ayush Date: Mon, 27 Nov 2017 16:58:42 +0100 Subject: [PATCH 09/56] SQLCatalog: Use isIndexingRequired as ERP5Site object has no longer isIndexable attribute --- product/ERP5/Document/BusinessTemplate.py | 2 +- product/ERP5/ERP5Site.py | 6 +++--- product/ZSQLCatalog/SQLCatalog.py | 17 +++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index 3b9775892da..7e4331eefed 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -5364,7 +5364,7 @@ Business Template is a set of definitions, such as skins, portal types and categ if update_catalog: catalog = _getCatalogValue(self) - if (catalog is None) or (not site.isIndexable): + if (catalog is None) or (not site.isIndexingRequired()): LOG('Business Template', 0, 'no SQL Catalog available') update_catalog = 0 else: diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index c8533dd4ad5..2448d384145 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -237,7 +237,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin): last_id = 0 icon = 'portal.gif' # Default value, prevents error during upgrade - isIndexable = ConstantGetter('isIndexable', value=True) + isIndexingRequired = ConstantGetter('isIndexingRequired', value=True) # There can remain a lot a activities to be processed once all BT5 are # installed, and scalability tests want a reliable way to know when the site # is ready to be tortured. @@ -1893,7 +1893,7 @@ class ERP5Generator(PortalGenerator): portal = self.klass(id=id) # Make sure reindex will not be called until business templates # will be installed - setattr(portal, 'isIndexable', ConstantGetter('isIndexable', value=False)) + setattr(portal, 'isIndexingRequired', ConstantGetter('isIndexingRequired', value=False)) # This is only used to refine log level. # Has no functional use, and should never have any: @@ -2206,7 +2206,7 @@ class ERP5Generator(PortalGenerator): def setupIndex(self, p, **kw): # Make sure all tools and folders have been indexed if kw.get('reindex', 1): - setattr(p, 'isIndexable', ConstantGetter('isIndexable', value=True)) + setattr(p, 'isIndexingRequired', ConstantGetter('isIndexingRequired', value=True)) # Clear portal ids sql table, like this we do not take # ids for a previously created web site p.portal_ids.clearGenerator(all=True) diff --git a/product/ZSQLCatalog/SQLCatalog.py b/product/ZSQLCatalog/SQLCatalog.py index dfef42fdefc..e79f65b4920 100644 --- a/product/ZSQLCatalog/SQLCatalog.py +++ b/product/ZSQLCatalog/SQLCatalog.py @@ -1100,8 +1100,8 @@ class Catalog(Folder, ), ) - security.declarePrivate('isIndexable') - def isIndexable(self): + security.declarePrivate('isIndexingRequired') + def isIndexingRequired(self): """ This is required to check in many methods that the site root and zope root are indexable @@ -1109,8 +1109,8 @@ class Catalog(Folder, zope_root = self.getZopeRoot() site_root = self.getSiteRoot() # XXX-JPS - Why don't we use getPortalObject here ? - root_indexable = int(getattr(zope_root, 'isIndexable', 1)) - site_indexable = int(getattr(site_root, 'isIndexable', 1)) + root_indexable = int(getattr(zope_root, 'isIndexingRequired', 1)) + site_indexable = int(getattr(site_root, 'isIndexingRequired', 1)) if not (root_indexable and site_indexable): return False return True @@ -1157,7 +1157,7 @@ class Catalog(Folder, Similar problems may happen with relations and acquisition of uid values (ex. order_uid) with the risk of graph loops """ - if not self.getPortalObject().isIndexable(): + if not self.getPortalObject().isIndexingRequired(): return None with global_reserved_uid_lock: @@ -1322,7 +1322,8 @@ class Catalog(Folder, if idxs not in (None, []): LOG('ZSLQCatalog.SQLCatalog:catalogObjectList', WARNING, 'idxs is ignored in this function and is only provided to be compatible with CMFCatalogAware.reindexObject.') - if not self.getPortalObject().isIndexable(): + + if not self.getPortalObject().isIndexingRequired(): return object_path_dict = {} @@ -1507,7 +1508,7 @@ class Catalog(Folder, """ Set the path as deleted """ - if not self.getPortalObject().isIndexable(): + if not self.getPortalObject().isIndexingRequired(): return None if uid is None and path is not None: @@ -1540,7 +1541,7 @@ class Catalog(Folder, XXX Add filter of methods """ - if not self.getPortalObject().isIndexable(): + if not self.getPortalObject().isIndexingRequired(): return None if uid is None and path is not None: -- 2.30.9 From 3925dae6e98e8da6487671c700909a3808242128 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Mon, 18 Dec 2017 11:45:16 +0100 Subject: [PATCH 10/56] TemplateTool: Do not add Business Manager in bt5list --- product/ERP5/Tool/TemplateTool.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index dee59ef228f..b5f6ee6efd1 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -1242,13 +1242,14 @@ class TemplateTool (BaseTool): 'version': '5.4.7'} if repository.endswith('/bt5'): - property_dict_list.append(bp_dict_1) - property_dict_list.append(bp_dict_2) - property_dict_list.append(bp_dict_3) + pass + #property_dict_list.append(bp_dict_1) + #property_dict_list.append(bp_dict_2) + #property_dict_list.append(bp_dict_3) #property_dict_list.append(bp_dict_4) - property_dict_list.append(bp_dict_5) - property_dict_list.append(bp_dict_6) - property_dict_list.append(bp_dict_7) + #property_dict_list.append(bp_dict_5) + #property_dict_list.append(bp_dict_6) + #property_dict_list.append(bp_dict_7) bm_dict_1 ={ 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], @@ -1345,12 +1346,13 @@ class TemplateTool (BaseTool): 'version': '5.4.7'} if repository.endswith('/bootstrap'): - property_dict_list.append(bm_dict_1) + pass + #property_dict_list.append(bm_dict_1) #property_dict_list.append(bm_dict_2) - property_dict_list.append(bm_dict_3) - property_dict_list.append(bm_dict_4) - property_dict_list.append(bm_dict_5) - property_dict_list.append(bm_dict_6) + #property_dict_list.append(bm_dict_3) + #property_dict_list.append(bm_dict_4) + #property_dict_list.append(bm_dict_5) + #property_dict_list.append(bm_dict_6) #property_dict_list.append(bm_dict_7) self.repository_dict[repository] = tuple(property_dict_list) -- 2.30.9 From a7a940a67976e9bef0552aa683d957ee35322f44 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Mon, 18 Dec 2017 14:43:29 +0100 Subject: [PATCH 11/56] TemplateTool: SImplify reduction function during migration of BT to BM --- product/ERP5/Tool/TemplateTool.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index b5f6ee6efd1..c299952da4b 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -852,22 +852,21 @@ class TemplateTool (BaseTool): version = dependency[1] if version: version = version[1:-1] - base_bt = self.getLastestBTOnRepos(dependency[0], version) else: - try: - base_bt = self.getLastestBTOnRepos(dependency[0]) - except BusinessTemplateIsMeta: - bt_list = self.getProviderList(dependency[0]) - # We explicilty use the Business Template which is used the most - # while dealing with provision list - repository_list = self.getRepositoryList() - if dependency[0] == 'erp5_full_text_catalog': - base_bt = [repository_list[1], 'erp5_full_text_mroonga_catalog'] - if dependency[0] == 'erp5_view_style': - base_bt = [repository_list[0], 'erp5_xhtml_style'] - if dependency[0] == 'erp5_catalog': - base_bt = [repository_list[0], 'erp5_mysql_innodb_catalog'] - # XXX: Create path for the BT(s) here + version = None + try: + base_bt = self.getLastestBTOnRepos(dependency[0], version) + except BusinessTemplateIsMeta: + bt_list = self.getProviderList(dependency[0]) + # We explicilty use the Business Template which is used the most + # while dealing with provision list + repository_list = self.getRepositoryList() + if dependency[0] == 'erp5_full_text_catalog': + base_bt = [repository_list[0], 'erp5_full_text_mroonga_catalog'] + if dependency[0] == 'erp5_view_style': + base_bt = [repository_list[1], 'erp5_xhtml_style'] + if dependency[0] == 'erp5_catalog': + base_bt = [repository_list[1], 'erp5_mysql_innodb_catalog'] # Download the base_bt base_bt_path = os.path.join(base_bt[0], base_bt[1]) -- 2.30.9 From 931ddbf163c9771bc7bba2b14a7370651871210d Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Mon, 18 Dec 2017 14:44:37 +0100 Subject: [PATCH 12/56] ERP5 Bootstrap: All bootstrap BT migrated to BM and upgrade bt5list --- product/ERP5/Tool/TemplateTool.py | 43 +++--------------- .../ERP5/bootstrap/erp5_core/erp5_core.zexp | Bin 0 -> 4665725 bytes .../bootstrap/erp5_jquery/erp5_jquery.zexp | Bin 0 -> 6498062 bytes .../erp5_mysql_innodb_catalog.zexp | Bin 0 -> 552219 bytes .../erp5_property_sheets.zexp | Bin 0 -> 1001059 bytes .../erp5_xhtml_style/erp5_xhtml_style.zexp | Bin 0 -> 8425416 bytes 6 files changed, 7 insertions(+), 36 deletions(-) create mode 100644 product/ERP5/bootstrap/erp5_core/erp5_core.zexp create mode 100644 product/ERP5/bootstrap/erp5_jquery/erp5_jquery.zexp create mode 100644 product/ERP5/bootstrap/erp5_mysql_innodb_catalog/erp5_mysql_innodb_catalog.zexp create mode 100644 product/ERP5/bootstrap/erp5_property_sheets/erp5_property_sheets.zexp create mode 100644 product/ERP5/bootstrap/erp5_xhtml_style/erp5_xhtml_style.zexp diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index c299952da4b..7cbe3f6402a 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -1277,19 +1277,6 @@ class TemplateTool (BaseTool): 'version': '5.4.7'} bm_dict_3 ={ - 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], - 'dependency_list': [], - 'description': '', - 'force_install': 0, - 'id': 'erp5_mysql_ndb_catalog', - 'license': 'GPL', - 'revision': '', - 'test_dependency_list': [], - 'provision_list': ['erp5_catalog'], - 'title': 'erp5_mysql_ndb_catalog', - 'version': '5.4.7'} - - bm_dict_4 ={ 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], 'dependency_list': ['erp5_view_style',], 'description': '', @@ -1302,7 +1289,7 @@ class TemplateTool (BaseTool): 'title': 'erp5_jquery', 'version': '5.4.7'} - bm_dict_5 ={ + bm_dict_4 ={ 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], 'dependency_list': [], 'description': '', @@ -1315,7 +1302,7 @@ class TemplateTool (BaseTool): 'title': 'erp5_property_sheets', 'version': '5.4.7'} - bm_dict_6 ={ + bm_dict_5 ={ 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], 'dependency_list': ['erp5_catalog (>= 1.1)', 'erp5_core_proxy_field_legacy', @@ -1331,28 +1318,12 @@ class TemplateTool (BaseTool): 'title': 'erp5_core', 'version': '5.4.7'} - bm_dict_7 ={ - 'copyright_list': ['Copyright (c) 2001-2017 Nexedi SA'], - 'dependency_list': [], - 'description': '', - 'force_install': 0, - 'id': 'erp5_business_package', - 'license': 'GPL', - 'revision': '', - 'test_dependency_list': [], - 'provision_list': [], - 'title': 'erp5_business_package', - 'version': '5.4.7'} - if repository.endswith('/bootstrap'): - pass - #property_dict_list.append(bm_dict_1) - #property_dict_list.append(bm_dict_2) - #property_dict_list.append(bm_dict_3) - #property_dict_list.append(bm_dict_4) - #property_dict_list.append(bm_dict_5) - #property_dict_list.append(bm_dict_6) - #property_dict_list.append(bm_dict_7) + property_dict_list.append(bm_dict_1) + property_dict_list.append(bm_dict_2) + property_dict_list.append(bm_dict_3) + property_dict_list.append(bm_dict_4) + property_dict_list.append(bm_dict_5) self.repository_dict[repository] = tuple(property_dict_list) diff --git a/product/ERP5/bootstrap/erp5_core/erp5_core.zexp b/product/ERP5/bootstrap/erp5_core/erp5_core.zexp new file mode 100644 index 0000000000000000000000000000000000000000..0b9896e4ffee2ace57f15e5171e4aa6a30e597da GIT binary patch literal 4665725 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

o~wnvAKYY?VQ;W?T&7zo)pC+uDA?xCVRZ9( zWJJ2`Ve}K+aym%q97buC!>HGEwxcO~(awhKMSa;lCpbNk#|f7pr=nUl^gT6PJa

C;NzlM&%Q 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