Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[18.0][FWD] jsonifier: misc imp/fix from 16.0 and 14.0 #3206

Merged
5 changes: 5 additions & 0 deletions .oca/oca-port/blacklist/jsonifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"pull_requests": {
"OCA/server-tools#2762": "already ported"
}
}
5 changes: 3 additions & 2 deletions jsonifier/models/ir_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,16 @@ 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]:
parser["fields"] = lang_parsers[False]
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
48 changes: 30 additions & 18 deletions jsonifier/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)
Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
67 changes: 57 additions & 10 deletions jsonifier/tests/test_get_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# Simone Orsi <[email protected]>
# 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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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