Files
odoo_source/odoo/addons/test_testing_utilities/nested_o2m.py
T
Gorash cdaa761ced [REF] base: Update modifier syntax (invisible, required, readonly)
Goal:
* Simplified modifiers to only have one way to define modifiers;
* Remove states attributes on python field;
* Use python expression in view `required`, `readonly`, `invisible`;
* More accurate validation of xml views.

This commit change the syntax to python expression. The next commit
will update/convert all xml views.

Before this commit:
* the `required`, `readonly` and `invisible` attributes can only have
values of `True`, `False`, 1, 0 or a python expression to use the
context;
* the `attrs` attribute define a dict. The key of this dict was
`required`, `readonly` and `invisible` and the values are the domain or
a string representing a domain to be evaluate as python expression.
This python expressions was evaluate by the javascript with view fields
and other contextual values as: context, uid, parent, active_id,
active_ids, active_model, allowed_company_ids, current_company_id.
* the `states` attribute in the view was a comma separated list of the
state. This list was combined with the `invisible` attribute;
* the `invisible` attribute on python field is used as default value;
* the `states` attribute on python field was dictionnary with state as
key and list of tuple. This structure was combined with `readonly` view
attribute.
* After combining, the resulting domains of the different attributes
`required`, `readonly` and `invisible` are evaluated with the values of
the fields. The `invisible` attributes is splitted into two use:
`invisible` and `column_invisible`.

After this commit:
* The attributes `required`, `readonly`, `invisible` and
`column_invisible` define python expression. This python expressions
are evaluate by the javascript with view fields and other contextual
values as: context, uid, parent, active_id, active_ids, active_model,
allowed_company_ids, current_company_id.

The domains can contains contextual value and will be evaluate by the
javascript.

```xml
    <field name="field_a" readonly="not context.get('show_a')" attrs="{'readonly': [('field_b', '!=', False), ('field_c', '=', parent.c)]}"/>
    <field name="field_b" states="draft"/>
```
will be replaced by
```xml
    <field name="field_a" readonly="not context.get('show_a') or field_b and field_c == parent.c"/>
    <field name="field_b" invisible="state != 'draft'"/>
```

Some inherited views will be modified differently in order to maintain
the previous behavior:

```xml
    <field name="field_a" readonly="not context.get('show_a')" attrs="{'invisible': [('field_b', '!=', False)]}">
```
```xml
    <field name="field_a" position="attributes">
        <attribute name="attrs">{'readonly': [('field_c', '=', False)], 'invisible': [('field_d', '!=', '3')]}<attribute>
    </field>
```
will be replaced by
```xml
    <field name="field_a" readonly="not context.get('show_a')" invisible="field_b">
```
```xml
    <field name="field_a" position="attributes">
        <attribute name="readonly" add="(not field_c)" separator=" or "/>
        <attribute name="invisible">field_d != 3<attribute>
    </field>
```

Validation:
A stricter control is made on the level of the attributes (modifiers)
and the fields necessary for these. The use of the previous attributes
'attr' and 'states' triggers an error (these no longer exist after the
application of the migration script)

task-2495504

Part-of: odoo/odoo#104741
2023-08-18 09:49:08 +02:00

160 lines
5.9 KiB
Python

from lxml.builder import E
from odoo import fields, models, api, Command
class Product(models.Model):
_name = _description = 'ttu.product'
class Root(models.Model):
_name = _description = 'ttu.root'
product_id = fields.Many2one('ttu.product')
product_qty = fields.Integer()
qty_producing = fields.Integer()
qty_produced = fields.Integer(compute='_get_produced_qty')
move_raw_ids = fields.One2many('ttu.child', 'root_raw_id')
move_finished_ids = fields.One2many('ttu.child', 'root_id')
@api.depends('move_finished_ids.move_line_ids.qty_done')
def _get_produced_qty(self):
for r in self:
r.qty_produced = sum(r.mapped('move_finished_ids.move_line_ids.qty_done'))
@api.onchange('qty_producing')
def _onchange_producing(self):
production_move = self.move_finished_ids.filtered(
lambda move: move.product_id == self.product_id
)
if not production_move:
# Happens when opening the mo?
return
for line in production_move.move_line_ids:
line.qty_done = 0
qty_producing = self.qty_producing - self.qty_produced
vals = production_move._set_quantity_done_prepare_vals(qty_producing)
if vals['to_create']:
for res in vals['to_create']:
production_move.move_line_ids.new(res)
if vals['to_write']:
for move_line, res in vals['to_write']:
move_line.update(res)
for move in (self.move_raw_ids | self.move_finished_ids.filtered(lambda m: m.product_id != self.product_id)):
new_qty = qty_producing * move.unit_factor
for line in move.move_line_ids:
line.qty_done = 0
vals = move._set_quantity_done_prepare_vals(new_qty)
if vals['to_create']:
for res in vals['to_create']:
move.move_line_ids.new(res)
if vals['to_write']:
for move_line, res in vals['to_write']:
move_line.update(res)
def _get_default_form_view(self):
move_subview = E.tree(
{'editable': 'bottom'},
E.field(name='product_id'),
E.field(name='unit_factor'),
E.field(name='quantity_done'),
E.field(
{'name': 'move_line_ids', 'column_invisible': '1'},
E.tree(
E.field(name='qty_done', invisible='1'),
E.field(name='product_id', invisible='1'),
E.field(name='move_id', invisible='1'),
E.field(name='id', invisible='1'),
)
)
)
t = E.form(
E.field(name='product_id'),
E.field(name='product_qty'),
E.field(name='qty_producing'),
E.field({'name': 'move_raw_ids', 'on_change': '1'}, move_subview),
E.field({'name': 'move_finished_ids', 'on_change': '1'}, move_subview),
)
# deoptimise to ensure we call onchange most of the time, as im the real
# case this is done as a result of the metric fuckton of computes, but
# here the near complete lack of computes causes most of the onchange
# triggers to get disabled
for f in t.iter('field'):
f.set('on_change', '1')
return t
class Child(models.Model):
_name = _description = 'ttu.child'
product_id = fields.Many2one('ttu.product')
unit_factor = fields.Integer(default=1, required=True) # should be computed but we can ignore that
quantity_done = fields.Integer(
compute='_quantity_done_compute',
inverse='_quantity_done_set'
)
root_raw_id = fields.Many2one('ttu.root')
root_id = fields.Many2one('ttu.root')
move_line_ids = fields.One2many('ttu.grandchild', 'move_id')
def _set_quantity_done_prepare_vals(self, qty):
res = {'to_write': [], 'to_create': []}
for ml in self.move_line_ids:
ml_qty = ml.product_uom_qty - ml.qty_done
if ml_qty <= 0:
continue
taken_qty = min(qty, ml_qty)
res['to_write'].append((ml, {'qty_done': ml.qty_done + taken_qty}))
qty -= taken_qty
if qty <= 0:
break
if qty > 0:
res['to_create'].append({
'move_id': self.id,
'product_id': self.product_id.id,
'product_uom_qty': 0,
'qty_done': qty,
})
return res
@api.depends('move_line_ids.qty_done')
def _quantity_done_compute(self):
for move in self:
move.quantity_done = sum(move.mapped('move_line_ids.qty_done'))
def _quantity_done_set(self):
quantity_done = self[0].quantity_done # any call to create will invalidate `move.quantity_done`
for move in self:
move_lines = move.move_line_ids
if not move_lines:
if quantity_done:
# do not impact reservation here
move_line = self.env['ttu.grandchild'].create({
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_qty': 0,
'qty_done': quantity_done,
})
move.write({'move_line_ids': [Command.link(move_line.id)]})
elif len(move_lines) == 1:
move_lines[0].qty_done = quantity_done
else:
# Bypass the error if we're trying to write the same value.
ml_quantity_done = sum(l.qty_done for l in move_lines)
assert quantity_done == ml_quantity_done, "Cannot set the done quantity from this stock move, work directly with the move lines."
class Grandchild(models.Model):
_name = _description = 'ttu.grandchild'
product_id = fields.Many2one('ttu.product')
product_uom_qty = fields.Integer()
qty_done = fields.Integer()
move_id = fields.Many2one('ttu.child')