I've extended the 'account.analytic.account' model with a One2many field that references a second model with a One2many field.
When I try to iterate through the second One2many field from the compute method it only lists records that have just been added. Previous records (which are visible on the interface) are not visible in code using the 'self' context until the parent record is saved.
example:
for line in self.One2manyField:
#only gets here for records I add during current session, or all records if parent is saved
#how can I see previously saved records?
Here is the code:
1.) extended 'account.analytic.account' model
class account_analytic_account(models.Model):
_inherit = ['account.analytic.account']
service_location_ids = fields.One2many(comodel_name='contract.service.location', inverse_name='contract_id', copy=True)
2.) First referenced One2many model:
class Contract_Service_Location(models.Model):
_name = 'contract.service.location'
_description = 'Service Location Record'
#problem is here!
#compute method for subtotal field
@api.one
@api.depends('recurring_line_ids','recurring_line_ids.price_subtotal')
def _compute_subtotal(self):
total = 0.0
#I tried to get previously saved ids, but returns nothing, until parent record is saved
old_ids = self.env['contract.recurring.line'].search([('service_location_id', '=', self.id)])
#this only works for new entries during same session, or until parent record is saved. Why?
for line in self.recurring_line_ids:
total = total + line.price_subtotal
#set field
self.price_subtotal = total
contract_id = fields.Many2one(comodel_name='account.analytic.account')
fiscal_position = fields.Many2one(comodel_name='account.fiscal.position', string='Default Taxes')
partner_id = fields.Many2one(comodel_name='res.partner', string='Service Location', help='Optional seperate billing address from customer AND service locations',required=True)
sequence = fields.Integer(string='Sequence', help="Gives the sequence order when displaying a list of sales order lines.")
price_subtotal = fields.Float(compute='_compute_subtotal', string='Subtotal', digits_compute= dp.get_precision('Account'), readonly=True, store=True)
pricelist_id = fields.Many2one(comodel_name='product.pricelist', string='Pricelist', required=True, help="Pricelist for current customer.", default=_get_default_pricelist)
recurring_line_ids = fields.One2many(comodel_name='contract.recurring.line', inverse_name='service_location_id', copy=True)
3.) Second referenced One2many model:
class Contract_Recurring_Line(models.Model):
_name = 'contract.recurring.line'
_description = 'Recurring Service Location Line'
@api.one
@api.depends('price_unit', 'discount', 'product_uom_qty','product_uos_qty',
'product_id', 'service_location_id.partner_id','service_location_id.pricelist_id')
def _compute_subtotal(self):
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
taxes = self.tax_id.compute_all(price, self.product_uom_qty, product=self.product_id, partner=self.service_location_id.partner_id)
self.price_subtotal = taxes['total']
if self.service_location_id:
self.price_subtotal = self.service_location_id.pricelist_id.currency_id.round(self.price_subtotal)
service_location_id = fields.Many2one(comodel_name='contract.service.location', required=True, ondelete='cascade', select=True)
name = fields.Text('Description', required=True)
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict')
price_unit = fields.Float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price'))
price_subtotal = fields.Float(compute='_compute_subtotal', string='Subtotal',store=True, readonly=True, digits_compute= dp.get_precision('Account'))
product_uom_qty = fields.Float('Quantity', default=float(1), digits_compute= dp.get_precision('Product UoS'))
discount = fields.Float('Discount (%)', digits_compute= dp.get_precision('Discount'))
Sadly OpenERP/Odoo only supports one level of Relationship in On-change methods, compute methods, and record modification tracking.
So out of the box, Parent/Child setups are allowed (as in FORMVIEW.one2manyLIST) but not Grandparent/Parent/Child (as in FORMVIEW.one2manyLIST.one2manyLIST).
An example of this would be:
Changes made to Model A will not save records of Model C, and on-change/compute methods on Model A cannot use fields on Model C
So, changes are lost and the problem above is experienced if changes are made on the second nested one2many field, or even if you attempt to read the nested one2many field.
I have created a solution that adds another level of tracking to the onchange/compute/write. You will need to modify the core of Odoo. I hope this changes in the future as Odoo SA has this listed on github as "wishlist".
Here is the code for Odoo 8.0. YMMV with other versions.
FIELDS.PY/_RelationalMulti Class. Replace the following method:
def convert_to_write(self, value, target=None, fnames=None):
# remove/delete former records
if target is None:
set_ids = []
result = [(6, 0, set_ids)]
add_existing = lambda id: set_ids.append(id)
else:
tag = 2 if self.type == 'one2many' else 3
result = [(tag, record.id) for record in target[self.name] - value]
add_existing = lambda id: result.append((4, id))
if fnames is None:
# take all fields in cache, except the inverses of self
fnames = set(value._fields) - set(MAGIC_COLUMNS)
for invf in self.inverse_fields:
fnames.discard(invf.name)
# add new and existing records
for record in value:
if not record.id or record._dirty:
values = dict((k, v) for k, v in record._cache.iteritems() if k in fnames)
tempVal = {}
for n in values:
f = record._fields[n] #get field def
if f.type == 'one2many':
subrec = record[n]
subfields = subrec._fields
tempVal[n] = f.convert_to_write(subrec,record)
else:
val = {}
val[n] = values.get(n)
tempVal[n] = record._convert_to_write(val)[n]
if tempVal:
values = tempVal
#add to result
if not record.id:
result.append((0, 0, values))
else:
result.append((1, record.id, values))
else:
add_existing(record.id)
return result
In MODELS.py/BaseModel Class, replace the following method:
@api.model
def new(self, values={}):
""" new([values]) -> record
Return a new record instance attached to the current environment and
initialized with the provided ``value``. The record is *not* created
in database, it only exists in memory.
"""
record = self.browse([NewId()])
record._cache.update(record._convert_to_cache(values, update=True))
if record.env.in_onchange:
# The cache update does not set inverse fields, so do it manually.
# This is useful for computing a function field on secondary
# records, if that field depends on the main record.
for name in values:
field = self._fields.get(name)
if field:
try:
for invf in field.inverse_fields:
invf._update(record[name], record)
#serarch this field for sub inverse fields
for ftmp in self[name]._fields:
f = self[name]._fields.get(ftmp)
if f and f != invf:
for invf in f.inverse_fields:
val = record[name]
invf._update(record[name][ftmp], val)
except:
pass
return record
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With