From 22ab49e3432b9342b657b9f8f4cf2d06b083ddad Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 15 Sep 2023 16:15:28 +0200 Subject: [PATCH] [IMP] *: use file_path and file_open Replace all the calls to get_resource_path to the better file_path or directly use file_open when not needed Doing both a get_resource_path and file_open means checking twice that the file exists. Doing a simple path concatenation before a file_open is safe. If given to another method (e.g. etree.parse), calling file_path is the prefered method. Note that get_resource_path used to return False when the file does not exists while file_path/file_open raises a FileNotFoundException closes odoo/odoo#135607 Related: odoo/upgrade#5187 Related: odoo/enterprise#47475 Signed-off-by: Martin Trigaux (mat) --- addons/account_edi/tests/common.py | 14 ------- .../tests/test_indexation.py | 3 +- .../tests/test_import_module.py | 1 - .../tests/test_recruitment_process.py | 5 +-- addons/hw_drivers/connection_manager.py | 1 - addons/hw_drivers/controllers/driver.py | 1 - .../iot_handlers/drivers/DisplayDriver_L.py | 3 +- .../iot_handlers/drivers/PrinterDriver_L.py | 1 - addons/hw_drivers/tools/helpers.py | 4 +- addons/hw_posbox_homepage/controllers/main.py | 14 +++---- .../tests/common.py | 9 ++--- .../tests/test_xml_oioubl_dk.py | 6 +-- .../models/account_edi_xml_ubl_21_zatca.py | 4 +- addons/l10n_sa_edi/models/account_journal.py | 6 +-- addons/lunch/models/lunch_product_category.py | 5 +-- addons/mail/models/template_reset_mixin.py | 7 ++-- addons/mail/tests/test_mail_template.py | 10 ++--- addons/pos_self_order/models/pos_config.py | 5 ++- addons/purchase_stock/tests/common.py | 1 - addons/rating/models/rating.py | 13 ++++--- .../sale/models/onboarding_onboarding_step.py | 4 +- .../sale_product_configurator/tests/common.py | 7 ++-- addons/sms/tests/test_sms_template.py | 8 ++-- .../tests/test_base_import.py | 11 ++---- addons/test_website/tests/test_qweb.py | 9 ++--- addons/web/controllers/binary.py | 10 ++--- addons/web/controllers/webclient.py | 6 +-- addons/web/models/base_document_layout.py | 4 +- addons/web/tests/test_js.py | 12 ++++-- addons/web_editor/controllers/main.py | 13 ++++--- addons/web_editor/models/ir_qweb_fields.py | 15 ++------ addons/website/models/website.py | 8 ++-- .../models/website_configurator_feature.py | 9 ++--- .../models/event_sponsor.py | 3 +- .../controllers/webmanifest.py | 5 +-- addons/website_sale/tests/test_customize.py | 6 +-- .../website_slides/tests/test_ui_wslides.py | 8 ++-- odoo/addons/base/models/assetsbundle.py | 11 +++--- odoo/addons/base/models/ir_http.py | 1 - odoo/addons/base/models/ir_module.py | 13 +++---- odoo/addons/base/models/ir_ui_menu.py | 20 +++++----- odoo/addons/base/models/ir_ui_view.py | 37 ++++++++++--------- odoo/addons/base/tests/test_pdf.py | 5 +-- odoo/addons/base/tests/test_qweb.py | 1 - .../tests/test_assetsbundle.py | 10 ++--- .../addons/test_convert/tests/test_convert.py | 4 +- odoo/addons/test_converter/tests/test_html.py | 7 ++-- odoo/addons/test_lint/tests/test_eslint.py | 4 +- odoo/addons/test_lint/tests/test_l10n.py | 4 +- odoo/addons/test_lint/tests/test_manifests.py | 10 +++-- .../test_lint/tests/test_onchange_domains.py | 4 +- odoo/addons/test_lint/tests/test_pofile.py | 8 ++-- .../tests/test_guess_mimetypes.py | 3 +- odoo/modules/db.py | 5 ++- odoo/modules/migration.py | 15 ++++++-- odoo/modules/module.py | 19 +++++----- odoo/modules/neutralize.py | 6 ++- odoo/tools/convert.py | 7 ++-- odoo/tools/misc.py | 5 +-- odoo/tools/translate.py | 16 +++++--- 60 files changed, 222 insertions(+), 244 deletions(-) diff --git a/addons/account_edi/tests/common.py b/addons/account_edi/tests/common.py index ce8c5084cd5..93e0495b67b 100644 --- a/addons/account_edi/tests/common.py +++ b/addons/account_edi/tests/common.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.modules.module import get_module_resource from odoo.addons.account.tests.common import AccountTestInvoicingCommon from contextlib import contextmanager @@ -88,19 +87,6 @@ class AccountEdiTestCommon(AccountTestInvoicingCommon): def edi_cron(self): self.env['account.edi.document'].sudo().search([('state', 'in', ('to_send', 'to_cancel'))])._process_documents_web_services(with_commit=False) - def create_invoice_from_file(self, module_name, subfolder, filename): - file_path = get_module_resource(module_name, subfolder, filename) - file = open(file_path, 'rb').read() - - attachment = self.env['ir.attachment'].create({ - 'name': filename, - 'datas': base64.encodebytes(file), - 'res_model': 'account.move', - }) - journal_id = self.company_data['default_journal_sale'] - action_vals = journal_id.with_context(default_move_type='in_invoice').create_document_from_attachment(attachment.ids) - return self.env['account.move'].browse(action_vals['res_id']) - def assert_generated_file_equal(self, invoice, expected_values, applied_xpath=None): invoice.action_post() invoice.edi_document_ids._process_documents_web_services(with_commit=False) # synchronous are called in post, but there's no CRON in tests for asynchronous diff --git a/addons/attachment_indexation/tests/test_indexation.py b/addons/attachment_indexation/tests/test_indexation.py index 36c558cdcc1..bb7eac8038b 100644 --- a/addons/attachment_indexation/tests/test_indexation.py +++ b/addons/attachment_indexation/tests/test_indexation.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase, tagged +from odoo.tools.misc import file_open from unittest import skipIf import os @@ -17,7 +18,7 @@ class TestCaseIndexation(TransactionCase): @skipIf(PDFResourceManager is None, "pdfminer not installed") def test_attachment_pdf_indexation(self): - with open(os.path.join(directory, 'files', 'test_content.pdf'), 'rb') as file: + with file_open(os.path.join(directory, 'files', 'test_content.pdf'), 'rb') as file: pdf = file.read() text = self.env['ir.attachment']._index(pdf, 'application/pdf') self.assertEqual(text, 'TestContent!!\x0c', 'the index content should be correct') diff --git a/addons/base_import_module/tests/test_import_module.py b/addons/base_import_module/tests/test_import_module.py index 69ccb8d06b8..488267d8100 100644 --- a/addons/base_import_module/tests/test_import_module.py +++ b/addons/base_import_module/tests/test_import_module.py @@ -3,7 +3,6 @@ import base64 import json import os -import tempfile from io import BytesIO from zipfile import ZipFile diff --git a/addons/hr_recruitment/tests/test_recruitment_process.py b/addons/hr_recruitment/tests/test_recruitment_process.py index 74503b35e61..5cbaab8eef2 100644 --- a/addons/hr_recruitment/tests/test_recruitment_process.py +++ b/addons/hr_recruitment/tests/test_recruitment_process.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests import common from odoo.addons.hr.tests.common import TestHrCommon -from odoo.modules.module import get_module_resource +from odoo.tools.misc import file_open class TestRecruitmentProcess(TestHrCommon): @@ -36,7 +35,7 @@ class TestRecruitmentProcess(TestHrCommon): # An applicant is interested in the job position. So he sends a resume by email. # In Order to test process of Recruitment so giving HR officer's rights - with open(get_module_resource('hr_recruitment', 'tests', 'resume.eml'), 'rb') as request_file: + with file_open('hr_recruitment/tests/resume.eml', 'rb') as request_file: request_message = request_file.read() self.env['mail.thread'].with_user(self.res_users_hr_recruitment_officer).message_process( 'hr.applicant', request_message, custom_values={"job_id": self.job_developer.id}) diff --git a/addons/hw_drivers/connection_manager.py b/addons/hw_drivers/connection_manager.py index 24e1142bac8..00a445e3d86 100644 --- a/addons/hw_drivers/connection_manager.py +++ b/addons/hw_drivers/connection_manager.py @@ -9,7 +9,6 @@ from threading import Thread import time import urllib3 -from odoo.modules.module import get_resource_path from odoo.addons.hw_drivers.main import iot_devices, manager from odoo.addons.hw_drivers.tools import helpers diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py index 3f1c526cb66..ff838a6c09d 100755 --- a/addons/hw_drivers/controllers/driver.py +++ b/addons/hw_drivers/controllers/driver.py @@ -9,7 +9,6 @@ import subprocess import time from odoo import http, tools -from odoo.modules.module import get_resource_path from odoo.addons.hw_drivers.event_manager import event_manager from odoo.addons.hw_drivers.main import iot_devices, manager diff --git a/addons/hw_drivers/iot_handlers/drivers/DisplayDriver_L.py b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver_L.py index 4a3d917cfab..a5819aee910 100644 --- a/addons/hw_drivers/iot_handlers/drivers/DisplayDriver_L.py +++ b/addons/hw_drivers/iot_handlers/drivers/DisplayDriver_L.py @@ -18,6 +18,7 @@ from odoo.addons.hw_drivers.driver import Driver from odoo.addons.hw_drivers.event_manager import event_manager from odoo.addons.hw_drivers.main import iot_devices from odoo.addons.hw_drivers.tools import helpers +from odoo.tools.misc import file_open path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../../views')) loader = jinja2.FileSystemLoader(path) @@ -203,7 +204,7 @@ class DisplayController(http.Controller): cust_js = None interfaces = ni.interfaces() - with open(os.path.join(os.path.dirname(__file__), "../../static/src/js/worker.js")) as js: + with file_open("hw_drivers/static/src/js/worker.js") as js: cust_js = js.read() display_ifaces = [] diff --git a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver_L.py b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver_L.py index 809057ee9b9..0ee38a68a03 100644 --- a/addons/hw_drivers/iot_handlers/drivers/PrinterDriver_L.py +++ b/addons/hw_drivers/iot_handlers/drivers/PrinterDriver_L.py @@ -11,7 +11,6 @@ import os from PIL import Image, ImageOps import re import subprocess -import tempfile from uuid import getnode as get_mac from odoo import http diff --git a/addons/hw_drivers/tools/helpers.py b/addons/hw_drivers/tools/helpers.py index da5ba861568..bc20cb19c42 100644 --- a/addons/hw_drivers/tools/helpers.py +++ b/addons/hw_drivers/tools/helpers.py @@ -23,7 +23,7 @@ import secrets from odoo import _, http, service from odoo.tools.func import lazy_property -from odoo.modules.module import get_resource_path +from odoo.tools.misc import file_path _logger = logging.getLogger(__name__) @@ -368,7 +368,7 @@ def load_iot_handlers(): And execute these python drivers and interfaces """ for directory in ['interfaces', 'drivers']: - path = get_resource_path('hw_drivers', 'iot_handlers', directory) + path = file_path(f'hw_drivers/iot_handlers/{directory}') filesList = list_file_by_os(path) for file in filesList: spec = util.spec_from_file_location(file, str(Path(path).joinpath(file))) diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py index 8ed59f4cde6..e3943db114c 100644 --- a/addons/hw_posbox_homepage/controllers/main.py +++ b/addons/hw_posbox_homepage/controllers/main.py @@ -14,11 +14,11 @@ import threading from odoo import http, service from odoo.http import Response -from odoo.modules.module import get_resource_path from odoo.addons.hw_drivers.connection_manager import connection_manager from odoo.addons.hw_drivers.main import iot_devices from odoo.addons.hw_drivers.tools import helpers from odoo.addons.web.controllers.home import Home +from odoo.tools.misc import file_path _logger = logging.getLogger(__name__) @@ -112,8 +112,8 @@ class IoTboxHomepage(Home): @http.route('/list_handlers', type='http', auth='none', website=True) def list_handlers(self): - drivers_list = helpers.list_file_by_os(get_resource_path('hw_drivers', 'iot_handlers', 'drivers')) - interfaces_list = helpers.list_file_by_os(get_resource_path('hw_drivers', 'iot_handlers', 'interfaces')) + drivers_list = helpers.list_file_by_os(file_path('hw_drivers/iot_handlers/drivers')) + interfaces_list = helpers.list_file_by_os(file_path('hw_drivers/iot_handlers/interfaces')) return handler_list_template.render({ 'title': "Odoo's IoT Box - Handlers list", 'breadcrumb': 'Handlers list', @@ -167,7 +167,7 @@ class IoTboxHomepage(Home): else: persistent = "" - subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_wifi.sh'), essid, password, persistent]) + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/connect_to_wifi.sh'), essid, password, persistent]) server = helpers.get_odoo_server_url() res_payload = { 'message': 'Connecting to ' + essid, @@ -198,7 +198,7 @@ class IoTboxHomepage(Home): @http.route('/handlers_clear', type='http', auth='none', cors='*', csrf=False) def clear_handlers_list(self): for directory in ['drivers', 'interfaces']: - for file in list(Path(get_resource_path('hw_drivers', 'iot_handlers', directory)).glob('*')): + for file in list(Path(file_path(f'hw_drivers/iot_handlers/{directory}')).glob('*')): if file.name != '__pycache__': helpers.unlink_file(str(file.relative_to(*file.parts[:3]))) return "" @@ -216,7 +216,7 @@ class IoTboxHomepage(Home): url = helpers.get_odoo_server_url() token = helpers.get_token() if iotname and platform.system() == 'Linux': - subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/rename_iot.sh'), iotname]) + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/rename_iot.sh'), iotname]) helpers.odoo_restart(5) return 'http://' + helpers.get_ip() + ':8069' @@ -238,7 +238,7 @@ class IoTboxHomepage(Home): token = token.split('|')[1] else: url = '' - subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server_wifi.sh'), url, iotname, token, essid, password, persistent]) + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/connect_to_server_wifi.sh'), url, iotname, token, essid, password, persistent]) return url # Set server address diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py index d860e4d9827..045ba3f243b 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py @@ -3,11 +3,11 @@ import base64 from freezegun import freeze_time from collections import Counter +from os.path import join as opj from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo import fields from odoo.tools import misc -from odoo.modules.module import get_resource_path, get_module_resource from lxml import etree @@ -117,8 +117,8 @@ class TestUBLCommon(AccountTestInvoicingCommon): def _update_invoice_from_file(self, module_name, subfolder, filename, invoice): """ Create an attachment from a file and post it on the invoice """ - file_path = get_module_resource(module_name, subfolder, filename) - with misc.file_open(file_path, 'rb') as file: + file_path = opj(module_name, subfolder, filename) + with misc.file_open(file_path, 'rb', filter_ext=('.xml',)) as file: attachment = self.env['ir.attachment'].create({ 'name': filename, 'datas': base64.encodebytes(file.read()), @@ -234,8 +234,7 @@ class TestUBLCommon(AccountTestInvoicingCommon): xml_content = base64.b64decode(attachment.with_context(bin_size=False).datas) xml_etree = self.get_xml_tree_from_string(xml_content) - expected_file_full_path = get_resource_path(self.test_module, 'tests/test_files', expected_file_path) - self.assertTrue(expected_file_full_path, "expected file not found") + expected_file_full_path = misc.file_path(f'{self.test_module}/tests/test_files/{expected_file_path}') expected_etree = etree.parse(expected_file_full_path).getroot() modified_etree = self.with_applied_xpath( diff --git a/addons/l10n_dk_oioubl/tests/test_xml_oioubl_dk.py b/addons/l10n_dk_oioubl/tests/test_xml_oioubl_dk.py index 9f2629fabc5..14e1e6d19cf 100644 --- a/addons/l10n_dk_oioubl/tests/test_xml_oioubl_dk.py +++ b/addons/l10n_dk_oioubl/tests/test_xml_oioubl_dk.py @@ -4,7 +4,6 @@ from odoo import Command, fields from odoo.addons.l10n_account_edi_ubl_cii_tests.tests.common import TestUBLCommon from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon from odoo.exceptions import UserError -from odoo.modules.module import get_resource_path from odoo.tests import tagged from odoo.tools import file_open @@ -179,9 +178,8 @@ class TestUBLDK(TestUBLCommon, TestAccountMoveSendCommon): ######### def import_bill_xml_file_in_purchase_journal(self, file_path): - full_file_path = get_resource_path(self.test_module, 'tests/test_files', file_path) - self.assertTrue(full_file_path, f'File not found: {file_path}') - with file_open(full_file_path, 'rb') as file: + file_path = f"{self.test_module}/tests/test_files/{file_path}" + with file_open(file_path, 'rb') as file: xml_attachment = self.env['ir.attachment'].create({ 'mimetype': 'application/xml', 'name': 'test_invoice.xml', diff --git a/addons/l10n_sa_edi/models/account_edi_xml_ubl_21_zatca.py b/addons/l10n_sa_edi/models/account_edi_xml_ubl_21_zatca.py index ddefa86e0e0..5d81f40a53e 100644 --- a/addons/l10n_sa_edi/models/account_edi_xml_ubl_21_zatca.py +++ b/addons/l10n_sa_edi/models/account_edi_xml_ubl_21_zatca.py @@ -3,7 +3,7 @@ from hashlib import sha256 from base64 import b64encode from lxml import etree from odoo import models, fields -from odoo.modules.module import get_module_resource +from odoo.tools.misc import file_path import re TAX_EXEMPTION_CODES = ['VATEX-SA-29', 'VATEX-SA-29-7', 'VATEX-SA-30'] @@ -57,7 +57,7 @@ class AccountEdiXmlUBL21Zatca(models.AbstractModel): def _transform_and_canonicalize_xml(content): """ Transform XML content to remove certain elements and signatures using an XSL template """ - invoice_xsl = etree.parse(get_module_resource('l10n_sa_edi', 'data', 'pre-hash_invoice.xsl')) + invoice_xsl = etree.parse(file_path('l10n_sa_edi/data/pre-hash_invoice.xsl')) transform = etree.XSLT(invoice_xsl) return _canonicalize_xml(transform(content)) diff --git a/addons/l10n_sa_edi/models/account_journal.py b/addons/l10n_sa_edi/models/account_journal.py index 1afa0d24327..569341c9438 100644 --- a/addons/l10n_sa_edi/models/account_journal.py +++ b/addons/l10n_sa_edi/models/account_journal.py @@ -6,7 +6,7 @@ from datetime import datetime from base64 import b64encode, b64decode from odoo import models, fields, service, _, api from odoo.exceptions import UserError -from odoo.modules.module import get_module_resource +from odoo.tools.misc import file_open from requests.exceptions import HTTPError, RequestException from cryptography import x509 from cryptography.x509 import ObjectIdentifier, load_der_x509_certificate @@ -288,8 +288,8 @@ class AccountJournal(models.Model): 'simplified/invoice.xml', 'simplified/credit.xml', 'simplified/debit.xml', ], {} for file in file_names: - fpath = get_module_resource('l10n_sa_edi', 'tests/compliance', file) - with open(fpath, 'rb') as ip: + fpath = f'l10n_sa_edi/tests/compliance/{file}' + with file_open(fpath, 'rb', filter_ext=('.xml',)) as ip: compliance_files[file] = ip.read().decode() return compliance_files diff --git a/addons/lunch/models/lunch_product_category.py b/addons/lunch/models/lunch_product_category.py index e53b8a89448..be850c5a244 100644 --- a/addons/lunch/models/lunch_product_category.py +++ b/addons/lunch/models/lunch_product_category.py @@ -5,7 +5,7 @@ import base64 from odoo import api, fields, models -from odoo.modules.module import get_module_resource +from odoo.tools.misc import file_open class LunchProductCategory(models.Model): @@ -16,8 +16,7 @@ class LunchProductCategory(models.Model): @api.model def _default_image(self): - image_path = get_module_resource('lunch', 'static/img', 'lunch.png') - return base64.b64encode(open(image_path, 'rb').read()) + return base64.b64encode(file_open('lunch/static/img/lunch.png', 'rb').read()) name = fields.Char('Product Category', required=True, translate=True) company_id = fields.Many2one('res.company') diff --git a/addons/mail/models/template_reset_mixin.py b/addons/mail/models/template_reset_mixin.py index aac6a0c6875..120936a05d8 100644 --- a/addons/mail/models/template_reset_mixin.py +++ b/addons/mail/models/template_reset_mixin.py @@ -7,10 +7,9 @@ from lxml import etree from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError -from odoo.modules import get_module_resource -from odoo.modules.module import get_resource_from_path, get_resource_path +from odoo.modules.module import get_resource_from_path from odoo.tools.convert import xml_import -from odoo.tools.misc import file_open +from odoo.tools.misc import file_path from odoo.tools.translate import TranslationImporter, get_po_paths @@ -79,7 +78,7 @@ class TemplateResetMixin(models.AbstractModel): for template in self.filtered('template_fs'): external_id = template.get_external_id().get(template.id) module, xml_id = external_id.split('.') - fullpath = get_resource_path(*template.template_fs.split('/')) + fullpath = file_path(template.template_fs) if fullpath: for field_name, field in template._fields.items(): if field.translate is True: diff --git a/addons/mail/tests/test_mail_template.py b/addons/mail/tests/test_mail_template.py index 621ed9a589c..3436c130459 100644 --- a/addons/mail/tests/test_mail_template.py +++ b/addons/mail/tests/test_mail_template.py @@ -5,7 +5,6 @@ from unittest.mock import patch from odoo.addons.mail.tests.common import MailCommon from odoo.exceptions import AccessError, ValidationError, UserError -from odoo.modules.module import get_module_resource from odoo.tests import Form, tagged, users from odoo.tools import convert_file @@ -174,13 +173,14 @@ class TestMailTemplate(MailCommon): @tagged('mail_template') class TestMailTemplateReset(MailCommon): - def _load(self, module, *args): + def _load(self, module, filepath): + # pylint: disable=no-value-for-parameter convert_file(self.env, module='mail', - filename=get_module_resource(module, *args), + filename=filepath, idref={}, mode='init', noupdate=False, kind='test') def test_mail_template_reset(self): - self._load('mail', 'tests', 'test_mail_template.xml') + self._load('mail', 'tests/test_mail_template.xml') mail_template = self.env.ref('mail.mail_template_test').with_context(lang=self.env.user.lang) @@ -212,7 +212,7 @@ class TestMailTemplateReset(MailCommon): def test_mail_template_reset_translation(self): """ Test if a translated value can be reset correctly when its translation exists/doesn't exist in the po file of the directory """ - self._load('mail', 'tests', 'test_mail_template.xml') + self._load('mail', 'tests/test_mail_template.xml') self.env['res.lang']._activate_lang('en_UK') self.env['res.lang']._activate_lang('fr_FR') diff --git a/addons/pos_self_order/models/pos_config.py b/addons/pos_self_order/models/pos_config.py index f9f146cc9f3..92028c04b9e 100644 --- a/addons/pos_self_order/models/pos_config.py +++ b/addons/pos_self_order/models/pos_config.py @@ -4,13 +4,14 @@ import io import uuid import base64 +from os.path import join as opj from PIL import Image from typing import Optional, List, Dict from werkzeug.urls import url_quote from odoo.exceptions import UserError from odoo.tools import image_to_base64 -from odoo import api, fields, models, modules, _, service +from odoo import api, fields, models, _, service from odoo.tools import file_open, split_every @@ -123,7 +124,7 @@ class PosConfig(models.Model): for pos_config_id in pos_config_ids: for image_name in ['landing_01.jpg', 'landing_02.jpg', 'landing_03.jpg']: - image_path = modules.get_module_resource("pos_self_order", "static/img", image_name) + image_path = opj("pos_self_order/static/img", image_name) attachment = self.env['ir.attachment'].create({ 'name': image_name, 'datas': base64.b64encode(file_open(image_path, "rb").read()), diff --git a/addons/purchase_stock/tests/common.py b/addons/purchase_stock/tests/common.py index 34cfdc6ec88..f235d4634c1 100644 --- a/addons/purchase_stock/tests/common.py +++ b/addons/purchase_stock/tests/common.py @@ -4,7 +4,6 @@ from datetime import timedelta from odoo import fields from odoo.addons.stock.tests.common2 import TestStockCommon from odoo import tools -from odoo.modules.module import get_module_resource class PurchaseTestCommon(TestStockCommon): diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py index 7b005fb929c..9e740c5faf7 100644 --- a/addons/rating/models/rating.py +++ b/addons/rating/models/rating.py @@ -5,7 +5,7 @@ import uuid from odoo import api, fields, models from odoo.addons.rating.models import rating_data -from odoo.modules.module import get_resource_path +from odoo.tools.misc import file_open class Rating(models.Model): @@ -96,12 +96,13 @@ class Rating(models.Model): self.rating_image_url = False self.rating_image = False for rating in self: + image_path = f'rating/static/src/img/{rating._get_rating_image_filename()}' + rating.rating_image_url = f'/{image_path}' try: - image_path = get_resource_path('rating', 'static/src/img', rating._get_rating_image_filename()) - rating.rating_image_url = '/rating/static/src/img/%s' % rating._get_rating_image_filename() - rating.rating_image = base64.b64encode(open(image_path, 'rb').read()) if image_path else False - except (IOError, OSError): - pass + rating.rating_image = base64.b64encode( + file_open(image_path, 'rb', filter_ext=('.png',)).read()) + except (IOError, OSError, FileNotFoundError): + rating.rating_image = False @api.depends('rating') def _compute_rating_text(self): diff --git a/addons/sale/models/onboarding_onboarding_step.py b/addons/sale/models/onboarding_onboarding_step.py index 68d34759929..5cb752115df 100644 --- a/addons/sale/models/onboarding_onboarding_step.py +++ b/addons/sale/models/onboarding_onboarding_step.py @@ -3,7 +3,6 @@ import base64 from odoo import _, api, Command, models -from odoo.modules.module import get_resource_path from odoo.tools import file_open @@ -39,8 +38,7 @@ class OnboardingStep(models.Model): # take any existing product or create one product = self.env['product.product'].search([], limit=1) if not product: - default_image_path = get_resource_path('product', 'static/img', 'product_product_13-image.jpg') - with file_open(default_image_path, 'rb') as default_image_stream: + with file_open('product/static/img/product_product_13-image.jpg', 'rb') as default_image_stream: product = self.env['product.product'].create({ 'name': _('Sample Product'), 'active': False, diff --git a/addons/sale_product_configurator/tests/common.py b/addons/sale_product_configurator/tests/common.py index 8088df922a4..e2ef4f1c82c 100644 --- a/addons/sale_product_configurator/tests/common.py +++ b/addons/sale_product_configurator/tests/common.py @@ -2,8 +2,7 @@ import base64 from odoo.tests.common import TransactionCase -from odoo.modules.module import get_module_resource - +from odoo.tools.misc import file_open class TestProductConfiguratorCommon(TransactionCase): @@ -78,8 +77,8 @@ class TestProductConfiguratorCommon(TransactionCase): cls.product_product_custo_desk.product_variant_ids[3].active = False # Setup a first optional product - img_path = get_module_resource('product', 'static', 'img', 'product_product_11-image.png') - img_content = base64.b64encode(open(img_path, "rb").read()) + img_path = 'product/static/img/product_product_11-image.png' + img_content = base64.b64encode(file_open(img_path, "rb").read()) cls.product_product_conf_chair = cls.env['product.template'].create({ 'name': 'Conference Chair (TEST)', 'image_1920': img_content, diff --git a/addons/sms/tests/test_sms_template.py b/addons/sms/tests/test_sms_template.py index 5802fd62cd6..09dc00e77c7 100644 --- a/addons/sms/tests/test_sms_template.py +++ b/addons/sms/tests/test_sms_template.py @@ -7,7 +7,6 @@ from odoo.addons.mail.tests.common import mail_new_test_user from odoo.exceptions import AccessError from odoo.tests import tagged from odoo.tools import mute_logger, convert_file -from odoo.modules.module import get_module_resource @tagged('post_install', '-at_install') @@ -113,13 +112,14 @@ class TestSmsTemplateAccessRights(TransactionCase): @tagged('post_install', '-at_install') class TestSMSTemplateReset(TransactionCase): - def _load(self, module, *args): + def _load(self, module, filepath): + # pylint: disable=no-value-for-parameter convert_file(self.env, module='sms', - filename=get_module_resource(module, *args), + filename=filepath, idref={}, mode='init', noupdate=False, kind='test') def test_sms_template_reset(self): - self._load('sms', 'tests', 'test_sms_template.xml') + self._load('sms', 'tests/test_sms_template.xml') sms_template = self.env.ref('sms.sms_template_test').with_context(lang=self.env.user.lang) diff --git a/addons/test_base_import/tests/test_base_import.py b/addons/test_base_import/tests/test_base_import.py index d5d407dbf99..345fa49ca84 100644 --- a/addons/test_base_import/tests/test_base_import.py +++ b/addons/test_base_import/tests/test_base_import.py @@ -7,8 +7,8 @@ import pprint import unittest from odoo.tests.common import TransactionCase, can_import, RecordCapturer -from odoo.modules.module import get_module_resource from odoo.tools import mute_logger, pycompat +from odoo.tools.misc import file_open from odoo.addons.base_import.models.base_import import ImportValidationError from odoo.addons.test_base_import.models.test_base_import import model as base_import_model @@ -358,8 +358,7 @@ class TestPreview(TransactionCase): @unittest.skipUnless(can_import('xlrd'), "XLRD module not available") def test_xls_success(self): - xls_file_path = get_module_resource('test_base_import', 'data', 'test.xls') - file_content = open(xls_file_path, 'rb').read() + file_content = file_open('test_base_import/data/test.xls', 'rb').read() import_wizard = self.env['base_import.import'].create({ 'res_model': base_import_model('preview'), 'file': file_content, @@ -382,8 +381,7 @@ class TestPreview(TransactionCase): @unittest.skipUnless(can_import('xlrd.xlsx'), "XLRD/XLSX not available") def test_xlsx_success(self): - xlsx_file_path = get_module_resource('test_base_import', 'data', 'test.xlsx') - file_content = open(xlsx_file_path, 'rb').read() + file_content = file_open('test_base_import/data/test.xlsx', 'rb').read() import_wizard = self.env['base_import.import'].create({ 'res_model': base_import_model('preview'), 'file': file_content, @@ -406,8 +404,7 @@ class TestPreview(TransactionCase): @unittest.skipUnless(can_import('odf'), "ODFPY not available") def test_ods_success(self): - ods_file_path = get_module_resource('test_base_import', 'data', 'test.ods') - file_content = open(ods_file_path, 'rb').read() + file_content = file_open('test_base_import/data/test.ods', 'rb').read() import_wizard = self.env['base_import.import'].create({ 'res_model': base_import_model('preview'), 'file': file_content, diff --git a/addons/test_website/tests/test_qweb.py b/addons/test_website/tests/test_qweb.py index 3f22322fad0..7e62156d815 100644 --- a/addons/test_website/tests/test_qweb.py +++ b/addons/test_website/tests/test_qweb.py @@ -6,19 +6,18 @@ import re from odoo import tools from odoo.addons.base.tests.common import TransactionCaseWithUserDemo -from odoo.modules.module import get_module_resource class TestQweb(TransactionCaseWithUserDemo): - def _load(self, module, *args): + def _load(self, module, filepath): tools.convert_file( - self.env, 'test_website', - get_module_resource(module, *args), + self.env, module, + filepath, {}, 'init', False, 'test' ) def test_qweb_cdn(self): - self._load('test_website', 'tests', 'template_qweb_test.xml') + self._load('test_website', 'tests/template_qweb_test.xml') website = self.env.ref('website.default_website') website.write({ diff --git a/addons/web/controllers/binary.py b/addons/web/controllers/binary.py index 08a1462e3b0..0a51a304b74 100644 --- a/addons/web/controllers/binary.py +++ b/addons/web/controllers/binary.py @@ -18,7 +18,6 @@ import odoo.modules.registry from odoo import http, _ from odoo.exceptions import AccessError, UserError from odoo.http import request, Response -from odoo.modules import get_resource_path from odoo.tools import file_open, file_path, replace_exceptions from odoo.tools.mimetypes import guess_mimetype from odoo.tools.image import image_guess_size_from_field_name @@ -214,12 +213,11 @@ class Binary(http.Controller): def company_logo(self, dbname=None, **kw): imgname = 'logo' imgext = '.png' - placeholder = functools.partial(get_resource_path, 'web', 'static', 'img') dbname = request.db uid = (request.session.uid if dbname else None) or odoo.SUPERUSER_ID if not dbname: - response = http.Stream.from_path(placeholder(imgname + imgext)).get_response() + response = http.Stream.from_path(file_path('web/static/img/logo.png')).get_response() else: try: # create an empty registry @@ -255,9 +253,9 @@ class Binary(http.Controller): response_class=Response, ) else: - response = http.Stream.from_path(placeholder('nologo.png')).get_response() + response = http.Stream.from_path(file_path('web/static/img/nologo.png')).get_response() except Exception: - response = http.Stream.from_path(placeholder(imgname + imgext)).get_response() + response = http.Stream.from_path(file_path(f'web/static/img/{imgname}{imgext}')).get_response() return response @@ -273,7 +271,7 @@ class Binary(http.Controller): """ supported_exts = ('.ttf', '.otf', '.woff', '.woff2') fonts = [] - fonts_directory = file_path(os.path.join('web', 'static', 'fonts', 'sign')) + fonts_directory = file_path('web/static/fonts/sign') if fontname: font_path = os.path.join(fonts_directory, fontname) with file_open(font_path, 'rb', filter_ext=supported_exts) as font_file: diff --git a/addons/web/controllers/webclient.py b/addons/web/controllers/webclient.py index e6d8783393f..47dcad879fb 100644 --- a/addons/web/controllers/webclient.py +++ b/addons/web/controllers/webclient.py @@ -13,10 +13,10 @@ import werkzeug.wsgi import odoo import odoo.modules.registry from odoo import http -from odoo.modules import get_manifest, get_resource_path +from odoo.modules import get_manifest from odoo.http import request from odoo.tools import lazy -from odoo.tools.misc import file_open +from odoo.tools.misc import file_open, file_path from .utils import _local_web_translations @@ -80,7 +80,7 @@ class WebClient(http.Controller): for addon_name in mods: manifest = get_manifest(addon_name) if manifest and manifest['bootstrap']: - f_name = get_resource_path(addon_name, 'i18n', f'{lang}.po') + f_name = file_path(f'{addon_name}/i18n/{lang}.po') if not f_name: continue translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)} diff --git a/addons/web/models/base_document_layout.py b/addons/web/models/base_document_layout.py index 7483339a310..2c40c6b8c11 100644 --- a/addons/web/models/base_document_layout.py +++ b/addons/web/models/base_document_layout.py @@ -5,8 +5,8 @@ from markupsafe import Markup from odoo import api, fields, models, tools from odoo.addons.base.models.ir_qweb_fields import nl2br -from odoo.modules import get_resource_path from odoo.tools import html2plaintext, is_html_empty +from odoo.tools.misc import file_path try: import sass as libsass @@ -289,7 +289,7 @@ class BaseDocumentLayout(models.TransientModel): precision = 8 output_style = 'expanded' - bootstrap_path = get_resource_path('web', 'static', 'lib', 'bootstrap', 'scss') + bootstrap_path = file_path('web/static/lib/bootstrap/scss') try: return libsass.compile( diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index 4cef8ec460c..a62817ce65a 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -2,7 +2,10 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import re +from contextlib import suppress + import odoo.tests +from odoo.tools.misc import file_open RE_ONLY = re.compile(r'QUnit\.(only|debug)\(') @@ -45,11 +48,12 @@ class WebSuite(odoo.tests.HttpCase): for asset in assets: filename = asset['filename'] - if not filename or not filename.endswith('.js'): + if not filename.endswith('.js'): continue - with open(filename, 'rb') as fp: - if RE_ONLY.search(fp.read().decode('utf-8')): - self.fail("`QUnit.only()` or `QUnit.debug()` used in file %r" % asset['url']) + with suppress(FileNotFoundError): + with file_open(filename, 'rb', filter_ext=('.js',)) as fp: + if RE_ONLY.search(fp.read().decode('utf-8')): + self.fail("`QUnit.only()` or `QUnit.debug()` used in file %r" % asset['url']) @odoo.tests.tagged('post_install', '-at_install') diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 4224f7471f5..1fd886ee95d 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -14,14 +14,14 @@ from lxml import etree from base64 import b64decode, b64encode from datetime import datetime from math import floor +from os.path import join as opj from odoo.http import request, Response from odoo import http, tools, _, SUPERUSER_ID from odoo.addons.http_routing.models.ir_http import slug, unslug from odoo.addons.web_editor.tools import get_video_url_data from odoo.exceptions import UserError, MissingError, ValidationError -from odoo.modules.module import get_resource_path -from odoo.tools import file_open +from odoo.tools.misc import file_open from odoo.tools.mimetypes import guess_mimetype from odoo.tools.image import image_data_uri, binary_to_image from odoo.addons.base.models.assetsbundle import AssetsBundle @@ -569,11 +569,12 @@ class Web_Editor(http.Controller): return '%s?access_token=%s' % (attachment.image_src, attachment.access_token) def _get_shape_svg(self, module, *segments): - shape_path = get_resource_path(module, 'static', *segments) - if not shape_path: + shape_path = opj(module, 'static', *segments) + try: + with file_open(shape_path, 'r', filter_ext=('.svg',)) as file: + return file.read() + except FileNotFoundError: raise werkzeug.exceptions.NotFound() - with tools.file_open(shape_path, 'r', filter_ext=('.svg',)) as file: - return file.read() def _update_svg_colors(self, options, svg): user_colors = [] diff --git a/addons/web_editor/models/ir_qweb_fields.py b/addons/web_editor/models/ir_qweb_fields.py index 98b5ea89d4a..7df43391ca6 100644 --- a/addons/web_editor/models/ir_qweb_fields.py +++ b/addons/web_editor/models/ir_qweb_fields.py @@ -29,7 +29,7 @@ from odoo import _, api, models, fields from odoo.exceptions import UserError, ValidationError from odoo.tools import ustr, posix_to_ldml, pycompat from odoo.tools import html_escape as escape -from odoo.tools.misc import get_lang, babel_locale_parse +from odoo.tools.misc import file_open, get_lang, babel_locale_parse REMOTE_CONNECTION_TIMEOUT = 2.5 @@ -479,20 +479,13 @@ class Image(models.AbstractModel): def load_local_url(self, url): match = self.local_url_re.match(urls.url_parse(url).path) - rest = match.group('rest') - for sep in os.sep, os.altsep: - if sep and sep != '/': - rest.replace(sep, '/') - path = odoo.modules.get_module_resource( - match.group('module'), 'static', *(rest.split('/'))) - - if not path: - return None + path = os.path.join( + match.group('module'), 'static', rest) try: - with open(path, 'rb') as f: + with file_open(path, 'rb') as f: # force complete image load to ensure it's valid image data image = I.open(f) image.load() diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 84d802998de..00a49c1d17d 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -23,7 +23,7 @@ from odoo.addons.portal.controllers.portal import pager from odoo.addons.iap.tools import iap_tools from odoo.exceptions import AccessError, MissingError, UserError, ValidationError from odoo.http import request -from odoo.modules.module import get_resource_path, get_manifest +from odoo.modules.module import get_manifest from odoo.osv.expression import AND, OR, FALSE_DOMAIN, get_unaccent_wrapper from odoo.tools.translate import _, xml_translate from odoo.tools import escape_psql, pycompat @@ -98,8 +98,7 @@ class Website(models.Model): return self.env.ref('base.main_company').social_tiktok def _default_logo(self): - image_path = get_resource_path('website', 'static/src/img', 'website_logo.svg') - with tools.file_open(image_path, 'rb') as f: + with tools.file_open('website/static/src/img/website_logo.svg', 'rb') as f: return base64.b64encode(f.read()) logo = fields.Binary('Website Logo', default=_default_logo, help="Display this logo on the website.") @@ -134,8 +133,7 @@ class Website(models.Model): robots_txt = fields.Html('Robots.txt', translate=False, groups='website.group_website_designer', sanitize=False) def _default_favicon(self): - img_path = get_resource_path('web', 'static/img/favicon.ico') - with tools.file_open(img_path, 'rb') as f: + with tools.file_open('web/static/img/favicon.ico', 'rb') as f: return base64.b64encode(f.read()) favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.", default=_default_favicon) diff --git a/addons/website/models/website_configurator_feature.py b/addons/website/models/website_configurator_feature.py index ce59015dd6f..fb9071a1545 100644 --- a/addons/website/models/website_configurator_feature.py +++ b/addons/website/models/website_configurator_feature.py @@ -5,7 +5,6 @@ import re from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError -from odoo.modules.module import get_resource_path class WebsiteConfiguratorFeature(models.Model): @@ -34,11 +33,11 @@ class WebsiteConfiguratorFeature(models.Model): @staticmethod def _process_svg(theme, colors, image_mapping): svg = None - preview_svg = get_resource_path(theme, 'static', 'description', theme + '.svg') - if not preview_svg: + try: + with tools.file_open(f'{theme}/static/description/{theme}.svg', 'r') as file: + svg = file.read() + except FileNotFoundError: return False - with tools.file_open(preview_svg, 'r') as file: - svg = file.read() default_colors = { 'color1': '#3AADAA', diff --git a/addons/website_event_exhibitor/models/event_sponsor.py b/addons/website_event_exhibitor/models/event_sponsor.py index 9a08a723e09..91af79d7edd 100644 --- a/addons/website_event_exhibitor/models/event_sponsor.py +++ b/addons/website_event_exhibitor/models/event_sponsor.py @@ -7,7 +7,6 @@ from pytz import timezone, utc from odoo import api, fields, models, _ from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.resource.models.utils import float_to_time -from odoo.modules.module import get_resource_path from odoo.tools import is_html_empty from odoo.tools.translate import html_translate @@ -116,7 +115,7 @@ class Sponsor(models.Model): elif sponsor.partner_id.image_256: sponsor.website_image_url = self.env['website'].image_url(sponsor.partner_id, 'image_256', size=256) else: - sponsor.website_image_url = get_resource_path('website_event_exhibitor', 'static/src/img', 'event_sponsor_default_%d.png' % (sponsor.id % 1)) + sponsor.website_image_url = 'website_event_exhibitor/static/src/img/event_sponsor_default_0.jpeg' def _synchronize_with_partner(self, fname): """ Synchronize with partner if not set. Setting a value does not write diff --git a/addons/website_event_track/controllers/webmanifest.py b/addons/website_event_track/controllers/webmanifest.py index 627c2581577..deee466bef1 100644 --- a/addons/website_event_track/controllers/webmanifest.py +++ b/addons/website_event_track/controllers/webmanifest.py @@ -7,8 +7,8 @@ import pytz from odoo import http from odoo.addons.http_routing.models.ir_http import url_for from odoo.http import request -from odoo.modules.module import get_module_resource from odoo.tools import ustr +from odoo.tools.misc import file_open from odoo.tools.translate import _ @@ -47,8 +47,7 @@ class TrackManifest(http.Controller): def service_worker(self): """ Returns a ServiceWorker javascript file scoped for website_event """ - sw_file = get_module_resource('website_event_track', 'static/src/js/service_worker.js') - with open(sw_file, 'r') as fp: + with file_open('website_event_track/static/src/js/service_worker.js', 'r') as fp: body = fp.read() js_cdn_url = 'undefined' if request.website.cdn_activated: diff --git a/addons/website_sale/tests/test_customize.py b/addons/website_sale/tests/test_customize.py index 652f688fa47..81e694c64fb 100644 --- a/addons/website_sale/tests/test_customize.py +++ b/addons/website_sale/tests/test_customize.py @@ -4,8 +4,9 @@ import base64 from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal from odoo.fields import Command -from odoo.modules.module import get_module_resource from odoo.tests import tagged +from odoo.tools.misc import file_open + @tagged('post_install', '-at_install') class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal): @@ -125,8 +126,7 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal): }) self.product_product_4_product_template.attribute_line_ids[0].write({'value_ids': [(4, product_attribute_value_7.id)]}) - img_path = get_module_resource('product', 'static', 'img', 'product_product_11-image.png') - img_content = base64.b64encode(open(img_path, "rb").read()) + img_content = base64.b64encode(file_open('product/static/img/product_product_11-image.png', "rb").read()) self.product_product_11_product_template = self.env['product.template'].create({ 'name': 'Conference Chair (TEST)', 'website_sequence': 9999, # laule diff --git a/addons/website_slides/tests/test_ui_wslides.py b/addons/website_slides/tests/test_ui_wslides.py index 4178f364da0..cff48fb183f 100644 --- a/addons/website_slides/tests/test_ui_wslides.py +++ b/addons/website_slides/tests/test_ui_wslides.py @@ -5,9 +5,9 @@ import base64 from dateutil.relativedelta import relativedelta from odoo import tests from odoo.fields import Datetime -from odoo.modules.module import get_module_resource from odoo.tools import mute_logger from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal +from odoo.tools.misc import file_open class TestUICommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal): @@ -15,10 +15,8 @@ class TestUICommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal): def setUp(self): super(TestUICommon, self).setUp() # Load pdf and img contents - pdf_path = get_module_resource('website_slides', 'static', 'src', 'img', 'presentation.pdf') - pdf_content = base64.b64encode(open(pdf_path, "rb").read()) - img_path = get_module_resource('website_slides', 'static', 'src', 'img', 'slide_demo_gardening_1.jpg') - img_content = base64.b64encode(open(img_path, "rb").read()) + pdf_content = base64.b64encode(file_open('website_slides/static/src/img/presentation.pdf', "rb").read()) + img_content = base64.b64encode(file_open('website_slides/static/src/img/slide_demo_gardening_1.jpg', "rb").read()) self.channel = self.env['slide.channel'].create({ 'name': 'Basics of Gardening - Test', diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py index afbd947082e..2226261c7ce 100644 --- a/odoo/addons/base/models/assetsbundle.py +++ b/odoo/addons/base/models/assetsbundle.py @@ -28,12 +28,11 @@ from rjsmin import jsmin as rjsmin from odoo import release, SUPERUSER_ID, _ from odoo.http import request -from odoo.modules.module import get_resource_path from odoo.tools import (func, misc, transpile_javascript, is_odoo_module, SourceMapGenerator, profiler, apply_inheritance_specs) from odoo.tools.constants import SCRIPT_EXTENSIONS, STYLE_EXTENSIONS -from odoo.tools.misc import file_open, html_escape as escape +from odoo.tools.misc import file_open, file_path from odoo.tools.pycompat import to_text _logger = logging.getLogger(__name__) @@ -186,8 +185,8 @@ class AssetsBundle(object): self.env.cr.execute(f"""DELETE FROM {attachments._table} WHERE id IN ( SELECT id FROM {attachments._table} WHERE id in %s FOR NO KEY UPDATE SKIP LOCKED )""", [tuple(attachments.ids)]) - for file_path in to_delete: - attachments._file_delete(file_path) + for fpath in to_delete: + attachments._file_delete(fpath) def clean_attachments(self, extension): """ Takes care of deleting any outdated ir.attachment records associated to a bundle before @@ -719,7 +718,7 @@ class AssetsBundle(object): except IOError: rtlcss = 'rtlcss' - cmd = [rtlcss, '-c', get_resource_path("base", "data/rtlcss.json"), '-'] + cmd = [rtlcss, '-c', file_path("base/data/rtlcss.json"), '-'] try: rtlcss = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) @@ -1091,7 +1090,7 @@ class SassStylesheetAsset(PreprocessedCSS): class ScssStylesheetAsset(PreprocessedCSS): @property def bootstrap_path(self): - return get_resource_path('web', 'static', 'lib', 'bootstrap', 'scss') + return file_path('web/static/lib/bootstrap/scss') precision = 8 output_style = 'expanded' diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index 56a28edfb7b..48f938ab1f3 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -31,7 +31,6 @@ from odoo.modules.registry import Registry from odoo.service import security from odoo.tools import get_lang, submap from odoo.tools.translate import code_translations -from odoo.modules.module import get_resource_path, get_module_path _logger = logging.getLogger(__name__) diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index cf5d5c8e550..8d18e2c015a 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -9,7 +9,6 @@ import io import logging import os import shutil -import tempfile import threading import zipfile @@ -32,7 +31,7 @@ from odoo.tools.parse_version import parse_version from odoo.tools.misc import topological_sort, get_flag from odoo.tools.translate import TranslationImporter, get_po_paths from odoo.http import request -from odoo.modules import get_module_path, get_module_resource +from odoo.modules import get_module_path _logger = logging.getLogger(__name__) @@ -162,10 +161,8 @@ class Module(models.Model): if not module.name: module.description_html = False continue - module_path = modules.get_module_path(module.name, display_warning=False) # avoid to log warning for fake community module - if module_path: - path = modules.check_resource_path(module_path, 'static/description/index.html') - if module_path and path: + path = os.path.join(module.name, '/static/description/index.html') + try: with tools.file_open(path, 'rb') as desc_file: doc = desc_file.read() if doc.startswith(XML_DECLARATION): @@ -191,7 +188,7 @@ class Module(models.Model): if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'): element.set('src', "/%s/static/description/%s" % (module.name, element.get('src'))) module.description_html = tools.html_sanitize(lxml.html.tostring(html)) - else: + except FileNotFoundError: overrides = { 'embed_stylesheet': False, 'doctitle_xform': False, @@ -246,7 +243,7 @@ class Module(models.Model): module.icon_image = '' if module.icon: path_parts = module.icon.split('/') - path = modules.get_module_resource(path_parts[1], *path_parts[2:]) + path = os.path.join(path_parts[1], *path_parts[2:]) elif module.id: path = modules.module.get_module_icon_path(module) else: diff --git a/odoo/addons/base/models/ir_ui_menu.py b/odoo/addons/base/models/ir_ui_menu.py index e35f6ff4d31..56533906471 100644 --- a/odoo/addons/base/models/ir_ui_menu.py +++ b/odoo/addons/base/models/ir_ui_menu.py @@ -3,13 +3,13 @@ import base64 from collections import defaultdict +from os.path import join as opj import operator import re from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError from odoo.http import request -from odoo.modules import get_module_resource from odoo.osv import expression MENU_ITEM_SEPARATOR = "/" @@ -56,16 +56,16 @@ class IrUiMenu(models.Model): else: return self.name - def read_image(self, path): + def _read_image(self, path): if not path: return False path_info = path.split(',') - icon_path = get_module_resource(path_info[0], path_info[1]) - icon_image = False - if icon_path: - with tools.file_open(icon_path, 'rb') as icon_file: - icon_image = base64.encodebytes(icon_file.read()) - return icon_image + icon_path = opj(path_info[0], path_info[1]) + try: + with tools.file_open(icon_path, 'rb', filter_ext=('.png',)) as icon_file: + return base64.encodebytes(icon_file.read()) + except FileNotFoundError: + return False @api.constrains('parent_id') def _check_parent_id(self): @@ -179,10 +179,10 @@ class IrUiMenu(models.Model): `web_icon` can either be: - an image icon [module, path] - a built icon [icon_class, icon_color, background_color] - and it only has to call `read_image` if it's an image. + and it only has to call `_read_image` if it's an image. """ if web_icon and len(web_icon.split(',')) == 2: - return self.read_image(web_icon) + return self._read_image(web_icon) def unlink(self): # Detach children and promote them to top-level, because it would be unwise to diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 21247415d6a..9e248f40bc4 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -21,12 +21,13 @@ import odoo from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError, AccessError, UserError from odoo.http import request -from odoo.modules.module import get_resource_from_path, get_resource_path +from odoo.modules.module import get_resource_from_path from odoo.tools import config, ConstantMapping, get_diff, pycompat, apply_inheritance_specs, locate_node, str2bool -from odoo.tools.convert import _fix_multiple_roots from odoo.tools import safe_eval, lazy, lazy_property, frozendict -from odoo.tools.view_validation import valid_view, get_domain_value_names, get_expression_field_names, get_dict_asts +from odoo.tools.convert import _fix_multiple_roots +from odoo.tools.misc import file_path from odoo.tools.translate import xml_translate, TRANSLATED_ATTRS +from odoo.tools.view_validation import valid_view, get_domain_value_names, get_expression_field_names, get_dict_asts from odoo.models import check_method_name from odoo.osv.expression import expression @@ -234,22 +235,24 @@ actual arch. if read_file and view.arch_fs and (view.xml_id or view.key): xml_id = view.xml_id or view.key # It is safe to split on / herebelow because arch_fs is explicitely stored with '/' - fullpath = get_resource_path(*view.arch_fs.split('/')) - if fullpath: - arch_fs = get_view_arch_from_file(fullpath, xml_id) - # replace %(xml_id)s, %(xml_id)d, %%(xml_id)s, %%(xml_id)d by the res_id - if arch_fs: - arch_fs = resolve_external_ids(arch_fs, xml_id).replace('%%', '%') - translation_dictionary = field_arch_db.get_translation_dictionary( - view.with_env(env_en).arch_db, {lang: view.with_env(env_lang).arch_db} - ) - arch_fs = field_arch_db.translate( - lambda term: translation_dictionary[term][lang], - arch_fs - ) - else: + try: + fullpath = file_path(view.arch_fs) + except FileNotFoundError: _logger.warning("View %s: Full path [%s] cannot be found.", xml_id, view.arch_fs) arch_fs = False + continue + + arch_fs = get_view_arch_from_file(fullpath, xml_id) + # replace %(xml_id)s, %(xml_id)d, %%(xml_id)s, %%(xml_id)d by the res_id + if arch_fs: + arch_fs = resolve_external_ids(arch_fs, xml_id).replace('%%', '%') + translation_dictionary = field_arch_db.get_translation_dictionary( + view.with_env(env_en).arch_db, {lang: view.with_env(env_lang).arch_db} + ) + arch_fs = field_arch_db.translate( + lambda term: translation_dictionary[term][lang], + arch_fs + ) view.arch = pycompat.to_text(arch_fs or view.arch_db) def _inverse_arch(self): diff --git a/odoo/addons/base/tests/test_pdf.py b/odoo/addons/base/tests/test_pdf.py index 6f1368e40d0..96d63ca71fa 100644 --- a/odoo/addons/base/tests/test_pdf.py +++ b/odoo/addons/base/tests/test_pdf.py @@ -3,7 +3,7 @@ from odoo.tests.common import TransactionCase from odoo.tools import pdf -from odoo.modules.module import get_module_resource +from odoo.tools.misc import file_open import io @@ -12,8 +12,7 @@ class TestPdf(TransactionCase): def setUp(self): super().setUp() - file_path = get_module_resource('base', 'tests', 'minimal.pdf') - self.file = open(file_path, 'rb').read() + self.file = file_open('base/tests/minimal.pdf', 'rb').read() self.minimal_reader_buffer = io.BytesIO(self.file) self.minimal_pdf_reader = pdf.OdooPdfFileReader(self.minimal_reader_buffer) diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index 5f9a22c5722..a57193dfe6c 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -12,7 +12,6 @@ from lxml.builder import E from copy import deepcopy from textwrap import dedent -from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase from odoo.addons.base.models.ir_qweb import QWebException, render from odoo.tools import misc, mute_logger diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py index 96213e3ab13..a202d1f0c41 100644 --- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py +++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py @@ -18,12 +18,12 @@ from odoo.addons import __path__ as ADDONS_PATH from odoo.addons.base.models.assetsbundle import AssetsBundle from odoo.addons.base.models.ir_asset import AssetPaths from odoo.addons.base.models.ir_attachment import IrAttachment -from odoo.modules.module import get_resource_path, get_manifest +from odoo.modules.module import get_manifest from odoo.tests import HttpCase, tagged from odoo.tests.common import TransactionCase from odoo.addons.base.models.ir_qweb import QWebException from odoo.tools import mute_logger, func - +from odoo.tools.misc import file_path GETMTINE = os.path.getmtime @@ -241,7 +241,7 @@ class TestJavascriptAssetsBundle(FileTouchable): last_modified0 = bundle0.get_checksum('js') version0 = bundle0.get_version('js') - path = get_resource_path('test_assetsbundle', 'static', 'src', 'js', 'test_jsfile1.js') + path = file_path('test_assetsbundle/static/src/js/test_jsfile1.js') bundle1 = self._get_asset(self.jsbundle_name, debug_assets=True) with self._touch(path): @@ -504,7 +504,7 @@ class TestJavascriptAssetsBundle(FileTouchable): # Touch test_cssfile1.css # Note: No lang specific context given while calling _get_asset so it will load assets for en_US - path = get_resource_path('test_assetsbundle', 'static', 'src', 'css', 'test_cssfile1.css') + path = file_path('test_assetsbundle/static/src/css/test_cssfile1.css') ltr_bundle1 = self._get_asset(self.cssbundle_name, debug_assets=True) with self._touch(path): @@ -768,7 +768,7 @@ class TestAssetsBundleWithIRAMock(FileTouchable): self._bundle(self._get_asset(), False, False, '(Second access, no change)') # Touch the file and compile a third time - path = get_resource_path('test_assetsbundle', 'static', 'src', 'scss', 'test_file1.scss') + path = file_path('test_assetsbundle/static/src/scss/test_file1.scss') t = time.time() + 5 asset = self._get_asset() with self._touch(path, t): diff --git a/odoo/addons/test_convert/tests/test_convert.py b/odoo/addons/test_convert/tests/test_convert.py index 549b19acd69..528fe355b4f 100644 --- a/odoo/addons/test_convert/tests/test_convert.py +++ b/odoo/addons/test_convert/tests/test_convert.py @@ -8,9 +8,9 @@ from lxml import etree as ET from lxml.builder import E import odoo -from odoo.modules.module import get_resource_path from odoo.tests import common from odoo.tools.convert import convert_file, xml_import, _eval_xml +from odoo.tools.misc import file_path Field = E.field Value = E.value @@ -237,7 +237,7 @@ class TestEvalXML(common.TransactionCase): self.assertEqual(record.name, 'bar') # Reset the value to the one from the XML data file, # with a lang passed in the environment. - filepath = get_resource_path('test_convert', 'data/test_translated_field/test_model_data.xml') + filepath = file_path('test_convert/data/test_translated_field/test_model_data.xml') doc = ET.parse(filepath) obj = xml_import(env_fr, 'test_convert', {}, mode='init', xml_filename=filepath) obj.parse(doc.getroot()) diff --git a/odoo/addons/test_converter/tests/test_html.py b/odoo/addons/test_converter/tests/test_html.py index 694f4e725b0..510a9524e29 100644 --- a/odoo/addons/test_converter/tests/test_html.py +++ b/odoo/addons/test_converter/tests/test_html.py @@ -7,6 +7,7 @@ import re from odoo.tests import common from odoo.tools import html_escape as e +from odoo.tools.misc import file_open directory = os.path.dirname(__file__) @@ -216,7 +217,7 @@ class TestBinaryExport(TestBasicExport): def test_image(self): converter = self.env['ir.qweb.field.image'] - with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f: + with file_open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f: content = f.read() encoded_content = base64.b64encode(content) @@ -225,13 +226,13 @@ class TestBinaryExport(TestBasicExport): self.assertEqual( value, u'' % encoded_content.decode('ascii')) - with open(os.path.join(directory, 'test_vectors', 'pdf'), 'rb') as f: + with file_open(os.path.join(directory, 'test_vectors', 'pdf'), 'rb') as f: content = f.read() with self.assertRaises(ValueError): converter.value_to_html(base64.b64encode(content), {}) - with open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f: + with file_open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f: content = f.read() with self.assertRaises(ValueError): diff --git a/odoo/addons/test_lint/tests/test_eslint.py b/odoo/addons/test_lint/tests/test_eslint.py index a7a5bfc68eb..35ffdf4ceb4 100644 --- a/odoo/addons/test_lint/tests/test_eslint.py +++ b/odoo/addons/test_lint/tests/test_eslint.py @@ -6,8 +6,8 @@ import re import subprocess from unittest import skipIf from odoo import tools -from odoo.modules.module import get_resource_path from odoo.tests import tagged +from odoo.tools.misc import file_path from . import lint_case @@ -32,7 +32,7 @@ class TestESLint(lint_case.LintCase): if not re.match('.*/libs?/.*', p) # don't check libraries if not re.match('.*/o_spreadsheet/o_spreadsheet.js', p) # don't check generated code ] - eslintrc_path = get_resource_path('test_lint', 'tests', 'eslintrc') + eslintrc_path = file_path('test_lint/tests/eslintrc') _logger.info('Testing %s js files', len(files_to_check)) # https://eslint.org/docs/user-guide/command-line-interface diff --git a/odoo/addons/test_lint/tests/test_l10n.py b/odoo/addons/test_lint/tests/test_l10n.py index 225ce9d2bcc..9d6f141613b 100644 --- a/odoo/addons/test_lint/tests/test_l10n.py +++ b/odoo/addons/test_lint/tests/test_l10n.py @@ -6,7 +6,7 @@ import itertools import os from . import lint_case - +from odoo.tools.misc import file_open class L10nChecker(lint_case.NodeVisitor): def matches_tagged(self, node): @@ -44,7 +44,7 @@ class L10nLinter(lint_case.LintCase): checker = L10nChecker() rs = [] for path in self.iter_module_files('**/l10n_*/tests/*.py'): - with open(path, 'rb') as f: + with file_open(path, 'rb') as f: t = ast.parse(f.read(), path) rs.extend(zip(itertools.repeat(os.path.relpath(path)), checker.visit(t))) diff --git a/odoo/addons/test_lint/tests/test_manifests.py b/odoo/addons/test_lint/tests/test_manifests.py index d5fd176e518..77a6adde81f 100644 --- a/odoo/addons/test_lint/tests/test_manifests.py +++ b/odoo/addons/test_lint/tests/test_manifests.py @@ -2,11 +2,12 @@ import logging from ast import literal_eval +from os.path import join as opj from odoo.modules import get_modules -from odoo.modules.module import _DEFAULT_MANIFEST, module_manifest, get_module_path, get_module_resource +from odoo.modules.module import _DEFAULT_MANIFEST, module_manifest, get_module_path from odoo.tests import BaseCase -from odoo.tools import file_open +from odoo.tools.misc import file_open, file_path _logger = logging.getLogger(__name__) @@ -105,8 +106,9 @@ class ManifestLinter(BaseCase): module) else: path_parts = value.split('/') - path = get_module_resource(path_parts[1], *path_parts[2:]) - if not path: + try: + file_path(opj(*path_parts[1:])) + except FileNotFoundError: _logger.warning( "Icon value specified in manifest of module %s wasn't found in given path." " Please specify a correct value or remove this key from the manifest.", diff --git a/odoo/addons/test_lint/tests/test_onchange_domains.py b/odoo/addons/test_lint/tests/test_onchange_domains.py index b67472e7541..44a521f901b 100644 --- a/odoo/addons/test_lint/tests/test_onchange_domains.py +++ b/odoo/addons/test_lint/tests/test_onchange_domains.py @@ -3,7 +3,7 @@ import itertools import os from . import lint_case - +from odoo.tools.misc import file_open class OnchangeChecker(lint_case.NodeVisitor): def matches_onchange(self, node): @@ -37,7 +37,7 @@ class TestOnchangeDomains(lint_case.LintCase): checker = OnchangeChecker() rs = [] for path in self.iter_module_files('*.py'): - with open(path, 'rb') as f: + with file_open(path, 'rb') as f: t = ast.parse(f.read(), path) rs.extend(zip(itertools.repeat(os.path.relpath(path)), checker.visit(t))) diff --git a/odoo/addons/test_lint/tests/test_pofile.py b/odoo/addons/test_lint/tests/test_pofile.py index 61f34a4cc94..f9f8e7baa72 100644 --- a/odoo/addons/test_lint/tests/test_pofile.py +++ b/odoo/addons/test_lint/tests/test_pofile.py @@ -3,9 +3,10 @@ from collections import Counter -from odoo.modules import get_modules, get_resource_path +from odoo.modules import get_modules from odoo.tests.common import TransactionCase from odoo.tools.translate import TranslationFileReader +from odoo.tools.misc import file_path class PotLinter(TransactionCase): @@ -21,8 +22,9 @@ class PotLinter(TransactionCase): # retrieve all modules, and their corresponding POT file for module in get_modules(): - filename = get_resource_path(module, 'i18n', module + '.pot') - if not filename: + try: + filename = file_path(f'{module}/i18n/{module}.pot') + except FileNotFoundError: continue counts = Counter(map(format, TranslationFileReader(filename))) duplicates = [key for key, count in counts.items() if count > 1] diff --git a/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py b/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py index 2568cd21db9..74f66268469 100644 --- a/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py +++ b/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py @@ -2,10 +2,11 @@ import os.path from odoo.tests.common import BaseCase +from odoo.tools.misc import file_open from odoo.tools.mimetypes import guess_mimetype def contents(extension): - with open(os.path.join( + with file_open(os.path.join( os.path.dirname(__file__), 'testfiles', 'case.{}'.format(extension) diff --git a/odoo/modules/db.py b/odoo/modules/db.py index 872c54177b0..fc7e6989996 100644 --- a/odoo/modules/db.py +++ b/odoo/modules/db.py @@ -25,8 +25,9 @@ def initialize(cr): and ir_model_data entries. """ - f = odoo.modules.get_module_resource('base', 'data', 'base_data.sql') - if not f: + try: + f = odoo.tools.misc.file_path('base/data/base_data.sql') + except FileNotFoundError: m = "File not found: 'base.sql' (provided by module 'base')." _logger.critical(m) raise IOError(m) diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py index 37081264f02..e55980aef7b 100644 --- a/odoo/modules/migration.py +++ b/odoo/modules/migration.py @@ -11,10 +11,10 @@ import os import re from os.path import join as opj -from odoo.modules.module import get_resource_path import odoo.release as release import odoo.upgrade from odoo.tools.parse_version import parse_version +from odoo.tools.misc import file_path _logger = logging.getLogger(__name__) @@ -47,7 +47,7 @@ VERSION_RE = re.compile( def load_script(path, module_name): - full_path = get_resource_path(*path.split(os.path.sep)) if not os.path.isabs(path) else path + full_path = file_path(path) if not os.path.isabs(path) else path spec = importlib.util.spec_from_file_location(module_name, full_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) @@ -122,14 +122,21 @@ class MigrationManager(object): if _verify_upgrade_version(path, version) } + def check_path(path): + try: + return file_path(path) + except FileNotFoundError: + return False + for pkg in self.graph: if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade' or getattr(pkg, 'load_state', None) == 'to upgrade'): continue + self.migrations[pkg.name] = { - 'module': get_scripts(get_resource_path(pkg.name, 'migrations')), - 'module_upgrades': get_scripts(get_resource_path(pkg.name, 'upgrades')), + 'module': get_scripts(check_path(pkg.name + '/migrations')), + 'module_upgrades': get_scripts(check_path(pkg.name + '/upgrades')), } scripts = defaultdict(list) diff --git a/odoo/modules/module.py b/odoo/modules/module.py index 79d6a9f3511..659a93e5509 100644 --- a/odoo/modules/module.py +++ b/odoo/modules/module.py @@ -243,17 +243,18 @@ def get_resource_from_path(path): return None def get_module_icon(module): - iconpath = ['static', 'description', 'icon.png'] - if get_module_resource(module, *iconpath): - return ('/' + module + '/') + '/'.join(iconpath) - return '/base/' + '/'.join(iconpath) + fpath = f"{module}/static/description/icon.png" + try: + file_path(fpath) + return "/" + fpath + except FileNotFoundError: + return "/base/static/description/icon.png" def get_module_icon_path(module): - iconpath = ['static', 'description', 'icon.png'] - path = get_module_resource(module.name, *iconpath) - if not path: - path = get_module_resource('base', *iconpath) - return path + try: + return file_path(f"{module}/static/description/icon.png") + except FileNotFoundError: + return file_path("base/static/description/icon.png") def module_manifest(path): """Returns path to module manifest if one can be found under `path`, else `None`.""" diff --git a/odoo/modules/neutralize.py b/odoo/modules/neutralize.py index 38b38eb5ef0..e8a08338142 100644 --- a/odoo/modules/neutralize.py +++ b/odoo/modules/neutralize.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from contextlib import suppress + import odoo import logging @@ -17,8 +19,8 @@ def get_installed_modules(cursor): def get_neutralization_queries(modules): # neutralization for each module for module in modules: - filename = odoo.modules.get_module_resource(module, 'data/neutralize.sql') - if filename: + filename = f'{module}/data/neutralize.sql' + with suppress(FileNotFoundError): with odoo.tools.misc.file_open(filename) as file: yield file.read().strip() diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index 2df1c5a5ccf..c3b1588e429 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -28,7 +28,7 @@ except ImportError: import odoo from . import pycompat from .config import config -from .misc import file_open, unquote, ustr, SKIPPED_ELEMENT_TYPES +from .misc import file_open, file_path, SKIPPED_ELEMENT_TYPES from .translate import _ from odoo import SUPERUSER_ID, api from odoo.exceptions import ValidationError @@ -155,9 +155,10 @@ def _eval_xml(self, node, env): # after that, only text content makes sense data = pycompat.to_text(data) if t == 'file': - from ..modules import module path = data.strip() - if not module.get_module_resource(self.module, path): + try: + file_path(os.path.join(self.module, path)) + except FileNotFoundError: raise IOError("No such file or directory: '%s' in %s" % ( path, self.module)) return '%s,%s' % (self.module, path) diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index ee2dfe3d9bd..8968a77cb80 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -428,10 +428,9 @@ def scan_languages(): :returns: a list of (lang_code, lang_name) pairs :rtype: [(str, unicode)] """ - csvpath = odoo.modules.module.get_resource_path('base', 'data', 'res.lang.csv') try: # read (code, name) from languages in base/data/res.lang.csv - with open(csvpath, 'rb') as csvfile: + with file_open('base/data/res.lang.csv', 'rb') as csvfile: reader = pycompat.csv_reader(csvfile, delimiter=',', quotechar='"') fields = next(reader) code_index = fields.index("code") @@ -441,7 +440,7 @@ def scan_languages(): for row in reader ] except Exception: - _logger.error("Could not read %s", csvpath) + _logger.error("Could not read res.lang.csv") result = [] return sorted(result or [('en_US', u'English')], key=itemgetter(1)) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index d5228d3f6ed..745ec634bbc 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -17,6 +17,7 @@ import tarfile import threading import warnings from collections import defaultdict, namedtuple +from contextlib import suppress from datetime import datetime from os.path import join @@ -27,9 +28,8 @@ from psycopg2.extras import Json import odoo from odoo.exceptions import UserError -from odoo.modules.module import get_resource_path from . import config, pycompat -from .misc import file_open, get_iso_codes, SKIPPED_ELEMENT_TYPES +from .misc import file_open, file_path, get_iso_codes, SKIPPED_ELEMENT_TYPES _logger = logging.getLogger(__name__) @@ -1146,7 +1146,7 @@ class TranslationModuleReader: if not module: return extra_comments = extra_comments or [] - src_file = open(fabsolutepath, 'rb') + src_file = file_open(fabsolutepath, 'rb') options = {} if extract_method == 'python': options['encoding'] = 'UTF-8' @@ -1239,7 +1239,8 @@ class TranslationImporter: the language must be present and activated in the database :param xmlids: if given, only translations for records with xmlid in xmlids will be loaded """ - with file_open(filepath, mode='rb') as fileobj: + with suppress(FileNotFoundError), file_open(filepath, mode='rb') as fileobj: + _logger.info('loading base translation file %s for language %s', filepath, lang) fileformat = os.path.splitext(filepath)[-1][1:].lower() self.load(fileobj, fileformat, lang, xmlids=xmlids) @@ -1484,12 +1485,15 @@ def get_po_paths(module_name: str, lang: str): else: langs = [lang_base, lang] - return [ + po_paths = [ path for lang_ in langs for dir_ in ('i18n', 'i18n_extra') - if (path := get_resource_path(module_name, dir_, lang_ + '.po')) + if (path := join(module_name, dir_, lang_ + '.po')) ] + for path in po_paths: + with suppress(FileNotFoundError): + yield file_path(path) class CodeTranslations: