メインコンテンツにスキップ

Odoo ValidationError: 必須フィールドが設定されていません — 修正ガイド

ValidationError のヒット: Odoo 17/18/19 ではフィールド X が必要ですか?コード例を使用して、スキーマ、デフォルト、および変更時のチェーンを診断して永続的に修正します。

E
ECOSIRE Research and Development Team
|2026年5月4日6 分で読める1.2k 語数|

この記事は現在英語版のみです。翻訳は近日公開予定です。

You save a record and Odoo blocks it with a red toast and this server log entry:

You save a record and Odoo blocks it with a red toast and this server log entry:

odoo.exceptions.ValidationError: The field 'partner_invoice_id' is required.
(Model: sale.order, Record: NewId_0x7f8a2c0)

Sometimes the field is one a user can see and fill in. More often it is an internal field that should have been auto-populated by an onchange, a default, or a related computation — and something broke that chain. This guide separates the user-facing case from the silent-data-pipeline case and gives a fix for each.

Quick Fix

If the field is visible on the form, the user just needs to fill it. If it is hidden or expected to auto-fill, run this in a shell to confirm the default is firing:

order = env['sale.order'].new({'partner_id': 7})
print(order.partner_invoice_id)  # should not be empty

If it is empty, the onchange chain is broken. The fastest patch is to add an explicit default:

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    partner_invoice_id = fields.Many2one(
        'res.partner',
        default=lambda self: self.env.user.partner_id.id,
    )

That keeps the form usable while you investigate the real cause.

Why This Happens

ValidationError for a missing required field has six common roots in 17.0/18.0/19.0:

  1. A required field was added to a model in a custom module without a default and without a database migration to backfill existing rows.
  2. An onchange method that used to set the field stopped firing — common when a parent field was renamed or a custom module overrides _onchange_partner_id and forgets to call super().
  3. A related field's source is empty. partner_invoice_id on sale.order is computed from partner_id. If partner_id is set to a partner without invoice_partner_id, the computation returns nothing.
  4. A compute field stopped recomputing because of a stale cache or wrong @api.depends.
  5. A studio/customization added required=True to an existing field without realizing legacy data has nulls.
  6. A web client form bypassed the server defaults — happens when third-party modules use JSON-RPC create directly with partial payloads.

Step-by-Step Diagnosis

1. Read the exception carefully. It names the model and the field. Both matter.

2. Confirm the field really is required=True. In a shell:

field = env['sale.order']._fields['partner_invoice_id']
print(field.required, field.default, field.related, field.compute)

If required is True but default and compute are both None, the field has no auto-fill and any incomplete payload will fail.

3. Test the onchange chain in isolation.

form = Form(env['sale.order'])
form.partner_id = env['res.partner'].browse(7)
print(form.partner_invoice_id)  # is it set?

If the form helper shows the field empty, your onchange is broken.

4. Check for migration backlog. If a field was made required recently and existing rows still have NULL:

SELECT count(*) FROM sale_order WHERE partner_invoice_id IS NULL;

Any non-zero result is a data-migration bug that will keep biting until backfilled.

5. Check @api.depends correctness. A common Odoo 18.0 mistake is depending on a @property instead of a real field — @api.depends only works with stored fields.

Permanent Fix

For each cause:

Cause 1 — New required field, no migration. Ship a migration script in migrations/<version>/post-migration.py:

def migrate(cr, version):
    if not version:
        return
    cr.execute("""
        UPDATE sale_order
        SET    partner_invoice_id = partner_id
        WHERE  partner_invoice_id IS NULL
    """)

Always backfill before promoting a field to required.

Cause 2 — Broken onchange. Audit all overrides:

@api.onchange('partner_id')
def _onchange_partner_id(self):
    res = super()._onchange_partner_id()  # <-- DO NOT DROP THIS
    # custom logic here
    return res

Forgetting super() in an onchange override is the single most common source of "required field not set" in custom modules.

Cause 3 — Empty related source. Add a defensive default that falls back to the parent:

partner_invoice_id = fields.Many2one(
    'res.partner',
    compute='_compute_partner_invoice_id',
    store=True,
    readonly=False,
)

@api.depends('partner_id')
def _compute_partner_invoice_id(self):
    for rec in self:
        rec.partner_invoice_id = rec.partner_id.address_get(['invoice'])['invoice'] or rec.partner_id

address_get is the canonical Odoo way to resolve invoice/delivery contacts with a fallback.

Cause 4 — Stale compute cache. If you suspect cache, force recompute:

self.invalidate_recordset()
self._compute_partner_invoice_id()

But the durable fix is to correct @api.depends to list every triggering field.

Cause 5 — required=True added by Studio. Either backfill the data with a one-off SQL update (with backup first), or relax the field to required=False and validate at the workflow boundary instead.

Cause 6 — JSON-RPC payloads. Validate at the controller layer. Never trust external callers to populate every required field — apply defaults server-side before calling create().

How to Prevent It

  • Treat required=True as a contract change. Adding required=True to an existing field is a breaking change. Ship it with a migration that guarantees no NULLs and a default that prevents future NULLs.
  • Always call super() in onchange/compute/create/write overrides. Make this a code-review rule. Most missing-default bugs trace back to a forgotten super-call.
  • Lint for @api.depends correctness. OCA's pylint-odoo ships a checker for this. Wire it into CI.
  • Server-side default fallback. For any required Many2one that depends on another field, always provide a default= callable as a belt-and-braces backup, not just an onchange.
  • Snapshot test the create path. A simple integration test that creates each major business record with the minimum payload catches every missing-default regression before deploy.

Frequently Asked Questions

Should I add required=True to fix data quality, or rely on workflow checks?

Workflow checks. required=True is enforced on every write at every layer, including imports and migrations, which makes data fixes painful. Validate "must have an invoice address before confirming" at action_confirm, not at the field level. You keep the data discipline without the migration nightmare.

My field is compute=... and store=True. Why does Odoo say it is empty?

Either the compute did not run (check @api.depends), or it ran but assigned an empty value. Recordsets can be assigned False or an empty recordset, both of which read as falsy. Add a unit test that creates the record and asserts the computed value is non-empty.

What is the difference between default= and _compute_default_*()?

default= runs once at record creation and never again. compute= runs whenever a dependency changes. If the field should be set once and respected forever (like a sequence-based name), use default. If it should track another field, use compute with store=True.

Why does the same record save fine via XML import but fail in the UI?

XML imports run as superuser and skip many onchange chains, populating fields directly from the XML. UI saves go through the full onchange/default pipeline. If the UI fails, the XML is not actually testing your defaults — write a Python test instead.


Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.

共有:
E

執筆者

ECOSIRE Team

Technical Writing

The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.

ECOSIRE

Odoo ERP でビジネスを変革

Odoo の専門家による実装、カスタマイズ、サポートにより、業務を合理化します。