[TEST] base_jsonify: test and document code
parent
a648e21313
commit
3812375799
|
@ -96,16 +96,31 @@ To use these features, a full parser follows the following structure:
|
|||
False: [
|
||||
{'name': 'description'},
|
||||
{'name': 'number', 'resolver': 5},
|
||||
({'name': 'partner_id', 'alias': 'partners'}, [{'name': 'display_name'}])
|
||||
({'name': 'partner_id', 'alias': 'partner'}, [{'name': 'display_name'}])
|
||||
],
|
||||
'fr_FR': [
|
||||
{'name': 'description', 'alias': 'descriptions_fr'},
|
||||
({'name': 'partner_id', 'alias': 'partners'}, [{'name': 'description', 'alias': 'description_fr'}])
|
||||
({'name': 'partner_id', 'alias': 'partner'}, [{'name': 'description', 'alias': 'description_fr'}])
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
One would get the a result having this structure (note that the translated fields are merged in the same dictionary):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
exported_json == {
|
||||
"description": "English description",
|
||||
"description_fr": "French description, voilà",
|
||||
"number": 42,
|
||||
"partner": {
|
||||
"display_name": "partner name",
|
||||
"description_fr": "French description of that partner",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Note that a resolver can be passed either as a recordset or as an id, so as to be fully serializable.
|
||||
A slightly simpler version in case the translation of fields is not needed,
|
||||
but other features like custom resolvers are:
|
||||
|
@ -114,7 +129,6 @@ but other features like custom resolvers are:
|
|||
|
||||
parser = {
|
||||
"resolver": 3,
|
||||
"language_agnostic": True,
|
||||
"fields": [
|
||||
{'name': 'description'},
|
||||
{'name': 'number', 'resolver': 5},
|
||||
|
@ -123,6 +137,15 @@ but other features like custom resolvers are:
|
|||
}
|
||||
|
||||
|
||||
By passing the `fields` key instead of `langs`, we have essentially the same behaviour as simple parsers,
|
||||
with the added benefit of being able to use resolvers.
|
||||
|
||||
Standard use-cases of resolvers are:
|
||||
- give field-specific defaults (e.g. `""` instead of `None`)
|
||||
- cast a field type (e.g. `int()`)
|
||||
- alias a particular field for a specific export
|
||||
- ...
|
||||
|
||||
A simple parser is simply translated into a full parser at export.
|
||||
|
||||
If the global resolver is given, then the json_dict goes through:
|
||||
|
@ -135,19 +158,17 @@ Which allows to add external data from the context or transform the dictionary
|
|||
if necessary. Similarly if given for a field the resolver evaluates the result.
|
||||
|
||||
It is possible for an alias to end with a '*':
|
||||
in that case the result it put into a list.
|
||||
in that case the result is put into a list.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parser = {
|
||||
"langs": {
|
||||
False: [
|
||||
fields: [
|
||||
{'name': 'name'},
|
||||
{'name': 'field_1', 'alias': 'customTags*'},
|
||||
{'name': 'field_2', 'alias': 'customTags*'},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Would result in the following json structure:
|
||||
|
|
|
@ -7,7 +7,13 @@ from collections import OrderedDict
|
|||
from odoo import fields, models
|
||||
|
||||
|
||||
def partition(l, accessor): # -> Dict
|
||||
def partition(l, accessor):
|
||||
"""Partition an iterable (e.g. a recordset) according to an accessor (e.g. a lambda)
|
||||
Return a dictionary whose keys are the values obtained from accessor, and values
|
||||
are the items that have this value.
|
||||
Example: partition([{"name": "ax"}, {"name": "by"}], lambda x: "x" in x["name"])
|
||||
=> {True: [{"name": "ax"}], False: [{"name": "by"}]}
|
||||
"""
|
||||
result = {}
|
||||
for item in l:
|
||||
key = accessor(item)
|
||||
|
|
|
@ -69,16 +69,31 @@ To use these features, a full parser follows the following structure:
|
|||
False: [
|
||||
{'name': 'description'},
|
||||
{'name': 'number', 'resolver': 5},
|
||||
({'name': 'partner_id', 'alias': 'partners'}, [{'name': 'display_name'}])
|
||||
({'name': 'partner_id', 'alias': 'partner'}, [{'name': 'display_name'}])
|
||||
],
|
||||
'fr_FR': [
|
||||
{'name': 'description', 'alias': 'descriptions_fr'},
|
||||
({'name': 'partner_id', 'alias': 'partners'}, [{'name': 'description', 'alias': 'description_fr'}])
|
||||
({'name': 'partner_id', 'alias': 'partner'}, [{'name': 'description', 'alias': 'description_fr'}])
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
One would get the a result having this structure (note that the translated fields are merged in the same dictionary):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
exported_json == {
|
||||
"description": "English description",
|
||||
"description_fr": "French description, voilà",
|
||||
"number": 42,
|
||||
"partner": {
|
||||
"display_name": "partner name",
|
||||
"description_fr": "French description of that partner",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Note that a resolver can be passed either as a recordset or as an id, so as to be fully serializable.
|
||||
A slightly simpler version in case the translation of fields is not needed,
|
||||
but other features like custom resolvers are:
|
||||
|
@ -87,7 +102,6 @@ but other features like custom resolvers are:
|
|||
|
||||
parser = {
|
||||
"resolver": 3,
|
||||
"language_agnostic": True,
|
||||
"fields": [
|
||||
{'name': 'description'},
|
||||
{'name': 'number', 'resolver': 5},
|
||||
|
@ -96,6 +110,15 @@ but other features like custom resolvers are:
|
|||
}
|
||||
|
||||
|
||||
By passing the `fields` key instead of `langs`, we have essentially the same behaviour as simple parsers,
|
||||
with the added benefit of being able to use resolvers.
|
||||
|
||||
Standard use-cases of resolvers are:
|
||||
- give field-specific defaults (e.g. `""` instead of `None`)
|
||||
- cast a field type (e.g. `int()`)
|
||||
- alias a particular field for a specific export
|
||||
- ...
|
||||
|
||||
A simple parser is simply translated into a full parser at export.
|
||||
|
||||
If the global resolver is given, then the json_dict goes through:
|
||||
|
@ -108,19 +131,17 @@ Which allows to add external data from the context or transform the dictionary
|
|||
if necessary. Similarly if given for a field the resolver evaluates the result.
|
||||
|
||||
It is possible for an alias to end with a '*':
|
||||
in that case the result it put into a list.
|
||||
in that case the result is put into a list.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parser = {
|
||||
"langs": {
|
||||
False: [
|
||||
fields: [
|
||||
{'name': 'name'},
|
||||
{'name': 'field_1', 'alias': 'customTags*'},
|
||||
{'name': 'field_2', 'alias': 'customTags*'},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Would result in the following json structure:
|
||||
|
|
|
@ -424,22 +424,33 @@ It is also to specify a lang to extract the translation of any given field.</p>
|
|||
<span class="kc">False</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'description'</span><span class="p">},</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'number'</span><span class="p">,</span> <span class="s1">'resolver'</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
|
||||
<span class="p">({</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'partner_id'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'partners'</span><span class="p">},</span> <span class="p">[{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'display_name'</span><span class="p">}])</span>
|
||||
<span class="p">({</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'partner_id'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'partner'</span><span class="p">},</span> <span class="p">[{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'display_name'</span><span class="p">}])</span>
|
||||
<span class="p">],</span>
|
||||
<span class="s1">'fr_FR'</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'description'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'descriptions_fr'</span><span class="p">},</span>
|
||||
<span class="p">({</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'partner_id'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'partners'</span><span class="p">},</span> <span class="p">[{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'description'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'description_fr'</span><span class="p">}])</span>
|
||||
<span class="p">({</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'partner_id'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'partner'</span><span class="p">},</span> <span class="p">[{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'description'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'description_fr'</span><span class="p">}])</span>
|
||||
<span class="p">],</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</pre>
|
||||
<p>One would get the a result having this structure (note that the translated fields are merged in the same dictionary):</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="n">exported_json</span> <span class="o">==</span> <span class="p">{</span>
|
||||
<span class="s2">"description"</span><span class="p">:</span> <span class="s2">"English description"</span><span class="p">,</span>
|
||||
<span class="s2">"description_fr"</span><span class="p">:</span> <span class="s2">"French description, voilà"</span><span class="p">,</span>
|
||||
<span class="s2">"number"</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span>
|
||||
<span class="s2">"partner"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="s2">"display_name"</span><span class="p">:</span> <span class="s2">"partner name"</span><span class="p">,</span>
|
||||
<span class="s2">"description_fr"</span><span class="p">:</span> <span class="s2">"French description of that partner"</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">}</span>
|
||||
</pre>
|
||||
<p>Note that a resolver can be passed either as a recordset or as an id, so as to be fully serializable.
|
||||
A slightly simpler version in case the translation of fields is not needed,
|
||||
but other features like custom resolvers are:</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="p">{</span>
|
||||
<span class="s2">"resolver"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="s2">"language_agnostic"</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="s2">"fields"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'description'</span><span class="p">},</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'number'</span><span class="p">,</span> <span class="s1">'resolver'</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
|
||||
|
@ -447,6 +458,13 @@ but other features like custom resolvers are:</p>
|
|||
<span class="p">],</span>
|
||||
<span class="p">}</span>
|
||||
</pre>
|
||||
<p>By passing the <cite>fields</cite> key instead of <cite>langs</cite>, we have essentially the same behaviour as simple parsers,
|
||||
with the added benefit of being able to use resolvers.</p>
|
||||
<p>Standard use-cases of resolvers are:
|
||||
- give field-specific defaults (e.g. <cite>“”</cite> instead of <cite>None</cite>)
|
||||
- cast a field type (e.g. <cite>int()</cite>)
|
||||
- alias a particular field for a specific export
|
||||
- …</p>
|
||||
<p>A simple parser is simply translated into a full parser at export.</p>
|
||||
<p>If the global resolver is given, then the json_dict goes through:</p>
|
||||
<pre class="code python literal-block">
|
||||
|
@ -455,16 +473,14 @@ but other features like custom resolvers are:</p>
|
|||
<p>Which allows to add external data from the context or transform the dictionary
|
||||
if necessary. Similarly if given for a field the resolver evaluates the result.</p>
|
||||
<p>It is possible for an alias to end with a ‘*’:
|
||||
in that case the result it put into a list.</p>
|
||||
in that case the result is put into a list.</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="p">{</span>
|
||||
<span class="s2">"langs"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="kc">False</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="n">fields</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'name'</span><span class="p">},</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'field_1'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'customTags*'</span><span class="p">},</span>
|
||||
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'field_2'</span><span class="p">,</span> <span class="s1">'alias'</span><span class="p">:</span> <span class="s1">'customTags*'</span><span class="p">},</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</pre>
|
||||
<p>Would result in the following json structure:</p>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
|
@ -281,3 +282,16 @@ class TestParser(SavepointCase):
|
|||
|
||||
json = self.category.jsonify(export.get_json_parser())[0]
|
||||
self.assertEqual(json, {"name": "yeah!"})
|
||||
|
||||
def test_bad_parsers(self):
|
||||
bad_field_name = ["Name"]
|
||||
with self.assertRaises(KeyError):
|
||||
self.category.jsonify(bad_field_name, one=True)
|
||||
|
||||
bad_function_name = {"fields": [{"name": "name", "function": "notafunction"}]}
|
||||
with self.assertRaises(UserError):
|
||||
self.category.jsonify(bad_function_name, one=True)
|
||||
|
||||
bad_subparser = {"fields": [({"name": "name"}, [{"name": "subparser_name"}])]}
|
||||
with self.assertRaises(UserError):
|
||||
self.category.jsonify(bad_subparser, one=True)
|
||||
|
|
|
@ -49,3 +49,19 @@ class TestIrExportsLine(TransactionCase):
|
|||
}
|
||||
)
|
||||
self.assertTrue(line)
|
||||
|
||||
def test_resolver_function_constrains(self):
|
||||
resolver = self.env["ir.exports.resolver"].create(
|
||||
{"python_code": "result = value", "type": "field"}
|
||||
)
|
||||
ir_export_lines_model = self.env["ir.exports.line"]
|
||||
with self.assertRaises(ValidationError):
|
||||
# the callable should be an existing model function, but it's not checked
|
||||
ir_export_lines_model.create(
|
||||
{
|
||||
"export_id": self.ir_export.id,
|
||||
"name": "name",
|
||||
"resolver_id": resolver.id,
|
||||
"function": "function_name",
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue