From 66bb444a67edcd41df749e1069203f3cb54832fb Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Mon, 8 May 2017 09:05:12 +1000 Subject: [PATCH 01/10] [FIX] Readonly fields not being stored. If the wizard turns a field to readony because the combination means it is unenterable now, then the value needs to be submitted when the client attempts to write the record. Since the Odoo client does not submit readonly values, then we need to ensure we get that value by keeping an "invisible" equivalent of the real value, which is never readonly. --- .../wizard/product_configurator.py | 77 +++++++++++++++---- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index 5e0055d0..47a49abf 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -21,6 +21,7 @@ class ProductConfigurator(models.TransientModel): # Prefix for the dynamicly injected fields field_prefix = '__attribute-' custom_field_prefix = '__custom-' + invisible_field_prefix = '__invisible-' # TODO: Since the configuration process can take a bit of time # depending on complexity and AFK time we must increase the lifespan @@ -78,7 +79,7 @@ def onchange_product_tmpl(self): def get_onchange_domains(self, values, cfg_val_ids): """Generate domains to be returned by onchange method in order - to restrict the availble values of dynamically inserted fields + to restrict the available values of dynamically inserted fields :param values: values argument passed to onchance wrapper :cfg_val_ids: current configuration passed as a list of value_ids @@ -335,6 +336,13 @@ def fields_get(self, allfields=None, attributes=None): relation='product.attribute.value', sequence=line.sequence, ) + res[self.invisible_field_prefix + str(attribute.id)] = dict( + default_attrs, + type='many2many' if line.multi else 'many2one', + string=line.attribute_id.name, + relation='product.attribute.value', + sequence=line.sequence, + ) return res @@ -358,8 +366,10 @@ def fields_view_get(self, view_id=None, view_type='form', # Get updated fields including the dynamic ones fields = self.fields_get() dynamic_fields = { - k: v for k, v in fields.iteritems() if k.startswith( - self.field_prefix) or k.startswith(self.custom_field_prefix) + k: v for k, v in fields.iteritems() if + k.startswith(self.field_prefix) or + k.startswith(self.custom_field_prefix) or + k.startswith(self.invisible_field_prefix) } res['fields'].update(dynamic_fields) @@ -367,7 +377,6 @@ def fields_view_get(self, view_id=None, view_type='form', # Update result dict from super with modified view res.update({'arch': etree.tostring(mod_view)}) - return res @api.model @@ -405,6 +414,7 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz): attribute_id = attr_line.attribute_id.id field_name = self.field_prefix + str(attribute_id) custom_field = self.custom_field_prefix + str(attribute_id) + invisible_field = self.invisible_field_prefix + str(attribute_id) # Check if the attribute line has been added to the db fields if field_name not in dynamic_fields: @@ -498,6 +508,17 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz): orm.setup_modifiers(node) xml_dynamic_form.append(node) + # Create the new invisible field in the view + node = etree.Element( + "field", + name=invisible_field, + invisible='1' + ) + if field_type == 'many2many': + node.attrib['widget'] = 'many2many_tags' + orm.setup_modifiers(node) + xml_dynamic_form.append(node) + if attr_line.custom and custom_field in dynamic_fields: widget = '' custom_ext_id = 'product_configurator.custom_attribute_value' @@ -560,8 +581,11 @@ def read(self, fields=None, load='_classic_read'): custom_attr_vals = [ f for f in fields if f.startswith(self.custom_field_prefix) ] + invis_vals = [ + f for f in fields if f.startswith(self.invisible_field_prefix) + ] - dynamic_fields = attr_vals + custom_attr_vals + dynamic_fields = attr_vals + custom_attr_vals + invis_vals fields = [f for f in fields if f not in dynamic_fields] custom_ext_id = 'product_configurator.custom_attribute_value' @@ -580,6 +604,8 @@ def read(self, fields=None, load='_classic_read'): if field_name not in dynamic_fields: continue + invis_field_name = self.invisible_field_prefix + str(attr_id) + custom_field_name = self.custom_field_prefix + str(attr_id) custom_vals = self.custom_value_ids.filtered( lambda x: x.attribute_id.id == attr_id) @@ -602,11 +628,15 @@ def read(self, fields=None, load='_classic_read'): custom_field_name: custom_vals.eval() }) elif attr_line.multi: - dynamic_vals = {field_name: [[6, 0, vals.ids]]} + dynamic_vals = {field_name: [[6, 0, vals.ids]], + invis_field_name: [[6, 0, vals.ids]] + } else: try: vals.ensure_one() - dynamic_vals = {field_name: vals.id} + dynamic_vals = {field_name: vals.id, + invis_field_name: vals.id + } except: continue res[0].update(dynamic_vals) @@ -625,28 +655,45 @@ def write(self, vals): attr_val_dict = {} custom_val_dict = {} + # attributes which are part of the current step which are + # readonly are not submitted by the client! We have to make sure + # these are treated as values to be written. + # They are submitted as part of an "invisible" field which is + # not readonly so that we can use that instead. + for attr_line in self.product_tmpl_id.attribute_line_ids: attr_id = attr_line.attribute_id.id field_name = self.field_prefix + str(attr_id) custom_field_name = self.custom_field_prefix + str(attr_id) + invisible_field_name = self.invisible_field_prefix + str(attr_id) - if field_name not in vals and custom_field_name not in vals: + if field_name not in vals and \ + custom_field_name not in vals and \ + invisible_field_name not in vals: continue + # If field value is not submitted, then perhaps it was readonly, + # in which case we should look at the invisible equivalent. + # If it was not submitted by the client, then it implies that we + # are here because a custom attribute was submitted. + field_val = vals[field_name] if field_name in vals else \ + vals[invisible_field_name] if invisible_field_name in vals \ + else custom_val.id + # Add attribute values from the client except custom attribute # If a custom value is being written, but field name is not in # the write dictionary, then it must be a custom value! - if vals.get(field_name, custom_val.id) != custom_val.id: - if attr_line.multi and isinstance(vals[field_name], list): - if not vals[field_name]: + if field_val != custom_val.id: + if attr_line.multi and isinstance(field_val, list): + if not field_val: field_val = None else: - field_val = vals[field_name][0][2] - elif not attr_line.multi and isinstance(vals[field_name], int): - field_val = vals[field_name] + field_val = field_val[0][2] + elif not attr_line.multi and isinstance(field_val, int): + pass else: raise Warning( - _('An error occursed while parsing value for ' + _('An error occurred while parsing value for ' 'attribute %s' % attr_line.attribute_id.name) ) attr_val_dict.update({ From c79476bebc5090eda028402154848b7fe746617d Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Mon, 8 May 2017 09:16:04 +1000 Subject: [PATCH 02/10] Changed value during onchange needs to be put in invisible equivalent. --- .../wizard/product_configurator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index 47a49abf..b46094ad 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -198,6 +198,14 @@ def onchange(self, values, field_name, field_onchange): domains = self.get_onchange_domains(values, cfg_val_ids) vals = self.get_form_vals(dynamic_fields, domains) + + # the on changed value is stored in "invisible" equivalent in case they + # become readonly + for k, v in values.items(): + if k.startswith(self.field_prefix): + vals[k.replace(self.field_prefix, + self.invisible_field_prefix, 1) + ] = v return {'value': vals, 'domain': domains} attribute_line_ids = fields.One2many( From 6719c906f63cb8eaa305bb28a5a0b54be76015bd Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Mon, 8 May 2017 09:41:35 +1000 Subject: [PATCH 03/10] If stored value is custom when read, then invisible equivalent needs to be custom, too. --- product_configurator_wizard/wizard/product_configurator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index b46094ad..d449635b 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -626,6 +626,7 @@ def read(self, fields=None, load='_classic_read'): if attr_line.custom and custom_vals: dynamic_vals.update({ field_name: custom_val.id, + invis_field_name: custom_val.id, }) if attr_line.attribute_id.custom_type == 'binary': dynamic_vals.update({ From 2b814d407768655e07e0eff2a66fcaa18fc4b0d0 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:03:41 +1000 Subject: [PATCH 04/10] Tests enhanced. --- product_configurator_wizard/tests/__init__.py | 1 + .../tests/test_wizard.py | 51 +++-- .../tests/test_wizard_attrs.py | 182 ++++++++++++++++++ .../wizard/product_configurator.py | 2 +- 4 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 product_configurator_wizard/tests/test_wizard_attrs.py diff --git a/product_configurator_wizard/tests/__init__.py b/product_configurator_wizard/tests/__init__.py index e037c216..404c70da 100644 --- a/product_configurator_wizard/tests/__init__.py +++ b/product_configurator_wizard/tests/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import test_wizard +from . import test_wizard_attrs diff --git a/product_configurator_wizard/tests/test_wizard.py b/product_configurator_wizard/tests/test_wizard.py index 3acd8643..86011580 100644 --- a/product_configurator_wizard/tests/test_wizard.py +++ b/product_configurator_wizard/tests/test_wizard.py @@ -23,7 +23,7 @@ def get_attr_values(self, attr_val_ext_ids=None): return attr_vals - def get_wizard_write_dict(self, wizard, attr_values): + def get_wizard_write_dict(self, wizard, attr_values, remove_values=[]): """Turn a series of attribute.value objects to a dictionary meant for writing values to the product.configurator wizard""" @@ -40,6 +40,10 @@ def get_wizard_write_dict(self, wizard, attr_values): continue write_dict.update({field_name: val.id}) + for val in remove_values: + field_name = wizard.field_prefix + str(val.attribute_id.id) + write_dict.update({field_name: False}) + return write_dict def wizard_write_proceed(self, wizard, attr_vals, value_ids=None): @@ -94,30 +98,55 @@ def test_wizard_configuration(self): self.assertTrue(len(config_variants) == 1, "Wizard did not create a configurable variant") + def do_reconfigure(self, order_line, attr_vals): + reconfig_action = order_line.reconfigure_product() + + wizard = self.env['product.configurator'].browse( + reconfig_action.get('res_id') + ) + + self.wizard_write_proceed(wizard, attr_vals) + + # if wizard only had one step, it would already have completed + if wizard.exists(): + # Cycle through steps until wizard ends + while wizard.action_next_step(): + pass + def test_reconfiguration(self): """Test reconfiguration functionality of the wizard""" self.test_wizard_configuration() + existing_lines = self.so.order_line + order_line = self.so.order_line.filtered( lambda l: l.product_id.config_ok ) - reconfig_action = order_line.reconfigure_product() + self.do_reconfigure(order_line, + self.get_attr_values(['diesel', '220d']) + ) - wizard = self.env['product.configurator'].browse( - reconfig_action.get('res_id') - ) + config_variants = self.env['product.product'].search([ + ('config_ok', '=', True) + ]) - attr_vals = self.get_attr_values(['diesel', '220d']) - self.wizard_write_proceed(wizard, attr_vals) + self.assertTrue(len(config_variants) == 2, + "Wizard reconfiguration did not create a new variant") + + created_line = self.so.order_line - existing_lines + self.assertTrue(len(created_line) == 0, + "Wizard created an order line on reconfiguration") - # Cycle through steps until wizard ends - while wizard.action_next_step(): - pass + # test that running through again with the same values does not + # create another variant + self.do_reconfigure(order_line, + self.get_attr_values(['diesel', '220d']) + ) config_variants = self.env['product.product'].search([ ('config_ok', '=', True) ]) self.assertTrue(len(config_variants) == 2, - "Wizard reconfiguration did not create a new variant") + "Wizard reconfiguration created a redundant variant") diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py new file mode 100644 index 00000000..5832fc38 --- /dev/null +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError +from test_wizard import ConfigurationRules + + +class ConfigurationAttributes(ConfigurationRules): + + def setUp(self): + """ + Product with 3 sizes: + Small or Medium allow Blue or Red, but colour is optional + Large does not allow colour selection + """ + super(ConfigurationAttributes, self).setUp() + + self.attr_size = self.env['product.attribute'].create( + {'name': 'Size'}) + self.attr_val_small = self.env['product.attribute.value'].create( + {'attribute_id': self.attr_size.id, + 'name': 'Small', + } + ) + self.attr_val_med = self.env['product.attribute.value'].create( + {'attribute_id': self.attr_size.id, + 'name': 'Medium', + } + ) + self.attr_val_large = self.env['product.attribute.value'].create( + {'attribute_id': self.attr_size.id, + 'name': 'Large (Green)', + } + ) + domain_small_med = self.env['product.config.domain'].create( + {'name': 'Small/Med', + 'domain_line_ids': [ + (0, 0, {'attribute_id': self.attr_size.id, + 'condition': 'in', + 'operator': 'and', + 'value_ids': + [(6, 0, + [self.attr_val_small.id, self.attr_val_med.id] + )] + }), + ], + } + ) + self.attr_colour = self.env['product.attribute'].create( + {'name': 'Colour'}) + self.attr_val_blue = self.env['product.attribute.value'].create( + {'attribute_id': self.attr_colour.id, + 'name': 'Blue', + } + ) + self.attr_val_red = self.env['product.attribute.value'].create( + {'attribute_id': self.attr_colour.id, + 'name': 'Red', + } + ) + self.product_temp = self.env['product.template'].create( + {'name': 'Config Product', + 'config_ok': True, + 'type': 'product', + 'categ_id': self.env['ir.model.data'].xmlid_to_res_id( + 'product.product_category_5' + ), + 'attribute_line_ids': [ + (0, 0, {'attribute_id': self.attr_size.id, + 'value_ids': [ + (6, 0, self.attr_size.value_ids.ids), + ], + 'required': True, + }), + (0, 0, {'attribute_id': self.attr_colour.id, + 'value_ids': [ + (6, 0, self.attr_colour.value_ids.ids), + ], + 'required': False, + }) + ], + } + ) + colour_line = self.product_temp.attribute_line_ids.filtered( + lambda a: a.attribute_id == self.attr_colour) + self.env['product.config.line'].create({ + 'product_tmpl_id': self.product_temp.id, + 'attribute_line_id': colour_line.id, + 'value_ids': [(6, 0, self.attr_colour.value_ids.ids)], + 'domain_id': domain_small_med.id, + }) + + def test_configurations_option_or_not_reqd(self): + # Start a new configuration wizard + wizard_obj = self.env['product.configurator'].with_context({ + 'active_model': 'sale.order', + 'default_order_id': self.so.id + }) + + wizard = wizard_obj.create({'product_tmpl_id': self.product_temp.id}) + wizard.action_next_step() + + dynamic_fields = {} + for attribute_line in self.product_temp.attribute_line_ids: + field_name = '%s%s' % ( + wizard.field_prefix, + attribute_line.attribute_id.id + ) + dynamic_fields[field_name] = [] if attribute_line.multi else False + field_name_colour = '%s%s' % ( + wizard.field_prefix, + self.attr_colour.id + ) + + # Define small without colour specified + self.wizard_write_proceed(wizard, [self.attr_val_small]) + new_variant = self.product_temp.product_variant_ids + self.assertTrue(len(new_variant) == 1 and + set(new_variant.attribute_value_ids.ids) == + set([self.attr_val_small.id]), + "Wizard did not accurately create a variant with " + "optional value undefined") + config_variants = self.product_temp.product_variant_ids + + order_line = self.so.order_line.filtered( + lambda l: l.product_id.config_ok + ) + + # Redefine to medium without colour + self.do_reconfigure(order_line, [self.attr_val_med]) + new_variant = self.product_temp.product_variant_ids - config_variants + self.assertTrue(len(new_variant) == 1 and + set(new_variant.attribute_value_ids.ids) == + set([self.attr_val_med.id]), + "Wizard did not accurately reconfigure a variant with " + "optional value undefined") + config_variants = self.product_temp.product_variant_ids + + # Redefine to medium blue + self.do_reconfigure(order_line, [self.attr_val_blue]) + new_variant = self.product_temp.product_variant_ids - config_variants + self.assertTrue(len(new_variant) == 1 and + set(new_variant.attribute_value_ids.ids) == + set([self.attr_val_med.id, self.attr_val_blue.id]), + "Wizard did not accurately reconfigure a variant with " + "to add an optional value") + config_variants = self.product_temp.product_variant_ids + + # Redefine to large - should remove colour, as this is invalid + reconfig_action = order_line.reconfigure_product() + wizard = self.env['product.configurator'].browse( + reconfig_action.get('res_id') + ) + attr_large_dict = self.get_wizard_write_dict(wizard, + [self.attr_val_large]) + attr_blue_dict = self.get_wizard_write_dict(wizard, + [self.attr_val_blue]) + oc_vals = dynamic_fields.copy() + oc_vals.update({'id': wizard.id}) + oc_vals.update(dict(attr_blue_dict, **attr_large_dict)) + oc_result = wizard.onchange( + oc_vals, + attr_large_dict.keys()[0], + {} + ) + self.assertTrue(field_name_colour in oc_result['value'] and + not oc_result['value'][field_name_colour], + "Colour should have been cleared by wizard" + ) + vals = self.get_wizard_write_dict(wizard, [self.attr_val_large], + remove_values=[self.attr_val_blue]) + wizard.write(vals) + wizard.action_next_step() + if wizard.exists(): + while wizard.action_next_step(): + pass + new_variant = self.product_temp.product_variant_ids - config_variants + self.assertTrue(len(new_variant) == 1 and + set(new_variant.attribute_value_ids.ids) == + set([self.attr_val_large.id]), + "Wizard did not accurately reconfigure a variant with " + "to remove invalid attribute") diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index d449635b..a1145c56 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -81,7 +81,7 @@ def get_onchange_domains(self, values, cfg_val_ids): """Generate domains to be returned by onchange method in order to restrict the available values of dynamically inserted fields - :param values: values argument passed to onchance wrapper + :param values: values argument passed to onchange wrapper :cfg_val_ids: current configuration passed as a list of value_ids (usually in the form of db value_ids + interface value_ids) From c0caae3c0819c8514bc2452dd644b2da4179e20a Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:16:01 +1000 Subject: [PATCH 05/10] Pylint errors. --- product_configurator_wizard/tests/test_wizard.py | 5 ++++- product_configurator_wizard/tests/test_wizard_attrs.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/product_configurator_wizard/tests/test_wizard.py b/product_configurator_wizard/tests/test_wizard.py index 86011580..6317d930 100644 --- a/product_configurator_wizard/tests/test_wizard.py +++ b/product_configurator_wizard/tests/test_wizard.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase +from ansible.module_utils.basic import remove_values class ConfigurationRules(TransactionCase): @@ -23,11 +24,13 @@ def get_attr_values(self, attr_val_ext_ids=None): return attr_vals - def get_wizard_write_dict(self, wizard, attr_values, remove_values=[]): + def get_wizard_write_dict(self, wizard, attr_values, remove_values=None): """Turn a series of attribute.value objects to a dictionary meant for writing values to the product.configurator wizard""" write_dict = {} + if remove_values is None: + remove_values = [] multi_attr_ids = wizard.product_tmpl_id.attribute_line_ids.filtered( lambda x: x.multi).mapped('attribute_id').ids diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index 5832fc38..24e1ec97 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -2,7 +2,8 @@ from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError -from test_wizard import ConfigurationRules +from odoo.addons.product_configurtest_wizard.test_wizard \ + import ConfigurationRules class ConfigurationAttributes(ConfigurationRules): From c20b4b1cac08d3ac712d377ffd680ffd21339e05 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:17:03 +1000 Subject: [PATCH 06/10] Typo fix on import --- product_configurator_wizard/tests/test_wizard_attrs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index 24e1ec97..92fcce48 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -2,7 +2,7 @@ from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError -from odoo.addons.product_configurtest_wizard.test_wizard \ +from odoo.addons.product_configurator_wizard.tests.test_wizard \ import ConfigurationRules From 341662ea811a0edf71b445915a1b9a1167dab4b5 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:44:11 +1000 Subject: [PATCH 07/10] Change code from test due to backport. --- product_configurator_wizard/tests/test_wizard.py | 2 +- product_configurator_wizard/tests/test_wizard_attrs.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/product_configurator_wizard/tests/test_wizard.py b/product_configurator_wizard/tests/test_wizard.py index 6317d930..23f0ca47 100644 --- a/product_configurator_wizard/tests/test_wizard.py +++ b/product_configurator_wizard/tests/test_wizard.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase -from ansible.module_utils.basic import remove_values +from odoo.exceptions import ValidationError class ConfigurationRules(TransactionCase): diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index 92fcce48..0fce2802 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -95,7 +95,8 @@ def test_configurations_option_or_not_reqd(self): # Start a new configuration wizard wizard_obj = self.env['product.configurator'].with_context({ 'active_model': 'sale.order', - 'default_order_id': self.so.id + 'active_id': self.so.id + # 'default_order_id': self.so.id }) wizard = wizard_obj.create({'product_tmpl_id': self.product_temp.id}) From 77d1724a47a45d99903c6f407a1837fe886e3281 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:49:28 +1000 Subject: [PATCH 08/10] Fix to visual indent for FLAKE8 --- .../tests/test_wizard_attrs.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index 0fce2802..c1f5277d 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -36,15 +36,15 @@ def setUp(self): domain_small_med = self.env['product.config.domain'].create( {'name': 'Small/Med', 'domain_line_ids': [ - (0, 0, {'attribute_id': self.attr_size.id, - 'condition': 'in', - 'operator': 'and', - 'value_ids': - [(6, 0, - [self.attr_val_small.id, self.attr_val_med.id] - )] - }), - ], + (0, 0, {'attribute_id': self.attr_size.id, + 'condition': 'in', + 'operator': 'and', + 'value_ids': + [(6, 0, + [self.attr_val_small.id, self.attr_val_med.id] + )] + }), + ], } ) self.attr_colour = self.env['product.attribute'].create( @@ -79,7 +79,7 @@ def setUp(self): ], 'required': False, }) - ], + ], } ) colour_line = self.product_temp.attribute_line_ids.filtered( From d44a857230296dbbf5c7a5ea39edf7ca6b9f47cd Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 14:52:58 +1000 Subject: [PATCH 09/10] Flake8 complaining on unused imports --- product_configurator_wizard/tests/test_wizard.py | 1 - product_configurator_wizard/tests/test_wizard_attrs.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/product_configurator_wizard/tests/test_wizard.py b/product_configurator_wizard/tests/test_wizard.py index 23f0ca47..d9ccdb56 100644 --- a/product_configurator_wizard/tests/test_wizard.py +++ b/product_configurator_wizard/tests/test_wizard.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase -from odoo.exceptions import ValidationError class ConfigurationRules(TransactionCase): diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index c1f5277d..603e2f17 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from odoo.tests.common import TransactionCase -from odoo.exceptions import ValidationError from odoo.addons.product_configurator_wizard.tests.test_wizard \ import ConfigurationRules From f9f867044c1dbef79b6487d5e603412bc4088168 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Tue, 9 May 2017 15:15:51 +1000 Subject: [PATCH 10/10] Even more tests. --- .../tests/test_wizard_attrs.py | 19 +++++++++++++++++++ .../wizard/product_configurator.py | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/product_configurator_wizard/tests/test_wizard_attrs.py b/product_configurator_wizard/tests/test_wizard_attrs.py index 603e2f17..4790fdad 100644 --- a/product_configurator_wizard/tests/test_wizard_attrs.py +++ b/product_configurator_wizard/tests/test_wizard_attrs.py @@ -111,6 +111,14 @@ def test_configurations_option_or_not_reqd(self): wizard.field_prefix, self.attr_colour.id ) + invisible_name_colour = '%s%s' % ( + wizard.invisible_field_prefix, + self.attr_colour.id + ) + invisible_name_size = '%s%s' % ( + wizard.invisible_field_prefix, + self.attr_size.id + ) # Define small without colour specified self.wizard_write_proceed(wizard, [self.attr_val_small]) @@ -167,6 +175,17 @@ def test_configurations_option_or_not_reqd(self): not oc_result['value'][field_name_colour], "Colour should have been cleared by wizard" ) + self.assertTrue(invisible_name_colour in oc_result['value'] and + not oc_result['value'][invisible_name_colour], + "Invisible Attribute Colour should have been " + "cleared by wizard" + ) + self.assertTrue(invisible_name_size in oc_result['value'] and + oc_result['value'][invisible_name_size] == + self.attr_val_large.id, + "Invisible Attribute Size should have been set " + "by wizard" + ) vals = self.get_wizard_write_dict(wizard, [self.attr_val_large], remove_values=[self.attr_val_blue]) wizard.write(vals) diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index a1145c56..80d57e20 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -206,6 +206,12 @@ def onchange(self, values, field_name, field_onchange): vals[k.replace(self.field_prefix, self.invisible_field_prefix, 1) ] = v + # likewise, any returned values will be co-stored + for k, v in vals.items(): + if k.startswith(self.field_prefix): + vals[k.replace(self.field_prefix, + self.invisible_field_prefix, 1) + ] = v return {'value': vals, 'domain': domains} attribute_line_ids = fields.One2many(