How to browse or search One2many field in Odoo?

匿名 (未验证) 提交于 2019-12-03 03:10:03

问题:

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')) 

回答1:

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:

  • Model A - Presented as service warranty form (grandparent model)
  • Model B - List of covered locations (parent model, with ref to grandparent)
  • Model C - List of services provided for each covered location (child model, with ref to parent)

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 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!