Skip to content

Commit

Permalink
Merge PR #3205 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by simahawk
  • Loading branch information
OCA-git-bot committed Feb 21, 2025
2 parents cb5783b + 2d5d36b commit 0f6047b
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 61 deletions.
6 changes: 6 additions & 0 deletions .oca/oca-port/blacklist/jsonifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pull_requests": {
"OCA/server-tools#2744": "too many conflicts, will run pre-commit afterwards",
"OCA/server-tools#2668": "Ported manually bc of too many conflicts"
}
}
7 changes: 7 additions & 0 deletions jsonifier/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2022 Camptocamp SA (http://www.camptocamp.com)
# Simone Orsi <simahawk@gmail.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).


class SwallableException(Exception):
"""An exception that can be safely skipped."""
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
10 changes: 8 additions & 2 deletions jsonifier/models/ir_exports_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@

help_message = [
"Compute the result from 'value' by setting the variable 'result'.",
"For fields resolvers:",
"\n" "For fields resolvers:",
":param record: the record",
":param name: name of the field",
":param value: value of the field",
":param field_type: type of the field",
"For global resolvers:",
"\n" "For global resolvers:",
":param value: JSON dict",
":param record: the record",
"\n"
"In both types, you can override the final json key."
"\nTo achieve this, simply return a dict like: "
"\n{'result': {'_value': $value, '_json_key': $new_json_key}}",
]


Expand Down Expand Up @@ -42,6 +47,7 @@ def resolve(self, param, records):
else: # param is a field
for record in records:
values = {
"record": record,
"value": record[param.name],
"name": param.name,
"field_type": param.type,
Expand Down
150 changes: 105 additions & 45 deletions jsonifier/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from odoo.tools.misc import format_duration
from odoo.tools.translate import _

from ..exceptions import SwallableException
from .utils import convert_simple_to_full_parser

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,60 +72,112 @@ 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_key in parser:
field_dict, subparser = rec.__parse_field(field_key)
field_name = field_dict["name"]
field = rec._fields.get(field_name)
for field in parser:
field_dict, subparser = rec.__parse_field(field)
function = field_dict.get("function")
if not field and not function:
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},
)
continue
json_key = field_dict.get("target", field_name)
try:
self._jsonify_record_validate_field(rec, field_dict, strict)
except SwallableException:
if not function:
continue
json_key = field_dict.get("target", field_dict["name"])
if function:
try:
value = self._function_value(rec, function, field_name)
except UserError:
if strict:
raise
if not tools.config["test_enable"]:
_logger.error(
"%(model)s.%(func)s not available",
{"model": self._name, "func": str(function)},
)
value = self._jsonify_record_handle_function(
rec, field_dict, strict
)
except SwallableException:
continue
elif subparser:
if not (field.relational or field.type == "reference"):
if strict:
self._jsonify_bad_parser_error(field_name)
if not tools.config["test_enable"]:
_logger.error(
"%(model)s.%(fname)s not relational",
{"model": self._name, "fname": field_name},
)
try:
value = self._jsonify_record_handle_subparser(
rec, field_dict, strict, subparser
)
except SwallableException:
continue
value = [
self._jsonify_record(subparser, r, {}) for r in rec[field_name]
]
if field.type in ("many2one", "reference"):
value = value[0] if value else None
else:
resolver = field_dict.get("resolver")
field = rec._fields[field_dict["name"]]
value = rec._jsonify_value(field, rec[field.name])
value = resolver.resolve(field, rec)[0] if resolver else value

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
)
# whatever json value we have found in subparser or not ass a sister key
# on the same level _fieldname_{json_key}
if rec.env.context.get("with_fieldname"):
json_key_fieldname = "_fieldname_" + json_key
# check if we are in a subparser has already the fieldname sister keys
fieldname_value = rec._fields[field_dict["name"]].string
self._add_json_key(root, json_key_fieldname, fieldname_value)
self._add_json_key(root, json_key, value)
return root

def jsonify(self, parser, one=False):
def _jsonify_record_validate_field(self, rec, field_dict, strict):
field_name = field_dict["name"]
if field_name not in rec._fields:
if strict:
# let it fail
rec._fields[field_name] # pylint: disable=pointless-statement
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):
field_name = field_dict["name"]
function = field_dict["function"]
try:
return self._function_value(rec, function, field_name)
except UserError as err:
if strict:
raise
if not tools.config["test_enable"]:
_logger.error(
"%(model)s.%(func)s not available",
{"model": self._name, "func": str(function)},
)
raise SwallableException() from err

def _jsonify_record_handle_subparser(self, rec, field_dict, strict, subparser):
field_name = field_dict["name"]
field = rec._fields[field_name]
if not (field.relational or field.type == "reference"):
if strict:
self._jsonify_bad_parser_error(field_name)
if not tools.config["test_enable"]:
_logger.error(
"%(model)s.%(fname)s not relational",
{"model": self._name, "fname": field_name},
)
raise SwallableException()
value = [self._jsonify_record(subparser, r, {}) for r in rec[field_name]]

if field.type in ("many2one", "reference"):
value = value[0] if value else None

return value

def _jsonify_record_handle_resolver(self, rec, field, resolver, json_key):
value = rec._jsonify_value(field, rec[field.name])
value = resolver.resolve(field, rec)[0] if resolver else value
if isinstance(value, dict) and "_json_key" in value and "_value" in value:
# Allow override of json_key.
# In this case,
# the final value must be encapsulated into _value key
value, json_key = value["_value"], value["_json_key"]
return value, json_key

def jsonify(self, parser, one=False, with_fieldname=False):
"""Convert the record according to the given parser.
Example of (simple) parser:
Expand Down Expand Up @@ -156,12 +209,19 @@ def jsonify(self, parser, one=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
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):
self._jsonify_record(parsers[lang], record, json)

Expand Down
28 changes: 28 additions & 0 deletions jsonifier/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
with_fieldname parameter
==========================

The with_fieldname option of jsonify() method, when true, will inject on
the same level of the data "_fieldname_$field" keys that will
contain the field name, in the language of the current user.


Examples of with_fieldname usage:

.. code-block:: python
# example 1
parser = [('name')]
a.jsonify(parser=parser)
[{'name': 'SO3996'}]
>>> a.jsonify(parser=parser, with_fieldname=False)
[{'name': 'SO3996'}]
>>> a.jsonify(parser=parser, with_fieldname=True)
[{'fieldname_name': 'Order Reference', 'name': 'SO3996'}}]
# example 2 - with a subparser-
parser=['name', 'create_date', ('order_line', ['id' , 'product_uom', 'is_expense'])]
>>> a.jsonify(parser=parser, with_fieldname=False)
[{'name': 'SO3996', 'create_date': '2015-06-02T12:18:26.279909+00:00', 'order_line': [{'id': 16649, 'product_uom': 'stuks', 'is_expense': False}, {'id': 16651, 'product_uom': 'stuks', 'is_expense': False}, {'id': 16650, 'product_uom': 'stuks', 'is_expense': False}]}]
>>> a.jsonify(parser=parser, with_fieldname=True)
[{'fieldname_name': 'Order Reference', 'name': 'SO3996', 'fieldname_create_date': 'Creation Date', 'create_date': '2015-06-02T12:18:26.279909+00:00', 'fieldname_order_line': 'Order Lines', 'order_line': [{'fieldname_id': 'ID', 'id': 16649, 'fieldname_product_uom': 'Unit of Measure', 'product_uom': 'stuks', 'fieldname_is_expense': 'Is expense', 'is_expense': False}]}]
Loading

0 comments on commit 0f6047b

Please sign in to comment.