diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index 9154260b351..9607d1af911 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -1497,11 +1497,11 @@ class AccountTaxRepartitionLine(models.Model): raise ValidationError(_("The tax named %s has already been used, you cannot add nor delete its tax repartition lines.", tax.name)) return super().create(vals) - def unlink(self): + @api.ondelete(at_uninstall=False) + def _check_tax_use(self): for repartition_line in self: if repartition_line.tax_id.is_used: raise ValidationError(_("The tax named %s has already been used, you cannot add nor delete its tax repartition lines.", repartition_line.tax_id.name)) - return super().unlink() @api.depends('company_id.multi_vat_foreign_country_ids', 'company_id.account_fiscal_country_id') def _compute_tag_ids_domain(self): diff --git a/addons/account_qr_code_sepa/models/res_bank.py b/addons/account_qr_code_sepa/models/res_bank.py index 638a83b7d00..2eef919c1d3 100644 --- a/addons/account_qr_code_sepa/models/res_bank.py +++ b/addons/account_qr_code_sepa/models/res_bank.py @@ -47,7 +47,7 @@ class ResPartnerBank(models.Model): sepa_iban_codes = {code for code in sepa_country_codes if code not in non_iban_codes} error_messages = [] if currency.name != 'EUR': - error_messages.append(_("Can't generate a SEPA QR Code with the %s currency." % currency.name)) + error_messages.append(_("Can't generate a SEPA QR Code with the %s currency.", currency.name)) if self.acc_type != 'iban': error_messages.append(_("Can't generate a SEPA QR code if the account type isn't IBAN.")) if not (self.sanitized_acc_number and self.sanitized_acc_number[:2] in sepa_iban_codes): diff --git a/addons/event_booth_sale/models/sale_order.py b/addons/event_booth_sale/models/sale_order.py index 94e6ef03ab9..4930cd6665f 100644 --- a/addons/event_booth_sale/models/sale_order.py +++ b/addons/event_booth_sale/models/sale_order.py @@ -28,8 +28,8 @@ class SaleOrder(models.Model): continue so_lines_missing_booth = so.order_line.filtered(lambda line: line.product_type == 'event_booth' and not line.event_booth_pending_ids) if so_lines_missing_booth: - so_lines_descriptions = "".join([f"- {so_line_description.name} \n" for so_line_description in so_lines_missing_booth]) - raise ValidationError(_("Please make sure all your event-booth related lines are configured before confirming this order: \n%s" % so_lines_descriptions)) + so_lines_descriptions = "".join(f"\n- {so_line_description.name}" for so_line_description in so_lines_missing_booth) + raise ValidationError(_("Please make sure all your event-booth related lines are configured before confirming this order:%s", so_lines_descriptions)) so.order_line._update_event_booths() return res diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index 19374fc6b58..5c3dd4cec8c 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -27,8 +27,8 @@ class SaleOrder(models.Model): continue so_lines_missing_events = so.order_line.filtered(lambda line: line.product_type == 'event' and not line.event_id) if so_lines_missing_events: - so_lines_descriptions = "".join([f"- {so_line_description.name} \n" for so_line_description in so_lines_missing_events]) - raise ValidationError(_("Please make sure all your event related lines are configured before confirming this order: \n%s" % so_lines_descriptions)) + so_lines_descriptions = "".join(f"\n- {so_line_description.name}" for so_line_description in so_lines_missing_events) + raise ValidationError(_("Please make sure all your event related lines are configured before confirming this order:%s", so_lines_descriptions)) # Initialize registrations so.order_line._init_registrations() if len(self) == 1: diff --git a/addons/hr_expense/models/account_move.py b/addons/hr_expense/models/account_move.py index 284199b8428..c043d9d1724 100644 --- a/addons/hr_expense/models/account_move.py +++ b/addons/hr_expense/models/account_move.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, fields, api, _ +from odoo.api import ondelete from odoo.exceptions import UserError from odoo.tools.misc import frozendict @@ -58,8 +59,7 @@ class AccountMove(models.Model): # else, when restarting the expense flow we get duplicate issue on vendor.bill return super()._reverse_moves(default_values_list=default_values_list, cancel=cancel) - def unlink(self): - # EXTENDS account + @ondelete(at_uninstall=True) + def _must_delete_all_expense_entries(self): if self.expense_sheet_id and self.expense_sheet_id.account_move_ids - self: # If not all the payments are to be deleted raise UserError(_("You cannot delete only some entries linked to an expense report. All entries must be deleted at the same time.")) - return super().unlink() diff --git a/addons/hr_expense/models/account_payment.py b/addons/hr_expense/models/account_payment.py index 7c890e1a080..b5eb8b73a6c 100644 --- a/addons/hr_expense/models/account_payment.py +++ b/addons/hr_expense/models/account_payment.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, _ +from odoo.api import ondelete from odoo.exceptions import UserError @@ -41,8 +42,7 @@ class AccountPayment(models.Model): return _("Payment created for: %s", self.move_id.expense_sheet_id._get_html_link()) return super()._creation_message() - def unlink(self): - # EXTENDS account + @ondelete(at_uninstall=True) + def _must_delete_all_expense_payments(self): if self.expense_sheet_id and self.expense_sheet_id.account_move_ids.payment_ids - self: # If not all the payments are to be deleted raise UserError(_("You cannot delete only some payments linked to an expense report. All payments must be deleted at the same time.")) - return super().unlink() diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 223a17f0e60..360d1c36d6d 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -710,24 +710,27 @@ class HolidaysRequest(models.Model): conflicting_holidays_strings = [] if holidays_only_have_uid: for conflicting_holiday_data in conflicting_holidays_list: - conflicting_holidays_string = _('From %(date_from)s To %(date_to)s - %(state)s', + conflicting_holidays_string = _('from %(date_from)s to %(date_to)s - %(state)s', date_from=conflicting_holiday_data['date_from'], date_to=conflicting_holiday_data['date_to'], state=conflicting_holiday_data['state']) conflicting_holidays_strings.append(conflicting_holidays_string) - raise ValidationError(_("You’ve already booked time off which overlaps with this period:" + - "\n".join(conflicting_holidays_strings) + - "Attempting to double-book your time off won't magically make your vacation 2x better!")) + raise ValidationError(_("""\ +You've already booked time off which overlaps with this period: +%s +Attempting to double-book your time off won't magically make your vacation 2x better! +""", + "\n".join(conflicting_holidays_strings))) for conflicting_holiday_data in conflicting_holidays_list: - conflicting_holidays_string = _('%(employee_name)s - From %(date_from)s To %(date_to)s - %(state)s', + conflicting_holidays_string = "\n" + _('%(employee_name)s - from %(date_from)s to %(date_to)s - %(state)s', employee_name=conflicting_holiday_data['employee_name'], date_from=conflicting_holiday_data['date_from'], date_to=conflicting_holiday_data['date_to'], state=conflicting_holiday_data['state']) conflicting_holidays_strings.append(conflicting_holidays_string) - conflicting_employees = set(employee_ids) - set(conflicting_holidays.employee_id.ids) - raise ValidationError(_("An employee already booked time off which overlaps with this period:" + - "\n".join(conflicting_holidays_strings))) + raise ValidationError(_( + "An employee already booked time off which overlaps with this period:%s", + "".join(conflicting_holidays_strings))) @api.constrains('date_from', 'date_to', 'employee_id') def _check_date_state(self): diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index b3e8e03f64c..fafc55a3aab 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -169,10 +169,10 @@ class HolidaysAllocation(models.Model): if allocation.env.context.get('is_employee_allocation'): if allocation.holiday_status_id: allocation_duration = allocation.number_of_days_display if allocation.type_request_unit != 'hour' else allocation.number_of_hours_display - title = _(" %s Allocation Request ( %s %s)" % ( + title = _("%s allocation request (%s %s)", allocation.holiday_status_id.name, allocation_duration, - allocation.type_request_unit)) + allocation.type_request_unit) else: title = _("Allocation Request") allocation.name = title diff --git a/addons/mail/models/discuss/discuss_channel.py b/addons/mail/models/discuss/discuss_channel.py index b463c2951d0..6f7c6573721 100644 --- a/addons/mail/models/discuss/discuss_channel.py +++ b/addons/mail/models/discuss/discuss_channel.py @@ -4,7 +4,6 @@ import base64 import logging from collections import defaultdict from hashlib import sha512 -from markupsafe import Markup from secrets import choice from markupsafe import Markup @@ -690,11 +689,9 @@ class Channel(models.Model): ''' notification = Markup(notification_text) % { - 'user_pinned_a_message_to_this_channel': _( - Markup('%(user_name)s pinned a message to this channel.') % { - 'user_name': self.env.user.display_name, - 'message_id': message_id - } + 'user_pinned_a_message_to_this_channel': Markup('%s') % ( + message_id, + _('%(user_name)s pinned a message to this channel.', user_name=self.env.user.display_name), ), 'see_all_pins': _('See all pinned messages.'), } diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py index 7e62142b6a3..7de6e67412c 100644 --- a/addons/mail/models/mail_alias.py +++ b/addons/mail/models/mail_alias.py @@ -154,8 +154,8 @@ class Alias(models.Model): company = owner[owner._mail_get_company_field()] if company and company.alias_domain_id != alias.alias_domain_id and alias.alias_domain_id.company_ids: raise ValidationError(_( - "We could not create alias %(alias_name)s because domain " + - "%(alias_domain_name)s belongs to company %(alias_company_names)s " + + "We could not create alias %(alias_name)s because domain " + "%(alias_domain_name)s belongs to company %(alias_company_names)s " "while the owner document belongs to company %(company_name)s.", alias_company_names=','.join(alias.alias_domain_id.company_ids.mapped('name')), alias_domain_name=alias.alias_domain_id.name, @@ -166,8 +166,8 @@ class Alias(models.Model): company = target[target._mail_get_company_field()] if company and company.alias_domain_id != alias.alias_domain_id and alias.alias_domain_id.company_ids: raise ValidationError(_( - "We could not create alias %(alias_name)s because domain " + - "%(alias_domain_name)s belongs to company %(alias_company_names)s " + + "We could not create alias %(alias_name)s because domain " + "%(alias_domain_name)s belongs to company %(alias_company_names)s " "while the target document belongs to company %(company_name)s.", alias_company_names=','.join(alias.alias_domain_id.company_ids.mapped('name')), alias_domain_name=alias.alias_domain_id.name, diff --git a/addons/payment_adyen/models/payment_transaction.py b/addons/payment_adyen/models/payment_transaction.py index 15360240671..ed4bdb4145c 100644 --- a/addons/payment_adyen/models/payment_transaction.py +++ b/addons/payment_adyen/models/payment_transaction.py @@ -429,22 +429,17 @@ class PaymentTransaction(models.Model): _("An error occurred during the processing of your payment. Please try again.") ) elif event_code == 'CANCELLATION': - failed_void_msg = "The void of the transaction with reference %s failed." - logger_msg = failed_void_msg + " reason: %s" - _logger.warning(logger_msg, self.reference, refusal_reason) + _logger.warning("The void of the transaction with reference %s failed. reason: %s", self.reference, refusal_reason) if self.source_transaction_id: # child tx => The event can't be retried. - self._set_error(_(failed_void_msg, self.reference)) + self._set_error(_("The void of the transaction with reference %s failed.", self.reference)) else: # source tx with failed void stays in its state, could be voided again - self._log_message_on_linked_documents(_(failed_void_msg, self.reference)) + self._log_message_on_linked_documents(_("The void of the transaction with reference %s failed.", self.reference)) else: # 'CAPTURE', 'CAPTURE_FAILED' - failed_capture_msg = "The capture of the transaction with reference %s failed." - logger_msg = failed_capture_msg + " reason: %s" - _logger.warning(logger_msg, self.reference, refusal_reason) + _logger.warning("The capture of the transaction with reference %s failed. reason: %s", self.reference, refusal_reason) if self.source_transaction_id: # child_tx => The event can't be retried. - self._set_error(_(failed_capture_msg, self.reference)) + self._set_error(_("The capture of the transaction with reference %s failed.", self.reference)) else: # source tx with failed capture stays in its state, could be captured again - self._log_message_on_linked_documents(_(failed_capture_msg, self.reference)) - elif payment_state in const.RESULT_CODES_MAPPING['refused']: + self._log_message_on_linked_documents(_("The capture of the transaction with reference %s failed.", self.reference)) _logger.warning( "the transaction with reference %s was refused. reason: %s", self.reference, refusal_reason diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 1a8e6b8ea53..2a897dd266e 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -216,28 +216,28 @@ class Location(models.Model): " Please unreserve the products first." )) if 'active' in values: - if values['active'] == False: + if not values['active']: for location in self: - warehouses = self.env['stock.warehouse'].search([('active', '=', True), '|', ('lot_stock_id', '=', location.id), ('view_location_id', '=', location.id)]) + warehouses = self.env['stock.warehouse'].search([('active', '=', True), '|', ('lot_stock_id', '=', location.id), ('view_location_id', '=', location.id)], limit=1) if warehouses: raise UserError(_( "You cannot archive the location %s as it is used by your warehouse %s", - location.display_name, warehouses[0].display_name)) + location.display_name, warehouses.display_name)) if not self.env.context.get('do_not_check_quant'): children_location = self.env['stock.location'].with_context(active_test=False).search([('id', 'child_of', self.ids)]) internal_children_locations = children_location.filtered(lambda l: l.usage == 'internal') children_quants = self.env['stock.quant'].search(['&', '|', ('quantity', '!=', 0), ('reserved_quantity', '!=', 0), ('location_id', 'in', internal_children_locations.ids)]) - if children_quants and values['active'] == False: + if children_quants and not values['active']: raise UserError(_( - 'You still have some product in locations %s' + "You can't disable locations %s because they still contain products.", ', '.join(children_quants.mapped('location_id.display_name')))) else: super(Location, children_location - self).with_context(do_not_check_quant=True).write({ 'active': values['active'], }) - res = super(Location, self).write(values) + res = super().write(values) self.invalidate_model(['warehouse_id']) return res diff --git a/odoo/addons/test_lint/tests/_odoo_checker_gettext.py b/odoo/addons/test_lint/tests/_odoo_checker_gettext.py index f702b0abcb7..0b59b9d2e83 100644 --- a/odoo/addons/test_lint/tests/_odoo_checker_gettext.py +++ b/odoo/addons/test_lint/tests/_odoo_checker_gettext.py @@ -1,11 +1,20 @@ import astroid +import pylint.interfaces from pylint.checkers import BaseChecker + try: from pylint.checkers.utils import only_required_for_messages except ImportError: from pylint.checkers.utils import check_messages as only_required_for_messages +def parse_version(s): + # can't use odoo.tools.parse_version because pythonpath is screwed from + # inside pylint on runbot + return [s.rjust(3, '0') for s in s.split('.')] + class OdooBaseChecker(BaseChecker): + if parse_version(pylint.__version__) < parse_version('2.14.0'): + __implements__ = pylint.interfaces.IAstroidChecker name = 'odoo' msgs = { diff --git a/odoo/addons/test_lint/tests/_odoo_checker_unlink_override.py b/odoo/addons/test_lint/tests/_odoo_checker_unlink_override.py index 9e4c75849a6..55c8a3d96cb 100644 --- a/odoo/addons/test_lint/tests/_odoo_checker_unlink_override.py +++ b/odoo/addons/test_lint/tests/_odoo_checker_unlink_override.py @@ -1,8 +1,15 @@ import astroid +import pylint.interfaces from pylint import checkers +def parse_version(s): + # can't use odoo.tools.parse_version because pythonpath is screwed from + # inside pylint on runbot + return [s.rjust(3, '0') for s in s.split('.')] class OdooBaseChecker(checkers.BaseChecker): + if parse_version(pylint.__version__) < parse_version('2.14.0'): + __implements__ = pylint.interfaces.IAstroidChecker name = 'odoo' msgs = { diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 05ef724bda9..97f82d1543c 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -426,9 +426,9 @@ class Cursor(BaseCursor): @contextmanager def _enable_table_tracking(self): + old = self._sql_table_tracking + self._sql_table_tracking = True try: - old = self._sql_table_tracking - self._sql_table_tracking = True yield finally: self._sql_table_tracking = old