diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py
index 25a4ef952e2..18497f07161 100644
--- a/addons/account/models/account_journal.py
+++ b/addons/account/models/account_journal.py
@@ -639,21 +639,22 @@ class AccountJournal(models.Model):
if 'refund_sequence' not in vals:
vals['refund_sequence'] = vals['type'] in ('sale', 'purchase')
- @api.model
- def create(self, vals):
- # OVERRIDE
- self._fill_missing_values(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ self._fill_missing_values(vals)
- journal = super(AccountJournal, self.with_context(mail_create_nolog=True)).create(vals)
+ journals = super(AccountJournal, self.with_context(mail_create_nolog=True)).create(vals_list)
- if 'alias_name' in vals:
- journal._update_mail_alias(vals)
+ for journal, vals in zip(journals, vals_list):
+ if 'alias_name' in vals:
+ journal._update_mail_alias(vals)
- # Create the bank_account_id if necessary
- if journal.type == 'bank' and not journal.bank_account_id and vals.get('bank_acc_number'):
- journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id'))
+ # Create the bank_account_id if necessary
+ if journal.type == 'bank' and not journal.bank_account_id and vals.get('bank_acc_number'):
+ journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id'))
- return journal
+ return journals
def set_bank_account(self, acc_number, bank_id=None):
""" Create a res.partner.bank (if not exists) and set it as value of the field bank_account_id """
diff --git a/addons/account/models/account_payment_method.py b/addons/account/models/account_payment_method.py
index 4a8a760ed46..af99fa533ac 100644
--- a/addons/account/models/account_payment_method.py
+++ b/addons/account/models/account_payment_method.py
@@ -156,10 +156,11 @@ class AccountPaymentMethodLine(models.Model):
account.reconcile = True
return super().write(vals)
- @api.model
+ @api.model_create_multi
def create(self, vals_list):
- if 'payment_account_id' in vals_list:
- account = self.env['account.account'].browse(vals_list['payment_account_id'])
- if not account.reconcile:
- account.reconcile = True
+ for vals in vals_list:
+ if 'payment_account_id' in vals:
+ account = self.env['account.account'].browse(vals['payment_account_id'])
+ if not account.reconcile:
+ account.reconcile = True
return super().create(vals_list)
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 9ff45fa5795..6903ba9c128 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -1187,25 +1187,26 @@ class AccountTaxRepartitionLineTemplate(models.Model):
plus_report_line_ids = fields.Many2many(string="Plus Tax Report Lines", relation='account_tax_repartition_plus_report_line', comodel_name='account.tax.report.line', copy=True, help="Tax report lines whose '+' tag will be assigned to move lines by this repartition line")
minus_report_line_ids = fields.Many2many(string="Minus Report Lines", relation='account_tax_repartition_minus_report_line', comodel_name='account.tax.report.line', copy=True, help="Tax report lines whose '-' tag will be assigned to move lines by this repartition line")
- @api.model
- def create(self, vals):
- if vals.get('plus_report_line_ids'):
- vals['plus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['plus_report_line_ids'])
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('plus_report_line_ids'):
+ vals['plus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['plus_report_line_ids'])
- if vals.get('minus_report_line_ids'):
- vals['minus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['minus_report_line_ids'])
+ if vals.get('minus_report_line_ids'):
+ vals['minus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['minus_report_line_ids'])
- if vals.get('tag_ids'):
- vals['tag_ids'] = self._convert_tag_syntax_to_orm(vals['tag_ids'])
+ if vals.get('tag_ids'):
+ vals['tag_ids'] = self._convert_tag_syntax_to_orm(vals['tag_ids'])
- if vals.get('use_in_tax_closing') is None:
- if not vals.get('account_id'):
- vals['use_in_tax_closing'] = False
- else:
- internal_group = self.env['account.account.template'].browse(vals.get('account_id')).user_type_id.internal_group
- vals['use_in_tax_closing'] = not (internal_group == 'income' or internal_group == 'expense')
+ if vals.get('use_in_tax_closing') is None:
+ if not vals.get('account_id'):
+ vals['use_in_tax_closing'] = False
+ else:
+ internal_group = self.env['account.account.template'].browse(vals.get('account_id')).user_type_id.internal_group
+ vals['use_in_tax_closing'] = not (internal_group == 'income' or internal_group == 'expense')
- return super(AccountTaxRepartitionLineTemplate, self).create(vals)
+ return super().create(vals_list)
@api.model
def _convert_tag_syntax_to_orm(self, tags_list):
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index 69fc1c66767..775ac97b105 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -148,13 +148,14 @@ class AccountFiscalPosition(models.Model):
zip_to = zip_to.rjust(max_length, '0')
return zip_from, zip_to
- @api.model
- def create(self, vals):
- zip_from = vals.get('zip_from')
- zip_to = vals.get('zip_to')
- if zip_from and zip_to:
- vals['zip_from'], vals['zip_to'] = self._convert_zip_values(zip_from, zip_to)
- return super(AccountFiscalPosition, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ zip_from = vals.get('zip_from')
+ zip_to = vals.get('zip_to')
+ if zip_from and zip_to:
+ vals['zip_from'], vals['zip_to'] = self._convert_zip_values(zip_from, zip_to)
+ return super().create(vals_list)
def write(self, vals):
zip_from = vals.get('zip_from')
diff --git a/addons/account/wizard/setup_wizards.py b/addons/account/wizard/setup_wizards.py
index 02d772bd92c..2c8ec7386a4 100644
--- a/addons/account/wizard/setup_wizards.py
+++ b/addons/account/wizard/setup_wizards.py
@@ -87,21 +87,22 @@ class SetupBarBankConfigWizard(models.TransientModel):
for record in self:
record.new_journal_name = record.acc_number
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
""" This wizard is only used to setup an account for the current active
company, so we always inject the corresponding partner when creating
the model.
"""
- vals['partner_id'] = self.env.company.partner_id.id
- vals['new_journal_name'] = vals['acc_number']
+ for vals in vals_list:
+ vals['partner_id'] = self.env.company.partner_id.id
+ vals['new_journal_name'] = vals['acc_number']
- # If no bank has been selected, but we have a bic, we are using it to find or create the bank
- if not vals['bank_id'] and vals['bank_bic']:
- vals['bank_id'] = self.env['res.bank'].search([('bic', '=', vals['bank_bic'])], limit=1).id \
- or self.env['res.bank'].create({'name': vals['bank_bic'], 'bic': vals['bank_bic']}).id
+ # If no bank has been selected, but we have a bic, we are using it to find or create the bank
+ if not vals['bank_id'] and vals['bank_bic']:
+ vals['bank_id'] = self.env['res.bank'].search([('bic', '=', vals['bank_bic'])], limit=1).id \
+ or self.env['res.bank'].create({'name': vals['bank_bic'], 'bic': vals['bank_bic']}).id
- return super(SetupBarBankConfigWizard, self).create(vals)
+ return super().create(vals_list)
@api.onchange('linked_journal_id')
def _onchange_new_journal_related_data(self):
diff --git a/addons/account_check_printing/models/account_journal.py b/addons/account_check_printing/models/account_journal.py
index 980c71563d4..82285b7c751 100644
--- a/addons/account_check_printing/models/account_journal.py
+++ b/addons/account_check_printing/models/account_journal.py
@@ -57,12 +57,11 @@ class AccountJournal(models.Model):
journal.check_sequence_id.sudo().number_next_actual = int(journal.check_next_number)
journal.check_sequence_id.sudo().padding = len(journal.check_next_number)
- @api.model
- def create(self, vals):
- rec = super(AccountJournal, self).create(vals)
- if not rec.check_sequence_id:
- rec._create_check_sequence()
- return rec
+ @api.model_create_multi
+ def create(self, vals_list):
+ journals = super().create(vals_list)
+ journals.filtered(lambda j: not j.check_sequence_id)._create_check_sequence()
+ return journals
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
diff --git a/addons/base_address_extended/tests/test_street_fields.py b/addons/base_address_extended/tests/test_street_fields.py
index 8d27ad3a178..0693a93ac34 100644
--- a/addons/base_address_extended/tests/test_street_fields.py
+++ b/addons/base_address_extended/tests/test_street_fields.py
@@ -53,21 +53,22 @@ class TestStreetFields(TransactionCase):
# test street -> street values (compute)
for idx, (company_values, expected_vals) in enumerate(zip(input_values, expected)):
- company_values['name'] = 'Test-%2d' % idx
- company = self.env['res.company'].create(company_values)
+ values = dict(company_values, name='Test-%2d' % idx)
+ company = self.env['res.company'].create(values)
self.assertStreetVals(company, expected_vals)
self.assertStreetVals(company.partner_id, expected_vals)
# test street_values -> street (inverse)
for idx, (company_values, expected_vals) in enumerate(zip(input_values, expected)):
- company_values['name'] = 'TestNew-%2d' % idx
- expected_street = company_values.pop('street')
- company_values.update(expected_vals)
- company = self.env['res.company'].create(company_values)
+ values = dict(company_values)
+ values['name'] = 'TestNew-%2d' % idx
+ expected_street = values.pop('street')
+ values.update(expected_vals)
+ company = self.env['res.company'].create(values)
self.assertEqual(company.street, expected_street)
- self.assertStreetVals(company, company_values)
+ self.assertStreetVals(company, values)
self.assertEqual(company.partner_id.street, expected_street)
- self.assertStreetVals(company.partner_id, company_values)
+ self.assertStreetVals(company.partner_id, values)
def test_company_write(self):
""" Will test the compute and inverse methods of street fields when updating partner records. """
diff --git a/addons/coupon/models/coupon_program.py b/addons/coupon/models/coupon_program.py
index 60c6f76d958..bbc8aed6ad3 100644
--- a/addons/coupon/models/coupon_program.py
+++ b/addons/coupon/models/coupon_program.py
@@ -83,14 +83,16 @@ class CouponProgram(models.Model):
if self.discount_type == 'fixed_amount':
self.discount_apply_on = 'on_order'
- @api.model
- def create(self, vals):
- program = super(CouponProgram, self).create(vals)
- if not vals.get('discount_line_product_id', False):
+ @api.model_create_multi
+ def create(self, vals_list):
+ programs = super().create(vals_list)
+ for program in programs:
+ if program.discount_line_product_id:
+ continue
values = program._get_discount_product_values()
discount_line_product_id = self.env['product.product'].create(values)
program.write({'discount_line_product_id': discount_line_product_id.id})
- return program
+ return programs
def write(self, vals):
res = super(CouponProgram, self).write(vals)
diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py
index 5dffe646dc1..cbb10ecc546 100644
--- a/addons/fleet/models/fleet_vehicle.py
+++ b/addons/fleet/models/fleet_vehicle.py
@@ -237,26 +237,30 @@ class FleetVehicle(models.Model):
res.append(('id', search_operator, res_ids))
return res
- @api.model
- def create(self, vals):
- # Fleet administrator may not have rights to create the plan_to_change_car value when the driver_id is a res.user
+ @api.model_create_multi
+ def create(self, vals_list):
+ # Fleet administrator may not have rights to create the plan_to_change_car
+ # value when the driver_id is a res.user.
# This trick is used to prevent access right error.
- ptc_value = 'plan_to_change_car' in vals.keys() and {'plan_to_change_car': vals.pop('plan_to_change_car')}
- res = super(FleetVehicle, self).create(vals)
- if ptc_value:
- res.sudo().write(ptc_value)
- if 'driver_id' in vals and vals['driver_id']:
- res.create_driver_history(vals)
- if 'future_driver_id' in vals and vals['future_driver_id']:
- state_waiting_list = self.env.ref('fleet.fleet_vehicle_state_waiting_list', raise_if_not_found=False)
- states = res.mapped('state_id').ids
- if not state_waiting_list or state_waiting_list.id not in states:
- future_driver = self.env['res.partner'].browse(vals['future_driver_id'])
- if self.vehicle_type == 'bike':
- future_driver.sudo().write({'plan_to_change_bike': True})
- if self.vehicle_type == 'car':
- future_driver.sudo().write({'plan_to_change_car': True})
- return res
+ ptc_values = [
+ 'plan_to_change_car' in vals.keys() and {'plan_to_change_car': vals.pop('plan_to_change_car')} for vals in vals_list
+ ]
+ vehicles = super().create(vals_list)
+ for vehicle, vals, ptc_value in zip(vehicles, vals_list, ptc_values):
+ if ptc_value:
+ vehicle.sudo().write(ptc_value)
+ if 'driver_id' in vals and vals['driver_id']:
+ vehicle.create_driver_history(vals)
+ if 'future_driver_id' in vals and vals['future_driver_id']:
+ state_waiting_list = self.env.ref('fleet.fleet_vehicle_state_waiting_list', raise_if_not_found=False)
+ states = vehicle.mapped('state_id').ids
+ if not state_waiting_list or state_waiting_list.id not in states:
+ future_driver = self.env['res.partner'].browse(vals['future_driver_id'])
+ if self.vehicle_type == 'bike':
+ future_driver.sudo().write({'plan_to_change_bike': True})
+ if self.vehicle_type == 'car':
+ future_driver.sudo().write({'plan_to_change_car': True})
+ return vehicles
def write(self, vals):
if 'driver_id' in vals and vals['driver_id']:
diff --git a/addons/hr/models/hr_department.py b/addons/hr/models/hr_department.py
index 944f4f5a92c..41ad2b4409d 100644
--- a/addons/hr/models/hr_department.py
+++ b/addons/hr/models/hr_department.py
@@ -53,16 +53,17 @@ class Department(models.Model):
if not self._check_recursion():
raise ValidationError(_('You cannot create recursive departments.'))
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# TDE note: auto-subscription of manager done by hand, because currently
# the tracking allows to track+subscribe fields linked to a res.user record
# An update of the limited behavior should come, but not currently done.
- department = super(Department, self.with_context(mail_create_nosubscribe=True)).create(vals)
- manager = self.env['hr.employee'].browse(vals.get("manager_id"))
- if manager.user_id:
- department.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
- return department
+ departments = super(Department, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
+ for department, vals in zip(departments, vals_list):
+ manager = self.env['hr.employee'].browse(vals.get("manager_id"))
+ if manager.user_id:
+ department.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
+ return departments
def write(self, vals):
""" If updating manager of a department, we need to update all the employees
diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py
index edc3c4e36e3..54569478b69 100644
--- a/addons/hr/models/hr_employee.py
+++ b/addons/hr/models/hr_employee.py
@@ -277,26 +277,28 @@ class HrEmployeePrivate(models.Model):
vals['tz'] = user.tz
return vals
- @api.model
- def create(self, vals):
- if vals.get('user_id'):
- user = self.env['res.users'].browse(vals['user_id'])
- vals.update(self._sync_user(user, bool(vals.get('image_1920'))))
- vals['name'] = vals.get('name', user.name)
- employee = super(HrEmployeePrivate, self).create(vals)
- if employee.department_id:
- self.env['mail.channel'].sudo().search([
- ('subscription_department_ids', 'in', employee.department_id.id)
- ])._subscribe_users_automatically()
- # Launch onboarding plans
- url = '/web#%s' % url_encode({
- 'action': 'hr.plan_wizard_action',
- 'active_id': employee.id,
- 'active_model': 'hr.employee',
- 'menu_id': self.env.ref('hr.menu_hr_root').id,
- })
- employee._message_log(body=_('Congratulations! May I recommend you to setup an onboarding plan?') % (url))
- return employee
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('user_id'):
+ user = self.env['res.users'].browse(vals['user_id'])
+ vals.update(self._sync_user(user, bool(vals.get('image_1920'))))
+ vals['name'] = vals.get('name', user.name)
+ employees = super().create(vals_list)
+ for employee in employees:
+ if employee.department_id:
+ self.env['mail.channel'].sudo().search([
+ ('subscription_department_ids', 'in', employee.department_id.id)
+ ])._subscribe_users_automatically()
+ # Launch onboarding plans
+ url = '/web#%s' % url_encode({
+ 'action': 'hr.plan_wizard_action',
+ 'active_id': employee.id,
+ 'active_model': 'hr.employee',
+ 'menu_id': self.env.ref('hr.menu_hr_root').id,
+ })
+ employee._message_log(body=_('Congratulations! May I recommend you to setup an onboarding plan?') % (url))
+ return employees
def write(self, vals):
if 'address_home_id' in vals:
diff --git a/addons/hr/models/hr_job.py b/addons/hr/models/hr_job.py
index 5222954cceb..8c5277d30a7 100644
--- a/addons/hr/models/hr_job.py
+++ b/addons/hr/models/hr_job.py
@@ -44,10 +44,10 @@ class Job(models.Model):
job.no_of_employee = result.get(job.id, 0)
job.expected_employees = result.get(job.id, 0) + job.no_of_recruitment
- @api.model
- def create(self, values):
+ @api.model_create_multi
+ def create(self, vals_list):
""" We don't want the current user to be follower of all created job """
- return super(Job, self.with_context(mail_create_nosubscribe=True)).create(values)
+ return super(Job, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py
index 4e38441afae..dd7e22b297f 100644
--- a/addons/hr_contract/models/hr_contract.py
+++ b/addons/hr_contract/models/hr_contract.py
@@ -229,11 +229,10 @@ class Contract(models.Model):
return res
- @api.model
- def create(self, vals):
- contracts = super(Contract, self).create(vals)
- if vals.get('state') == 'open':
- contracts._assign_open_contract()
+ @api.model_create_multi
+ def create(self, vals_list):
+ contracts = super().create(vals_list)
+ contracts.filtered(lambda c: c.state == 'open')._assign_open_contract()
open_contracts = contracts.filtered(lambda c: c.state == 'open' or c.state == 'draft' and c.kanban_state == 'done')
# sync contract calendar -> calendar employee
for contract in open_contracts.filtered(lambda c: c.employee_id and c.resource_calendar_id):
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 1338b44804c..b08d4dff9a9 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -946,16 +946,16 @@ class HrExpenseSheet(models.Model):
if any(expense.company_id != sheet.company_id for expense in sheet.expense_line_ids):
raise ValidationError(_('An expense report must contain only lines from the same company.'))
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
context = clean_context(self.env.context)
context.update({
'mail_create_nosubscribe': True,
'mail_auto_subscribe_no_notify': True
})
- sheet = super(HrExpenseSheet, self.with_context(context)).create(vals)
- sheet.activity_update()
- return sheet
+ sheets = super(HrExpenseSheet, self.with_context(context)).create(vals_list)
+ sheets.activity_update()
+ return sheets
@api.ondelete(at_uninstall=False)
def _unlink_except_posted_or_paid(self):
@@ -1134,12 +1134,21 @@ class HrExpenseSheet(models.Model):
return self.env['res.users']
def activity_update(self):
- for expense_report in self.filtered(lambda hol: hol.state == 'submit'):
- self.activity_schedule(
- 'hr_expense.mail_act_expense_approval',
- user_id=expense_report.sudo()._get_responsible_for_approval().id or self.env.user.id)
- self.filtered(lambda hol: hol.state == 'approve').activity_feedback(['hr_expense.mail_act_expense_approval'])
- self.filtered(lambda hol: hol.state in ('draft', 'cancel')).activity_unlink(['hr_expense.mail_act_expense_approval'])
+ reports_requiring_feedback = self.env['hr.expense.sheet']
+ reports_activity_unlink = self.env['hr.expense.sheet']
+ for expense_report in self:
+ if expense_report.state == 'submit':
+ expense_report.activity_schedule(
+ 'hr_expense.mail_act_expense_approval',
+ user_id=expense_report.sudo()._get_responsible_for_approval().id or self.env.user.id)
+ elif expense_report.state == 'approve':
+ reports_requiring_feedback |= expense_report
+ elif expense_report.state in ('draft', 'cancel'):
+ reports_activity_unlink |= expense_report
+ if reports_requiring_feedback:
+ reports_requiring_feedback.activity_feedback(['hr_expense.mail_act_expense_approval'])
+ if reports_activity_unlink:
+ reports_activity_unlink.activity_unlink(['hr_expense.mail_act_expense_approval'])
def action_register_payment(self):
''' Open the account.payment.register wizard to pay the selected journal entries.
diff --git a/addons/hr_fleet/models/fleet_vehicle.py b/addons/hr_fleet/models/fleet_vehicle.py
index e35f6fc54c9..d9e647c69f5 100644
--- a/addons/hr_fleet/models/fleet_vehicle.py
+++ b/addons/hr_fleet/models/fleet_vehicle.py
@@ -90,10 +90,11 @@ class FleetVehicle(models.Model):
employee = employee_ids[0].id
vals['future_driver_employee_id'] = employee
- @api.model
- def create(self, vals):
- self._update_create_write_vals(vals)
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ self._update_create_write_vals(vals)
+ return super().create(vals_list)
def write(self, vals):
self._update_create_write_vals(vals)
diff --git a/addons/hr_holidays/models/hr_employee.py b/addons/hr_holidays/models/hr_employee.py
index 87573810a88..2c5ed40de6f 100644
--- a/addons/hr_holidays/models/hr_employee.py
+++ b/addons/hr_holidays/models/hr_employee.py
@@ -182,16 +182,19 @@ class HrEmployeeBase(models.AbstractModel):
])
return [('id', 'in', holidays.mapped('employee_id').ids)]
- @api.model
- def create(self, values):
- if 'parent_id' in values:
- manager = self.env['hr.employee'].browse(values['parent_id']).user_id
- values['leave_manager_id'] = values.get('leave_manager_id', manager.id)
- if values.get('leave_manager_id', False):
- approver_group = self.env.ref('hr_holidays.group_hr_holidays_responsible', raise_if_not_found=False)
- if approver_group:
- approver_group.sudo().write({'users': [(4, values['leave_manager_id'])]})
- return super(HrEmployeeBase, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ approver_group = self.env.ref('hr_holidays.group_hr_holidays_responsible', raise_if_not_found=False)
+ group_updates = []
+ for vals in vals_list:
+ if 'parent_id' in vals:
+ manager = self.env['hr.employee'].browse(vals['parent_id']).user_id
+ vals['leave_manager_id'] = vals.get('leave_manager_id', manager.id)
+ if approver_group and vals.get('leave_manager_id'):
+ group_updates.append((4, vals['leave_manager_id']))
+ if group_updates:
+ approver_group.sudo().write({'users': group_updates})
+ return super().create(vals_list)
def write(self, values):
if 'parent_id' in values:
diff --git a/addons/hr_maintenance/models/equipment.py b/addons/hr_maintenance/models/equipment.py
index 279b0edeb5a..d6e727cf18f 100644
--- a/addons/hr_maintenance/models/equipment.py
+++ b/addons/hr_maintenance/models/equipment.py
@@ -41,18 +41,19 @@ class MaintenanceEquipment(models.Model):
equipment.employee_id = equipment.employee_id
equipment.assign_date = fields.Date.context_today(self)
- @api.model
- def create(self, vals):
- equipment = super(MaintenanceEquipment, self).create(vals)
- # subscribe employee or department manager when equipment assign to him.
- partner_ids = []
- if equipment.employee_id and equipment.employee_id.user_id:
- partner_ids.append(equipment.employee_id.user_id.partner_id.id)
- if equipment.department_id and equipment.department_id.manager_id and equipment.department_id.manager_id.user_id:
- partner_ids.append(equipment.department_id.manager_id.user_id.partner_id.id)
- if partner_ids:
- equipment.message_subscribe(partner_ids=partner_ids)
- return equipment
+ @api.model_create_multi
+ def create(self, vals_list):
+ equipments = super().create(vals_list)
+ for equipment in equipments:
+ # subscribe employee or department manager when equipment assign to him.
+ partner_ids = []
+ if equipment.employee_id and equipment.employee_id.user_id:
+ partner_ids.append(equipment.employee_id.user_id.partner_id.id)
+ if equipment.department_id and equipment.department_id.manager_id and equipment.department_id.manager_id.user_id:
+ partner_ids.append(equipment.department_id.manager_id.user_id.partner_id.id)
+ if partner_ids:
+ equipment.message_subscribe(partner_ids=partner_ids)
+ return equipments
def write(self, vals):
partner_ids = []
@@ -95,12 +96,13 @@ class MaintenanceRequest(models.Model):
else:
r.owner_user_id = False
- @api.model
- def create(self, vals):
- result = super(MaintenanceRequest, self).create(vals)
- if result.employee_id.user_id:
- result.message_subscribe(partner_ids=[result.employee_id.user_id.partner_id.id])
- return result
+ @api.model_create_multi
+ def create(self, vals_list):
+ requests = super().create(vals_list)
+ for request in requests:
+ if request.employee_id.user_id:
+ request.message_subscribe(partner_ids=[request.employee_id.user_id.partner_id.id])
+ return requests
def write(self, vals):
if vals.get('employee_id'):
diff --git a/addons/hr_recruitment/models/hr_employee.py b/addons/hr_recruitment/models/hr_employee.py
index 8640872d37d..cb7d7fe0195 100644
--- a/addons/hr_recruitment/models/hr_employee.py
+++ b/addons/hr_recruitment/models/hr_employee.py
@@ -23,12 +23,13 @@ class HrEmployee(models.Model):
])
return [('id', 'in', employees.ids)]
- @api.model
- def create(self, vals):
- new_employee = super(HrEmployee, self).create(vals)
- if new_employee.applicant_id:
- new_employee.applicant_id.message_post_with_view(
- 'hr_recruitment.applicant_hired_template',
- values={'applicant': new_employee.applicant_id},
- subtype_id=self.env.ref("hr_recruitment.mt_applicant_hired").id)
- return new_employee
+ @api.model_create_multi
+ def create(self, vals_list):
+ employees = super().create(vals_list)
+ for employee in employees:
+ if employee.applicant_id:
+ employee.applicant_id.message_post_with_view(
+ 'hr_recruitment.applicant_hired_template',
+ values={'applicant': employee.applicant_id},
+ subtype_id=self.env.ref("hr_recruitment.mt_applicant_hired").id)
+ return employees
diff --git a/addons/hr_recruitment/models/hr_job.py b/addons/hr_recruitment/models/hr_job.py
index de35aeab27b..2425b175bcc 100644
--- a/addons/hr_recruitment/models/hr_job.py
+++ b/addons/hr_recruitment/models/hr_job.py
@@ -120,18 +120,19 @@ class Job(models.Model):
})
return values
- @api.model
- def create(self, vals):
- vals['favorite_user_ids'] = vals.get('favorite_user_ids', []) + [(4, self.env.uid)]
- new_job = super(Job, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ vals['favorite_user_ids'] = vals.get('favorite_user_ids', []) + [(4, self.env.uid)]
+ jobs = super().create(vals_list)
utm_linkedin = self.env.ref("utm.utm_source_linkedin", raise_if_not_found=False)
if utm_linkedin:
- source_vals = {
+ source_vals = [{
'source_id': utm_linkedin.id,
- 'job_id': new_job.id,
- }
+ 'job_id': job.id,
+ } for job in jobs]
self.env['hr.recruitment.source'].create(source_vals)
- return new_job
+ return jobs
def write(self, vals):
res = super().write(vals)
diff --git a/addons/hr_recruitment/models/hr_recruitment.py b/addons/hr_recruitment/models/hr_recruitment.py
index 36dc5757aef..070909a9d12 100644
--- a/addons/hr_recruitment/models/hr_recruitment.py
+++ b/addons/hr_recruitment/models/hr_recruitment.py
@@ -340,31 +340,30 @@ class Applicant(models.Model):
if not applicant.stage_id.hired_stage:
applicant.date_closed = False
- @api.model
- def create(self, vals):
- if vals.get('department_id') and not self._context.get('default_department_id'):
- self = self.with_context(default_department_id=vals.get('department_id'))
- if vals.get('user_id'):
- vals['date_open'] = fields.Datetime.now()
- if vals.get('email_from'):
- vals['email_from'] = vals['email_from'].strip()
- res = super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('user_id'):
+ vals['date_open'] = fields.Datetime.now()
+ if vals.get('email_from'):
+ vals['email_from'] = vals['email_from'].strip()
+ applicants = super().create(vals_list)
# Record creation through calendar, creates the calendar event directly, it will also create the activity.
if 'default_activity_date_deadline' in self.env.context:
deadline = fields.Datetime.to_datetime(self.env.context.get('default_activity_date_deadline'))
- partners = res.partner_id | res.user_id.partner_id | res.department_id.manager_id.user_id.partner_id
-
category = self.env.ref('hr_recruitment.categ_meet_interview')
- self.env['calendar.event'].sudo().with_context(default_applicant_id=res.id).create({
- 'applicant_id': res.id,
- 'partner_ids': [(6, 0, partners.ids)],
- 'user_id': self.env.uid,
- 'name': res.name,
- 'categ_ids': [category.id],
- 'start': deadline,
- 'stop': deadline + relativedelta(minutes=30),
- })
- return res
+ for applicant in applicants:
+ partners = applicant.partner_id | applicant.user_id.partner_id | applicant.department_id.manager_id.user_id.partner_id
+ self.env['calendar.event'].sudo().with_context(default_applicant_id=applicant.id).create({
+ 'applicant_id': applicant.id,
+ 'partner_ids': [(6, 0, partners.ids)],
+ 'user_id': self.env.uid,
+ 'name': applicant.name,
+ 'categ_ids': [category.id],
+ 'start': deadline,
+ 'stop': deadline + relativedelta(minutes=30),
+ })
+ return applicants
def write(self, vals):
# user_id change: update date_open
diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py
index 90725ed2854..2c149c44f17 100644
--- a/addons/hr_timesheet/models/project.py
+++ b/addons/hr_timesheet/models/project.py
@@ -167,13 +167,13 @@ class Project(models.Model):
Note: create it before calling super() to avoid raising the ValidationError from _check_allow_timesheet
"""
defaults = self.default_get(['allow_timesheets', 'analytic_account_id'])
- for values in vals_list:
- allow_timesheets = values.get('allow_timesheets', defaults.get('allow_timesheets'))
- analytic_account_id = values.get('analytic_account_id', defaults.get('analytic_account_id'))
+ for vals in vals_list:
+ allow_timesheets = vals.get('allow_timesheets', defaults.get('allow_timesheets'))
+ analytic_account_id = vals.get('analytic_account_id', defaults.get('analytic_account_id'))
if allow_timesheets and not analytic_account_id:
- analytic_account = self._create_analytic_account_from_values(values)
- values['analytic_account_id'] = analytic_account.id
- return super(Project, self).create(vals_list)
+ analytic_account = self._create_analytic_account_from_values(vals)
+ vals['analytic_account_id'] = analytic_account.id
+ return super().create(vals_list)
def write(self, values):
# create the AA for project still allowing timesheet
diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py
index 8dd9a8de5ae..42fb4c35c24 100644
--- a/addons/l10n_ch/models/res_bank.py
+++ b/addons/l10n_ch/models/res_bank.py
@@ -150,12 +150,13 @@ class ResPartnerBank(models.Model):
else:
record.l10n_ch_qr_iban = None
- @api.model
- def create(self, vals):
- if vals.get('l10n_ch_qr_iban'):
- validate_qr_iban(vals['l10n_ch_qr_iban'])
- vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('l10n_ch_qr_iban'):
+ validate_qr_iban(vals['l10n_ch_qr_iban'])
+ vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
+ return super().create(vals_list)
def write(self, vals):
if vals.get('l10n_ch_qr_iban'):
diff --git a/addons/l10n_cl/models/res_partner.py b/addons/l10n_cl/models/res_partner.py
index abb6f70d756..53f0d867fd8 100644
--- a/addons/l10n_cl/models/res_partner.py
+++ b/addons/l10n_cl/models/res_partner.py
@@ -47,11 +47,12 @@ class ResPartner(models.Model):
n_vat, n_dv = vat_l[0], vat_l[1]
return '%s-%s' % (format(int(n_vat), ',d').replace(',', '.'), n_dv)
- @api.model
- def create(self, values):
- if values.get('vat'):
- values['vat'] = self._format_vat_cl(values)
- return super().create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('vat'):
+ vals['vat'] = self._format_vat_cl(vals)
+ return super().create(vals_list)
def write(self, values):
if any(field in values for field in ['vat', 'l10n_latam_identification_type_id', 'country_id']):
diff --git a/addons/l10n_es_edi_sii/models/l10n_es_edi_certificate.py b/addons/l10n_es_edi_sii/models/l10n_es_edi_certificate.py
index ef02f07f33f..84b563ddd07 100644
--- a/addons/l10n_es_edi_sii/models/l10n_es_edi_certificate.py
+++ b/addons/l10n_es_edi_sii/models/l10n_es_edi_certificate.py
@@ -61,27 +61,28 @@ class Certificate(models.Model):
# LOW-LEVEL METHODS
# -------------------------------------------------------------------------
- @api.model
- def create(self, vals):
- record = super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ certificates = super().create(vals_list)
spain_tz = timezone('Europe/Madrid')
spain_dt = self._get_es_current_datetime()
- try:
- _pem_certificate, _pem_private_key, certificate = record._decode_certificate()
- cert_date_start = spain_tz.localize(certificate.not_valid_before)
- cert_date_end = spain_tz.localize(certificate.not_valid_after)
- except Exception:
- raise ValidationError(_(
- "There has been a problem with the certificate, some usual problems can be:\n"
- "- The password given or the certificate are not valid.\n"
- "- The certificate content is invalid."
- ))
- # Assign extracted values from the certificate
- record.write({
- 'date_start': fields.Datetime.to_string(cert_date_start),
- 'date_end': fields.Datetime.to_string(cert_date_end),
- })
- if spain_dt > cert_date_end:
- raise ValidationError(_("The certificate is expired since %s", record.date_end))
- return record
+ for certificate in certificates:
+ try:
+ _pem_certificate, _pem_private_key, certif = certificate._decode_certificate()
+ cert_date_start = spain_tz.localize(certif.not_valid_before)
+ cert_date_end = spain_tz.localize(certif.not_valid_after)
+ except Exception:
+ raise ValidationError(_(
+ "There has been a problem with the certificate, some usual problems can be:\n"
+ "- The password given or the certificate are not valid.\n"
+ "- The certificate content is invalid."
+ ))
+ # Assign extracted values from the certificate
+ certificate.write({
+ 'date_start': fields.Datetime.to_string(cert_date_start),
+ 'date_end': fields.Datetime.to_string(cert_date_end),
+ })
+ if spain_dt > cert_date_end:
+ raise ValidationError(_("The certificate is expired since %s", certificate.date_end))
+ return certificates
diff --git a/addons/l10n_it_stock_ddt/models/stock_picking.py b/addons/l10n_it_stock_ddt/models/stock_picking.py
index 15c81ae4853..4abd783cdca 100644
--- a/addons/l10n_it_stock_ddt/models/stock_picking.py
+++ b/addons/l10n_it_stock_ddt/models/stock_picking.py
@@ -43,20 +43,21 @@ class StockPickingType(models.Model):
ir_seq_prefix = sequence_code + '/DDT'
return ir_seq_name, ir_seq_prefix
- @api.model
- def create(self, vals):
- company = self.env['res.company'].browse(vals['company_id'])
- if 'l10n_it_ddt_sequence_id' not in vals or not vals['l10n_it_ddt_sequence_id'] and vals['code'] == 'outgoing' \
- and company.country_id.code == 'IT':
- ir_seq_name, ir_seq_prefix = self._get_dtt_ir_seq_vals(vals.get('warehouse_id'), vals['sequence_code'])
- vals['l10n_it_ddt_sequence_id'] = self.env['ir.sequence'].create({
- 'name': ir_seq_name,
- 'prefix': ir_seq_prefix,
- 'padding': 5,
- 'company_id': vals['company_id'],
- 'implementation': 'no_gap',
- }).id
- return super(StockPickingType, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ company = self.env['res.company'].browse(vals['company_id'])
+ if 'l10n_it_ddt_sequence_id' not in vals or not vals['l10n_it_ddt_sequence_id'] and vals['code'] == 'outgoing' \
+ and company.country_id.code == 'IT':
+ ir_seq_name, ir_seq_prefix = self._get_dtt_ir_seq_vals(vals.get('warehouse_id'), vals['sequence_code'])
+ vals['l10n_it_ddt_sequence_id'] = self.env['ir.sequence'].create({
+ 'name': ir_seq_name,
+ 'prefix': ir_seq_prefix,
+ 'padding': 5,
+ 'company_id': vals['company_id'],
+ 'implementation': 'no_gap',
+ }).id
+ return super().create(vals_list)
def write(self, vals):
if 'sequence_code' in vals:
diff --git a/addons/l10n_latam_base/models/res_company.py b/addons/l10n_latam_base/models/res_company.py
index 165d0afb5f6..40dfee15324 100644
--- a/addons/l10n_latam_base/models/res_company.py
+++ b/addons/l10n_latam_base/models/res_company.py
@@ -6,13 +6,15 @@ from odoo import models, fields, api
class ResCompany(models.Model):
_inherit = 'res.company'
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
""" If exists, use specific vat identification.type for the country of the company """
- country_id = vals.get('country_id')
- if country_id:
+ companies = super().create(vals_list)
+ for company in companies:
+ if not company.country_id:
+ continue
country_vat_type = self.env['l10n_latam.identification.type'].search(
- [('is_vat', '=', True), ('country_id', '=', country_id)], limit=1)
+ [('is_vat', '=', True), ('country_id', '=', company.country_id.id)], limit=1)
if country_vat_type:
- self = self.with_context(default_l10n_latam_identification_type_id=country_vat_type.id)
- return super().create(vals)
+ company.partner_id.l10n_latam_identification_type_id = country_vat_type
+ return companies
diff --git a/addons/lunch/models/lunch_order.py b/addons/lunch/models/lunch_order.py
index d2ea25a8723..c3559040f97 100644
--- a/addons/lunch/models/lunch_order.py
+++ b/addons/lunch/models/lunch_order.py
@@ -116,18 +116,22 @@ class LunchOrder(models.Model):
if not check:
raise ValidationError(errors[quantity] % label)
- @api.model
- def create(self, values):
- lines = self._find_matching_lines({
- **values,
- 'toppings': self._extract_toppings(values),
- })
- if lines:
- # YTI FIXME This will update multiple lines in the case there are multiple
- # matching lines which should not happen through the interface
- lines.update_quantity(1)
- return lines[:1]
- return super().create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ orders = self.env['lunch.order']
+ for vals in vals_list:
+ lines = self._find_matching_lines({
+ **vals,
+ 'toppings': self._extract_toppings(vals),
+ })
+ if lines:
+ # YTI FIXME This will update multiple lines in the case there are multiple
+ # matching lines which should not happen through the interface
+ lines.update_quantity(1)
+ orders |= lines[:1]
+ else:
+ orders |= super().create(vals)
+ return orders
def write(self, values):
merge_needed = 'note' in values or 'topping_ids_1' in values or 'topping_ids_2' in values or 'topping_ids_3' in values
diff --git a/addons/mail_plugin/models/res_partner.py b/addons/mail_plugin/models/res_partner.py
index 8ff4c99fc03..3aff947f961 100644
--- a/addons/mail_plugin/models/res_partner.py
+++ b/addons/mail_plugin/models/res_partner.py
@@ -27,20 +27,18 @@ class ResPartner(models.Model):
partner.iap_enrich_info = False
partner.iap_search_domain = False
- @api.model
- def create(self, vals):
- partner = super(ResPartner, self).create(vals)
-
- if vals.get('iap_enrich_info') or vals.get('iap_search_domain'):
- # Not done with inverse method so we do not need to search
- # for existing
- self.env['res.partner.iap'].sudo().create({
- 'partner_id': partner.id,
- 'iap_enrich_info': vals.get('iap_enrich_info'),
- 'iap_search_domain': vals.get('iap_search_domain'),
- })
-
- return partner
+ @api.model_create_multi
+ def create(self, vals_list):
+ partners = super().create(vals_list)
+ # Not done with inverse method so we do not need to search
+ # for existing
+ partner_iap_vals_list = [{
+ 'partner_id': partner.id,
+ 'iap_enrich_info': vals.get('iap_enrich_info'),
+ 'iap_search_domain': vals.get('iap_search_domain'),
+ } for partner, vals in zip(partners, vals_list) if vals.get('iap_enrich_info') or vals.get('iap_search_domain')]
+ self.env['res.partner.iap'].sudo().create(partner_iap_vals_list)
+ return partners
def write(self, vals):
super(ResPartner, self).write(vals)
diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py
index d7b4441d5a9..1388bff3f22 100644
--- a/addons/maintenance/models/maintenance.py
+++ b/addons/maintenance/models/maintenance.py
@@ -200,12 +200,13 @@ class MaintenanceEquipment(models.Model):
('serial_no', 'unique(serial_no)', "Another asset already exists with this serial number!"),
]
- @api.model
- def create(self, vals):
- equipment = super(MaintenanceEquipment, self).create(vals)
- if equipment.owner_user_id:
- equipment.message_subscribe(partner_ids=[equipment.owner_user_id.partner_id.id])
- return equipment
+ @api.model_create_multi
+ def create(self, vals_list):
+ equipments = super().create(vals_list)
+ for equipment in equipments:
+ if equipment.owner_user_id:
+ equipment.message_subscribe(partner_ids=[equipment.owner_user_id.partner_id.id])
+ return equipments
def write(self, vals):
if vals.get('owner_user_id'):
@@ -331,16 +332,17 @@ class MaintenanceRequest(models.Model):
if not self.user_id or not self.equipment_id or (self.user_id and not self.equipment_id.technician_user_id):
self.user_id = self.category_id.technician_user_id
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# context: no_log, because subtype already handle this
- request = super(MaintenanceRequest, self).create(vals)
- if request.owner_user_id or request.user_id:
- request._add_followers()
- if request.equipment_id and not request.maintenance_team_id:
- request.maintenance_team_id = request.equipment_id.maintenance_team_id
- request.activity_update()
- return request
+ maintenance_requests = super().create(vals_list)
+ for request in maintenance_requests:
+ if request.owner_user_id or request.user_id:
+ request._add_followers()
+ if request.equipment_id and not request.maintenance_team_id:
+ request.maintenance_team_id = request.equipment_id.maintenance_team_id
+ maintenance_requests.activity_update()
+ return maintenance_requests
def write(self, vals):
# Overridden to reset the kanban_state to normal whenever
diff --git a/addons/mass_mailing_sms/models/mailing_mailing.py b/addons/mass_mailing_sms/models/mailing_mailing.py
index 43944101226..beef6c971a8 100644
--- a/addons/mass_mailing_sms/models/mailing_mailing.py
+++ b/addons/mass_mailing_sms/models/mailing_mailing.py
@@ -88,12 +88,14 @@ class Mailing(models.Model):
# ORM OVERRIDES
# --------------------------------------------------
- @api.model
- def create(self, values):
- # Get subject from "sms_subject" field when SMS installed (used to build the name of record in the super 'create' method)
- if values.get('mailing_type') == 'sms' and values.get('sms_subject'):
- values['subject'] = values['sms_subject']
- return super(Mailing, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ # Get subject from "sms_subject" field when SMS installed (used to
+ # build the name of record in the super 'create' method)
+ if vals.get('mailing_type') == 'sms' and vals.get('sms_subject'):
+ vals['subject'] = vals['sms_subject']
+ return super().create(vals_list)
# --------------------------------------------------
# BUSINESS / VIEWS ACTIONS
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 601e4cd6c5f..92bbdfc5014 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -765,37 +765,39 @@ class MrpProduction(models.Model):
production.date_planned_finished = new_date_planned_start + datetime.timedelta(hours=1)
return res
- @api.model
- def create(self, values):
- # Remove from `move_finished_ids` the by-product moves and then move `move_byproduct_ids`
- # into `move_finished_ids` to avoid duplicate and inconsistency.
- if values.get('move_finished_ids', False):
- values['move_finished_ids'] = list(filter(lambda move: move[2]['byproduct_id'] is False, values['move_finished_ids']))
- if values.get('move_byproduct_ids', False):
- values['move_finished_ids'] = values.get('move_finished_ids', []) + values['move_byproduct_ids']
- del values['move_byproduct_ids']
- if not values.get('name', False) or values['name'] == _('New'):
- picking_type_id = values.get('picking_type_id') or self._get_default_picking_type()
- picking_type_id = self.env['stock.picking.type'].browse(picking_type_id)
- if picking_type_id:
- values['name'] = picking_type_id.sequence_id.next_by_id()
- else:
- values['name'] = self.env['ir.sequence'].next_by_code('mrp.production') or _('New')
- if not values.get('procurement_group_id'):
- procurement_group_vals = self._prepare_procurement_group_vals(values)
- values['procurement_group_id'] = self.env["procurement.group"].create(procurement_group_vals).id
- production = super(MrpProduction, self).create(values)
- (production.move_raw_ids | production.move_finished_ids).write({
- 'group_id': production.procurement_group_id.id,
- 'origin': production.name
- })
- production.move_raw_ids.write({'date': production.date_planned_start})
- production.move_finished_ids.write({'date': production.date_planned_finished})
- # Trigger move_raw creation when importing a file
- if 'import_file' in self.env.context:
- production._onchange_move_raw()
- production._onchange_move_finished()
- return production
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ # Remove from `move_finished_ids` the by-product moves and then move `move_byproduct_ids`
+ # into `move_finished_ids` to avoid duplicate and inconsistency.
+ if vals.get('move_finished_ids', False):
+ vals['move_finished_ids'] = list(filter(lambda move: move[2]['byproduct_id'] is False, vals['move_finished_ids']))
+ if vals.get('move_byproduct_ids', False):
+ vals['move_finished_ids'] = vals.get('move_finished_ids', []) + vals['move_byproduct_ids']
+ del vals['move_byproduct_ids']
+ if not vals.get('name', False) or vals['name'] == _('New'):
+ picking_type_id = vals.get('picking_type_id') or self._get_default_picking_type()
+ picking_type_id = self.env['stock.picking.type'].browse(picking_type_id)
+ if picking_type_id:
+ vals['name'] = picking_type_id.sequence_id.next_by_id()
+ else:
+ vals['name'] = self.env['ir.sequence'].next_by_code('mrp.production') or _('New')
+ if not vals.get('procurement_group_id'):
+ procurement_group_vals = self._prepare_procurement_group_vals(vals)
+ vals['procurement_group_id'] = self.env["procurement.group"].create(procurement_group_vals).id
+ productions = super().create(vals_list)
+ for production in productions:
+ (production.move_raw_ids | production.move_finished_ids).write({
+ 'group_id': production.procurement_group_id.id,
+ 'origin': production.name
+ })
+ production.move_raw_ids.write({'date': production.date_planned_start})
+ production.move_finished_ids.write({'date': production.date_planned_finished})
+ # Trigger move_raw creation when importing a file
+ if 'import_file' in self.env.context:
+ production._onchange_move_raw()
+ production._onchange_move_finished()
+ return productions
@api.ondelete(at_uninstall=False)
def _unlink_except_done(self):
diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py
index d7698766a5b..c3ca662652b 100644
--- a/addons/mrp/models/mrp_unbuild.py
+++ b/addons/mrp/models/mrp_unbuild.py
@@ -108,11 +108,12 @@ class MrpUnbuild(models.Model):
if unbuild.product_qty <= 0:
raise ValueError(_('Unbuild Order product quantity has to be strictly positive.'))
- @api.model
- def create(self, vals):
- if not vals.get('name') or vals['name'] == _('New'):
- vals['name'] = self.env['ir.sequence'].next_by_code('mrp.unbuild') or _('New')
- return super(MrpUnbuild, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if not vals.get('name') or vals['name'] == _('New'):
+ vals['name'] = self.env['ir.sequence'].next_by_code('mrp.unbuild') or _('New')
+ return super().create(vals_list)
@api.ondelete(at_uninstall=False)
def _unlink_except_done(self):
diff --git a/addons/mrp_repair/models/repair.py b/addons/mrp_repair/models/repair.py
index 7f526a56f7c..5ae591fba41 100644
--- a/addons/mrp_repair/models/repair.py
+++ b/addons/mrp_repair/models/repair.py
@@ -6,11 +6,11 @@ from odoo import api, models
class Repair(models.Model):
_inherit = 'repair.order'
- @api.model
- def create(self, vals):
- res = super().create(vals)
- res.action_explode()
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ orders = super().create(vals_list)
+ orders.action_explode()
+ return orders
def write(self, vals):
res = super().write(vals)
diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py
index 9a0d7343607..fad362748fd 100644
--- a/addons/point_of_sale/models/pos_config.py
+++ b/addons/point_of_sale/models/pos_config.py
@@ -435,26 +435,27 @@ class PosConfig(models.Model):
result.append((config.id, "%s (%s)" % (config.name, last_session.user_id.name)))
return result
- @api.model
- def create(self, values):
- IrSequence = self.env['ir.sequence'].sudo()
- val = {
- 'name': _('POS Order %s', values['name']),
- 'padding': 4,
- 'prefix': "%s/" % values['name'],
- 'code': "pos.order",
- 'company_id': values.get('company_id', False),
- }
- # force sequence_id field to new pos.order sequence
- values['sequence_id'] = IrSequence.create(val).id
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ IrSequence = self.env['ir.sequence'].sudo()
+ val = {
+ 'name': _('POS Order %s', vals['name']),
+ 'padding': 4,
+ 'prefix': "%s/" % vals['name'],
+ 'code': "pos.order",
+ 'company_id': vals.get('company_id', False),
+ }
+ # force sequence_id field to new pos.order sequence
+ vals['sequence_id'] = IrSequence.create(val).id
- val.update(name=_('POS order line %s', values['name']), code='pos.order.line')
- values['sequence_line_id'] = IrSequence.create(val).id
- pos_config = super(PosConfig, self).create(values)
- pos_config.sudo()._check_modules_to_install()
- pos_config.sudo()._check_groups_implied()
+ val.update(name=_('POS order line %s', vals['name']), code='pos.order.line')
+ vals['sequence_line_id'] = IrSequence.create(val).id
+ pos_configs = super().create(vals_list)
+ pos_configs.sudo()._check_modules_to_install()
+ pos_configs.sudo()._check_groups_implied()
# If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install.
- return pos_config
+ return pos_configs
def write(self, vals):
opened_session = self.mapped('session_ids').filtered(lambda s: s.state != 'closed')
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 86b4c15ba31..afe90c04ff8 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -406,11 +406,12 @@ class PosOrder(models.Model):
for pos_order in self.filtered(lambda pos_order: pos_order.state not in ['draft', 'cancel']):
raise UserError(_('In order to delete a sale, it must be new or cancelled.'))
- @api.model
- def create(self, values):
- session = self.env['pos.session'].browse(values['session_id'])
- values = self._complete_values_from_session(session, values)
- return super(PosOrder, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ session = self.env['pos.session'].browse(vals['session_id'])
+ vals = self._complete_values_from_session(session, vals)
+ return super().create(vals_list)
@api.model
def _complete_values_from_session(self, session, values):
@@ -936,17 +937,18 @@ class PosOrderLine(models.Model):
'refunded_orderline_id': self.id,
}
- @api.model
- def create(self, values):
- if values.get('order_id') and not values.get('name'):
- # set name based on the sequence specified on the config
- config = self.env['pos.order'].browse(values['order_id']).session_id.config_id
- if config.sequence_line_id:
- values['name'] = config.sequence_line_id._next()
- if not values.get('name'):
- # fallback on any pos.order sequence
- values['name'] = self.env['ir.sequence'].next_by_code('pos.order.line')
- return super(PosOrderLine, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('order_id') and not vals.get('name'):
+ # set name based on the sequence specified on the config
+ config = self.env['pos.order'].browse(vals['order_id']).session_id.config_id
+ if config.sequence_line_id:
+ vals['name'] = config.sequence_line_id._next()
+ if not vals.get('name'):
+ # fallback on any pos.order sequence
+ vals['name'] = self.env['ir.sequence'].next_by_code('pos.order.line')
+ return super().create(vals_list)
def write(self, values):
if values.get('pack_lot_line_ids'):
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 2d485098d82..ffc1dc316eb 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -199,52 +199,52 @@ class PosSession(models.Model):
['%s - %s' % (invoice.name, invoice.state) for invoice in
unposted_invoices]))
- @api.model
- def create(self, values):
- config_id = values.get('config_id') or self.env.context.get('default_config_id')
- if not config_id:
- raise UserError(_("You should assign a Point of Sale to your session."))
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ config_id = vals.get('config_id') or self.env.context.get('default_config_id')
+ if not config_id:
+ raise UserError(_("You should assign a Point of Sale to your session."))
- # journal_id is not required on the pos_config because it does not
- # exists at the installation. If nothing is configured at the
- # installation we do the minimal configuration. Impossible to do in
- # the .xml files as the CoA is not yet installed.
- pos_config = self.env['pos.config'].browse(config_id)
- ctx = dict(self.env.context, company_id=pos_config.company_id.id)
+ # journal_id is not required on the pos_config because it does not
+ # exists at the installation. If nothing is configured at the
+ # installation we do the minimal configuration. Impossible to do in
+ # the .xml files as the CoA is not yet installed.
+ pos_config = self.env['pos.config'].browse(config_id)
+ ctx = dict(self.env.context, company_id=pos_config.company_id.id)
- pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
- if values.get('name'):
- pos_name += ' ' + values['name']
+ pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
+ if vals.get('name'):
+ pos_name += ' ' + vals['name']
- cash_payment_methods = pos_config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)
- statement_ids = self.env['account.bank.statement']
- if self.user_has_groups('point_of_sale.group_pos_user'):
- statement_ids = statement_ids.sudo()
- for cash_journal in cash_payment_methods.mapped('journal_id'):
- ctx['journal_id'] = cash_journal.id if pos_config.cash_control and cash_journal.type == 'cash' else False
- st_values = {
- 'journal_id': cash_journal.id,
- 'user_id': self.env.user.id,
+ cash_payment_methods = pos_config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)
+ statement_ids = self.env['account.bank.statement']
+ if self.user_has_groups('point_of_sale.group_pos_user'):
+ statement_ids = statement_ids.sudo()
+ for cash_journal in cash_payment_methods.mapped('journal_id'):
+ ctx['journal_id'] = cash_journal.id if pos_config.cash_control and cash_journal.type == 'cash' else False
+ st_vals = {
+ 'journal_id': cash_journal.id,
+ 'user_id': self.env.user.id,
+ 'name': pos_name,
+ }
+ statement_ids |= statement_ids.with_context(ctx).create(st_vals)
+
+ update_stock_at_closing = pos_config.company_id.point_of_sale_update_stock_quantities == "closing"
+
+ vals.update({
'name': pos_name,
- }
- statement_ids |= statement_ids.with_context(ctx).create(st_values)
-
- update_stock_at_closing = pos_config.company_id.point_of_sale_update_stock_quantities == "closing"
-
- values.update({
- 'name': pos_name,
- 'statement_ids': [(6, 0, statement_ids.ids)],
- 'config_id': config_id,
- 'update_stock_at_closing': update_stock_at_closing,
- })
+ 'statement_ids': [(6, 0, statement_ids.ids)],
+ 'config_id': config_id,
+ 'update_stock_at_closing': update_stock_at_closing,
+ })
if self.user_has_groups('point_of_sale.group_pos_user'):
- res = super(PosSession, self.with_context(ctx).sudo()).create(values)
+ sessions = super(PosSession, self.with_context(ctx).sudo()).create(vals_list)
else:
- res = super(PosSession, self.with_context(ctx)).create(values)
- res.action_pos_session_open()
-
- return res
+ sessions = super(PosSession, self.with_context(ctx)).create(vals_list)
+ sessions.action_pos_session_open()
+ return sessions
def unlink(self):
for session in self.filtered(lambda s: s.statement_ids):
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 2a0dde8cc48..ad5b9ba18c2 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -481,14 +481,15 @@ class Project(models.Model):
self.browse(res[0]).type_ids += self.env['project.task.type'].sudo().create({'name': _('New')})
return res
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# Prevent double project creation
self = self.with_context(mail_create_nosubscribe=True)
- project = super(Project, self).create(vals)
- if project.privacy_visibility == 'portal' and project.partner_id:
- project.message_subscribe(project.partner_id.ids)
- return project
+ projects = super().create(vals_list)
+ for project in projects:
+ if project.privacy_visibility == 'portal' and project.partner_id:
+ project.message_subscribe(project.partner_id.ids)
+ return projects
def write(self, vals):
# directly compute is_favorite to dodge allow write access right
diff --git a/addons/project/models/project_task_recurrence.py b/addons/project/models/project_task_recurrence.py
index 346578a740c..a80156be610 100644
--- a/addons/project/models/project_task_recurrence.py
+++ b/addons/project/models/project_task_recurrence.py
@@ -276,13 +276,14 @@ class ProjectTaskRecurrence(models.Model):
recurrence.recurrence_left -= 1
recurring_today._set_next_recurrence_date()
- @api.model
- def create(self, vals):
- if vals.get('repeat_number'):
- vals['recurrence_left'] = vals.get('repeat_number')
- res = super(ProjectTaskRecurrence, self).create(vals)
- res._set_next_recurrence_date()
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('repeat_number'):
+ vals['recurrence_left'] = vals.get('repeat_number')
+ recurrences = super().create(vals_list)
+ recurrences._set_next_recurrence_date()
+ return recurrences
def write(self, vals):
if vals.get('repeat_number'):
diff --git a/addons/project/models/project_update.py b/addons/project/models/project_update.py
index 3088cb3fa68..85ee35f5185 100644
--- a/addons/project/models/project_update.py
+++ b/addons/project/models/project_update.py
@@ -75,11 +75,12 @@ class ProjectUpdate(models.Model):
# ---------------------------------
# ORM Override
# ---------------------------------
- @api.model
- def create(self, vals):
- update = super().create(vals)
- update.project_id.sudo().last_update_id = update
- return update
+ @api.model_create_multi
+ def create(self, vals_list):
+ updates = super().create(vals_list)
+ for update in updates:
+ update.project_id.sudo().last_update_id = update
+ return updates
def unlink(self):
projects = self.project_id
diff --git a/addons/project_timesheet_holidays/models/hr_employee.py b/addons/project_timesheet_holidays/models/hr_employee.py
index da1cb562353..d42514abd6a 100644
--- a/addons/project_timesheet_holidays/models/hr_employee.py
+++ b/addons/project_timesheet_holidays/models/hr_employee.py
@@ -7,29 +7,30 @@ from odoo import api, fields, models
class Employee(models.Model):
_inherit = 'hr.employee'
- @api.model
- def create(self, vals):
- result = super(Employee, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ employees = super().create(vals_list)
# We need to create timesheet entries for the global time off that are already created
# and are planned for after this employee creation date
# First we look for the global time off that are already planned after today
today = fields.Datetime.today()
- global_leaves = result.resource_calendar_id.global_leave_ids.filtered(lambda l: l.date_from >= today)
- work_hours_data = global_leaves._work_time_per_day()
+ lines_vals = []
+ for employee in employees:
+ global_leaves = employee.resource_calendar_id.global_leave_ids.filtered(lambda l: l.date_from >= today)
+ work_hours_data = global_leaves._work_time_per_day()
- vals_list = []
- for global_time_off in global_leaves:
- for index, (day_date, work_hours_count) in enumerate(work_hours_data[global_time_off.id]):
- vals_list.append(
- global_time_off._timesheet_prepare_line_values(
- index,
- result,
- work_hours_data[global_time_off.id],
- day_date,
- work_hours_count
+ for global_time_off in global_leaves:
+ for index, (day_date, work_hours_count) in enumerate(work_hours_data[global_time_off.id]):
+ lines_vals.append(
+ global_time_off._timesheet_prepare_line_values(
+ index,
+ employee,
+ work_hours_data[global_time_off.id],
+ day_date,
+ work_hours_count
+ )
)
- )
- self.env['account.analytic.line'].sudo().create(vals_list)
- return result
+ self.env['account.analytic.line'].sudo().create(lines_vals)
+ return employees
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 9151b6e810e..4cbda32138e 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -231,21 +231,26 @@ class PurchaseOrder(models.Model):
self.partner_id.sudo().write(partner_vals) # Because the purchase user doesn't have write on `res.partner`
return res
- @api.model
- def create(self, vals):
- company_id = vals.get('company_id', self.default_get(['company_id'])['company_id'])
- # Ensures default picking type and currency are taken from the right company.
- self_comp = self.with_company(company_id)
- if vals.get('name', 'New') == 'New':
- seq_date = None
- if 'date_order' in vals:
- seq_date = fields.Datetime.context_timestamp(self, fields.Datetime.to_datetime(vals['date_order']))
- vals['name'] = self_comp.env['ir.sequence'].next_by_code('purchase.order', sequence_date=seq_date) or '/'
- vals, partner_vals = self._write_partner_values(vals)
- res = super(PurchaseOrder, self_comp).create(vals)
- if partner_vals:
- res.sudo().write(partner_vals) # Because the purchase user doesn't have write on `res.partner`
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ orders = self.browse()
+ partner_vals_list = []
+ for vals in vals_list:
+ company_id = vals.get('company_id', self.default_get(['company_id'])['company_id'])
+ # Ensures default picking type and currency are taken from the right company.
+ self_comp = self.with_company(company_id)
+ if vals.get('name', 'New') == 'New':
+ seq_date = None
+ if 'date_order' in vals:
+ seq_date = fields.Datetime.context_timestamp(self, fields.Datetime.to_datetime(vals['date_order']))
+ vals['name'] = self_comp.env['ir.sequence'].next_by_code('purchase.order', sequence_date=seq_date) or '/'
+ vals, partner_vals = self._write_partner_values(vals)
+ partner_vals_list.append(partner_vals)
+ orders |= super(PurchaseOrder, self_comp).create(vals)
+ for order, partner_vals in zip(orders, partner_vals_list):
+ if partner_vals:
+ order.sudo().write(partner_vals) # Because the purchase user doesn't have write on `res.partner`
+ return orders
@api.ondelete(at_uninstall=False)
def _unlink_if_cancelled(self):
diff --git a/addons/purchase_requisition/models/purchase.py b/addons/purchase_requisition/models/purchase.py
index 2fb39475351..965d6f8395e 100644
--- a/addons/purchase_requisition/models/purchase.py
+++ b/addons/purchase_requisition/models/purchase.py
@@ -88,14 +88,17 @@ class PurchaseOrder(models.Model):
po.requisition_id.action_done()
return res
- @api.model
- def create(self, vals):
- purchase = super(PurchaseOrder, self).create(vals)
- if purchase.requisition_id:
- purchase.message_post_with_view('mail.message_origin_link',
- values={'self': purchase, 'origin': purchase.requisition_id},
- subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'))
- return purchase
+ @api.model_create_multi
+ def create(self, vals_list):
+ orders = super().create(vals_list)
+ mt_note = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note')
+ for order in orders:
+ if order.requisition_id:
+ order.message_post_with_view(
+ 'mail.message_origin_link',
+ values={'self': order, 'origin': order.requisition_id},
+ subtype_id=mt_note)
+ return orders
def write(self, vals):
result = super(PurchaseOrder, self).write(vals)
diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py
index ca1f4e35d98..fa7050c7a1f 100644
--- a/addons/purchase_requisition/models/purchase_requisition.py
+++ b/addons/purchase_requisition/models/purchase_requisition.py
@@ -183,19 +183,20 @@ class PurchaseRequisitionLine(models.Model):
schedule_date = fields.Date(string='Scheduled Date')
supplier_info_ids = fields.One2many('product.supplierinfo', 'purchase_requisition_line_id')
- @api.model
- def create(self,vals):
- res = super(PurchaseRequisitionLine, self).create(vals)
- if res.requisition_id.state not in ['draft', 'cancel', 'done'] and res.requisition_id.is_quantity_copy == 'none':
- supplier_infos = self.env['product.supplierinfo'].search([
- ('product_id', '=', vals.get('product_id')),
- ('partner_id', '=', res.requisition_id.vendor_id.id),
- ])
- if not any(s.purchase_requisition_id for s in supplier_infos):
- res.create_supplier_info()
- if vals['price_unit'] <= 0.0:
- raise UserError(_('You cannot confirm the blanket order without price.'))
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ lines = super().create(vals_list)
+ for line, vals in zip(lines, vals_list):
+ if line.requisition_id.state not in ['draft', 'cancel', 'done'] and line.requisition_id.is_quantity_copy == 'none':
+ supplier_infos = self.env['product.supplierinfo'].search([
+ ('product_id', '=', vals.get('product_id')),
+ ('partner_id', '=', line.requisition_id.vendor_id.id),
+ ])
+ if not any(s.purchase_requisition_id for s in supplier_infos):
+ line.create_supplier_info()
+ if vals['price_unit'] <= 0.0:
+ raise UserError(_('You cannot confirm the blanket order without price.'))
+ return lines
def write(self, vals):
res = super(PurchaseRequisitionLine, self).write(vals)
diff --git a/addons/repair/models/repair.py b/addons/repair/models/repair.py
index e6f5ad33540..395407d2f37 100644
--- a/addons/repair/models/repair.py
+++ b/addons/repair/models/repair.py
@@ -211,11 +211,12 @@ class Repair(models.Model):
if order.state not in ('draft', 'cancel'):
raise UserError(_('You can not delete a repair order once it has been confirmed. You must first cancel it.'))
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# We generate a standard reference
- vals['name'] = self.env['ir.sequence'].next_by_code('repair.order') or '/'
- return super(Repair, self).create(vals)
+ for vals in vals_list:
+ vals['name'] = self.env['ir.sequence'].next_by_code('repair.order') or '/'
+ return super().create(vals_list)
def button_dummy(self):
# TDE FIXME: this button is very interesting
diff --git a/addons/resource/models/res_company.py b/addons/resource/models/res_company.py
index 694ce86a1c7..6879df1aa5d 100644
--- a/addons/resource/models/res_company.py
+++ b/addons/resource/models/res_company.py
@@ -23,12 +23,14 @@ class ResCompany(models.Model):
'company_id': company.id
}).id
- @api.model
- def create(self, values):
- company = super(ResCompany, self).create(values)
- if not company.resource_calendar_id:
- company.sudo()._create_resource_calendar()
+ @api.model_create_multi
+ def create(self, vals_list):
+ companies = super().create(vals_list)
+ companies_without_calendar = companies.filtered(lambda c: not c.resource_calendar_id)
+ if companies_without_calendar:
+ companies_without_calendar.sudo()._create_resource_calendar()
# calendar created from form view: no company_id set because record was still not created
- if not company.resource_calendar_id.company_id:
- company.resource_calendar_id.company_id = company.id
- return company
+ for company in companies:
+ if not company.resource_calendar_id.company_id:
+ company.resource_calendar_id.company_id = company.id
+ return companies
diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py
index 6dbbe982c99..a2047543c1c 100644
--- a/addons/resource/models/resource_mixin.py
+++ b/addons/resource/models/resource_mixin.py
@@ -33,17 +33,24 @@ class ResourceMixin(models.AbstractModel):
string='Timezone', related='resource_id.tz', readonly=False,
help="This field is used in order to define in which timezone the resources will work.")
- @api.model
- def create(self, values):
- if not values.get('resource_id'):
- resource_vals = {'name': values.get(self._rec_name)}
- tz = (values.pop('tz', False) or
- self.env['resource.calendar'].browse(values.get('resource_calendar_id')).tz)
- if tz:
- resource_vals['tz'] = tz
- resource = self.env['resource.resource'].create(resource_vals)
- values['resource_id'] = resource.id
- return super(ResourceMixin, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ resources_vals_list = []
+ calendar_ids = [vals['resource_calendar_id'] for vals in vals_list if vals.get('resource_calendar_id')]
+ calendars_tz = {calendar.id: calendar.tz for calendar in self.env['resource.calendar'].browse(calendar_ids)}
+ for vals in vals_list:
+ if not vals.get('resource_id'):
+ resource_vals = {'name': vals.get(self._rec_name)}
+ tz = vals.pop('tz', False) or calendars_tz.get(vals.get('resource_calendar_id'))
+ if tz:
+ resource_vals['tz'] = tz
+ resources_vals_list.append(resource_vals)
+ resources = self.env['resource.resource'].create(resources_vals_list)
+ resources_iter = iter(resources.ids)
+ for vals in vals_list:
+ if not vals.get('resource_id'):
+ vals['resource_id'] = next(resources_iter)
+ return super().create(vals_list)
def copy_data(self, default=None):
if default is None:
diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml
index e24ff7a1015..957f0e08f89 100644
--- a/addons/sale/data/sale_demo.xml
+++ b/addons/sale/data/sale_demo.xml
@@ -49,28 +49,22 @@
-
3
-
295.00
-
5
-
145.00
-
2
-
65.00
@@ -90,19 +84,15 @@
-
24
-
75.00
-
30
-
38.25
@@ -121,20 +111,14 @@
-
10
-
30.75
-
- 1
-
- 70.00
@@ -151,38 +135,29 @@
-
16
-
75.00
-
10
-
45.00
-
3
-
150.00
-
2
-
- 70.00
@@ -200,10 +175,7 @@
-
- 1
-
405.00
@@ -222,10 +194,7 @@
-
- 1
-
750.00
@@ -244,37 +213,26 @@
-
5
-
295.00
-
- 1
-
173.00
-
- 1
-
40.00
-
- 1
-
18.00
@@ -293,19 +251,15 @@
-
2
-
110.50
-
2
-
120.50
@@ -326,19 +280,15 @@
-
3
-
97.50
-
3
-
120.50
@@ -358,19 +308,15 @@
-
2
-
255.00
-
2
-
120.50
@@ -390,19 +336,15 @@
-
3
-
245.00
-
3
-
120.50
@@ -422,19 +364,14 @@
-
- 1
-
315.00
-
2
-
120.50
@@ -453,19 +390,13 @@
-
- 1
-
295.00
-
- 1
-
120.50
@@ -484,19 +415,15 @@
-
4
-
275.00
-
4
-
120.50
@@ -515,19 +442,15 @@
-
4
-
295.00
-
3
-
120.50
@@ -546,19 +469,15 @@
-
3
-
275.00
-
3
-
120.50
@@ -577,19 +496,15 @@
-
2
-
355.00
-
2
-
120.50
@@ -608,19 +523,15 @@
-
2
-
295.00
-
2
-
120.50
@@ -729,28 +640,22 @@ Thanks!
-
3
-
295.00
-
5
-
145.00
-
2
-
65.00
@@ -767,19 +672,15 @@ Thanks!
-
24
-
75.00
-
30
-
38.25
diff --git a/addons/sale/tests/__init__.py b/addons/sale/tests/__init__.py
index b75fb04a21b..f86f2e9323f 100644
--- a/addons/sale/tests/__init__.py
+++ b/addons/sale/tests/__init__.py
@@ -5,7 +5,6 @@ from . import test_access_rights
from . import test_accrued_sale_orders
from . import test_onchange
from . import test_payment_flow
-from . import test_performance
from . import test_reinvoice
from . import test_sale_flow
from . import test_sale_order
diff --git a/addons/sale/tests/test_performance.py b/addons/sale/tests/test_performance.py
deleted file mode 100644
index 1e7ad76b249..00000000000
--- a/addons/sale/tests/test_performance.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from odoo.tests import tagged
-
-from odoo.addons.base.tests.common import PerformanceCommon
-
-
-@tagged('post_install', '-at_install')
-class TestSalePerformance(PerformanceCommon):
-
- def test_batch_creation_sale_orders(self):
- """Enforce sale.order create overrides always support batch creation"""
- self.assertModelCreateMulti('sale.order')
-
- def test_batch_creation_sale_order_line(self):
- """Enforce sale.order.line create overrides always support batch creation"""
- self.assertModelCreateMulti('sale.order.line')
diff --git a/addons/sale_management/models/sale_order_template.py b/addons/sale_management/models/sale_order_template.py
index 1f303f298d4..e3a1d34338a 100644
--- a/addons/sale_management/models/sale_order_template.py
+++ b/addons/sale_management/models/sale_order_template.py
@@ -127,11 +127,12 @@ class SaleOrderTemplateLine(models.Model):
self.product_uom_id = self.product_id.uom_id.id
self.name = self.product_id.get_product_multiline_description_sale()
- @api.model
- def create(self, values):
- if values.get('display_type', self.default_get(['display_type'])['display_type']):
- values.update(product_id=False, product_uom_qty=0, product_uom_id=False)
- return super(SaleOrderTemplateLine, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('display_type', self.default_get(['display_type'])['display_type']):
+ vals.update(product_id=False, product_uom_qty=0, product_uom_id=False)
+ return super().create(vals_list)
def write(self, values):
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
diff --git a/addons/sale_timesheet/models/project_sale_line_employee_map.py b/addons/sale_timesheet/models/project_sale_line_employee_map.py
index 6ca913e2017..fa2b2864288 100644
--- a/addons/sale_timesheet/models/project_sale_line_employee_map.py
+++ b/addons/sale_timesheet/models/project_sale_line_employee_map.py
@@ -65,11 +65,11 @@ class ProjectProductEmployeeMap(models.Model):
for map_entry in self:
map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.timesheet_cost
- @api.model
- def create(self, values):
- res = super(ProjectProductEmployeeMap, self).create(values)
- res._update_project_timesheet()
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ maps = super().create(vals_list)
+ maps._update_project_timesheet()
+ return maps
def write(self, values):
res = super(ProjectProductEmployeeMap, self).write(values)
diff --git a/addons/stock/models/res_company.py b/addons/stock/models/res_company.py
index 18bd43a5aa3..dcc81cc91d2 100644
--- a/addons/stock/models/res_company.py
+++ b/addons/stock/models/res_company.py
@@ -191,17 +191,18 @@ class Company(models.Model):
def _create_per_company_rules(self):
self.ensure_one()
- @api.model
- def create(self, vals):
- company = super(Company, self).create(vals)
- company.sudo()._create_per_company_locations()
- company.sudo()._create_per_company_sequences()
- company.sudo()._create_per_company_picking_types()
- company.sudo()._create_per_company_rules()
- self.env['stock.warehouse'].sudo().create({
+ @api.model_create_multi
+ def create(self, vals_list):
+ companies = super().create(vals_list)
+ for company in companies:
+ company.sudo()._create_per_company_locations()
+ company.sudo()._create_per_company_sequences()
+ company.sudo()._create_per_company_picking_types()
+ company.sudo()._create_per_company_rules()
+ self.env['stock.warehouse'].sudo().create([{
'name': company.name,
'code': self.env.context.get('default_code') or company.name[:5],
'company_id': company.id,
'partner_id': company.partner_id.id
- })
- return company
+ } for company in companies])
+ return companies
diff --git a/addons/stock/models/stock_package_level.py b/addons/stock/models/stock_package_level.py
index 836d7587d9e..7d701d1c9e1 100644
--- a/addons/stock/models/stock_package_level.py
+++ b/addons/stock/models/stock_package_level.py
@@ -139,13 +139,14 @@ class StockPackageLevel(models.Model):
'company_id': package_level.company_id.id,
})
- @api.model
- def create(self, vals):
- result = super(StockPackageLevel, self).create(vals)
- if vals.get('location_dest_id'):
- result.mapped('move_line_ids').write({'location_dest_id': vals['location_dest_id']})
- result.mapped('move_ids').write({'location_dest_id': vals['location_dest_id']})
- return result
+ @api.model_create_multi
+ def create(self, vals_list):
+ package_levels = super().create(vals_list)
+ for package_level, vals in zip(package_levels, vals_list):
+ if vals.get('location_dest_id'):
+ package_level.move_line_ids.write({'location_dest_id': vals['location_dest_id']})
+ package_level.move_ids.write({'location_dest_id': vals['location_dest_id']})
+ return package_levels
def write(self, vals):
result = super(StockPackageLevel, self).write(vals)
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 5cb189e9d2c..c5e4bc7ddce 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -88,25 +88,24 @@ class PickingType(models.Model):
'res.company', 'Company', required=True,
default=lambda s: s.env.company.id, index=True)
- @api.model
- def create(self, vals):
- if 'sequence_id' not in vals or not vals['sequence_id']:
- if vals['warehouse_id']:
- wh = self.env['stock.warehouse'].browse(vals['warehouse_id'])
- vals['sequence_id'] = self.env['ir.sequence'].sudo().create({
- 'name': wh.name + ' ' + _('Sequence') + ' ' + vals['sequence_code'],
- 'prefix': wh.code + '/' + vals['sequence_code'] + '/', 'padding': 5,
- 'company_id': wh.company_id.id,
- }).id
- else:
- vals['sequence_id'] = self.env['ir.sequence'].sudo().create({
- 'name': _('Sequence') + ' ' + vals['sequence_code'],
- 'prefix': vals['sequence_code'], 'padding': 5,
- 'company_id': vals.get('company_id') or self.env.company.id,
- }).id
-
- picking_type = super(PickingType, self).create(vals)
- return picking_type
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if 'sequence_id' not in vals or not vals['sequence_id']:
+ if vals['warehouse_id']:
+ wh = self.env['stock.warehouse'].browse(vals['warehouse_id'])
+ vals['sequence_id'] = self.env['ir.sequence'].sudo().create({
+ 'name': wh.name + ' ' + _('Sequence') + ' ' + vals['sequence_code'],
+ 'prefix': wh.code + '/' + vals['sequence_code'] + '/', 'padding': 5,
+ 'company_id': wh.company_id.id,
+ }).id
+ else:
+ vals['sequence_id'] = self.env['ir.sequence'].sudo().create({
+ 'name': _('Sequence') + ' ' + vals['sequence_code'],
+ 'prefix': vals['sequence_code'], 'padding': 5,
+ 'company_id': vals.get('company_id') or self.env.company.id,
+ }).id
+ return super().create(vals_list)
def write(self, vals):
if 'company_id' in vals:
@@ -724,32 +723,37 @@ class Picking(models.Model):
"location_dest_id": self.location_dest_id
})
- @api.model
- def create(self, vals):
- defaults = self.default_get(['name', 'picking_type_id'])
- picking_type = self.env['stock.picking.type'].browse(vals.get('picking_type_id', defaults.get('picking_type_id')))
- if vals.get('name', '/') == '/' and defaults.get('name', '/') == '/' and vals.get('picking_type_id', defaults.get('picking_type_id')):
- if picking_type.sequence_id:
- vals['name'] = picking_type.sequence_id.next_by_id()
+ @api.model_create_multi
+ def create(self, vals_list):
+ scheduled_dates = []
+ for vals in vals_list:
+ defaults = self.default_get(['name', 'picking_type_id'])
+ picking_type = self.env['stock.picking.type'].browse(vals.get('picking_type_id', defaults.get('picking_type_id')))
+ if vals.get('name', '/') == '/' and defaults.get('name', '/') == '/' and vals.get('picking_type_id', defaults.get('picking_type_id')):
+ if picking_type.sequence_id:
+ vals['name'] = picking_type.sequence_id.next_by_id()
- # make sure to write `schedule_date` *after* the `stock.move` creation in
- # order to get a determinist execution of `_set_scheduled_date`
- scheduled_date = vals.pop('scheduled_date', False)
- res = super(Picking, self).create(vals)
- if scheduled_date:
- res.with_context(mail_notrack=True).write({'scheduled_date': scheduled_date})
- res._autoconfirm_picking()
+ # make sure to write `schedule_date` *after* the `stock.move` creation in
+ # order to get a determinist execution of `_set_scheduled_date`
+ scheduled_dates.append(vals.pop('scheduled_date', False))
- # set partner as follower
- if vals.get('partner_id'):
- for picking in res.filtered(lambda p: p.location_id.usage == 'supplier' or p.location_dest_id.usage == 'customer'):
- picking.message_subscribe([vals.get('partner_id')])
- if vals.get('picking_type_id'):
- for move in res.move_ids:
- if not move.description_picking:
- move.description_picking = move.product_id.with_context(lang=move._get_lang())._get_description(move.picking_id.picking_type_id)
+ pickings = super().create(vals_list)
- return res
+ for picking, scheduled_date in zip(pickings, scheduled_dates):
+ if scheduled_date:
+ picking.with_context(mail_notrack=True).write({'scheduled_date': scheduled_date})
+ pickings._autoconfirm_picking()
+
+ for picking, vals in zip(pickings, vals_list):
+ # set partner as follower
+ if vals.get('partner_id'):
+ if picking.location_id.usage == 'supplier' or picking.location_dest_id.usage == 'customer':
+ picking.message_subscribe([vals.get('partner_id')])
+ if vals.get('picking_type_id'):
+ for move in picking.move_ids:
+ if not move.description_picking:
+ move.description_picking = move.product_id.with_context(lang=move._get_lang())._get_description(move.picking_id.picking_type_id)
+ return pickings
def write(self, vals):
if vals.get('picking_type_id') and any(picking.state != 'draft' for picking in self):
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index 96e9d87f096..486c3b62c92 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -170,40 +170,43 @@ class StockQuant(models.Model):
domain_operator = 'in'
return [('id', domain_operator, quant_query)]
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
""" Override to handle the "inventory mode" and create a quant as
superuser the conditions are met.
"""
- if self._is_inventory_mode() and any(f in vals for f in ['inventory_quantity', 'inventory_quantity_auto_apply']):
- allowed_fields = self._get_inventory_fields_create()
- if any(field for field in vals.keys() if field not in allowed_fields):
- raise UserError(_("Quant's creation is restricted, you can't do this operation."))
+ quants = self.env['stock.quant']
+ is_inventory_mode = self._is_inventory_mode()
+ allowed_fields = self._get_inventory_fields_create()
+ for vals in vals_list:
+ if is_inventory_mode and any(f in vals for f in ['inventory_quantity', 'inventory_quantity_auto_apply']):
+ if any(field for field in vals.keys() if field not in allowed_fields):
+ raise UserError(_("Quant's creation is restricted, you can't do this operation."))
+ inventory_quantity = vals.pop('inventory_quantity', False) or vals.pop(
+ 'inventory_quantity_auto_apply', False) or 0
+ # Create an empty quant or write on a similar one.
+ product = self.env['product.product'].browse(vals['product_id'])
+ location = self.env['stock.location'].browse(vals['location_id'])
+ lot_id = self.env['stock.lot'].browse(vals.get('lot_id'))
+ package_id = self.env['stock.quant.package'].browse(vals.get('package_id'))
+ owner_id = self.env['res.partner'].browse(vals.get('owner_id'))
+ quant = self._gather(product, location, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
- inventory_quantity = vals.pop('inventory_quantity', False) or vals.pop(
- 'inventory_quantity_auto_apply', False) or 0
- # Create an empty quant or write on a similar one.
- product = self.env['product.product'].browse(vals['product_id'])
- location = self.env['stock.location'].browse(vals['location_id'])
- lot_id = self.env['stock.lot'].browse(vals.get('lot_id'))
- package_id = self.env['stock.quant.package'].browse(vals.get('package_id'))
- owner_id = self.env['res.partner'].browse(vals.get('owner_id'))
- quant = self._gather(product, location, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
-
- if quant:
- quant = quant[0].sudo()
+ if quant:
+ quant = quant[0].sudo()
+ else:
+ quant = self.sudo().create(vals)
+ # Set the `inventory_quantity` field to create the necessary move.
+ quant.inventory_quantity = inventory_quantity
+ quant.user_id = vals.get('user_id', self.env.user.id)
+ quant.inventory_date = fields.Date.today()
+ quants |= quant
else:
- quant = self.sudo().create(vals)
- # Set the `inventory_quantity` field to create the necessary move.
- quant.inventory_quantity = inventory_quantity
- quant.user_id = vals.get('user_id', self.env.user.id)
- quant.inventory_date = fields.Date.today()
-
- return quant
- res = super(StockQuant, self).create(vals)
- if self._is_inventory_mode():
- res._check_company()
- return res
+ quant = super().create(vals)
+ quants |= quant
+ if self._is_inventory_mode():
+ quant._check_company()
+ return quants
def _load_records_create(self, values):
""" Add default location if import file did not fill it"""
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 206dd34271e..f57ee7eb4b5 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -106,44 +106,47 @@ class Warehouse(models.Model):
}
}
- @api.model
- def create(self, vals):
- # create view location for warehouse then create all locations
- loc_vals = {'name': vals.get('code'), 'usage': 'view',
- 'location_id': self.env.ref('stock.stock_location_locations').id}
- if vals.get('company_id'):
- loc_vals['company_id'] = vals.get('company_id')
- vals['view_location_id'] = self.env['stock.location'].create(loc_vals).id
- sub_locations = self._get_locations_values(vals)
-
- for field_name, values in sub_locations.items():
- values['location_id'] = vals['view_location_id']
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ # create view location for warehouse then create all locations
+ loc_vals = {'name': vals.get('code'), 'usage': 'view',
+ 'location_id': self.env.ref('stock.stock_location_locations').id}
if vals.get('company_id'):
- values['company_id'] = vals.get('company_id')
- vals[field_name] = self.env['stock.location'].with_context(active_test=False).create(values).id
+ loc_vals['company_id'] = vals.get('company_id')
+ vals['view_location_id'] = self.env['stock.location'].create(loc_vals).id
+ sub_locations = self._get_locations_values(vals)
+
+ for field_name, values in sub_locations.items():
+ values['location_id'] = vals['view_location_id']
+ if vals.get('company_id'):
+ values['company_id'] = vals.get('company_id')
+ vals[field_name] = self.env['stock.location'].with_context(active_test=False).create(values).id
# actually create WH
- warehouse = super(Warehouse, self).create(vals)
- # create sequences and operation types
- new_vals = warehouse._create_or_update_sequences_and_picking_types()
- warehouse.write(new_vals) # TDE FIXME: use super ?
- # create routes and push/stock rules
- route_vals = warehouse._create_or_update_route()
- warehouse.write(route_vals)
+ warehouses = super().create(vals_list)
- # Update global route with specific warehouse rule.
- warehouse._create_or_update_global_routes_rules()
+ for warehouse, vals in zip(warehouses, vals_list):
+ # create sequences and operation types
+ new_vals = warehouse._create_or_update_sequences_and_picking_types()
+ warehouse.write(new_vals) # TDE FIXME: use super ?
+ # create routes and push/stock rules
+ route_vals = warehouse._create_or_update_route()
+ warehouse.write(route_vals)
- # create route selectable on the product to resupply the warehouse from another one
- warehouse.create_resupply_routes(warehouse.resupply_wh_ids)
+ # Update global route with specific warehouse rule.
+ warehouse._create_or_update_global_routes_rules()
- # update partner data if partner assigned
- if vals.get('partner_id'):
- self._update_partner_data(vals['partner_id'], vals.get('company_id'))
+ # create route selectable on the product to resupply the warehouse from another one
+ warehouse.create_resupply_routes(warehouse.resupply_wh_ids)
+
+ # update partner data if partner assigned
+ if vals.get('partner_id'):
+ self._update_partner_data(vals['partner_id'], vals.get('company_id'))
self._check_multiwarehouse_group()
- return warehouse
+ return warehouses
def write(self, vals):
if 'company_id' in vals:
diff --git a/addons/stock/tests/test_inventory.py b/addons/stock/tests/test_inventory.py
index edfff18fc22..1c3defee8bd 100644
--- a/addons/stock/tests/test_inventory.py
+++ b/addons/stock/tests/test_inventory.py
@@ -300,7 +300,7 @@ class TestInventory(TransactionCase):
after an adjustment.
"""
# Set product quantity to 42.
- inventory_quant = self.env['stock.quant'].create(vals={
+ inventory_quant = self.env['stock.quant'].create({
'product_id': self.product1.id,
'location_id': self.stock_location.id,
'inventory_quantity': 42,
diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py
index a0c6c28f02a..520f739f2f8 100644
--- a/addons/stock_landed_costs/models/stock_landed_cost.py
+++ b/addons/stock_landed_costs/models/stock_landed_cost.py
@@ -83,11 +83,12 @@ class StockLandedCost(models.Model):
if self.target_model != 'picking':
self.picking_ids = False
- @api.model
- def create(self, vals):
- if vals.get('name', _('New')) == _('New'):
- vals['name'] = self.env['ir.sequence'].next_by_code('stock.landed.cost')
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('name', _('New')) == _('New'):
+ vals['name'] = self.env['ir.sequence'].next_by_code('stock.landed.cost')
+ return super().create(vals_list)
def unlink(self):
self.button_cancel()
diff --git a/addons/stock_picking_batch/models/stock_picking.py b/addons/stock_picking_batch/models/stock_picking.py
index a5d807260b9..af660b050f1 100644
--- a/addons/stock_picking_batch/models/stock_picking.py
+++ b/addons/stock_picking_batch/models/stock_picking.py
@@ -43,12 +43,13 @@ class StockPicking(models.Model):
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help='Batch associated to this transfer', copy=False)
- @api.model
- def create(self, vals):
- res = super().create(vals)
- if vals.get('batch_id'):
- res.batch_id._sanity_check()
- return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ pickings = super().create(vals_list)
+ for picking, vals in zip(pickings, vals_list):
+ if vals.get('batch_id'):
+ picking.batch_id._sanity_check()
+ return pickings
def write(self, vals):
batches = self.batch_id
diff --git a/addons/stock_picking_batch/models/stock_picking_batch.py b/addons/stock_picking_batch/models/stock_picking_batch.py
index e6c3107ed41..f648b8290d6 100644
--- a/addons/stock_picking_batch/models/stock_picking_batch.py
+++ b/addons/stock_picking_batch/models/stock_picking_batch.py
@@ -152,14 +152,16 @@ class StockPickingBatch(models.Model):
# -------------------------------------------------------------------------
# CRUD
# -------------------------------------------------------------------------
- @api.model
- def create(self, vals):
- if vals.get('name', '/') == '/':
- if vals.get('is_wave'):
- vals['name'] = self.env['ir.sequence'].next_by_code('picking.wave') or '/'
- else:
- vals['name'] = self.env['ir.sequence'].next_by_code('picking.batch') or '/'
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('name', '/') == '/':
+ company_id = vals.get('company_id', self.env.company.id)
+ if vals.get('is_wave'):
+ vals['name'] = self.env['ir.sequence'].with_company(company_id).next_by_code('picking.wave') or '/'
+ else:
+ vals['name'] = self.env['ir.sequence'].with_company(company_id).next_by_code('picking.batch') or '/'
+ return super().create(vals_list)
def write(self, vals):
res = super().write(vals)
diff --git a/addons/website/models/ir_attachment.py b/addons/website/models/ir_attachment.py
index dec1a662a61..2b9b7a6d466 100644
--- a/addons/website/models/ir_attachment.py
+++ b/addons/website/models/ir_attachment.py
@@ -17,12 +17,13 @@ class Attachment(models.Model):
key = fields.Char(help='Technical field used to resolve multiple attachments in a multi-website environment.')
website_id = fields.Many2one('website')
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
website = self.env['website'].get_current_website(fallback=False)
- if website and 'website_id' not in vals and 'not_force_website_id' not in self.env.context:
- vals['website_id'] = website.id
- return super(Attachment, self).create(vals)
+ for vals in vals_list:
+ if website and 'website_id' not in vals and 'not_force_website_id' not in self.env.context:
+ vals['website_id'] = website.id
+ return super().create(vals_list)
@api.model
def get_serving_groups(self):
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 7f95d721d3a..2a9dc2a8f46 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -174,24 +174,26 @@ class Website(models.Model):
def _get_menu_ids(self):
return self.env['website.menu'].search([('website_id', '=', self.id)]).ids
- @api.model
- def create(self, vals):
- self._handle_create_write(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ self._handle_create_write(vals)
- if 'user_id' not in vals:
- company = self.env['res.company'].browse(vals.get('company_id'))
- vals['user_id'] = company._get_public_user().id if company else self.env.ref('base.public_user').id
+ if 'user_id' not in vals:
+ company = self.env['res.company'].browse(vals.get('company_id'))
+ vals['user_id'] = company._get_public_user().id if company else self.env.ref('base.public_user').id
- res = super(Website, self).create(vals)
- res.company_id._compute_website_id()
- res._bootstrap_homepage()
+ websites = super().create(vals_list)
+ websites.company_id._compute_website_id()
+ for website in websites:
+ website._bootstrap_homepage()
if not self.env.user.has_group('website.group_multi_website') and self.search_count([]) > 1:
all_user_groups = 'base.group_portal,base.group_user,base.group_public'
groups = self.env['res.groups'].concat(*(self.env.ref(it) for it in all_user_groups.split(',')))
groups.write({'implied_ids': [(4, self.env.ref('website.group_multi_website').id)]})
- return res
+ return websites
def write(self, values):
public_user_to_change_websites = self.env['website']
diff --git a/addons/website/models/website_menu.py b/addons/website/models/website_menu.py
index f5bfbf4678d..81f6678b7cc 100644
--- a/addons/website/models/website_menu.py
+++ b/addons/website/models/website_menu.py
@@ -61,8 +61,8 @@ class Menu(models.Model):
res.append((menu.id, menu_name))
return res
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
''' In case a menu without a website_id is trying to be created, we duplicate
it for every website.
Note: Particulary useful when installing a module that adds a menu like
@@ -72,27 +72,32 @@ class Menu(models.Model):
'''
self.clear_caches()
# Only used when creating website_data.xml default menu
- if vals.get('url') == '/default-main-menu':
- return super(Menu, self).create(vals)
-
- if 'website_id' in vals:
- return super(Menu, self).create(vals)
- elif self._context.get('website_id'):
- vals['website_id'] = self._context.get('website_id')
- return super(Menu, self).create(vals)
- else:
- # create for every site
- for website in self.env['website'].search([]):
- w_vals = dict(vals, **{
+ menus = self.env['website.menu']
+ for vals in vals_list:
+ if vals.get('url') == '/default-main-menu':
+ menus |= super().create(vals)
+ continue
+ if 'website_id' in vals:
+ menus |= super().create(vals)
+ continue
+ elif self._context.get('website_id'):
+ vals['website_id'] = self._context.get('website_id')
+ menus |= super().create(vals)
+ continue
+ else:
+ # create for every site
+ w_vals = [dict(vals, **{
'website_id': website.id,
'parent_id': website.menu_id.id,
- })
- res = super(Menu, self).create(w_vals)
- # if creating a default menu, we should also save it as such
- default_menu = self.env.ref('website.main_menu', raise_if_not_found=False)
- if default_menu and vals.get('parent_id') == default_menu.id:
- res = super(Menu, self).create(vals)
- return res # Only one record is returned but multiple could have been created
+ }) for website in self.env['website'].search([])]
+ new_menu = super().create(w_vals)[-1:] # take the last one
+ # if creating a default menu, we should also save it as such
+ default_menu = self.env.ref('website.main_menu', raise_if_not_found=False)
+ if default_menu and vals.get('parent_id') == default_menu.id:
+ new_menu = super().create(vals)
+ menus |= new_menu
+ # Only one record per vals is returned but multiple could have been created
+ return menus
def write(self, values):
res = super().write(values)
diff --git a/addons/website/models/website_rewrite.py b/addons/website/models/website_rewrite.py
index 31f8bc088a6..5c8b338c48c 100644
--- a/addons/website/models/website_rewrite.py
+++ b/addons/website/models/website_rewrite.py
@@ -108,11 +108,11 @@ class WebsiteRewrite(models.Model):
result.append((rewrite.id, name))
return result
- @api.model
- def create(self, vals):
- res = super(WebsiteRewrite, self).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ rewrites = super().create(vals_list)
self._invalidate_routing()
- return res
+ return rewrites
def write(self, vals):
res = super(WebsiteRewrite, self).write(vals)
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
index 1d4129d7db6..2794514552b 100644
--- a/addons/website_blog/models/website_blog.py
+++ b/addons/website_blog/models/website_blog.py
@@ -229,11 +229,12 @@ class BlogPost(models.Model):
return True
return False
- @api.model
- def create(self, vals):
- post_id = super(BlogPost, self.with_context(mail_create_nolog=True)).create(vals)
- post_id._check_for_publication(vals)
- return post_id
+ @api.model_create_multi
+ def create(self, vals_list):
+ posts = super(BlogPost, self.with_context(mail_create_nolog=True)).create(vals_list)
+ for post, vals in zip(posts, vals_list):
+ post._check_for_publication(vals)
+ return posts
def write(self, vals):
result = True
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 340c24b1819..5c519646883 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -532,28 +532,31 @@ class Post(models.Model):
if not self._check_recursion():
raise ValidationError(_('You cannot create recursive forum posts.'))
- @api.model
- def create(self, vals):
- if 'content' in vals and vals.get('forum_id'):
- vals['content'] = self._update_content(vals['content'], vals['forum_id'])
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if 'content' in vals and vals.get('forum_id'):
+ vals['content'] = self._update_content(vals['content'], vals['forum_id'])
- post = super(Post, self.with_context(mail_create_nolog=True)).create(vals)
- # deleted or closed questions
- if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active is False):
- raise UserError(_('Posting answer on a [Deleted] or [Closed] question is not possible.'))
- # karma-based access
- if not post.parent_id and not post.can_ask:
- raise AccessError(_('%d karma required to create a new question.', post.forum_id.karma_ask))
- elif post.parent_id and not post.can_answer:
- raise AccessError(_('%d karma required to answer a question.', post.forum_id.karma_answer))
- if not post.parent_id and not post.can_post:
- post.sudo().state = 'pending'
+ posts = super(Post, self.with_context(mail_create_nolog=True)).create(vals_list)
- # add karma for posting new questions
- if not post.parent_id and post.state == 'active':
- self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
- post.post_notification()
- return post
+ for post in posts:
+ # deleted or closed questions
+ if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active is False):
+ raise UserError(_('Posting answer on a [Deleted] or [Closed] question is not possible.'))
+ # karma-based access
+ if not post.parent_id and not post.can_ask:
+ raise AccessError(_('%d karma required to create a new question.', post.forum_id.karma_ask))
+ elif post.parent_id and not post.can_answer:
+ raise AccessError(_('%d karma required to answer a question.', post.forum_id.karma_answer))
+ if not post.parent_id and not post.can_post:
+ post.sudo().state = 'pending'
+
+ # add karma for posting new questions
+ if not post.parent_id and post.state == 'active':
+ self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
+ posts.post_notification()
+ return posts
@api.model
def _get_mail_message_access(self, res_ids, operation, model_name=None):
@@ -1077,20 +1080,22 @@ class Vote(models.Model):
}
return _karma_upd[old_vote][new_vote]
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
# can't modify owner of a vote
if not self.env.is_admin():
- vals.pop('user_id', None)
+ for vals in vals_list:
+ vals.pop('user_id', None)
- vote = super(Vote, self).create(vals)
+ votes = super(Vote, self).create(vals_list)
- vote._check_general_rights()
- vote._check_karma_rights(vote.vote == '1')
+ for vote in votes:
+ vote._check_general_rights()
+ vote._check_karma_rights(vote.vote == '1')
- # karma update
- vote._vote_update_karma('0', vote.vote)
- return vote
+ # karma update
+ vote._vote_update_karma('0', vote.vote)
+ return votes
def write(self, values):
# can't modify owner of a vote
@@ -1160,9 +1165,10 @@ class Tags(models.Model):
for tag in self:
tag.posts_count = len(tag.post_ids)
- @api.model
- def create(self, vals):
- forum = self.env['forum.forum'].browse(vals.get('forum_id'))
- if self.env.user.karma < forum.karma_tag_create:
- raise AccessError(_('%d karma required to create a new Tag.', forum.karma_tag_create))
- return super(Tags, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ forum = self.env['forum.forum'].browse(vals.get('forum_id'))
+ if self.env.user.karma < forum.karma_tag_create:
+ raise AccessError(_('%d karma required to create a new Tag.', forum.karma_tag_create))
+ return super(Tags, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(vals_list)
diff --git a/addons/website_sale/models/product_misc.py b/addons/website_sale/models/product_misc.py
index c747d4b61c0..339cb7896d2 100644
--- a/addons/website_sale/models/product_misc.py
+++ b/addons/website_sale/models/product_misc.py
@@ -44,16 +44,19 @@ class ProductPricelist(models.Model):
website = self.env['website']
website._get_pl_partner_order.clear_cache(website)
- @api.model
- def create(self, data):
- if data.get('company_id') and not data.get('website_id'):
- # l10n modules install will change the company currency, creating a
- # pricelist for that currency. Do not use user's company in that
- # case as module install are done with OdooBot (company 1)
- self = self.with_context(default_company_id=data['company_id'])
- res = super(ProductPricelist, self).create(data)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('company_id') and not vals.get('website_id'):
+ # l10n modules install will change the company currency, creating a
+ # pricelist for that currency. Do not use user's company in that
+ # case as module install are done with OdooBot (company 1)
+ # YTI FIXME: The fix is not at the correct place
+ # It be set when we actually create the pricelist
+ self = self.with_context(default_company_id=vals['company_id'])
+ pricelists = super().create(vals_list)
self.clear_cache()
- return res
+ return pricelists
def write(self, data):
res = super(ProductPricelist, self).write(data)
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index 158a3fa2b36..80d4cf6fca5 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -447,23 +447,26 @@ class Channel(models.Model):
""" % {'table_name': self._table}
self.env.cr.execute(query)
- @api.model
- def create(self, vals):
- # Ensure creator is member of its channel it is easier for him to manage it (unless it is odoobot)
- if not vals.get('channel_partner_ids') and not self.env.is_superuser():
- vals['channel_partner_ids'] = [(0, 0, {
- 'partner_id': self.env.user.partner_id.id
- })]
- if not is_html_empty(vals.get('description')) and is_html_empty(vals.get('description_short')):
- vals['description_short'] = vals['description']
- channel = super(Channel, self.with_context(mail_create_nosubscribe=True)).create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ # Ensure creator is member of its channel it is easier for him to manage it (unless it is odoobot)
+ if not vals.get('channel_partner_ids') and not self.env.is_superuser():
+ vals['channel_partner_ids'] = [(0, 0, {
+ 'partner_id': self.env.user.partner_id.id
+ })]
+ if not is_html_empty(vals.get('description')) and is_html_empty(vals.get('description_short')):
+ vals['description_short'] = vals['description']
- if channel.user_id:
- channel._action_add_members(channel.user_id.partner_id)
- if 'enroll_group_ids' in vals:
- channel._add_groups_members()
+ channels = super(Channel, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
- return channel
+ for channel in channels:
+ if channel.user_id:
+ channel._action_add_members(channel.user_id.partner_id)
+ if channel.enroll_group_ids:
+ channel._add_groups_members()
+
+ return channels
def write(self, vals):
# If description_short wasn't manually modified, there is an implicit link between this field and description.
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 33eb7933f9a..a62953d6090 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -546,38 +546,41 @@ class Slide(models.Model):
# ORM Overrides
# ---------------------------------------------------------
- @api.model
- def create(self, values):
- # Do not publish slide if user has not publisher rights
- channel = self.env['slide.channel'].browse(values['channel_id'])
- if not channel.can_publish:
- # 'website_published' is handled by mixin
- values['date_published'] = False
+ @api.model_create_multi
+ def create(self, vals_list):
+ channel_ids = [vals['channel_id'] for vals in vals_list]
+ can_publish_channel_ids = self.env['slide.channel'].browse(channel_ids).filtered(lambda c: c.can_publish).ids
+ for vals in vals_list:
+ # Do not publish slide if user has not publisher rights
+ if vals['channel_id'] not in can_publish_channel_ids:
+ # 'website_published' is handled by mixin
+ vals['date_published'] = False
- if values.get('is_category'):
- values['is_preview'] = True
- values['is_published'] = True
- if values.get('is_published') and not values.get('date_published'):
- values['date_published'] = datetime.datetime.now()
+ if vals.get('is_category'):
+ vals['is_preview'] = True
+ vals['is_published'] = True
+ if vals.get('is_published') and not vals.get('date_published'):
+ vals['date_published'] = datetime.datetime.now()
- slide = super(Slide, self).create(values)
+ slides = super().create(vals_list)
- # avoid fetching external metadata when installing the module (i.e. for demo data)
- # we also support a context key if you don't want to fetch the metadata when creating a slide
- if any(values.get(url_param) for url_param in ['url', 'video_url', 'document_google_url', 'image_google_url']) \
- and not self.env.context.get('install_mode') \
- and not self.env.context.get('website_slides_skip_fetch_metadata'):
- slide_metadata, _error = slide._fetch_external_metadata()
- if slide_metadata:
- # only update keys that are not set in the incoming values
- slide.update({key: value for key, value in slide_metadata.items() if key not in values.keys()})
+ for slide, vals in zip(slides, vals_list):
+ # avoid fetching external metadata when installing the module (i.e. for demo data)
+ # we also support a context key if you don't want to fetch the metadata when creating a slide
+ if any(vals.get(url_param) for url_param in ['url', 'video_url', 'document_google_url', 'image_google_url']) \
+ and not self.env.context.get('install_mode') \
+ and not self.env.context.get('website_slides_skip_fetch_metadata'):
+ slide_metadata, _error = slide._fetch_external_metadata()
+ if slide_metadata:
+ # only update keys that are not set in the incoming vals
+ slide.update({key: value for key, value in slide_metadata.items() if key not in vals.keys()})
- if not 'completion_time' not in values:
- slide._on_change_document_binary_content()
+ if not 'completion_time' not in vals:
+ slide._on_change_document_binary_content()
- if slide.is_published and not slide.is_category:
- slide._post_publication()
- return slide
+ if slide.is_published and not slide.is_category:
+ slide._post_publication()
+ return slides
def write(self, values):
if values.get('is_category'):
diff --git a/addons/website_slides_survey/models/slide_slide.py b/addons/website_slides_survey/models/slide_slide.py
index 882c176158b..5036882c396 100644
--- a/addons/website_slides_survey/models/slide_slide.py
+++ b/addons/website_slides_survey/models/slide_slide.py
@@ -64,14 +64,13 @@ class Slide(models.Model):
if slide.slide_category == 'certification':
slide.slide_type = 'certification'
- @api.model
- def create(self, values):
- rec = super(Slide, self).create(values)
- if rec.survey_id:
- rec.slide_category = 'certification'
- if 'survey_id' in values:
- rec._ensure_challenge_category()
- return rec
+ @api.model_create_multi
+ def create(self, vals_list):
+ slides = super().create(vals_list)
+ slides_with_survey = slides.filtered('survey_id')
+ slides_with_survey.slide_category = 'certification'
+ slides_with_survey._ensure_challenge_category()
+ return slides
def write(self, values):
old_surveys = self.mapped('survey_id')
@@ -94,7 +93,7 @@ class Slide(models.Model):
old_certification_challenges = old_surveys.mapped('certification_badge_id').challenge_ids
old_certification_challenges.write({'challenge_category': 'certification'})
if not unlink:
- certification_challenges = self.mapped('survey_id').mapped('certification_badge_id').challenge_ids
+ certification_challenges = self.survey_id.certification_badge_id.challenge_ids
certification_challenges.write({'challenge_category': 'slides'})
def _generate_certification_url(self):
diff --git a/addons/website_twitter/models/res_config_settings.py b/addons/website_twitter/models/res_config_settings.py
index 14b6ec5acd7..e22becc304a 100644
--- a/addons/website_twitter/models/res_config_settings.py
+++ b/addons/website_twitter/models/res_config_settings.py
@@ -61,12 +61,13 @@ class ResConfigSettings(models.TransientModel):
_logger.info('Please double-check your Twitter API Key and Secret!', exc_info=True)
raise UserError(_('Twitter authorization error! Please double-check your Twitter API Key and Secret!'))
- @api.model
- def create(self, vals):
- TwitterConfig = super(ResConfigSettings, self).create(vals)
- if vals.get('twitter_api_key') or vals.get('twitter_api_secret') or vals.get('twitter_screen_name'):
- TwitterConfig._check_twitter_authorization()
- return TwitterConfig
+ @api.model_create_multi
+ def create(self, vals_list):
+ configs = super().create(vals_list)
+ for config in configs:
+ if config.twitter_api_key or config.twitter_api_secret or config.twitter_screen_name:
+ config._check_twitter_authorization()
+ return configs
def write(self, vals):
TwitterConfig = super(ResConfigSettings, self).write(vals)
diff --git a/odoo/addons/base/models/ir_attachment.py b/odoo/addons/base/models/ir_attachment.py
index 4a16a78de9f..0b036ddd7e2 100644
--- a/odoo/addons/base/models/ir_attachment.py
+++ b/odoo/addons/base/models/ir_attachment.py
@@ -621,7 +621,7 @@ class IrAttachment(models.Model):
for record_tuple in record_tuple_set:
(res_model, res_id) = record_tuple
self.check('create', values={'res_model':res_model, 'res_id':res_id})
- return super(IrAttachment, self).create(vals_list)
+ return super().create(vals_list)
def _post_add_create(self):
pass
diff --git a/odoo/addons/base/models/ir_cron.py b/odoo/addons/base/models/ir_cron.py
index 864e38a0723..6e2f8de7f26 100644
--- a/odoo/addons/base/models/ir_cron.py
+++ b/odoo/addons/base/models/ir_cron.py
@@ -65,12 +65,13 @@ class ir_cron(models.Model):
lastcall = fields.Datetime(string='Last Execution Date', help="Previous time the cron ran successfully, provided to the job through the context on the `lastcall` key")
priority = fields.Integer(default=5, help='The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.')
- @api.model
- def create(self, values):
- values['usage'] = 'ir_cron'
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ vals['usage'] = 'ir_cron'
if os.getenv('ODOO_NOTIFY_CRON_CHANGES'):
self._cr.postcommit.add(self._notifydb)
- return super(ir_cron, self).create(values)
+ return super().create(vals_list)
@api.model
def default_get(self, fields_list):
diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py
index 50e30fcb8e2..75c2502ac72 100644
--- a/odoo/addons/base/models/ir_module.py
+++ b/odoo/addons/base/models/ir_module.py
@@ -724,18 +724,18 @@ class Module(models.Model):
'to_buy': False
}
- @api.model
- def create(self, vals):
- new = super(Module, self).create(vals)
- module_metadata = {
- 'name': 'module_%s' % vals['name'],
+ @api.model_create_multi
+ def create(self, vals_list):
+ modules = super().create(vals_list)
+ module_metadata_list = [{
+ 'name': 'module_%s' % module.name,
'model': 'ir.module.module',
'module': 'base',
- 'res_id': new.id,
+ 'res_id': module.id,
'noupdate': True,
- }
- self.env['ir.model.data'].create(module_metadata)
- return new
+ } for module in modules]
+ self.env['ir.model.data'].create(module_metadata_list)
+ return modules
# update the list of available packages
@assert_log_admin_access
diff --git a/odoo/addons/base/models/ir_sequence.py b/odoo/addons/base/models/ir_sequence.py
index b139ee9dd89..7beec5cfed9 100644
--- a/odoo/addons/base/models/ir_sequence.py
+++ b/odoo/addons/base/models/ir_sequence.py
@@ -150,14 +150,15 @@ class IrSequence(models.Model):
use_date_range = fields.Boolean(string='Use subsequences per date_range')
date_range_ids = fields.One2many('ir.sequence.date_range', 'sequence_id', string='Subsequences')
- @api.model
- def create(self, values):
+ @api.model_create_multi
+ def create(self, vals_list):
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
"""
- seq = super(IrSequence, self).create(values)
- if values.get('implementation', 'standard') == 'standard':
- _create_sequence(self._cr, "ir_sequence_%03d" % seq.id, values.get('number_increment', 1), values.get('number_next', 1))
- return seq
+ seqs = super().create(vals_list)
+ for seq in seqs:
+ if seq.implementation == 'standard':
+ _create_sequence(self._cr, "ir_sequence_%03d" % seq.id, seq.number_increment or 1, seq.number_next or 1)
+ return seqs
def unlink(self):
_drop_sequences(self._cr, ["ir_sequence_%03d" % x.id for x in self])
@@ -356,15 +357,16 @@ class IrSequenceDateRange(models.Model):
for seq in self:
_alter_sequence(self._cr, "ir_sequence_%03d_%03d" % (seq.sequence_id.id, seq.id), number_increment=number_increment, number_next=number_next)
- @api.model
- def create(self, values):
+ @api.model_create_multi
+ def create(self, vals_list):
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
"""
- seq = super(IrSequenceDateRange, self).create(values)
- main_seq = seq.sequence_id
- if main_seq.implementation == 'standard':
- _create_sequence(self._cr, "ir_sequence_%03d_%03d" % (main_seq.id, seq.id), main_seq.number_increment, values.get('number_next_actual', 1))
- return seq
+ seqs = super().create(vals_list)
+ for seq in seqs:
+ main_seq = seq.sequence_id
+ if main_seq.implementation == 'standard':
+ _create_sequence(self._cr, "ir_sequence_%03d_%03d" % (main_seq.id, seq.id), main_seq.number_increment, seq.number_next_actual or 1)
+ return seqs
def unlink(self):
_drop_sequences(self._cr, ["ir_sequence_%03d_%03d" % (x.sequence_id.id, x.id) for x in self])
diff --git a/odoo/addons/base/models/res_company.py b/odoo/addons/base/models/res_company.py
index 3ee845aad83..73774f81da4 100644
--- a/odoo/addons/base/models/res_company.py
+++ b/odoo/addons/base/models/res_company.py
@@ -198,37 +198,51 @@ class Company(models.Model):
def cache_restart(self):
self.clear_caches()
- @api.model
- def create(self, vals):
- if not vals.get('favicon'):
- vals['favicon'] = self._get_default_favicon()
- if not vals.get('name') or vals.get('partner_id'):
- self.clear_caches()
- return super(Company, self).create(vals)
- partner = self.env['res.partner'].create({
- 'name': vals['name'],
- 'is_company': True,
- 'image_1920': vals.get('logo'),
- 'email': vals.get('email'),
- 'phone': vals.get('phone'),
- 'website': vals.get('website'),
- 'vat': vals.get('vat'),
- 'country_id': vals.get('country_id'),
- })
- # compute stored fields, for example address dependent fields
- partner.flush()
- vals['partner_id'] = partner.id
- self.clear_caches()
- company = super(Company, self).create(vals)
- # The write is made on the user to set it automatically in the multi company group.
- self.env.user.write({'company_ids': [Command.link(company.id)]})
+ @api.model_create_multi
+ def create(self, vals_list):
+ # add default favicon
+ for vals in vals_list:
+ if not vals.get('favicon'):
+ vals['favicon'] = self._get_default_favicon()
- # Make sure that the selected currency is enabled
- if vals.get('currency_id'):
- currency = self.env['res.currency'].browse(vals['currency_id'])
- if not currency.active:
- currency.write({'active': True})
- return company
+ # create missing partners
+ no_partner_vals_list = [
+ vals
+ for vals in vals_list
+ if vals.get('name') and not vals.get('partner_id')
+ ]
+ if no_partner_vals_list:
+ partners = self.env['res.partner'].create([
+ {
+ 'name': vals['name'],
+ 'is_company': True,
+ 'image_1920': vals.get('logo'),
+ 'email': vals.get('email'),
+ 'phone': vals.get('phone'),
+ 'website': vals.get('website'),
+ 'vat': vals.get('vat'),
+ 'country_id': vals.get('country_id'),
+ }
+ for vals in no_partner_vals_list
+ ])
+ # compute stored fields, for example address dependent fields
+ partners.flush()
+ for vals, partner in zip(no_partner_vals_list, partners):
+ vals['partner_id'] = partner.id
+
+ self.clear_caches()
+ companies = super().create(vals_list)
+
+ # The write is made on the user to set it automatically in the multi company group.
+ if companies:
+ self.env.user.write({
+ 'company_ids': [Command.link(company.id) for company in companies],
+ })
+
+ # Make sure that the selected currencies are enabled
+ companies.currency_id.sudo().filtered(lambda c: not c.active).active = True
+
+ return companies
def write(self, values):
self.clear_caches()
diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py
index 20e2cf0f05f..a4f1cb2b488 100644
--- a/odoo/addons/base/models/res_config.py
+++ b/odoo/addons/base/models/res_config.py
@@ -765,34 +765,35 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin)
return RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
return UserError(msg % values)
- @api.model
- def create(self, values):
+ @api.model_create_multi
+ def create(self, vals_list):
# Optimisation: saving a res.config.settings even without changing any
# values will trigger the write of all related values. This in turn may
# trigger chain of further recomputation. To avoid it, delete values
# that were not changed.
- for field in self._fields.values():
- if not (field.name in values and field.related and not field.readonly):
- continue
- # we write on a related field like
- # qr_code = fields.Boolean(related='company_id.qr_code', readonly=False)
- fname0, *fnames = field.related.split(".")
- if fname0 not in values:
- continue
+ for vals in vals_list:
+ for field in self._fields.values():
+ if not (field.name in vals and field.related and not field.readonly):
+ continue
+ # we write on a related field like
+ # qr_code = fields.Boolean(related='company_id.qr_code', readonly=False)
+ fname0, *fnames = field.related.split(".")
+ if fname0 not in vals:
+ continue
- # determine the current value
- field0 = self._fields[fname0]
- old_value = field0.convert_to_record(
- field0.convert_to_cache(values[fname0], self), self)
- for fname in fnames:
- old_value = next(iter(old_value), old_value)[fname]
+ # determine the current value
+ field0 = self._fields[fname0]
+ old_value = field0.convert_to_record(
+ field0.convert_to_cache(vals[fname0], self), self)
+ for fname in fnames:
+ old_value = next(iter(old_value), old_value)[fname]
- # determine the new value
- new_value = field.convert_to_record(
- field.convert_to_cache(values[field.name], self), self)
+ # determine the new value
+ new_value = field.convert_to_record(
+ field.convert_to_cache(vals[field.name], self), self)
- # drop if the value is the same
- if old_value == new_value:
- values.pop(field.name)
+ # drop if the value is the same
+ if old_value == new_value:
+ vals.pop(field.name)
- return super(ResConfigSettings, self).create(values)
+ return super().create(vals_list)
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index 1886acdd1a2..646dca8063b 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -1162,13 +1162,13 @@ class UsersImplied(models.Model):
class GroupsView(models.Model):
_inherit = 'res.groups'
- @api.model
- def create(self, values):
- user = super(GroupsView, self).create(values)
+ @api.model_create_multi
+ def create(self, vals_list):
+ groups = super().create(vals_list)
self._update_user_groups_view()
# actions.get_bindings() depends on action records
self.env['ir.actions.actions'].clear_caches()
- return user
+ return groups
def write(self, values):
# determine which values the "user groups view" depends on
diff --git a/odoo/addons/base/tests/common.py b/odoo/addons/base/tests/common.py
index 7bd8a2d5a75..0fd845209db 100644
--- a/odoo/addons/base/tests/common.py
+++ b/odoo/addons/base/tests/common.py
@@ -8,27 +8,6 @@ from odoo.tests.common import TransactionCase, HttpCase
from odoo import Command
-class PerformanceCommon(TransactionCase):
-
- def assertModelCreateMulti(self, model_name, vals_list=None):
- """Ensures model_name records can be created in batch.
-
- :param str model_name: name of the model on which batch record creation should be verified
- :param list vals_list: example list of values, if needed
- """
- vals_list = vals_list or [dict(), dict()]
- if not isinstance(vals_list, list):
- raise ValueError("Invalid arguments for assertModelCreateMulti")
- assert len(vals_list) > 1, "Batch creation cannot be tested with only one value."
- def patched_create(self, values_list):
- if len(values_list) != len(vals_list):
- raise ValueError(f"Model {model_name} doesn't support batch records creation.")
- # Do not create anything
- return self
- with patch('odoo.models.BaseModel.create', patched_create):
- self.env[model_name].create(vals_list)
-
-
class TransactionCaseWithUserDemo(TransactionCase):
def setUp(self):
diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
index 3f669a3135b..6ee0c60acad 100644
--- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
+++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py
@@ -738,10 +738,10 @@ class TestAssetsBundleWithIRAMock(FileTouchable):
origin_create = IrAttachment.create
origin_unlink = IrAttachment.unlink
- @api.model
- def create(self, vals):
- counter.update(['create'])
- return origin_create(self, vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ counter.update(['create'] * len(vals_list))
+ return origin_create(self, vals_list)
def unlink(self):
counter.update(['unlink'])
diff --git a/odoo/api.py b/odoo/api.py
index b623861813e..b0fca49f9da 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -401,6 +401,7 @@ def model_create_single(method):
record = model.create(vals)
records = model.create([vals, ...])
"""
+ _create_logger.warning("The model %s is not overriding the create method in batch", method.__module__)
wrapper = decorate(method, _model_create_single)
wrapper._api = 'model_create'
return wrapper