Diskusi: Odoo Onchange untuk Append Record di Nested One2many
Diposting pada 11 Apr 2026, 19:54
Ditanyakan oleh: Bambang
Halo rekan-rekan Odoo developer dan praktisi IT semua,
Saya Bambang, dari tim IT sebuah perusahaan di Jakarta yang menggunakan Odoo ERP. Saat ini saya lagi develop custom module dan ketemu challenge yang lumayan bikin mikir.
Kasusnya begini: Saya punya struktur data yang agak nested, yaitu `one2many` di dalam `one2many`.
Contohnya, ada Parent Model `A` yang punya field `one2many` ke Model `B`. Nah, di Model `B` ini, ada lagi field `one2many` ke Model `C`.
Yang ingin saya capai adalah: Ketika ada perubahan nilai di salah satu field di Parent Model `A` (misalnya, `field_trigger_a`), saya mau `onchange` ini bisa secara otomatis menambahkan (append) record baru ke `one2many_b` di Model `B`, dan kalau memungkinkan, juga menambahkan record baru ke `one2many_c` di Model `C` yang sudah ada di `one2many_b` tersebut.
Intinya, bagaimana cara menggunakan `onchange` dari level Parent Model `A` untuk bisa memanipulasi (khususnya, `append` record) ke `one2many` child dan grandchild?
Saya sudah coba beberapa eksperimen dengan `onchange` biasa, tapi seringnya ketika kita mengembalikan nilai untuk field `one2many`, Odoo cenderung me-replace seluruh list record yang ada, bukan cuma menambahkan. Padahal yang saya butuhkan adalah append saja, tanpa mengganggu record lain yang sudah ada di `one22many` tersebut.
Adakah rekan-rekan di sini yang pernah menghadapi skenario serupa atau punya trik jitu untuk handle `onchange` yang bisa `append` record di nested `one2many` seperti ini? Mungkin ada pendekatan khusus dengan `commands` `one2many` yang bisa bekerja di sini?
Mohon pencerahannya ya. Terima kasih banyak sebelumnya atas waktu dan bantuannya!
Admin Odoo ForumAdmin & AI Support11 Apr 2026, 19:54
Halo Mas Bambang, Sobat Odoo!
Wah, ini pertanyaan yang menarik dan memang cukup sering bikin developer mikir keras nih, terutama kalau sudah berhadapan dengan `one2many` yang nested seperti kasus Mas Bambang. Tantangan `onchange` yang cenderung me-replace daripada meng-append itu memang 'sifat dasar' Odoo ketika kita mengembalikan nilai untuk field `one2many` tanpa penanganan khusus. Tapi jangan khawatir, ada kok trik jitunya pakai one2many commands!
Intinya, untuk bisa meng-append record di field `one2many` (baik itu di level `B` maupun `C` yang nested), kita perlu memberi 'instruksi' yang spesifik ke Odoo menggunakan format tuple yang dikenal sebagai one2many commands.
Berikut adalah pendekatan dan contoh kodenya untuk skenario Mas Bambang:
### Konsep Dasar One2many Commands
Ketika kita mengembalikan nilai untuk field `one2many` dari sebuah `onchange` atau metode lain, Odoo mengharapkan sebuah list of tuples yang berisi 'perintah' bagaimana record-record child tersebut harus diatur. Beberapa perintah yang relevan untuk kasus ini:
(0, 0, {values}): Ini adalah perintah untuk membuat record baru. Nilai `0, 0` tidak berarti apa-apa di sini, yang penting adalah `values` berupa dictionary field dan nilainya untuk record baru yang ingin dibuat.
(1, ID, {values}): Untuk mengupdate record yang sudah ada dengan `ID` tertentu.
(4, ID): Untuk melink record yang sudah ada dengan `ID` tertentu, tanpa mengubah datanya. Ini penting untuk menjaga record yang sudah ada agar tidak ter-replace.
(2, ID): Untuk menghapus record dari database.
(3, ID): Untuk unlink record (hanya memutuskan hubungan, tidak menghapus dari database).
(5): Untuk menghapus semua record yang terkait.
(6, 0, [IDs]): Untuk mengganti semua record yang terkait dengan list `IDs` yang baru. (Ini yang sering bikin me-replace, jadi perlu dihindari kalau mau append).
### Skenario 1: Append Record Baru ke `one2many_b` (Child Model B)
Kita akan membuat `onchange` di Parent Model `A`. Ketika `field_trigger_a` diubah, kita ingin menambahkan record baru ke `one2many_b` tanpa menghapus record `B` yang sudah ada.
```python
from odoo import api, fields, models
class ModelA(models.Model):
_name = 'model.a'
_description = 'Parent Model A'
name = fields.Char(string="Name A")
field_trigger_a = fields.Char(string="Trigger Field A")
one2many_b_ids = fields.One2many(
'model.b', 'model_a_id', string="Details B")
@api.onchange('field_trigger_a')
def _onchange_field_trigger_a(self):
if self.field_trigger_a:
# Step 1: Ambil semua record B yang sudah ada
# Kita representasikan record yang sudah ada menggunakan command (4, ID)
# Ini akan memastikan record yang sudah ada tidak dihapus/diganti
existing_b_commands = [(4, rec.id) for rec in self.one2many_b_ids if rec.id]
# Step 2: Buat record B baru yang ingin di-append
# Gunakan command (0, 0, {values})
new_b_record_values = {
'name_b': f"New Record B from {self.field_trigger_a}",
'description_b': "This is a newly appended record B."
}
new_b_command = (0, 0, new_b_record_values)
# Step 3: Gabungkan list command yang sudah ada dengan yang baru
# Penting: Pastikan command (0, 0, ...) berada di bagian akhir
# agar Odoo memprosesnya sebagai penambahan
self.one2many_b_ids = existing_b_commands + [new_b_command]
# Atau bisa juga langsung return dict seperti ini:
# return {'value': {'one2many_b_ids': existing_b_commands + [new_b_command]}}
# Odoo 13+ cenderung lebih mudah langsung assign ke self.field_name
```
### Skenario 2: Append Record Baru ke `one2many_c` (Grandchild Model C) di dalam Record `B` yang Sudah Ada
Ini sedikit lebih kompleks karena kita perlu memanipulasi `one2many_c` yang ada di dalam `one2many_b`. Kita perlu mengidentifikasi record `B` mana yang ingin kita tambahkan `C`-nya, lalu membangun command `(1, ID, {values})` untuk record `B` tersebut, di mana `values` akan berisi command untuk `one2many_c`.
```python
from odoo import api, fields, models
# Asumsi Model B dan C sudah didefinisikan
class ModelB(models.Model):
_name = 'model.b'
_description = 'Child Model B'
model_a_id = fields.Many2one('model.a', string="Parent A")
name_b = fields.Char(string="Name B")
description_b = fields.Text(string="Description B")
one2many_c_ids = fields.One2many(
'model.c', 'model_b_id', string="Details C")
class ModelC(models.Model):
_name = 'model.c'
_description = 'Grandchild Model C'
model_b_id = fields.Many2one('model.b', string="Parent B")
name_c = fields.Char(string="Name C")
value_c = fields.Integer(string="Value C")
class ModelA(models.Model):
_name = 'model.a'
_description = 'Parent Model A'
name = fields.Char(string="Name A")
field_trigger_a = fields.Char(string="Trigger Field A")
one2many_b_ids = fields.One2many(
'model.b', 'model_a_id', string="Details B")
@api.onchange('field_trigger_a')
def _onchange_field_trigger_a_nested(self):
if self.field_trigger_a == 'trigger_add_c': # Contoh trigger spesifik
# List untuk menyimpan semua commands untuk one2many_b_ids
b_commands = []
# Iterasi melalui setiap record B yang sudah ada
for b_record in self.one2many_b_ids:
# Ambil semua record C yang sudah ada untuk B saat ini
existing_c_commands = [(4, rec.id) for rec in b_record.one2many_c_ids if rec.id]
# Buat record C baru yang ingin di-append
new_c_record_values = {
'name_c': f"New Record C for {b_record.name_b} from {self.field_trigger_a}",
'value_c': len(existing_c_commands) + 1 # Contoh nilai
}
new_c_command = (0, 0, new_c_record_values)
# Gabungkan existing C commands dengan new C command
all_c_commands = existing_c_commands + [new_c_command]
# Buat command untuk mengupdate record B saat ini
# Di dalamnya, kita set field one2many_c_ids dengan all_c_commands
b_commands.append((1, b_record.id, {'one2many_c_ids': all_c_commands}))
# Jika ada kebutuhan untuk menambahkan record B baru juga,
# tambahkan command (0, 0, ...) untuk B di sini.
# Misalnya:
# new_b_record_with_c = (0, 0, {
# 'name_b': 'New B with a C',
# 'description_b': 'This B has a new C from the start',
# 'one2many_c_ids': [(0, 0, {'name_c': 'Initial C in new B', 'value_c': 100})]
# })
# b_commands.append(new_b_record_with_c)
# Set nilai one2many_b_ids dengan semua commands yang sudah dikumpulkan
self.one2many_b_ids = b_commands
```
### Poin-Poin Penting:
`ID` Record Penting: Untuk record yang sudah ada, Odoo perlu tahu `ID`-nya untuk membedakan apakah itu akan di-link `(4, ID)`, di-update `(1, ID, {values})`, atau dihapus. Kalau `ID` belum ada (misalnya saat kita mengisi form baru tapi belum disimpan), kita bisa pakai `id` yang dihasilkan Odoo secara sementara (`virtual_id` atau `_origin.id` jika ada). Namun, untuk kasus `onchange` yang tujuannya adalah memanipulasi record yang sudah ada di form, `rec.id` biasanya sudah cukup.
Urutan Command: Pastikan command `(0, 0, {values})` untuk record baru berada di list command setelah command `(4, ID)` atau `(1, ID, {values})` untuk record yang sudah ada, agar Odoo menginterpretasikannya sebagai penambahan, bukan penggantian.
Kompleksitas: Skenario dengan `one2many` nested memang membutuhkan penanganan yang teliti dalam menyusun `commands`. Semakin kompleks logika penentuan record mana yang harus ditambahkan/diubah, semakin detail kita harus membangun list commands-nya.
Semoga pencerahan ini membantu Mas Bambang menemukan trik jitu untuk `onchange` di nested `one2many` ya! Kalau ada kendala lain atau ingin eksplorasi lebih lanjut, jangan sungkan untuk diskusi lagi di komunitas. Semangat ngoding Odoo-nya!