← Back to team overview

openerp-community team mailing list archive

lp:~akretion-team/openerp-product-attributes/polymorphic-relations into lp:openerp-product-attributes

 

Raphaël Valyi - http://www.akretion.com has proposed merging lp:~akretion-team/openerp-product-attributes/polymorphic-relations into lp:openerp-product-attributes.

Requested reviews:
  Product Core Editors (product-core-editors)

For more details, see:
https://code.launchpad.net/~akretion-team/openerp-product-attributes/polymorphic-relations/+merge/150725

Hello,

This is the round 2 of my proposed generalization of product_custom_attributes for OpenERP v7 and based on my experience building 2 product configurators for OpenERP.

So it does what the commits says:

[IMP][product_custom_attributes] generalization of the m2o and m2m custom options: you can still define a selection of attribute.option (names) BUT now you can also use the relation_model_id and the 'Change Options' button to define a selection of existing references to ANY kind of OpenERP record you want! You can also simply define a domain of valid records of a given model if you prefer. This will make this module a powerful reusable component for OpenERP configurators such as a product configurator.

[IMP][product_custom_attributes] when an attribute is relational and has a specific relation_model_id defined, then it's now created with the according ttype.relation. Further, attribute edition now has its domain properly tuned if it's such a relational field. Notice that the old behavior is totally preserved.

Notice that if that is accepted, I plan a very simple round 3 that will just consist in extracting a part of that module into a lower level custom_attributes mixin module that could be injected in any OpenERP object, not just products.

I also plan a little demo to show the power of this change. I know that it's not a trivial merge. However, I insist that the former behavior is largely preserved (if not totally) while accepting such change could make this module used in many more situations by much more people. Or said differently: it's makes it a little more complex but then more people will be maintaining it and IMHO this is totally worth the few additional lines of this merge.

Be the LGTM Gods with me!
-- 
https://code.launchpad.net/~akretion-team/openerp-product-attributes/polymorphic-relations/+merge/150725
Your team OpenERP Community is subscribed to branch lp:openerp-product-attributes.
=== modified file 'product_custom_attributes/product.py'
--- product_custom_attributes/product.py	2013-02-15 04:36:45 +0000
+++ product_custom_attributes/product.py	2013-02-27 05:28:18 +0000
@@ -95,11 +95,19 @@
         kwargs = {'name': "%s" % attribute.name}
         if attribute.ttype == 'many2many':
             parent = etree.SubElement(page, 'group', colspan="2", col="4")
+            #FIXME the following isn't displayed in v7:
             sep = etree.SubElement(parent, 'separator',
                                     string="%s" % attribute.field_description, colspan="4")
             kwargs['nolabel'] = "1"
         if attribute.ttype in ['many2one', 'many2many']:
-            kwargs['domain'] = "[('attribute_id', '=', %s)]" % attribute.attribute_id.id
+            if attribute.relation_model_id:
+                if attribute.domain:
+                    kwargs['domain'] = attribute.domain
+                else:
+                    ids = [op.value_ref.id for op in attribute.option_ids]
+                    kwargs['domain'] = "[('id', 'in', %s)]" % ids
+            else:
+                kwargs['domain'] = "[('attribute_id', '=', %s)]" % attribute.attribute_id.id
         field = etree.SubElement(parent, 'field', **kwargs)
         return parent
 

=== modified file 'product_custom_attributes/product_attribute.py'
--- product_custom_attributes/product_attribute.py	2013-02-06 14:37:21 +0000
+++ product_custom_attributes/product_attribute.py	2013-02-27 05:28:18 +0000
@@ -20,27 +20,109 @@
 ###############################################################################
 
 from openerp.osv.orm import Model
+from openerp.osv import osv
 from openerp.osv import fields
 from openerp.osv.osv import except_osv
+from lxml import etree
 from openerp.tools.translate import _
 from unidecode import unidecode # Debian package python-unidecode
 
+
 class attribute_option(Model):
     _name = "attribute.option"
     _description = "Attribute Option"
     _order="sequence"
 
+    _rec_name = 'value_ref' #FIXME add validation constraint to enforce model homogeneity
+
     _columns = {
         'name': fields.char('Name', size=128, translate=True, required=True),
+        'value_ref': fields.reference('Reference', selection=[], size=128),
         'attribute_id': fields.many2one('product.attribute', 'Product Attribute', required=True),
         'sequence': fields.integer('Sequence'),
     }
 
+    def name_change(self, cr, uid, ids, name, relation_model_id, context=None):
+        if relation_model_id:
+            warning = {'title': _('Error!'), 'message': _("Use the 'Change Options' button instead to select appropriate model references'")}
+            return {"value": {"name": False}, "warning": warning}
+        else:
+            return True
+
+class attribute_option_wizard(osv.osv_memory):
+    _name = "attribute.option.wizard"
+    _rec_name = 'attribute_id'
+
+    _columns = {
+        'attribute_id': fields.many2one('product.attribute', 'Product Attribute', required=True),
+    }
+
+    _defaults = {
+        'attribute_id': lambda self, cr, uid, context: context.get('attribute_id', False)
+    }
+
+    def validate(self, cr, uid, ids, context=None):
+        return True
+
+    def create(self, cr, uid, vals, context=None):
+        attr_obj = self.pool.get("product.attribute")
+        attr = attr_obj.browse(cr, uid, vals['attribute_id'])
+        op_ids = [op.id for op in attr.option_ids]
+        opt_obj = self.pool.get("attribute.option")
+        opt_obj.unlink(cr, uid, op_ids)
+        for op_id in (vals.get("option_ids") and vals['option_ids'][0][2] or []):
+            model = attr.relation_model_id.model
+            name = self.pool.get(model).name_get(cr, uid, [op_id], context)[0][1]
+            opt_obj.create(cr, uid, {
+                'attribute_id': vals['attribute_id'],
+                'name': name,
+                'value_ref': "%s,%s" % (attr.relation_model_id.model, op_id)
+            })
+        res = super(attribute_option_wizard, self).create(cr, uid, vals, context)
+        return res
+
+    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
+        res = super(attribute_option_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
+        if context and context.get("attribute_id"):
+            attr_obj = self.pool.get("product.attribute")
+            model_id = attr_obj.read(cr, uid, [context.get("attribute_id")], ['relation_model_id'])[0]['relation_model_id'][0]
+            relation = self.pool.get("ir.model").read(cr, uid, [model_id], ["model"])[0]["model"]
+            res['fields'].update({'option_ids': {
+                            'domain': [],
+                            'string': "Options",
+                            'type': 'many2many',
+                            'relation': relation,
+                            'required': True,
+                            }
+                        })
+            eview = etree.fromstring(res['arch'])
+            options = etree.Element('field', name='option_ids', colspan='6')
+            placeholder = eview.xpath("//separator[@string='options_placeholder']")[0]
+            placeholder.getparent().replace(placeholder, options)
+            res['arch'] = etree.tostring(eview, pretty_print=True)
+        return res
+
 
 class product_attribute(Model):
     _name = "product.attribute"
     _description = "Product Attribute"
     _inherits = {'ir.model.fields': 'field_id'}
+
+    def relation_model_id_change(self, cr, uid, ids, relation_model_id, option_ids, context=None):
+        "removed selected options as they would be inconsistent" 
+        return {'value': {'option_ids': [(2, i[1]) for i in option_ids]}}
+
+    def button_add_options(self, cr, uid, ids, context=None):
+        return {
+            'context': "{'attribute_id': %s}" % (ids[0]),
+            'name': _('Options Wizard'),
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'attribute.option.wizard',
+            'type': 'ir.actions.act_window',
+            'target': 'new',
+        }
+
     _columns = {
         'field_id': fields.many2one('ir.model.fields', 'Ir Model Fields', required=True, ondelete="cascade"),
         'attribute_type': fields.selection([('char','Char'),
@@ -62,9 +144,15 @@
                                      'Based on', required=True),
         'option_ids': fields.one2many('attribute.option', 'attribute_id', 'Attribute Options'),
         'create_date': fields.datetime('Created date', readonly=True),
+        'relation_model_id': fields.many2one('ir.model', 'Model'),
+        'domain': fields.char('Domain', size=256),
         }
 
     def create(self, cr, uid, vals, context=None):
+        if vals.get('relation_model_id'):
+            relation = self.pool.get('ir.model').read(cr, uid, [vals.get('relation_model_id')], ['model'])[0]['model']
+        else:
+            relation = 'attribute.option'
         if vals.get('based_on') == 'product_template':
             vals['model_id'] = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'product.template')], context=context)[0]
             serial_name = 'attribute_custom_tmpl'
@@ -75,10 +163,10 @@
             vals['serialization_field_id'] = self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', serial_name)], context=context)[0]
         if vals['attribute_type'] == 'select':
             vals['ttype'] = 'many2one'
-            vals['relation'] = 'attribute.option'
+            vals['relation'] = relation
         elif vals['attribute_type'] == 'multiselect':
             vals['ttype'] = 'many2many'
-            vals['relation'] = 'attribute.option'
+            vals['relation'] = relation
             if not vals.get('serialized'):
                 raise except_osv(_('Create Error'), _("The field serialized should be ticked for a multiselect field !"))
         else:

=== modified file 'product_custom_attributes/product_attribute_view.xml'
--- product_custom_attributes/product_attribute_view.xml	2013-02-11 22:58:10 +0000
+++ product_custom_attributes/product_attribute_view.xml	2013-02-27 05:28:18 +0000
@@ -88,23 +88,43 @@
                     <field name="size" attrs="{'invisible':[('attribute_type', '!=', 'char')]}"/>
                     <field name="translate" attrs="{'invisible':[('attribute_type', 'not in', ('char', 'text'))]}"/>
                     <newline />
-                    <field name="option_ids" colspan="4" attrs="{'invisible':[('attribute_type', 'not in', ['select', 'multiselect'])]}" widget="one2many_list" nolabel="1">
-                        <tree string="Attribute Options" editable="top" >
-                            <field name="sequence" />
-                            <field name="name" />
+                    <group colspan="4" attrs="{'invisible':[('attribute_type', 'not in', ['select', 'multiselect'])]}">
+                    <field name="relation_model_id" on_change="relation_model_id_change(relation_model_id, option_ids, context)"/>
+                    <field name="domain" attrs="{'invisible':[('relation_model_id', '=', False)]}"/>
+                    <group colspan="4" attrs="{'invisible':[('domain', '!=', False)]}">
+                    <button name="button_add_options" attrs="{'invisible':[('relation_model_id', '=', False)]}" type="object" string="Change Options"/>
+                    <field name="option_ids" colspan="4" nolabel="1">
+                        <tree string="Attribute Options" editable="top">
+                            <field name="sequence" invisible="1"/>
+                            <field name="name" on_change="name_change(name, parent.relation_model_id, context)"/>
                         </tree>
                     </field>
+                    </group>
+                    </group>
                     <field name="create_date" invisible="1"/>
                 </form>
             </field>
         </record>
 
+        <record id="attribute_option_wizard_form_view" model="ir.ui.view">
+            <field name="name">attribute.option.wizard</field>
+            <field name="model">attribute.option.wizard</field>
+            <field name="arch" type="xml">
+                <form string="Options Wizard" col="6">
+                    <field name="attribute_id" invisible="1" colspan="2"/>
+                    <separator string="options_placeholder"/>
+                    <button special="cancel" string="Cancel" icon="gtk-cancel"/>
+                    <button name="validate" string="Validate" type="object" icon="gtk-convert"/>
+                </form>
+            </field>
+        </record>
+
         <record id="attribute_option_form_view" model="ir.ui.view">
             <field name="name">attribute.option.form</field>
             <field name="model">attribute.option</field>
             <field name="arch" type="xml">
                 <form string="Attribute Option" col="6">
-                    <field name="name" colspan="2"/>
+                    <field name="value_ref" colspan="2"/>
                     <field name="sequence" colspan="2"/>
                     <field name="attribute_id" colspan="2"/>
                 </form>
@@ -163,7 +183,7 @@
             <field eval="1" name="priority"/>
             <field name="arch" type="xml">
                 <tree string="Attribute Option">
-                    <field name="name" />
+                    <field name="value_ref" />
                 </tree>
             </field>
         </record>
@@ -175,7 +195,7 @@
             <field name="arch" type="xml">
                 <tree string="Attribute Option">
                     <field name="sequence" />
-                    <field name="name" />
+                    <field name="value_ref" />
                     <field name="attribute_id" />
                 </tree>
             </field>
@@ -229,7 +249,7 @@
             <field name="model">attribute.option</field>
             <field name="arch" type="xml">
                 <search string="Search Attribute Options">
-                    <field name="name" />
+                    <field name="value_ref" />
                     <field name="attribute_id"/>
                </search>
             </field>


Follow ups