Why am I getting the error "Expected singleton" despite submitting only one ID?
I added some fields to the model stock.move
and I imported a CSV file to create some records. I go through all the lines and create entries one by one, as you can see here:
lot_id = self._get_lot_id(
n, row, product_id, picking_type_id,
box_quantity, product_uom_qty
)
move = {
'auto_lot_name': False,
'box_quantity': 2,
'client_order_ref': '581002',
'date': datetime.datetime(2017, 6, 24, 19, 55, 52, 372648),
'invoice_state': '2binvoiced',
'location_dest_id': 9,
'location_id': 12,
'name': 'Product name',
'partner_id': 487,
'partner_shipping_id': 488,
'picking_type_id': 2,
'price_unit': 4.0,
'pricelist_id': 1,
'product_code': u'36033',
'product_id': 3,
'product_uom': 3,
'product_uom_qty': 6.0,
'restrict_lot_id': 12222, # lot_id
'tax_id': [(4, 67)]
}
result = self.env['stock.move'].create(move)
I create a lot as needed in a method _get_lot_id
. If the batch has already been created, I return the ID. It works well
It is very simple and works many times, but sometimes I get the following error even though I restrict_lot_id
only fill the field with one ID, as you can see in the dictionary. It looks like it is adding the batch ID of the previous cycle. How is this possible? Is my database crashed?
File "/[ ... ]/import_moves/models/stock_picking_import_wizard.py", line 129, in _generate_moves_from_csv
result = self.env['stock.move'].create(move)
File "/[ ... ]/openerp/api.py", line 266, in wrapper
return new_api(self, *args, **kwargs)
File "/[ ... ]/openerp/api.py", line 508, in new_api
result = method(self._model, cr, uid, *args, **old_kwargs)
File "/[ ... ]/stock/stock.py", line 1993, in create
res = super(stock_move, self).create(cr, uid, vals, context=context)
File "/[ ... ]/openerp/api.py", line 268, in wrapper
return old_api(self, *args, **kwargs)
File "/[ ... ]/openerp/api.py", line 372, in old_api
result = method(recs, *args, **kwargs)
File "/[ ... ]/connector/producer.py", line 48, in create ##> strange because this module is not installed
record_id = create_original(self, vals)
File "/[ ... ]/openerp/api.py", line 266, in wrapper
return new_api(self, *args, **kwargs)
File "/[ ... ]/openerp/models.py", line 4126, in create
record = self.browse(self._create(old_vals))
File "/[ ... ]/openerp/api.py", line 266, in wrapper
return new_api(self, *args, **kwargs)
File "/[ ... ]/openerp/api.py", line 508, in new_api
result = method(self._model, cr, uid, *args, **old_kwargs)
File "/[ ... ]/openerp/models.py", line 4323, in _create
recs._validate_fields(vals)
File "/[ ... ]/openerp/api.py", line 266, in wrapper
return new_api(self, *args, **kwargs)
File "/[ ... ]/openerp/models.py", line 1285, in _validate_fields
raise ValidationError("Error while validating constraint\n\n%s" % tools.ustr(e))
ValidationError: ('ValidateError', u'Error while validating constraint\n\nValueError\nExpected singleton: stock.production.lot(12286, 12287)')
I have also confirmed that the ID arrives well inside the original function create
in the module stock
. It doesn't make any sense to me. What's happening?
I just checked that if I always create a lot it works well. So, something must be wrong with the method _get_lot_id
I am showing here
def _get_lot_id(self, n, row, product_id,
picking_type_id, box_quantity, product_uom_qty):
lot_id_name = row.get('lot_id_name', False)
if lot_id_name != '':
if picking_type_id == STOCK_PICKING_TYPE_OUT:
lot_ids = self.env['stock.production.lot'].search([
('product_id', '=', product_id.id),
('name', '=', lot_id_name)
])
if len(lot_ids) == 0:
try:
lot_id = self.env['stock.production.lot'].create({
'name': lot_id_name,
'product_id': product_id.id,
})
except Exception:
raise Warning(_('The lot could not be created. '
'\nROW: %s') % n)
return lot_id.ensure_one().id
if len(lot_ids) == 1:
return lot_ids[0].id
else:
raise Warning(_('ERROR\nThere is more than one lot with the same name for that product.'
'\nROW: %s') % n)
elif picking_type_id == STOCK_PICKING_TYPE_IN:
lot_ids = self.env['stock.production.lot'].search([
('product_id', '=', product_id.id),
('name', '=', lot_id_name)
])
if len(lot_ids) == 1:
return lot_ids[0].id
try:
lot_id = self.env['stock.production.lot'].create({
'name': lot_id_name,
'product_id': product_id.id,
})
return lot_id.id
except Exception:
raise Warning(_('The lot could not be created. '
'\nROW: %s') % n)
else:
if picking_type_id == STOCK_PICKING_TYPE_OUT:
raise Warning(_('The lot is required for outgoing moves. '
'\nROW: %s') % n)
elif picking_type_id == STOCK_PICKING_TYPE_IN:
# set "auto_lot_name = True" >> this is set by default, so the lot is automatically created
return False
source to share
I think the problem is in one of your code.
Let me explain why this error can happen in Odoo. self
in a method is recordSet
, which means that it can contain one or more records. When you use a decorator
@api.multi
, depends
or constraints
there self
may contain more than one record, so as to avoid such errors, make sure that you loop
- self
.
for rec in self:
# your code here
# always access to rec fields not self here
If you don't loop when you do self.some_field
it works fine when recordset
you only have one record, but when you have more it confuses which record you want to get the value from some_field
and here you get this error.
But when you use a decorator @api.one
, a method will be called here for every record, a single error never occurs with api.one
, because self always contains one record.
source to share
The method create
was probably overridden by some custom (or not) module that modifies the vals passed to the create function.
To find out what is happening, you need to go to the definition of the create
model method stock.production.lot
(addons / stock / stock.py) and either raise the exception, or import traceback;traceback.print_stack()
to see the methods that are called. After that, you will see the one that changes your values.
If not, you need to share more code for us to find out what's going on and how you create the dictionary move
source to share
I finally found the bug. The error was in the computed field as @Cherif Odoo said. I had this method
@api.multi
@api.depends('incoming_moves', 'incoming_moves.box_quantity')
def _compute_in_box_vqty(self):
for lot in self:
lot.in_box_vqty = sum(
move.box_quantity for move in self.incoming_moves)
And I had to replace self
with the lot
following
@api.multi
@api.depends('incoming_moves', 'incoming_moves.box_quantity')
def _compute_in_box_vqty(self):
for lot in self:
lot.in_box_vqty = sum(
move.box_quantity for move in lot.incoming_moves)
I haven't found the error before because the stack trace was not very helpful. I found it after comments on methods, fields, constraints, creates ...
source to share