server-tools/base_jsonify/models/models.py

156 lines
6.0 KiB
Python

# Copyright 2017 Akretion (http://www.akretion.com)
# Sébastien BEAU <sebastien.beau@akretion.com>
# Raphaël Reverdy <raphael.reverdy@akretion.com>
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# Simone Orsi <simahawk@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools.translate import _
class Base(models.AbstractModel):
_inherit = "base"
@api.model
def __parse_field(self, parser_field):
"""Deduct how to handle a field from its parser."""
return parser_field if isinstance(parser_field, tuple) else (parser_field, None)
@api.model
def convert_simple_to_full_parser(self, parser):
def _f(f, function=None):
field_split = f.split(":")
field_dict = {"name": field_split[0]}
if len(field_split) > 1:
field_dict["alias"] = field_split[1]
if function:
field_dict["function"] = function
return field_dict
def _convert_parser(parser):
result = []
for line in parser:
if isinstance(line, str):
result.append(_f(line))
else:
f, sub = line
if callable(sub) or isinstance(sub, str):
result.append(_f(f, sub))
else:
result.append((_f(f), _convert_parser(sub)))
return result
return {"language_agnostic": False, "fields": _convert_parser(parser)}
@api.model
def _jsonify_bad_parser_error(self, field_name):
raise UserError(_("Wrong parser configuration for field: `%s`") % field_name)
def _function_value(self, record, function, field_name):
if function in dir(record):
method = getattr(self, function, None)
return method(field_name)
elif callable(function):
return function(record, field_name)
else:
return self._jsonify_bad_parser_error(field_name)
@api.model
def _jsonify_value(self, record, field, resolver):
# TODO: we should get default by field (eg: char field -> "")
value = record[field.name]
if value is False and field.type != "boolean":
value = None
elif field.type == "date":
value = fields.Date.to_date(value).isoformat()
elif field.type == "datetime":
# Ensures value is a datetime
value = fields.Datetime.to_datetime(value)
# Get the timestamp converted to the client's timezone.
# This call also add the tzinfo into the datetime object
value = fields.Datetime.context_timestamp(record, value)
value = value.isoformat()
return self._resolve(resolver, field, record)[0] if resolver else value
@api.model
def _resolve(self, resolver, *args):
if isinstance(resolver, int):
resolver = self.env["ir.exports.resolver"].browse(resolver)
return resolver.eval(*args)
@api.model
def _jsonify_record(self, parser, rec, root):
for field in parser:
field_dict, subparser = rec.__parse_field(field)
field_name = field_dict["name"]
json_key = field_dict.get("alias", field_name)
field = rec._fields[field_name]
if field_dict.get("function"):
function = field_dict["function"]
value = self._function_value(rec, function, field_name)
elif subparser:
if not (field.relational or field.type == "reference"):
self._jsonify_bad_parser_error(field_name)
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:
value = self._jsonify_value(rec, field, field_dict.get("resolver"))
if json_key.endswith("*"): # sublist field
key = json_key[:-1]
if not root.get(key):
root[key] = []
root[key].append(value)
else:
root[json_key] = value
return root
def jsonify(self, parser, one=False):
"""Convert the record according to the given parser.
Example of (simple) parser:
parser = [
'name',
'number',
'create_date',
('partner_id', ['id', 'display_name', 'ref'])
('shipping_id', callable)
('delivery_id', "record_method")
('line_id', ['id', ('product_id', ['name']), 'price_unit'])
]
In order to be consistent with the odoo api the jsonify method always
return a list of object even if there is only one element in input.
You can change this behavior by passing `one=True` to get only one element.
By default the key into the json is the name of the field extracted
from the model. If you need to specify an alternate name to use as
key, you can define your mapping as follow into the parser definition:
parser = [
'field_name:json_key'
]
"""
if one:
self.ensure_one()
if isinstance(parser, list):
parser = self.convert_simple_to_full_parser(parser)
resolver = parser.get("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
for record, json in zip(records, results):
self._jsonify_record(parsers[lang], record, json)
results = self._resolve(resolver, results, self) if resolver else results
return results[0] if one else results