Techbot

Contact

Author

Odoo Developer Guide: Data Security & Access Controls in Odoo

Odoo Developer Guide: Data Security & Access Controls in Odoo Blogs September 14, 2026 Moossa M. Alavi Technical Analyst at Techbot ERP Moossa M. Alavi is the Founder & CEO of Techbot ERP and Altamyz Advertising. He is a certified Odoo consultant with more than 27 years of experience in business, advertising, and ERP software. Moossa started his career in the UAE in 1997 with a well-known group in Abu Dhabi. Over the years, he built his own companies to help other businesses work better using technology. Moossa helps with customized ERP implementation for various industries, including manufacturing, insurance, supercar rental, and logistics, through Techbot ERP. He resolves these issues with Odoo ERP and supports businesses in growing with the right assets and guidance. Moossa has received many awards for his work, including the Arabian Best of Best Award and the Industry Leader Award from BNI UAE. He is also a BNI Ambassador and mentors other business owners. He believes in giving back to the community and helping others grow, following the “Givers Gain” principle. About This Guide This guide breaks down how Odoo protects your data and controls user permissions across your business. It explains how roles, permissions, and security rules work together to ensure employees only see and manage the specific records they need—keeping your business information safe and organized. What You Will Learn User Roles & Access Control: How to set up job roles and assign specific read, edit, or delete permissions across your business modules. Row-Level Security: How to automatically restrict record visibility so users only view data relevant to their role or branch. Field Privacy: How to hide sensitive information (like private medical notes or financial data) from unauthorized team members. Best Practices: How to avoid common configuration mistakes and keep your overall system safe from unintended access. Odoo provides two main data-driven mechanisms to manage or restrict access to data without writing custom hardcoded logic. Both mechanisms link to users through User Groups (res.groups): a user belongs to any number of groups, and security mechanisms are attached to groups to govern permissions. This guide provides a comprehensive breakdown of Odoo security architecture using a Hospital Management (custom_hospital_management) module as an example. Group Architecture (res.groups) User groups define roles and serve as the foundation for both Access Rights and Record Rules. Model Attributes (res.groups) tname: Serves as user-readable identification for the group (spells out the role or purpose of the group, e.g., “Doctor”, “Patient Administrator”). category_id: The module category. Associates groups with an Odoo App (a set of related business models) and converts them into an exclusive selection box on the User setup form. implied_ids: Other groups to assign to the user alongside this one. This acts as a convenience pseudo-inheritance relationship (e.g., a “Hospital Manager” group implies the “Hospital User” group). It remains possible to explicitly remove implied groups from a user without removing the main group. comment: Additional technical notes or descriptions detailing the purpose of the group. Access Rights (ir.model.access) Access Control Lists (ACLs) grant access to an entire model for a given set of operations (Create, Read, Update, Delete). If no access right matches an operation on a model for a user (through their assigned groups), the user is denied access. Core Properties Additive Nature: Access rights are cumulative. A user’s total access is the union of access rights from all groups they belong to.Example: If Group A grants Read and Create, and Group B grants Update, a user in both groups has Read, Create, and Update access. Default Behavior: Unmatched operations default to denied access. Model Attributes (ir.model.access) name: Descriptive purpose or role of the access control record. model_id: The target model whose access the ACL controls (formatted as model_<model_name_with_underscores>). group_id: The res.groups record to which access is granted. Leaving group_id empty grants access to every user (including non-employees such as portal or public users). CRUD Attributes (perm_*): Grant the corresponding operation when set to 1 (True). All permissions are unset (0 / False) by default: perm_read: Read/View records. perm_create: Create new records. perm_write: Modify existing records. perm_unlink: Delete records. Implementation Example: Hospital Management File location: custom_hospital_management/security/ir.model.access.csv Code snippet id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_hospital_patient_user,access.hospital.patient.user,model_hospital_patient,base.group_user,1,1,1,0 In this configuration, standard internal users (base.group_user) can Read, Write, and Create patient records (hospital.patient), but cannot Delete (perm_unlink = 0) them. Record Rules (ir.rule) Record rules are row-level conditions evaluated record-by-record after Access Rights pass. While Access Rights grant access to the table, Record Rules filter which individual rows inside that table a user can see or modify. Core Properties Default-Allow: If Access Rights grant permission and no record rule applies to the operation/model for the user, access is allowed. Operation Selection: Unlike ir.model.access, setting perm_* flags on an ir.rule determines which operations the rule actively filters. If an operation is unset (False), the rule ignores that operation and permits it freely. All operations are selected (True) by default. Model Attributes (ir.rule) name: Description of the rule. model_id: The model to which the rule applies. groups: The res.groups to which the rule applies. If no group is specified, the rule is marked as Global. global: Computed field indicating whether the rule applies globally across all users. domain_force: A domain predicate (Python expression). Records matching the domain are allowed; non-matching records are forbidden. CRUD Operation Flags: perm_read, perm_write, perm_create, perm_unlink. Available Domain Evaluation Context When writing expressions inside domain_force, Odoo provides the following evaluation variables: user: The current user record (as a singleton recordset). company_id: The user’s currently active company ID (integer). company_ids: All company IDs accessible to the user (list of integers). time: Python’s native time module. Global Rules vs. Group Rules   Rule Type Composition Behavior Technical Rule Global Rules (groups empty) Intersection (AND) Adding global rules always restricts access further. All global rules must be satisfied simultaneously. Group Rules (groups specified) Unification (OR) Adding group rules expands access. If any group rule matches, access is granted. Combined Evaluation Intersection (AND) Global rulesets and Group rulesets intersect. The user must pass ALL Global rules AND at

Odoo Custom Module Development: Step-by-Step Tutorial

Odoo Custom Module Development: Step-by-Step Tutorial (2026) Blogs September 8, 2026 Shaan Jose Technical Analyst at Techbot ERP About This Guide This guide provides a beginner-friendly, step-by-step introduction to building custom applications in Odoo from scratch. Using a hospital management system as a practical example, it walks you through structuring your app, storing custom data, creating user-friendly screens, and making updates safely—all structured so anyone can follow along and build custom features easily. What You Will Learn App Organization: How to set up clean folder structures and manage how your app files load together. Data & Screens: How to create custom fields to store information, build clean lists and detailed views, and manage record statuses. Security & Access: How to control who can view, edit, or create records within your app. App Updates: How to properly install, update, and apply changes to your custom Odoo apps. Prerequisites: Finding Your Odoo Server & Custom Addons Directory Before writing code, you need to know where Odoo lives on your computer and where to place your custom code so Odoo can discover it. Step 1: Locate the custom-addons Directory in File Explorer If you are running Odoo locally on Windows: Open File Explorer by pressing Ctrl + E. Navigate to your installation path (typically C:Program FilesOdoo 19.0serverodooaddons or custom-addons). You can paste your folder path directly into the File Explorer address bar. Crucial Rule on Folder Naming: Pick one clean, lowercase name for your custom module folder using underscores instead of spaces or hyphens (for example: custom_hospital_management or real_estate). Throughout this guide, we will strictly use custom_hospital_management across all code files, paths, and configurations. Step 2: Create the Module Directory Structure Inside your custom-addons directory, create a main folder named custom_hospital_management. Inside that main folder, create three sub-folders: models/ — Holds all Python files that construct database tables and business logic. views/ — Holds all XML files that render screens, forms, tables, and top navigation menus. security/ — Holds CSV files that grant users access permissions to your database tables. Chapter 1: The Odoo Module Architecture Every Odoo module is a self-contained directory containing Python code, XML layouts, and security rules. Complete Directory Layout When viewed inside Visual Studio Code or File Explorer, your module structure must look like this: Plaintext custom_hospital_management/ │ ├── __init__.py                  <– Root Python file initializing the module package ├── __manifest__.py              <– Module configuration, metadata, and loading sequence │ ├── models/                      <– Python database layer │   ├── __init__.py              <– Registers all model Python files │   └── patient.py               <– Patient database schema & logic │ ├── views/                       <– XML user interface layer │   ├── patient_views.xml        <– Form and list (tree) view definitions │   └── menu_views.xml           <– Top menu navigation links │ └── security/                    <– Access control security layer     └── ir.model.access.csv      <– Security rights (Read, Write, Create, Delete) Chapter 2: Module Registration (__manifest__.py) The __manifest__.py file tells Odoo what your app does, what base Odoo apps it depends on, and which data files to load into PostgreSQL. When viewed inside Visual Studio Code or File Explorer, your module structure must look like this: Manifest Configuration Code File location: custom_hospital_management/__manifest__.py Python {     ‘name’: ‘Hospital Management’,     ‘version’: ‘1.0.0’,     ‘summary’: ‘Manage patient records, appointments, and medical histories’,     ‘category’: ‘Healthcare’,     ‘author’: ‘Your Company Name’,     ‘license’: ‘LGPL-3’,          # Dependencies: List official Odoo apps required before this module can be installed     ‘depends’: [‘base’],           # Data Files: Must be listed in STRICT sequential dependency order     ‘data’: [         ‘security/ir.model.access.csv’,  # 1. Security MUST load first         ‘views/patient_views.xml’,       # 2. Views and window actions MUST load second         ‘views/menu_views.xml’,          # 3. Menus referencing actions MUST load last     ],          ‘installable’: True,     ‘application’: True,  # Setting to True places this app on the main Odoo dashboard }  Core Concept: Why Manifest Order Matters Odoo executes files listed inside ‘data’: […] strictly from top to bottom: If views/menu_views.xml is placed above views/patient_views.xml, Odoo will crash with an External ID not found error because the menu will try to link to an action that hasn’t been created yet. Security files (ir.model.access.csv) must always sit at the top so permissions are active before screens are drawn. Chapter 3: Creating Database Tables (Models & ORM Fields) In Odoo, you do not write raw SQL statements (like CREATE TABLE). Instead, you define Python classes inheriting from models.Model. Odoo’s Object-Relational Mapping (ORM) creates and updates the underlying PostgreSQL database automatically. Step 1: Register the Models Directory File location: custom_hospital_management/__init__.py Python from . import models File location: custom_hospital_management/models/__init__.py Python from . import patient Step 2: Define the Model & Data Fields File location: custom_hospital_management/models/patient.py Python from odoo import models, fields, api   class HospitalPatient(models.Model):     # Technical database table identifier created in PostgreSQL: “hospital_patient”     _name = ‘hospital.patient’     _description = ‘Hospital Patient Record’       # Standard Database Fields     name = fields.Char(string=’Full Name’, required=True)     age = fields.Integer(string=’Age’)     gender = fields.Selection([         (‘male’, ‘Male’),         (‘female’, ‘Female’),         (‘other’, ‘Other’),     ], string=’Gender’, default=’male’)          note = fields.Text(string=’Medical History’)     active = fields.Boolean(string=’Active’, default=True)       # State tracking field for record lifecycles     state = fields.Selection([         (‘draft’, ‘Draft’),         (‘confirmed’, ‘Confirmed’),         (‘done’, ‘Done’),         (‘cancel’, ‘Cancelled’),     ], string=’Status’, default=’draft’, required=True)       # Computed Field: Calculates values dynamically on the fly     is_minor = fields.Boolean(string=’Is Minor’, compute=’_compute_is_minor’)       @api.depends(‘age’)     def _compute_is_minor(self):         # ALWAYS loop over ‘self’ because Odoo handles records in batches (recordsets)         for record in self:             if record.age and record.age < 18:                 record.is_minor = True             else:                 record.is_minor = False       # Workflow Action Methods (Connected to UI Buttons)     def action_confirm(self):         for record in self:             record.state = ‘confirmed’       def action_done(self):         for record in self:             record.state = ‘done’       def action_cancel(self):         for record