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