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