mangono-addon-export_json 18.0.1.0.4__py3-none-any.whl → 19.0.1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/METADATA +2 -3
  2. mangono_addon_export_json-19.0.1.0.1.dist-info/RECORD +52 -0
  3. odoo/addons/export_json/README.rst +2 -2
  4. odoo/addons/export_json/__init__.py +4 -0
  5. odoo/addons/export_json/__manifest__.py +10 -3
  6. odoo/addons/export_json/controller/main.py +1 -1
  7. odoo/addons/export_json/demo/export_demo.xml +7 -0
  8. odoo/addons/export_json/demo/ir.exports.line.csv +16 -0
  9. odoo/addons/export_json/demo/resolver_demo.xml +12 -0
  10. odoo/addons/export_json/jsonifier/README.rst +284 -0
  11. odoo/addons/export_json/jsonifier/__init__.py +1 -0
  12. odoo/addons/export_json/jsonifier/__manifest__.old +27 -0
  13. odoo/addons/export_json/jsonifier/demo/export_demo.xml +7 -0
  14. odoo/addons/export_json/jsonifier/demo/ir.exports.line.csv +16 -0
  15. odoo/addons/export_json/jsonifier/demo/resolver_demo.xml +12 -0
  16. odoo/addons/export_json/jsonifier/exceptions.py +7 -0
  17. odoo/addons/export_json/jsonifier/i18n/ca.po +234 -0
  18. odoo/addons/export_json/jsonifier/i18n/es.po +266 -0
  19. odoo/addons/export_json/jsonifier/i18n/it.po +277 -0
  20. odoo/addons/export_json/jsonifier/i18n/zh_CN.po +260 -0
  21. odoo/addons/export_json/jsonifier/models/__init__.py +5 -0
  22. odoo/addons/export_json/jsonifier/models/ir_exports.py +124 -0
  23. odoo/addons/export_json/jsonifier/models/ir_exports_line.py +55 -0
  24. odoo/addons/export_json/jsonifier/models/ir_exports_resolver.py +58 -0
  25. odoo/addons/export_json/jsonifier/models/models.py +263 -0
  26. odoo/addons/export_json/jsonifier/models/utils.py +35 -0
  27. odoo/addons/export_json/jsonifier/pyproject.toml +3 -0
  28. odoo/addons/export_json/jsonifier/readme/CONTRIBUTORS.md +8 -0
  29. odoo/addons/export_json/jsonifier/readme/CREDITS.md +1 -0
  30. odoo/addons/export_json/jsonifier/readme/DESCRIPTION.md +163 -0
  31. odoo/addons/export_json/jsonifier/readme/USAGE.md +26 -0
  32. odoo/addons/export_json/jsonifier/security/ir.model.access.csv +2 -0
  33. odoo/addons/export_json/jsonifier/tests/__init__.py +3 -0
  34. odoo/addons/export_json/jsonifier/tests/test_get_parser.py +436 -0
  35. odoo/addons/export_json/jsonifier/tests/test_helpers.py +45 -0
  36. odoo/addons/export_json/jsonifier/tests/test_ir_exports_line.py +66 -0
  37. odoo/addons/export_json/jsonifier/views/ir_exports_resolver_view.xml +26 -0
  38. odoo/addons/export_json/jsonifier/views/ir_exports_view.xml +38 -0
  39. odoo/addons/export_json/models/file_json.py +1 -2
  40. odoo/addons/export_json/tests/test_export_json.py +14 -11
  41. mangono_addon_export_json-18.0.1.0.4.dist-info/RECORD +0 -20
  42. {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/WHEEL +0 -0
  43. {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,55 @@
1
+ # Copyright 2017 ACSONE SA/NV
2
+ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
3
+
4
+ from odoo import _, api, fields, models
5
+ from odoo.exceptions import ValidationError
6
+
7
+
8
+ class IrExportsLine(models.Model):
9
+ _inherit = "ir.exports.line"
10
+
11
+ target = fields.Char(
12
+ help="The complete path to the field where you can specify a target on the step as field:target",
13
+ )
14
+ active = fields.Boolean(default=True)
15
+ lang_id = fields.Many2one(
16
+ comodel_name="res.lang",
17
+ string="Language",
18
+ help="If set, the language in which the field is exported",
19
+ )
20
+ resolver_id = fields.Many2one(
21
+ comodel_name="ir.exports.resolver",
22
+ string="Custom resolver",
23
+ help="If set, will apply the resolver on the field value",
24
+ )
25
+ instance_method_name = fields.Char(
26
+ string="Function",
27
+ help="A method defined on the model that takes a record and a field_name",
28
+ )
29
+
30
+ @api.constrains("resolver_id", "instance_method_name")
31
+ def _check_function_resolver(self):
32
+ for rec in self:
33
+ if rec.resolver_id and rec.instance_method_name:
34
+ msg = _("Either set a function or a resolver, not both.")
35
+ raise ValidationError(msg)
36
+
37
+ @api.constrains("target", "name")
38
+ def _check_target(self):
39
+ for rec in self:
40
+ if not rec.target:
41
+ continue
42
+ names = rec.name.split("/")
43
+ names_with_target = rec.target.split("/")
44
+ if len(names) != len(names_with_target):
45
+ raise ValidationError(_("Name and Target must have the same hierarchy depth"))
46
+ for name, name_with_target in zip(names, names_with_target, strict=True):
47
+ field_name = name_with_target.split(":")[0]
48
+ if name != field_name:
49
+ raise ValidationError(
50
+ _(
51
+ "The target must reference the same field as in "
52
+ "name '%(name)s' not in '%(name_with_target)s'"
53
+ )
54
+ % dict(name=name, name_with_target=name_with_target)
55
+ )
@@ -0,0 +1,58 @@
1
+ # Copyright 2020 ACSONE SA/NV
2
+ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
3
+
4
+ from odoo import fields, models
5
+ from odoo.tools.safe_eval import safe_eval
6
+
7
+ help_message = [
8
+ "Compute the result from 'value' by setting the variable 'result'.",
9
+ "\nFor fields resolvers:",
10
+ ":param record: the record",
11
+ ":param name: name of the field",
12
+ ":param value: value of the field",
13
+ ":param field_type: type of the field",
14
+ "\nFor global resolvers:",
15
+ ":param value: JSON dict",
16
+ ":param record: the record",
17
+ "\n"
18
+ "In both types, you can override the final json key."
19
+ "\nTo achieve this, simply return a dict like: "
20
+ "\n{'result': {'_value': $value, '_json_key': $new_json_key}}",
21
+ ]
22
+
23
+
24
+ class FieldResolver(models.Model):
25
+ """Arbitrary function to process a field or a dict at export time."""
26
+
27
+ _name = "ir.exports.resolver"
28
+ _description = "Export Resolver"
29
+
30
+ name = fields.Char()
31
+ type = fields.Selection([("field", "Field"), ("global", "Global")])
32
+ python_code = fields.Text(
33
+ default="\n".join(["# " + h for h in help_message] + ["result = value"]),
34
+ help="\n".join(help_message),
35
+ )
36
+
37
+ def resolve(self, param, records):
38
+ self.ensure_one()
39
+ result = []
40
+ context = records.env.context
41
+ if self.type == "global":
42
+ assert len(param) == len(records)
43
+ for value, record in zip(param, records, strict=True):
44
+ values = {"value": value, "record": record, "context": context}
45
+ safe_eval(self.python_code, values, mode="exec", nocopy=True)
46
+ result.append(values["result"])
47
+ else: # param is a field
48
+ for record in records:
49
+ values = {
50
+ "record": record,
51
+ "value": record[param.name],
52
+ "name": param.name,
53
+ "field_type": param.type,
54
+ "context": context,
55
+ }
56
+ safe_eval(self.python_code, values, mode="exec", nocopy=True)
57
+ result.append(values["result"])
58
+ return result
@@ -0,0 +1,263 @@
1
+ # Copyright 2017 Akretion (http://www.akretion.com)
2
+ # Sébastien BEAU <sebastien.beau@akretion.com>
3
+ # Raphaël Reverdy <raphael.reverdy@akretion.com>
4
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
5
+ # Simone Orsi <simahawk@gmail.com>
6
+ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
7
+
8
+ import logging
9
+
10
+ from odoo import api, fields, models, tools
11
+ from odoo.exceptions import UserError
12
+ from odoo.tools.misc import format_duration
13
+ from odoo.tools.translate import _
14
+
15
+ from ..exceptions import SwallableException
16
+ from .utils import convert_simple_to_full_parser
17
+
18
+ _logger = logging.getLogger(__name__)
19
+
20
+
21
+ class Base(models.AbstractModel):
22
+ _inherit = "base"
23
+
24
+ @api.model
25
+ def __parse_field(self, parser_field):
26
+ """Deduct how to handle a field from its parser."""
27
+ return parser_field if isinstance(parser_field, tuple) else (parser_field, None)
28
+
29
+ @api.model
30
+ def _jsonify_bad_parser_error(self, field_name):
31
+ raise UserError(_("Wrong parser configuration for field: `%s`") % field_name)
32
+
33
+ def _function_value(self, record, function, field_name):
34
+ if function in dir(record):
35
+ method = getattr(record, function, None)
36
+ return method(field_name)
37
+ elif callable(function):
38
+ return function(record, field_name)
39
+ else:
40
+ return self._jsonify_bad_parser_error(field_name)
41
+
42
+ @api.model
43
+ def _jsonify_value(self, field, value):
44
+ """Override this function to support new field types."""
45
+ if value is False and field.type != "boolean":
46
+ value = None
47
+ elif field.type == "date":
48
+ value = fields.Date.to_date(value).isoformat()
49
+ elif field.type == "datetime":
50
+ # Ensures value is a datetime
51
+ value = fields.Datetime.to_datetime(value)
52
+ value = value.isoformat()
53
+ elif field.type in ("many2one", "reference"):
54
+ value = value.display_name if value else None
55
+ elif field.type in ("one2many", "many2many"):
56
+ value = [v.display_name for v in value]
57
+ return value
58
+
59
+ @api.model
60
+ def _add_json_key(self, values, json_key, value):
61
+ """To manage defaults, you can use a specific resolver."""
62
+ key, sep, marshaller = json_key.partition("=")
63
+ if marshaller == "list": # sublist field
64
+ if not values.get(key):
65
+ values[key] = []
66
+ values[key].append(value)
67
+ else:
68
+ values[key] = value
69
+
70
+ @api.model
71
+ def _jsonify_record(self, parser, rec, root):
72
+ """JSONify one record (rec). Private function called by jsonify."""
73
+ strict = self.env.context.get("jsonify_record_strict", False)
74
+ for field_key in parser:
75
+ field_dict, subparser = rec.__parse_field(field_key)
76
+ function = field_dict.get("function")
77
+ try:
78
+ self._jsonify_record_validate_field(rec, field_dict, strict)
79
+ except SwallableException:
80
+ if not function:
81
+ # If we have a function we can use it to get the value
82
+ # even if the field is not available.
83
+ # If not, well there's nothing we can do.
84
+ continue
85
+ json_key = field_dict.get("target", field_dict["name"])
86
+ if function:
87
+ try:
88
+ value = self._jsonify_record_handle_function(rec, field_dict, strict)
89
+ except SwallableException:
90
+ continue
91
+ elif subparser:
92
+ try:
93
+ value = self._jsonify_record_handle_subparser(rec, field_dict, strict, subparser)
94
+ except SwallableException:
95
+ continue
96
+ else:
97
+ field = rec._fields[field_dict["name"]]
98
+ value = rec._jsonify_value(field, rec[field.name])
99
+ resolver = field_dict.get("resolver")
100
+ if resolver:
101
+ if isinstance(resolver, int):
102
+ # cached versions of the parser are stored as integer
103
+ resolver = self.env["ir.exports.resolver"].browse(resolver)
104
+ value, json_key = self._jsonify_record_handle_resolver(rec, field, resolver, json_key)
105
+ # whatever json value we have found in subparser or not ass a sister key
106
+ # on the same level _fieldname_{json_key}
107
+ if rec.env.context.get("with_fieldname"):
108
+ json_key_fieldname = "_fieldname_" + json_key
109
+ # check if we are in a subparser has already the fieldname sister keys
110
+ fieldname_value = rec._fields[field_dict["name"]].string
111
+ self._add_json_key(root, json_key_fieldname, fieldname_value)
112
+ self._add_json_key(root, json_key, value)
113
+ return root
114
+
115
+ def _jsonify_record_validate_field(self, rec, field_dict, strict):
116
+ field_name = field_dict["name"]
117
+ if field_name not in rec._fields:
118
+ if strict:
119
+ # let it fail
120
+ rec._fields[field_name] # pylint: disable=pointless-statement
121
+ else:
122
+ if not tools.config["test_enable"]:
123
+ # If running live, log proper error
124
+ # so that techies can track it down
125
+ _logger.warning(
126
+ "%(model)s.%(fname)s not available",
127
+ {"model": self._name, "fname": field_name},
128
+ )
129
+ raise SwallableException()
130
+ return True
131
+
132
+ def _jsonify_record_handle_function(self, rec, field_dict, strict):
133
+ field_name = field_dict["name"]
134
+ function = field_dict["function"]
135
+ try:
136
+ return self._function_value(rec, function, field_name)
137
+ except UserError as err:
138
+ if strict:
139
+ raise
140
+ if not tools.config["test_enable"]:
141
+ _logger.error(
142
+ "%(model)s.%(func)s not available",
143
+ {"model": self._name, "func": str(function)},
144
+ )
145
+ raise SwallableException() from err
146
+
147
+ def _jsonify_record_handle_subparser(self, rec, field_dict, strict, subparser):
148
+ field_name = field_dict["name"]
149
+ field = rec._fields[field_name]
150
+ if not (field.relational or field.type == "reference"):
151
+ if strict:
152
+ self._jsonify_bad_parser_error(field_name)
153
+ if not tools.config["test_enable"]:
154
+ _logger.error(
155
+ "%(model)s.%(fname)s not relational",
156
+ {"model": self._name, "fname": field_name},
157
+ )
158
+ raise SwallableException()
159
+ value = [self._jsonify_record(subparser, r, {}) for r in rec[field_name]]
160
+
161
+ if field.type in ("many2one", "reference"):
162
+ value = value[0] if value else None
163
+
164
+ return value
165
+
166
+ def _jsonify_record_handle_resolver(self, rec, field, resolver, json_key):
167
+ value = rec._jsonify_value(field, rec[field.name])
168
+ value = resolver.resolve(field, rec)[0] if resolver else value
169
+ if isinstance(value, dict) and "_json_key" in value and "_value" in value:
170
+ # Allow override of json_key.
171
+ # In this case,
172
+ # the final value must be encapsulated into _value key
173
+ value, json_key = value["_value"], value["_json_key"]
174
+ return value, json_key
175
+
176
+ def jsonify(self, parser, one=False, with_fieldname=False):
177
+ """Convert the record according to the given parser.
178
+
179
+ Example of (simple) parser:
180
+ parser = [
181
+ 'name',
182
+ 'number',
183
+ 'create_date',
184
+ ('partner_id', ['id', 'display_name', 'ref'])
185
+ ('shipping_id', callable)
186
+ ('delivery_id', "record_method")
187
+ ('line_id', ['id', ('product_id', ['name']), 'price_unit'])
188
+ ]
189
+
190
+ In order to be consistent with the Odoo API the jsonify method always
191
+ returns a list of objects even if there is only one element in input.
192
+ You can change this behavior by passing `one=True` to get only one element.
193
+
194
+ By default the key into the JSON is the name of the field extracted
195
+ from the model. If you need to specify an alternate name to use as
196
+ key, you can define your mapping as follow into the parser definition:
197
+
198
+ parser = [
199
+ 'field_name:json_key'
200
+ ]
201
+
202
+ """
203
+ if one:
204
+ self.ensure_one()
205
+ if isinstance(parser, list):
206
+ parser = convert_simple_to_full_parser(parser)
207
+ resolver = parser.get("resolver")
208
+ if isinstance(resolver, int):
209
+ # cached versions of the parser are stored as integer
210
+ resolver = self.env["ir.exports.resolver"].browse(resolver)
211
+ results = [{} for record in self]
212
+ parsers = {False: parser["fields"]} if "fields" in parser else parser["langs"]
213
+ for lang in parsers:
214
+ translate = lang or parser.get("language_agnostic")
215
+ new_ctx = {}
216
+ if translate:
217
+ new_ctx["lang"] = lang
218
+ if with_fieldname:
219
+ new_ctx["with_fieldname"] = True
220
+ records = self.with_context(**new_ctx) if new_ctx else self
221
+ for record, json in zip(records, results, strict=False):
222
+ self._jsonify_record(parsers[lang], record, json)
223
+
224
+ if resolver:
225
+ results = resolver.resolve(results, self)
226
+ return results[0] if one else results
227
+
228
+ # HELPERS
229
+
230
+ def _jsonify_m2o_to_id(self, fname):
231
+ """Helper to get an ID only from a m2o field.
232
+
233
+ Example:
234
+
235
+ <field name="name">m2o_id</field>
236
+ <field name="target">m2o_id:rel_id</field>
237
+ <field name="instance_method_name">_jsonify_m2o_to_id</field>
238
+
239
+ """
240
+ return self[fname].id
241
+
242
+ def _jsonify_x2m_to_ids(self, fname):
243
+ """Helper to get a list of IDs only from a o2m or m2m field.
244
+
245
+ Example:
246
+
247
+ <field name="name">m2m_ids</field>
248
+ <field name="target">m2m_ids:rel_ids</field>
249
+ <field name="instance_method_name">_jsonify_x2m_to_ids</field>
250
+
251
+ """
252
+ return self[fname].ids
253
+
254
+ def _jsonify_format_duration(self, fname):
255
+ """Helper to format a Float-like duration to string 00:00.
256
+
257
+ Example:
258
+
259
+ <field name="name">duration</field>
260
+ <field name="instance_method_name">_jsonify_format_duration</field>
261
+
262
+ """
263
+ return format_duration(self[fname])
@@ -0,0 +1,35 @@
1
+ def convert_simple_to_full_parser(parser):
2
+ """Convert a simple API style parser to a full parser"""
3
+ assert isinstance(parser, list)
4
+ return {"fields": _convert_parser(parser)}
5
+
6
+
7
+ def _convert_field(fld, function=None):
8
+ """Return a dict from the string encoding a field to export.
9
+ The : is used as a separator to specify a target, if any.
10
+ """
11
+ name, sep, target = fld.partition(":")
12
+ field_dict = {"name": name}
13
+ if target:
14
+ field_dict["target"] = target
15
+ if function:
16
+ field_dict["function"] = function
17
+ return field_dict
18
+
19
+
20
+ def _convert_parser(parser):
21
+ """Recursively process each list to replace encoded fields as string
22
+ by dicts specifying each attribute by its relevant key.
23
+ """
24
+ result = []
25
+ for line in parser:
26
+ if isinstance(line, str):
27
+ field_def = _convert_field(line)
28
+ else:
29
+ fld, sub = line
30
+ if callable(sub) or isinstance(sub, str):
31
+ field_def = _convert_field(fld, sub)
32
+ else:
33
+ field_def = (_convert_field(fld), _convert_parser(sub))
34
+ result.append(field_def)
35
+ return result
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["whool"]
3
+ build-backend = "whool.buildapi"
@@ -0,0 +1,8 @@
1
+ - BEAU Sébastien \<<sebastien.beau@akretion.com>\>
2
+ - Raphaël Reverdy \<<raphael.reverdy@akretion.com>\>
3
+ - Laurent Mignon \<<laurent.mignon@acsone.eu>\>
4
+ - Nans Lefebvre \<<nans.lefebvre@acsone.eu>\>
5
+ - Simone Orsi \<<simone.orsi@camptocamp.com>\>
6
+ - Iván Todorovich \<<ivan.todorovich@camptocamp.com>\>
7
+ - Nguyen Minh Chien \<<chien@trobz.com>\>
8
+ - Thien Vo \<<thienvh@trobz.com>\>
@@ -0,0 +1 @@
1
+ The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp.
@@ -0,0 +1,163 @@
1
+ This module adds a 'jsonify' method to every model of the ORM. It works
2
+ on the current recordset and requires a single argument 'parser' that
3
+ specify the field to extract.
4
+
5
+ Example of a simple parser:
6
+
7
+ ``` python
8
+ parser = [
9
+ 'name',
10
+ 'number',
11
+ 'create_date',
12
+ ('partner_id', ['id', 'display_name', 'ref'])
13
+ ('line_id', ['id', ('product_id', ['name']), 'price_unit'])
14
+ ]
15
+ ```
16
+
17
+ In order to be consistent with the Odoo API the jsonify method always
18
+ returns a list of objects even if there is only one element in the
19
+ recordset.
20
+
21
+ By default the key into the JSON is the name of the field extracted from
22
+ the model. If you need to specify an alternate name to use as key, you
23
+ can define your mapping as follow into the parser definition:
24
+
25
+ ``` python
26
+ parser = [
27
+ 'field_name:json_key'
28
+ ]
29
+ ```
30
+
31
+ ``` python
32
+ parser = [
33
+ 'name',
34
+ 'number',
35
+ 'create_date:creationDate',
36
+ ('partner_id:partners', ['id', 'display_name', 'ref'])
37
+ ('line_id:lines', ['id', ('product_id', ['name']), 'price_unit'])
38
+ ]
39
+ ```
40
+
41
+ If you need to parse the value of a field in a custom way, you can pass
42
+ a callable or the name of a method on the model:
43
+
44
+ ``` python
45
+ parser = [
46
+ ('name', "jsonify_name") # method name
47
+ ('number', lambda rec, field_name: rec[field_name] * 2)) # callable
48
+ ]
49
+ ```
50
+
51
+ Also the module provide a method "get_json_parser" on the ir.exports
52
+ object that generate a parser from an ir.exports configuration.
53
+
54
+ Further features are available for advanced uses. It defines a simple
55
+ "resolver" model that has a "python_code" field and a resolve function
56
+ so that arbitrary functions can be configured to transform fields, or
57
+ process the resulting dictionary. It is also to specify a lang to
58
+ extract the translation of any given field.
59
+
60
+ To use these features, a full parser follows the following structure:
61
+
62
+ ``` python
63
+ parser = {
64
+ "resolver": 3,
65
+ "language_agnostic": True,
66
+ "langs": {
67
+ False: [
68
+ {'name': 'description'},
69
+ {'name': 'number', 'resolver': 5},
70
+ ({'name': 'partner_id', 'target': 'partner'}, [{'name': 'display_name'}])
71
+ ],
72
+ 'fr_FR': [
73
+ {'name': 'description', 'target': 'descriptions_fr'},
74
+ ({'name': 'partner_id', 'target': 'partner'}, [{'name': 'description', 'target': 'description_fr'}])
75
+ ],
76
+ }
77
+ }
78
+ ```
79
+
80
+ One would get a result having this structure (note that the translated
81
+ fields are merged in the same dictionary):
82
+
83
+ ``` python
84
+ exported_json == {
85
+ "description": "English description",
86
+ "description_fr": "French description, voilà",
87
+ "number": 42,
88
+ "partner": {
89
+ "display_name": "partner name",
90
+ "description_fr": "French description of that partner",
91
+ },
92
+ }
93
+ ```
94
+
95
+ Note that a resolver can be passed either as a recordset or as an id, so
96
+ as to be fully serializable. A slightly simpler version in case the
97
+ translation of fields is not needed, but other features like custom
98
+ resolvers are:
99
+
100
+ ``` python
101
+ parser = {
102
+ "resolver": 3,
103
+ "fields": [
104
+ {'name': 'description'},
105
+ {'name': 'number', 'resolver': 5},
106
+ ({'name': 'partner_id', 'target': 'partners'}, [{'name': 'display_name'}]),
107
+ ],
108
+ }
109
+ ```
110
+
111
+ By passing the fields key instead of langs, we have essentially the same
112
+ behaviour as simple parsers, with the added benefit of being able to use
113
+ resolvers.
114
+
115
+ Standard use-cases of resolvers are: - give field-specific defaults
116
+ (e.g. "" instead of None) - cast a field type (e.g. int()) - alias a
117
+ particular field for a specific export - ...
118
+
119
+ A simple parser is simply translated into a full parser at export.
120
+
121
+ If the global resolver is given, then the json_dict goes through:
122
+
123
+ ``` python
124
+ resolver.resolve(dict, record)
125
+ ```
126
+
127
+ Which allows to add external data from the context or transform the
128
+ dictionary if necessary. Similarly if given for a field the resolver
129
+ evaluates the result.
130
+
131
+ It is possible for a target to have a marshaller by ending the target
132
+ with '=list': in that case the result is put into a list.
133
+
134
+ ``` python
135
+ parser = {
136
+ fields: [
137
+ {'name': 'name'},
138
+ {'name': 'field_1', 'target': 'customTags=list'},
139
+ {'name': 'field_2', 'target': 'customTags=list'},
140
+ ]
141
+ }
142
+ ```
143
+
144
+ Would result in the following JSON structure:
145
+
146
+ ``` python
147
+ {
148
+ 'name': 'record_name',
149
+ 'customTags': ['field_1_value', 'field_2_value'],
150
+ }
151
+ ```
152
+
153
+ The intended use-case is to be compatible with APIs that require all
154
+ translated parameters to be exported simultaneously, and ask for custom
155
+ properties to be put in a sub-dictionary. Since it is often the case
156
+ that some of these requirements are optional, new requirements could be
157
+ met without needing to add field or change any code.
158
+
159
+ Note that the export values with the simple parser depends on the
160
+ record's lang; this is in contrast with full parsers which are designed
161
+ to be language agnostic.
162
+
163
+ NOTE: this module was named base_jsonify till version 14.0.1.5.0.
@@ -0,0 +1,26 @@
1
+ ## with_fieldname parameter
2
+
3
+ The with_fieldname option of jsonify() method, when true, will inject on
4
+ the same level of the data "\_fieldname_\$field" keys that will
5
+ contain the field name, in the language of the current user.
6
+
7
+ > Examples of with_fieldname usage:
8
+
9
+ ``` python
10
+ # example 1
11
+ parser = [('name')]
12
+ a.jsonify(parser=parser)
13
+ [{'name': 'SO3996'}]
14
+ >>> a.jsonify(parser=parser, with_fieldname=False)
15
+ [{'name': 'SO3996'}]
16
+ >>> a.jsonify(parser=parser, with_fieldname=True)
17
+ [{'fieldname_name': 'Order Reference', 'name': 'SO3996'}}]
18
+
19
+
20
+ # example 2 - with a subparser-
21
+ parser=['name', 'create_date', ('order_line', ['id' , 'product_uom', 'is_expense'])]
22
+ >>> a.jsonify(parser=parser, with_fieldname=False)
23
+ [{'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}]}]
24
+ >>> a.jsonify(parser=parser, with_fieldname=True)
25
+ [{'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}]}]
26
+ ```
@@ -0,0 +1,2 @@
1
+ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2
+ access_ir_exports_resolver,ir.exports.resolver,model_ir_exports_resolver,base.group_system,1,1,1,1
@@ -0,0 +1,3 @@
1
+ from . import test_get_parser
2
+ from . import test_helpers
3
+ from . import test_ir_exports_line