Skip to content

Odoo Access Rights Debugging: Systematic Troubleshooting Guide

DeployMonkey Team · March 23, 2026 12 min read

The Access Rights Stack

Odoo security has four layers. When a user gets an AccessError, the problem is in one of these layers:

  1. ir.model.access (Model-level ACL): Can this group read/write/create/unlink this model?
  2. ir.rule (Record rules): Can this user access this specific record?
  3. Field-level access: groups attribute on field definitions
  4. Menu/Action access: Can the user see the menu item or action?

Layer 1: Model Access (ir.model.access.csv)

Model access controls CRUD permissions per security group:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_order_salesman,sale.order.salesman,model_sale_order,sales_team.group_sale_salesman,1,1,1,0
access_sale_order_manager,sale.order.manager,model_sale_order,sales_team.group_sale_manager,1,1,1,1

Debugging model access:

# Check what access a user has on a model
def check_model_access(self, model_name, user_id):
    user = self.env['res.users'].browse(user_id)
    groups = user.groups_id
    accesses = self.env['ir.model.access'].search([
        ('model_id.model', '=', model_name),
        '|',
        ('group_id', 'in', groups.ids),
        ('group_id', '=', False),  # global access (no group)
    ])
    for acc in accesses:
        print(f'{acc.name}: R={acc.perm_read} W={acc.perm_write} '
              f'C={acc.perm_create} D={acc.perm_unlink} '
              f'Group={acc.group_id.name or "Global"}')

Layer 2: Record Rules (ir.rule)

Record rules filter which records a user can access. They add domain conditions to every query:

<record id="sale_order_own_rule" model="ir.rule">
    <field name="name">Own Sale Orders</field>
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
    <field name="perm_read">1</field>
    <field name="perm_write">1</field>
    <field name="perm_create">1</field>
    <field name="perm_unlink">1</field>
</record>

Key record rule behaviors:

  • Rules with a group: ORed within the same group, ANDed across different groups
  • Global rules (no group): always ANDed
  • sudo() bypasses record rules

Debugging Record Rules

# Find all rules for a model
rules = self.env['ir.rule'].search([
    ('model_id.model', '=', 'sale.order')
])
for rule in rules:
    groups = rule.groups.mapped('name') or ['Global']
    print(f'{rule.name}: {rule.domain_force} -> {groups}')

# Test a specific rule for a user
user = self.env['res.users'].browse(user_id)
try:
    record = self.env['sale.order'].with_user(user).browse(record_id)
    record.check_access_rights('read')
    record.check_access_rule('read')
    print('Access granted')
except AccessError as e:
    print(f'Access denied: {e}')

Layer 3: Field-Level Access

Fields can restrict access to specific groups:

cost_price = fields.Float(groups='account.group_account_manager')
secret_notes = fields.Text(groups='base.group_system')

If a user does not belong to the specified group, the field is invisible and inaccessible via API. Check field groups:

# List restricted fields on a model
for name, field in self.env['sale.order']._fields.items():
    if field.groups:
        print(f'{name}: groups={field.groups}')

Systematic Debugging Workflow

When a user reports an AccessError, follow this sequence:

Step 1: Read the Error Message

Odoo's AccessError messages tell you exactly which operation was blocked:

# Model access error
"You are not allowed to access 'Sale Order' (sale.order) records."
"Allowed operations: Read"

# Record rule error
"Due to security restrictions, you are not allowed to access 'Sale Order' (sale.order) records."
"Records: SO001 (id=42)"

Model access errors mention the model name. Record rule errors mention specific record IDs.

Step 2: Check User Groups

user = self.env['res.users'].browse(user_id)
print('Groups:', user.groups_id.mapped('full_name'))

Step 3: Check Model Access

# Via shell
self.env['ir.model.access'].check('sale.order', 'write', raise_exception=False)
# Returns True/False

Step 4: Check Record Rules

try:
    record.with_user(user).check_access_rule('write')
except AccessError:
    # Check which rules apply
    rules = self.env['ir.rule']._compute_domain('sale.order', 'write')
    print('Computed domain:', rules)

Step 5: Test with sudo()

# If sudo() works, the issue is access rights (not a logic bug)
try:
    record.with_user(user).write({'state': 'done'})  # fails?
    record.sudo().write({'state': 'done'})  # works?
    print('Issue is access rights, not logic')
except Exception as e:
    print(f'Other error: {e}')

Common AccessError Patterns

Error PatternLikely CauseFix
Cannot read modelMissing ir.model.access for user groupAdd CSV access line
Can read but not writeperm_write=0 in access CSVSet perm_write=1
Can access some recordsRecord rule domain too restrictiveExpand domain or add group exception
Lost access after module installNew record rule added by moduleCheck new rules from installed module
Error only in productionDemo user has admin rightsTest with a non-admin user

Logging Access Checks

# Enable security logging
import logging
logging.getLogger('odoo.addons.base.models.ir_rule').setLevel(logging.DEBUG)
logging.getLogger('odoo.addons.base.models.ir_model_access').setLevel(logging.DEBUG)

Best Practices

  • Always test permissions with a non-admin user — admin bypasses most checks
  • Use check_access_rights() and check_access_rule() in your code when you need to verify access before operations
  • Document your security model — list all groups, access rules, and record rules
  • Avoid global record rules unless absolutely necessary — they affect all users
  • Use sudo() sparingly and only when the business logic requires cross-user access
  • Test all CRUD operations, not just read — write and unlink are commonly missed