Add Function fields on Product from Template
issue5087 review23611002
This commit is contained in:
1 parent
6fe65f9fe2
commit
0f385fc72e
7 files changed
+138
-69
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Add Function fields on Product from Template
|
||||
|
||||
Version 3.8.0 - 2015-11-02
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Be more strict on unit of measure conversion
|
||||
|
||||
+85
-54
@@ -1,14 +1,17 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
import copy
|
||||
|
||||
from sql import Column
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, Model, fields
|
||||
from trytond.pyson import Eval
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond import backend
|
||||
from trytond.const import OPERATORS
|
||||
from trytond.config import config
|
||||
|
||||
__all__ = ['Template', 'Product', 'price_digits']
|
||||
__all__ = ['Template', 'Product', 'price_digits', 'TemplateFunction']
|
||||
|
||||
STATES = {
|
||||
'readonly': ~Eval('active', True),
|
||||
@@ -129,6 +132,36 @@ class Template(ModelSQL, ModelView):
|
||||
yield record, rec_name, icon
|
||||
|
||||
|
||||
class TemplateFunction(fields.Function):
|
||||
|
||||
def __init__(self, field):
|
||||
super(TemplateFunction, self).__init__(
|
||||
field, 'get_template', searcher='search_template')
|
||||
|
||||
def __copy__(self):
|
||||
return TemplateFunction(copy.copy(self._field))
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return TemplateFunction(copy.deepcopy(self._field, memo))
|
||||
|
||||
@staticmethod
|
||||
def order(name):
|
||||
@classmethod
|
||||
def order(cls, tables):
|
||||
pool = Pool()
|
||||
Template = pool.get('product.template')
|
||||
product, _ = tables[None]
|
||||
if 'template' not in tables:
|
||||
template = Template.__table__()
|
||||
tables['template'] = {
|
||||
None: (template, product.template == template.id),
|
||||
}
|
||||
else:
|
||||
template = tables['template']
|
||||
return [Column(template, name)]
|
||||
return order
|
||||
|
||||
|
||||
class Product(ModelSQL, ModelView):
|
||||
"Product Variant"
|
||||
__name__ = "product.product"
|
||||
@@ -141,15 +174,60 @@ class Product(ModelSQL, ModelView):
|
||||
description = fields.Text("Description", translate=True, states=STATES,
|
||||
depends=DEPENDS)
|
||||
active = fields.Boolean('Active', select=True)
|
||||
default_uom = fields.Function(fields.Many2One('product.uom',
|
||||
'Default UOM'), 'get_default_uom', searcher='search_default_uom')
|
||||
type = fields.Function(fields.Selection(TYPES, 'Type'),
|
||||
'on_change_with_type', searcher='search_type')
|
||||
list_price_uom = fields.Function(fields.Numeric('List Price',
|
||||
digits=price_digits), 'get_price_uom')
|
||||
cost_price_uom = fields.Function(fields.Numeric('Cost Price',
|
||||
digits=price_digits), 'get_price_uom')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
pool = Pool()
|
||||
Template = pool.get('product.template')
|
||||
|
||||
if not hasattr(cls, '_no_template_field'):
|
||||
cls._no_template_field = set()
|
||||
cls._no_template_field.update(['products'])
|
||||
|
||||
super(Product, cls).__setup__()
|
||||
|
||||
for attr in dir(Template):
|
||||
tfield = getattr(Template, attr)
|
||||
if not isinstance(tfield, fields.Field):
|
||||
continue
|
||||
if attr in cls._no_template_field:
|
||||
continue
|
||||
field = getattr(cls, attr, None)
|
||||
if not field or isinstance(field, TemplateFunction):
|
||||
setattr(cls, attr, TemplateFunction(copy.deepcopy(tfield)))
|
||||
order_method = getattr(cls, 'order_%s' % attr, None)
|
||||
if not order_method:
|
||||
order_method = TemplateFunction.order(attr)
|
||||
setattr(cls, 'order_%s' % attr, order_method)
|
||||
|
||||
@fields.depends('template')
|
||||
def on_change_template(self):
|
||||
for name, field in self._fields.iteritems():
|
||||
if isinstance(field, TemplateFunction):
|
||||
if self.template:
|
||||
value = getattr(self.template, name)
|
||||
else:
|
||||
value = None
|
||||
setattr(self, name, value)
|
||||
|
||||
def get_template(self, name):
|
||||
value = getattr(self.template, name)
|
||||
if isinstance(value, Model):
|
||||
return value.id
|
||||
elif (isinstance(value, (list, tuple))
|
||||
and value and isinstance(value[0], Model)):
|
||||
return [r.id for r in value]
|
||||
else:
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def search_template(cls, name, clause):
|
||||
return [('template.%s' % name,) + tuple(clause[1:])]
|
||||
|
||||
@classmethod
|
||||
def order_rec_name(cls, tables):
|
||||
pool = Pool()
|
||||
@@ -169,13 +247,6 @@ class Product(ModelSQL, ModelView):
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return super(Product, self).__getattr__(name)
|
||||
except AttributeError:
|
||||
pass
|
||||
return getattr(self.template, name)
|
||||
|
||||
def get_rec_name(self, name):
|
||||
if self.code:
|
||||
return '[' + self.code + '] ' + self.name
|
||||
@@ -193,22 +264,6 @@ class Product(ModelSQL, ModelView):
|
||||
('template.name',) + tuple(clause[1:]),
|
||||
]
|
||||
|
||||
def get_default_uom(self, name):
|
||||
return self.template.default_uom.id
|
||||
|
||||
@classmethod
|
||||
def search_default_uom(cls, name, clause):
|
||||
return [('template.default_uom',) + tuple(clause[1:])]
|
||||
|
||||
@fields.depends('template')
|
||||
def on_change_with_type(self, name=None):
|
||||
if self.template:
|
||||
return self.template.type
|
||||
|
||||
@classmethod
|
||||
def search_type(cls, name, clause):
|
||||
return [('template.type',) + tuple(clause[1:])]
|
||||
|
||||
@staticmethod
|
||||
def get_price_uom(products, name):
|
||||
Uom = Pool().get('product.uom')
|
||||
@@ -229,27 +284,3 @@ class Product(ModelSQL, ModelView):
|
||||
for id_, rec_name, icon in super(Product, cls).search_global(text):
|
||||
icon = icon or 'tryton-product'
|
||||
yield id_, rec_name, icon
|
||||
|
||||
@classmethod
|
||||
def search_domain(cls, domain, active_test=True, tables=None):
|
||||
def is_leaf(expression):
|
||||
return (isinstance(expression, (list, tuple))
|
||||
and len(expression) > 2
|
||||
and isinstance(expression[1], basestring)
|
||||
and expression[1] in OPERATORS) # TODO remove OPERATORS test
|
||||
|
||||
def convert_domain(domain):
|
||||
'Replace missing product field by the template one'
|
||||
if is_leaf(domain):
|
||||
field = domain[0].split('.', 1)[0]
|
||||
if not getattr(cls, field, None):
|
||||
field = 'template.' + domain[0]
|
||||
return (field,) + tuple(domain[1:])
|
||||
else:
|
||||
return domain
|
||||
elif domain in ['OR', 'AND']:
|
||||
return domain
|
||||
else:
|
||||
return [convert_domain(d) for d in domain]
|
||||
return super(Product, cls).search_domain(convert_domain(domain),
|
||||
active_test=active_test, tables=tables)
|
||||
+20
-4
@@ -79,19 +79,35 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_view_tree">
|
||||
<field name="model">product.product</field>
|
||||
<field name="type" eval="None"/>
|
||||
<field name="inherit" ref="template_view_tree"/>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name"></field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_view_tree_simple">
|
||||
<field name="model">product.product</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="inherit" eval="None"/>
|
||||
<field name="name">product_tree</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">product_tree_simple</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_view_form">
|
||||
<field name="model">product.product</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit" eval="None"/>
|
||||
<field name="type" eval="None"/>
|
||||
<field name="inherit" ref="template_view_form"/>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">product_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_view_form_simple">
|
||||
<field name="model">product.product</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">product_form_simple</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_product_form">
|
||||
<field name="name">Variants</field>
|
||||
<field name="res_model">product.product</field>
|
||||
|
||||
+15
-10
@@ -1,13 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form string="Product" col="6">
|
||||
<label name="template"/>
|
||||
<field name="template"/>
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="active"/>
|
||||
<field name="active" xexpand="0" width="100"/>
|
||||
<separator name="description" colspan="6"/>
|
||||
<field name="description" colspan="6"/>
|
||||
</form>
|
||||
<data>
|
||||
<xpath expr="/form/field[@name='name']" position="after">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/notebook/page[@id='general']/label[@name='type']"
|
||||
position="before">
|
||||
<label name="template"/>
|
||||
<field name="template" colspan="3"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/notebook/page/field[@name='products']" position="replace">
|
||||
<separator name="description" colspan="6"/>
|
||||
<field name="description" colspan="6"/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form string="Product" col="6">
|
||||
<label name="template"/>
|
||||
<field name="template"/>
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="active"/>
|
||||
<field name="active" xexpand="0" width="100"/>
|
||||
<separator name="description" colspan="6"/>
|
||||
<field name="description" colspan="6"/>
|
||||
</form>
|
||||
File renamed without changes.
@@ -6,6 +6,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name" xexpand="1"/>
|
||||
<label name="active"/>
|
||||
<field name="active" xexpand="0" width="100"/>
|
||||
<newline/>
|
||||
<notebook colspan="6">
|
||||
<page string="General" id="general">
|
||||
<label name="type"/>
|
||||
@@ -23,7 +24,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="cost_price"/>
|
||||
<label name="cost_price_method"/>
|
||||
<field name="cost_price_method"/>
|
||||
<field name="products" mode="form,tree" colspan="4"/>
|
||||
<field name="products" mode="form,tree" colspan="4"
|
||||
view_ids="product.product_view_form_simple"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
Reference in new issue
Block a user