Commit 963169d4 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'nexedi/fix/hashseed' into erp5-vifib-print-and-hashseed

parents a395d6ce a64a94e8
Pipeline #33350 canceled with stage
in 0 seconds
...@@ -72,4 +72,6 @@ for item_value in value_list: ...@@ -72,4 +72,6 @@ for item_value in value_list:
sub_field_dict[item_key]['value'] = item_value sub_field_dict[item_key]['value'] = item_value
# Return the list of subfield configuration. # Return the list of subfield configuration.
return sub_field_dict.values() return sorted(
sub_field_dict.values(),
key=lambda v: v['title'])
...@@ -90,23 +90,22 @@ Test Account GAP Parallel listfield. ...@@ -90,23 +90,22 @@ Test Account GAP Parallel listfield.
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//label[@for='subfield_field_my_gap_list_my_country/my_accounting_standards' and text()='GAP - My Accounting Standards']</td> <td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//label[@for='subfield_field_my_gap_list_another_country/another_standards' and text()='GAP - Another Standards']</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//p[text()='1 - Equity Accounts']</td> <td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//p[text()='1 - Dummy Account']</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//label[@for='subfield_field_my_gap_list_another_country/another_standards' and text()='GAP - Another Standards']</td> <td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//label[@for='subfield_field_my_gap_list_my_country/my_accounting_standards' and text()='GAP - My Accounting Standards']</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//p[text()='1 - Dummy Account']</td> <td>//div[@data-gadget-scope='field_my_gap_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//p[text()='1 - Equity Accounts']</td>
<td></td> <td></td>
</tr> </tr>
......
...@@ -305,7 +305,8 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -305,7 +305,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
invoice_transaction_movement_1 =\ invoice_transaction_movement_1 =\
invoice_transaction_applied_rule.contentValues()[0] invoice_transaction_applied_rule.contentValues()[0]
self.assertEqual(currency, self.assertEqual(currency,
...@@ -397,7 +398,8 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -397,7 +398,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
invoice_transaction_movement =\ invoice_transaction_movement =\
invoice_transaction_applied_rule.contentValues()[0] invoice_transaction_applied_rule.contentValues()[0]
self.assertEqual(currency, self.assertEqual(currency,
...@@ -684,7 +686,8 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -684,7 +686,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
result_list = [] result_list = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues(): for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getDestinationTotalAssetPrice())) result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getDestinationTotalAssetPrice()))
...@@ -788,7 +791,8 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -788,7 +791,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
result_list = [] result_list = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues(): for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getSourceTotalAssetPrice())) result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getSourceTotalAssetPrice()))
......
...@@ -581,12 +581,24 @@ class TestCurrencyExchangeCell(CurrencyExchangeTestCase): ...@@ -581,12 +581,24 @@ class TestCurrencyExchangeCell(CurrencyExchangeTestCase):
euro_to_usd.setPriceCurrencyValue(usd) euro_to_usd.setPriceCurrencyValue(usd)
self.assertEqual(2, len(euro_to_usd.contentValues())) self.assertEqual(2, len(euro_to_usd.contentValues()))
# cell range is like this: # cell range is like this, matrix cell range does not have ordering
self.assertEqual([ # of the keys, only asCellRange script has.
['currency_exchange_type/type_a', 'currency_exchange_type/type_b'], self.assertEqual(
['resource/%s' % euro.getRelativeUrl()], euro_to_usd.CurrencyExchangeLine_asCellRange(base_id='path'),
['price_currency/%s' % usd.getRelativeUrl()], (
], euro_to_usd.getCellRange(base_id='path')) ['currency_exchange_type/type_a', 'currency_exchange_type/type_b'],
['resource/%s' % euro.getRelativeUrl()],
['price_currency/%s' % usd.getRelativeUrl()],
)
)
self.assertEqual(
[sorted(r) for r in euro_to_usd.getCellRange(base_id='path')],
[
['currency_exchange_type/type_a', 'currency_exchange_type/type_b'],
['resource/%s' % euro.getRelativeUrl()],
['price_currency/%s' % usd.getRelativeUrl()],
]
)
type_a_cell = euro_to_usd.getCell( type_a_cell = euro_to_usd.getCell(
'currency_exchange_type/type_a', 'currency_exchange_type/type_a',
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,13 +37,7 @@ class BaseVariantMovementGroup(MovementGroup): ...@@ -37,13 +37,7 @@ class BaseVariantMovementGroup(MovementGroup):
portal_type = 'Base Variant Movement Group' portal_type = 'Base Variant Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} return {'_base_category_list': sorted(movement.getVariationBaseCategoryList() or [])}
category_list = movement.getVariationBaseCategoryList()
if category_list is None:
category_list = []
category_list.sort()
property_dict['_base_category_list'] = category_list
return property_dict
def test(self, document, property_dict, **kw): def test(self, document, property_dict, **kw):
# This movement group does not affect updating. # This movement group does not affect updating.
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
# #
############################################################################## ##############################################################################
from collections import OrderedDict
from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup
class CategoryMovementGroup(PropertyMovementGroup): class CategoryMovementGroup(PropertyMovementGroup):
...@@ -43,7 +44,7 @@ class CategoryMovementGroup(PropertyMovementGroup): ...@@ -43,7 +44,7 @@ class CategoryMovementGroup(PropertyMovementGroup):
portal_type = 'Category Movement Group' portal_type = 'Category Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} property_dict = OrderedDict()
getProperty = movement.getProperty getProperty = movement.getProperty
for prop in self.getTestedPropertyList(): for prop in self.getTestedPropertyList():
list_prop = prop + '_list' list_prop = prop + '_list'
......
...@@ -38,10 +38,7 @@ class CausalityMovementGroup(MovementGroup): ...@@ -38,10 +38,7 @@ class CausalityMovementGroup(MovementGroup):
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} return {'_explanation': self._getExplanationRelativeUrl(movement)}
explanation_relative_url = self._getExplanationRelativeUrl(movement)
property_dict['_explanation'] = explanation_relative_url
return property_dict
def test(self, movement, property_dict, **kw): def test(self, movement, property_dict, **kw):
# we don't care the difference of explanation url when updating # we don't care the difference of explanation url when updating
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
# #
############################################################################## ##############################################################################
from collections import OrderedDict
from erp5.component.document.MovementGroup import MovementGroup from erp5.component.document.MovementGroup import MovementGroup
from DateTime import DateTime from DateTime import DateTime
from erp5.component.module.DateUtils import atTheEndOfPeriod from erp5.component.module.DateUtils import atTheEndOfPeriod
...@@ -45,7 +46,7 @@ class MonthlyRangeMovementGroup(MovementGroup): ...@@ -45,7 +46,7 @@ class MonthlyRangeMovementGroup(MovementGroup):
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
"""Gather start_date and stop_date, converge them to the end of month. """Gather start_date and stop_date, converge them to the end of month.
""" """
property_dict = {} property_dict = OrderedDict()
for property_name in self.getTestedPropertyList() or ('start_date', 'stop_date'): for property_name in self.getTestedPropertyList() or ('start_date', 'stop_date'):
date = movement.getProperty(property_name, None) date = movement.getProperty(property_name, None)
if date is not None: if date is not None:
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
# #
############################################################################## ##############################################################################
from collections import OrderedDict
from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup
from Products.ERP5Type.Utils import UpperCase from Products.ERP5Type.Utils import UpperCase
...@@ -41,7 +42,7 @@ class ParentDeliveryPropertyMovementGroup(PropertyMovementGroup): ...@@ -41,7 +42,7 @@ class ParentDeliveryPropertyMovementGroup(PropertyMovementGroup):
portal_type = 'Parent Delivery Property Movement Group' portal_type = 'Parent Delivery Property Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} property_dict = OrderedDict()
parent_delivery = self._getParentDelivery(movement) parent_delivery = self._getParentDelivery(movement)
if parent_delivery is not None: if parent_delivery is not None:
for prop in self.getTestedPropertyList(): for prop in self.getTestedPropertyList():
......
...@@ -40,10 +40,7 @@ class ParentExplanationMovementGroup(MovementGroup): ...@@ -40,10 +40,7 @@ class ParentExplanationMovementGroup(MovementGroup):
portal_type = 'Parent Explanation Movement Group' portal_type = 'Parent Explanation Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} return {'parent_explanation_value': movement.getParentExplanationValue()}
parent_explanation_value = movement.getParentExplanationValue()
property_dict['parent_explanation_value'] = parent_explanation_value
return property_dict
def test(self, document, property_dict, **kw): def test(self, document, property_dict, **kw):
if document.getParentExplanationValue() == \ if document.getParentExplanationValue() == \
......
...@@ -26,8 +26,10 @@ ...@@ -26,8 +26,10 @@
# #
############################################################################## ##############################################################################
from collections import OrderedDict
from erp5.component.document.MovementGroup import MovementGroup from erp5.component.document.MovementGroup import MovementGroup
class PropertyMovementGroup(MovementGroup): class PropertyMovementGroup(MovementGroup):
""" """
The purpose of MovementGroup is to define how movements are grouped, The purpose of MovementGroup is to define how movements are grouped,
...@@ -40,7 +42,7 @@ class PropertyMovementGroup(MovementGroup): ...@@ -40,7 +42,7 @@ class PropertyMovementGroup(MovementGroup):
portal_type = 'Property Movement Group' portal_type = 'Property Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} property_dict = OrderedDict()
getProperty = movement.getProperty getProperty = movement.getProperty
for prop in self.getTestedPropertyList(): for prop in self.getTestedPropertyList():
property_dict[prop] = getProperty(prop) property_dict[prop] = getProperty(prop)
......
...@@ -36,7 +36,7 @@ class RequirementMovementGroup(MovementGroup): ...@@ -36,7 +36,7 @@ class RequirementMovementGroup(MovementGroup):
portal_type = 'Requirement Movement Group' portal_type = 'Requirement Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
return {'requirement':self._getRequirementList(movement)} return {'requirement': self._getRequirementList(movement)}
def test(self, movement, property_dict, **kw): def test(self, movement, property_dict, **kw):
# We can always update # We can always update
......
...@@ -39,10 +39,7 @@ class RootAppliedRuleCausalityMovementGroup(MovementGroup): ...@@ -39,10 +39,7 @@ class RootAppliedRuleCausalityMovementGroup(MovementGroup):
portal_type = 'Root Applied Rule Causality Movement Group' portal_type = 'Root Applied Rule Causality Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} return {'root_causality_value_list': [self._getRootCausalityValue(movement)]}
root_causality_value = self._getRootCausalityValue(movement)
property_dict['root_causality_value_list'] = [root_causality_value]
return property_dict
def test(self, movement, property_dict, **kw): def test(self, movement, property_dict, **kw):
# We can always update # We can always update
......
...@@ -37,9 +37,7 @@ class TitleMovementGroup(MovementGroup): ...@@ -37,9 +37,7 @@ class TitleMovementGroup(MovementGroup):
portal_type = 'Title Movement Group' portal_type = 'Title Movement Group'
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
property_dict = {} return {'title': self._getTitle(movement)}
property_dict['title'] = self._getTitle(movement)
return property_dict
def test(self, document, property_dict, **kw): def test(self, document, property_dict, **kw):
# If title is different, we want to update existing document instead # If title is different, we want to update existing document instead
......
...@@ -331,8 +331,6 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -331,8 +331,6 @@ class BuilderMixin(XMLObject, Amount, Predicate):
# 'variation_category' or 'variation_property' pseudo properties, # 'variation_category' or 'variation_property' pseudo properties,
# which rely on the resource being set to discover which # which rely on the resource being set to discover which
# categories/properties to set # categories/properties to set
# XXX-Leo: in the future: using an ordered_dict would be nice,
# but this would have to be respected on Base._edit()
edit_order = [] edit_order = []
property_dict = {'edit_order': edit_order} property_dict = {'edit_order': edit_order}
for d in property_dict_list: for d in property_dict_list:
......
currency_exchange_type_list = context.portal_categories.currency_exchange_type.getCategoryChildRelativeUrlList() currency_exchange_type_list = context.portal_categories.currency_exchange_type.getCategoryChildRelativeUrlList(
local_sort_id=("int_index", "title"))
resource_list = ['resource/%s' % context.getParentValue().getRelativeUrl()] resource_list = ['resource/%s' % context.getParentValue().getRelativeUrl()]
price_currency_list = [context.getPriceCurrency(base=True)] price_currency_list = [context.getPriceCurrency(base=True)]
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>after_script/portal_workflow/movement_resource_interaction_workflow/script_Movement_copyQuantityUnitFromResource</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_setQuantityUnit</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
<tuple>
<string>_setResource.*</string>
</tuple>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<key> <string>categories</string> </key> <key> <string>categories</string> </key>
<value> <value>
<tuple> <tuple>
<string>after_script/portal_workflow/movement_resource_interaction_workflow/script_Movement_copyBaseContributionFromResource</string> <string>after_script/portal_workflow/movement_resource_interaction_workflow/script_Movement_copyCategoryListFromResource</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>interaction_setBaseContribution</string> </value> <value> <string>interaction_setResource</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>after_script/portal_workflow/movement_resource_interaction_workflow/script_Movement_copyUseFromResource</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_setUse</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
<tuple>
<string>_setResource.*</string>
</tuple>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</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>_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>script_Movement_copyBaseContributionFromResource</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -2,6 +2,22 @@ movement = state_change['object'] ...@@ -2,6 +2,22 @@ movement = state_change['object']
resource = movement.getResourceValue() resource = movement.getResourceValue()
if resource is not None: if resource is not None:
# quantity unit can be acquired from resource (see Amount.getQuantityUnit)
# we check that it's really set on the movement.
if movement.hasQuantityUnit():
# if the movement already have a quantity unit which is valid for this resource, don't change it
if movement.getQuantityUnit() not in resource.getQuantityUnitList():
movement.setQuantityUnit(resource.getDefaultQuantityUnit())
else:
# initialise to the default quantity unit
movement.setQuantityUnit(resource.getDefaultQuantityUnit())
# if the movement already have a use which is valid for this resource, don't change it.
# ( unlike quantity unit, use is not acquired )
if movement.getUse() not in resource.getUseList():
# otherwise initialise to the default use
movement.setUse(resource.getDefaultUse())
# We can over-write base contribution list always. # We can over-write base contribution list always.
# Because when we change the resource, we need to set all the base contribution into movement. # Because when we change the resource, we need to set all the base contribution into movement.
# Imagine that we buy a product which have complex tax definitions with discounting. # Imagine that we buy a product which have complex tax definitions with discounting.
......
...@@ -60,9 +60,15 @@ ...@@ -60,9 +60,15 @@
</tuple> </tuple>
</value> </value>
</item> </item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>script_Movement_copyQuantityUnitFromResource</string> </value> <value> <string>script_Movement_copyCategoryListFromResource</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
......
movement = state_change['object']
resource = movement.getResourceValue()
if resource is not None:
# quantity unit can be acquired from resource.
# (Amount class has getQuantityUnit method for backward compatibility and it tries to acquire value from resource).
if movement.hasCategory('quantity_unit'):
# if the movement already have a quantity unit which is valid for this resource, don't change it
movement_quantity_unit = movement.getQuantityUnit()
if movement_quantity_unit and movement_quantity_unit in resource.getQuantityUnitList():
return
# otherwise initialise to the default quantity unit
movement.setQuantityUnit(resource.getDefaultQuantityUnit())
movement = state_change['object']
resource = movement.getResourceValue()
if resource is not None:
# if the movement already have a use which is valid for this resource, don't change it
movement_use = movement.getUse()
if movement_use and movement_use in resource.getUseList():
return
# otherwise initialise to the default use
movement.setUse(resource.getDefaultUse())
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</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>_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>script_Movement_copyUseFromResource</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Form.AudioField import AudioField from Products.ERP5Form.AudioField import AudioField
from Products.ERP5Type.tests.utils import canonical_html
class TestAudioField(ERP5TypeTestCase): class TestAudioField(ERP5TypeTestCase):
...@@ -44,9 +45,10 @@ class TestAudioField(ERP5TypeTestCase): ...@@ -44,9 +45,10 @@ class TestAudioField(ERP5TypeTestCase):
def test_render_view(self): def test_render_view(self):
self.field.values['default'] = 'Audio content' self.field.values['default'] = 'Audio content'
self.assertEqual('<audio preload="preload" src="Audio content" ' + self.assertEqual(
'controls="controls" >Your browser does not ' + canonical_html(self.field.render_view(value='Audio content')),
'support audio tag.</audio>', self.field.render_view(value='Audio content')) '<audio controls="controls" preload="preload" src="Audio content"'
+ '>Your browser does not support audio tag.</audio>',)
self.field.values['audio_preload'] = False self.field.values['audio_preload'] = False
self.field.values['audio_loop'] = True self.field.values['audio_loop'] = True
...@@ -54,13 +56,7 @@ class TestAudioField(ERP5TypeTestCase): ...@@ -54,13 +56,7 @@ class TestAudioField(ERP5TypeTestCase):
self.field.values['audio_autoplay'] = True self.field.values['audio_autoplay'] = True
self.field.values['audio_error_message'] = 'Another error message' self.field.values['audio_error_message'] = 'Another error message'
self.assertEqual('<audio src="Another Audio content" ' + self.assertEqual(
'loop="loop" autoplay="autoplay" >Another error ' + canonical_html(self.field.render_view(value='Another Audio content')),
'message</audio>', self.field.render_view(value='Another Audio content')) '<audio autoplay="autoplay" loop="loop" src="Another Audio content"'
+ '>Another error message</audio>')
import unittest
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestAudioField))
return suite
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
# #
############################################################################## ##############################################################################
import collections
import pprint import pprint
import httplib import httplib
import urlparse import urlparse
...@@ -206,9 +207,15 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -206,9 +207,15 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
actions = self.portal.portal_actions.listFilteredActionsFor(target) actions = self.portal.portal_actions.listFilteredActionsFor(target)
got = {} got = {}
for category, actions in actions.items(): for category, actions in actions.items():
actions_by_priority = collections.defaultdict(list)
got[category] = [dict(title=action['title'], id=action['id']) got[category] = [dict(title=action['title'], id=action['id'])
for action in actions for action in actions
if action['visible']] if action['visible']]
for action in actions:
actions_by_priority[action['priority']].append(actions)
for actions in actions_by_priority.values():
if len(actions) > 1:
self.assertFalse(actions) # no actions with same priority
msg = ("Actions do not match. Expected:\n%s\n\nGot:\n%s\n" % msg = ("Actions do not match. Expected:\n%s\n\nGot:\n%s\n" %
(pprint.pformat(expected), pprint.pformat(got))) (pprint.pformat(expected), pprint.pformat(got)))
self.assertEqual(expected, got, msg) self.assertEqual(expected, got, msg)
...@@ -222,8 +229,6 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -222,8 +229,6 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
'id': 'category_tool'}, 'id': 'category_tool'},
{'title': 'Manage Callables', {'title': 'Manage Callables',
'id': 'callable_tool'}, 'id': 'callable_tool'},
{'title': 'Create Module',
'id': 'create_module'},
{'title': 'Configure Portal Types', {'title': 'Configure Portal Types',
'id': 'types_tool'}, 'id': 'types_tool'},
{'id': 'property_sheet_tool', {'id': 'property_sheet_tool',
...@@ -232,7 +237,9 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -232,7 +237,9 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
'title': 'Configure Portal Catalog'}, 'title': 'Configure Portal Catalog'},
{'id': 'portal_alarms_action', {'id': 'portal_alarms_action',
'title': 'Configure Alarms'}, 'title': 'Configure Alarms'},
{'title': 'Undo', 'id': 'undo'}], {'title': 'Undo', 'id': 'undo'},
{'title': 'Create Module',
'id': 'create_module'},],
'object': [], 'object': [],
'object_action': [{'id': 'diff_object_action', 'title': 'Diff Object'}], 'object_action': [{'id': 'diff_object_action', 'title': 'Diff Object'}],
'object_exchange': [{'id': 'csv_export', 'title': 'Export Csv File'}, # erp5_csv_style 'object_exchange': [{'id': 'csv_export', 'title': 'Export Csv File'}, # erp5_csv_style
......
...@@ -740,7 +740,7 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): ...@@ -740,7 +740,7 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
# the list. # the list.
self.assertEqual(person.getDefaultRegion(), 'beta') self.assertEqual(person.getDefaultRegion(), 'beta')
person.setRegionSet(['alpha', 'beta', 'alpha']) person.setRegionSet(['alpha', 'beta', 'alpha'])
self.assertEqual(person.getRegionList(), ['beta', 'alpha']) self.assertEqual(sorted(person.getRegionList()), ['alpha', 'beta'])
# calling a set setter did not change the default region # calling a set setter did not change the default region
self.assertEqual(person.getDefaultRegion(), 'beta') self.assertEqual(person.getDefaultRegion(), 'beta')
...@@ -2185,6 +2185,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): ...@@ -2185,6 +2185,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
checked_permission=checked_permission) checked_permission=checked_permission)
self.assertSameSet([beta_path, gamma_path], foo.getRegionList()) self.assertSameSet([beta_path, gamma_path], foo.getRegionList())
foo.setRegionList([beta_path])
foo.setRegionSet([gamma_path, beta_path])
self.assertEqual(foo.getRegionList(), [beta_path, gamma_path])
foo.setRegionValue(None) foo.setRegionValue(None)
self.assertEqual(None, foo.getRegion()) self.assertEqual(None, foo.getRegion())
# Check setCategoryValueSet accessor # Check setCategoryValueSet accessor
...@@ -2199,6 +2203,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): ...@@ -2199,6 +2203,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
checked_permission=checked_permission) checked_permission=checked_permission)
self.assertSameSet([beta_path, gamma_path], foo.getRegionList()) self.assertSameSet([beta_path, gamma_path], foo.getRegionList())
foo.setRegionValueList([beta])
foo.setRegionValueSet([gamma, beta])
self.assertEqual(foo.getRegionValueList(), [beta, gamma])
# check hasCategory accessors # check hasCategory accessors
foo.setRegionValue(None) foo.setRegionValue(None)
self.assertEqual(None, foo.getRegion()) self.assertEqual(None, foo.getRegion())
...@@ -2385,6 +2393,18 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): ...@@ -2385,6 +2393,18 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
self.assertEqual('Could not get object region/gamma', self.assertEqual('Could not get object region/gamma',
logged_errors[0].getMessage()) logged_errors[0].getMessage())
def test_portal_type_property_sheet_have_priority_over_class_property_sheet(self):
self._addProperty(
'Person',
self.id(),
'first_name',
elementary_type='string',
property_default='string:property sheet default',
portal_type='Standard Property',
)
obj = self.getPersonModule().newContent(portal_type='Person')
self.assertEqual(obj.getFirstName(), 'property sheet default')
def test_list_accessors(self): def test_list_accessors(self):
self._addProperty('Person', 'test_list_accessors', 'dummy', self._addProperty('Person', 'test_list_accessors', 'dummy',
elementary_type='lines', elementary_type='lines',
...@@ -2406,12 +2426,23 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): ...@@ -2406,12 +2426,23 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
person.setDummyList(['a', 'b']) person.setDummyList(['a', 'b'])
self.assertEqual(person.getDummy(), 'a') self.assertEqual(person.getDummy(), 'a')
self.assertEqual(person.getDummyList(), ['a', 'b']) self.assertEqual(person.getDummyList(), ['a', 'b'])
self.assertEqual(person.getDummySet(), ['a', 'b']) self.assertEqual(sorted(person.getDummySet()), ['a', 'b'])
person.setDummySet(['b', 'a', 'c'])
self.assertEqual(person.getDummy(), 'a')
self.assertEqual(sorted(person.getDummyList()), ['a', 'b', 'c'])
person.setDummySet(['b', 'c'])
self.assertEqual(sorted(person.getDummyList()), ['b', 'c'])
person.setDummyList(['a', 'b', 'b'])
self.assertEqual(person.getDummy(), 'a')
self.assertEqual(person.getDummyList(), ['a', 'b', 'b'])
self.assertEqual(sorted(person.getDummySet()), ['a', 'b'])
person.setDummy('value') person.setDummy('value')
self.assertEqual(person.getDummy(), 'value') self.assertEqual(person.getDummy(), 'value')
self.assertEqual(person.getDummyList(), ['value']) self.assertEqual(person.getDummyList(), ['value'])
self.assertEqual(person.getDummySet(), ['value']) self.assertEqual(sorted(person.getDummySet()), ['value'])
def test_translated_accessors(self): def test_translated_accessors(self):
self._addProperty('Person', self._addProperty('Person',
...@@ -3335,6 +3366,13 @@ def test_suite(): ...@@ -3335,6 +3366,13 @@ def test_suite():
pass pass
else: else:
import ZPublisher.tests.test_WSGIPublisher import ZPublisher.tests.test_WSGIPublisher
# TestLoadApp tests are confused because running as a live test interfere with
# transaction system. Aborting the transaction at beginning of test seems OK.
TestLoadApp_setUp = ZPublisher.tests.test_WSGIPublisher.TestLoadApp.setUp
def setUp(self):
TestLoadApp_setUp(self)
transaction.abort()
ZPublisher.tests.test_WSGIPublisher.TestLoadApp.setUp = setUp
add_tests(suite, ZPublisher.tests.test_WSGIPublisher) add_tests(suite, ZPublisher.tests.test_WSGIPublisher)
import ZPublisher.tests.test_mapply import ZPublisher.tests.test_mapply
......
...@@ -45,7 +45,7 @@ from Products.ERP5Form.CaptchaField import CaptchaField ...@@ -45,7 +45,7 @@ from Products.ERP5Form.CaptchaField import CaptchaField
from Products.ERP5Form.EditorField import EditorField from Products.ERP5Form.EditorField import EditorField
from Products.Formulator.MethodField import Method from Products.Formulator.MethodField import Method
from Products.Formulator.TALESField import TALESMethod from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.tests.utils import canonical_html
from Products.ERP5Type.Core.Folder import Folder from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import field_value_cache from Products.ERP5Form.Form import field_value_cache
from Products.ERP5Form.Form import getFieldValue from Products.ERP5Form.Form import getFieldValue
...@@ -1180,8 +1180,11 @@ class TestCaptchaField(ERP5TypeTestCase): ...@@ -1180,8 +1180,11 @@ class TestCaptchaField(ERP5TypeTestCase):
def test_numeric_good_captcha(self): def test_numeric_good_captcha(self):
self.field.values['captcha_type'] = 'numeric' self.field.values['captcha_type'] = 'numeric'
def random_choice(seq):
self.assertIn('+', seq)
return '+'
with mock.patch('Products.ERP5Form.CaptchaField.random.randint', return_value=1), \ with mock.patch('Products.ERP5Form.CaptchaField.random.randint', return_value=1), \
mock.patch('Products.ERP5Form.CaptchaField.random.choice', side_effect=lambda seq: seq[0]): mock.patch('Products.ERP5Form.CaptchaField.random.choice', side_effect=random_choice):
field_html = self.field.render(REQUEST=self.portal.REQUEST) field_html = self.field.render(REQUEST=self.portal.REQUEST)
self.assertIn('1 plus 1', field_html) self.assertIn('1 plus 1', field_html)
self.assertIn(hashlib.md5(b'1 + 1').hexdigest(), field_html) self.assertIn(hashlib.md5(b'1 + 1').hexdigest(), field_html)
...@@ -1197,8 +1200,11 @@ class TestCaptchaField(ERP5TypeTestCase): ...@@ -1197,8 +1200,11 @@ class TestCaptchaField(ERP5TypeTestCase):
def test_numeric_bad_captcha(self): def test_numeric_bad_captcha(self):
self.field.values['captcha_type'] = 'numeric' self.field.values['captcha_type'] = 'numeric'
def random_choice(seq):
self.assertIn('+', seq)
return '+'
with mock.patch('Products.ERP5Form.CaptchaField.random.randint', return_value=1), \ with mock.patch('Products.ERP5Form.CaptchaField.random.randint', return_value=1), \
mock.patch('Products.ERP5Form.CaptchaField.random.choice', side_effect=lambda seq: seq[0]): mock.patch('Products.ERP5Form.CaptchaField.random.choice', side_effect=random_choice):
self.field.render(REQUEST=self.portal.REQUEST) self.field.render(REQUEST=self.portal.REQUEST)
self.assertRaises( self.assertRaises(
ValidationError, self.validator.validate, self.field, 'field_test', { ValidationError, self.validator.validate, self.field, 'field_test', {
...@@ -1269,8 +1275,8 @@ class TestEditorField(ERP5TypeTestCase): ...@@ -1269,8 +1275,8 @@ class TestEditorField(ERP5TypeTestCase):
def test_render_editable_textarea(self): def test_render_editable_textarea(self):
self.field.values['default'] = 'value' self.field.values['default'] = 'value'
self.assertEqual( self.assertEqual(
self.field.render(REQUEST=self.portal.REQUEST), canonical_html(self.field.render(REQUEST=self.portal.REQUEST)),
'<textarea rows="5" cols="40" name="field_test_field" >\nvalue</textarea>') '<textarea cols="40" name="field_test_field" rows="5">\nvalue</textarea>')
def test_render_editable_textarea_REQUEST(self): def test_render_editable_textarea_REQUEST(self):
self.field.values['default'] = 'default value' self.field.values['default'] = 'default value'
...@@ -1279,8 +1285,8 @@ class TestEditorField(ERP5TypeTestCase): ...@@ -1279,8 +1285,8 @@ class TestEditorField(ERP5TypeTestCase):
self.field.generate_field_key(key=self.field.id) self.field.generate_field_key(key=self.field.id)
] = 'user <value>' ] = 'user <value>'
self.assertEqual( self.assertEqual(
self.field.render(REQUEST=self.portal.REQUEST), canonical_html(self.field.render(REQUEST=self.portal.REQUEST)),
'<textarea rows="5" cols="40" name="field_test_field" >\nuser &lt;value&gt;</textarea>') '<textarea cols="40" name="field_test_field" rows="5">\nuser &lt;value&gt;</textarea>')
def test_render_non_editable_textarea(self): def test_render_non_editable_textarea(self):
self.field.values['default'] = '<not &scaped' self.field.values['default'] = '<not &scaped'
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Form.VideoField import VideoField from Products.ERP5Form.VideoField import VideoField
from Products.ERP5Type.tests.utils import canonical_html
class TestVideoField(ERP5TypeTestCase): class TestVideoField(ERP5TypeTestCase):
"""Tests Video field """Tests Video field
...@@ -43,8 +45,11 @@ class TestVideoField(ERP5TypeTestCase): ...@@ -43,8 +45,11 @@ class TestVideoField(ERP5TypeTestCase):
def test_render_view(self): def test_render_view(self):
self.field.values['default'] = 'Video content' self.field.values['default'] = 'Video content'
self.assertEqual('<video preload="auto" src="Video content" controls="controls" height="85" width="160" >Your browser does not support video tag.</video>', \ self.assertEqual(
self.field.render_view(value='Video content')) canonical_html(self.field.render_view(value='Video content')),
'<video controls="controls" height="85" preload="auto" src="Video content"'
+ ' width="160">Your browser does not support video tag.</video>',
)
self.field.values['video_preload'] = False self.field.values['video_preload'] = False
self.field.values['video_loop'] = True self.field.values['video_loop'] = True
...@@ -54,14 +59,9 @@ class TestVideoField(ERP5TypeTestCase): ...@@ -54,14 +59,9 @@ class TestVideoField(ERP5TypeTestCase):
self.field.values['video_height'] = 800 self.field.values['video_height'] = 800
self.field.values['video_width'] = 1280 self.field.values['video_width'] = 1280
self.assertEqual('<video src="Another Video content" ' +
'height="800" width="1280" loop="loop" autoplay="autoplay" ' +
'>Another error message</video>', \
self.field.render_view(value='Another Video content'))
import unittest
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestVideoField))
return suite
self.assertEqual(
canonical_html(self.field.render_view(value='Another Video content')),
'<video autoplay="autoplay" height="800" loop="loop"'
+ ' src="Another Video content" width="1280">Another error message</video>'
)
import json import json
return ['test report %s' % json.dumps(kw)] return ['test report %s' % json.dumps(kw, sort_keys=True)]
...@@ -206,11 +206,11 @@ class TestCorporateIdentityMethod(ERP5TypeTestCase): ...@@ -206,11 +206,11 @@ class TestCorporateIdentityMethod(ERP5TypeTestCase):
# it has no matter with/without follow up # it has no matter with/without follow up
doc_content = '<div> <a href="sale_opportunity_module/template_test_embed_sale_opportunity?report=Base_generateCorporareIdentityTestReport&amp;test=23"></a> </div>' doc_content = '<div> <a href="sale_opportunity_module/template_test_embed_sale_opportunity?report=Base_generateCorporareIdentityTestReport&amp;test=23"></a> </div>'
output =web_page_with_follow_up.WebPage_embedReportDocumentList(doc_content) output =web_page_with_follow_up.WebPage_embedReportDocumentList(doc_content)
self.assertEqual(output, '<div> test report {"test": "23", "document_language": null, "format": null} </div>') self.assertEqual(output, '<div> test report {"document_language": null, "format": null, "test": "23"} </div>')
doc_content = '<div> <a href="sale_opportunity_module/template_test_embed_sale_opportunity?report=Base_generateCorporareIdentityTestReport&amp;test=23"></a> </div>' doc_content = '<div> <a href="sale_opportunity_module/template_test_embed_sale_opportunity?report=Base_generateCorporareIdentityTestReport&amp;test=23"></a> </div>'
output =web_page_no_follow_up.WebPage_embedReportDocumentList(doc_content) output =web_page_no_follow_up.WebPage_embedReportDocumentList(doc_content)
self.assertEqual(output, '<div> test report {"test": "23", "document_language": null, "format": null} </div>') self.assertEqual(output, '<div> test report {"document_language": null, "format": null, "test": "23"} </div>')
def test_getTemplateProxyParameter_override_person(self): def test_getTemplateProxyParameter_override_person(self):
output_dict_list = self.test_person.Base_getTemplateProxyParameter( output_dict_list = self.test_person.Base_getTemplateProxyParameter(
......
...@@ -305,12 +305,12 @@ def Base_runJupyterCode(self, jupyter_code, old_notebook_context): ...@@ -305,12 +305,12 @@ def Base_runJupyterCode(self, jupyter_code, old_notebook_context):
# Whenever we have new imports we need to warn the user about the # Whenever we have new imports we need to warn the user about the
# environment # environment
if (import_fixer.warning_module_names != []): if (import_fixer.warning_module_names != []):
warning = ("print '" warning = ("print ('"
"WARNING: You imported from the modules %s without " "WARNING: You imported from the modules %s without "
"using the environment object, which is not recomended. " "using the environment object, which is not recomended. "
"Your import was automatically converted to use such method. " "Your import was automatically converted to use such method. "
"The setup functions were named as *module*_setup. " "The setup functions were named as *module*_setup. "
"'") % (', '.join(import_fixer.warning_module_names)) "')") % (', '.join(import_fixer.warning_module_names))
tree = ast.parse(warning) tree = ast.parse(warning)
tree.body[0].lineno = ast_node.body[-1].lineno+5 tree.body[0].lineno = ast_node.body[-1].lineno+5
ast_node.body.append(tree.body[0]) ast_node.body.append(tree.body[0])
......
...@@ -102,7 +102,7 @@ class TestExecuteJupyter(ERP5TypeTestCase): ...@@ -102,7 +102,7 @@ class TestExecuteJupyter(ERP5TypeTestCase):
python_script = """ python_script = """
portal = context.getPortalObject() portal = context.getPortalObject()
portal.setTitle('%s') portal.setTitle('%s')
print an_undefined_variable print(an_undefined_variable)
"""%new_test_title """%new_test_title
# Create python_script object with the above given code and containers # Create python_script object with the above given code and containers
...@@ -254,7 +254,7 @@ portal.%s() ...@@ -254,7 +254,7 @@ portal.%s()
""" """
portal = self.portal portal = self.portal
self.login('dev_user') self.login('dev_user')
python_expression = "print 52" python_expression = "print(52)"
reference = 'Test.Notebook.AddNewNotebookLine %s' % time.time() reference = 'Test.Notebook.AddNewNotebookLine %s' % time.time()
title = 'Test NB Title %s' % time.time() title = 'Test NB Title %s' % time.time()
...@@ -313,7 +313,7 @@ portal.%s() ...@@ -313,7 +313,7 @@ portal.%s()
""" """
portal = self.portal portal = self.portal
self.login('dev_user') self.login('dev_user')
python_expression = 'a=2; b=3; print a+b' python_expression = 'a=2; b=3; print(a+b)'
reference = 'Test.Notebook.ExecutePythonExpressionWithVariables %s' % time.time() reference = 'Test.Notebook.ExecutePythonExpressionWithVariables %s' % time.time()
title = 'Test NB Title %s' % time.time() title = 'Test NB Title %s' % time.time()
...@@ -340,7 +340,7 @@ portal.%s() ...@@ -340,7 +340,7 @@ portal.%s()
""" """
portal = self.portal portal = self.portal
self.login('dev_user') self.login('dev_user')
python_expression = 'a=2; b=3; print a+b' python_expression = 'a=2; b=3; print(a+b)'
reference = 'Test.Notebook.ExecutePythonExpressionWithVariables %s' % time.time() reference = 'Test.Notebook.ExecutePythonExpressionWithVariables %s' % time.time()
title = 'Test NB Title %s' % time.time() title = 'Test NB Title %s' % time.time()
...@@ -351,7 +351,7 @@ portal.%s() ...@@ -351,7 +351,7 @@ portal.%s()
) )
self.tic() self.tic()
python_expression = 'x=5; b=4; print a+b+x' python_expression = 'x=5; b=4; print(a+b+x)'
result = portal.Base_executeJupyter( result = portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=python_expression python_expression=python_expression
...@@ -379,7 +379,7 @@ import sys ...@@ -379,7 +379,7 @@ import sys
) )
self.tic() self.tic()
jupyter_code = "print imh.__name__" jupyter_code = "print(imh.__name__)"
result = portal.Base_executeJupyter( result = portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code) python_expression=jupyter_code)
...@@ -392,39 +392,37 @@ import sys ...@@ -392,39 +392,37 @@ import sys
Test the fucntioning of the ERP5ImageProcessor and the custom system Test the fucntioning of the ERP5ImageProcessor and the custom system
display hook too. display hook too.
""" """
self.image_module = self.portal.getDefaultModule('Image')
self.assertTrue(self.image_module is not None)
# Create a new ERP5 image object # Create a new ERP5 image object
reference = 'testBase_displayImageReference5' reference = 'testBase_displayImageReference5'
data_template = '<img src="data:application/unknown;base64,%s" /><br />' data_template = '<img src="data:image/png;base64,%s"'
data = 'qwertyuiopasdfghjklzxcvbnm<somerandomcharacterstosaveasimagedata>' data = bytes(self.portal.restrictedTraverse('images/erp5_logo.png').data)
if getattr(self.image_module, 'testBase_displayImageID5', None) is not None: img = self.portal.image_module.newContent(
self.image_module.manage_delObjects(ids=['testBase_displayImageID5'])
self.image_module.newContent(
portal_type='Image', portal_type='Image',
id='testBase_displayImageID5', id=self.id(),
reference=reference, reference=reference,
data=data, data=data,
filename='test.png' filename='test.png'
) )
def cleanup():
self.portal.image_module.manage_delObjects(ids=[img.getId()])
self.tic()
self.addCleanup(cleanup)
self.tic() self.tic()
# Call Base_displayImage from inside of Base_runJupyter # Call Base_displayImage from inside of Base_runJupyter
jupyter_code = """ jupyter_code = """
image = context.portal_catalog.getResultValue(portal_type='Image',reference='%s') image = context.portal_catalog.getResultValue(portal_type='Image',reference='%s')
context.Base_renderAsHtml(image) context.Base_renderAsHtml(image)
"""%reference """ % reference
notebook_context = {'setup' : {}, 'variables' : {}} notebook_context = {'setup': {}, 'variables': {}}
result = self.portal.Base_runJupyter( result = self.portal.Base_runJupyter(
jupyter_code=jupyter_code, jupyter_code=jupyter_code,
old_notebook_context=notebook_context old_notebook_context=notebook_context
) )
self.assertIn((data_template % base64.b64encode(data).decode()), result['result_string'])
self.assertIn((data_template % base64.b64encode(data)), result['result_string']) self.assertEqual(result['mime_type'], 'text/html')
# Mime_type shouldn't be image/png just because of filename, instead it is self.assertEqual(result['status'], 'ok')
# dependent on file and file data
self.assertNotEqual(result['mime_type'], 'image/png')
def testImportSameModuleDifferentNamespace(self): def testImportSameModuleDifferentNamespace(self):
""" """
...@@ -436,7 +434,7 @@ context.Base_renderAsHtml(image) ...@@ -436,7 +434,7 @@ context.Base_renderAsHtml(image)
# First we execute a jupyter_code which imports sys module as 'ss' namespace # First we execute a jupyter_code which imports sys module as 'ss' namespace
jupyter_code = "import sys as ss" jupyter_code = "import sys as ss"
reference = 'Test.Notebook.MutlipleImports %s' %time.time() reference = 'Test.Notebook.MutlipleImports %s' % time.time()
portal.Base_executeJupyter( portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code python_expression=jupyter_code
...@@ -454,7 +452,7 @@ context.Base_renderAsHtml(image) ...@@ -454,7 +452,7 @@ context.Base_renderAsHtml(image)
# Call Base_executeJupyter to check for the name of module and match it with # Call Base_executeJupyter to check for the name of module and match it with
# namespace 'ss1' # namespace 'ss1'
jupyter_code2 = "print ss1.__name__" jupyter_code2 = "print(ss1.__name__)"
result = portal.Base_executeJupyter( result = portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code2 python_expression=jupyter_code2
...@@ -488,8 +486,8 @@ environment.define(create_sum_machines, 'creates sum function and class') ...@@ -488,8 +486,8 @@ environment.define(create_sum_machines, 'creates sum function and class')
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print sum_function(1, 1) print(sum_function(1, 1))
print Calculator().sum(2, 2) print(Calculator().sum(2, 2))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -517,7 +515,7 @@ environment.define(x='couscous') ...@@ -517,7 +515,7 @@ environment.define(x='couscous')
self.tic() self.tic()
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = 'print x' jupyter_code = 'print(x)'
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code python_expression=jupyter_code
...@@ -566,8 +564,8 @@ environment.undefine('creates sum function and class') ...@@ -566,8 +564,8 @@ environment.undefine('creates sum function and class')
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print 'sum_function' in locals() print('sum_function' in locals())
print 'Calculator' in locals() print('Calculator' in locals())
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
...@@ -603,7 +601,7 @@ environment.define(x='couscous') ...@@ -603,7 +601,7 @@ environment.define(x='couscous')
self.tic() self.tic()
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = "print 'x' in locals()" jupyter_code = "print('x' in locals())"
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code python_expression=jupyter_code
...@@ -630,7 +628,7 @@ import random ...@@ -630,7 +628,7 @@ import random
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print random.randint(1,1) print(random.randint(1,1))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -721,7 +719,7 @@ import random as rand ...@@ -721,7 +719,7 @@ import random as rand
self.assertEqual(json.loads(result)['status'], 'ok') self.assertEqual(json.loads(result)['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print rand.randint(1,1) print(rand.randint(1,1))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -750,7 +748,7 @@ iframe = context.Base_erp5PivotTableUI(my_df) ...@@ -750,7 +748,7 @@ iframe = context.Base_erp5PivotTableUI(my_df)
context.Base_renderAsHtml(iframe) context.Base_renderAsHtml(iframe)
''' '''
reference = 'Test.Notebook.PivotTableJsIntegration %s' % time.time() reference = 'Test.Notebook.PivotTableJsIntegration %s' % time.time()
notebook = self._newNotebook(reference=reference) self._newNotebook(reference=reference)
result = portal.Base_executeJupyter( result = portal.Base_executeJupyter(
reference=reference, reference=reference,
python_expression=jupyter_code python_expression=jupyter_code
...@@ -783,7 +781,7 @@ from string import ascii_lowercase, ascii_uppercase, digits ...@@ -783,7 +781,7 @@ from string import ascii_lowercase, ascii_uppercase, digits
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print ascii_lowercase print(ascii_lowercase)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -796,7 +794,7 @@ print ascii_lowercase ...@@ -796,7 +794,7 @@ print ascii_lowercase
self.assertEqual(result['code_result'].strip(), 'abcdefghijklmnopqrstuvwxyz') self.assertEqual(result['code_result'].strip(), 'abcdefghijklmnopqrstuvwxyz')
jupyter_code = ''' jupyter_code = '''
print ascii_uppercase print(ascii_uppercase)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -809,7 +807,7 @@ print ascii_uppercase ...@@ -809,7 +807,7 @@ print ascii_uppercase
self.assertEqual(result['code_result'].strip(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') self.assertEqual(result['code_result'].strip(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
jupyter_code = ''' jupyter_code = '''
print digits print(digits)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -840,7 +838,7 @@ from string import * ...@@ -840,7 +838,7 @@ from string import *
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print ascii_lowercase print(ascii_lowercase)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -871,7 +869,7 @@ from string import digits as dig ...@@ -871,7 +869,7 @@ from string import digits as dig
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print dig print(dig)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -893,7 +891,7 @@ print dig ...@@ -893,7 +891,7 @@ print dig
notebook_title = u''.join(random.choice(string.ascii_lowercase) for _ in range(30)) notebook_title = u''.join(random.choice(string.ascii_lowercase) for _ in range(30))
notebook_module = self.portal.getDefaultModule(portal_type='Data Notebook') notebook_module = self.portal.getDefaultModule(portal_type='Data Notebook')
data_notebook = notebook_module.DataNotebookModule_addDataNotebook( notebook_module.DataNotebookModule_addDataNotebook(
title=notebook_title, title=notebook_title,
reference=notebook_reference, reference=notebook_reference,
batch_mode=True) batch_mode=True)
...@@ -921,7 +919,7 @@ import numpy as np ...@@ -921,7 +919,7 @@ import numpy as np
result = json.loads(result) result = json.loads(result)
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print np.random.rand(256, 256, 256) print(np.random.rand(256, 256, 256))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
...@@ -934,7 +932,7 @@ print np.random.rand(256, 256, 256) ...@@ -934,7 +932,7 @@ print np.random.rand(256, 256, 256)
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print np.random.randint(low = 2 ** 63 - 1, size = (256, 256, 256), dtype = 'int64') print(np.random.randint(low = 2 ** 63 - 1, size = (256, 256, 256), dtype = 'int64'))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
...@@ -974,7 +972,7 @@ import datetime ...@@ -974,7 +972,7 @@ import datetime
self.assertEqual(result['code_result'].strip(), expected_result) self.assertEqual(result['code_result'].strip(), expected_result)
jupyter_code = ''' jupyter_code = '''
print np.array([1, 2, 3]) print(np.array([1, 2, 3]))
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
...@@ -1005,7 +1003,7 @@ import os.path ...@@ -1005,7 +1003,7 @@ import os.path
self.assertEqual(result['status'], 'ok') self.assertEqual(result['status'], 'ok')
jupyter_code = ''' jupyter_code = '''
print os.path print(os.path)
''' '''
result = self.portal.Base_executeJupyter( result = self.portal.Base_executeJupyter(
reference=reference, reference=reference,
......
...@@ -74,4 +74,7 @@ class_definition = { ...@@ -74,4 +74,7 @@ class_definition = {
} }
} }
return json.dumps(dict(graph=getBusinessProcessGraph(context), class_definition=class_definition), indent=2) return json.dumps(
dict(graph=getBusinessProcessGraph(context), class_definition=class_definition),
sort_keys=True,
indent=2)
...@@ -89,4 +89,7 @@ class_definition = { ...@@ -89,4 +89,7 @@ class_definition = {
} }
} }
return json.dumps(dict(graph=getDCWorkflowGraph(context), class_definition=class_definition), indent=2) return json.dumps(
dict(graph=getDCWorkflowGraph(context), class_definition=class_definition),
sort_keys=True,
indent=2)
...@@ -64,4 +64,7 @@ def getWorkflowGraph(workflow): ...@@ -64,4 +64,7 @@ def getWorkflowGraph(workflow):
graph['node'][state_id]['coordinate'] = position_graph['node'][state_id]['coordinate'] graph['node'][state_id]['coordinate'] = position_graph['node'][state_id]['coordinate']
return graph return graph
return json.dumps(dict(graph=getWorkflowGraph(context), class_definition={}), indent=2) return json.dumps(
dict(graph=getWorkflowGraph(context), class_definition={}),
sort_keys=True,
indent=2)
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</item> </item>
<item> <item>
<key> <string>height</string> </key> <key> <string>height</string> </key>
<value> <int>284</int> </value> <value> <int>277</int> </value>
</item> </item>
<item> <item>
<key> <string>precondition</string> </key> <key> <string>precondition</string> </key>
......
...@@ -677,7 +677,8 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None, ...@@ -677,7 +677,8 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None,
json.dumps(ensureSerializable({ json.dumps(ensureSerializable({
'original_form_id': form.id, 'original_form_id': form.id,
'field_id': field.id 'field_id': field.id
}))))) }),
sort_keys=True))))
} }
}) })
...@@ -799,9 +800,9 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None, ...@@ -799,9 +800,9 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None,
"form_relative_url": "%s/%s" % (form_relative_url, field.id), "form_relative_url": "%s/%s" % (form_relative_url, field.id),
"list_method": list_method_name, "list_method": list_method_name,
"default_param_json": bytes2str(urlsafe_b64encode(str2bytes( "default_param_json": bytes2str(urlsafe_b64encode(str2bytes(
json.dumps(ensureSerializable(list_method_query_dict))))), json.dumps(ensureSerializable(list_method_query_dict), sort_keys=True)))),
"extra_param_json": bytes2str(urlsafe_b64encode(str2bytes( "extra_param_json": bytes2str(urlsafe_b64encode(str2bytes(
json.dumps(ensureSerializable(extra_param_dict))))) json.dumps(ensureSerializable(extra_param_dict), sort_keys=True))))
} }
# once we imprint `default_params` into query string of 'list method' we # once we imprint `default_params` into query string of 'list method' we
# don't want them to propagate to the query as well # don't want them to propagate to the query as well
...@@ -820,7 +821,7 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None, ...@@ -820,7 +821,7 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None,
"script_id": script.id, "script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"), "relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"list_method": list_method_name, "list_method": list_method_name,
"default_param_json": bytes2str(urlsafe_b64encode(str2bytes(json.dumps(ensureSerializable(list_method_query_dict))))) "default_param_json": bytes2str(urlsafe_b64encode(str2bytes(json.dumps(ensureSerializable(list_method_query_dict), sort_keys=True))))
} }
list_method_query_dict = {} list_method_query_dict = {}
""" """
...@@ -1069,7 +1070,9 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti ...@@ -1069,7 +1070,9 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
'proxy_listbox_id': x, 'proxy_listbox_id': x,
'original_form_id': extra_param_json['original_form_id'], 'original_form_id': extra_param_json['original_form_id'],
'field_id': extra_param_json['field_id'] 'field_id': extra_param_json['field_id']
}))))) }),
sort_keys=True,
))))
}) for x, y in proxy_form_id_list], }) for x, y in proxy_form_id_list],
"first_item": 1, "first_item": 1,
"required": 0, "required": 0,
...@@ -1092,7 +1095,8 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti ...@@ -1092,7 +1095,8 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
'proxy_listbox_id': REQUEST.get('proxy_listbox_id', None), 'proxy_listbox_id': REQUEST.get('proxy_listbox_id', None),
'original_form_id': extra_param_json['original_form_id'], 'original_form_id': extra_param_json['original_form_id'],
'field_id': extra_param_json['field_id'] 'field_id': extra_param_json['field_id']
}))))) }),
sort_keys=True))))
} }
# Go through all groups ("left", "bottom", "hidden" etc.) and add fields from # Go through all groups ("left", "bottom", "hidden" etc.) and add fields from
...@@ -1591,7 +1595,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1591,7 +1595,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
"script_id": script.id, # this script (ERP5Document_getHateoas) "script_id": script.id, # this script (ERP5Document_getHateoas)
"relative_url": getRealRelativeUrl(traversed_document).replace("/", "%2F"), "relative_url": getRealRelativeUrl(traversed_document).replace("/", "%2F"),
"view": erp5_action_list[-1]['name'], "view": erp5_action_list[-1]['name'],
"extra_param_json": bytes2str(urlsafe_b64encode(str2bytes(json.dumps(ensureSerializable(extra_param_json))))) "extra_param_json": bytes2str(urlsafe_b64encode(str2bytes(json.dumps(ensureSerializable(extra_param_json), sort_keys=True))))
} }
if erp5_action_list: if erp5_action_list:
...@@ -1758,7 +1762,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1758,7 +1762,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
'relative_url': relative_url, 'relative_url': relative_url,
'group_by': group_by, 'group_by': group_by,
'sort_on': sort_on 'sort_on': sort_on
}))))) }),
sort_keys=True,
))))
# set 'here' for field rendering which contain TALES expressions # set 'here' for field rendering which contain TALES expressions
REQUEST.set('here', traversed_document) REQUEST.set('here', traversed_document)
...@@ -2205,7 +2211,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -2205,7 +2211,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
"relative_url": url_parameter_dict['view_kw']['jio_key'].replace("/", "%2F"), "relative_url": url_parameter_dict['view_kw']['jio_key'].replace("/", "%2F"),
"view": url_parameter_dict['view_kw']['view'], "view": url_parameter_dict['view_kw']['view'],
"extra_param_json": bytes2str(urlsafe_b64encode(str2bytes( "extra_param_json": bytes2str(urlsafe_b64encode(str2bytes(
json.dumps(ensureSerializable(extra_url_param_dict))))) json.dumps(ensureSerializable(extra_url_param_dict), sort_keys=True))))
} }
# endfor select # endfor select
...@@ -2353,7 +2359,8 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -2353,7 +2359,8 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
checkPermission = portal.Base_checkPermission checkPermission = portal.Base_checkPermission
work_list = [] work_list = []
for action in action_list: for action in action_list:
query = sql_catalog.buildQuery(action['query'])\ # sort the query for easier testing
query = sql_catalog.buildQuery(OrderedDict(sorted(action['query'].items())))\
.asSearchTextExpression(sql_catalog) .asSearchTextExpression(sql_catalog)
if (action['local_roles']): if (action['local_roles']):
...@@ -2423,7 +2430,7 @@ if mode == 'url_generator': ...@@ -2423,7 +2430,7 @@ if mode == 'url_generator':
else: else:
generator_key = 'traverse_generator_action' generator_key = 'traverse_generator_action'
keep_items_json = bytes2str(urlsafe_b64encode(str2bytes( keep_items_json = bytes2str(urlsafe_b64encode(str2bytes(
json.dumps(ensureSerializable(keep_items))))) json.dumps(ensureSerializable(keep_items), sort_keys=True))))
return url_template_dict[generator_key] % { return url_template_dict[generator_key] % {
"root_url": site_root.absolute_url(), "root_url": site_root.absolute_url(),
"script_id": 'ERP5Document_getHateoas', "script_id": 'ERP5Document_getHateoas',
......
...@@ -30,8 +30,8 @@ ...@@ -30,8 +30,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,4 +37,7 @@ for i, tracking in enumerate(reversed(portal.portal_simulation.getTrackingList(a ...@@ -37,4 +37,7 @@ for i, tracking in enumerate(reversed(portal.portal_simulation.getTrackingList(a
link=movement.absolute_url(), link=movement.absolute_url(),
source=movement.getSourceUid() or "null", source=movement.getSourceUid() or "null",
destination=movement.getDestinationUid() or "null") destination=movement.getDestinationUid() or "null")
return json.dumps(dict(graph=graph, class_definition=class_definition), indent=2) return json.dumps(
dict(graph=graph, class_definition=class_definition),
sort_keys=True,
indent=2)
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
</item> </item>
<item> <item>
<key> <string>priority</string> </key> <key> <string>priority</string> </key>
<value> <float>6.0</float> </value> <value> <float>20.0</float> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
# #
############################################################################## ##############################################################################
import six.moves.urllib.parse
import uuid import uuid
import mock import mock
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
...@@ -92,11 +93,15 @@ class TestFacebookLogin(ERP5TypeTestCase): ...@@ -92,11 +93,15 @@ class TestFacebookLogin(ERP5TypeTestCase):
self.logout() self.logout()
self.portal.ERP5Site_redirectToFacebookLoginPage() self.portal.ERP5Site_redirectToFacebookLoginPage()
location = self.portal.REQUEST.RESPONSE.getHeader("Location") location = self.portal.REQUEST.RESPONSE.getHeader("Location")
self.assertIn("https://www.facebook.com/v2.10/dialog/oauth?", location)
self.assertIn("scope=email&redirect_uri=", location)
self.assertIn("client_id=%s" % CLIENT_ID, location)
self.assertNotIn("secret_key=", location)
self.assertIn("ERP5Site_callbackFacebookLogin", location) self.assertIn("ERP5Site_callbackFacebookLogin", location)
parsed_location = six.moves.urllib.parse.urlparse(location)
self.assertEqual(parsed_location.hostname, 'www.facebook.com')
self.assertEqual(parsed_location.path, '/v2.10/dialog/oauth')
params = dict(six.moves.urllib.parse.parse_qsl(parsed_location.query))
self.assertEqual(params['scope'], 'email')
self.assertEqual(params['client_id'], CLIENT_ID)
self.assertIn("redirect_uri", params)
self.assertNotIn("secret_key", params)
def test_existing_user(self): def test_existing_user(self):
self.login() self.login()
......
...@@ -116,7 +116,7 @@ social_contribution_stop_date = None ...@@ -116,7 +116,7 @@ social_contribution_stop_date = None
for employee_result in paysheet_data_list: for employee_result in paysheet_data_list:
employee_ctp = employee_result['ctp'] employee_ctp = employee_result['ctp']
for ctp_code in employee_ctp: for ctp_code in employee_ctp:
if social_contribution_organisation is None: if social_contribution_organisation is None and 'corporate_registration_code' in employee_ctp[ctp_code]:
social_contribution_organisation = employee_ctp[ctp_code]['corporate_registration_code'] social_contribution_organisation = employee_ctp[ctp_code]['corporate_registration_code']
social_contribution_start_date = employee_ctp[ctp_code]['start_date'] social_contribution_start_date = employee_ctp[ctp_code]['start_date']
social_contribution_stop_date = employee_ctp[ctp_code]['stop_date'] social_contribution_stop_date = employee_ctp[ctp_code]['stop_date']
...@@ -284,13 +284,13 @@ for employee_data_dict, paysheet_data_dict in employee_result_list: ...@@ -284,13 +284,13 @@ for employee_data_dict, paysheet_data_dict in employee_result_list:
for remuneration_block in paysheet_data_dict['remuneration']: for remuneration_block in paysheet_data_dict['remuneration']:
dsn_file.append(remuneration_block) dsn_file.append(remuneration_block)
for bonus_category in sorted(six.itervalues(paysheet_data_dict['other_bonus'])): for bonus_category in sorted(six.itervalues(paysheet_data_dict['other_bonus']), key=lambda v: (v['code'],)):
dsn_file.append(getDSNBlockDict(block_id='S21.G00.52', target=bonus_category)) dsn_file.append(getDSNBlockDict(block_id='S21.G00.52', target=bonus_category))
for bonus_category in sorted(six.itervalues(paysheet_data_dict['other_income'])): for bonus_category in sorted(six.itervalues(paysheet_data_dict['other_income']), key=lambda v: (v['code'],)):
dsn_file.append(getDSNBlockDict(block_id='S21.G00.54', target=bonus_category)) dsn_file.append(getDSNBlockDict(block_id='S21.G00.54', target=bonus_category))
for taxable_base_category in sorted(six.itervalues(paysheet_data_dict['taxable_base'])): for taxable_base_category in sorted(six.itervalues(paysheet_data_dict['taxable_base']), key=lambda v: (v['code'], v['contract_id'],)):
dsn_file.append(getDSNBlockDict(block_id='S21.G00.78', target=taxable_base_category)) dsn_file.append(getDSNBlockDict(block_id='S21.G00.78', target=taxable_base_category))
if taxable_base_category['code'] == '02': # Assiette Brute plafonnee if taxable_base_category['code'] == '02': # Assiette Brute plafonnee
if ('063', '') in paysheet_data_dict['individual_contribution']: if ('063', '') in paysheet_data_dict['individual_contribution']:
......
...@@ -205,19 +205,6 @@ S21.G00.54.001,'18' ...@@ -205,19 +205,6 @@ S21.G00.54.001,'18'
S21.G00.54.002,'23.00' S21.G00.54.002,'23.00'
S21.G00.54.003,'01012015' S21.G00.54.003,'01012015'
S21.G00.54.004,'31012015' S21.G00.54.004,'31012015'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'94.91'
S21.G00.78.001,'02' S21.G00.78.001,'02'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
...@@ -236,10 +223,27 @@ S21.G00.81.001,'226' ...@@ -236,10 +223,27 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011' S21.G00.81.002,'75367340900011'
S21.G00.81.003,'2250.00' S21.G00.81.003,'2250.00'
S21.G00.81.005,'59378' S21.G00.81.005,'59378'
S21.G00.78.001,'04'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'2305.53'
S21.G00.78.001,'07' S21.G00.78.001,'07'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
S21.G00.78.004,'2250.00' S21.G00.78.004,'2250.00'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'94.91'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'31' S21.G00.78.001,'31'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
...@@ -249,10 +253,6 @@ S21.G00.79.001,'11' ...@@ -249,10 +253,6 @@ S21.G00.79.001,'11'
S21.G00.79.004,'2250.00' S21.G00.79.004,'2250.00'
S21.G00.81.001,'059' S21.G00.81.001,'059'
S21.G00.81.004,'15.75' S21.G00.81.004,'15.75'
S21.G00.78.001,'04'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'2305.53'
S21.G00.81.001,'018' S21.G00.81.001,'018'
S21.G00.81.002,'75367340900011' S21.G00.81.002,'75367340900011'
S21.G00.81.003,'2250.00' S21.G00.81.003,'2250.00'
...@@ -335,19 +335,6 @@ S21.G00.54.001,'17' ...@@ -335,19 +335,6 @@ S21.G00.54.001,'17'
S21.G00.54.002,'62.40' S21.G00.54.002,'62.40'
S21.G00.54.003,'01012015' S21.G00.54.003,'01012015'
S21.G00.54.004,'31012015' S21.G00.54.004,'31012015'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'94.07'
S21.G00.78.001,'02' S21.G00.78.001,'02'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
...@@ -364,10 +351,27 @@ S21.G00.81.001,'226' ...@@ -364,10 +351,27 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011' S21.G00.81.002,'75367340900011'
S21.G00.81.003,'3085.28' S21.G00.81.003,'3085.28'
S21.G00.81.005,'59378' S21.G00.81.005,'59378'
S21.G00.78.001,'04'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'4125.36'
S21.G00.78.001,'07' S21.G00.78.001,'07'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
S21.G00.78.004,'3085.28' S21.G00.78.004,'3085.28'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'94.07'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'31' S21.G00.78.001,'31'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
...@@ -377,10 +381,6 @@ S21.G00.79.001,'11' ...@@ -377,10 +381,6 @@ S21.G00.79.001,'11'
S21.G00.79.004,'3085.28' S21.G00.79.004,'3085.28'
S21.G00.81.001,'059' S21.G00.81.001,'059'
S21.G00.81.004,'21.60' S21.G00.81.004,'21.60'
S21.G00.78.001,'04'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'4125.36'
S21.G00.86.001,'01' S21.G00.86.001,'01'
S21.G00.86.002,'02' S21.G00.86.002,'02'
S21.G00.86.003,'11' S21.G00.86.003,'11'
...@@ -459,36 +459,12 @@ S21.G00.54.001,'17' ...@@ -459,36 +459,12 @@ S21.G00.54.001,'17'
S21.G00.54.002,'67.20' S21.G00.54.002,'67.20'
S21.G00.54.003,'01012015' S21.G00.54.003,'01012015'
S21.G00.54.004,'31012015' S21.G00.54.004,'31012015'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'86.36'
S21.G00.78.001,'02' S21.G00.78.001,'02'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
S21.G00.78.004,'3170.00' S21.G00.78.004,'3170.00'
S21.G00.81.001,'063' S21.G00.81.001,'063'
S21.G00.81.004,'323.55' S21.G00.81.004,'323.55'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'2'
S21.G00.79.001,'11'
S21.G00.79.004,'3170.00'
S21.G00.79.001,'13'
S21.G00.79.004,'657.86'
S21.G00.81.001,'059'
S21.G00.81.004,'29.29'
S21.G00.78.001,'03' S21.G00.78.001,'03'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
...@@ -499,14 +475,38 @@ S21.G00.81.001,'226' ...@@ -499,14 +475,38 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011' S21.G00.81.002,'75367340900011'
S21.G00.81.003,'3827.86' S21.G00.81.003,'3827.86'
S21.G00.81.005,'59378' S21.G00.81.005,'59378'
S21.G00.78.001,'04'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'3847.23'
S21.G00.78.001,'07' S21.G00.78.001,'07'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
S21.G00.78.004,'3827.86' S21.G00.78.004,'3827.86'
S21.G00.78.001,'04' S21.G00.78.001,'13'
S21.G00.78.002,'01012015' S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015' S21.G00.78.003,'31012015'
S21.G00.78.004,'3847.23' S21.G00.78.004,'86.36'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'1'
S21.G00.79.001,'20'
S21.G00.79.004,'97.16'
S21.G00.81.001,'059'
S21.G00.81.004,'97.16'
S21.G00.78.001,'31'
S21.G00.78.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'0.00'
S21.G00.78.005,'2'
S21.G00.79.001,'11'
S21.G00.79.004,'3170.00'
S21.G00.79.001,'13'
S21.G00.79.004,'657.86'
S21.G00.81.001,'059'
S21.G00.81.004,'29.29'
S21.G00.86.001,'01' S21.G00.86.001,'01'
S21.G00.86.002,'02' S21.G00.86.002,'02'
S21.G00.86.003,'11' S21.G00.86.003,'11'
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -102,7 +102,8 @@ class TestInvoice(TestInvoiceMixin): ...@@ -102,7 +102,8 @@ class TestInvoice(TestInvoiceMixin):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
invoice_transaction_movement =\ invoice_transaction_movement =\
invoice_transaction_applied_rule.contentValues()[0] invoice_transaction_applied_rule.contentValues()[0]
self.assertEqual(currency, self.assertEqual(currency,
...@@ -349,7 +350,8 @@ class TestInvoice(TestInvoiceMixin): ...@@ -349,7 +350,8 @@ class TestInvoice(TestInvoiceMixin):
delivery_movement = delivery_applied_rule.contentValues()[0] delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = [x for x in invoice_movement.contentValues() \
if x.getSpecialiseReference() == 'default_invoice_transaction_rule'][0]
# utility function to return the simulation movement that should be used # utility function to return the simulation movement that should be used
# for "income" line # for "income" line
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -220,12 +220,12 @@ class SyncMLSignature(XMLObject): ...@@ -220,12 +220,12 @@ class SyncMLSignature(XMLObject):
def getFirstPdataChunk(self, max_len): def getFirstPdataChunk(self, max_len):
""" """
""" """
partial_data = self._baseGetPartialData() partial_data = bytes(self._baseGetPartialData()).decode('utf-8')
chunk = partial_data[:max_len] chunk = partial_data[:max_len]
rest_in_queue = partial_data[max_len:] rest_in_queue = partial_data[max_len:]
if rest_in_queue is not None: if rest_in_queue is not None:
self.setPartialData(rest_in_queue) self.setPartialData(rest_in_queue.encode('utf-8'))
return bytes(chunk) return chunk.encode('utf-8')
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'setSubscriberXupdate') 'setSubscriberXupdate')
......
...@@ -192,13 +192,15 @@ def getXupdateObject(object_xml=None, old_xml=None): ...@@ -192,13 +192,15 @@ def getXupdateObject(object_xml=None, old_xml=None):
def cutXML(xml_string, length=None): def cutXML(xml_string, length=None):
""" """
Sliced a xml tree a return two fragment Sliced a xml tree and return two fragments
""" """
if length is None: if length is None:
length = MAX_LEN length = MAX_LEN
if not isinstance(xml_string, six.text_type):
xml_string = xml_string.decode('utf-8')
short_string = xml_string[:length] short_string = xml_string[:length]
rest_string = xml_string[length:] rest_string = xml_string[length:]
xml_string = etree.CDATA(short_string.decode('utf-8')) xml_string = etree.CDATA(short_string)
return xml_string, rest_string return xml_string, rest_string
class XMLSyncUtilsMixin(object): class XMLSyncUtilsMixin(object):
......
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key> <key> <string>tested_property</string> </key>
<value> <value>
<tuple> <tuple>
<string>start_date</string>
<string>stop_date</string> <string>stop_date</string>
<string>start_date</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -81,13 +81,13 @@ ...@@ -81,13 +81,13 @@
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//span[@class="listbox-current-page-total-number x0_listbox-current-page-total-number"]</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../..//span[contains(@class, "current-page-total-number")]</td>
<td>4 records</td> <!-- Creation + edited 3 times --> <td>4 records</td> <!-- Creation + edited 3 times -->
</tr> </tr>
<!-- First modification --> <!-- First modification -->
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-1 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[2]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -153,13 +153,13 @@ ...@@ -153,13 +153,13 @@
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//span[@class="listbox-current-page-total-number x0_listbox-current-page-total-number"]</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../..//span[contains(@class, "current-page-total-number")]</td>
<td>4 records</td> <!-- Creation + edited 3 times --> <td>4 records</td> <!-- Creation + edited 3 times -->
</tr> </tr>
<!-- First modification --> <!-- First modification -->
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-1 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[2]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
......
...@@ -81,13 +81,12 @@ ...@@ -81,13 +81,12 @@
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//span[@class="listbox-current-page-total-number x0_listbox-current-page-total-number"]</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../..//span[contains(@class, "current-page-total-number")]</td>
<td>4 records</td> <!-- Creation + edited 3 times --> <td>4 records</td> <!-- Creation + edited 3 times -->
</tr> </tr>
<!-- First modification -->
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-3 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[4]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -153,13 +152,12 @@ ...@@ -153,13 +152,12 @@
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//span[@class="listbox-current-page-total-number x0_listbox-current-page-total-number"]</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../..//span[contains(@class, "current-page-total-number")]</td>
<td>4 records</td> <!-- Creation + edited 3 times --> <td>4 records</td> <!-- Creation + edited 3 times -->
</tr> </tr>
<!-- First modification -->
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-3 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[4]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//span[@class="listbox-current-page-total-number x0_listbox-current-page-total-number"]</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../..//span[contains(@class, "current-page-total-number")]</td>
<td>4 records</td> <!-- Creation + edited 3 times --> <td>4 records</td> <!-- Creation + edited 3 times -->
</tr> </tr>
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-1 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[2]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-2 DataA']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[3]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
</tal:block> </tal:block>
...@@ -235,7 +235,7 @@ ...@@ -235,7 +235,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//tr[@class='x0_listbox-data-line-3 DataB']/td[4]/a</td> <td>//div[@class="listbox-title"]//span[text()="Edit Workflow"]/../../../../../..//table//tr[4]/td[4]/a</td>
<td></td> <td></td>
</tr> </tr>
</tal:block> </tal:block>
......
...@@ -279,7 +279,7 @@ ...@@ -279,7 +279,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>_text</string> </key> <key> <string>_text</string> </key>
<value> <string>python: \' \'.join(here.getCategoryList())</string> </value> <value> <string>python: \' \'.join(sorted(here.getCategoryList()))</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -27,12 +27,13 @@ ...@@ -27,12 +27,13 @@
# #
############################################################################## ##############################################################################
import lxml.html
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5Type.tests.Sequence import SequenceList from Products.ERP5Type.tests.Sequence import SequenceList
class TestGUISecurity(ERP5TypeTestCase): class TestGUISecurity(ERP5TypeTestCase):
""" """
""" """
...@@ -82,9 +83,10 @@ class TestGUISecurity(ERP5TypeTestCase): ...@@ -82,9 +83,10 @@ class TestGUISecurity(ERP5TypeTestCase):
Try to view the Foo_view form, make sure our category name is displayed Try to view the Foo_view form, make sure our category name is displayed
""" """
self.loginAs() self.loginAs()
self.assertIn( self.assertTrue(
self.category_field_markup, lxml.html.fromstring(
self.portal.foo_module.foo.Foo_view()) self.portal.foo_module.foo.Foo_view()
).xpath(self.category_field_xpath))
self.login() self.login()
def stepAccessFooDoesNotDisplayCategoryName(self, sequence = None, sequence_list = None, **kw): def stepAccessFooDoesNotDisplayCategoryName(self, sequence = None, sequence_list = None, **kw):
...@@ -92,9 +94,10 @@ class TestGUISecurity(ERP5TypeTestCase): ...@@ -92,9 +94,10 @@ class TestGUISecurity(ERP5TypeTestCase):
Try to view the Foo_view form, make sure our category name is not displayed Try to view the Foo_view form, make sure our category name is not displayed
""" """
self.loginAs() self.loginAs()
self.assertNotIn( self.assertFalse(
self.category_field_markup, lxml.html.fromstring(
self.portal.foo_module.foo.Foo_view()) self.portal.foo_module.foo.Foo_view()
).xpath(self.category_field_xpath))
self.login() self.login()
def stepChangeCategorySecurity(self, sequence = None, sequence_list = None, **kw): def stepChangeCategorySecurity(self, sequence = None, sequence_list = None, **kw):
...@@ -127,7 +130,7 @@ class TestGUISecurity(ERP5TypeTestCase): ...@@ -127,7 +130,7 @@ class TestGUISecurity(ERP5TypeTestCase):
An attempt to view the document form would raise Unauthorized. An attempt to view the document form would raise Unauthorized.
""" """
# this really depends on the generated markup # this really depends on the generated markup
self.category_field_markup = '<input name="field_my_foo_category_title" value="a" type="text"' self.category_field_xpath = '//input[@name="field_my_foo_category_title" and @type="text" and @value="a"]'
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_string = '\ sequence_string = '\
...@@ -156,11 +159,18 @@ class TestGUISecurity(ERP5TypeTestCase): ...@@ -156,11 +159,18 @@ class TestGUISecurity(ERP5TypeTestCase):
self.stepCreateObjects() self.stepCreateObjects()
self.stepCreateTestFoo() self.stepCreateTestFoo()
protected_property_markup = '<input name="field_my_protected_property" value="Protected Property" type="text"' protected_property_xpath = '//input[@name="field_my_protected_property" and @type="text" and @value="Protected Property"]'
self.assertIn(protected_property_markup, self.portal.foo_module.foo.Foo_viewSecurity())
self.assertTrue(
lxml.html.fromstring(
self.portal.foo_module.foo.Foo_viewSecurity()
).xpath(protected_property_xpath))
self.loginAs() # user without permission to access protected property self.loginAs() # user without permission to access protected property
self.assertNotIn(protected_property_markup, self.portal.foo_module.foo.Foo_viewSecurity()) self.assertFalse(
lxml.html.fromstring(
self.portal.foo_module.foo.Foo_viewSecurity()
).xpath(protected_property_xpath))
def test_translated_state_title_lookup(self): def test_translated_state_title_lookup(self):
""" """
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tr> <tr>
<td>clickAndWait</td> <td>clickAndWait</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_label_field.html')]/a[contains(@href, "<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EAccounting+Transactions%24&lang=en'></span>")]</td> <td>//div[contains(@data-gadget-url, 'gadget_erp5_label_field.html')]/a[contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EAccounting+Transactions%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
</tr> </tr>
<tr> <tr>
<td>waitForElementPresent</td> <td>waitForElementPresent</td>
<td>//label[@for="field_listbox_left"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EListbox%24&lang=en'></span>"]</td> <td>//label[@for="field_listbox_left"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EListbox%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//label[@for="field_listbox_left"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EListbox%24&lang=en'></span>"]</td> <td>//label[@for="field_listbox_left"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EListbox%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
</tr> </tr>
<tr> <tr>
<td>waitForElementPresent</td> <td>waitForElementPresent</td>
<td>//label[@for="field_listbox_right"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EListbox%24&lang=en'></span>"]</td> <td>//label[@for="field_listbox_right"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EListbox%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//label[@for="field_listbox_right"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EListbox%24&lang=en'></span>"]</td> <td>//label[@for="field_listbox_right"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EListbox%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//label[@for="field_my_title"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5ETitle%24&lang=en'></span>"]</td> <td>//label[@for="field_my_title"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5ETitle%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//label[@for="field_your_description"]/a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EDescription%24&lang=en'></span>"]</td> <td>//label[@for="field_your_description"]/a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EDescription%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
</tr> </tr>
<tr> <tr>
<td>assertElementPresent</td> <td>assertElementPresent</td>
<td>//a[contains(@class, 'translate-title') and @href="<span tal:replace='string:Localizer/erp5_ui/manage_messages?regex=%5EListbox%24&lang=en'></span>"]</td> <td>//a[contains(@class, 'translate-title') and contains(@href, 'Localizer/erp5_ui/manage_messages') and contains(@href, 'regex=%5EListbox%24') and contains(@href, 'lang=en')]</td>
<td></td> <td></td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_module_list" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_module_list" />
......
...@@ -143,21 +143,46 @@ ...@@ -143,21 +143,46 @@
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[1]/td[1]</td> <td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[1]/td[1]</td>
<td>frozen</td>
</tr>
<tr>
<td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[2]/td[1]</td>
<td>lines_list</td>
</tr>
<tr>
<td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[2]/td[2]</td>
<td></td>
</tr>
<tr>
<td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[2]/td[3]</td>
<td>Foo,Bar</td>
</tr>
<tr>
<td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[2]/td[4]</td>
<td>Foo,Bar</td>
</tr>
<tr>
<td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[3]/td[1]</td>
<td>short_title</td> <td>short_title</td>
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[1]/td[2]</td> <td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[3]/td[2]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[1]/td[3]</td> <td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[3]/td[3]</td>
<td>A new foo</td> <td>A new foo</td>
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[1]/td[4]</td> <td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[3]/td[4]</td>
<td>A new foo</td> <td>A new foo</td>
</tr> </tr>
......
...@@ -49,6 +49,19 @@ ...@@ -49,6 +49,19 @@
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr>
<td colspan="3"><b>Listfields are displayed in alphabetic order</b></td>
</tr>
<tr>
<td>assertText</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']/div/label</td>
<td>Foo Category</td>
</tr>
<tr>
<td>assertText</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']/div/label</td>
<td>foo_big_category</td>
</tr>
<tr> <tr>
<td colspan="3"><b>Empty value by default</b></td> <td colspan="3"><b>Empty value by default</b></td>
</tr> </tr>
...@@ -89,12 +102,12 @@ ...@@ -89,12 +102,12 @@
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//select</td>
<td>label=c1</td> <td>label=a/a1</td>
</tr> </tr>
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//select</td>
<td>label=a/a1</td> <td>label=c1</td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
...@@ -106,12 +119,12 @@ ...@@ -106,12 +119,12 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//select</td>
<td>label=c1</td> <td>label=a/a1</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//select</td>
<td>label=a/a1</td> <td>label=c1</td>
</tr> </tr>
<tr> <tr>
<td>assertElementNotPresent</td> <td>assertElementNotPresent</td>
......
...@@ -45,6 +45,19 @@ ...@@ -45,6 +45,19 @@
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr>
<td colspan="3"><b>Listfields are displayed in alphabetic order</b></td>
</tr>
<tr>
<td>assertText</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']/div/label</td>
<td>Foo Category</td>
</tr>
<tr>
<td>assertText</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']/div/label</td>
<td>foo_big_category</td>
</tr>
<tr> <tr>
<td colspan="3"><b>Empty value by default</b></td> <td colspan="3"><b>Empty value by default</b></td>
</tr> </tr>
...@@ -80,18 +93,18 @@ ...@@ -80,18 +93,18 @@
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div//select</td>
<td>label=c1</td> <td>label=a</td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr> <tr>
<td>assertText</td> <td>assertText</td>
<td>//p[@id="field_category_list"]</td> <td>//p[@id="field_category_list"]</td>
<td>foo_big_category/c1</td> <td>foo_category/a</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td>
<td>label=c1</td> <td>label=a</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
...@@ -120,7 +133,7 @@ ...@@ -120,7 +133,7 @@
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div//select</td>
<td>label=a</td> <td>label=c1</td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr> <tr>
...@@ -131,7 +144,7 @@ ...@@ -131,7 +144,7 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td>
<td>label=c1</td> <td>label=a</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
...@@ -146,7 +159,7 @@ ...@@ -146,7 +159,7 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td>
<td>label=a</td> <td>label=c1</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
...@@ -164,7 +177,7 @@ ...@@ -164,7 +177,7 @@
</tr> </tr>
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[2]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[2]//select</td>
<td>label=c22</td> <td>label=c22</td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
...@@ -176,45 +189,46 @@ ...@@ -176,45 +189,46 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td>
<td>label=c1</td> <td>label=a</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[2]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[2]//select</td>
<td>label=c22</td>
</tr>
<tr>
<td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[3]//select</td>
<td>label=</td> <td>label=</td>
</tr> </tr>
<tr> <tr>
<td>assertElementNotPresent</td> <td>assertElementNotPresent</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[4]</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[3]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td>
<td>label=a</td> <td>label=c1</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[2]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[2]//select</td>
<td>label=c22</td>
</tr>
<tr>
<td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[3]//select</td>
<td>label=</td> <td>label=</td>
</tr> </tr>
<tr> <tr>
<td>assertElementNotPresent</td> <td>assertElementNotPresent</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[3]</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[4]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td colspan="3"><b>Remove first value</b></td> <td colspan="3"><b>Remove first foo_big_category</b></td>
</tr> </tr>
<tr> <tr>
<td>select</td> <td>select</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td>
<td>label=</td> <td>label=</td>
</tr> </tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
...@@ -226,7 +240,7 @@ ...@@ -226,7 +240,7 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div[1]//select</td>
<td>label=c22</td> <td>label=a</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
...@@ -241,7 +255,7 @@ ...@@ -241,7 +255,7 @@
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div[1]//select</td>
<td>label=a</td> <td>label=c22</td>
</tr> </tr>
<tr> <tr>
<td>assertSelected</td> <td>assertSelected</td>
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
<!-- Check that Category titles in parallel_list_field are well translated --> <!-- Check that Category titles in parallel_list_field are well translated -->
<tr> <tr>
<td>assertText</td> <td>assertText</td>
<td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_1']//div//label</td> <td>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div//label</td>
<td>Foo leibie</td> <td>Foo leibie</td>
</tr> </tr>
......
...@@ -85,7 +85,12 @@ ...@@ -85,7 +85,12 @@
</tr> </tr>
<tr> <tr>
<td>assertLocation</td> <td>assertLocation</td>
<td>regexp:${base_url}/web_site_module/test_web_site/web_page_module/[^/]+(/view)+\?portal_status_message=Created%20Clone%20Web%20Page.&amp;editable_mode:int=1</td> <td>regexp:${base_url}/web_site_module/test_web_site/web_page_module/[^/]+(/view)+\?.*portal_status_message=Created%20Clone%20Web%20Page.</td>
<td></td>
</tr>
<tr>
<td>assertLocation</td>
<td>regexp:${base_url}/web_site_module/test_web_site/web_page_module/[^/]+(/view)+\?.*editable_mode:int=1</td>
<td></td> <td></td>
</tr> </tr>
</tbody></table> </tbody></table>
......
...@@ -238,6 +238,7 @@ class TestConvertedWorkflow(TestERP5WorkflowMixin): ...@@ -238,6 +238,7 @@ class TestConvertedWorkflow(TestERP5WorkflowMixin):
self.workflow = portal_workflow._getOb(self.workflow_id) self.workflow = portal_workflow._getOb(self.workflow_id)
self.resetComponentTool() self.resetComponentTool()
self.login() self.login()
self.tic()
def test_13_permission(self): def test_13_permission(self):
""" """
......
...@@ -70,6 +70,8 @@ class Resource(XMLObject, XMLMatrix, VariatedMixin): ...@@ -70,6 +70,8 @@ class Resource(XMLObject, XMLMatrix, VariatedMixin):
, PropertySheet.Aggregated , PropertySheet.Aggregated
) )
_default_edit_order = XMLObject._default_edit_order + VariatedMixin._default_edit_order
# Is it OK now ? # Is it OK now ?
# The same method is at about 3 different places # The same method is at about 3 different places
# Some genericity is needed # Some genericity is needed
......
...@@ -988,7 +988,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, PortalObjectBase, CacheCook ...@@ -988,7 +988,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, PortalObjectBase, CacheCook
for state in wf.getStateValueList(): for state in wf.getStateValueList():
if group in state.getStateTypeList(): if group in state.getStateTypeList():
state_set.add(state.getReference()) state_set.add(state.getReference())
return tuple(state_set) return tuple(sorted(state_set))
getStateList = CachingMethod(getStateList, getStateList = CachingMethod(getStateList,
id=('_getPortalGroupedStateList', group), id=('_getPortalGroupedStateList', group),
......
...@@ -909,7 +909,7 @@ class TemplateTool (BaseTool): ...@@ -909,7 +909,7 @@ class TemplateTool (BaseTool):
repository_dict = {} repository_dict = {}
undependent_list = [] undependent_list = []
for repository, bt_id in bt_list: for repository, bt_id in sorted(bt_list):
bt = [x for x in self.repository_dict[repository] \ bt = [x for x in self.repository_dict[repository] \
if x['id'] == bt_id][0] if x['id'] == bt_id][0]
bt_title = bt['title'] bt_title = bt['title']
...@@ -924,7 +924,7 @@ class TemplateTool (BaseTool): ...@@ -924,7 +924,7 @@ class TemplateTool (BaseTool):
# Calculate the reverse dependency graph # Calculate the reverse dependency graph
reverse_dependency_dict = {} reverse_dependency_dict = {}
for bt_id, dependency_id_list in dependency_dict.items(): for bt_id, dependency_id_list in sorted(dependency_dict.items()):
update_dependency_id_list = [] update_dependency_id_list = []
for dependency_id in dependency_id_list: for dependency_id in dependency_id_list:
...@@ -934,10 +934,7 @@ class TemplateTool (BaseTool): ...@@ -934,10 +934,7 @@ class TemplateTool (BaseTool):
update_dependency_id_list.append(dependency_id) update_dependency_id_list.append(dependency_id)
# Fill incoming edge dict # Fill incoming edge dict
if dependency_id in reverse_dependency_dict: reverse_dependency_dict.setdefault(dependency_id, []).append(bt_id)
reverse_dependency_dict[dependency_id].append(bt_id)
else:
reverse_dependency_dict[dependency_id] = [bt_id]
# Remove from free node list # Remove from free node list
try: try:
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</item> </item>
<item> <item>
<key> <string>priority</string> </key> <key> <string>priority</string> </key>
<value> <float>8.0</float> </value> <value> <float>9.0</float> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
</item> </item>
<item> <item>
<key> <string>priority</string> </key> <key> <string>priority</string> </key>
<value> <float>3.0</float> </value> <value> <float>30.0</float> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
</item> </item>
<item> <item>
<key> <string>priority</string> </key> <key> <string>priority</string> </key>
<value> <float>100.0</float> </value> <value> <float>98.0</float> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -66,6 +66,29 @@ class Amount(Base, VariatedMixin): ...@@ -66,6 +66,29 @@ class Amount(Base, VariatedMixin):
, PropertySheet.Reference , PropertySheet.Reference
) )
_default_edit_order = Base._default_edit_order + (
'resource',
'resource_value',
'resource_uid',
# If variations and resources are set at the same time, resource must be
# set before any variation.
'variation_base_category_list',
'variation_category_list',
# If (quantity unit, base_contribution, or use) and resource are set at the same time,
# resource must be set first, because of an interaction that copies quantity unit
# base contribution and use from resource if not set.
'quantity_unit_value',
'quantity_unit',
'use_value',
'use',
'base_contribution_list',
'base_contribution_value_list',
'base_contribution_value',
'base_contribution',
)
# A few more mix-in methods which should be relocated # A few more mix-in methods which should be relocated
# THIS MUST BE UPDATE WITH CATEGORY ACQUISITION # THIS MUST BE UPDATE WITH CATEGORY ACQUISITION
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
......
...@@ -50,6 +50,8 @@ class AmountGeneratorLine(MappedValue, XMLMatrix, Amount, ...@@ -50,6 +50,8 @@ class AmountGeneratorLine(MappedValue, XMLMatrix, Amount,
property_sheets = (PropertySheet.DublinCore, property_sheets = (PropertySheet.DublinCore,
PropertySheet.AmountGeneratorLine) PropertySheet.AmountGeneratorLine)
_default_edit_order = Amount._default_edit_order
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getCellAggregateKey') 'getCellAggregateKey')
def getCellAggregateKey(self): def getCellAggregateKey(self):
......
...@@ -80,6 +80,11 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin, ...@@ -80,6 +80,11 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
, PropertySheet.Price , PropertySheet.Price
) )
_default_edit_order = XMLObject._default_edit_order + (
'stop_date',
'start_date',
)
security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable') security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable')
def isAccountable(self): def isAccountable(self):
""" """
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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