mirror of
https://github.com/OCA/server-backend.git
synced 2025-02-18 09:52:42 +02:00
Cached methods should never return recordsets, which are tied to specific context, env, cursor, uid. Instead, they should return IDs which, later, can be browsed in the current context. With this change _usable_rules() cached method is now returing ids instead of a recordset, and also the _match_find method is properly browsing the results ids of the cached method.
179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
# Copyright 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
|
|
# Copyright 2016 Tecnativa - Vicent Cubells
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
import logging
|
|
|
|
from odoo import api, fields, models, tools
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BaseImportMatch(models.Model):
|
|
_name = "base_import.match"
|
|
_description = "Deduplicate settings prior to CSV imports."
|
|
_order = "sequence, name"
|
|
|
|
name = fields.Char(compute="_compute_name", store=True, index=True)
|
|
sequence = fields.Integer(index=True)
|
|
model_id = fields.Many2one(
|
|
"ir.model",
|
|
"Model",
|
|
required=True,
|
|
ondelete="cascade",
|
|
domain=[("transient", "=", False)],
|
|
help="In this model you will apply the match.",
|
|
)
|
|
model_name = fields.Char(
|
|
string="Model name", related="model_id.model", store=True, index=True
|
|
)
|
|
field_ids = fields.One2many(
|
|
comodel_name="base_import.match.field",
|
|
inverse_name="match_id",
|
|
string="Fields",
|
|
required=True,
|
|
help="Fields that will define an unique key.",
|
|
)
|
|
|
|
@api.onchange("model_id")
|
|
def _onchange_model_id(self):
|
|
self.field_ids = False
|
|
|
|
@api.depends("model_id", "field_ids")
|
|
def _compute_name(self):
|
|
"""Automatic self-descriptive name for the setting records."""
|
|
for one in self:
|
|
one.name = "{}: {}".format(
|
|
one.model_id.display_name,
|
|
" + ".join(one.field_ids.mapped("display_name")),
|
|
)
|
|
|
|
@api.model
|
|
def _match_find(self, model, converted_row, imported_row):
|
|
"""Find a update target for the given row.
|
|
|
|
This will traverse by order all match rules that can be used with the
|
|
imported data, and return a match for the first rule that returns a
|
|
single result.
|
|
|
|
:param odoo.models.Model model:
|
|
Model object that is being imported.
|
|
|
|
:param dict converted_row:
|
|
Row converted to Odoo api format, like the 3rd value that
|
|
:meth:`odoo.models.Model._convert_records` returns.
|
|
|
|
:param dict imported_row:
|
|
Row as it is being imported, in format::
|
|
|
|
{
|
|
"field_name": "string value",
|
|
"other_field": "True",
|
|
...
|
|
}
|
|
|
|
:return odoo.models.Model:
|
|
Return a dataset with one single match if it was found, or an
|
|
empty dataset if none or multiple matches were found.
|
|
"""
|
|
# Get usable rules to perform matches
|
|
usable = self._usable_rules(model._name, converted_row)
|
|
usable = self.browse(usable)
|
|
# Traverse usable combinations
|
|
for combination in usable:
|
|
combination_valid = True
|
|
domain = list()
|
|
for field in combination.field_ids:
|
|
# Check imported value if it is a conditional field
|
|
if field.conditional:
|
|
# Invalid combinations are skipped
|
|
if imported_row[field.name] != field.imported_value:
|
|
combination_valid = False
|
|
break
|
|
domain.append((field.name, "=", converted_row[field.name]))
|
|
if not combination_valid:
|
|
continue
|
|
match = model.search(domain)
|
|
# When a single match is found, stop searching
|
|
if len(match) == 1:
|
|
return match
|
|
elif match:
|
|
_logger.warning(
|
|
"Found multiple matches for model %s and domain %s; "
|
|
"falling back to default behavior (create new record)",
|
|
model._name,
|
|
domain,
|
|
)
|
|
# Return an empty match if none or multiple was found
|
|
return model
|
|
|
|
@api.model
|
|
@tools.ormcache("model_name", "frozenset(fields)")
|
|
def _usable_rules(self, model_name, fields):
|
|
"""Return a set of elements usable for calling ``load()``.
|
|
|
|
:param str model_name:
|
|
Technical name of the model where you are loading data.
|
|
E.g. ``res.partner``.
|
|
|
|
:param list(str|bool) fields:
|
|
List of field names being imported.
|
|
|
|
:return bool:
|
|
Indicates if we should patch its load method.
|
|
"""
|
|
result = self
|
|
available = self.search([("model_name", "=", model_name)])
|
|
# Use only criteria with all required fields to match
|
|
for record in available:
|
|
if all(f.name in fields for f in record.field_ids):
|
|
result |= record
|
|
return result.ids
|
|
|
|
|
|
class BaseImportMatchField(models.Model):
|
|
_name = "base_import.match.field"
|
|
_description = "Field import match definition"
|
|
|
|
name = fields.Char(related="field_id.name")
|
|
field_id = fields.Many2one(
|
|
comodel_name="ir.model.fields",
|
|
string="Field",
|
|
required=True,
|
|
ondelete="cascade",
|
|
domain="[('model_id', '=', model_id)]",
|
|
help="Field that will be part of an unique key.",
|
|
)
|
|
match_id = fields.Many2one(
|
|
comodel_name="base_import.match",
|
|
string="Match",
|
|
ondelete="cascade",
|
|
required=True,
|
|
)
|
|
model_id = fields.Many2one(related="match_id.model_id")
|
|
conditional = fields.Boolean(
|
|
help="Enable if you want to use this field only in some conditions."
|
|
)
|
|
imported_value = fields.Char(
|
|
help="If the imported value is not this, the whole matching rule will "
|
|
"be discarded. Be careful, this data is always treated as a "
|
|
"string, and comparison is case-sensitive so if you set 'True', "
|
|
"it will NOT match '1' nor 'true', only EXACTLY 'True'."
|
|
)
|
|
|
|
@api.depends("conditional", "field_id", "imported_value")
|
|
def name_get(self):
|
|
result = []
|
|
for one in self:
|
|
pattern = "{name} ({cond})" if one.conditional else "{name}"
|
|
name = pattern.format(
|
|
name=one.field_id.name,
|
|
cond=one.imported_value,
|
|
)
|
|
result.append((one.id, name))
|
|
return result
|
|
|
|
@api.onchange("field_id", "match_id", "conditional", "imported_value")
|
|
def _onchange_match_id_name(self):
|
|
"""Update match name."""
|
|
self.mapped("match_id")._compute_name()
|