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)