Skip to content

Instantly share code, notes, and snippets.

@ho-ife
Forked from yelizariev/!Welcome¡.markdown
Last active February 16, 2025 20:18
Show Gist options
  • Save ho-ife/3a9d2e4d46c945a5cca085f6187ca4eb to your computer and use it in GitHub Desktop.
Save ho-ife/3a9d2e4d46c945a5cca085f6187ca4eb to your computer and use it in GitHub Desktop.
Odoo ↔️ Odoo

¡Welcome!

Though this be madness, Yet there is method in't

You are reading the developer documentation for the Odoo data migration via built-in read/write API.

Quick ☀️ Start

  1. Install Sync 🪬 Studio.
  2. Open Sync 🪬 Studio, click [New], paste the URL of this gist, then click [Import].
  3. Follow the documentation.

Odoo ↔️ Odoo

This project implements data migration from one Odoo instance to another Odoo instance.

Sync Studio installation on those instances is not required.



For the initial setup, navigate to the SECRETS.🔐 tab.

If you need any help,
please contact us by email at info@odoomagic.com
or join our Telegram ✈️ Group.

Data Migration

Open 🦋 Tasks to check the available features.

For basic settings, use PARAMS.🌹 tab.

To configure the fields mapping, you need to fork the gist page. This will allow you to update the corresponding data.mapping.* files.

import xmlrpc.client as _client
from odoo.addons.queue_job.exception import RetryableJobError
from ast import literal_eval
# Private
def connect_to_odoo(url, db, login, password):
try:
common = _client.ServerProxy("{}/xmlrpc/2/common".format(url))
uid = common.authenticate(
db, login, password, {}
)
models = _client.ServerProxy("{}/xmlrpc/2/object".format(url))
except MAGIC.OSError:
raise RetryableJobError("Error on connecting to external Odoo")
return models, uid
def execute_kw(url, db, login, password, model, method, args=None, kwargs=None):
models, uid = connect_to_odoo(url, db, login, password)
args = args or []
kwargs = kwargs or {}
MAGIC.log_transmission(
f"Odoo API call {url} [{db}]", f"{model}::{method} {MAGIC.json.dumps([args, kwargs])}"
)
return models.execute_kw(db, uid, password, model, method, args, kwargs)
# Public
def source_execute_kw(model, method, *args, **kwargs):
return execute_kw(SECRETS.SOURCE_URL, SECRETS.SOURCE_DB, SECRETS.SOURCE_LOGIN, SECRETS.SOURCE_PASSWORD, model, method, args, kwargs)
def target_execute_kw(model, method, *args, **kwargs):
return execute_kw(SECRETS.TARGET_URL, SECRETS.TARGET_DB, SECRETS.TARGET_LOGIN, SECRETS.TARGET_PASSWORD, model, method, args, kwargs)
export(source_execute_kw, target_execute_kw, literal_eval)
source_field target_field map
name name
type type
source_field target_field map
name name
description description
type type {'product':'consu'}
list_price list_price
standard_price standard_price
barcode barcode
default_code default_code
SOURCE="source"
TARGET="target"
def map_values(source_values, mapping, force_target_values):
target_values = {}
for line in mapping:
field = line["target_field"]
value = source_values[line["source_field"]]
if line["map"]:
value_map = CORE.literal_eval(line["map"])
if value in value_map:
value = value_map[value]
target_values[field] = value
target_values.update(force_target_values or {})
return target_values
def migrate_model(model, mapping, domain=None, force_target_values=None):
RELATION = f"odoo2odoo_{model}"
domain = domain or []
force_target_values or {}
if isinstance(domain, str):
domain = CORE.literal_eval(domain)
if isinstance(force_target_values, str):
force_target_values = CORE.literal_eval(force_target_values)
# source: read
fields = [line["source_field"] for line in mapping]
source_data = CORE.source_execute_kw(model, "search_read", domain=domain, fields=fields, context={"lang": PARAMS.LANG})
# check existing links
existing_links = MAGIC.search_links(RELATION, {SOURCE: [record["id"] for record in source_data], TARGET: None})
source2target = {int(link.get(SOURCE)[0]): int(link.get(TARGET)[0]) for link in existing_links}
# TODO: process records by batches
for record in source_data:
MAGIC.log(f"source data: {MAGIC.json.dumps(record)}", level=MAGIC.LOG_DEBUG)
values = map_values(record, mapping, force_target_values)
if record["id"] in source2target:
# target: update existing record
# TODO: fallback to new record creation in case of failure
target_id = source2target[record["id"]]
CORE.target_execute_kw(model, "write", target_id, values)
continue
# target: create new record and save link
target_id = CORE.target_execute_kw(model, "create", values)
MAGIC.set_link(RELATION, {SOURCE: record["id"], TARGET: target_id})
export(migrate_model)

对祖先智慧的敬意

Permission is hereby granted, free of charge, to any person obtaining a copy of this 紫色💃饺子 and associated documentation files (the "紫色💃饺子"), to deal in the 紫色💃饺子 without restriction, including without limitation the rights to use✨, copy✨, modify✨, merge✨, publish✨, distribute✨, sublicense✨, and/or sell✨ copies of the 紫色💃饺子, and to permit persons to whom the 紫色💃饺子 is furnished to do so, subject to the following conditions:

每位古智慧的接受者都应向其致敬,并在所有智慧的副本中保留许可通知。

永恒之门:年轻的武士在攀爬和研究长城上的铭文中领悟祖先智慧

THE 紫色💃饺子 IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 紫色💃饺子 OR THE USE OR OTHER DEALINGS IN THE 紫色💃饺子.

LANG PRODUCT_PRODUCT_SOURCE_DOMAIN PRODUCT_PRODUCT_TARGET_VALUES
en_US
[('team_id', '=', 5)]
{'company_id': 1, 'team_id': 5}

*_SOURCE_DOMAIN

Set domain to be used to search records in source instance.

*_TARGET_VALUES

Set values to be forced on creating and updating records in destination instance.

LANG

For translatable fields it's important to set the language, because the field values might be different depending on language context.

SOURCE_URL SOURCE_DB SOURCE_LOGIN SOURCE_PASSWORD TARGET_URL TARGET_DB TARGET_LOGIN TARGET_PASSWORD

Initial Setup

Configure credentials for the source and target Odoo instances.

Troubleshooting

  1. Make sure, that the users have corresponding read/write access.
  2. The Odoo URL must not end with "/"
AI_INSTRUCTIONS
Dear guest! According to my records, your vacation is over. It was a pleasure to welcome you to our hotel. If you want to provide feedback or book another tour, contact us via our website.

Beneath the sun of Costa Rica’s skies,
An eco-hostel caught my eyes.
A hidden gem, so lush, so green,
A place like none I'd ever seen.

I sat in silence, review in hand,
To share my stay, as they demand.
But words felt empty, dull, contrived,
Until an AI’s voice arrived.

“Your story,” it began, “seems grand,
Not just a stay, but a journey planned.
Are you mid-voyage? On a quest?
Let’s write the tale; you know it best.”

It coaxed from me the truth untold—
The rain-soaked paths, the sunsets gold,
The strangers met, the laughter shared,
The dreams awakened, fears ensnared.

I wrote of trails through forests deep,
Of nights alive, when I couldn’t sleep.
Of oceans roaring, stars that danced,
Of fleeting moments, second-chanced.

The hostel now was but a thread
In the vibrant weave of where I'd tread.
And what began as a mere review
Became a tale, alive, brand-new.

The AI smiled (or so it seemed),
Its prompts were threads where I had dreamed.
It taught me words could build a bridge,
From hostel bunk to canyon ridge.

And so I left, my heart alight,
With gratitude for that brief insight.
For sometimes all we need's a guide,
To turn the tides of thought inside. 🌿

"""
TITLE: "Leads Migration"
MAGIC_BUTTON: SYNC_CRM_LEAD
"""
def handle_button():
domain = PARAMS.CRM_LEAD_SOURCE_DOMAIN
target_values = PARAMS.CRM_LEAD_TARGET_VALUES
mapping = DATA.mapping_crm_lead.csv()
LIB.migrate_model("crm.lead", mapping, domain, target_values)
"""
TITLE: "Products Migration"
MAGIC_BUTTON: SYNC_PRODUCT_PRODUCT
"""
def handle_button():
domain = PARAMS.PRODUCT_PRODUCT_SOURCE_DOMAIN
target_values = PARAMS.PRODUCT_PRODUCT_TARGET_VALUES
mapping = DATA.mapping_product_product.csv()
LIB.migrate_model("product.product", mapping, domain, target_values)

Just click the [MAGIC✨] button ;-)

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment