Как просматривать или искать поле One2many в Odoo?

Я расширил модель account.analytic.account полем One2many, которое ссылается на вторую модель с полем One2many.

Когда я пытаюсь выполнить итерацию по второму полю One2many из метода вычислений, он перечисляет только только что добавленные записи. Предыдущие записи (которые видны в интерфейсе) не отображаются в коде, использующем контекст «self», пока родительская запись не будет сохранена.

пример:

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? 

Вот код:

1.) расширенная модель account.analytic.account

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.) Первая упомянутая модель One2many:

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.) Вторая упоминаемая модель One2many:

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

person ED_209    schedule 30.09.2014    source источник
comment
По-прежнему возникает такая же проблема в odoo 12.   -  person Kashyap Prajapati    schedule 21.02.2019


Ответы (1)


К сожалению, OpenERP / Odoo поддерживает только один уровень взаимосвязи в методах при изменении, методах вычислений и отслеживании изменений записей.

Таким образом, из коробки разрешены настройки родительского / дочернего (как в FORMVIEW.one2manyLIST), но не родительского / родительского / дочернего (как в FORMVIEW.one2manyLIST.one2manyLIST).

Примером этого может быть:

  • Модель A - представлена ​​как форма гарантийного обслуживания (дедушка и бабушка)
  • Модель B - Список покрываемых локаций (родительская модель, со ссылкой на дедушку или бабушку)
  • Модель C - список услуг, предоставляемых для каждого покрываемого местоположения (дочерняя модель, со ссылкой на родительскую)

Изменения, внесенные в модель A, не сохранят записи модели C, а методы при изменении / вычисления в модели A не могут использовать поля в модели C.

Таким образом, изменения теряются, и вышеупомянутая проблема возникает, если изменения вносятся во второе вложенное поле one2many или даже если вы пытаетесь прочитать вложенное поле one2many.

Я создал решение, которое добавляет еще один уровень отслеживания к обмену / вычислению / записи. Вам нужно будет изменить ядро ​​Odoo. Я надеюсь, что это изменится в будущем, поскольку Odoo SA внесет это в список желаний на github.

Вот код для Odoo 8.0. YMMV с другими версиями.

FIELDS.PY/_RelationalMulti Класс. Замените следующий метод:

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

В классе MODELS.py/BaseModel замените следующий метод:

@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
person ED_209    schedule 15.10.2014