diff --git a/.oca/oca-port/blacklist/jsonifier.json b/.oca/oca-port/blacklist/jsonifier.json new file mode 100644 index 00000000000..9a2622bcf1a --- /dev/null +++ b/.oca/oca-port/blacklist/jsonifier.json @@ -0,0 +1,5 @@ +{ + "pull_requests": { + "OCA/server-tools#2762": "already ported" + } +} diff --git a/jsonifier/models/ir_exports.py b/jsonifier/models/ir_exports.py index c4cca1b7ec6..6e621d651aa 100644 --- a/jsonifier/models/ir_exports.py +++ b/jsonifier/models/ir_exports.py @@ -109,7 +109,8 @@ def get_json_parser(self): if line.target: names = line.target.split("/") function = line.instance_method_name - options = {"resolver": line.resolver_id, "function": function} + # resolver must be passed as ID to avoid cache issues + options = {"resolver": line.resolver_id.id, "function": function} update_dict(dict_parser, names, options) lang_parsers[lang] = convert_dict(dict_parser) if list(lang_parsers.keys()) == [False]: @@ -117,7 +118,7 @@ def get_json_parser(self): else: parser["langs"] = lang_parsers if self.global_resolver_id: - parser["resolver"] = self.global_resolver_id + parser["resolver"] = self.global_resolver_id.id if self.language_agnostic: parser["language_agnostic"] = self.language_agnostic return parser diff --git a/jsonifier/models/models.py b/jsonifier/models/models.py index 44582f2c80d..55ae11cdf7e 100644 --- a/jsonifier/models/models.py +++ b/jsonifier/models/models.py @@ -71,14 +71,19 @@ def _add_json_key(self, values, json_key, value): def _jsonify_record(self, parser, rec, root): """JSONify one record (rec). Private function called by jsonify.""" strict = self.env.context.get("jsonify_record_strict", False) - for field in parser: - field_dict, subparser = rec.__parse_field(field) + for field_key in parser: + field_dict, subparser = rec.__parse_field(field_key) + function = field_dict.get("function") try: self._jsonify_record_validate_field(rec, field_dict, strict) except SwallableException: - continue + if not function: + # If we have a function we can use it to get the value + # even if the field is not available. + # If not, well there's nothing we can do. + continue json_key = field_dict.get("target", field_dict["name"]) - if field_dict.get("function"): + if function: try: value = self._jsonify_record_handle_function( rec, field_dict, strict @@ -97,6 +102,9 @@ def _jsonify_record(self, parser, rec, root): value = rec._jsonify_value(field, rec[field.name]) resolver = field_dict.get("resolver") if resolver: + if isinstance(resolver, int): + # cached versions of the parser are stored as integer + resolver = self.env["ir.exports.resolver"].browse(resolver) value, json_key = self._jsonify_record_handle_resolver( rec, field, resolver, json_key ) @@ -116,15 +124,15 @@ def _jsonify_record_validate_field(self, rec, field_dict, strict): if strict: # let it fail rec._fields[field_name] # pylint: disable=pointless-statement - if not tools.config["test_enable"]: - # If running live, log proper error - # so that techies can track it down - _logger.error( - "%(model)s.%(fname)s not available", - {"model": self._name, "fname": field_name}, - ) + else: + if not tools.config["test_enable"]: + # If running live, log proper error + # so that techies can track it down + _logger.warning( + "%(model)s.%(fname)s not available", + {"model": self._name, "fname": field_name}, + ) raise SwallableException() - return True def _jsonify_record_handle_function(self, rec, field_dict, strict): @@ -203,16 +211,20 @@ def jsonify(self, parser, one=False, with_fieldname=False): if isinstance(parser, list): parser = convert_simple_to_full_parser(parser) resolver = parser.get("resolver") - + if isinstance(resolver, int): + # cached versions of the parser are stored as integer + resolver = self.env["ir.exports.resolver"].browse(resolver) results = [{} for record in self] parsers = {False: parser["fields"]} if "fields" in parser else parser["langs"] for lang in parsers: translate = lang or parser.get("language_agnostic") - records = self.with_context(lang=lang) if translate else self - records = ( - records.with_context(with_fieldname=True) if with_fieldname else records - ) - for record, json in zip(records, results, strict=True): + new_ctx = {} + if translate: + new_ctx["lang"] = lang + if with_fieldname: + new_ctx["with_fieldname"] = True + records = self.with_context(**new_ctx) if new_ctx else self + for record, json in zip(records, results, strict=False): self._jsonify_record(parsers[lang], record, json) if resolver: diff --git a/jsonifier/tests/test_get_parser.py b/jsonifier/tests/test_get_parser.py index 364e9ac2105..0f587ec5a48 100644 --- a/jsonifier/tests/test_get_parser.py +++ b/jsonifier/tests/test_get_parser.py @@ -3,7 +3,6 @@ # Simone Orsi # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -from unittest import mock from odoo import tools from odoo.exceptions import UserError @@ -214,6 +213,51 @@ def test_json_export(self): "_fieldname_partner_latitude": "Geo Latitude", "create_date": "2019-10-31T14:39:49", } + expected_json_with_fieldname = { + "_fieldname_lang": "Language", + "lang": "en_US", + "_fieldname_comment": "Notes", + "comment": None, + "_fieldname_partner_latitude": "Geo Latitude", + "_fieldname_name": "Name", + "name": "Akretion", + "_fieldname_color": "Color Index", + "color": 0, + "_fieldname_children": "Contact", + "children": [ + { + "_fieldname_children": "Contact", + "children": [], + "_fieldname_email": "Email", + "email": None, + "_fieldname_country": "Country", + "country": { + "_fieldname_code": "Country Code", + "code": "FR", + "_fieldname_name": "Country Name", + "name": "France", + }, + "_fieldname_name": "Name", + "name": "Sebatien Beau", + "_fieldname_id": "ID", + "id": self.partner.child_ids.id, + } + ], + "_fieldname_country": "Country", + "country": { + "_fieldname_code": "Country Code", + "code": "FR", + "_fieldname_name": "Country Name", + "name": "France", + }, + "_fieldname_active": "Active", + "active": True, + "_fieldname_category_id": "Tags", + "category_id": [{"_fieldname_name": "Name", "name": "Inovator"}], + "_fieldname_create_date": "Created on", + "create_date": "2019-10-31T14:39:49", + "partner_latitude": 0.0, + } json_partner = self.partner.jsonify(parser) self.assertDictEqual(json_partner[0], expected_json) json_partner_with_fieldname = self.partner.jsonify( @@ -252,10 +296,12 @@ def test_json_export_callable_parser(self): # callable subparser ("name", lambda rec, fname: rec[fname] + " rocks!"), ("name:custom", "jsonify_custom"), + ("unknown_field", lambda rec, fname: "yeah again!"), ] expected_json = { "name": "Akretion rocks!", "custom": "yeah!", + "unknown_field": "yeah again!", } json_partner = self.partner.jsonify(parser) self.assertDictEqual(json_partner[0], expected_json) @@ -378,24 +424,25 @@ def test_bad_parsers_strict(self): def test_bad_parsers_fail_gracefully(self): rec = self.category - logger_patch_path = "odoo.addons.jsonifier.models.models._logger.error" - - # logging is disabled when testing as it's useless and makes build fail. + # logging is disabled when testing as it makes too much noise tools.config["test_enable"] = False + logger_name = "odoo.addons.jsonifier.models.models" bad_field_name = ["Name"] - with mock.patch(logger_patch_path) as mocked_logger: + with self.assertLogs(logger=logger_name, level="WARNING") as capt: rec.jsonify(bad_field_name, one=True) - mocked_logger.assert_called() + self.assertIn("res.partner.category.Name not availabl", capt.output[0]) bad_function_name = {"fields": [{"name": "name", "function": "notafunction"}]} - with mock.patch(logger_patch_path) as mocked_logger: + with self.assertLogs(logger=logger_name, level="WARNING") as capt: rec.jsonify(bad_function_name, one=True) - mocked_logger.assert_called() + self.assertIn( + "res.partner.category.notafunction not available", capt.output[0] + ) bad_subparser = {"fields": [({"name": "name"}, [{"name": "subparser_name"}])]} - with mock.patch(logger_patch_path) as mocked_logger: + with self.assertLogs(logger=logger_name, level="WARNING") as capt: rec.jsonify(bad_subparser, one=True) - mocked_logger.assert_called() + self.assertIn("res.partner.category.name not relational", capt.output[0]) tools.config["test_enable"] = True