124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
# copyright 2016 Camptocamp
|
|
# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
|
|
|
import json
|
|
from datetime import date, datetime
|
|
|
|
import dateutil
|
|
import lxml
|
|
|
|
from odoo import fields, models
|
|
from odoo.tools.func import lazy
|
|
|
|
|
|
class JobSerialized(fields.Field):
|
|
"""Provide the storage for job fields stored as json
|
|
|
|
A base_type must be set, it must be dict, list or tuple.
|
|
When the field is not set, the json will be the corresponding
|
|
json string ("{}" or "[]").
|
|
|
|
Support for some custom types has been added to the json decoder/encoder
|
|
(see JobEncoder and JobDecoder).
|
|
"""
|
|
|
|
type = "job_serialized"
|
|
column_type = ("text", "text")
|
|
|
|
_base_type = None
|
|
|
|
# these are the default values when we convert an empty value
|
|
_default_json_mapping = {
|
|
dict: "{}",
|
|
list: "[]",
|
|
tuple: "[]",
|
|
models.BaseModel: lambda env: json.dumps(
|
|
{"_type": "odoo_recordset", "model": "base", "ids": [], "uid": env.uid}
|
|
),
|
|
}
|
|
|
|
def __init__(self, string=fields.Default, base_type=fields.Default, **kwargs):
|
|
super().__init__(string=string, _base_type=base_type, **kwargs)
|
|
|
|
def _setup_attrs(self, model, name): # pylint: disable=missing-return
|
|
super()._setup_attrs(model, name)
|
|
if self._base_type not in self._default_json_mapping:
|
|
raise ValueError("%s is not a supported base type" % (self._base_type))
|
|
|
|
def _base_type_default_json(self, env):
|
|
default_json = self._default_json_mapping.get(self._base_type)
|
|
if not isinstance(default_json, str):
|
|
default_json = default_json(env)
|
|
return default_json
|
|
|
|
def convert_to_column(self, value, record, values=None, validate=True):
|
|
return self.convert_to_cache(value, record, validate=validate)
|
|
|
|
def convert_to_cache(self, value, record, validate=True):
|
|
# cache format: json.dumps(value) or None
|
|
if isinstance(value, self._base_type):
|
|
return json.dumps(value, cls=JobEncoder)
|
|
else:
|
|
return value or None
|
|
|
|
def convert_to_record(self, value, record):
|
|
default = self._base_type_default_json(record.env)
|
|
return json.loads(value or default, cls=JobDecoder, env=record.env)
|
|
|
|
|
|
class JobEncoder(json.JSONEncoder):
|
|
"""Encode Odoo recordsets so that we can later recompose them"""
|
|
|
|
def _get_record_context(self, obj):
|
|
return obj._job_prepare_context_before_enqueue()
|
|
|
|
def default(self, obj):
|
|
if isinstance(obj, models.BaseModel):
|
|
return {
|
|
"_type": "odoo_recordset",
|
|
"model": obj._name,
|
|
"ids": obj.ids,
|
|
"uid": obj.env.uid,
|
|
"su": obj.env.su,
|
|
"context": self._get_record_context(obj),
|
|
}
|
|
elif isinstance(obj, datetime):
|
|
return {"_type": "datetime_isoformat", "value": obj.isoformat()}
|
|
elif isinstance(obj, date):
|
|
return {"_type": "date_isoformat", "value": obj.isoformat()}
|
|
elif isinstance(obj, lxml.etree._Element):
|
|
return {
|
|
"_type": "etree_element",
|
|
"value": lxml.etree.tostring(obj, encoding=str),
|
|
}
|
|
elif isinstance(obj, lazy):
|
|
return obj._value
|
|
return json.JSONEncoder.default(self, obj)
|
|
|
|
|
|
class JobDecoder(json.JSONDecoder):
|
|
"""Decode json, recomposing recordsets"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
env = kwargs.pop("env")
|
|
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
|
assert env
|
|
self.env = env
|
|
|
|
def object_hook(self, obj):
|
|
if "_type" not in obj:
|
|
return obj
|
|
type_ = obj["_type"]
|
|
if type_ == "odoo_recordset":
|
|
model = self.env(user=obj.get("uid"), su=obj.get("su"))[obj["model"]]
|
|
if obj.get("context"):
|
|
model = model.with_context(**obj.get("context"))
|
|
return model.browse(obj["ids"])
|
|
elif type_ == "datetime_isoformat":
|
|
return dateutil.parser.parse(obj["value"])
|
|
elif type_ == "date_isoformat":
|
|
return dateutil.parser.parse(obj["value"]).date()
|
|
elif type_ == "etree_element":
|
|
return lxml.etree.fromstring(obj["value"])
|
|
return obj
|