Commit 0ed7ff48 authored by Jérome Perrin's avatar Jérome Perrin

Fix code depending on python2 __hash__

With python2, iterating on a dictionary or a set always produces the same result,
although this is not a documented behavior. On python3 this is not the case,
because the hashing algorithm is random by default, which can also be set using [`PYTHONHASHSEED`](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED). On SlapOS, this is done with slapos!1535 

This fixes the parts where ERP5 code depends on python2 order, mostly tests, but also places
where we iterate on a dictionary or set. Most of the time, the fix has been to sort so that
the order is deterministic regardless of the hash algorithm randomization, but sometimes we
had to extend a bit the configuration where the order was really important. We did this after
discovering the problematic areas by running tests multiple times with different hash randomization
seeds. It's not impossible that changing from "default python2 order" to "sorted" reveals some
more problems in custom configurations, but this would mean that the configuration must be 
adjusted to use explicit order instead of being lucky with the default python2 order.

The main pattern was the use of `edit` method which edits properties in an order that is a bit
constrained with the `edit_order` mechanism, because some properties depend on other properties,
so it's important to set them in order. This extends a bit the `edit_order` mechanism to specify
more properties that were edited in the right order with `PYTHONHASHSEED=0` by chance.

This also extends delivery builders to edit properties in order defined in the equivalence tester,
most equivalence tester were already properly configured, except the `start_date` and `stop_date`
from delivery level movement groups. That probably only matters for some specific test assertions,
but in practice this was visible in a lot of failing tests.

Some visible changes are that:
 - workflows are now sorted alphabetically on history tab
 - properties are now sorted alphabetically on the diff view of history tab
 - business templates are installed in the order of dependencies and in alphabetic order when they
  are not constrained.

See merge request !1882
parents a56f5a9d 86af6e35
......@@ -72,4 +72,6 @@ for item_value in value_list:
sub_field_dict[item_key]['value'] = item_value
# 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.
<tr>
<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>
</tr>
<tr>
<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>
</tr>
<tr>
<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>
</tr>
<tr>
<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>
</tr>
......
......@@ -305,7 +305,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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_applied_rule.contentValues()[0]
self.assertEqual(currency,
......@@ -397,7 +398,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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_applied_rule.contentValues()[0]
self.assertEqual(currency,
......@@ -684,7 +686,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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 = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getDestinationTotalAssetPrice()))
......@@ -788,7 +791,8 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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 = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getSourceTotalAssetPrice()))
......
......@@ -581,12 +581,24 @@ class TestCurrencyExchangeCell(CurrencyExchangeTestCase):
euro_to_usd.setPriceCurrencyValue(usd)
self.assertEqual(2, len(euro_to_usd.contentValues()))
# cell range is like this:
self.assertEqual([
# cell range is like this, matrix cell range does not have ordering
# of the keys, only asCellRange script has.
self.assertEqual(
euro_to_usd.CurrencyExchangeLine_asCellRange(base_id='path'),
(
['currency_exchange_type/type_a', 'currency_exchange_type/type_b'],
['resource/%s' % euro.getRelativeUrl()],
['price_currency/%s' % usd.getRelativeUrl()],
], euro_to_usd.getCellRange(base_id='path'))
)
)
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(
'currency_exchange_type/type_a',
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,13 +37,7 @@ class BaseVariantMovementGroup(MovementGroup):
portal_type = 'Base Variant Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
category_list = movement.getVariationBaseCategoryList()
if category_list is None:
category_list = []
category_list.sort()
property_dict['_base_category_list'] = category_list
return property_dict
return {'_base_category_list': sorted(movement.getVariationBaseCategoryList() or [])}
def test(self, document, property_dict, **kw):
# This movement group does not affect updating.
......
......@@ -25,6 +25,7 @@
#
##############################################################################
from collections import OrderedDict
from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup
class CategoryMovementGroup(PropertyMovementGroup):
......@@ -43,7 +44,7 @@ class CategoryMovementGroup(PropertyMovementGroup):
portal_type = 'Category Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
property_dict = OrderedDict()
getProperty = movement.getProperty
for prop in self.getTestedPropertyList():
list_prop = prop + '_list'
......
......@@ -38,10 +38,7 @@ class CausalityMovementGroup(MovementGroup):
def _getPropertyDict(self, movement, **kw):
property_dict = {}
explanation_relative_url = self._getExplanationRelativeUrl(movement)
property_dict['_explanation'] = explanation_relative_url
return property_dict
return {'_explanation': self._getExplanationRelativeUrl(movement)}
def test(self, movement, property_dict, **kw):
# we don't care the difference of explanation url when updating
......
......@@ -26,6 +26,7 @@
#
##############################################################################
from collections import OrderedDict
from erp5.component.document.MovementGroup import MovementGroup
from DateTime import DateTime
from erp5.component.module.DateUtils import atTheEndOfPeriod
......@@ -45,7 +46,7 @@ class MonthlyRangeMovementGroup(MovementGroup):
def _getPropertyDict(self, movement, **kw):
"""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'):
date = movement.getProperty(property_name, None)
if date is not None:
......
......@@ -25,6 +25,7 @@
#
##############################################################################
from collections import OrderedDict
from erp5.component.document.PropertyMovementGroup import PropertyMovementGroup
from Products.ERP5Type.Utils import UpperCase
......@@ -41,7 +42,7 @@ class ParentDeliveryPropertyMovementGroup(PropertyMovementGroup):
portal_type = 'Parent Delivery Property Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
property_dict = OrderedDict()
parent_delivery = self._getParentDelivery(movement)
if parent_delivery is not None:
for prop in self.getTestedPropertyList():
......
......@@ -40,10 +40,7 @@ class ParentExplanationMovementGroup(MovementGroup):
portal_type = 'Parent Explanation Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
parent_explanation_value = movement.getParentExplanationValue()
property_dict['parent_explanation_value'] = parent_explanation_value
return property_dict
return {'parent_explanation_value': movement.getParentExplanationValue()}
def test(self, document, property_dict, **kw):
if document.getParentExplanationValue() == \
......
......@@ -26,8 +26,10 @@
#
##############################################################################
from collections import OrderedDict
from erp5.component.document.MovementGroup import MovementGroup
class PropertyMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
......@@ -40,7 +42,7 @@ class PropertyMovementGroup(MovementGroup):
portal_type = 'Property Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
property_dict = OrderedDict()
getProperty = movement.getProperty
for prop in self.getTestedPropertyList():
property_dict[prop] = getProperty(prop)
......
......@@ -36,7 +36,7 @@ class RequirementMovementGroup(MovementGroup):
portal_type = 'Requirement Movement Group'
def _getPropertyDict(self, movement, **kw):
return {'requirement':self._getRequirementList(movement)}
return {'requirement': self._getRequirementList(movement)}
def test(self, movement, property_dict, **kw):
# We can always update
......
......@@ -39,10 +39,7 @@ class RootAppliedRuleCausalityMovementGroup(MovementGroup):
portal_type = 'Root Applied Rule Causality Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
root_causality_value = self._getRootCausalityValue(movement)
property_dict['root_causality_value_list'] = [root_causality_value]
return property_dict
return {'root_causality_value_list': [self._getRootCausalityValue(movement)]}
def test(self, movement, property_dict, **kw):
# We can always update
......
......@@ -37,9 +37,7 @@ class TitleMovementGroup(MovementGroup):
portal_type = 'Title Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
property_dict['title'] = self._getTitle(movement)
return property_dict
return {'title': self._getTitle(movement)}
def test(self, document, property_dict, **kw):
# If title is different, we want to update existing document instead
......
......@@ -331,8 +331,6 @@ class BuilderMixin(XMLObject, Amount, Predicate):
# 'variation_category' or 'variation_property' pseudo properties,
# which rely on the resource being set to discover which
# 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 = []
property_dict = {'edit_order': edit_order}
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()]
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 @@
<key> <string>categories</string> </key>
<value>
<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>
</value>
</item>
......@@ -22,7 +22,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_setBaseContribution</string> </value>
<value> <string>interaction_setResource</string> </value>
</item>
<item>
<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']
resource = movement.getResourceValue()
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.
# 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.
......
......@@ -60,9 +60,15 @@
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Movement_copyQuantityUnitFromResource</string> </value>
<value> <string>script_Movement_copyCategoryListFromResource</string> </value>
</item>
<item>
<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 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -28,6 +28,7 @@
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Form.AudioField import AudioField
from Products.ERP5Type.tests.utils import canonical_html
class TestAudioField(ERP5TypeTestCase):
......@@ -44,9 +45,10 @@ class TestAudioField(ERP5TypeTestCase):
def test_render_view(self):
self.field.values['default'] = 'Audio content'
self.assertEqual('<audio preload="preload" src="Audio content" ' +
'controls="controls" >Your browser does not ' +
'support audio tag.</audio>', self.field.render_view(value='Audio content'))
self.assertEqual(
canonical_html(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_loop'] = True
......@@ -54,13 +56,7 @@ class TestAudioField(ERP5TypeTestCase):
self.field.values['audio_autoplay'] = True
self.field.values['audio_error_message'] = 'Another error message'
self.assertEqual('<audio src="Another Audio content" ' +
'loop="loop" autoplay="autoplay" >Another error ' +
'message</audio>', self.field.render_view(value='Another Audio content'))
import unittest
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestAudioField))
return suite
self.assertEqual(
canonical_html(self.field.render_view(value='Another Audio content')),
'<audio autoplay="autoplay" loop="loop" src="Another Audio content"'
+ '>Another error message</audio>')
......@@ -368,13 +368,9 @@ return result
py_script_obj = getattr(portal, python_script_id)
py_script_params = "value=10000, long_parameter=''"
py_script_body = """
import time
def veryExpensiveMethod(value):
# do something expensive for some time
# no 'time.sleep()' available in Zope
# so concatenate strings
s = ''
for i in range(0, value):
s = str(value * value * value) + s
time.sleep(1)
return value
veryExpensiveMethod(value)
......
......@@ -27,6 +27,7 @@
#
##############################################################################
import collections
import pprint
import httplib
import urlparse
......@@ -206,9 +207,15 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
actions = self.portal.portal_actions.listFilteredActionsFor(target)
got = {}
for category, actions in actions.items():
actions_by_priority = collections.defaultdict(list)
got[category] = [dict(title=action['title'], id=action['id'])
for action in actions
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" %
(pprint.pformat(expected), pprint.pformat(got)))
self.assertEqual(expected, got, msg)
......@@ -222,8 +229,6 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
'id': 'category_tool'},
{'title': 'Manage Callables',
'id': 'callable_tool'},
{'title': 'Create Module',
'id': 'create_module'},
{'title': 'Configure Portal Types',
'id': 'types_tool'},
{'id': 'property_sheet_tool',
......@@ -232,7 +237,9 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
'title': 'Configure Portal Catalog'},
{'id': 'portal_alarms_action',
'title': 'Configure Alarms'},
{'title': 'Undo', 'id': 'undo'}],
{'title': 'Undo', 'id': 'undo'},
{'title': 'Create Module',
'id': 'create_module'},],
'object': [],
'object_action': [{'id': 'diff_object_action', 'title': 'Diff Object'}],
'object_exchange': [{'id': 'csv_export', 'title': 'Export Csv File'}, # erp5_csv_style
......
......@@ -740,7 +740,7 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
# the list.
self.assertEqual(person.getDefaultRegion(), 'beta')
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
self.assertEqual(person.getDefaultRegion(), 'beta')
......@@ -2185,6 +2185,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
checked_permission=checked_permission)
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)
self.assertEqual(None, foo.getRegion())
# Check setCategoryValueSet accessor
......@@ -2199,6 +2203,10 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
checked_permission=checked_permission)
self.assertSameSet([beta_path, gamma_path], foo.getRegionList())
foo.setRegionValueList([beta])
foo.setRegionValueSet([gamma, beta])
self.assertEqual(foo.getRegionValueList(), [beta, gamma])
# check hasCategory accessors
foo.setRegionValue(None)
self.assertEqual(None, foo.getRegion())
......@@ -2385,6 +2393,18 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
self.assertEqual('Could not get object region/gamma',
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):
self._addProperty('Person', 'test_list_accessors', 'dummy',
elementary_type='lines',
......@@ -2406,12 +2426,23 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
person.setDummyList(['a', 'b'])
self.assertEqual(person.getDummy(), 'a')
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')
self.assertEqual(person.getDummy(), 'value')
self.assertEqual(person.getDummyList(), ['value'])
self.assertEqual(person.getDummySet(), ['value'])
self.assertEqual(sorted(person.getDummySet()), ['value'])
def test_translated_accessors(self):
self._addProperty('Person',
......@@ -3335,6 +3366,13 @@ def test_suite():
pass
else:
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)
import ZPublisher.tests.test_mapply
......
......@@ -45,7 +45,7 @@ from Products.ERP5Form.CaptchaField import CaptchaField
from Products.ERP5Form.EditorField import EditorField
from Products.Formulator.MethodField import Method
from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.tests.utils import canonical_html
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import field_value_cache
from Products.ERP5Form.Form import getFieldValue
......@@ -1180,8 +1180,11 @@ class TestCaptchaField(ERP5TypeTestCase):
def test_numeric_good_captcha(self):
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), \
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)
self.assertIn('1 plus 1', field_html)
self.assertIn(hashlib.md5(b'1 + 1').hexdigest(), field_html)
......@@ -1197,8 +1200,11 @@ class TestCaptchaField(ERP5TypeTestCase):
def test_numeric_bad_captcha(self):
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), \
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.assertRaises(
ValidationError, self.validator.validate, self.field, 'field_test', {
......@@ -1269,8 +1275,8 @@ class TestEditorField(ERP5TypeTestCase):
def test_render_editable_textarea(self):
self.field.values['default'] = 'value'
self.assertEqual(
self.field.render(REQUEST=self.portal.REQUEST),
'<textarea rows="5" cols="40" name="field_test_field" >\nvalue</textarea>')
canonical_html(self.field.render(REQUEST=self.portal.REQUEST)),
'<textarea cols="40" name="field_test_field" rows="5">\nvalue</textarea>')
def test_render_editable_textarea_REQUEST(self):
self.field.values['default'] = 'default value'
......@@ -1279,8 +1285,8 @@ class TestEditorField(ERP5TypeTestCase):
self.field.generate_field_key(key=self.field.id)
] = 'user <value>'
self.assertEqual(
self.field.render(REQUEST=self.portal.REQUEST),
'<textarea rows="5" cols="40" name="field_test_field" >\nuser &lt;value&gt;</textarea>')
canonical_html(self.field.render(REQUEST=self.portal.REQUEST)),
'<textarea cols="40" name="field_test_field" rows="5">\nuser &lt;value&gt;</textarea>')
def test_render_non_editable_textarea(self):
self.field.values['default'] = '<not &scaped'
......
......@@ -28,6 +28,8 @@
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Form.VideoField import VideoField
from Products.ERP5Type.tests.utils import canonical_html
class TestVideoField(ERP5TypeTestCase):
"""Tests Video field
......@@ -43,8 +45,11 @@ class TestVideoField(ERP5TypeTestCase):
def test_render_view(self):
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.field.render_view(value='Video content'))
self.assertEqual(
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_loop'] = True
......@@ -54,14 +59,9 @@ class TestVideoField(ERP5TypeTestCase):
self.field.values['video_height'] = 800
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
return ['test report %s' % json.dumps(kw)]
return ['test report %s' % json.dumps(kw, sort_keys=True)]
......@@ -206,11 +206,11 @@ class TestCorporateIdentityMethod(ERP5TypeTestCase):
# 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>'
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>'
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):
output_dict_list = self.test_person.Base_getTemplateProxyParameter(
......
......@@ -392,39 +392,37 @@ import sys
Test the fucntioning of the ERP5ImageProcessor and the custom system
display hook too.
"""
self.image_module = self.portal.getDefaultModule('Image')
self.assertTrue(self.image_module is not None)
# Create a new ERP5 image object
reference = 'testBase_displayImageReference5'
data_template = '<img src="data:application/unknown;base64,%s" /><br />'
data = 'qwertyuiopasdfghjklzxcvbnm<somerandomcharacterstosaveasimagedata>'
if getattr(self.image_module, 'testBase_displayImageID5', None) is not None:
self.image_module.manage_delObjects(ids=['testBase_displayImageID5'])
self.image_module.newContent(
data_template = '<img src="data:image/png;base64,%s"'
data = bytes(self.portal.restrictedTraverse('images/erp5_logo.png').data)
img = self.portal.image_module.newContent(
portal_type='Image',
id='testBase_displayImageID5',
id=self.id(),
reference=reference,
data=data,
filename='test.png'
)
def cleanup():
self.portal.image_module.manage_delObjects(ids=[img.getId()])
self.tic()
self.addCleanup(cleanup)
self.tic()
# Call Base_displayImage from inside of Base_runJupyter
jupyter_code = """
image = context.portal_catalog.getResultValue(portal_type='Image',reference='%s')
context.Base_renderAsHtml(image)
"""%reference
""" % reference
notebook_context = {'setup' : {}, 'variables' : {}}
notebook_context = {'setup': {}, 'variables': {}}
result = self.portal.Base_runJupyter(
jupyter_code=jupyter_code,
old_notebook_context=notebook_context
)
self.assertIn((data_template % base64.b64encode(data)), result['result_string'])
# Mime_type shouldn't be image/png just because of filename, instead it is
# dependent on file and file data
self.assertNotEqual(result['mime_type'], 'image/png')
self.assertIn((data_template % base64.b64encode(data).decode()), result['result_string'])
self.assertEqual(result['mime_type'], 'text/html')
self.assertEqual(result['status'], 'ok')
def testImportSameModuleDifferentNamespace(self):
"""
......@@ -436,7 +434,7 @@ context.Base_renderAsHtml(image)
# First we execute a jupyter_code which imports sys module as 'ss' namespace
jupyter_code = "import sys as ss"
reference = 'Test.Notebook.MutlipleImports %s' %time.time()
reference = 'Test.Notebook.MutlipleImports %s' % time.time()
portal.Base_executeJupyter(
reference=reference,
python_expression=jupyter_code
......
......@@ -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 = {
}
}
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):
graph['node'][state_id]['coordinate'] = position_graph['node'][state_id]['coordinate']
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 @@
</item>
<item>
<key> <string>height</string> </key>
<value> <int>284</int> </value>
<value> <int>277</int> </value>
</item>
<item>
<key> <string>precondition</string> </key>
......
......@@ -677,7 +677,8 @@ def renderField(traversed_document, field, form, value=MARKER, meta_type=None,
json.dumps(ensureSerializable({
'original_form_id': form.id,
'field_id': field.id
})))))
}),
sort_keys=True))))
}
})
......@@ -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),
"list_method": list_method_name,
"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(
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
# 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,
"script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"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 = {}
"""
......@@ -1069,7 +1070,9 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
'proxy_listbox_id': x,
'original_form_id': extra_param_json['original_form_id'],
'field_id': extra_param_json['field_id']
})))))
}),
sort_keys=True,
))))
}) for x, y in proxy_form_id_list],
"first_item": 1,
"required": 0,
......@@ -1092,7 +1095,8 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
'proxy_listbox_id': REQUEST.get('proxy_listbox_id', None),
'original_form_id': extra_param_json['original_form_id'],
'field_id': extra_param_json['field_id']
})))))
}),
sort_keys=True))))
}
# 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,
"script_id": script.id, # this script (ERP5Document_getHateoas)
"relative_url": getRealRelativeUrl(traversed_document).replace("/", "%2F"),
"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:
......@@ -1758,7 +1762,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
'relative_url': relative_url,
'group_by': group_by,
'sort_on': sort_on
})))))
}),
sort_keys=True,
))))
# set 'here' for field rendering which contain TALES expressions
REQUEST.set('here', traversed_document)
......@@ -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"),
"view": url_parameter_dict['view_kw']['view'],
"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
......@@ -2353,7 +2359,8 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
checkPermission = portal.Base_checkPermission
work_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)
if (action['local_roles']):
......@@ -2423,7 +2430,7 @@ if mode == 'url_generator':
else:
generator_key = 'traverse_generator_action'
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] % {
"root_url": site_root.absolute_url(),
"script_id": 'ERP5Document_getHateoas',
......
......@@ -21,7 +21,7 @@ import mock
from zope.globalrequest import setRequest # pylint: disable=no-name-in-module, import-error
from Acquisition import aq_base
from Products.ERP5Form.Selection import Selection, DomainSelection
from Products.ERP5Type.Utils import ensure_list, str2unicode, unicode2str
from Products.ERP5Type.Utils import str2unicode, unicode2str
def changeSkin(skin_name):
......@@ -174,12 +174,9 @@ class ERP5HALJSONStyleSkinsMixin(ERP5TypeTestCase):
def _makeDocument(self):
new_id = self.generateNewId()
foo = self.portal.foo_module.newContent(portal_type="Foo")
foo.edit(
title="live_test_%s" % new_id,
reference="live_test_%s" % new_id
)
return foo
return self.portal.foo_module.newContent(
portal_type="Foo",
reference="live_test_%s" % new_id)
#####################################################
......@@ -677,7 +674,7 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_embedded']['_view']['listbox']['editable_column_list'], [['id', 'ID'], ['title', 'Title'], ['quantity', 'quantity'], ['start_date', 'Date']])
self.assertEqual(result_dict['_embedded']['_view']['listbox']['sort_column_list'], [['id', 'ID'], ['title', 'Title'], ['quantity', 'Quantity'], ['start_date', 'Date']])
self.assertEqual(result_dict['_embedded']['_view']['listbox']['list_method_template'],
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_ui_test/Foo_view/listbox&list_method=objectValues&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=&default_param_json=eyJwb3J0YWxfdHlwZSI6IFsiRm9vIExpbmUiXSwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,group_by*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_ui_test/Foo_view/listbox&list_method=objectValues&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=&default_param_json=eyJpZ25vcmVfdW5rbm93bl9jb2x1bW5zIjogdHJ1ZSwgInBvcnRhbF90eXBlIjogWyJGb28gTGluZSJdfQ=={&query,select_list*,limit*,group_by*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
self.assertEqual(result_dict['_embedded']['_view']['listbox']['domain_root_list'], [['foo_category', 'FooCat'], ['foo_domain', 'FooDomain'], ['not_existing_domain', 'NotExisting']])
NBSP_prefix = u'\xA0' * 4
self.assertEqual(result_dict['_embedded']['_view']['listbox']['domain_dict'], {'foo_domain': [['a', 'a'], ['%sa1' % NBSP_prefix, 'a/a1'], ['%sa2' % NBSP_prefix, 'a/a2'], ['b', 'b']], 'foo_category': [['a', 'a'], ['a/a1', 'a/a1'], ['a/a2', 'a/a2'], ['b', 'b']]})
......@@ -1189,47 +1186,53 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_embedded']['_view']['form_id']['required'], 1)
self.assertEqual(result_dict['_embedded']['_view']['form_id']['type'], 'StringField')
# this report has 3 report sections, sorted by workflow title
self.assertEqual([
section['listbox']['default_params']['workflow_id']
for section in result_dict['_embedded']['_view']['report_section_list']],
['edit_workflow', 'foo_validation_workflow', 'foo_workflow'])
# Check embedded report section rendering
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['default'], 'Base_viewWorkflowHistory')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['editable'], 0)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['hidden'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['key'], 'form_id')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['required'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['form_id']['type'], 'StringField')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['_links']['traversed_document']['href'], 'urn:jio:get:%s' % document.getRelativeUrl())
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['_links']['traversed_document']['name'], document.getRelativeUrl())
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['_links']['traversed_document']['title'], str2unicode(document.getTitle()))
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['_links']['form_definition']['href'], 'urn:jio:get:portal_skins/erp5_core/Base_viewWorkflowHistory')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['_links']['form_definition']['name'], 'Base_viewWorkflowHistory')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['default'], 'Base_viewWorkflowHistory')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['editable'], 0)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['hidden'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['key'], 'form_id')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['required'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['form_id']['type'], 'StringField')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['_links']['traversed_document']['href'], 'urn:jio:get:%s' % document.getRelativeUrl())
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['_links']['traversed_document']['name'], document.getRelativeUrl())
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['_links']['traversed_document']['title'], str2unicode(document.getTitle()))
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['_links']['form_definition']['href'], 'urn:jio:get:portal_skins/erp5_core/Base_viewWorkflowHistory')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['_links']['form_definition']['name'], 'Base_viewWorkflowHistory')
self.assertEqual(
result_dict['_embedded']['_view']['_embedded']['form_definition']['pt'],
'report_view'
)
self.assertSameSet(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['default_params'].keys(), ['checked_permission', 'ignore_unknown_columns', 'workflow_id', 'workflow_title'])
self.assertTrue(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['default_params']['ignore_unknown_columns'])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['default_params']['checked_permission'], 'View')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['default_params']['workflow_id'], 'foo_workflow')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['default_params']['workflow_title'], 'Foo Workflow')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['type'], 'ListBox')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['key'], 'x1_listbox')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['title'], 'Workflow History')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['selection_name'], 'base_workflow_history_selection')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['checked_uid_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['lines'], 15)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['editable'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['show_anchor'], 0)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['list_method'], 'Base_getWorkflowHistoryItemList')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['query'], 'urn:jio:allDocs?query=')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['portal_type'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['column_list'], [['action', 'Action'], ['state', 'State'], ['actor', 'Actor'], ['time', 'Time'], ['comment', 'Comment'], ['error_message', 'Error Message']])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['search_column_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['editable_column_list'], [['time', 'Time'], ['comment', 'Comment'], ['error_message', 'Error Message']])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['sort_column_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['list_method_template'],
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_core/Base_viewWorkflowHistory/listbox&list_method=Base_getWorkflowHistoryItemList&extra_param_json=eyJmb3JtX2lkIjogIkJhc2Vfdmlld1dvcmtmbG93SGlzdG9yeSJ9&default_param_json=eyJ3b3JrZmxvd19pZCI6ICJmb29fd29ya2Zsb3ciLCAiY2hlY2tlZF9wZXJtaXNzaW9uIjogIlZpZXciLCAid29ya2Zsb3dfdGl0bGUiOiAiRm9vIFdvcmtmbG93IiwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,group_by*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
self.assertSameSet(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['default_params'].keys(), ['checked_permission', 'ignore_unknown_columns', 'workflow_id', 'workflow_title'])
self.assertTrue(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['default_params']['ignore_unknown_columns'])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['default_params']['checked_permission'], 'View')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['default_params']['workflow_id'], 'foo_workflow')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['default_params']['workflow_title'], 'Foo Workflow')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['type'], 'ListBox')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['key'], 'x2_listbox')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['title'], 'Workflow History')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['selection_name'], 'base_workflow_history_selection')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['checked_uid_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['lines'], 15)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['editable'], 1)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['show_anchor'], 0)
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['list_method'], 'Base_getWorkflowHistoryItemList')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['query'], 'urn:jio:allDocs?query=')
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['portal_type'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['column_list'], [['action', 'Action'], ['state', 'State'], ['actor', 'Actor'], ['time', 'Time'], ['comment', 'Comment'], ['error_message', 'Error Message']])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['search_column_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['editable_column_list'], [['time', 'Time'], ['comment', 'Comment'], ['error_message', 'Error Message']])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['sort_column_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][2]['listbox']['list_method_template'],
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_core/Base_viewWorkflowHistory/listbox&list_method=Base_getWorkflowHistoryItemList&extra_param_json=eyJmb3JtX2lkIjogIkJhc2Vfdmlld1dvcmtmbG93SGlzdG9yeSJ9&default_param_json=eyJjaGVja2VkX3Blcm1pc3Npb24iOiAiVmlldyIsICJpZ25vcmVfdW5rbm93bl9jb2x1bW5zIjogdHJ1ZSwgIndvcmtmbG93X2lkIjogImZvb193b3JrZmxvdyIsICJ3b3JrZmxvd190aXRsZSI6ICJGb28gV29ya2Zsb3cifQ=={&query,select_list*,limit*,group_by*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
@simulate('Base_getRequestUrl', '*args, **kwargs',
......@@ -2069,7 +2072,7 @@ return url
# Test the URL value
self.assertEqual(result_dict['_embedded']['contents'][0]['title']['url_value']['command'], 'raw')
self.assertEqual(ensure_list(result_dict['_embedded']['contents'][0]['title']['url_value']['options'].keys()), [u'url', u'reset'])
self.assertEqual(sorted(result_dict['_embedded']['contents'][0]['title']['url_value']['options'].keys()), ['reset', 'url'])
self.assertEqual(result_dict['_embedded']['contents'][0]['title']['url_value']['options']['url'], 'https://officejs.com')
# Test if the value of the column is with right key
......@@ -2378,8 +2381,8 @@ return context.getPortalObject().portal_catalog(portal_type='Foo', sort_on=[('id
})
self.assertEqual(selection.getSortOrder(), [('title', 'DESC')])
self.assertEqual(selection.columns, [('title', 'Title')])
self.assertEqual(selection.getDomainPath(), ['foo_domain', 'foo_category'])
self.assertEqual(selection.getDomainList(), ['foo_domain/a', 'foo_domain/a/a1', 'foo_category/a', 'foo_category/a/a2'])
self.assertEqual(sorted(selection.getDomainPath()), ['foo_category', 'foo_domain'])
self.assertEqual(sorted(selection.getDomainList()), ['foo_category/a', 'foo_category/a/a2', 'foo_domain/a', 'foo_domain/a/a1',])
self.assertEqual(selection.flat_list_mode, 0)
self.assertEqual(selection.domain_tree_mode, 1)
self.assertEqual(selection.report_tree_mode, 0)
......@@ -2791,7 +2794,7 @@ class TestERP5Document_getHateoas_mode_url_generator(ERP5HALJSONStyleSkinsMixin)
result,
'%s/web_site_module/hateoas/ERP5Document_getHateoas?'
'mode=traverse&relative_url=foo%%2Fbar&view=Foo_viewBar'
'&extra_param_json=eyJmb28iOiAiYSIsICJiYXIiOiAiYiJ9' % self.portal.absolute_url()
'&extra_param_json=eyJiYXIiOiAiYiIsICJmb28iOiAiYSJ9' % self.portal.absolute_url()
)
@simulate('Base_checkOnContextDocument', '*args, **kwargs',
......
......@@ -30,8 +30,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,4 +37,7 @@ for i, tracking in enumerate(reversed(portal.portal_simulation.getTrackingList(a
link=movement.absolute_url(),
source=movement.getSourceUid() 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 @@
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>6.0</float> </value>
<value> <float>20.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -24,8 +24,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -25,6 +25,7 @@
#
##############################################################################
import six.moves.urllib.parse
import uuid
import mock
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
......@@ -92,11 +93,15 @@ class TestFacebookLogin(ERP5TypeTestCase):
self.logout()
self.portal.ERP5Site_redirectToFacebookLoginPage()
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)
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):
self.login()
......
......@@ -116,7 +116,7 @@ social_contribution_stop_date = None
for employee_result in paysheet_data_list:
employee_ctp = employee_result['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_start_date = employee_ctp[ctp_code]['start_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:
for remuneration_block in paysheet_data_dict['remuneration']:
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))
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))
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))
if taxable_base_category['code'] == '02': # Assiette Brute plafonnee
if ('063', '') in paysheet_data_dict['individual_contribution']:
......
......@@ -205,19 +205,6 @@ S21.G00.54.001,'18'
S21.G00.54.002,'23.00'
S21.G00.54.003,'01012015'
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.002,'01012015'
S21.G00.78.003,'31012015'
......@@ -236,10 +223,27 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011'
S21.G00.81.003,'2250.00'
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.002,'01012015'
S21.G00.78.003,'31012015'
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.002,'01012015'
S21.G00.78.003,'31012015'
......@@ -249,10 +253,6 @@ S21.G00.79.001,'11'
S21.G00.79.004,'2250.00'
S21.G00.81.001,'059'
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.002,'75367340900011'
S21.G00.81.003,'2250.00'
......@@ -335,19 +335,6 @@ S21.G00.54.001,'17'
S21.G00.54.002,'62.40'
S21.G00.54.003,'01012015'
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.002,'01012015'
S21.G00.78.003,'31012015'
......@@ -364,10 +351,27 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011'
S21.G00.81.003,'3085.28'
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.002,'01012015'
S21.G00.78.003,'31012015'
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.002,'01012015'
S21.G00.78.003,'31012015'
......@@ -377,10 +381,6 @@ S21.G00.79.001,'11'
S21.G00.79.004,'3085.28'
S21.G00.81.001,'059'
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.002,'02'
S21.G00.86.003,'11'
......@@ -459,36 +459,12 @@ S21.G00.54.001,'17'
S21.G00.54.002,'67.20'
S21.G00.54.003,'01012015'
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.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'3170.00'
S21.G00.81.001,'063'
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.002,'01012015'
S21.G00.78.003,'31012015'
......@@ -499,14 +475,38 @@ S21.G00.81.001,'226'
S21.G00.81.002,'75367340900011'
S21.G00.81.003,'3827.86'
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.002,'01012015'
S21.G00.78.003,'31012015'
S21.G00.78.004,'3827.86'
S21.G00.78.001,'04'
S21.G00.78.001,'13'
S21.G00.78.002,'01012015'
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.002,'02'
S21.G00.86.003,'11'
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -102,7 +102,8 @@ class TestInvoice(TestInvoiceMixin):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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_applied_rule.contentValues()[0]
self.assertEqual(currency,
......@@ -349,7 +350,8 @@ class TestInvoice(TestInvoiceMixin):
delivery_movement = delivery_applied_rule.contentValues()[0]
invoice_applied_rule = delivery_movement.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
# for "income" line
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -220,12 +220,12 @@ class SyncMLSignature(XMLObject):
def getFirstPdataChunk(self, max_len):
"""
"""
partial_data = self._baseGetPartialData()
partial_data = bytes(self._baseGetPartialData()).decode('utf-8')
chunk = partial_data[:max_len]
rest_in_queue = partial_data[max_len:]
if rest_in_queue is not None:
self.setPartialData(rest_in_queue)
return bytes(chunk)
self.setPartialData(rest_in_queue.encode('utf-8'))
return chunk.encode('utf-8')
security.declareProtected(Permissions.ModifyPortalContent,
'setSubscriberXupdate')
......
......@@ -192,13 +192,15 @@ def getXupdateObject(object_xml=None, old_xml=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:
length = MAX_LEN
if not isinstance(xml_string, six.text_type):
xml_string = xml_string.decode('utf-8')
short_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
class XMLSyncUtilsMixin(object):
......
......@@ -37,8 +37,8 @@
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
<string>start_date</string>
</tuple>
</value>
</item>
......
......@@ -81,13 +81,13 @@
</tr>
<tr>
<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 -->
</tr>
<!-- First modification -->
<tr>
<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>
</tr>
<tr>
......@@ -153,13 +153,13 @@
</tr>
<tr>
<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 -->
</tr>
<!-- First modification -->
<tr>
<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>
</tr>
<tr>
......
......@@ -81,13 +81,12 @@
</tr>
<tr>
<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 -->
</tr>
<!-- First modification -->
<tr>
<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>
</tr>
<tr>
......@@ -153,13 +152,12 @@
</tr>
<tr>
<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 -->
</tr>
<!-- First modification -->
<tr>
<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>
</tr>
<tr>
......
......@@ -84,7 +84,7 @@
</tr>
<tr>
<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 -->
</tr>
......@@ -92,7 +92,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr>
<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>
</tr>
<tr>
......@@ -157,7 +157,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr>
<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>
</tr>
</tal:block>
......@@ -235,7 +235,7 @@
<tal:block tal:condition="python: context.TestTool_getSkinName()!='Mobile'">
<tr>
<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>
</tr>
</tal:block>
......
......@@ -279,7 +279,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: \' \'.join(here.getCategoryList())</string> </value>
<value> <string>python: \' \'.join(sorted(here.getCategoryList()))</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -27,12 +27,13 @@
#
##############################################################################
import lxml.html
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5Type.tests.Sequence import SequenceList
class TestGUISecurity(ERP5TypeTestCase):
"""
"""
......@@ -82,9 +83,10 @@ class TestGUISecurity(ERP5TypeTestCase):
Try to view the Foo_view form, make sure our category name is displayed
"""
self.loginAs()
self.assertIn(
self.category_field_markup,
self.portal.foo_module.foo.Foo_view())
self.assertTrue(
lxml.html.fromstring(
self.portal.foo_module.foo.Foo_view()
).xpath(self.category_field_xpath))
self.login()
def stepAccessFooDoesNotDisplayCategoryName(self, sequence = None, sequence_list = None, **kw):
......@@ -92,9 +94,10 @@ class TestGUISecurity(ERP5TypeTestCase):
Try to view the Foo_view form, make sure our category name is not displayed
"""
self.loginAs()
self.assertNotIn(
self.category_field_markup,
self.portal.foo_module.foo.Foo_view())
self.assertFalse(
lxml.html.fromstring(
self.portal.foo_module.foo.Foo_view()
).xpath(self.category_field_xpath))
self.login()
def stepChangeCategorySecurity(self, sequence = None, sequence_list = None, **kw):
......@@ -127,7 +130,7 @@ class TestGUISecurity(ERP5TypeTestCase):
An attempt to view the document form would raise Unauthorized.
"""
# 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_string = '\
......@@ -156,11 +159,18 @@ class TestGUISecurity(ERP5TypeTestCase):
self.stepCreateObjects()
self.stepCreateTestFoo()
protected_property_markup = '<input name="field_my_protected_property" value="Protected Property" type="text"'
self.assertIn(protected_property_markup, self.portal.foo_module.foo.Foo_viewSecurity())
protected_property_xpath = '//input[@name="field_my_protected_property" and @type="text" and @value="Protected Property"]'
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.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):
"""
......
......@@ -35,7 +35,7 @@
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tr>
<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>
</tr>
<tr>
......
......@@ -53,7 +53,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -63,7 +63,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -73,7 +73,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -83,7 +83,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -93,7 +93,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -103,7 +103,7 @@
</tr>
<tr>
<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>
</tr>
<tr>
......@@ -113,7 +113,7 @@
</tr>
<tr>
<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>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_module_list" />
......
......@@ -143,21 +143,46 @@
<tr>
<td>verifyText</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>
</tr>
<tr>
<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>
</tr>
<tr>
<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>
</tr>
<tr>
<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>
</tr>
......
......@@ -49,6 +49,19 @@
</tr>
<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>
<td colspan="3"><b>Empty value by default</b></td>
</tr>
......@@ -89,12 +102,12 @@
<tr>
<td>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>
<td>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>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
......@@ -106,12 +119,12 @@
<tr>
<td>assertSelected</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>
<td>assertSelected</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>
<td>assertElementNotPresent</td>
......
......@@ -45,6 +45,19 @@
</tr>
<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>
<td colspan="3"><b>Empty value by default</b></td>
</tr>
......@@ -80,18 +93,18 @@
<tr>
<td>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>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr>
<td>assertText</td>
<td>//p[@id="field_category_list"]</td>
<td>foo_big_category/c1</td>
<td>foo_category/a</td>
</tr>
<tr>
<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>label=c1</td>
<td>label=a</td>
</tr>
<tr>
<td>assertSelected</td>
......@@ -120,7 +133,7 @@
<tr>
<td>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>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
<tr>
......@@ -131,7 +144,7 @@
<tr>
<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>label=c1</td>
<td>label=a</td>
</tr>
<tr>
<td>assertSelected</td>
......@@ -146,7 +159,7 @@
<tr>
<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>label=a</td>
<td>label=c1</td>
</tr>
<tr>
<td>assertSelected</td>
......@@ -164,7 +177,7 @@
</tr>
<tr>
<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>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
......@@ -176,45 +189,46 @@
<tr>
<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>label=c1</td>
<td>label=a</td>
</tr>
<tr>
<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>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>
</tr>
<tr>
<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>
</tr>
<tr>
<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>label=a</td>
<td>label=c1</td>
</tr>
<tr>
<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>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>
</tr>
<tr>
<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>
</tr>
<tr>
<td colspan="3"><b>Remove first value</b></td>
<td colspan="3"><b>Remove first foo_big_category</b></td>
</tr>
<tr>
<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>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/save" />
......@@ -226,7 +240,7 @@
<tr>
<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>label=c22</td>
<td>label=a</td>
</tr>
<tr>
<td>assertSelected</td>
......@@ -241,7 +255,7 @@
<tr>
<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>label=a</td>
<td>label=c22</td>
</tr>
<tr>
<td>assertSelected</td>
......
......@@ -51,7 +51,7 @@
<!-- Check that Category titles in parallel_list_field are well translated -->
<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>//div[@data-gadget-scope='field_my_category_list']//div[@data-gadget-scope='PARALLEL_SUB_FIELD_0']//div//label</td>
<td>Foo leibie</td>
</tr>
......
......@@ -85,7 +85,12 @@
</tr>
<tr>
<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>
</tr>
</tbody></table>
......
......@@ -238,6 +238,7 @@ class TestConvertedWorkflow(TestERP5WorkflowMixin):
self.workflow = portal_workflow._getOb(self.workflow_id)
self.resetComponentTool()
self.login()
self.tic()
def test_13_permission(self):
"""
......
......@@ -70,6 +70,8 @@ class Resource(XMLObject, XMLMatrix, VariatedMixin):
, PropertySheet.Aggregated
)
_default_edit_order = XMLObject._default_edit_order + VariatedMixin._default_edit_order
# Is it OK now ?
# The same method is at about 3 different places
# Some genericity is needed
......
......@@ -988,7 +988,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, PortalObjectBase, CacheCook
for state in wf.getStateValueList():
if group in state.getStateTypeList():
state_set.add(state.getReference())
return tuple(state_set)
return tuple(sorted(state_set))
getStateList = CachingMethod(getStateList,
id=('_getPortalGroupedStateList', group),
......
......@@ -909,7 +909,7 @@ class TemplateTool (BaseTool):
repository_dict = {}
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] \
if x['id'] == bt_id][0]
bt_title = bt['title']
......@@ -924,7 +924,7 @@ class TemplateTool (BaseTool):
# Calculate the reverse dependency graph
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 = []
for dependency_id in dependency_id_list:
......@@ -934,10 +934,7 @@ class TemplateTool (BaseTool):
update_dependency_id_list.append(dependency_id)
# Fill incoming edge dict
if dependency_id in reverse_dependency_dict:
reverse_dependency_dict[dependency_id].append(bt_id)
else:
reverse_dependency_dict[dependency_id] = [bt_id]
reverse_dependency_dict.setdefault(dependency_id, []).append(bt_id)
# Remove from free node list
try:
......
......@@ -52,7 +52,7 @@
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>8.0</float> </value>
<value> <float>9.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
......
......@@ -42,7 +42,7 @@
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>3.0</float> </value>
<value> <float>30.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
......
......@@ -44,7 +44,7 @@
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>100.0</float> </value>
<value> <float>98.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
......
......@@ -66,6 +66,29 @@ class Amount(Base, VariatedMixin):
, 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
# THIS MUST BE UPDATE WITH CATEGORY ACQUISITION
security.declareProtected(Permissions.AccessContentsInformation,
......
......@@ -50,6 +50,8 @@ class AmountGeneratorLine(MappedValue, XMLMatrix, Amount,
property_sheets = (PropertySheet.DublinCore,
PropertySheet.AmountGeneratorLine)
_default_edit_order = Amount._default_edit_order
security.declareProtected(Permissions.AccessContentsInformation,
'getCellAggregateKey')
def getCellAggregateKey(self):
......
......@@ -80,6 +80,11 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
, PropertySheet.Price
)
_default_edit_order = XMLObject._default_edit_order + (
'stop_date',
'start_date',
)
security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable')
def isAccountable(self):
"""
......
......@@ -124,24 +124,6 @@ class DeliveryCell(MappedValue, Movement, ImmobilisationMovement):
self._setPredicateValueList(new_predicate_value)
# No reindex needed since uid stable
# XXX FIXME: option variation are today not well implemented
# This little hack is needed to make the matrixbox working
# in DeliveryLine_viewIndustrialPhase
# Generic form (DeliveryLine_viewOption) is required
def _edit(self, **kw):
"""
Store variation_category_list, in order to store new value of
industrial_phase after.
"""
edit_order = ['variation_category_list', # edit this one first
'item_id_list'] # this one must be the last
edit_order[1:1] = [x for x in kw.pop('edit_order', ())
if x not in edit_order]
# Base._edit updates unordered properties first
edit_order[1:1] = [x for x in kw if x not in edit_order]
MappedValue._edit(self, edit_order=edit_order, **kw)
# if self.isSimulated():
# self.getRootDeliveryValue().activate().propagateResourceToSimulation()
security.declareProtected(Permissions.ModifyPortalContent,
'updateSimulationDeliveryProperties')
......
......@@ -36,9 +36,6 @@ from Products.ERP5Type.XMLMatrix import XMLMatrix
from erp5.component.document.Movement import Movement
from erp5.component.document.ImmobilisationMovement import ImmobilisationMovement
from inspect import getargspec
from Products.ERP5Type.Base import Base
edit_args_list = getargspec(Base._edit).args
from erp5.component.interface.IDivergenceController import IDivergenceController
......@@ -75,29 +72,6 @@ class DeliveryLine(Movement, XMLMatrix, ImmobilisationMovement):
# Multiple inheritance definition
updateRelatedContent = XMLMatrix.updateRelatedContent
# Force in _edit to modify variation_base_category_list first
def _edit(self, edit_order=(), **kw):
# XXX FIXME For now, special cases are handled in _edit methods in many
# documents : DeliveryLine, DeliveryCell ... Ideally, to prevent code
# duplication, it should be handled in a _edit method present only in
# Amount.py
# If variations and resources are set at the same time, resource must be
# set before any variation.
before_order = ('resource', 'resource_value',
'variation_base_category_list',
'variation_category_list')
before_kw = {k: kw.pop(k) for k in before_order if k in kw}
if before_kw:
before_kw.update((k, kw[k]) for k in edit_args_list if k in kw)
Base._edit(self, edit_order=before_order, **before_kw)
if kw:
Movement._edit(self, edit_order=edit_order, **kw)
# We must check if the user has changed the resource of particular line
security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
def edit(self, REQUEST=None, force_update = 0, reindex_object=1, **kw):
return self._edit(REQUEST=REQUEST, force_update=force_update, reindex_object=reindex_object, **kw)
security.declareProtected(Permissions.AccessContentsInformation,
'isAccountable')
......
......@@ -73,7 +73,7 @@ class Movement(XMLObject, Amount, CompositionMixin, AmountGeneratorMixin):
to define quantities in orders
- Deliveries: movements track the actual transfer of resources
in the past (accounting) or in the future (planning / budgetting)
in the past (accounting) or in the future (planning / budgeting)
For example, the following objects are Orders:
......@@ -217,8 +217,13 @@ class Movement(XMLObject, Amount, CompositionMixin, AmountGeneratorMixin):
, PropertySheet.Movement
, PropertySheet.Price
, PropertySheet.Simulation # XXX-JPS property should be moved to GeneratedMovement class
)
_default_edit_order = Amount._default_edit_order + (
'stop_date',
'start_date',
)
def isPropertyRecorded(self, k): # XXX-JPS method should be moved to GeneratedMovement class
return False
......@@ -726,9 +731,6 @@ class Movement(XMLObject, Amount, CompositionMixin, AmountGeneratorMixin):
def _edit(self, edit_order=(), **kw):
"""Overloaded _edit to support setting debit and credit at the same time,
which is required for the GUI.
Also sets the variation category list and property dict at the end, because
_setVariationCategoryList and _setVariationPropertyDict needs the resource
to be set.
"""
quantity = 0
if 'source_debit' in kw and 'source_credit' in kw:
......@@ -757,9 +759,7 @@ class Movement(XMLObject, Amount, CompositionMixin, AmountGeneratorMixin):
if kw.get('destination_asset_credit') in (None, ''):
kw.pop('destination_asset_credit', None)
if not edit_order:
edit_order = ('variation_category_list', 'variation_property_dict',)
return XMLObject._edit(self, edit_order=edit_order, **kw)
return super(Movement, self)._edit(edit_order=edit_order, **kw)
# Debit and credit methods for asset
security.declareProtected( Permissions.AccessContentsInformation,
......
......@@ -315,7 +315,6 @@ class TextDocument(CachedConvertableMixin, BaseConvertableFileMixin, TextContent
try:
tree = etree.fromstring(text_content)
text_content = etree.tostring(tree, encoding='utf-8', xml_declaration=True)
content_type = 'application/xml'
message = 'Conversion to base format succeeds'
except etree.XMLSyntaxError: # pylint: disable=catching-non-exception
message = 'Conversion to base format without codec fails'
......
......@@ -28,11 +28,10 @@
#
##############################################################################
from collections import OrderedDict
from warnings import warn
from Products.PythonScripts.Utility import allow_class
class FakeMovementError(Exception) : pass
class MovementGroupError(Exception) : pass
class MovementGroupNode:
# XXX last_line_movement_group is a wrong name. Actually, it is
......@@ -60,7 +59,7 @@ class MovementGroupNode:
last_line_movement_group=self._last_line_movement_group,
separate_method_name_list=self._separate_method_name_list,
merge_delivery=self._merge_delivery)
nested_instance.setGroupEdit(**property_dict)
nested_instance.setGroupEdit(property_dict)
split_movement_list = nested_instance.append(movement_list)
self._group_list.append(nested_instance)
return split_movement_list
......@@ -93,27 +92,20 @@ class MovementGroupNode:
def getGroupList(self):
return self._group_list
def setGroupEdit(self, **kw):
def setGroupEdit(self, kw):
"""
Store properties for the futur created object
Store properties for the future created object
"""
self._property_dict = kw
def updateGroupEdit(self, **kw):
"""
Update properties for the futur created object
"""
self._property_dict.update(kw)
def getGroupEditDict(self):
"""
Get property dict for the futur created object
Get property dict for the future created object
"""
property_dict = getattr(self, '_property_dict', {}).copy()
for key in property_dict.keys():
if key.startswith('_'):
del(property_dict[key])
return property_dict
return OrderedDict([
(k, v)
for (k, v) in getattr(self, '_property_dict', {}).items()
if not k.startswith('_')])
def getCurrentMovementGroup(self):
return self._movement_group
......
......@@ -23,7 +23,7 @@ result = []
binary_data_explanation = Base_translateString("Binary data can't be displayed")
base_error_message = Base_translateString('(value retrieval failed)')
for prop_dict in context.getPropertyMap():
for prop_dict in sorted(context.getPropertyMap(), key=lambda prop: prop['id']):
prop = prop_dict['id']
error = False
try:
......
......@@ -42,4 +42,6 @@ for item in item_list:
# Return the list of subfield configuration.
return sub_field_dict.values()
return sorted(
sub_field_dict.values(),
key=lambda v: v['title'])
......@@ -11,5 +11,5 @@ return [
"workflow_id": workflow_id,
"workflow_title": portal_workflow[workflow_id].title or workflow_id},
temporary_selection=False)
for workflow_id in context.Base_getWorkflowHistory()
for workflow_id in sorted(context.Base_getWorkflowHistory())
]
......@@ -49,6 +49,10 @@
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: \'\'</string> </value>
</item>
<item>
<key> <string>translatable</string> </key>
<value> <int>1</int> </value>
......
......@@ -59,6 +59,11 @@ class VariatedMixin:
isRADContent = 1 # for 'property_sheets'
property_sheets = (PropertySheet.VariationRange, )
_default_edit_order = (
'variation_base_category_list',
'variation_category_list',
)
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationBaseCategoryList')
def getVariationBaseCategoryList(self, omit_optional_variation=0,
......
......@@ -1640,7 +1640,7 @@ class TestInventoryList(InventoryAPITestCase):
for month, value in six.iteritems(data):
for mov in value['movement_list']:
d = DateTime('%s/15 15:00 UTC' % month)
self._makeMovement(start_date=d, resource_uid=resource_uid, **mov)
self._makeMovement(start_date=d, resource_value=resource, **mov)
# and check
for cur in sorted(data)[1:]:
......@@ -1665,7 +1665,7 @@ class TestInventoryList(InventoryAPITestCase):
for month, value in six.iteritems(internal_data):
for mov in value['movement_list']:
d = DateTime('%s/15 15:00 UTC' % month)
self._makeMovement(is_internal=1, start_date=d, resource_uid=resource_uid, **mov)
self._makeMovement(is_internal=1, start_date=d, resource_value=resource, **mov)
for cur in sorted(internal_data):
to_date = DateTime("%s/1" % cur) + 31
# check by section
......
......@@ -197,8 +197,15 @@ class CaptchaWidget(Widget.TextWidget):
provider = CaptchaProviderFactory.getProvider(captcha_type)
(captcha_key, captcha_answer) = provider.generate(field)
portal_sessions = field.getPortalObject().portal_sessions
while not self.add_captcha(portal_sessions, md5(captcha_key).hexdigest(), captcha_answer):
retries = 10
while retries:
if self.add_captcha(portal_sessions, md5(captcha_key).hexdigest(), captcha_answer):
(captcha_key, captcha_answer) = provider.generate(field)
break
retries = retries - 1
else:
raise RuntimeError("Error adding captcha")
captcha_field = provider.getHTML(field, captcha_key)
key_field = Widget.render_element("input",
......
......@@ -452,11 +452,16 @@ class TestDeferredStyleBase(DeferredStyleTestCase):
# request parameter.
mail_message = email.message_from_string(self.portal.MailHost._last_message[2])
part, = [x for x in mail_message.walk() if x.get_content_type() == self.content_type]
report_as_txt = self.portal.portal_transforms.convertTo(
'text/plain',
part.get_payload(decode=True),
context=self.portal,
mimetype=self.content_type).getData()
doc = self.portal.document_module.newContent(
portal_type=self.portal_type,
content_type=self.content_type,
data=part.get_payload(decode=True),
temp_object=True,
)
doc.convertToBaseFormat()
report_as_txt = doc.asText()
self.assertIn(
'in_list_method: set_in_dialog_request == set_in_dialog_request, set_in_report_method == set_in_report_method',
report_as_txt)
......
......@@ -1507,7 +1507,7 @@ class TestIngestion(IngestionTestCase):
self.assertEqual(['anybody'], document.getGroupList())
document.discoverMetadata(document.getFilename(), other_user.Person_getUserId())
self.assertEqual(['anybody/a1', 'anybody/a2'], document.getGroupList())
self.assertEqual(sorted(document.getGroupList()), ['anybody/a1', 'anybody/a2'])
def test_IngestionConfigurationByTypeBasedMethod_usecase1(self):
"""How to configure meta data discovery so that each time a file
......
......@@ -27,6 +27,7 @@ from __future__ import absolute_import
#
##############################################################################
from collections import OrderedDict
from .Base import func_code, type_definition, list_types, ATTRIBUTE_PREFIX, Getter as BaseGetter
from Products.ERP5Type.PsycoWrapper import psyco
......@@ -208,4 +209,4 @@ class SetGetter(ListGetter):
Gets a category value set
"""
def __call__(self, instance, *args, **kw):
return list(set(ListGetter.__call__(self, instance, *args, **kw)))
return list(OrderedDict.fromkeys(ListGetter.__call__(self, instance, *args, **kw)))
......@@ -28,6 +28,7 @@ from __future__ import absolute_import
##############################################################################
from collections import OrderedDict
from .Base import func_code, type_definition, list_types,\
ATTRIBUTE_PREFIX, Method, evaluateTales
from .TypeDefinition import asList, identity
......@@ -80,8 +81,7 @@ class DefaultSetter(Base.Setter):
else:
if self._item_cast is not identity:
value = self._item_cast(value)
list_value = set(getattr(instance, self._storage_id, ()))
list_value.discard(value)
list_value = list(OrderedDict.fromkeys(e for e in getattr(instance, self._storage_id, ()) if e != value))
setattr(instance, self._storage_id, (value,) + tuple(list_value))
class ListSetter(DefaultSetter):
......@@ -145,7 +145,7 @@ class SetSetter(Base.Setter):
if self._item_cast is not identity:
value = [self._item_cast(v) for v in value]
if value:
value = set(value)
value = list(OrderedDict.fromkeys(value))
list_value = getattr(instance, self._storage_id, None)
if list_value:
default_value = list_value[0]
......@@ -273,6 +273,6 @@ class SetGetter(ListGetter):
def __call__(self, instance, *args, **kw):
result_list = ListGetter.__call__(self, instance, *args, **kw)
if result_list is not None:
return list(set(result_list))
return list(OrderedDict.fromkeys(result_list))
Tester = Base.Tester
......@@ -27,6 +27,7 @@ from __future__ import absolute_import
#
##############################################################################
from collections import OrderedDict
from .Base import func_code, type_definition, list_types, ATTRIBUTE_PREFIX, Getter as BaseGetter, Setter as BaseSetter
from Products.ERP5Type.PsycoWrapper import psyco
from zLOG import LOG
......@@ -106,4 +107,4 @@ class SetGetter(ListGetter):
Gets a category value set
"""
def __call__(self, instance, *args, **kw):
return list(set(ListGetter.__call__(self, instance, *args, **kw)))
return list(OrderedDict.fromkeys(ListGetter.__call__(self, instance, *args, **kw)))
......@@ -27,6 +27,7 @@ from __future__ import absolute_import
#
##############################################################################
from collections import OrderedDict
from .Base import func_code, type_definition, list_types, \
ATTRIBUTE_PREFIX, Getter as BaseGetter, Setter as BaseSetter
from Products.ERP5Type.PsycoWrapper import psyco
......@@ -112,7 +113,7 @@ class SetGetter(ListGetter):
Gets a category value set
"""
def __call__(self, instance, *args, **kw):
return list(set(ListGetter.__call__(self, instance, *args, **kw)))
return list(OrderedDict.fromkeys(ListGetter.__call__(self, instance, *args, **kw)))
class DefaultPropertyGetter(BaseGetter):
......@@ -192,7 +193,7 @@ class PropertySetGetter(PropertyListGetter):
Gets a category value set
"""
def __call__(self, instance, *args, **kw):
return list(set(PropertyListGetter.__call__(self, instance, *args, **kw)))
return list(OrderedDict.fromkeys(PropertyListGetter.__call__(self, instance, *args, **kw)))
class DefaultIdGetter(PropertyGetter):
......
......@@ -232,7 +232,7 @@ class UidSetSetter(BaseSetter):
def __call__(self, instance, *args, **kw):
if self._warning:
LOG("ERP5Type Deprecated Getter Id:",0, self._id)
instance._setValueUidList(self._key, set(args[0]),
instance._setValueUidList(self._key, list(OrderedDict.fromkeys(args[0])),
spec=kw.get('spec',()),
filter=kw.get('filter', None),
portal_type=kw.get('portal_type',()),
......@@ -298,4 +298,4 @@ class PropertySetGetter(PropertyListGetter):
"""
def __call__(self, instance, *args, **kw):
r = PropertyListGetter.__call__(self, instance, **kw)
return list(set(r)) if r or not args else args[0]
return list(OrderedDict.fromkeys(r)) if r or not args else args[0]
......@@ -222,7 +222,7 @@ class WorkflowMethod(Method):
# Otherwise, an exception is raised if the workflow transition does not
# exist from the current state, or if the guard rejects it.
valid_transition_item_list = []
for wf_id, transition_list in candidate_transition_item_list:
for wf_id, transition_list in sorted(candidate_transition_item_list):
candidate_workflow = wf[wf_id]
valid_list = []
state = candidate_workflow._getWorkflowStateOf(instance, id_only=0)
......@@ -779,6 +779,11 @@ class Base(
# Declarative properties
property_sheets = ( PropertySheet.Base, )
_default_edit_order = (
'title',
'reference',
)
# We want to use a default property view
manage_main = manage_propertiesForm = DTMLFile( 'properties', _dtmldir )
manage_main._setName('manage_main')
......@@ -1478,7 +1483,7 @@ class Base(
# Object attributes update method
def _edit(self, REQUEST=None, force_update=0, reindex_object=0,
keep_existing=0, activate_kw=None, edit_order=[], restricted=0, **kw):
keep_existing=0, activate_kw=None, edit_order=(), restricted=0, **kw):
"""
Generic edit Method for all ERP5 object
The purpose of this method is to update attributed, eventually do
......@@ -1496,7 +1501,8 @@ class Base(
"""
if not kw:
return
key_list = kw.keys()
edit_order = edit_order or self._default_edit_order
key_list = sorted(kw.keys())
modified_property_dict = self._v_modified_property_dict = {}
modified_object_dict = {}
......
......@@ -225,11 +225,11 @@ class CachingMethod:
## generate cache id out of arguments passed.
## depending on arguments we may have different
## cache_id for same method_id
return str((method_id, args, kw))
return str((method_id, args, sorted(kw.items())))
@staticmethod
def erasable_cache_id_generator(method_id, obj, *args, **kw):
return str((method_id, obj.getCacheCookie(method_id), args, kw))
return str((method_id, obj.getCacheCookie(method_id), args, sorted(kw.items())))
def __init__(self, callable_object, id, cache_duration=180,
cache_factory=DEFAULT_CACHE_FACTORY,
......
......@@ -97,7 +97,7 @@ def Base_asXML(object, root=None):
portal_type=self.getPortalType()))
# We have to find every property
for prop_id in set(self.propertyIds()):
for prop_id in sorted(set(self.propertyIds())):
# In most case, we should not synchronize acquired properties
if prop_id not in ('uid', 'workflow_history', 'id', 'portal_type') and (prop_id != 'user_id' or 'ERP5User' not in getattr(
getattr(
......
......@@ -33,6 +33,7 @@ Accessor Holders, that is, generation of methods for ERP5
* Utils, Property Sheet Tool can be probably be cleaned up as well by
moving specialized code here.
"""
import collections
from six import string_types as basestring
from types import ModuleType
......@@ -342,7 +343,7 @@ def applyCategoryAsRelatedValueAccessor(accessor_holder,
accessor = accessor_class(accessor_name % uppercase_category_id, category_id)
accessor_holder.registerAccessor(accessor, read_permission)
def getPropertySheetValueList(site, property_sheet_name_set):
def getPropertySheetValueList(site, property_sheet_name_list):
try:
property_sheet_tool = site.portal_property_sheets
......@@ -356,7 +357,7 @@ def getPropertySheetValueList(site, property_sheet_name_set):
property_sheet_value_list = []
for property_sheet_name in property_sheet_name_set:
for property_sheet_name in property_sheet_name_list:
try:
property_sheet = property_sheet_tool._getOb(property_sheet_name)
except (AttributeError, KeyError):
......@@ -420,7 +421,10 @@ def createAllAccessorHolderList(site,
"""
from erp5 import accessor_holder as accessor_holder_module
property_sheet_name_set = set()
# Use an ordered dict as a set keeping the order, to generate the list
# of accessor holders that will be the bases of the portal type class
# in the same order as the property sheets defined in the class hierarchy.
property_sheet_name_set = collections.OrderedDict()
accessor_holder_list = []
# Get the accessor holders of the Portal Type
......@@ -456,7 +460,7 @@ def createAllAccessorHolderList(site,
else:
for property_sheet in zodb_property_sheet_name_set:
if property_sheet.endswith('Preference'):
property_sheet_name_set.add(property_sheet)
property_sheet_name_set[property_sheet] = None
# XXX a hook to add per-portal type accessor holders maybe?
if portal_type_name == "Preference Tool":
......@@ -479,15 +483,15 @@ def createAllAccessorHolderList(site,
if not isinstance(property_sheet, basestring):
property_sheet = property_sheet.__name__
property_sheet_name_set.add(property_sheet)
property_sheet_name_set[property_sheet] = None
property_sheet_name_set = property_sheet_name_set - \
portal_type_property_sheet_name_set
property_sheet_value_list = getPropertySheetValueList(
site,
[ps for ps in property_sheet_name_set
if ps not in portal_type_property_sheet_name_set])
document_accessor_holder_list = \
getAccessorHolderList(site, portal_type_name,
getPropertySheetValueList(site,
property_sheet_name_set))
getAccessorHolderList(site, portal_type_name, property_sheet_value_list)
accessor_holder_list.extend(document_accessor_holder_list)
......
......@@ -55,6 +55,18 @@ from Products.ERP5Type.Utils import simple_decorator
from Products.ZSQLCatalog.SQLCatalog import Catalog
import pytz
import six
import lxml.html
def canonical_html(html):
# type: (str) -> str
"""returns canonical form of html text.
"""
return lxml.html.tostring(
lxml.html.fromstring(html),
method="c14n",
).decode('utf-8')
class FileUpload(file):
"""Act as an uploaded file.
......
......@@ -14,6 +14,7 @@
##############################################################################
from __future__ import absolute_import
from collections import OrderedDict
import six
from six import string_types as basestring
from six.moves import xrange
......@@ -1931,7 +1932,7 @@ class Catalog(Folder,
)
else:
query_list = []
value_dict = {}
value_dict = OrderedDict()
append = query_list.append
for subnode in node.getNodeList():
if subnode.isLeaf():
......
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