Commit 5d3a182b authored by Łukasz Nowak's avatar Łukasz Nowak

Merge remote-tracking branch 'origin/rename_instance'

parents 151c6c7c 51979277
......@@ -60,7 +60,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Software Instance</string> </value>
<value> <string>Requester</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
......@@ -79,7 +79,7 @@
<key> <string>text</string> </key>
<value> <string encoding="cdata"><![CDATA[
string:${object_url}/Base_jumpToRelatedObject?base_category=predecessor&portal_type=Software+Instance
string:${object_url}/Base_jumpToRelatedObject?base_category=predecessor&portal_type:list=Software+Instance&portal_type:list=Hosting+Subscription
]]></string> </value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string># Did not import Products.ERP5Security.ERP5GroupManager\n
# because of semantic matter.\n
class ConsistencyError(Exception):\n
pass\n
\n
predecessor_software_instance = context\n
while (predecessor_software_instance is not None):\n
root_software_instance = predecessor_software_instance\n
predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n
portal_type=["Software Instance", "Slave Instance"])\n
\n
root_hosting_subscription = root_software_instance.getPredecessorRelatedValue(\n
portal_type="Hosting Subscription")\n
\n
# XXX-Antoine: As Romain wanted it, we raise an error if it\'s not attached to a\n
# root Hosting Subscription.\n
if root_hosting_subscription is None:\n
raise ConsistencyError(("Software instance %s has no hosting "\n
"subscription as root.") % context.getReference())\n
\n
return root_hosting_subscription.getUid()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SoftwareInstance_getRootHostingSubscriptionUid</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -51,17 +51,18 @@
<item>
<key> <string>_body</string> </key>
<value> <string>software_instance = state_change[\'object\']\n
portal = context.getPortalObject()\n
portal = software_instance.getPortalObject()\n
\n
root_software_instance = software_instance\n
while (software_instance is not None):\n
root_software_instance = software_instance\n
software_instance = software_instance.getPredecessorRelatedValue()\n
root_hosting_subscription = portal.portal_catalog.getResultValue(uid=software_instance.SoftwareInstance_getRootHostingSubscriptionUid())\n
tag = \'%s_%s_inProgress\' % (software_instance.getUid(), root_hosting_subscription.getTitle())\n
root_software_instance = root_hosting_subscription.HostingSubscription_requestRootSoftwareInstance(tag)\n
\n
for software_instance in context.portal_catalog(\n
portal_type=\'Software Instance\',\n
root_uid=root_software_instance.getUid()):\n
software_instance = software_instance.getObject()\n
# Use iterative algorithm instead of recursive approach in order to avoid\n
# complexity as much as possible.\n
flat_tree = [root_software_instance]\n
while flat_tree:\n
software_instance = flat_tree.pop(0)\n
flat_tree.extend(software_instance.getPredecessorValueList())\n
try:\n
software_instance.Item_getInstancePackingListLine(service_relative_url=portal.portal_preferences.getPreferredInstanceCleanupResource())\n
except ValueError:\n
......
......@@ -50,21 +50,24 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>predecessor_software_instance = context\n
while (predecessor_software_instance is not None):\n
root_software_instance = predecessor_software_instance\n
predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n
portal_type=["Software Instance", "Slave Instance"])\n
return root_software_instance.getUid()\n
<value> <string># Get the software_instance\n
software_instance = state_change[\'object\']\n
\n
# Change the title\n
software_instance.edit(title=state_change.kwargs[\'new_name\'])\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SoftwareInstance_getRootSoftwareInstanceUid</string> </value>
<value> <string>SoftwareInstance_rename</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Rename software instance</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -64,6 +64,8 @@ is_slave = kwargs.get("shared", False)\n
sla_xml = kwargs["sla_xml"]\n
state = kwargs["state"]\n
\n
from DateTime import DateTime\n
\n
if is_slave == True:\n
software_instance_portal_type = "Slave Instance"\n
else:\n
......@@ -72,20 +74,23 @@ else:\n
# graph allows to "simulate" tree change after requested operation\n
graph = {}\n
# Get root software instance and create initial graph\n
predecessor_software_instance = software_instance\n
while (predecessor_software_instance is not None):\n
predecessor_software_instance_pred_uid_list = predecessor_software_instance.getPredecessorUidList()\n
graph[predecessor_software_instance.getUid()] = predecessor_software_instance_pred_uid_list\n
# as this walking is not fetching all instances fill predecessors into graph, in order to have\n
# "nearly" complete representation\n
for uid in predecessor_software_instance_pred_uid_list:\n
if uid not in graph:\n
graph[uid] = []\n
root_software_instance = predecessor_software_instance\n
predecessor_software_instance = predecessor_software_instance.getPredecessorRelatedValue(\n
portal_type="Software Instance")\n
root_hosting_subscription = software_instance.portal_catalog.getResultValue(\n
uid=software_instance.SoftwareInstance_getRootHostingSubscriptionUid(),\n
)\n
\n
tag = "%s_%s_inProgress" % (root_software_instance.getUid(),\n
predecessor_list = root_hosting_subscription.getPredecessorValueList()\n
graph[root_hosting_subscription.getUid()] = [predecessor.getUid() for predecessor in predecessor_list]\n
while True:\n
try:\n
current_software_instance = predecessor_list.pop(0)\n
except IndexError:\n
break\n
current_software_instance_predecessor_list = current_software_instance.getPredecessorValueList() or []\n
graph[current_software_instance.getUid()] = [predecessor.getUid()\n
for predecessor in current_software_instance_predecessor_list]\n
predecessor_list.extend(current_software_instance_predecessor_list)\n
\n
tag = "%s_%s_inProgress" % (root_hosting_subscription.getUid(),\n
requested_partition_reference)\n
\n
# Check if it already exists\n
......@@ -94,20 +99,22 @@ request_software_instance = software_instance.portal_catalog.getResultValue(\n
# XXX: User based property is used in non manual operation\n
# XXX-2: Do we really need to use root_uid?\n
title=requested_partition_reference,\n
root_uid=root_software_instance.getUid(),\n
root_uid=root_hosting_subscription.getUid(),\n
)\n
\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n
# As it is not possible to fetch informations, it is better to raise an error\n
raise NotImplementedError(tag)\n
\n
root_software_instance = root_hosting_subscription.HostingSubscription_requestRootSoftwareInstance(tag)\n
\n
# above query does not find root software instance, but as this case is easy\n
# to find, just check if such request does not come and raise\n
if root_software_instance.getTitle() == requested_partition_reference:\n
raise ValueError(\'It is disallowed to request root software instance\')\n
\n
if (request_software_instance is None):\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n
# As it is not possible to fetch informations, it is better to raise an error\n
raise NotImplementedError(tag)\n
else:\n
# First time that the software instance is requested\n
# Create a new one\n
module = software_instance.getDefaultModule(portal_type="Software Instance")\n
......@@ -121,11 +128,6 @@ if (request_software_instance is None):\n
**portal.Base_getNewSoftwareInstanceCoordinate()\n
)\n
request_software_instance.portal_workflow.doActionFor(request_software_instance, \'validate_action\')\n
if state == "started":\n
request_software_instance.startRequested()\n
if state == "stopped":\n
request_software_instance.stopRequested()\n
setup_service_relative_url = portal.portal_preferences.getPreferredInstanceSetupResource()\n
packing_list_line = software_instance.getAggregateRelatedValue(portal_type="Sale Packing List Line")\n
subscription = packing_list_line.getAggregateValue(portal_type="Hosting Subscription")\n
software_release_document = context.portal_catalog.getResultValue(\n
......@@ -147,6 +149,7 @@ if (request_software_instance is None):\n
activate_kw={\'tag\': tag},\n
)\n
\n
setup_service_relative_url = portal.portal_preferences.getPreferredInstanceSetupResource()\n
sale_order.setSpecialise(trade_condition, portal_type="Sale Trade Condition")\n
sale_order_line = sale_order.newContent(\n
portal_type="Sale Order Line",\n
......@@ -168,21 +171,30 @@ else:\n
# Update the predecessor category of the original caller\n
predecessor_software_instance = request_software_instance.getPredecessorRelatedValue(\n
portal_type="Software Instance")\n
\n
predecessor = None\n
if predecessor_software_instance is None:\n
raise ValueError(\'Requested Software Instance %s should have a predecessor\' % request_software_instance.getRelativeUrl())\n
sale_packing_list_line = context.SoftwareInstance_getInstanceSetupPackingListLine(state_change)\n
hosting_subscription = sale_packing_list_line.getAggregateValue(portal_type=\'Hosting Subscription\')\n
if request_software_instance.getPredecessorRelatedValue(portal_type="Hosting Subscription") != hosting_subscription:\n
raise ValueError(\'Requested Software Instance %s should have a predecessor from same tree\' % request_software_instance.getRelativeUrl())\n
else:\n
predecessor = hosting_subscription\n
else:\n
predecessor_uid_list = predecessor_software_instance.getPredecessorUidList()\n
predecessor = predecessor_software_instance\n
\n
predecessor_uid_list = predecessor.getPredecessorUidList()\n
predecessor_uid_list.remove(request_software_instance.getUid())\n
predecessor_software_instance.edit(\n
predecessor.edit(\n
predecessor_uid_list=predecessor_uid_list,\n
activate_kw={\'tag\': tag},)\n
graph[predecessor_software_instance.getUid()] = predecessor_uid_list\n
graph[predecessor.getUid()] = predecessor_uid_list\n
\n
if state == \'started\':\n
request_software_instance.startRequested()\n
request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n
elif state == \'stopped\':\n
request_software_instance.stopRequested() \n
request_software_instance.stopRequested()\n
request_software_instance.activate(after_tag=tag).requestStopComputerPartition()\n
else:\n
raise ValueError(\'State %r is not supported\' % state)\n
......@@ -195,7 +207,7 @@ if not request_software_instance.getUid() in graph:\n
graph[software_instance.getUid()] = software_instance.getPredecessorUidList() + [request_software_instance.getUid()]\n
\n
# check if all elements are still connected and if there is no cycle\n
software_instance.checkConnected(graph, root_software_instance.getUid())\n
software_instance.checkConnected(graph, root_hosting_subscription.getUid())\n
software_instance.checkNotCyclic(graph)\n
\n
software_instance.edit(\n
......
......@@ -73,7 +73,11 @@ service_uid_state_mapping = {\n
setup_resource_uid: [\'stopped\'],\n
hosting_resource_uid: [\'started\', \'delivered\']\n
}\n
previous_packing_list_line = context.SoftwareInstance_getInstancePackingListLine(state_change)\n
try:\n
previous_packing_list_line = context.SoftwareInstance_getInstancePackingListLine(state_change)\n
except ValueError:\n
# no packing list yet, no need to update\n
return\n
\n
if previous_packing_list_line.getResourceUid() == hosting_resource_uid:\n
if previous_packing_list_line.getSimulationState() not in service_uid_state_mapping[hosting_resource_uid]:\n
......
......@@ -24,6 +24,7 @@
<tuple>
<string>destroy_computer_partition</string>
<string>destroy_requested</string>
<string>rename</string>
<string>report_computer_partition_bang</string>
<string>report_computer_partition_error</string>
<string>request_computer_partition</string>
......
......@@ -24,6 +24,7 @@
<tuple>
<string>destroy_computer_partition</string>
<string>destroy_requested</string>
<string>rename</string>
<string>report_computer_partition_bang</string>
<string>report_computer_partition_error</string>
<string>request_computer_partition</string>
......
......@@ -24,6 +24,7 @@
<tuple>
<string>destroy_computer_partition</string>
<string>destroy_requested</string>
<string>rename</string>
<string>report_computer_partition_bang</string>
<string>report_computer_partition_error</string>
<string>request_computer_partition</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>SoftwareInstance_rename</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rename</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Rename Software Instance</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -9,7 +9,7 @@
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromAggregateMovementItemByPerson</property>
<multi_property id='base_category'>destination_section</multi_property>
</role>
<role id='Auditor'>
<role id='Assignor'>
<property id='title'>Related Software Instance Group</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromSelf</property>
<multi_property id='base_category'>aggregate</multi_property>
......
......@@ -6,7 +6,7 @@
</role>
<role id='Assignee'>
<property id='title'>Customer of the Instance</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromAggregateMovementItemByPerson</property>
<property id='base_category_script'>SoftwareType_getSecurityCategoryFromAggregateMovementItemByPerson</property>
<multi_property id='base_category'>destination_section</multi_property>
</role>
<role id='Assignee'>
......@@ -17,7 +17,7 @@
</role>
<role id='Assignor'>
<property id='title'>Slave Instance related by Hosting Subscription</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromAggregateMovementItemByHostingSubscription</property>
<property id='base_category_script'>SoftwareType_getSecurityCategoryFromAggregateMovementItemByHostingSubscription</property>
<multi_property id='base_category'>aggregate</multi_property>
</role>
<role id='Assignor'>
......
......@@ -6,7 +6,7 @@
</role>
<role id='Assignee'>
<property id='title'>Customer of the Instance</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromAggregateMovementItemByPerson</property>
<property id='base_category_script'>SoftwareType_getSecurityCategoryFromAggregateMovementItemByPerson</property>
<multi_property id='base_category'>destination_section</multi_property>
</role>
<role id='Assignee'>
......@@ -17,7 +17,7 @@
</role>
<role id='Assignor'>
<property id='title'>Software Instance related by Hosting Subscription</property>
<property id='base_category_script'>ERP5Type_getSecurityCategoryFromAggregateMovementItemByHostingSubscription</property>
<property id='base_category_script'>SoftwareType_getSecurityCategoryFromAggregateMovementItemByHostingSubscription</property>
<multi_property id='base_category'>aggregate</multi_property>
</role>
</type_roles>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string># XXX For now, this script requires proxy manager\n
\n
# base_category_list : list of category values we need to retrieve\n
# user_name : string obtained from getSecurityManager().getUser().getUserName() [NuxUserGroup]\n
# or from getSecurityManager().getUser().getId() [PluggableAuthService with ERP5GroupManager]\n
# object : object which we want to assign roles to.\n
# portal_type : portal type of object\n
\n
# must always return a list of dicts\n
\n
category_list = []\n
\n
if obj is None:\n
return []\n
\n
item_portal_type = "Hosting Subscription"\n
movement_portal_type = "Sale Order Line"\n
\n
portal = obj.getPortalObject()\n
\n
movement = portal.portal_catalog.getResultValue(\n
portal_type=movement_portal_type,\n
strict_aggregate_uid=obj.getUid(),\n
simulation_state=(\'confirmed\', \'ordered\'),\n
)\n
\n
if movement is not None:\n
item = movement.getAggregateValue(portal_type=item_portal_type)\n
if item is not None:\n
category_list.append({"aggregate": [item.getRelativeUrl()]})\n
\n
return category_list\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>base_category_list, user_name, obj, portal_type</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SoftwareType_getSecurityCategoryFromAggregateMovementItemByHostingSubscription</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
# XXX For now, this script requires proxy manager\n
\n
# base_category_list : list of category values we need to retrieve\n
# user_name : string obtained from getSecurityManager().getUser().getUserName() [NuxUserGroup]\n
# or from getSecurityManager().getUser().getId() [PluggableAuthService with ERP5GroupManager]\n
# object : object which we want to assign roles to.\n
# portal_type : portal type of object\n
\n
# must always return a list of dicts\n
\n
category_list = []\n
\n
if obj is None:\n
return []\n
\n
movement_portal_type = "Sale Order Line"\n
\n
portal = obj.getPortalObject()\n
\n
security_dict = {}\n
\n
parent_url_list = []\n
for movement in portal.portal_catalog(\n
portal_type=movement_portal_type,\n
strict_aggregate_uid=obj.getUid(),\n
simulation_state=(\'ordered\', \'confirmed\'),\n
):\n
item = movement.getDestinationSectionValue(portal_type="Person")\n
if item is not None:\n
parent_url_list.append(item.getRelativeUrl())\n
\n
if len(parent_url_list) > 0:\n
parent_url_list.sort()\n
category_list.append({"destination_section": parent_url_list})\n
return category_list\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>base_category_list, user_name, obj, portal_type</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SoftwareType_getSecurityCategoryFromAggregateMovementItemByPerson</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>SaleOrder_updateAggregateLocalRoles</string>
</list>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleOrder_order</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>order</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<list>
<string>Sale Order</string>
</list>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -54,7 +54,7 @@
This script updates all local roles on the object. It requires Assignor\n
proxy role since it may be called by owner in draft state.\n
"""\n
portal_type_list = ["Software Instance", "Slave Instance", "Hosting Subscription", "Computer Partition"]\n
portal_type_list = ["Hosting Subscription", "Computer Partition", "Software Instance", "Slave Instance"]\n
for movement in state_change[\'object\'].getMovementList():\n
tag = "softinssec_%s" % movement.getUid()\n
movement.activate(activity=\'SQLQueue\', tag=tag, after_path_and_method_id=(movement.getPath(),\n
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>"""\n
This script updates all local roles on the object. It requires Assignor\n
proxy role since it may be called by owner in draft state.\n
"""\n
portal_type_list = ["Slave Instance", "Software Instance", "Hosting Subscription"]\n
for movement in state_change[\'object\'].getMovementList():\n
tag = "softinssec_%s" % movement.getUid()\n
movement.activate(activity=\'SQLQueue\', tag=tag, after_path_and_method_id=(movement.getPath(),\n
(\'immediateReindexObject\', \'recursiveImmediateReindexObject\'))).serialize()\n
movement.activate(activity=\'SQLQueue\', tag=tag, after_path_and_method_id=(movement.getParentValue().getPath(),\n
(\'immediateReindexObject\', \'recursiveImmediateReindexObject\'))).serialize()\n
for software_instance in movement.getAggregateValueList(portal_type=portal_type_list):\n
software_instance.activate(after_tag=tag).updateLocalRolesOnSecurityGroups()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleOrder_updateAggregateLocalRoles</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -15,7 +15,7 @@
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid\r\n
SoftwareInstance_getRootSoftwareInstanceUid</string> </value>
SoftwareInstance_getRootHostingSubscriptionUid</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
......@@ -69,7 +69,7 @@ WHERE\n
<dtml-in prefix="loop" expr="_.range(_.len(uid))">\n
<dtml-call expr="row_list.append([\n
uid[loop_item],\n
SoftwareInstance_getRootSoftwareInstanceUid[loop_item]])">\n
SoftwareInstance_getRootHostingSubscriptionUid[loop_item]])">\n
</dtml-in>\n
\n
<dtml-if "row_list">\n
......
32
\ No newline at end of file
33
\ No newline at end of file
<base_category_list>
<portal_type id="Hosting Subscription">
<item>predecessor</item>
</portal_type>
<portal_type id="Hosting Subscription Module">
<item>business_application</item>
</portal_type>
......
<property_sheet_list>
<portal_type id="Hosting Subscription">
<item>HostingSubscription</item>
<item>SoftwareInstance</item>
<item>TextDocument</item>
<item>VariationRange</item>
</portal_type>
</property_sheet_list>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>HostingSubscription</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/boolean</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>root_slave_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>root_software_release_url_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>root_state_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -95,6 +95,10 @@
<list>
<string>my_title</string>
<string>my_reference</string>
<string>my_predecessor_title_list</string>
<string>my_source_reference</string>
<string>my_text_content</string>
<string>my_sla_xml</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>portal_type</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_predecessor_title_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_multi_relation_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value>
<list>
<tuple>
<string>Software Instance</string>
<string>Slave Instance</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Predecessors</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_sla_xml</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_text_area_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Root SLA XML</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_source_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_read_only_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Root Software Type</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_text_content</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_text_area_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Root XML</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
Hosting Subscription Module | business_application
Hosting Subscription | predecessor
\ No newline at end of file
Hosting Subscription | HostingSubscription
Hosting Subscription | SoftwareInstance
Hosting Subscription | TextDocument
Hosting Subscription | VariationRange
\ No newline at end of file
HostingSubscription
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
from DateTime import DateTime\n
from Products.ERP5Type.DateUtils import getClosestDate, addToDate\n
\n
portal = context.getPortalObject()\n
cleanup_resource = portal.portal_preferences.getPreferredInstanceCleanupResource()\n
\n
# copy and paste from\n
# portal_workflow/person_slap_interface_workflow/scripts/Person_requestSoftwareInstance\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n
# As it is not possible to fetch informations, it is better to raise an error\n
raise NotImplementedError(tag)\n
hosting_subscription = context\n
\n
sale_order_portal_type = "Sale Order"\n
sale_order_line_portal_type = "Sale Order Line"\n
hosting_subscription_portal_type = "Hosting Subscription"\n
if hosting_subscription.isRootSlave():\n
software_instance_portal_type = "Slave Instance"\n
else:\n
software_instance_portal_type = "Software Instance"\n
\n
# Mostly copy and paste from\n
# portal_workflow/person_slap_interface_workflow/scripts/Person_requestSoftwareInstance\n
request_software_instance = hosting_subscription.portal_catalog.getResultValue(\n
portal_type=software_instance_portal_type,\n
title=hosting_subscription.getTitle(),\n
root_uid=hosting_subscription.getUid(),\n
)\n
\n
if person is None:\n
open_sale_order_line = hosting_subscription.portal_catalog.getResultValue(\n
portal_type=\'Open Sale Order Line\', strict_aggregate_uid=hosting_subscription.getUid())\n
open_sale_order = open_sale_order_line.getParentValue()\n
person_relative_url = open_sale_order.getDestinationSection()\n
else:\n
person_relative_url = person.getRelativeUrl()\n
\n
new_one = False\n
if (request_software_instance is None):\n
new_one = True\n
setup_service_relative_url = portal.portal_preferences.getPreferredInstanceSetupResource()\n
request_software_instance = portal.getDefaultModule(portal_type=software_instance_portal_type).newContent(\n
portal_type=software_instance_portal_type,\n
source_reference=hosting_subscription.getSourceReference(),\n
title=hosting_subscription.getTitle(),\n
text_content=hosting_subscription.getTextContent(),\n
sla_xml=hosting_subscription.getSlaXml(),\n
activate_kw={\'tag\': tag},\n
**portal.Base_getNewSoftwareInstanceCoordinate()\n
)\n
request_software_instance.portal_workflow.doActionFor(request_software_instance,\n
\'validate_action\')\n
\n
software_release_document = context.portal_catalog.getResultValue(\n
portal_type=\'Software Release\',\n
url_string=hosting_subscription.getRootSoftwareReleaseUrl())\n
\n
sale_order = portal.getDefaultModule(portal_type=sale_order_portal_type).newContent(\n
portal_type=sale_order_portal_type,\n
destination=person_relative_url,\n
destination_section=person_relative_url,\n
destination_decision=person_relative_url,\n
start_date=DateTime(),\n
received_date=DateTime(),\n
# XXX Hardcoded values\n
source="organisation_module/vifib_internet",\n
source_section="organisation_module/vifib_internet",\n
quantity_unit="unit/piece",\n
price_currency="currency_module/EUR",\n
activate_kw={\'tag\': tag},\n
)\n
sale_order_line = sale_order.newContent(\n
portal_type=sale_order_line_portal_type,\n
resource=setup_service_relative_url,\n
quantity=1,\n
price=0,\n
aggregate_value_list=[request_software_instance,\n
hosting_subscription,\n
software_release_document\n
],\n
activate_kw={\'tag\': tag},\n
)\n
#portal.portal_workflow.doActionFor(sale_order, "order_action")\n
# XXX: Do not verify security\n
sale_order.order()\n
\n
if person is not None:\n
# Update Open Order\n
# does this order have a client-specific Open Order\n
open_order = portal.portal_catalog.getResultValue(\n
default_destination_section_uid=person.getUid(),\n
portal_type="Open Sale Order",\n
validation_state="validated")\n
if open_order is None:\n
person_slap_interface_state = person.getSlapState()\n
if person_slap_interface_state == \'open_order_created\':\n
open_order_url = context.portal_workflow.getInfoFor(\n
ob=person, name=\'open_order_url\',\n
wf_id=\'person_slap_interface_workflow\')\n
open_order = portal.restrictedTraverse(open_order_url)\n
else:\n
raise ValueError(\'Person has no open order created\')\n
\n
now = DateTime()\n
start_date = getClosestDate(target_date=now, precision=\'day\', before=1)\n
# 12 months of subscription by default\n
stop_date = addToDate(getClosestDate(target_date=now, precision=\'month\', before=1), year=1)\n
\n
subscription_service_relative_url = portal.portal_preferences.getPreferredInstanceSubscriptionResource()\n
open_order.newContent(\n
portal_type="Open Sale Order Line",\n
title=hosting_subscription.getTitle(),\n
aggregate_value=hosting_subscription,\n
start_date=start_date,\n
stop_date=stop_date,\n
quantity=1,\n
quantity_unit="unit/piece",\n
price=1,\n
price_currency="currency_module/EUR",\n
specialise=sale_order.getSpecialise(portal_type="Sale Trade Condition"),\n
# XXX Hardcoded values\\n\n
resource=subscription_service_relative_url,\n
destination=person_relative_url,\n
destination_section=person_relative_url,\n
source="organisation_module/vifib_internet",\n
source_section="organisation_module/vifib_internet",\n
activate_kw={\'tag\': tag},\n
)\n
\n
hosting_subscription_predecessor_list = hosting_subscription.getPredecessorList()\n
hosting_subscription_predecessor_list.append(request_software_instance.getRelativeUrl())\n
hosting_subscription.edit(predecessor_list=hosting_subscription_predecessor_list)\n
\n
# Find Computer partition\n
# XXX Link subscript to open order\n
else:\n
# Update existing software instance\n
request_software_instance.edit(\n
text_content=hosting_subscription.getTextContent(),\n
source_reference=hosting_subscription.getSourceReference(),\n
sla_xml=hosting_subscription.getSlaXml(),\n
activate_kw={\'tag\': tag},\n
)\n
for predecessor_related in request_software_instance.getPredecessorRelatedValueList():\n
predecessor_list = predecessor_related.getPredecessorList()\n
predecessor_list.remove(request_software_instance.getRelativeUrl())\n
predecessor_related.setPredecessorList(predecessor_list)\n
\n
hosting_subscription_predecessor_list = hosting_subscription.getPredecessorList()\n
hosting_subscription_predecessor_list.append(request_software_instance.getRelativeUrl())\n
hosting_subscription.setPredecessorList(hosting_subscription_predecessor_list)\n
\n
if hosting_subscription.getRootState() == \'started\':\n
request_software_instance.startRequested()\n
if not new_one:\n
request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n
elif hosting_subscription.getRootState() == \'stopped\':\n
request_software_instance.stopRequested()\n
if not new_one:\n
request_software_instance.activate(after_tag=tag).requestStopComputerPartition()\n
\n
return request_software_instance\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>tag, person=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>HostingSubscription_requestRootSoftwareInstance</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -52,8 +52,6 @@
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
from DateTime import DateTime\n
from Products.ERP5Type.DateUtils import getClosestDate, addToDate\n
person = state_change[\'object\']\n
portal = person.getPortalObject()\n
# Get required arguments\n
......@@ -80,142 +78,60 @@ tag = "%s_%s_inProgress" % (person.getUid(), \n
\n
# Check if it already exists\n
cleanup_resource = portal.portal_preferences.getPreferredInstanceCleanupResource()\n
request_software_instance = None\n
for si in portal.portal_catalog(\n
portal_type=software_instance_portal_type,\n
request_hosting_subscription = None\n
for hs in portal.portal_catalog(\n
portal_type=hosting_subscription_portal_type,\n
title=software_title,\n
):\n
si = hs.portal_catalog.getResultValue(title=software_title,\n
root_uid=hs.getUid())\n
try:\n
cleanup_delivery_line = si.Item_getInstancePackingListLine(cleanup_resource)\n
except ValueError:\n
request_software_instance = si\n
request_hosting_subscription = hs\n
break\n
else:\n
if cleanup_delivery_line.getSimulationState() != \'delivered\':\n
request_software_instance = si\n
request_hosting_subscription = hs\n
break\n
\n
if (request_software_instance is None):\n
hosting_subscription_reference = "HOSTSUBS-%s" % context.getPortalObject().portal_ids\\\n
.generateNewId(id_group=\'slap_hosting_subscription_reference\', id_generator=\'uid\')\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
if (portal.portal_activities.countMessageWithTag(tag) > 0):\n
# The software instance is already under creation but can not be fetched from catalog\n
# As it is not possible to fetch informations, it is better to raise an error\n
raise NotImplementedError(tag)\n
else:\n
\n
setup_service_relative_url = portal.portal_preferences.getPreferredInstanceSetupResource()\n
software_instance = portal.getDefaultModule(portal_type=software_instance_portal_type).newContent(\n
portal_type=software_instance_portal_type,\n
source_reference=software_type,\n
title=software_title,\n
text_content=instance_xml,\n
sla_xml=sla_xml,\n
activate_kw={\'tag\': tag},\n
**portal.Base_getNewSoftwareInstanceCoordinate()\n
)\n
software_instance.portal_workflow.doActionFor(software_instance, \'validate_action\')\n
subscription = portal.getDefaultModule(portal_type=hosting_subscription_portal_type).newContent(\n
if (request_hosting_subscription is None):\n
hosting_subscription_reference = "HOSTSUBS-%s" % context.getPortalObject().portal_ids\\\n
.generateNewId(id_group=\'slap_hosting_subscription_reference\', id_generator=\'uid\')\n
request_hosting_subscription = portal.getDefaultModule(portal_type=hosting_subscription_portal_type).newContent(\n
portal_type=hosting_subscription_portal_type,\n
reference=hosting_subscription_reference,\n
title=software_title,\n
source_reference=software_type,\n
text_content=instance_xml,\n
sla_xml=sla_xml,\n
root_software_release_url=software_release_url_string,\n
root_slave=is_slave,\n
root_state=state,\n
periodicity_hour_list=[0],\n
periodicity_minute_list=[0],\n
periodicity_month_day=[1],\n
activate_kw={\'tag\': tag},\n
)\n
subscription.portal_workflow.doActionFor(subscription, \'validate_action\')\n
if state == \'started\':\n
software_instance.startRequested()\n
elif state == \'stopped\':\n
software_instance.stopRequested()\n
\n
software_release_document = context.portal_catalog.getResultValue(\n
portal_type=\'Software Release\',\n
url_string=software_release_url_string)\n
\n
sale_order = portal.getDefaultModule(portal_type=sale_order_portal_type).newContent(\n
portal_type=sale_order_portal_type,\n
destination_value=person,\n
destination_section_value=person,\n
destination_decision_value=person,\n
start_date=DateTime(),\n
received_date=DateTime(),\n
# XXX Hardcoded values\n
source="organisation_module/vifib_internet",\n
source_section="organisation_module/vifib_internet",\n
quantity_unit="unit/piece",\n
price_currency="currency_module/EUR",\n
activate_kw={\'tag\': tag},\n
)\n
sale_order_line = sale_order.newContent(\n
portal_type=sale_order_line_portal_type,\n
resource=setup_service_relative_url,\n
quantity=1,\n
price=0,\n
aggregate_value_list=[software_instance, subscription, software_release_document],\n
activate_kw={\'tag\': tag},\n
)\n
portal.portal_workflow.doActionFor(sale_order, "order_action")\n
\n
# Update Open Order\n
# does this order have a client-specific Open Order\n
open_order = portal.portal_catalog.getResultValue(\n
default_destination_section_uid=person.getUid(),\n
portal_type="Open Sale Order",\n
validation_state="validated")\n
if open_order is None:\n
person_slap_interface_state = person.getSlapState()\n
if person_slap_interface_state == \'open_order_created\':\n
open_order_url = context.portal_workflow.getInfoFor(\n
ob=person, name=\'open_order_url\',\n
wf_id=\'person_slap_interface_workflow\')\n
open_order = portal.restrictedTraverse(open_order_url)\n
else:\n
raise ValueError(\'Person has no open order created\')\n
\n
now = DateTime()\n
start_date = getClosestDate(target_date=now, precision=\'day\', before=1)\n
# 12 months of subscription by default\n
stop_date = addToDate(getClosestDate(target_date=now, precision=\'month\', before=1), year=1)\n
\n
subscription_service_relative_url = portal.portal_preferences.getPreferredInstanceSubscriptionResource()\n
open_order.newContent(\n
portal_type="Open Sale Order Line",\n
title=software_title,\n
aggregate_value=subscription,\n
start_date=start_date,\n
stop_date=stop_date,\n
quantity=1,\n
quantity_unit="unit/piece",\n
price=1,\n
price_currency="currency_module/EUR",\n
specialise=sale_order.getSpecialise(portal_type="Sale Trade Condition"),\n
# XXX Hardcoded values\\n\n
resource=subscription_service_relative_url,\n
destination_value=person,\n
destination_section_value=person,\n
source="organisation_module/vifib_internet",\n
source_section="organisation_module/vifib_internet",\n
activate_kw={\'tag\': tag},\n
)\n
\n
# Find Computer partition\n
# XXX Link subscript to open order\n
request_hosting_subscription.portal_workflow.doActionFor(request_hosting_subscription,\n
\'validate_action\')\n
else:\n
# Update existing software instance\n
request_software_instance.edit(\n
request_hosting_subscription.edit(\n
text_content=instance_xml,\n
source_reference=software_type,\n
sla_xml=sla_xml,\n
root_software_release_url=software_release_url_string,\n
root_slave=is_slave,\n
root_state=state,\n
activate_kw={\'tag\': tag},\n
)\n
if state == \'started\':\n
request_software_instance.startRequested()\n
request_software_instance.activate(after_tag=tag).requestStartComputerPartition()\n
elif state == \'stopped\':\n
request_software_instance.stopRequested()\n
request_software_instance.activate(after_tag=tag).requestStopComputerPartition()\n
\n
request_hosting_subscription.HostingSubscription_requestRootSoftwareInstance(tag, person=person)\n
]]></string> </value>
......
506
\ No newline at end of file
507
\ No newline at end of file
......@@ -245,6 +245,17 @@ class SlapTool(BaseTool):
return self._softwareInstanceError(computer_id, computer_partition_id,
error_log)
security.declareProtected(Permissions.AccessContentsInformation,
'softwareInstanceRename')
def softwareInstanceRename(self, new_name, computer_id,
computer_partition_id, slave_reference=None):
"""
Change the title of a Software Instance using Workflow.
"""
return self._softwareInstanceRename(new_name, computer_id,
computer_partition_id,
slave_reference)
security.declareProtected(Permissions.AccessContentsInformation,
'softwareInstanceBang')
def softwareInstanceBang(self, computer_id,
......@@ -632,6 +643,15 @@ class SlapTool(BaseTool):
computer_partition_id).reportComputerPartitionError(
comment=error_log)
@convertToREST
def _softwareInstanceRename(self, new_name, computer_id,
computer_partition_id, slave_reference):
software_instance = self._getSoftwareInstanceForComputerPartition(
computer_id, computer_partition_id,
slave_reference)
return software_instance.rename(new_name=new_name,
comment="Rename %s into %s" % (software_instance.title, new_name))
@convertToREST
def _softwareInstanceBang(self, computer_id,
computer_partition_id, message):
......
......@@ -718,6 +718,344 @@ class TestVifibSlapBang(TestVifibSlapWebServiceMixin):
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def stepCheckTreeLooksLikeRenameComplexTree(self, sequence, **kw):
hosting_subscription_uid = sequence['hosting_subscription_uid']
hosting_subscription = self.portal.portal_catalog.getResultValue(
uid=hosting_subscription_uid
)
root_software_instance = hosting_subscription.portal_catalog.getResultValue(
title=hosting_subscription.getTitle(), portal_type="Software Instance",
root_uid=hosting_subscription_uid)
self.failIfEqual(root_software_instance, None)
children_titles = set([si.getTitle()
for si in root_software_instance.getPredecessorValueList()])
self.failUnless(set(['children_a', 'children_b']) <= children_titles)
children_b_child = hosting_subscription.portal_catalog.getResultValue(
title='children_b_child',
root_uid=hosting_subscription.getUid(),
)
self.failIfEqual(children_b_child, None)
def test_ComputerPartition_rename_root_and_bang(self):
r"""
Request Master: __________
/ \
| HS: Master |
\__________/
_____|____
/ \
| SI: Master |
\__________/
Rename Software Instance Master into MasterDead:
__________
/ \
| HS: Master |
\__________/
_______|______
/ \
| SI: MasterDead |
\______________/
Banging the tree should result:
_____________________________
/ \
| HS: Master |
\______________________________/
____/_____ _______\______
/ \ / \
| SI: Master | | SI: MasterDead |
\__________/ \______________/
"""
self.computer_partition_amount = 2
sequence_list = SequenceList()
sequence_string = self.prepare_install_requested_computer_partition_sequence_string + """
SetRootSoftwareInstanceCurrentInstance
Tic
LoginDefaultUser
RenameCurrentSoftwareInstanceDead
Tic
Logout
SlapLoginCurrentSoftwareInstance
Bang
ConfirmOrderedSaleOrderActiveSense
SlapLogout
Tic
LoginTestVifibCustomer
CheckTreeHasARootSoftwareInstance
Logout
"""
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_ComputerPartition_rename_root_complex_tree(self):
r"""
Request Master which is a software realease having a complex tree :
___________________________
/ \
| HS: Master |
\____________________________/
_____________|_____________
/ \
| SI: Master |
\____________________________/
_____/_____ _____\_____
/ \ / \
| SI: Child A | | SI: Child B |
\___________/ \___________/
______|______
/ \
| SI: GrandChild |
\______________/
Rename Software Instance Master :
___________________________
/ \
| SI: Master |
\____________________________/
_____________|_____________
/ \
| SI: MasterDead |
\____________________________/
_____/_____ _____\_____
/ \ / \
| SI: Child A | | SI: Child B |
\___________/ \___________/
______|______
/ \
| SI: GrandChild |
\______________/
Run bang() on the tree. We expect to have a new root as :
_______________________________________________
/ \
| HS: Master |
\_______________________________________________/
_____________|______________ _______|______
/ \ / \
| SI: Master | | SI: MasterDead |
\____________________________/ \______________/
_____/_____ _____\_____
/ \ / \
| SI: Child A | | SI: Child B |
\___________/ \___________/
______|______
/ \
| SI: GrandChild |
\______________/
"""
self.computer_partition_amount = 5
sequence_list = SequenceList()
sequence_string = self.prepare_children_a_children_b_sequence_string + """
LoginDefaultUser
SetSoftwareInstanceChildrenB
SelectRequestedReferenceChildrenBChild
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
Tic
CheckRaisesNotFoundComputerPartitionParameterDict
Tic
RequestComputerPartition
Tic
SlapLogout
LoginDefaultUser
SlapLoginTestVifibCustomer
SetSoftwareInstanceRoot
RenameCurrentSoftwareInstanceDead
Tic
Bang
ConfirmOrderedSaleOrderActiveSense
Tic
Logout
SlapLogout
LoginDefaultUser
SetSoftwareInstanceGetRootOfTheTree
SetRootSoftwareInstanceCurrentInstance
SelectRequestedReferenceChildrenA
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
Tic
SlapLogout
LoginDefaultUser
SelectRequestedReferenceChildrenB
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SetSoftwareInstanceChildrenB
SelectRequestedReferenceChildrenBChild
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SlapLoginCurrentComputer
CheckTreeHasARootSoftwareInstance
CheckTreeLooksLikeRenameComplexTree
SlapLogout
Logout
"""
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_ComputerPartition_rename_child_complex_tree(self):
r"""
Request A which is a software realease having a complex tree :
___________________________
/ \
| HS: Master |
\____________________________/
_____________|_____________
/ \
| SI: Master |
\____________________________/
_____/_____ _____\_____
/ \ / \
| SI: Child A | | SI: Child B |
\___________/ \___________/
______|______
/ \
| SI: GrandChild |
\______________/
Rename child C into E :
(Rename reattach to root as Luke wanted it)
________________________________
/ \
| HS: Master |
\________________________________/
________________|_______________
/ \
| SI: Master |
\________________________________/
_____/_____ _________\______
/ \ / \
| SI: Child A | | SI: Child B Dead |
\___________/ \________________/
______|______
/ \
| SI: GrandChild |
\______________/
Bang the tree. We espect to have a new C replacing it,
as : _________________________________________________
/ \
| HS: Master |
\_________________________________________________/
_____________|___________________________________
/ \
| SI: Master |
\_________________________________________________/
_____/_____ _____\_____ _______|________
/ \ / \ / \
| SI: Child A | | SI: Child B | | SI: Child B Dead |
\___________/ \___________/ \________________/
______|______
/ \
| SI: GrandChild |
\______________/
"""
self.computer_partition_amount = 5
sequence_list = SequenceList()
sequence_string = self.prepare_children_a_children_b_sequence_string + """
LoginDefaultUser
SetSoftwareInstanceChildrenB
SelectRequestedReferenceChildrenBChild
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
Tic
CheckRaisesNotFoundComputerPartitionParameterDict
Tic
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SlapLoginTestVifibCustomer
SetSoftwareInstanceChildrenB
RenameCurrentSoftwareInstanceDead
Tic
Bang
ConfirmOrderedSaleOrderActiveSense
Tic
Logout
SlapLogout
LoginDefaultUser
SetSoftwareInstanceGetRootOfTheTree
SetRootSoftwareInstanceCurrentInstance
SelectRequestedReferenceChildrenA
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SelectRequestedReferenceChildrenB
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SetSoftwareInstanceChildrenB
SelectRequestedReferenceChildrenBChild
SelectEmptyRequestedParameterDict
Logout
SlapLoginCurrentSoftwareInstance
RequestComputerPartition
ConfirmOrderedSaleOrderActiveSense
Tic
SlapLogout
LoginDefaultUser
SlapLoginCurrentComputer
CheckTreeHasARootSoftwareInstance
CheckTreeLooksLikeRenameComplexTree
SlapLogout
"""
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestVifibSlapBang))
......
......@@ -2,6 +2,7 @@ from Products.ERP5Type.tests.Sequence import SequenceList
import unittest
from testVifibSlapWebService import TestVifibSlapWebServiceMixin
import random
import transaction
class TestVifibSlapBug(TestVifibSlapWebServiceMixin):
def test_bug_Person_request_more_then_one_instance(self):
......@@ -702,6 +703,7 @@ class TestVifibSlapBug(TestVifibSlapWebServiceMixin):
sla_xml=self.minimal_correct_xml,
state='started'
)
transaction.abort()
def test_bug_orhpaned_software_instance(self):
"""Check that no orphaned Software Instances would be created
......@@ -866,6 +868,7 @@ class TestVifibSlapBug(TestVifibSlapWebServiceMixin):
sla_xml=self.minimal_correct_xml,
state='started'
)
transaction.abort()
def test_bug_cyclic_software_instance(self):
"""Check that no cyclic Software Instance trees would be created
......
......@@ -571,9 +571,9 @@ class TestVifibSlapComputerPartitionUpdate(TestVifibSlapWebServiceMixin):
slap_computer_partition.request(**kw)
def stepCheckActivityRequestInProgress(self, sequence, **kw):
software_instance_uid = sequence['software_instance_uid']
hosting_subscription_uid = sequence['hosting_subscription_uid']
requested_partition_reference = sequence.get('software_type', 'requested_reference')
tag = "%s_%s_inProgress" % (software_instance_uid,
tag = "%s_%s_inProgress" % (hosting_subscription_uid,
requested_partition_reference)
self.assertNotEqual(0, self.portal.portal_activities.
......
......@@ -1834,12 +1834,13 @@ class TestVifibSlapWebServiceMixin(testVifibMixin):
def _stepSetSoftwareInstanceChildren(self, sequence, source_reference):
software_instance_uid = sequence['root_software_instance_uid']
hosting_subscription_uid = sequence['hosting_subscription_uid']
software_instance = self.portal.portal_catalog.getResultValue(
uid=software_instance_uid)
children_software_instance = \
software_instance.portal_catalog.getResultValue(
portal_type="Software Instance", source_reference=source_reference,
root_uid=software_instance_uid)
root_uid=hosting_subscription_uid)
self.assertNotEqual(None, children_software_instance)
self.assertNotEqual(software_instance.getRelativeUrl(),
children_software_instance.getRelativeUrl())
......@@ -1861,6 +1862,18 @@ class TestVifibSlapWebServiceMixin(testVifibMixin):
self.assertNotEqual(None, software_instance_uid)
sequence.edit(root_software_instance_uid=software_instance_uid)
def stepSetSoftwareInstanceRoot(self, sequence, **kw):
root_software_instance_uid = sequence['root_software_instance_uid']
self.failIfEqual(None, root_software_instance_uid)
root_software_instance = self.portal.portal_catalog.getResultValue(
uid=root_software_instance_uid,
)
self.failIfEqual(None, root_software_instance)
computer_partition_reference = self._softwareInstance_getComputerPartition(
root_software_instance).getReference()
sequence.edit(software_instance_uid=root_software_instance_uid,
computer_partition_reference=computer_partition_reference)
def stepRequestComputerPartitionDifferentReferenceSameTransaction(self,
sequence, **kw):
software_release_uri = sequence['software_release_uri']
......@@ -2014,6 +2027,8 @@ class TestVifibSlapWebServiceMixin(testVifibMixin):
finally:
Base.serialize = Base.serialize_call
transaction.abort()
def _getComputerPartitionByReference(self, sequence):
computer_partition_list = self.portal.portal_catalog(
portal_type=self.computer_partition_portal_type,
......@@ -3893,6 +3908,47 @@ class TestVifibSlapWebServiceMixin(testVifibMixin):
def stepSetRandomRequestedReference(self, sequence, **kw):
sequence['requested_reference'] = self.id() + str(random())
def stepRenameCurrentSoftwareInstanceDead(self, sequence, **kw):
hosting_subscription = self.portal.portal_catalog.getResultValue(
uid=sequence['hosting_subscription_uid'],
)
software_instance = self.portal.portal_catalog.getResultValue(
uid=sequence['software_instance_uid']
)
software_instance.rename(new_name='%sDead' % software_instance.getTitle())
def stepCheckTreeHasARootSoftwareInstance(self, sequence, **kw):
hosting_subscription_uid = sequence['hosting_subscription_uid']
hosting_subscription = self.portal.portal_catalog.getResultValue(
uid=hosting_subscription_uid,
)
root_software_instance = self.portal.portal_catalog.getResultValue(
root_uid=hosting_subscription_uid,
title=hosting_subscription.getTitle(),
)
self.failIfEqual(root_software_instance, None,
"No root software instance")
def stepSetSoftwareInstanceGetRootOfTheTree(self, sequence, **kw):
hosting_subscription_uid = sequence['hosting_subscription_uid']
hosting_subscription = self.portal.portal_catalog.getResultValue(
uid=hosting_subscription_uid,
)
root_software_instance = self.portal.portal_catalog.getResultValue(
root_uid=hosting_subscription_uid,
title=hosting_subscription.getTitle(),
)
self.failIfEqual(root_software_instance, None,
"No root software instance")
computer_partition_reference = self._softwareInstance_getComputerPartition(
root_software_instance).getReference()
sequence.edit(software_instance_uid=root_software_instance.getUid(),
computer_partition_reference=computer_partition_reference)
class TestVifibSlapWebService(TestVifibSlapWebServiceMixin):
########################################
# slap.initializeConnection
......@@ -4255,6 +4311,7 @@ class TestVifibSlapWebService(TestVifibSlapWebServiceMixin):
"""
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
# class IComputerPartition
# def started():
# def stopped():
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment