Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions base_import_match/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
======================
Import Match by Fields
======================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:1aa689e0bf3e1bd6437f5b19cc2e0eb4b9a4d76fcfd4c0f7f71e65fa385c1c08
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge_devstat| image:: https://img.shields.io/badge/maturity-beta-brightgreen.png
:target: https://odoo-community.org/page/development-status
:alt: Beta

.. |badge_license| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:alt: AGPL-3

|badge_devstat| |badge_license|

This module extends Odoo's standard import wizard to allow identifying
and updating existing records by any combination of stored field values,
without requiring the External ID (``id``) column that changes across
databases.

Problem
-------

In a standard Odoo import, updating an existing record requires
including its **External ID** (``id``) or its **Database ID** (``.id``).
Both values change between instances (production, staging, development,
…), making it hard to prepare reusable import files.

Solution
--------

Configure one or more *match configurations* that tell the module which
field(s) to use as a lookup key. At import time the module searches the
database using those fields; if a single matching record is found it is
updated, otherwise a new record is created.

Multiple fields can be combined with AND semantics. Many2one fields are
resolved automatically by display name.

**Table of contents**

.. contents::
:local:

Configuration
=============

1. Go to **Settings → Import → Import Match Configurations**.

2. Create a new configuration:

- **Name** – a free label (e.g.
``E-commerce categories by name + seo_name``).
- **Model** – the Odoo model to target (e.g.
``product.public.category``).
- **Match Fields** – one or more stored fields to match on (e.g.
``name`` and ``seo_name``).

3. Save the configuration.

Multiple configurations can exist for the same model. The **Priority**
(sequence) field controls which is tried first; the first one that
returns a unique match is applied.

Usage
=====

Prepare your CSV or XLSX file using only business columns — no External
ID column required.

**Example** — update e-commerce category descriptions matching by name
**and** seo_name:

.. code:: text

name,seo_name,description
Smartphones,Electronics,All our smartphone models
Laptops,Electronics,Premium laptop range

When importing into ``product.public.category``:

- **Unique match** (``name`` + ``seo_name`` → one record found): the
existing record is updated.
- **No match**: a new record is created.
- **Multiple matches**: the row is skipped and a warning is written to
the server log to avoid ambiguous writes.

..

**Note:** If your file already contains an ``id`` or ``.id`` column,
this module deactivates itself for that import and Odoo's standard
behaviour applies.

Many2one resolution
~~~~~~~~~~~~~~~~~~~

For many2one fields (e.g. ``website_id``), the raw string value from the
import file is resolved to a database ID via an exact ``name_search``.
If zero or more than one related record matches the display name, the
row is treated as a new record (no update is performed) and a message is
written to the log.

Credits
=======

Authors
-------

* Tecnativa

Contributors
------------

- `Tecnativa <https://www.tecnativa.com>`__:

- Carlos Dauden

Maintainers
-----------

This module is maintained by Tecnativa.

Contact the maintainer through their official support channels in case you find
any issues with this module.



.. |maintainer-carlosdauden| image:: https://github.com/carlosdauden.png?size=40px
:target: https://github.com/carlosdauden
:alt: carlosdauden

Current maintainer:

|maintainer-carlosdauden|
4 changes: 4 additions & 0 deletions base_import_match/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 Tecnativa - Carlos Dauden
# License AGPL-3.0-or-later (https://www.gnu.org/licenses/agpl).
from . import models
from . import wizards
22 changes: 22 additions & 0 deletions base_import_match/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Tecnativa - Carlos Dauden
# License AGPL-3.0-or-later (https://www.gnu.org/licenses/agpl).
{
"name": "Import Match by Fields",
"summary": (
"Update existing records on import by matching any stored field(s), "
"without requiring the External ID."
),
"version": "18.0.1.0.0",
"category": "Tools",
"website": "https://github.com/OCA/server-tools",
"author": "Tecnativa, Odoo Community Association (OCA)",
"maintainers": ["carlosdauden"],
"license": "AGPL-3",
"depends": ["base_import"],
"data": [
"security/ir.model.access.csv",
"views/base_import_match_views.xml",
"views/menu.xml",
],
"installable": True,
}
213 changes: 213 additions & 0 deletions base_import_match/i18n/base_import_match.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_import_match
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-23 12:17+0000\n"
"PO-Revision-Date: 2026-04-23 12:17+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: base_import_match
#: model_terms:ir.actions.act_window,help:base_import_match.action_base_import_match
msgid ""
"<strong>Example:</strong> to update e-commerce category\n"
" descriptions, create a configuration for\n"
" <em>product.public.category</em> using fields\n"
" <em>name</em> and <em>seo_name</em>."
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "<strong>How it works</strong>"
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid ""
"<strong>Multiple matches</strong> — the row is\n"
" skipped and a warning is written to the server\n"
" log to avoid ambiguous updates."
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid ""
"<strong>No match</strong> — a new record is\n"
" created."
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid ""
"<strong>Unique match</strong> — the existing\n"
" record is updated with the imported values."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__active
msgid "Active"
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid ""
"All selected fields must match simultaneously\n"
" (AND). For <em>many2one</em> fields, the\n"
" related record is resolved by its display\n"
" name."
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "Archived"
msgstr ""

#. module: base_import_match
#. odoo-python
#: code:addons/base_import_match/models/base_import_match.py:0
msgid "At least one match field must be selected in configuration '%(name)s'."
msgstr ""

#. module: base_import_match
#: model:ir.model,name:base_import_match.model_base_import_import
msgid "Base Import"
msgstr ""

#. module: base_import_match
#: model_terms:ir.actions.act_window,help:base_import_match.action_base_import_match
msgid ""
"Configure which fields to use to identify existing records\n"
" during import, instead of relying on the External ID."
msgstr ""

#. module: base_import_match
#: model_terms:ir.actions.act_window,help:base_import_match.action_base_import_match
msgid "Create a new import match configuration"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__create_uid
msgid "Created by"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__create_date
msgid "Created on"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__display_name
msgid "Display Name"
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid ""
"Do <em>not</em> include an\n"
" <code>id</code> (External ID) or\n"
" <code>.id</code> (Database ID) column in your\n"
" import file; the module resolves those\n"
" automatically."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,help:base_import_match.field_base_import_match__sequence
msgid ""
"Evaluation order when several configurations exist for the same model. The "
"first one that returns a unique record is applied."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,help:base_import_match.field_base_import_match__field_ids
msgid ""
"Fields used to look up existing records. All selected fields must match "
"simultaneously (AND). For many2one fields the related record is resolved by "
"display name."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,help:base_import_match.field_base_import_match__name
msgid "Human-readable label for this match configuration."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__id
msgid "ID"
msgstr ""

#. module: base_import_match
#: model:ir.ui.menu,name:base_import_match.menu_base_import_match_root
msgid "Import"
msgstr ""

#. module: base_import_match
#: model:ir.model,name:base_import_match.model_base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "Import Match Configuration"
msgstr ""

#. module: base_import_match
#: model:ir.actions.act_window,name:base_import_match.action_base_import_match
#: model:ir.ui.menu,name:base_import_match.menu_base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_list
msgid "Import Match Configurations"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__write_uid
msgid "Last Updated by"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__write_date
msgid "Last Updated on"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__field_ids
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "Match Fields"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__model_id
msgid "Model"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__name
msgid "Name"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,help:base_import_match.field_base_import_match__model_id
msgid "Odoo model to which this configuration applies."
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__sequence
msgid "Priority"
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "Settings"
msgstr ""

#. module: base_import_match
#: model:ir.model.fields,field_description:base_import_match.field_base_import_match__model_name
msgid "Technical Model Name"
msgstr ""

#. module: base_import_match
#: model_terms:ir.ui.view,arch_db:base_import_match.view_base_import_match_form
msgid "e.g. E-commerce categories by name + seo_name"
msgstr ""
3 changes: 3 additions & 0 deletions base_import_match/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright 2026 Tecnativa - Carlos Dauden
# License AGPL-3.0-or-later (https://www.gnu.org/licenses/agpl).
from . import base_import_match
Loading
Loading