Skip to content

Debugging & Troubleshooting

Last Updated: February 2026

Reading Odoo Logs

# Live log tail
sudo tail -f /var/log/odoo17/odoo17.log

# Last 5 minutes of logs
sudo journalctl -u odoo17.service --since "5 minutes ago" --no-pager | tail -100

# Search for errors
sudo grep -i "error\|traceback" /var/log/odoo17/odoo17.log | tail -20

Running Odoo in Debug Mode

Add ?debug=1 to the URL in your browser to enable Odoo's debug mode. This shows:

  • Technical field names on hover
  • Developer menu in the top bar
  • View metadata and XML IDs

For asset debug (unminified JS/CSS), use ?debug=assets.

Common Log Patterns

Log Pattern What It Means
AccessError User doesn't have permission — check ir.model.access.csv and record rules
ValidationError A Python constraint failed — check @api.constrains methods
KeyError: 'field_name' A field referenced in XML/Python doesn't exist on the model
psycopg2.errors.UndefinedColumn Database column missing — run -u module_name to update
ValueError: External ID not found An XML ID reference is broken — check ref() calls in data files
FileNotFoundError in assets JS/CSS file path in manifest doesn't match actual file location

Odoo Shell for Quick Testing

sudo -u odoo17 /opt/odoo17/odoo17-venv/bin/python3 \
  /opt/odoo17/odoo17/odoo-bin shell -c /etc/odoo17.conf -d test1

Useful shell commands:

# Find a record
partner = env['res.partner'].search([('name', 'ilike', 'scott')], limit=1)
print(partner.name, partner.email)

# Check a model's fields
fields = env['sr.inventory.item']._fields
for name, field in fields.items():
    print(f"{name}: {field.type}")

# Test a method
record = env['sr.crm.lead'].browse(42)
record.action_schedule_pickup()

# Commit changes (shell doesn't auto-commit)
env.cr.commit()

Security

Access Control (ir.model.access.csv)

Every model needs an access control entry. Format:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sr_model_user,sr.model.user,model_sr_model,base.group_user,1,1,1,0
access_sr_model_manager,sr.model.manager,model_sr_model,sr_module.group_manager,1,1,1,1
  • group_id:id — leave blank for public access (rarely appropriate)
  • base.group_user — all internal users
  • Create custom groups in your module for role-based access

Record Rules

Use record rules to restrict which records users can see:

<record id="sr_model_own_records_rule" model="ir.rule">
    <field name="name">Own Records Only</field>
    <field name="model_id" ref="model_sr_model"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>

Security Groups

Define groups in security/groups.xml:

<record id="group_sr_manager" model="res.groups">
    <field name="name">SR Manager</field>
    <field name="category_id" ref="base.module_category_operations"/>
    <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>

OWL Components (Odoo 17)

Odoo 17 uses the OWL framework for frontend components. Custom modules use OWL for dashboards, widgets, and interactive UI elements.

Basic Component Structure

/** @odoo-module */
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";

export class MyWidget extends Component {
    static template = "sr_module.MyWidget";

    setup() {
        this.orm = useService("orm");
        this.action = useService("action");
    }

    async onButtonClick() {
        const result = await this.orm.call(
            "sr.model", "my_method", [this.props.recordId]
        );
    }
}

// Register as an action
registry.category("actions").add("sr_module.my_widget", MyWidget);

Component Template (XML)

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="sr_module.MyWidget">
        <div class="o_my_widget">
            <button t-on-click="onButtonClick" class="btn btn-primary">
                Click Me
            </button>
        </div>
    </t>
</templates>

Registering Assets

In __manifest__.py:

"assets": {
    "web.assets_backend": [
        "sr_module/static/src/components/**/*.js",
        "sr_module/static/src/components/**/*.xml",
        "sr_module/static/src/components/**/*.scss",
    ],
},

If assets don't load after install, see the JS Asset Registration workaround in Common Patterns.


Common Patterns

XML-RPC Testing

import xmlrpc.client

url = "http://localhost:1818"
db = "test1"
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
uid = common.authenticate(db, "admin", "admin", {})
models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")

# Read
records = models.execute_kw(db, uid, "admin", 'res.partner', 'search_read',
    [[['name', 'like', 'Scott']]],
    {'fields': ['name', 'phone'], 'limit': 5})

# Write (Odoo 17)
models.execute_kw(db, uid, "admin", 'res.partner', 'write',
    [[record_id], {'phone': '+18659733067'}])

JS Asset Registration (Odoo 17 Workaround)

If a module's JS doesn't load after installation, manually register assets:

env['ir.asset'].create({
    'name': 'module: description',
    'bundle': 'web.assets_backend',
    'path': 'module_name/static/src/components/file.js',
})

Then clear asset cache: delete ir.attachment records where url LIKE '/web/assets/%' and restart Odoo.

Scheduled Actions (Cron Jobs)

<record id="sr_module_cron_daily" model="ir.cron">
    <field name="name">SR Module: Daily Task</field>
    <field name="model_id" ref="model_sr_model"/>
    <field name="state">code</field>
    <field name="code">model.cron_daily_task()</field>
    <field name="interval_number">1</field>
    <field name="interval_type">days</field>
    <field name="numbercall">-1</field>
</record>

Computed Fields with Store

total_weight = fields.Float(
    string="Total Weight",
    compute="_compute_total_weight",
    store=True,
)

@api.depends('line_ids.weight')
def _compute_total_weight(self):
    for record in self:
        record.total_weight = sum(record.line_ids.mapped('weight'))

Override Create/Write

@api.model_create_multi
def create(self, vals_list):
    for vals in vals_list:
        if not vals.get('reference'):
            vals['reference'] = self.env['ir.sequence'].next_by_code('sr.model')
    return super().create(vals_list)

def write(self, vals):
    if 'status' in vals and vals['status'] == 'done':
        self._on_complete()
    return super().write(vals)