Como construir módulos Odoo personalizados: um guia do desenvolvedor para OWL, ORM e herança

Guia do desenvolvedor para construir módulos Odoo personalizados. Abrange estrutura de módulo, estrutura OWL, herança ORM, visualizações, regras de segurança, testes e diretrizes OCA.

E

ECOSIRE Research and Development Team

Equipe ECOSIRE

19 de fevereiro de 20267 min de leitura1.4k Palavras

Como construir módulos Odoo personalizados: um guia do desenvolvedor para OWL, ORM e herança

Quando os 82 módulos oficiais e mais de 40.000 módulos da comunidade do Odoo não atendem exatamente aos seus requisitos de negócios, o desenvolvimento de módulos personalizados preenche a lacuna. Este guia cobre os fundamentos da construção de módulos Odoo em 2026, incluindo estrutura do módulo, estrutura de front-end OWL, padrões ORM, mecanismos de herança e práticas recomendadas alinhadas com as diretrizes da OCA (Odoo Community Association).

O que é um módulo Odoo personalizado?

Um módulo Odoo personalizado é um pacote independente de lógica de back-end Python, definições de visualização XML, componentes de front-end JavaScript e regras de segurança que estendem a funcionalidade do Odoo. Os módulos podem adicionar recursos totalmente novos, modificar o comportamento existente ou integrar o Odoo com sistemas externos. Cada peça do Odoo é um módulo, tornando a arquitetura inerentemente extensível.

Estrutura de diretório do módulo

Um módulo bem organizado segue esta estrutura padrão:

my_custom_module/
├── __init__.py              # Python package init
├── __manifest__.py          # Module metadata and dependencies
├── models/
│   ├── __init__.py
│   └── my_model.py          # Business logic and data models
├── views/
│   ├── my_model_views.xml   # Form, tree, and kanban views
│   └── menu.xml             # Menu items and actions
├── security/
│   ├── ir.model.access.csv  # Access control list
│   └── security.xml         # Record rules and groups
├── data/
│   └── data.xml             # Default data records
├── static/
│   └── src/
│       ├── js/              # OWL components
│       ├── css/             # Stylesheets
│       └── xml/             # QWeb templates
├── wizard/                  # Transient models for wizards
├── reports/                 # QWeb report templates
└── tests/                   # Unit tests

O arquivo de manifesto

O arquivo __manifest__.py define a identidade do seu módulo:

{
    'name': 'My Custom Module',
    'version': '18.0.1.0.0',
    'category': 'Custom',
    'summary': 'Short description of what the module does',
    'description': """
        Long description with details about
        features and configuration.
    """,
    'author': 'ECOSIRE',
    'website': 'https://ecosire.com',
    'license': 'LGPL-3',
    'depends': ['base', 'sale', 'stock'],
    'data': [
        'security/ir.model.access.csv',
        'views/my_model_views.xml',
        'views/menu.xml',
        'data/data.xml',
    ],
    'assets': {
        'web.assets_backend': [
            'my_custom_module/static/src/js/**/*',
            'my_custom_module/static/src/css/**/*',
            'my_custom_module/static/src/xml/**/*',
        ],
    },
    'installable': True,
    'application': False,
    'auto_install': False,
}

Convenção de versão: {odoo_version}.{major}.{minor}.{patch} (por exemplo, 18.0.1.0.0).

Trabalhando com o ORM

O Mapeamento Objeto-Relacional (ORM) do Odoo é a base de todo o desenvolvimento de back-end. Os modelos são mapeados para tabelas de banco de dados, e o ORM fornece operações CRUD, campos computados, restrições e gerenciamento de fluxo de trabalho.

Definindo um modelo

from odoo import models, fields, api

class ProjectTask(models.Model):
    _name = 'my_module.task'
    _description = 'Project Task'
    _order = 'priority desc, create_date desc'

    name = fields.Char(string='Task Name', required=True)
    description = fields.Html(string='Description')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('in_progress', 'In Progress'),
        ('done', 'Done'),
        ('cancelled', 'Cancelled'),
    ], default='draft', tracking=True)
    assigned_to = fields.Many2one('res.users', string='Assigned To')
    deadline = fields.Date(string='Deadline')
    priority = fields.Selection([
        ('0', 'Normal'),
        ('1', 'Important'),
        ('2', 'Urgent'),
    ], default='0')
    tag_ids = fields.Many2many('my_module.tag', string='Tags')
    progress = fields.Float(compute='_compute_progress', store=True)

Referência de tipos de campo

| Tipo de campo | Tipo Python | Caso de uso | |---|---|---| | Caráter | | Texto curto (nome, referência) | | Texto | | Texto longo e simples | | HTML | | Conteúdo de texto rico | | Inteiro | interno | Números inteiros | | Flutuador | flutuar | Números decimais | | Booleano | bool | Sinalizadores Verdadeiro/Falso | | Data | data | Data sem hora | | Datahora | datahora | Data com hora | | Seleção | | Opções suspensas | | Muitos2one | interno | Link para um registro | | Um2muitos | lista | Reverso de Many2one | | Muitos2muitos | lista | Link para vários registros | | Binário | bytes | Anexos de arquivo |

Campos e restrições computados

@api.depends('subtask_ids.state')
def _compute_progress(self):
    for task in self:
        total = len(task.subtask_ids)
        done = len(task.subtask_ids.filtered(
            lambda t: t.state == 'done'
        ))
        task.progress = (done / total * 100) if total else 0

@api.constrains('deadline')
def _check_deadline(self):
    for task in self:
        if task.deadline and task.deadline < fields.Date.today():
            raise ValidationError(
                "Deadline cannot be in the past."
            )

Mecanismos de herança

Odoo oferece três tipos de herança, cada um servindo a um propósito diferente:

1. Herança de classe (extensão)

Estenda um modelo existente adicionando campos ou substituindo métodos. Este é o padrão mais comum.

class SaleOrderExtend(models.Model):
    _inherit = 'sale.order'

    custom_reference = fields.Char(string='Custom Ref')
    approved_by = fields.Many2one('res.users')

    def action_confirm(self):
        # Add custom logic before standard confirmation
        for order in self:
            if order.amount_total > 10000 and not order.approved_by:
                raise UserError("Orders over $10,000 require approval.")
        return super().action_confirm()

2. Herança do protótipo

Crie um novo modelo que copie todos os campos e métodos de um modelo existente.

class CustomPartner(models.Model):
    _name = 'my_module.partner'
    _inherit = 'res.partner'   # Copies structure
    _description = 'Custom Partner'

3. Herança de delegação

Crie um novo modelo que delegue para um modelo existente por meio de um link Many2one. Os campos do modelo pai aparecem no modelo filho de forma transparente.

class LibraryMember(models.Model):
    _name = 'library.member'
    _inherits = {'res.partner': 'partner_id'}

    partner_id = fields.Many2one('res.partner', required=True,
                                  ondelete='cascade')
    membership_date = fields.Date()
    member_number = fields.Char()

A estrutura OWL (front-end)

Odoo 18 usa OWL (Odoo Web Library) como estrutura de front-end. OWL é uma estrutura baseada em componentes semelhante ao React ou Vue, mas projetada especificamente para as necessidades do Odoo.

Componente Básico OWL

/** @odoo-module */

import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";

class TaskDashboard extends Component {
    static template = "my_module.TaskDashboard";

    setup() {
        this.state = useState({
            tasks: [],
            filter: 'all',
        });
        this.loadTasks();
    }

    async loadTasks() {
        this.state.tasks = await this.env.services.orm.searchRead(
            "my_module.task",
            [["state", "!=", "cancelled"]],
            ["name", "state", "assigned_to", "deadline"]
        );
    }

    get filteredTasks() {
        if (this.state.filter === 'all') return this.state.tasks;
        return this.state.tasks.filter(
            t => t.state === this.state.filter
        );
    }
}

Modelo QWeb (XML)

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="my_module.TaskDashboard">
        <div class="o_task_dashboard">
            <div class="task-filters">
                <button t-on-click="() => state.filter = 'all'">All</button>
                <button t-on-click="() => state.filter = 'in_progress'">
                    In Progress
                </button>
            </div>
            <div class="task-list">
                <t t-foreach="filteredTasks" t-as="task" t-key="task.id">
                    <div class="task-card">
                        <span t-esc="task.name"/>
                    </div>
                </t>
            </div>
        </div>
    </t>
</templates>

Configuração de segurança

Todo modelo precisa de regras de acesso explícitas. Sem eles, nenhum usuário poderá acessar os dados do modelo.

Lista de controle de acesso (ir.model.access.csv)

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_task_user,task.user,model_my_module_task,base.group_user,1,1,1,0
access_task_manager,task.manager,model_my_module_task,my_module.group_manager,1,1,1,1

Regras de registro (security.xml)

<record id="task_own_rule" model="ir.rule">
    <field name="name">Own Tasks Only</field>
    <field name="model_id" ref="model_my_module_task"/>
    <field name="domain_force">
        [('assigned_to', '=', user.id)]
    </field>
    <field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>

Testando seu módulo

Odoo oferece suporte a testes de unidade Python usando a estrutura unittest com classes de teste específicas do Odoo:

from odoo.tests.common import TransactionCase

class TestTask(TransactionCase):
    def setUp(self):
        super().setUp()
        self.task = self.env['my_module.task'].create({
            'name': 'Test Task',
            'state': 'draft',
        })

    def test_task_creation(self):
        self.assertEqual(self.task.state, 'draft')
        self.assertEqual(self.task.progress, 0)

    def test_deadline_constraint(self):
        with self.assertRaises(ValidationError):
            self.task.write({
                'deadline': fields.Date.subtract(
                    fields.Date.today(), days=1
                ),
            })

Execute testes com: odoo-bin -d test_db --test-enable -i my_custom_module --stop-after-init

Perguntas frequentes

P: Quanto tempo leva para construir um módulo Odoo personalizado? Módulos simples (novos campos, visualizações básicas) levam de 1 a 3 dias. Módulos moderados (novos modelos, fluxos de trabalho, relatórios) levam de 1 a 3 semanas. Módulos complexos (sistemas multimodelos, integrações externas, componentes OWL personalizados) levam de 4 a 12 semanas. Para empresas que precisam de módulos personalizados, mas não possuem desenvolvedores Odoo internos, contrate um desenvolvedor Odoo experiente de nossa equipe.

P: Devo modificar o código principal do Odoo ou criar um módulo separado? Sempre crie um módulo separado. A modificação do código principal prejudica a capacidade de atualização e cria conflitos de mesclagem durante as atualizações de versão. Use herança para estender modelos e visualizações existentes do seu módulo personalizado.

P: Quais são as diretrizes do OCA? A Odoo Community Association (OCA) publica padrões de codificação para qualidade de módulo, incluindo convenções de nomenclatura, requisitos de documentação, expectativas de cobertura de teste e regras de estilo de código. Seguir as diretrizes da OCA garante que seu módulo seja sustentável e compatível com o ecossistema mais amplo da comunidade.

Obtendo ajuda profissional

A construção de módulos Odoo personalizados requer conhecimento em Python, JavaScript, PostgreSQL e convenções de estrutura do Odoo. Se você precisa de uma extensão de campo simples ou de um sistema complexo de vários módulos, os serviços de personalização Odoo da ECOSIRE fornecem módulos prontos para produção construídos de acordo com os padrões OCA.

Entre em contato conosco para discutir os requisitos do seu módulo personalizado e obter uma estimativa de desenvolvimento.

Compartilhar:
E

Escrito por

ECOSIRE Research and Development Team

Construindo produtos digitais de nível empresarial na ECOSIRE. Compartilhando insights sobre integrações Odoo, automação de e-commerce e soluções de negócios com IA.

Converse no WhatsApp