Skip to content

Commit 9f48040

Browse files
[FIX] base_import_match: fix name reporting and cache invalidation
- load() no longer mutates the fields/data list passed in by core. Work on local copies so the import success screen reports names correctly instead of match values (e.g. emails). - Invalidate the _usable_rules ormcache on create/write/unlink of match rules and their fields, so programmatic imports don't use stale rules. - Make the Match column tooltip translatable (bind via _t). - Add a regression test covering the name-reporting fix.
1 parent ca0b45e commit 9f48040

5 files changed

Lines changed: 56 additions & 2 deletions

File tree

‎base_import_match/models/base.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ def load(self, fields, data):
7070
self.env["base_import.match"]._usable_rules(self._name, fields)
7171
)
7272
if match_only_fields or has_rules:
73+
# Work on local copies so we don't mutate the caller's lists
74+
# (core reuses them after load() to report imported record names).
75+
fields = list(fields)
76+
data = [list(row) for row in data]
7377
newdata = list()
7478
match_errors = []
7579
# Change .id (dbid) by id (xmlid)
7680
if ".id" in fields:
7781
column = fields.index(".id")
7882
fields[column] = "id"
79-
# data[0][column] = "id"
8083
for values in data:
8184
dbid = int(values[column])
8285
values[column] = self.browse(dbid).get_external_id().get(dbid)

‎base_import_match/models/base_import.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,30 @@
99
_logger = logging.getLogger(__name__)
1010

1111

12+
class BaseImportMatchCacheMixin(models.AbstractModel):
13+
_name = "base_import.match.cache.mixin"
14+
_description = "Invalidate the _usable_rules cache on rule changes"
15+
16+
@api.model_create_multi
17+
def create(self, vals_list):
18+
records = super().create(vals_list)
19+
self.env.registry.clear_cache()
20+
return records
21+
22+
def write(self, vals):
23+
res = super().write(vals)
24+
self.env.registry.clear_cache()
25+
return res
26+
27+
def unlink(self):
28+
res = super().unlink()
29+
self.env.registry.clear_cache()
30+
return res
31+
32+
1233
class BaseImportMatch(models.Model):
1334
_name = "base_import.match"
35+
_inherit = "base_import.match.cache.mixin"
1436
_description = "Deduplicate settings prior to CSV imports."
1537
_order = "sequence, name"
1638

@@ -150,6 +172,7 @@ def _usable_rules(self, model_name, fields):
150172

151173
class BaseImportMatchField(models.Model):
152174
_name = "base_import.match.field"
175+
_inherit = "base_import.match.cache.mixin"
153176
_description = "Field import match definition"
154177

155178
name = fields.Char(related="field_id.name")

‎base_import_match/static/src/import_data_content.esm.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
33

44
import {ImportDataContent} from "@base_import/import_data_content/import_data_content";
5+
import {_t} from "@web/core/l10n/translation";
56
import {patch} from "@web/core/utils/patch";
67

78
patch(ImportDataContent.prototype, {
89
onMatchOnlyChanged: (column, ev) => (column.matchOnly = ev.target.checked),
10+
get matchColumnTooltip() {
11+
return _t(
12+
"Use this field to find existing records without updating its value."
13+
);
14+
},
915
get hasIdColumn() {
1016
return this.props.columns.some(
1117
(c) => c.fieldInfo && ["id", ".id"].includes(c.fieldInfo.fieldPath)

‎base_import_match/static/src/import_data_content.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
scope="col"
1212
class="text-nowrap"
1313
style="min-width: 0 !important; width: auto;"
14-
title="Use this field to find existing records without updating its value."
14+
t-att-title="matchColumnTooltip"
1515
>Match</th>
1616
</xpath>
1717
<xpath expr="//td[.//t[@t-foreach='column.comments']]" position="before">

‎base_import_match/tests/test_import.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ def test_match_only_from_ui(self):
186186
self.assertEqual(partner.function, "New Function")
187187
self.assertEqual(partner.email, "match@example.com")
188188

189+
def test_match_only_name_reporting(self):
190+
"""A match-only column before ``name`` must not corrupt result['name'].
191+
192+
Core reports imported record names by indexing the original columns it
193+
passed to ``load()``; dropping the match-only column in place would shift
194+
that index and surface the match value (email) instead of the name.
195+
"""
196+
partner = self.Partner.create(
197+
{"name": "Report Original", "email": "report@example.com"}
198+
)
199+
record = self._base_import_record(
200+
"res.partner", data="report@example.com,Report Changed\n"
201+
)
202+
options = dict(OPTIONS, import_match_only_fields=["email"])
203+
result = record.execute_import(["email", "name"], [], options)
204+
partner.env.cache.invalidate()
205+
# The DB write targets the right column...
206+
self.assertEqual(partner.name, "Report Changed")
207+
# ...and the reported name is the name column, not the match-only email.
208+
self.assertEqual(result["name"][0], "Report Changed")
209+
self.assertNotIn("report@example.com", result["name"])
210+
189211
def test_match_only_no_match_blocks(self):
190212
"""When match-only field doesn't find a record, block the import."""
191213
record = self._base_import_record(

0 commit comments

Comments
 (0)