{"id":9677,"date":"2026-10-06T16:08:02","date_gmt":"2026-10-06T13:08:02","guid":{"rendered":"https:\/\/machinser.com\/?p=9677"},"modified":"2026-10-06T16:09:09","modified_gmt":"2026-10-06T13:09:09","slug":"odoo-19-custom-module-components","status":"publish","type":"post","link":"https:\/\/machinser.com\/ar\/odoo-19-custom-module-components\/","title":{"rendered":"Odoo 19 Custom Module Components: A Powerful Guide to Building Better Modules"},"content":{"rendered":"<p>Once the basic structure of an Odoo 19 custom module is understood, the next step is to understand what each component actually does. A custom module is not simply a collection of Python and XML files. Different components are responsible for different parts of the application.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Models define data and business logic.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Views define how users interact with that data.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Security controls who can access it.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Data files provide predefined records and configuration.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Actions and menus connect the functionality to the Odoo interface.<\/p>\n<p class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Controllers, wizards, reports, and frontend assets can be added when the module requires more advanced functionality.<\/p>\n<p>This separation allows an Odoo 19 custom module to remain organized and maintainable as its functionality grows.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">1. Models: The Core of the Business Logic<\/h2>\n<p>The models directory contains the <a href=\"https:\/\/docs.python.org\" target=\"_blank\" rel=\"noopener\">Python<\/a> code responsible for defining or extending Odoo models. Models can contain fields, business logic, computed values, constraints, onchange methods, automated operations, model inheritance, and relationships between records.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">from odoo import models, fields\r\n\r\nclass EmployeeDetails(models.Model):\r\n    _name = \"employee.details\"\r\n    _description = \"Employee Details\"\r\n\r\n    name = fields.Char(string=\"Employee Name\")\r\n    email = fields.Char(string=\"Email\")\r\n    phone = fields.Char(string=\"Phone\")\r\n    active = fields.Boolean(default=True)<\/pre>\n<p>The _name attribute defines the technical name of the model. The fields define the information that the model stores. Methods inside the model contain the business rules that determine how the application behaves.<\/p>\n<figure id=\"attachment_9683\" aria-describedby=\"caption-attachment-9683\" style=\"width: 960px\" class=\"wp-caption aligncenter\"><img fetchpriority=\"high\" decoding=\"async\" class=\"wp-image-9683\" src=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-300x173.jpeg\" alt=\"Odoo 19 Custom Module Components\" width=\"960\" height=\"555\" srcset=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-300x173.jpeg 300w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-1024x592.jpeg 1024w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-768x444.jpeg 768w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-1536x888.jpeg 1536w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1-18x10.jpeg 18w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/1-1.jpeg 1600w\" sizes=\"(max-width: 960px) 100vw, 960px\" \/><figcaption id=\"caption-attachment-9683\" class=\"wp-caption-text\">Figure 1: Python model defining fields and business logic in the Employee Management custom module.<\/figcaption><\/figure>\n<div><img \/><\/div>\n<h2 class=\"codex-docx-preview_heading1\">2. Common Field Types<\/h2>\n<p>Odoo provides many field types for representing different kinds of business data.<\/p>\n<table class=\"first-row first-col no-vband codex-docx-preview_tablegrid\">\n<colgroup>\n<col \/>\n<col \/><\/colgroup>\n<tbody>\n<tr>\n<td>Field<\/td>\n<td>Typical use<\/td>\n<\/tr>\n<tr>\n<td>Char<\/td>\n<td>Short text<\/td>\n<\/tr>\n<tr>\n<td>Text<\/td>\n<td>Long text<\/td>\n<\/tr>\n<tr>\n<td>Integer<\/td>\n<td>Whole numbers<\/td>\n<\/tr>\n<tr>\n<td>Float<\/td>\n<td>Decimal values<\/td>\n<\/tr>\n<tr>\n<td>Boolean<\/td>\n<td>True\/False values<\/td>\n<\/tr>\n<tr>\n<td>Date<\/td>\n<td>Dates<\/td>\n<\/tr>\n<tr>\n<td>Datetime<\/td>\n<td>Date and time<\/td>\n<\/tr>\n<tr>\n<td>Selection<\/td>\n<td>Fixed list of choices<\/td>\n<\/tr>\n<tr>\n<td>Many2one<\/td>\n<td>Link to one record<\/td>\n<\/tr>\n<tr>\n<td>One2many<\/td>\n<td>Multiple related records<\/td>\n<\/tr>\n<tr>\n<td>Many2many<\/td>\n<td>Multiple records on both sides<\/td>\n<\/tr>\n<tr>\n<td>Monetary<\/td>\n<td>Currency values<\/td>\n<\/tr>\n<tr>\n<td>Binary<\/td>\n<td>Files or binary data<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre class=\"codex-docx-preview_codeblock\">department_id = fields.Many2one(\r\n    \"hr.department\",\r\n    string=\"Department\"\r\n)\r\n\r\njoining_date = fields.Date(\r\n    string=\"Joining Date\"\r\n)\r\n\r\nsalary = fields.Float(\r\n    string=\"Salary\"\r\n)<\/pre>\n<p>Choosing the appropriate field type is important because it affects how the data is stored, displayed, searched, and related to other records.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">3. Model Relationships<\/h2>\n<p>Real business applications rarely consist of completely independent records. Odoo provides relational fields to connect models.<\/p>\n<h4 class=\"codex-docx-preview_heading2\">Many2one<\/h4>\n<p>A record belongs to one related record.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">department_id = fields.Many2one(\r\n    \"hr.department\"\r\n)<\/pre>\n<p>For example, many employees can belong to the same department.<\/p>\n<h4 class=\"codex-docx-preview_heading2\">One2many<\/h4>\n<p>A One2many field represents multiple related records from the other side of a relationship.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">employee_ids = fields.One2many(\r\n    \"employee.details\",\r\n    \"department_id\"\r\n)<\/pre>\n<h4 class=\"codex-docx-preview_heading2\">Many2many<\/h4>\n<p>A Many2many field allows records to be related to multiple records on both sides of a relationship.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">sskill_ids = fields.Many2many(\r\n    \"employee.skill\"\r\n)<\/pre>\n<p>These relationships are fundamental when designing custom business applications in Odoo.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">4. Business Logic in Models<\/h2>\n<p>Models are not only used to define fields. They can also contain methods that implement business rules.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">def action_confirm(self):\r\n    for record in self:\r\n        record.state = \"confirmed\"<\/pre>\n<p>A method can be triggered by buttons, automated processes, scheduled actions, other model methods, controllers, and user interactions. This is where much of an application&#8217;s business behavior is implemented.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">5. Model Inheritance<\/h2>\n<p>One of Odoo&#8217;s most useful development features is model inheritance. Instead of creating a completely new model, a custom module can extend an existing Odoo model.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">from odoo import models, fields\r\n\r\nclass SaleOrder(models.Model):\r\n    _inherit = \"sale.order\"\r\n\r\n    customer_reference = fields.Char(\r\n        string=\"Customer Reference\"\r\n    )<\/pre>\n<p>This adds a new field to the existing Sales Order model. The original Odoo model does not need to be copied or rewritten. This approach is useful when implementing customer-specific requirements.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">6. Views: Connecting Data to the User Interface<\/h2>\n<p>Models define the data, but users need an interface to work with that data. This is the role of views. Odoo views are generally defined using XML.<\/p>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Form<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">List<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Kanban<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Search<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Calendar<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Graph<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Pivot<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Activity<\/h6>\n<p>The view determines how the fields are presented to the user.<\/p>\n<figure id=\"attachment_9684\" aria-describedby=\"caption-attachment-9684\" style=\"width: 960px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" class=\"wp-image-9684\" src=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-300x155.jpeg\" alt=\"\" width=\"960\" height=\"496\" srcset=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-300x155.jpeg 300w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-1024x529.jpeg 1024w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-768x396.jpeg 768w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-1536x793.jpeg 1536w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1-18x9.jpeg 18w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.1.jpeg 1600w\" sizes=\"(max-width: 960px) 100vw, 960px\" \/><figcaption id=\"caption-attachment-9684\" class=\"wp-caption-text\">Figure 2: Form view of the Employee Management custom module, showing how model fields are presented to users.<\/figcaption><\/figure>\n<div><img \/><\/div>\n<p>&nbsp;<\/p>\n<h2 class=\"codex-docx-preview_heading1\">7. Form Views<\/h2>\n<p>A form view is used to create and edit individual records. Typical examples include customer forms, employee forms, sales orders, purchase orders, and products.<\/p>\n<p>&lt;sheet&gt;<br \/>\n&lt;group&gt;<br \/>\n&lt;field name=&#8221;name&#8221;\/&gt;<br \/>\n&lt;field name=&#8221;email&#8221;\/&gt;<br \/>\n&lt;\/group&gt;<br \/>\n&lt;\/sheet&gt;<\/p>\n<p>A form can organize fields using group, sheet, notebook, page, and header elements.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">8. List Views<\/h2>\n<p>List views display multiple records at the same time. They are useful for searching records, sorting records, selecting records, and performing bulk operations.<\/p>\n<p>&lt;list&gt;<br \/>\n&lt;field name=&#8221;name&#8221;\/&gt;<br \/>\n&lt;field name=&#8221;email&#8221;\/&gt;<br \/>\n&lt;field name=&#8221;phone&#8221;\/&gt;<br \/>\n&lt;\/list&gt;<\/p>\n<div>\n<p><img \/><\/p>\n<figure id=\"attachment_9685\" aria-describedby=\"caption-attachment-9685\" style=\"width: 960px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" class=\"wp-image-9685\" src=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-300x162.jpeg\" alt=\"\" width=\"960\" height=\"519\" srcset=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-300x162.jpeg 300w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-1024x554.jpeg 1024w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-768x415.jpeg 768w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-1536x830.jpeg 1536w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4-18x10.jpeg 18w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c4.jpeg 1600w\" sizes=\"(max-width: 960px) 100vw, 960px\" \/><figcaption id=\"caption-attachment-9685\" class=\"wp-caption-text\">Figure 3: List view displaying employee records and the fields configured for the custom model.<\/figcaption><\/figure>\n<\/div>\n<p>&nbsp;<\/p>\n<h2 class=\"codex-docx-preview_heading1\">9. Search Views<\/h2>\n<p>Search views control the available search fields, filters, and grouping options.<\/p>\n<pre>&lt;search&gt;\r\n\u00a0\u00a0\u00a0 &lt;field name=\"name\"\/&gt;\r\n\r\n\u00a0\u00a0\u00a0 &lt;filter\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 name=\"active\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string=\"Active\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 domain=\"[('active', '=', True)]\"\r\n\u00a0\u00a0\u00a0 \/&gt;\r\n&lt;\/search&gt;<\/pre>\n<p>Search views can provide search fields, filters, group-by options, and predefined search conditions. For modules containing many records, a good search view can significantly improve usability.<\/p>\n<div>\n<figure id=\"attachment_9686\" aria-describedby=\"caption-attachment-9686\" style=\"width: 960px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-9686\" src=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-300x173.jpeg\" alt=\"\" width=\"960\" height=\"555\" srcset=\"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-300x173.jpeg 300w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-1024x592.jpeg 1024w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-768x444.jpeg 768w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-1536x888.jpeg 1536w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3-18x10.jpeg 18w, https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/c3.jpeg 1600w\" sizes=\"(max-width: 960px) 100vw, 960px\" \/><figcaption id=\"caption-attachment-9686\" class=\"wp-caption-text\">Figure 4: Search view XML showing searchable fields, a Draft filter, and a group-by option.<\/figcaption><\/figure>\n<p><img \/><\/p>\n<\/div>\n<h2 class=\"codex-docx-preview_heading1\">10. Actions and Menus<\/h2>\n<p>Views alone do not determine how users navigate to a feature. An action connects a model with a user interface.<\/p>\n<pre>&lt;record id=\"employee_details_action\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 model=\"ir.actions.act_window\"&gt;\r\n\r\n\u00a0\u00a0\u00a0 &lt;field name=\"name\"&gt;Employees&lt;\/field&gt;\r\n\r\n\u00a0\u00a0\u00a0 &lt;field name=\"res_model\"&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 employee.details\r\n\u00a0\u00a0\u00a0 &lt;\/field&gt;\r\n\r\n\u00a0\u00a0\u00a0 &lt;field name=\"view_mode\"&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 list,form\r\n\u00a0\u00a0\u00a0 &lt;\/field&gt;\r\n\r\n&lt;\/record&gt;<\/pre>\n<p>A menu can then trigger the action:<\/p>\n<pre>&lt;menuitem\r\n\u00a0\u00a0\u00a0 id=\"employee_menu\"\r\n\u00a0\u00a0\u00a0 name=\"Employees\"\r\n\u00a0\u00a0\u00a0 action=\"employee_details_action\"\r\n\/&gt;<\/pre>\n<p>The basic relationship is Menu to Action to View to Model.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">11. Security and Access Rights<\/h2>\n<p>Security is an important part of every business application. Odoo uses access control mechanisms to determine what users can do with a model.<\/p>\n<p>A common module file is:<\/p>\n<p class=\"codex-docx-preview_codeblock\">security\/<br \/>\n\u2514\u2500\u2500 ir.model.access.csv<\/p>\n<p>Access records can control Read, Write, Create, and Delete permissions.<\/p>\n<pre class=\"CodeBlock\" style=\"margin: 0cm 0cm 7.0pt 18.0pt;\"><span lang=\"EN-US\">id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r\naccess_employee,employee access,model_employee_details,base.group_user,1,1,1,1<\/span><\/pre>\n<p>The four permission columns determine whether users in the specified group can perform each operation.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">12. Groups and More Granular Security<\/h2>\n<p>Access control can be assigned to specific user groups. For example, an application might have Employee User, Employee Manager, HR Manager, and Administrator groups.<\/p>\n<p>Different groups can receive different permissions. For more advanced requirements, Odoo also provides record rules that can restrict which records a user can access.<\/p>\n<p>This creates an important distinction:<\/p>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Access rights: What operations can the user perform?<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Record rules: Which records can the user perform those operations on?<\/h6>\n<p>For example, a salesperson might be allowed to read customers but only access customers belonging to their assigned sales team.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">13. Data Files<\/h2>\n<p>Custom modules often need predefined records. These can be loaded through XML or CSV files.<\/p>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Sequences<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Configuration records<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Default settings<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Email templates<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Scheduled actions<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Predefined groups<\/h6>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Initial records<\/h6>\n<p>Data files are especially useful when a module requires certain records to exist immediately after installation.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">14. Wizards<\/h2>\n<p>Wizards are used for temporary user interactions. They are commonly implemented using TransientModel.<\/p>\n<p>For example, a wizard could allow a user to select a date range, select a customer, choose a report type, and then generate a report.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">wizard\/\r\n\u251c\u2500\u2500 __init__.py\r\n\u2514\u2500\u2500 employee_report_wizard.py<\/pre>\n<p>Unlike normal business models, wizard records are temporary and are intended for short-lived operations.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">15. Reports<\/h2>\n<p>Custom modules can also contain reports. Odoo supports report generation using its reporting framework and QWeb templates.<\/p>\n<h6 class=\"codex-docx-preview_listbullet codex-docx-preview-num-1-0\">Invoices<\/h6>\n<p>Quotations<\/p>\n<p>Delivery documents<\/p>\n<p>Employee documents<\/p>\n<p>Certificates<\/p>\n<p>Business reports<\/p>\n<p>A report generally combines Odoo data, a report action, and a QWeb template to produce the final document.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">16. Controllers<\/h2>\n<p>Controllers are used when a module needs to handle HTTP requests. They can be useful for website functionality, custom web pages, external integrations, APIs, and receiving external requests.<\/p>\n<p>A custom integration can follow a simple flow from an external system to an HTTP request, then to an Odoo controller, business logic, and an Odoo model.<\/p>\n<p>Controllers should be used carefully, especially when exposing business data through external endpoints.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">17. Static Assets<\/h2>\n<p>A module can also contain frontend resources such as JavaScript, CSS, XML templates, images, and module icons.<\/p>\n<pre class=\"codex-docx-preview_codeblock\">static\/\r\n\u251c\u2500\u2500 src\/\r\n\u2502 \u251c\u2500\u2500 js\/\r\n\u2502 \u251c\u2500\u2500 css\/\r\n\u2502 \u2514\u2500\u2500 xml\/\r\n\u2514\u2500\u2500 description\/<\/pre>\n<p>These resources are useful for modules that modify the Odoo web client or website.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">18. How the Components Work Together<\/h2>\n<p>The components of an Odoo 19 custom module are connected rather than independent.<\/p>\n<p class=\"codex-docx-preview_codeblock\">User<br \/>\n\u2193<br \/>\nMenu<br \/>\n\u2193<br \/>\nAction<br \/>\n\u2193<br \/>\nView<br \/>\n\u2193<br \/>\nModel<br \/>\n\u2193<br \/>\nBusiness Logic<br \/>\n\u2193<br \/>\nDatabase<\/p>\n<p>Security works across this flow by determining what the current user is allowed to do. Reports, automation, controllers, and frontend assets can extend the same basic structure.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">19. Choosing the Right Component<\/h2>\n<table class=\"first-row first-col no-vband codex-docx-preview_tablegrid\">\n<colgroup>\n<col \/>\n<col \/><\/colgroup>\n<tbody>\n<tr>\n<td>Requirement<\/td>\n<td>Usually handled by<\/td>\n<\/tr>\n<tr>\n<td>Store new information<\/td>\n<td>Model + Fields<\/td>\n<\/tr>\n<tr>\n<td>Add a field to an existing Odoo model<\/td>\n<td>Model inheritance<\/td>\n<\/tr>\n<tr>\n<td>Change how information appears<\/td>\n<td>XML View<\/td>\n<\/tr>\n<tr>\n<td>Add a new menu<\/td>\n<td>Menu + Action<\/td>\n<\/tr>\n<tr>\n<td>Restrict user operations<\/td>\n<td>Access Rights<\/td>\n<\/tr>\n<tr>\n<td>Restrict records by conditions<\/td>\n<td>Record Rules<\/td>\n<\/tr>\n<tr>\n<td>Add predefined records<\/td>\n<td>Data files<\/td>\n<\/tr>\n<tr>\n<td>Temporary user input<\/td>\n<td>Wizard<\/td>\n<\/tr>\n<tr>\n<td>Printable document<\/td>\n<td>Report<\/td>\n<\/tr>\n<tr>\n<td>External HTTP endpoint<\/td>\n<td>Controller<\/td>\n<\/tr>\n<tr>\n<td>Change web-client behavior<\/td>\n<td>JavaScript \/ Assets<\/td>\n<\/tr>\n<tr>\n<td>Add custom styling<\/td>\n<td>CSS \/ Assets<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 class=\"codex-docx-preview_heading1\">20. A Good Custom Module Design<\/h2>\n<p>A well-designed module keeps responsibilities separated. For example:<\/p>\n<pre class=\"codex-docx-preview_codeblock\">employee_management\/\r\n\u2502\r\n\u251c\u2500\u2500 models\/\r\n\u2502 \u251c\u2500\u2500 employee.py\r\n\u2502 \u2514\u2500\u2500 department.py\r\n\u2502\r\n\u251c\u2500\u2500 views\/\r\n\u2502 \u251c\u2500\u2500 employee_views.xml\r\n\u2502 \u2514\u2500\u2500 department_views.xml\r\n\u2502\r\n\u251c\u2500\u2500 security\/\r\n\u2502 \u251c\u2500\u2500 ir.model.access.csv\r\n\u2502 \u2514\u2500\u2500 security.xml\r\n\u2502\r\n\u251c\u2500\u2500 data\/\r\n\u2502 \u2514\u2500\u2500 sequence.xml\r\n\u2502\r\n\u251c\u2500\u2500 wizard\/\r\n\u2502 \u2514\u2500\u2500 employee_report.py\r\n\u2502\r\n\u251c\u2500\u2500 report\/\r\n\u2502 \u2514\u2500\u2500 employee_report.xml\r\n\u2502\r\n\u2514\u2500\u2500 static\/\r\n\u2514\u2500\u2500 src\/<\/pre>\n<p>Not every module needs all these directories. They should be added only when the functionality requires them.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">21. Common Development Considerations<\/h2>\n<h6 class=\"codex-docx-preview_heading2\">Keep business logic in Python<\/h6>\n<p>Avoid putting complex business logic directly into views.<\/p>\n<h6 class=\"codex-docx-preview_heading2\">Keep views focused on presentation<\/h6>\n<p>XML should primarily control how information is displayed and interacted with.<\/p>\n<h6 class=\"codex-docx-preview_heading2\">Apply security deliberately<\/h6>\n<p>Do not give every user full read, write, create, and delete access simply because it makes development easier.<\/p>\n<h6 class=\"codex-docx-preview_heading2\">Avoid unnecessary inheritance<\/h6>\n<p>Extend an existing model when the requirement genuinely belongs to that model.<\/p>\n<h6 class=\"codex-docx-preview_heading2\">Keep modules focused<\/h6>\n<p>A module should have a clear business purpose instead of becoming a collection of unrelated customizations.<\/p>\n<h2 class=\"codex-docx-preview_heading1\">Conclusion<\/h2>\n<p>An Odoo 19 custom module is built from several specialized components rather than a single type of code. The most important development concepts are models for data and business logic, fields for data representation, inheritance for extending existing functionality, views for the user interface, actions and menus for navigation, security for access control, data files for predefined records and configuration, wizards for temporary workflows, reports for business documents, and controllers and assets for web and frontend functionality.<\/p>\n<p>Understanding how these components interact is more important than memorizing folder names. Once the responsibility of each component is clear, custom requirements can be mapped to the appropriate Odoo development mechanism.<\/p>\n<p>This gives developers a practical foundation for building clean, maintainable Odoo 19 custom modules without unnecessarily modifying Odoo&#8217;s standard source code.<\/p>\n<p>&nbsp;<\/p>\n<p>To learn more about setting up your module directory and manifest file, read our complete guide on <a class=\"ng-star-inserted\" href=\"https:\/\/machinser.com\/odoo-19-module-structure-developers-handbook\/\" target=\"_blank\" rel=\"noopener\" data-hveid=\"0\" data-ved=\"0CAAQ_4QMahcKEwjA_v2cuKWXAxUAAAAAHQAAAAAQJw\">Odoo 19 Module Structure: Developer&#8217;s Handbook<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Once the basic structure of an Odoo 19 custom module is understood, the next step is to understand what each component actually does. A custom module is not simply a collection of Python and XML files. Different components are responsible for different parts of the application. Models define data and business logic. Views define how [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":9692,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[330,318,313,195,192],"tags":[],"class_list":["post-9677","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-custome-addons","category-odoo-19","category-odoo-configuration","category-odoo-customization","category-odoo-erp"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/machinser.com\/wp-content\/uploads\/2026\/10\/Odoo-19-Custom-Module-Guide.png","_links":{"self":[{"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/posts\/9677","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/comments?post=9677"}],"version-history":[{"count":4,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/posts\/9677\/revisions"}],"predecessor-version":[{"id":9691,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/posts\/9677\/revisions\/9691"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/media\/9692"}],"wp:attachment":[{"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/media?parent=9677"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/categories?post=9677"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/machinser.com\/ar\/wp-json\/wp\/v2\/tags?post=9677"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}