Skip to Content
Menu
This question has been flagged
1 Reply
2458 Views

Hello,


i've got a very strange problem with a create in a loop  !! 


I create a model s_placement_of like this :


and i added the following field in mrp.production model :

 

in mrp.production, i added this function :

Note that i added a log function at the beginning of the loop to write in log the value of colum s_nom_placement read (key PLA:), and an other one at the end to see all values i will create (key pla:)


Here is the last MO for the product (CO115) i will use :

You can see i added a new tab "Placements" to display all placement of the MO

Every lines are identicals except in columns "Lettre" and "Nom Placement" (in red)

Note that (in blue), in column "Matiere", every values are identicals (very important for the problem, see later)


Now, if i create a manual MO and select the same product CO115

We can see in the log, that each value of the column "Nom Placement" (s_nom_placement) are different. That's ok!


But when i look in the tab "Placements" (in red), i can see that the 3 lines are identicals (and in the database too)



I don't understand why according to the log, i write the good values but after create, values are wrongs.

And other thing, if i select the same product again (without save the MO), values are good!


Last thing

In a previous screen above, we see (in blue) that values are identicals in the column "Matière" (s_matiere)

If i create a new MO and select an other product wich last MO had 3 lines of placements with differents "matière", there is no problem!!!!


Is anybody understand what's happen here because i search until days without solution?

Hope you can help me


Vincent

Avatar
Discard
Best Answer

Hi,

just a few points I guess are worth mentioning:

1. [I think it is the reason] Creating anything in @api.onchange is quite a bad thing, since it assumes committing to a database, while onchange methods relate to interface points (no commits until you press the button 'Save'). Besides, a lot of values are not actual (e.g. if a one2many line is of a new - not created object - its ID would be also not real. Thus, self.id is - what is not good).

Instead of that, move logic to the inverse method (triggered after writing /creating in a parent model; so after committing).  

Otherwise, try to work with one2many operators (the first 3 in the 'best answer'): to prepare values for the field, not to create those lines


2. Make sure that the create method of your model has the decorator @api.model_create_multi (for the example have a look at the module product - the file product_template.py). So, make sure the method actually can receive the list of dicts, not just dict (if the decorator is @api.model). Otherwise, try to create each record in a loop, not a list outside that.

Actually a good thing for debugging would be to add loggers to that method and have a look what it receives.


3. A minor thing which might influence (partially relate to the previous point): Python lists work in many cases not intuitively.

For example,

a = b = [] 

b = [1,2,3]

would make 'a' also as [1,2,3], since it is a reference, not actual variable.

This hardly relates to the scenario you described. However, it would be a good idea to make create in each iteration (so, not to use any lists) for experimenting. At least if you decided to use onchange anyway.

Avatar
Discard
Author

Hi faOTools,

Thank you for your complete and very instructive answer!

I choose the one2many operators solution (see my code below, sorry i could not paste a picture)

But if now, lines are not created in db, there is always the same problem (3 times the same line whereas logs are ok

Note, i used list again but i tried without list for the same result

My code modified :

last_of = self.env['mrp.production'].search([('product_id', '=', self.product_id.id)], order="id desc", limit=1)

if last_of:

list_placement = self.env['s_placement_of'].search([('s_of_id', '=', last_of.id)])

placement_vals = []

for placement in list_placement:

_logger.critical('PLA: ' + str(placement.s_nom_placement))

placement_vals.append((0, 0, {

's_of_id': self.id,

's_gabarit': placement.s_gabarit,

'display_name': placement.s_gabarit,

's_matiere': placement.s_matiere.id,

's_type': placement.s_type,

's_qte': qty,

's_laize': placement.s_laize,

's_lettre': placement.s_lettre,

's_metrage': placement.s_metrage,

's_nb_epaisseur': placement.s_nb_epaisseur,

's_nom_placement': placement.s_nom_placement,

}))

if placement_vals:

_logger.critical('pla: ' + str(placement_vals))

self.s_tab_placement=placement_vals

Hi, at the first glance I cannot see the problem (quite difficult to read code without formatting). Have you tried to switch onchange to inverse?

That's quite simple: to replace the decorator and define an attribute 'inverse' for the field product_id in a line model (https://www.odoo.com/documentation/14.0/developer/reference/orm.html ).

Author

No i don't tried to switch onchange to inverse function because i see how to do that for a simple field (char, float,...) but i don't understand how to do that for a one2many field. I try to find an example but without success for now.

make inverse not for o2m field, for the same field you do in onchange (product_id). Onchange and inverse in the latest Odoos work in a very similar manner (if I'm not mistaken in Odoo 15 onchnage is planned to be fully removed by compute and inverse)

Author

Hi faOtools,

you mean override the field product_id in the model mrp.production with compute and inverse functions ?

But i don't understand because a compute function change the value of the concerned field but i don't want to change value of product_id!!

And if i override product_id only with inverse function (so without compute function), i've got an error : Table 'mrp_production': unable to set NOT NULL on column 'product_id'

Also i tried with compute and inverse function:

In compute function, i just add a line "pass"

In inverse function, the same code as my last comment

But when i save my MO, i've got this error/message :

1/On screen : mrp.production(392,).product_id

2/In log :

- inverse function : Non-stored field mrp.production.product_id cannot be searched.

- compute function : ('mrp.production(392,).product_id', None)

Any idea of my mystake or maybe you have url with a similar example ?

product_id is not computed field and it's stored in db. Have you also added the compute method for that? If so, you do not need that (it would break everything!). You should add only the inverse method.

The latter does not assume changing product_id field. It only catches the moment when the field is changed (including creating). So, the general logic is:

1. You inherit the model mrp.production

2. Declare a field product_id there as:

product_id = fields.Many2one(inverse=find_dossier_product_article)

3. Remove the decorator onchange from the method find_dossier_product_article

Author

Hi faOtools,

It works with the inverse function.

Thank you so much for your help!

Vincent