Browse Source
Merge pull request #420 from Yajo/base_import_deduplicator
Merge pull request #420 from Yajo/base_import_deduplicator
[ADD] base_import_match: Import deduplicator.pull/238/merge
Daniel Reis
9 years ago
18 changed files with 781 additions and 0 deletions
-
128base_import_match/README.rst
-
5base_import_match/__init__.py
-
26base_import_match/__openerp__.py
-
29base_import_match/data/base_import_match.yml
-
40base_import_match/demo/base_import_match.yml
-
83base_import_match/i18n/es.po
-
5base_import_match/models/__init__.py
-
297base_import_match/models/base_import.py
-
5base_import_match/security/ir.model.access.csv
-
BINbase_import_match/static/description/icon.png
-
5base_import_match/tests/__init__.py
-
2base_import_match/tests/import_data/res_partner_email.csv
-
2base_import_match/tests/import_data/res_partner_name.csv
-
2base_import_match/tests/import_data/res_partner_parent_name_is_company.csv
-
2base_import_match/tests/import_data/res_partner_vat.csv
-
2base_import_match/tests/import_data/res_users_login.csv
-
68base_import_match/tests/test_import.py
-
80base_import_match/views/base_import_match_view.xml
@ -0,0 +1,128 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
================= |
|||
Base Import Match |
|||
================= |
|||
|
|||
By default, when importing data (like CSV import) with the ``base_import`` |
|||
module, Odoo follows this rule: |
|||
|
|||
- If you import the XMLID of a record, make an **update**. |
|||
- If you do not, **create** a new record. |
|||
|
|||
This module allows you to set additional rules to match if a given import is an |
|||
update or a new record. |
|||
|
|||
This is useful when you need to sync heterogeneous databases, and the field you |
|||
use to match records in those databases with Odoo's is not the XMLID but the |
|||
name, VAT, email, etc. |
|||
|
|||
After installing this module, the import logic will be changed to: |
|||
|
|||
- If you import the XMLID of a record, make an **update**. |
|||
- If you do not: |
|||
|
|||
- If there are import match rules for the model you are importing: |
|||
|
|||
- Discard the rules that require fields you are not importing. |
|||
- Traverse the remaining rules one by one in order to find a match in the database. |
|||
|
|||
- Skip the rule if it requires a special condition that is not |
|||
satisfied. |
|||
- If one match is found: |
|||
|
|||
- Stop traversing the rest of valid rules. |
|||
- **Update** that record. |
|||
- If zero or multiple matches are found: |
|||
|
|||
- Continue with the next rule. |
|||
- If all rules are exhausted and no single match is found: |
|||
|
|||
- **Create** a new record. |
|||
- If there are no match rules for your model: |
|||
|
|||
- **Create** a new record. |
|||
|
|||
By default 2 rules are installed for production instances: |
|||
|
|||
- One rule that will allow you to update companies based on their VAT, when |
|||
``is_company`` is ``True``. |
|||
- One rule that will allow you to update users based on their login. |
|||
|
|||
In demo instances there are more examples. |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
To configure this module, you need to: |
|||
|
|||
#. Go to *Settings > Technical > Database Structure > Import Match*. |
|||
#. *Create*. |
|||
#. Choose a *Model*. |
|||
#. Choose the *Fields* that conform an unique key in that model. |
|||
#. If the rule must be used only for certain imported values, check |
|||
*Conditional* and enter the **exact string** that is going to be imported |
|||
in *Imported value*. |
|||
|
|||
#. Keep in mind that the match here is evaluated as a case sensitive |
|||
**text string** always. If you enter e.g. ``True``, it will match that |
|||
string, but will not match ``1`` or ``true``. |
|||
#. *Save*. |
|||
|
|||
In that list view, you can sort rules by drag and drop. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
To use this module, you need to: |
|||
|
|||
#. Follow steps in **Configuration** section above. |
|||
#. Go to any list view. |
|||
#. Press *Import* and follow the import procedure as usual. |
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
|||
|
|||
Known Issues / Roadmap |
|||
====================== |
|||
|
|||
* Add a setting to throw an error when multiple matches are found, instead of |
|||
falling back to creation of new record. |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please |
|||
check there if your issue has already been reported. If you spotted it first, |
|||
help us smashing it by providing a detailed and welcomed `feedback |
|||
<https://github.com/OCA/ |
|||
server-tools/issues/new?body=module:%20 |
|||
base_import_match%0Aversion:%20 |
|||
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Jairo Llopis <yajo.sk8@gmail.com> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import models |
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Base Import Match", |
|||
"summary": "Try to avoid duplicates before importing", |
|||
"version": "8.0.1.0.0", |
|||
"category": "Tools", |
|||
"website": "https://grupoesoc.es", |
|||
"author": "Grupo ESOC Ingeniería de Servicios, " |
|||
"Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"application": False, |
|||
"installable": True, |
|||
"depends": [ |
|||
"base_import", |
|||
], |
|||
"data": [ |
|||
"security/ir.model.access.csv", |
|||
"data/base_import_match.yml", |
|||
"views/base_import_match_view.xml", |
|||
], |
|||
"demo": [ |
|||
"demo/base_import_match.yml", |
|||
], |
|||
} |
@ -0,0 +1,29 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
- !context {noupdate: True} |
|||
|
|||
# Match partners by VAT when is_company is True |
|||
- !record {id: res_partner_vat, model: base_import.match}: |
|||
model_id: base.model_res_partner |
|||
sequence: 10 |
|||
|
|||
- !record {id: res_partner_vat_vat, model: base_import.match.field}: |
|||
match_id: res_partner_vat |
|||
field_id: base.field_res_partner_vat |
|||
|
|||
- !record {id: res_partner_vat_is_company, model: base_import.match.field}: |
|||
match_id: res_partner_vat |
|||
field_id: base.field_res_partner_is_company |
|||
conditional: True |
|||
imported_value: "True" |
|||
|
|||
# Match users by login |
|||
- !record {id: res_users_login, model: base_import.match}: |
|||
model_id: base.model_res_users |
|||
sequence: 50 |
|||
|
|||
- !record {id: res_users_login_login, model: base_import.match.field}: |
|||
match_id: res_users_login |
|||
field_id: base.field_res_users_login |
@ -0,0 +1,40 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
- !context {noupdate: True} |
|||
|
|||
# Match partners by name, parent_id and is_company |
|||
- !record {id: res_partner_parent_name_is_company, model: base_import.match}: |
|||
model_id: base.model_res_partner |
|||
sequence: 20 |
|||
|
|||
- !record {id: res_partner_parent_name_is_company_name, model: base_import.match.field}: |
|||
match_id: res_partner_parent_name_is_company |
|||
field_id: base.field_res_partner_name |
|||
|
|||
- !record {id: res_partner_parent_name_is_company_parent, model: base_import.match.field}: |
|||
match_id: res_partner_parent_name_is_company |
|||
field_id: base.field_res_partner_parent_id |
|||
|
|||
- !record {id: res_partner_parent_name_is_company_is_company, model: base_import.match.field}: |
|||
match_id: res_partner_parent_name_is_company |
|||
field_id: base.field_res_partner_is_company |
|||
|
|||
# Match partner by email |
|||
- !record {id: res_partner_email, model: base_import.match}: |
|||
model_id: base.model_res_partner |
|||
sequence: 30 |
|||
|
|||
- !record {id: res_partner_email_email, model: base_import.match.field}: |
|||
match_id: res_partner_email |
|||
field_id: base.field_res_partner_email |
|||
|
|||
# Match partner by name |
|||
- !record {id: res_partner_name, model: base_import.match}: |
|||
model_id: base.model_res_partner |
|||
sequence: 40 |
|||
|
|||
- !record {id: res_partner_name_name, model: base_import.match.field}: |
|||
match_id: res_partner_name |
|||
field_id: base.field_res_partner_name |
@ -0,0 +1,83 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * base_import_match |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 8.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2016-05-11 12:24+0200\n" |
|||
"PO-Revision-Date: 2016-05-11 12:26+0200\n" |
|||
"Language-Team: \n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: 8bit\n" |
|||
"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
|||
"X-Generator: Poedit 1.8.7.1\n" |
|||
"Last-Translator: Jairo Llopis <j.llopis@grupoesoc.es>\n" |
|||
"Language: es_ES\n" |
|||
|
|||
#. module: base_import_match |
|||
#: model:ir.model,name:base_import_match.model_base_import_match |
|||
msgid "Deduplicate settings prior to CSV imports." |
|||
msgstr "Configuración para deduplicar antes de importar CSV." |
|||
|
|||
#. module: base_import_match |
|||
#: field:base_import.match,display_name:0 |
|||
msgid "Display Name" |
|||
msgstr "Nombre a mostrar" |
|||
|
|||
#. module: base_import_match |
|||
#: sql_constraint:base_import.match:0 |
|||
msgid "Duplicated match!" |
|||
msgstr "¡Coincidencia duplicada!" |
|||
|
|||
#. module: base_import_match |
|||
#: field:base_import.match,field_ids:0 |
|||
msgid "Fields" |
|||
msgstr "Campos" |
|||
|
|||
#. module: base_import_match |
|||
#: help:base_import.match,field_ids:0 |
|||
msgid "Fields that will define an unique key." |
|||
msgstr "Campos que definirán una clave única." |
|||
|
|||
#. module: base_import_match |
|||
#: view:base_import.match:base_import_match.match_search_view |
|||
msgid "Group By" |
|||
msgstr "Agrupar por" |
|||
|
|||
#. module: base_import_match |
|||
#: view:base_import.match:base_import_match.match_form_view |
|||
#: view:base_import.match:base_import_match.match_search_view |
|||
#: view:base_import.match:base_import_match.match_tree_view |
|||
#: model:ir.actions.act_window,name:base_import_match.match_action |
|||
#: model:ir.ui.menu,name:base_import_match.match_menu |
|||
msgid "Import Match" |
|||
msgstr "Coincidencia de importación" |
|||
|
|||
#. module: base_import_match |
|||
#: help:base_import.match,model_id:0 |
|||
msgid "In this model you will apply the match." |
|||
msgstr "En este modelo se aplicará la coincidencia." |
|||
|
|||
#. module: base_import_match |
|||
#: field:base_import.match,__last_update:0 |
|||
msgid "Last Modified on" |
|||
msgstr "Última actualización por" |
|||
|
|||
#. module: base_import_match |
|||
#: view:base_import.match:base_import_match.match_search_view |
|||
#: field:base_import.match,model_id:0 field:base_import.match,model_name:0 |
|||
msgid "Model" |
|||
msgstr "Modelo" |
|||
|
|||
#. module: base_import_match |
|||
#: field:base_import.match,name:0 |
|||
msgid "Name" |
|||
msgstr "Nombre" |
|||
|
|||
#. module: base_import_match |
|||
#: field:base_import.match,sequence:0 |
|||
msgid "Sequence" |
|||
msgstr "Secuencia" |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import base_import |
@ -0,0 +1,297 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openerp import api, fields, models |
|||
from openerp import SUPERUSER_ID # TODO remove in v10 |
|||
|
|||
|
|||
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=[("osv_memory", "=", False)], |
|||
help="In this model you will apply the match.") |
|||
model_name = fields.Char( |
|||
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.multi |
|||
@api.onchange("model_id") |
|||
def _onchange_model_id(self): |
|||
self.field_ids.unlink() |
|||
|
|||
@api.model |
|||
def create(self, vals): |
|||
"""Wrap the model after creation.""" |
|||
result = super(BaseImportMatch, self).create(vals) |
|||
self._load_autopatch(result.model_name) |
|||
return result |
|||
|
|||
@api.multi |
|||
def unlink(self): |
|||
"""Unwrap the model after deletion.""" |
|||
models = set(self.mapped("model_name")) |
|||
result = super(BaseImportMatch, self).unlink() |
|||
for model in models: |
|||
self._load_autopatch(model) |
|||
return result |
|||
|
|||
@api.multi |
|||
def write(self, vals): |
|||
"""Wrap the model after writing.""" |
|||
result = super(BaseImportMatch, self).write(vals) |
|||
|
|||
if "model_id" in vals or "model_name" in vals: |
|||
for s in self: |
|||
self._load_autopatch(s.model_name) |
|||
|
|||
return result |
|||
|
|||
# TODO convert to @api.model_cr in v10 |
|||
def _register_hook(self, cr): |
|||
"""Autopatch on init.""" |
|||
models = set( |
|||
self.browse( |
|||
cr, |
|||
SUPERUSER_ID, |
|||
self.search(cr, SUPERUSER_ID, list())) |
|||
.mapped("model_name")) |
|||
for model in models: |
|||
self._load_autopatch(cr, SUPERUSER_ID, model) |
|||
|
|||
@api.multi |
|||
@api.depends("model_id", "field_ids") |
|||
def _compute_name(self): |
|||
"""Automatic self-descriptive name for the setting records.""" |
|||
for s in self: |
|||
s.name = "{}: {}".format( |
|||
s.model_id.display_name, |
|||
" + ".join( |
|||
s.field_ids.mapped( |
|||
lambda r: ( |
|||
str(r.field_id.name) + |
|||
(" ({})".format(r.imported_value) |
|||
if r.conditional else ""))))) |
|||
|
|||
@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 openerp.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:`openerp.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 openerp.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_for_load(model._name, converted_row.keys()) |
|||
|
|||
# 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 |
|||
|
|||
# Return an empty match if none or multiple was found |
|||
return model |
|||
|
|||
@api.model |
|||
def _load_wrapper(self): |
|||
"""Create a new load patch method.""" |
|||
@api.model |
|||
def wrapper(self, fields, data): |
|||
"""Try to identify rows by other pseudo-unique keys. |
|||
|
|||
It searches for rows that have no XMLID specified, and gives them |
|||
one if any :attr:`~.field_ids` combination is found. With a valid |
|||
XMLID in place, Odoo will understand that it must *update* the |
|||
record instead of *creating* a new one. |
|||
""" |
|||
newdata = list() |
|||
|
|||
# Data conversion to ORM format |
|||
import_fields = map(models.fix_import_export_id_paths, fields) |
|||
converted_data = self._convert_records( |
|||
self._extract_records(import_fields, data)) |
|||
|
|||
# Mock Odoo to believe the user is importing the ID field |
|||
if "id" not in fields: |
|||
fields.append("id") |
|||
import_fields.append(["id"]) |
|||
|
|||
# Needed to match with converted data field names |
|||
clean_fields = [f[0] for f in import_fields] |
|||
|
|||
for dbid, xmlid, record, info in converted_data: |
|||
row = dict(zip(clean_fields, data[info["record"]])) |
|||
match = self |
|||
|
|||
if xmlid: |
|||
# Skip rows with ID, they do not need all this |
|||
row["id"] = xmlid |
|||
elif dbid: |
|||
# Find the xmlid for this dbid |
|||
match = self.browse(dbid) |
|||
else: |
|||
# Store records that match a combination |
|||
match = self.env["base_import.match"]._match_find( |
|||
self, record, row) |
|||
|
|||
# Give a valid XMLID to this row if a match was found |
|||
row["id"] = (match._BaseModel__export_xml_id() |
|||
if match else row.get("id", u"")) |
|||
|
|||
# Store the modified row, in the same order as fields |
|||
newdata.append(tuple(row[f] for f in clean_fields)) |
|||
|
|||
# Leave the rest to Odoo itself |
|||
del data |
|||
return wrapper.origin(self, fields, newdata) |
|||
|
|||
# Flag to avoid confusions with other possible wrappers |
|||
wrapper.__base_import_match = True |
|||
|
|||
return wrapper |
|||
|
|||
@api.model |
|||
def _load_autopatch(self, model_name): |
|||
"""[Un]apply patch automatically.""" |
|||
self._load_unpatch(model_name) |
|||
if self.search([("model_name", "=", model_name)]): |
|||
self._load_patch(model_name) |
|||
|
|||
@api.model |
|||
def _load_patch(self, model_name): |
|||
"""Apply patch for :param:`model_name`'s load method. |
|||
|
|||
:param str model_name: |
|||
Model technical name, such as ``res.partner``. |
|||
""" |
|||
self.env[model_name]._patch_method( |
|||
"load", self._load_wrapper()) |
|||
|
|||
@api.model |
|||
def _load_unpatch(self, model_name): |
|||
"""Apply patch for :param:`model_name`'s load method. |
|||
|
|||
:param str model_name: |
|||
Model technical name, such as ``res.partner``. |
|||
""" |
|||
model = self.env[model_name] |
|||
|
|||
# Unapply patch only if there is one |
|||
try: |
|||
if model.load.__base_import_match: |
|||
model._revert_method("load") |
|||
except AttributeError: |
|||
pass |
|||
|
|||
@api.model |
|||
def _usable_for_load(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. |
|||
""" |
|||
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 |
|||
|
|||
|
|||
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", |
|||
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.multi |
|||
@api.onchange("field_id", "match_id", "conditional", "imported_value") |
|||
def _onchange_match_id_name(self): |
|||
"""Update match name.""" |
|||
self.mapped("match_id")._compute_name() |
@ -0,0 +1,5 @@ |
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
|||
access_base_import_match,Access base_import.match,model_base_import_match,base.group_user,1,0,0,0 |
|||
access_base_import_match_field,Access base_import.match.field,model_base_import_match_field,base.group_user,1,0,0,0 |
|||
write_base_import_match,Write base_import.match,model_base_import_match,base.group_system,1,1,1,1 |
|||
write_base_import_match_field,Write base_import.match.field,model_base_import_match_field,base.group_system,1,1,1,1 |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import test_import |
@ -0,0 +1,2 @@ |
|||
email,name |
|||
michel.fletcher@agrolait.example.com,Michel Fletcher Changed |
@ -0,0 +1,2 @@ |
|||
function,name |
|||
Function Changed,Michel Fletcher |
@ -0,0 +1,2 @@ |
|||
name,is_company,parent_id/id,email |
|||
Michel Fletcher,False,base.res_partner_2,changed@agrolait.example.com |
@ -0,0 +1,2 @@ |
|||
name,vat,is_company |
|||
Agrolait Changed,BE0477472701,True |
@ -0,0 +1,2 @@ |
|||
login,name |
|||
demo,Demo User Changed |
@ -0,0 +1,68 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from os import path |
|||
from openerp.tests.common import TransactionCase |
|||
|
|||
|
|||
PATH = path.join(path.dirname(__file__), "import_data", "%s.csv") |
|||
OPTIONS = { |
|||
"headers": True, |
|||
"quoting": '"', |
|||
"separator": ",", |
|||
} |
|||
|
|||
|
|||
class ImportCase(TransactionCase): |
|||
def _base_import_record(self, res_model, file_name): |
|||
"""Create and return a ``base_import.import`` record.""" |
|||
with open(PATH % file_name) as demo_file: |
|||
return self.env["base_import.import"].create({ |
|||
"res_model": res_model, |
|||
"file": demo_file.read(), |
|||
"file_name": "%s.csv" % file_name, |
|||
"file_type": "csv", |
|||
}) |
|||
|
|||
def test_res_partner_vat(self): |
|||
"""Change name based on VAT.""" |
|||
agrolait = self.env.ref("base.res_partner_2") |
|||
agrolait.vat = "BE0477472701" |
|||
record = self._base_import_record("res.partner", "res_partner_vat") |
|||
record.do(["name", "vat", "is_company"], OPTIONS) |
|||
agrolait.env.invalidate_all() |
|||
self.assertEqual(agrolait.name, "Agrolait Changed") |
|||
|
|||
def test_res_partner_parent_name_is_company(self): |
|||
"""Change email based on parent_id, name and is_company.""" |
|||
record = self._base_import_record( |
|||
"res.partner", "res_partner_parent_name_is_company") |
|||
record.do(["name", "is_company", "parent_id/id", "email"], OPTIONS) |
|||
self.assertEqual( |
|||
self.env.ref("base.res_partner_address_4").email, |
|||
"changed@agrolait.example.com") |
|||
|
|||
def test_res_partner_email(self): |
|||
"""Change name based on email.""" |
|||
record = self._base_import_record("res.partner", "res_partner_email") |
|||
record.do(["email", "name"], OPTIONS) |
|||
self.assertEqual( |
|||
self.env.ref("base.res_partner_address_4").name, |
|||
"Michel Fletcher Changed") |
|||
|
|||
def test_res_partner_name(self): |
|||
"""Change function based on name.""" |
|||
record = self._base_import_record("res.partner", "res_partner_name") |
|||
record.do(["function", "name"], OPTIONS) |
|||
self.assertEqual( |
|||
self.env.ref("base.res_partner_address_4").function, |
|||
"Function Changed") |
|||
|
|||
def test_res_users_login(self): |
|||
"""Change name based on login.""" |
|||
record = self._base_import_record("res.users", "res_users_login") |
|||
record.do(["login", "name"], OPTIONS) |
|||
self.assertEqual( |
|||
self.env.ref("base.user_demo").name, |
|||
"Demo User Changed") |
@ -0,0 +1,80 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> |
|||
|
|||
<openerp> |
|||
<data> |
|||
|
|||
<record id="match_form_view" model="ir.ui.view"> |
|||
<field name="name">Import match form view</field> |
|||
<field name="model">base_import.match</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Import Match"> |
|||
<sheet> |
|||
<h1> |
|||
<field name="name"/> |
|||
</h1> |
|||
<group> |
|||
<field name="model_id"/> |
|||
<field name="field_ids"> |
|||
<tree editable="bottom"> |
|||
<field name="field_id" |
|||
options="{'no_create': True}"/> |
|||
<field name="match_id" invisible="True"/> |
|||
<field name="model_id" invisible="True"/> |
|||
<field name="conditional"/> |
|||
<field |
|||
name="imported_value" |
|||
attrs="{ |
|||
'readonly': [ |
|||
('conditional', '=', False), |
|||
], |
|||
}"/> |
|||
</tree> |
|||
</field> |
|||
<field name="sequence"/> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="match_tree_view" model="ir.ui.view"> |
|||
<field name="name">Import match tree view</field> |
|||
<field name="model">base_import.match</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Import Match"> |
|||
<field name="name"/> |
|||
<field name="sequence" invisible="True"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="match_search_view" model="ir.ui.view"> |
|||
<field name="name">Import match search view</field> |
|||
<field name="model">base_import.match</field> |
|||
<field name="arch" type="xml"> |
|||
<search string="Import Match"> |
|||
<field name="name"/> |
|||
<field name="model_id"/> |
|||
<field name="field_ids"/> |
|||
<separator/> |
|||
<group expand="0" string="Group By"> |
|||
<filter string="Model" context="{'group_by': 'model_id'}"/> |
|||
</group> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<act_window |
|||
name="Import Match" |
|||
res_model="base_import.match" |
|||
id="match_action"/> |
|||
|
|||
<menuitem |
|||
id="match_menu" |
|||
action="match_action" |
|||
parent="base.next_id_9"/> |
|||
|
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue