|
|
# -*- coding: utf-8 -*- # © 2016 Jairo Llopis <jairo.llopis@tecnativa.com> # License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html from openerp import _, api, fields, models, SUPERUSER_ID from openerp.exceptions import ValidationError from openerp.tools.safe_eval import safe_eval
class CustomInfoValue(models.Model): _description = "Custom information value" _name = "custom.info.value" _rec_name = 'value' _order = ("model, res_id, category_sequence, category_id, " "property_sequence, property_id") _sql_constraints = [ ("property_owner", "UNIQUE (property_id, model, res_id)", "Another property with that name exists for that resource."), ]
model = fields.Char( related="property_id.model", index=True, readonly=True, auto_join=True, store=True, ) owner_id = fields.Reference( selection="_selection_owner_id", string="Owner", compute="_compute_owner_id", inverse="_inverse_owner_id", help="Record that owns this custom value.", ) res_id = fields.Integer( "Resource ID", required=True, index=True, store=True, ondelete="cascade", ) property_id = fields.Many2one( comodel_name='custom.info.property', required=True, string='Property') property_sequence = fields.Integer( related="property_id.sequence", store=True, index=True, readonly=True, ) category_sequence = fields.Integer( related="property_id.category_id.sequence", store=True, readonly=True, ) category_id = fields.Many2one( related="property_id.category_id", store=True, readonly=True, ) name = fields.Char(related='property_id.name', readonly=True) field_type = fields.Selection(related="property_id.field_type") field_name = fields.Char( compute="_compute_field_name", help="Technical name of the field where the value is stored.", ) required = fields.Boolean(related="property_id.required") value = fields.Char( compute="_compute_value", inverse="_inverse_value", search="_search_value", help="Value, always converted to/from the typed field.", ) value_str = fields.Char( string="Text value", translate=True, index=True, ) value_int = fields.Integer( string="Whole number value", index=True, ) value_float = fields.Float( string="Decimal number value", index=True, ) value_bool = fields.Boolean( string="Yes/No value", index=True, ) value_id = fields.Many2one( comodel_name="custom.info.option", string="Selection value", ondelete="cascade", domain="[('property_ids', 'in', [property_id])]", )
@api.multi def check_access_rule(self, operation): """You access a value if you access its property and owner record.""" if self.env.uid == SUPERUSER_ID: return for s in self: s.property_id.check_access_rule(operation) s.owner_id.check_access_rights(operation) s.owner_id.check_access_rule(operation) return super(CustomInfoValue, self).check_access_rule(operation)
@api.model def create(self, vals): """Skip constrains in 1st lap.""" # HACK https://github.com/odoo/odoo/pull/13439 if "value" in vals: self.env.context.skip_required = True return super(CustomInfoValue, self).create(vals)
@api.model def _selection_owner_id(self): """You can choose among models linked to a template.""" models = self.env["ir.model.fields"].search([ ("ttype", "=", "many2one"), ("relation", "=", "custom.info.template"), ("model_id.transient", "=", False), "!", ("model", "=like", "custom.info.%"), ]).mapped("model_id") models = models.search([("id", "in", models.ids)], order="name") return [(m.model, m.name) for m in models if m.model in self.env and self.env[m.model]._auto]
@api.multi @api.depends("property_id.field_type") def _compute_field_name(self): """Get the technical name where the real typed value is stored.""" for s in self: s.field_name = "value_{!s}".format(s.property_id.field_type)
@api.multi @api.depends("res_id", "model") def _compute_owner_id(self): """Get the id from the linked record.""" for s in self: s.owner_id = "{},{}".format(s.model, s.res_id)
@api.multi def _inverse_owner_id(self): """Store the owner according to the model and ID.""" for s in self: s.model = s.owner_id._name s.res_id = s.owner_id.id
@api.multi @api.depends("property_id.field_type", "field_name", "value_str", "value_int", "value_float", "value_bool", "value_id") def _compute_value(self): """Get the value as a string, from the original field.""" for s in self: if s.field_type == "id": s.value = ", ".join(s.value_id.mapped("display_name")) elif s.field_type == "bool": s.value = _("Yes") if s.value_bool else _("No") else: s.value = getattr(s, s.field_name, False)
@api.multi def _inverse_value(self): """Write the value correctly converted in the typed field.""" for s in self: s[s.field_name] = self._transform_value( s.value, s.field_type, s.property_id)
@api.one @api.constrains("required", "field_name", "value_str", "value_int", "value_float", "value_bool", "value_id") def _check_required(self): """Ensure required fields are filled""" # HACK https://github.com/odoo/odoo/pull/13439 try: del self.env.context.skip_required except AttributeError: if self.required and not self[self.field_name]: raise ValidationError( _("Property %s is required.") % self.property_id.display_name)
@api.one @api.constrains("property_id", "field_type", "field_name", "value_str", "value_int", "value_float") def _check_min_max_limits(self): """Ensure value falls inside the property's stablished limits.""" minimum, maximum = self.property_id.minimum, self.property_id.maximum if minimum <= maximum: value = self[self.field_name] if not value: # This is a job for :meth:`.~_check_required` return if self.field_type == "str": number = len(self.value_str) message = _( "Length for %(prop)s is %(val)s, but it should be " "between %(min)d and %(max)d.") elif self.field_type in {"int", "float"}: number = value if self.field_type == "int": message = _( "Value for %(prop)s is %(val)s, but it should be " "between %(min)d and %(max)d.") else: message = _( "Value for %(prop)s is %(val)s, but it should be " "between %(min)f and %(max)f.") else: return if not minimum <= number <= maximum: raise ValidationError(message % { "prop": self.property_id.display_name, "val": number, "min": minimum, "max": maximum, })
@api.multi @api.onchange("property_id") def _onchange_property_set_default_value(self): """Load default value for this property.""" for record in self: if not record.value and record.property_id.default_value: record.value = record.property_id.default_value
@api.model def _transform_value(self, value, format_, properties=None): """Transforms a text value to the expected format.
:param str/bool value: Custom value in raw string.
:param str format_: Target conversion format for the value. Must be available among ``custom.info.property`` options.
:param recordset properties: Useful when :param:`format_` is ``id``, as it helps to ensure the option is available in these properties. If :param:`format_` is ``id`` and :param:`properties` is ``None``, no transformation will be made for :param:`value`. """
if not value: value = False elif format_ == "id" and properties: value = self.env["custom.info.option"].search( [("property_ids", "in", properties.ids), ("name", "=ilike", value)]) value.ensure_one() elif format_ == "bool": value = value.strip().lower() not in { "0", "false", "", "no", "off", _("No").lower()} elif format_ not in {"str", "id"}: value = safe_eval("{!s}({!r})".format(format_, value)) return value
@api.model def _search_value(self, operator, value): """Search from the stored field directly.""" options = ( o[0] for o in self.property_id._fields["field_type"] .get_description(self.env)["selection"]) domain = [] for fmt in options: try: _value = (self._transform_value(value, fmt) if not isinstance(value, list) else [self._transform_value(v, fmt) for v in value]) except ValueError: # If you are searching something that cannot be casted, then # your property is probably from another type continue domain += [ "&", ("field_type", "=", fmt), ("value_" + fmt, operator, _value), ] return ["|"] * (len(domain) / 3 - 1) + domain
|