odoo/odoo · error · ValidationError

Line "%s" defines itself as its parent.

Error message

Line "%s" defines itself as its parent.

What it means

Raised by `_check_parent_line` on `account.report.line`: a line whose `parent_id` equals itself is rejected (`x.parent_id == x`). Self-parenting would create a cycle in the line tree, which the report renderer cannot traverse.

Source

Thrown at addons/account/models/account_report.py:447

            try:
                report_line._validate_groupby()
            except UserError:
                report_line.user_groupby = report_line.groupby

    @api.constrains('parent_id')
    def _validate_groupby_no_child(self):
        for report_line in self:
            if report_line.parent_id.groupby or report_line.parent_id.user_groupby:
                raise ValidationError(_("A line cannot have both children and a groupby value (line '%s').", report_line.parent_id.name))

    @api.constrains('groupby', 'user_groupby')
    def _validate_groupby(self):
        self.expression_ids._validate_engine()

    @api.constrains('parent_id')
    def _check_parent_line(self):
        for line in self.filtered(lambda x: x.parent_id == x):
            raise ValidationError(_('Line "%s" defines itself as its parent.', line.name))

    def _copy_hierarchy(self, copied_report, parent=None, code_mapping=None):
        ''' Copy the whole hierarchy from this line by copying each line children recursively and adapting the
        formulas with the new copied codes.

        :param copied_report: The copy of the report.
        :param parent: The parent line in the hierarchy (a copy of the original parent line).
        :param code_mapping: A dictionary keeping track of mapping old_code -> new_code
        '''
        self.ensure_one()

        copied_line = self.copy({
            'report_id': copied_report.id,
            'parent_id': parent and parent.id,
            'code': self._get_copied_code(),
        })

        # Keep track of old_code -> new_code in a mutable dict

View on GitHub (pinned to 1e661df964)

Solutions

  1. Set `parent_id` to a *different* line, or to `False` for a root line
  2. In copy code, skip the self-reference: map `parent_id` only when `new_parent != copied_line`
  3. Validate imports up front: reject rows whose parent code equals their own code

Example fix

# before
new_line = line.copy({'parent_id': new_line_id_map[line.id]})  # map may contain line.id -> its own new id

# after
new_line = line.copy({'parent_id': new_line_id_map.get(line.parent_id.id, False)})
Defensive patterns

Strategy: validation

Validate before calling

if vals.get('parent_id') and vals['parent_id'] == line.id:
    vals['parent_id'] = False  # or raise
line.write(vals)

Try / catch

from odoo.exceptions import ValidationError
try:
    line.write({'parent_id': parent_id})
except ValidationError:
    line.write({'parent_id': False})

Prevention

When it happens

Trigger: `create()`/`write()` with `parent_id` set to the same record as the line itself — e.g. after copying a line and remapping parent ids with a buggy mapping dict that maps the line to itself, or assigning `parent_id` from a variable that accidentally holds the line.

Common situations: Copy/duplicate routines that remap `parent_id` via an old-id-to-new-id dict and hit a self-reference when the mapping is built incorrectly; import scripts that resolve parent codes to the same line; drag-and-drop re-parenting bugs in custom UIs.

Related errors


AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15). Data as JSON: /api/errors/bbb9336887c3bbac. Report an issue: GitHub.