Skip to content
This repository has been archived by the owner on Apr 28, 2022. It is now read-only.

[ENH] - Default Values based on domains. #87

Open
wants to merge 9 commits into
base: 10.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions product_configurator/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'demo/product_attribute.xml',
'demo/product_config_domain.xml',
'demo/product_config_lines.xml',
'demo/product_config_defaults.xml',
'demo/product_config_step.xml',
'demo/config_image_ids.xml',
],
Expand Down
20 changes: 20 additions & 0 deletions product_configurator/demo/product_config_defaults.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_config_default_gasoline_engines" model="product.config.default">
<field name="product_tmpl_id" ref="bmw_2_series"/>
<field name="value_ids" eval="[(6, 0, [
ref('product_attribute_value_218i'),
ref('product_attribute_value_red'),
])]"/>
<field name="domain_id" ref="product_config_domain_gasoline"/>
</record>

<record id="product_config_default_diesel_engines" model="product.config.default">
<field name="product_tmpl_id" ref="bmw_2_series"/>
<field name="value_ids" eval="[(6, 0, [
ref('product_attribute_value_218d'),
ref('product_attribute_value_black'),
])]"/>
<field name="domain_id" ref="product_config_domain_diesel"/>
</record>
</odoo>
44 changes: 44 additions & 0 deletions product_configurator/models/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class ProductTemplate(models.Model):
string="Attribute Dependencies"
)

config_default_ids = fields.One2many(
comodel_name='product.config.default',
inverse_name='product_tmpl_id',
string="Attribute Defaults"
)

config_image_ids = fields.One2many(
comodel_name='product.config.image',
inverse_name='product_tmpl_id',
Expand Down Expand Up @@ -439,6 +445,44 @@ def values_available(self, attr_val_ids, sel_val_ids):

return avail_val_ids

@api.multi
def find_default_value(self, selectable_value_ids, value_ids):
"""Based on the current values, which of the available template value ids
is the best default value to use.

:param selectable_value_ids: list of product.attribute.value
object already trimmed down as selectable, for one
attribute line.
:param value_ids: list of attribute value ids already chosen

:returns: The first matched default id

"""
self.ensure_one()

if not selectable_value_ids:
return False
# assume all values are from the same attribute line - they should be!
default_lines = self.config_default_ids.filtered(
lambda l: set(l.value_ids.ids) & set(selectable_value_ids)
)

for default_line in default_lines:
if not default_line.domain_id:
# No domain - always considered true. Use this.
break
domains = default_line.mapped('domain_id').compute_domain()
if self.validate_domains_against_sels(domains, value_ids):
# Domain OK, use this
break
else:
# parsed all lines without a match
return False
# pick one at random...
return (
set(default_line.value_ids.ids) & set(selectable_value_ids)
).pop()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work for multi, the client expects a list. For instance, adding options:armrest will cause a js error in AbstractManyField.set_value()


@api.multi
def validate_configuration(self, value_ids, custom_vals=None, final=True):
""" Verifies if the configuration values passed via value_ids and custom_vals
Expand Down
53 changes: 53 additions & 0 deletions product_configurator/models/product_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,59 @@ def check_value_attributes(self):
)


class ProductConfigDefault(models.Model):
_name = 'product.config.default'

product_tmpl_id = fields.Many2one(
comodel_name='product.template',
string='Product Template',
ondelete='cascade',
required=True
)

# TODO: Find a more elegant way to restrict the value_ids
attr_line_val_ids = fields.Many2many(
comodel_name='product.attribute.value',
compute='_compute_attr_vals'
)

value_ids = fields.Many2many(
comodel_name='product.attribute.value',
id1="cfg_dflt_id",
id2="attr_val_id",
string="Values"
)

domain_id = fields.Many2one(
comodel_name='product.config.domain',
string='Applied If',
help='Default will be attempted if this rule passes, or leave blank '
'for a generic default'
)

sequence = fields.Integer(string='Sequence', default=10)

_order = 'product_tmpl_id, sequence, id'

@api.multi
def _compute_attr_vals(self):
for config_default in self:
config_default.attr_line_val_ids = \
config_default.product_tmpl_id.attribute_line_ids.mapped(
'value_ids'
)

@api.model
def default_get(self, fields):
result = super(ProductConfigDefault, self).default_get(fields)
if self.env.context.get('for_template_id'):
result['attr_line_val_ids'] = \
self.env['product.template'].browse(
self.env.context['for_template_id']
).attribute_line_ids.mapped('value_ids').ids
return result


class ProductConfigImage(models.Model):
_name = 'product.config.image'

Expand Down
3 changes: 3 additions & 0 deletions product_configurator/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
product_configurator_config_line,Config Line,model_product_config_line,group_product_configurator,1,1,1,1
product_configurator_config_default,Config Default,model_product_config_default,group_product_configurator,1,1,1,1
product_configurator_config_image,Config Image,model_product_config_image,group_product_configurator,1,1,1,1
product_configurator_config_step,Config Step,model_product_config_step,group_product_configurator,1,1,1,1
product_configurator_config_step_line,Config Step Line,model_product_config_step_line,group_product_configurator,1,1,1,1
Expand All @@ -10,6 +11,7 @@ product_configurator_config_session,Config Session,model_product_config_session,
product_configurator_config_session_custom_value,Config Session Custom Value,model_product_config_session_custom_value,group_product_configurator,1,1,1,1
,,,,,,,
user_config_line,User Config Line,model_product_config_line,base.group_user,1,0,0,0
user_config_default,User Config Default,model_product_config_default,base.group_user,1,0,0,0
user_config_image,User Config Image,model_product_config_image,base.group_user,1,0,0,0
user_config_step,User Config Step,model_product_config_step,base.group_user,1,0,0,0
user_config_step_line,User Config Step Line,model_product_config_step_line,base.group_user,1,0,0,0
Expand All @@ -25,6 +27,7 @@ portal_config_step,Portal Config Step,model_product_config_step,base.group_porta
portal_config_session,Portal Config Session,model_product_config_session,base.group_portal,1,0,0,0
portal_config_session_custom_value,Portal Config Session Custom Value,model_product_config_session_custom_value,base.group_portal,1,0,0,0
portal_configurator_config_line,Portal Config Line,model_product_config_line,base.group_portal,1,0,0,0
portal_configurator_config_default,Portal Config Default,model_product_config_default,base.group_portal,1,0,0,0
portal_configurator_config_step_line,Portal Config Step Line,model_product_config_step_line,base.group_portal,1,0,0,0
portal_configurator_config_domain,Portal Config Domain,model_product_config_domain,base.group_portal,1,0,0,0
portal_configurator_config_domain_line,Portal Config Domain Line,model_product_config_domain_line,base.group_portal,1,0,0,0
39 changes: 38 additions & 1 deletion product_configurator/tests/test_configuration_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,41 @@ def test_invalid_custom_value_configuration(self):
self.assertFalse(validation, "Custom value accepted for fixed "
"attribute color")

# TODO: Test configuration with disallowed custom type value
def test_configuration_defaults(self):
conf = ['gasoline', 'tapistry_black']
engine_selections = self.env.ref(
'product_configurator.product_config_line_gasoline_engines'
)
attr_val_ids = self.get_attr_val_ids(conf)
default_value_engine = self.cfg_tmpl.find_default_value(
engine_selections.value_ids.ids,
attr_val_ids,
)
self.assertEqual(
[default_value_engine], self.get_attr_val_ids(['218i']),
"Gasoline Engine default not set correctly"
)

color_selection_ids = self.get_attr_val_ids(['red', 'silver', 'black'])
attr_val_ids = self.get_attr_val_ids(conf)
default_value_color = self.cfg_tmpl.find_default_value(
color_selection_ids,
attr_val_ids,
)
self.assertEqual(
[default_value_color], self.get_attr_val_ids(['red']),
"Gasoline Color default not set correctly"
)

color_selection_ids = self.get_attr_val_ids(['silver', 'black'])
attr_val_ids = self.get_attr_val_ids(conf)
default_value_color = self.cfg_tmpl.find_default_value(
color_selection_ids,
attr_val_ids,
)
self.assertFalse(
default_value_color,
"Gasoline Color should not have been returned unselectable value"
)

# Test configuration with disallowed custom type value
18 changes: 18 additions & 0 deletions product_configurator/views/product_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@
<field name="domain_id"/>
</tree>
</field>
<separator colspan="4" string="Configuration Defaults"/>
<field name="config_default_ids"
attrs="{'readonly': [('attribute_line_ids','=',[])]}"
context="{'show_attribute': True, 'for_template_id': id}">
<tree string="Attribute Value Defaults" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="domain_id"/>
<!-- # TODO: Find a more elegant way to restrict the value_ids -->
<field name="attr_line_val_ids"
widget="many2many_tags"
invisible="True"/>
<field name="value_ids"
widget="many2many_tags"
domain="[('id','in',attr_line_val_ids[0][2])]"
options="{'no_create': True, 'no_create_edit': True}"
context="{'show_attribute': True}"/>
</tree>
</field>
<separator colspan="4" string="Configuration Steps"/>
<field name="config_step_line_ids"
attrs="{'readonly': [('attribute_line_ids','=',[])]}">
Expand Down
48 changes: 48 additions & 0 deletions product_configurator_wizard/tests/test_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,51 @@ def test_reconfiguration(self):

self.assertTrue(len(config_variants) == 2,
"Wizard reconfiguration did not create a new variant")

def test_wizard_domains(self):
"""Test product configurator wizard default values"""

# Start a new configuration wizard
wizard = self.env['product.configurator'].create({
'product_tmpl_id': self.cfg_tmpl.id
})

dynamic_fields = {}
for attribute_line in self.cfg_tmpl.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

attr_gasoline_vals = self.get_attr_values(['gasoline'])
attr_gasoline_dict = self.get_wizard_write_dict(wizard,
attr_gasoline_vals)
attr_218i_vals = self.get_attr_values(['218i'])
attr_218i_dict = self.get_wizard_write_dict(wizard, attr_218i_vals)
gasoline_engine_vals = self.env.ref(
'product_configurator.product_config_line_gasoline_engines'
).value_ids

oc_vals = dynamic_fields.copy()
oc_vals.update({'id': wizard.id,
})
oc_vals.update(attr_gasoline_dict)
oc_result = wizard.onchange(
oc_vals,
attr_gasoline_dict.keys()[0],
{}
)
k, v = attr_218i_dict.iteritems().next()
self.assertEqual(
oc_result.get('value', {}).get(k),
v,
"Engine default value not set correctly by onchange wizard"
)
oc_domain = oc_result.get('domain', {}).get(k, [])
domain_ids = oc_domain and oc_domain[0][2] or []
self.assertEqual(
set(domain_ids),
set(gasoline_engine_vals.ids),
"Engine domain value not set correctly by onchange wizard"
)
Loading