Guia de desenvolvimento de tema Odoo: crie temas personalizados para Odoo 17/18
O ecossistema temático Odoo gera mais de US$ 12 milhões anualmente na Odoo App Store, mas menos de 8% dos temas recebem uma classificação de 5 estrelas. A diferença entre um tema esquecível e um best-seller se resume à compreensão do pipeline de renderização do Odoo, à herança do modelo QWeb e à arquitetura de arrastar e soltar do construtor de sites.
Este guia cobre todas as camadas do desenvolvimento do tema Odoo - desde a estrutura do projeto até a arquitetura SCSS avançada, desde a construção de snippets personalizados até fazer seu tema funcionar perfeitamente com o construtor de sites Odoo.
Principais conclusões
- Os temas Odoo são módulos padrão com uma estrutura de diretório específica — eles estendem o módulo
websitee registram modelos, SCSS e JavaScript por meio do sistema de agrupamento de ativos. - QWeb é o mecanismo de modelo do lado do servidor do Odoo — ele usa diretivas XML (t-if, t-foreach, t-call, t-inherit) que são compiladas em HTML. Compreender a herança de modelos com XPath é essencial para personalizar qualquer parte do site.
- O pipeline SCSS é compilado por meio do gerenciador de ativos do Odoo — variáveis personalizadas substituem os padrões do Bootstrap 5 e
_variables.scssdeve ser carregado antes da estrutura. - A integração do construtor de sites requer a definição de opções de snippet, blocos de construção personalizados e blocos de conteúdo dinâmico que usuários não técnicos podem configurar por meio do editor de arrastar e soltar.
- O design responsivo no Odoo aproveita o sistema de grade do Bootstrap 5 com utilitários específicos do Odoo — teste em todos os pontos de interrupção e considere linguagens RTL para implantações internacionais.
1. Estrutura do módulo temático
Um tema Odoo segue a estrutura de módulo padrão com adições específicas do site:
theme_ecosire/
├── __init__.py
├── __manifest__.py
├── data/
│ ├── theme_data.xml # Default pages, menus
│ └── images.xml # Pre-loaded image library
├── static/
│ ├── description/
│ │ ├── icon.png # Module icon (128x128)
│ │ └── icon.svg # SVG version
│ ├── src/
│ │ ├── img/ # Theme images
│ │ ├── fonts/ # Custom fonts
│ │ ├── scss/
│ │ │ ├── _variables.scss # Bootstrap variable overrides
│ │ │ ├── _mixins.scss # Custom SCSS mixins
│ │ │ ├── _typography.scss # Font stacks, sizes
│ │ │ ├── _header.scss # Header styles
│ │ │ ├── _footer.scss # Footer styles
│ │ │ ├── _snippets.scss # Snippet-specific styles
│ │ │ └── primary.scss # Main entry point
│ │ ├── js/
│ │ │ ├── snippets/ # Snippet JS (animations, interactions)
│ │ │ └── theme.js # Theme-wide JS
│ │ └── xml/
│ │ └── snippets.xml # OWL snippet templates
│ └── img/ # Public images (backgrounds, icons)
├── views/
│ ├── assets.xml # Asset bundle registration
│ ├── layout.xml # Header/footer templates
│ ├── pages.xml # Pre-built page templates
│ └── snippets/
│ ├── options.xml # Snippet options (color pickers, etc.)
│ └── s_custom_block.xml # Custom snippet definitions
└── tests/
└── test_theme.py # Theme installation tests
O Manifesto do Tema
# __manifest__.py
{
'name': 'Theme Ecosire',
'description': 'Modern, performance-optimized theme for Odoo Website',
'category': 'Theme',
'version': '18.0.1.0.0',
'author': 'ECOSIRE Private Limited',
'website': 'https://ecosire.com',
'license': 'LGPL-3',
'depends': ['website'],
'data': [
'views/assets.xml',
'views/layout.xml',
'views/pages.xml',
'views/snippets/options.xml',
'views/snippets/s_custom_block.xml',
],
'assets': {
'web.assets_frontend': [
# Variables MUST come before Bootstrap
('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
'theme_ecosire/static/src/scss/primary.scss',
'theme_ecosire/static/src/js/theme.js',
],
'website.assets_wysiwyg': [
'theme_ecosire/static/src/js/snippets/*.js',
],
},
'images': [
'static/description/banner.png',
'static/description/theme_screenshot.jpg',
],
'snippet_lists': {
'website.snippets': [
('replace', 'theme_ecosire.s_custom_hero'),
],
},
'installable': True,
'application': False,
'auto_install': False,
}
Nota crítica sobre a chave assets: No Odoo 17+, o dicionário assets em __manifest__.py é a forma preferida de registrar arquivos estáticos. A abordagem views/assets.xml mais antiga usando <template inherit_id="web.assets_frontend"> ainda funciona, mas está sendo descontinuada. Use ambos para máxima compatibilidade.
2. Aprofundamento em modelos QWeb
QWeb é o mecanismo de modelo baseado em XML do Odoo. Ele processa diretivas prefixadas com t- e gera HTML. Compreender o QWeb não é negociável para o desenvolvimento do tema.
Diretrizes Básicas
<!-- Conditional rendering -->
<div t-if="website.is_publisher()">
<span>Edit mode controls here</span>
</div>
<div t-elif="request.env.user._is_internal()">
<span>Internal user view</span>
</div>
<div t-else="">
<span>Public visitor view</span>
</div>
<!-- Loops -->
<ul>
<t t-foreach="website.menu_id.child_id" t-as="menu_item">
<li t-attf-class="nav-item #{('active' if menu_item.is_active else '')}">
<a t-att-href="menu_item.url" t-out="menu_item.name"/>
</li>
</t>
</ul>
<!-- Variable setting -->
<t t-set="company" t-value="res_company"/>
<t t-set="primary_color" t-value="'#FF6B00'"/>
<!-- Output (auto-escaped) -->
<span t-out="partner.name"/>
<!-- Raw HTML output (use with caution) -->
<div t-raw="sanitized_html_content"/>
<!-- Dynamic attributes -->
<div t-att-id="'section-' + str(section.id)"
t-attf-class="container #{extra_class}"
t-att-data-section="section.sequence"/>
<!-- Template calling -->
<t t-call="theme_ecosire.hero_section">
<t t-set="title">Welcome to Our Store</t>
<t t-set="subtitle">Premium Odoo Themes</t>
</t>
Herança de modelo com XPath
É aqui que o desenvolvimento do tema se torna poderoso. Você pode modificar qualquer modelo existente sem substituí-lo:
<!-- views/layout.xml -->
<template id="custom_header" inherit_id="website.layout" name="Custom Header">
<!-- Replace the entire header -->
<xpath expr="//header" position="replace">
<header id="top" class="ecosire-header">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" t-att-href="'/'">
<img t-if="website.logo"
t-att-src="website.image_url(website, 'logo')"
alt="Logo" class="img-fluid" style="max-height: 50px;"/>
</a>
<!-- Custom mega menu -->
<t t-call="theme_ecosire.mega_menu"/>
<!-- CTA button -->
<a href="/contactus" class="btn btn-primary ms-3">
Get Started
</a>
</div>
</nav>
</header>
</xpath>
<!-- Add a top bar before the header -->
<xpath expr="//header[@id='top']" position="before">
<div class="ecosire-topbar bg-dark text-white py-1">
<div class="container d-flex justify-content-between">
<span t-out="res_company.phone"/>
<span t-out="res_company.email"/>
</div>
</div>
</xpath>
<!-- Modify footer: add a newsletter section -->
<xpath expr="//footer" position="inside">
<div class="ecosire-newsletter bg-primary text-white py-5">
<div class="container text-center">
<h3>Stay Updated</h3>
<t t-call="website.s_newsletter_block"/>
</div>
</div>
</xpath>
</template>
Referência de posição XPath
| Posição | Efeito |
|---|---|
| CÓDIGO0 | Substitua totalmente o elemento correspondente |
| CÓDIGO0 | Anexe dentro do elemento correspondente |
| CÓDIGO0 | Inserir antes do elemento correspondente |
| CÓDIGO0 | Inserir após o elemento correspondente |
| CÓDIGO0 | Modifique os atributos do elemento correspondente |
<!-- Modify attributes example -->
<xpath expr="//div[@id='wrapwrap']" position="attributes">
<attribute name="class" add="ecosire-theme" separator=" "/>
</xpath>
<!-- Remove an element -->
<xpath expr="//div[hasclass('o_not_editable')]" position="replace"/>
3. Arquitetura SCSS
O pipeline SCSS do Odoo é compilado por meio de seu gerenciador de ativos, o que significa que suas substituições de variáveis devem ser carregadas antes dos padrões do Bootstrap. Use a diretiva 'prepend' no dicionário de ativos do seu manifesto para garantir que seu arquivo _variables.scss tenha prioridade. Este único arquivo controla cores, fontes, espaçamento, raio da borda e todos os outros tokens de design do Bootstrap no seu tema.
Substituições de variáveis (_variables.scss)
// _variables.scss — loaded BEFORE Bootstrap
// Override Bootstrap 5 defaults
// Brand Colors
$o-brand-primary: #FF6B00;
$o-brand-secondary: #1A1F36;
// Bootstrap color overrides
$primary: $o-brand-primary;
$secondary: $o-brand-secondary;
$success: #00C853;
$info: #2196F3;
$warning: #FFB300;
$danger: #FF1744;
// Typography
$font-family-sans-serif: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-family-monospace: 'JetBrains Mono', 'Fira Code', monospace;
$font-size-base: 1rem;
$headings-font-weight: 700;
$headings-line-height: 1.2;
// Spacing
$spacer: 1rem;
// Border radius
$border-radius: 0.5rem;
$border-radius-lg: 1rem;
$border-radius-pill: 50rem;
// Shadows
$box-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
$box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
$box-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
// Navbar
$navbar-padding-y: 1rem;
$navbar-brand-font-size: 1.5rem;
// Cards
$card-border-radius: $border-radius-lg;
$card-border-color: transparent;
$card-box-shadow: $box-shadow;
Folha de estilo principal (primary.scss)
// primary.scss — Main entry point
@import 'variables'; // Already prepended, but import for IDE support
@import 'mixins';
@import 'typography';
@import 'header';
@import 'footer';
@import 'snippets';
// ============================================
// Global Overrides
// ============================================
#wrapwrap.ecosire-theme {
// Smooth scrolling
scroll-behavior: smooth;
// Better text rendering
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// ============================================
// Section Spacing
// ============================================
.s_section {
padding: 5rem 0;
@media (max-width: 768px) {
padding: 3rem 0;
}
}
// ============================================
// Button Styles
// ============================================
.btn-primary {
background: linear-gradient(135deg, $primary, darken($primary, 10%));
border: none;
padding: 0.75rem 2rem;
font-weight: 600;
letter-spacing: 0.02em;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba($primary, 0.4);
}
}
// ============================================
// Card Component
// ============================================
.ecosire-card {
background: white;
border-radius: $border-radius-lg;
overflow: hidden;
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: $box-shadow-lg;
}
&__image {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
&__body {
padding: 1.5rem;
}
}
Carregamento de fonte personalizada
// _typography.scss
@font-face {
font-family: 'Inter';
src: url('/theme_ecosire/static/src/fonts/Inter-Variable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('/theme_ecosire/static/src/fonts/Inter-Variable-Italic.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: italic;
}
// Heading hierarchy
h1, .h1 { font-size: 2.5rem; font-weight: 800; }
h2, .h2 { font-size: 2rem; font-weight: 700; }
h3, .h3 { font-size: 1.5rem; font-weight: 600; }
h4, .h4 { font-size: 1.25rem; font-weight: 600; }
4. Snippets personalizados (blocos de construção)
Snippets são os blocos de construção de arrastar e soltar no construtor de sites Odoo. Criar snippets personalizados é o que torna um tema verdadeiramente valioso.
Definindo um modelo de snippet
<!-- views/snippets/s_custom_block.xml -->
<odoo>
<!-- The snippet template -->
<template id="s_ecosire_hero" name="Ecosire Hero Section">
<section class="s_ecosire_hero pt-5 pb-5 o_cc o_cc1">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold">
Transform Your Business
</h1>
<p class="lead text-muted mt-3">
Enterprise solutions that scale with your ambitions.
</p>
<div class="mt-4">
<a href="/contactus" class="btn btn-primary btn-lg me-3">
Get Started
</a>
<a href="/about" class="btn btn-outline-secondary btn-lg">
Learn More
</a>
</div>
</div>
<div class="col-lg-6">
<img src="/theme_ecosire/static/src/img/hero-illustration.svg"
alt="Hero" class="img-fluid" loading="lazy"/>
</div>
</div>
</div>
</section>
</template>
<!-- Register snippet in the builder panel -->
<template id="snippets" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_structure']//t[@t-snippet]/.."
position="inside">
<t t-snippet="theme_ecosire.s_ecosire_hero"
t-thumbnail="/theme_ecosire/static/src/img/snippets/hero_thumb.png"/>
</xpath>
</template>
</odoo>
Opções de snippet (personalização do usuário)
<!-- views/snippets/options.xml -->
<odoo>
<template id="s_ecosire_hero_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="EcosireHeroOptions"
data-selector=".s_ecosire_hero">
<!-- Layout selector -->
<we-select string="Layout">
<we-button data-select-class="s_hero_left">Text Left</we-button>
<we-button data-select-class="s_hero_center">Centered</we-button>
<we-button data-select-class="s_hero_right">Text Right</we-button>
</we-select>
<!-- Background style -->
<we-select string="Background">
<we-button data-select-class="bg-white">White</we-button>
<we-button data-select-class="bg-dark text-white">Dark</we-button>
<we-button data-select-class="bg-gradient">Gradient</we-button>
</we-select>
<!-- Animation toggle -->
<we-checkbox string="Enable Animation"
data-toggle-class="s_hero_animated"/>
</div>
</xpath>
</template>
</odoo>
Snippet JavaScript (animações)
// static/src/js/snippets/s_ecosire_hero.js
/** @odoo-module */
import publicWidget from '@web/legacy/js/public/public_widget';
const EcosireHero = publicWidget.Widget.extend({
selector: '.s_ecosire_hero.s_hero_animated',
disabledInEditableMode: true,
start() {
this._super(...arguments);
this._initAnimation();
return Promise.resolve();
},
_initAnimation() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
this.el.querySelectorAll('.animate-target').forEach(el => {
observer.observe(el);
});
},
destroy() {
this._super(...arguments);
},
});
publicWidget.registry.EcosireHero = EcosireHero;
export default EcosireHero;
5. Melhores práticas de design responsivo
SCSS que prioriza dispositivos móveis
// _responsive.scss
// Mobile-first breakpoints (Bootstrap 5)
// xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px
.ecosire-header {
// Mobile default
padding: 0.5rem 0;
.navbar-brand img {
max-height: 35px;
}
.nav-link {
padding: 0.75rem 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
// Tablet and up
@include media-breakpoint-up(md) {
padding: 0.75rem 0;
.nav-link {
padding: 0.5rem 1rem;
border-bottom: none;
}
}
// Desktop
@include media-breakpoint-up(lg) {
padding: 1rem 0;
.navbar-brand img {
max-height: 50px;
}
}
}
// Touch-friendly elements
@media (hover: none) {
.btn {
min-height: 44px;
min-width: 44px;
}
.nav-link {
min-height: 44px;
display: flex;
align-items: center;
}
}
Suporte RTL
// _rtl.scss — Critical for Arabic, Hebrew, Urdu markets
html[dir="rtl"] {
.ecosire-header {
.navbar-brand {
margin-left: 1rem;
margin-right: 0;
}
.btn + .btn {
margin-right: 0.75rem;
margin-left: 0;
}
}
.ecosire-card__body {
text-align: right;
}
// Use logical properties where possible
.ms-3 { margin-inline-start: 1rem; }
.me-3 { margin-inline-end: 1rem; }
}
6. Otimização de desempenho
Otimização de imagem
<!-- Use responsive images with srcset -->
<img t-att-src="'/web/image/ir.attachment/%s/datas/800x400' % image.id"
t-att-srcset="'/web/image/ir.attachment/%s/datas/400x200 400w, /web/image/ir.attachment/%s/datas/800x400 800w, /web/image/ir.attachment/%s/datas/1200x600 1200w' % (image.id, image.id, image.id)"
sizes="(max-width: 576px) 400px, (max-width: 992px) 800px, 1200px"
loading="lazy"
decoding="async"
alt="Descriptive alt text"/>
Inlining CSS crítico
<!-- views/layout.xml -->
<template id="critical_css" inherit_id="website.layout">
<xpath expr="//head" position="inside">
<style type="text/css">
/* Inline critical above-the-fold CSS */
.ecosire-header { min-height: 72px; }
.s_ecosire_hero { min-height: 60vh; }
.navbar-brand img { max-height: 50px; }
</style>
</xpath>
</template>
Estratégia de carregamento de ativos
# __manifest__.py
'assets': {
# Critical CSS — loaded in head
'web.assets_frontend': [
('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
'theme_ecosire/static/src/scss/primary.scss',
],
# Non-critical JS — deferred
'web.assets_frontend_lazy': [
'theme_ecosire/static/src/js/animations.js',
'theme_ecosire/static/src/js/parallax.js',
],
# Editor-only assets
'website.assets_wysiwyg': [
'theme_ecosire/static/src/js/snippets/*.js',
'theme_ecosire/static/src/scss/_editor.scss',
],
},
7. Integração do Construtor de Sites
Blocos de conteúdo dinâmico
<!-- A snippet that fetches products dynamically -->
<template id="s_featured_products" name="Featured Products Grid">
<section class="s_featured_products pt-5 pb-5">
<div class="container">
<h2 class="text-center mb-5">Featured Products</h2>
<div class="row" t-if="products">
<t t-foreach="products[:8]" t-as="product">
<div class="col-md-3 mb-4">
<div class="ecosire-card h-100">
<img t-att-src="product.image_url"
t-att-alt="product.name"
class="ecosire-card__image" loading="lazy"/>
<div class="ecosire-card__body">
<h5 t-out="product.name"/>
<p class="text-primary fw-bold"
t-out="product.list_price"
t-options='{"widget": "monetary"}'/>
<a t-att-href="product.website_url"
class="btn btn-outline-primary btn-sm">
View Details
</a>
</div>
</div>
</div>
</t>
</div>
</div>
</section>
</template>
Modelos de página
<!-- Pre-built page templates that users can select -->
<template id="landing_page_template" name="Landing Page">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<t t-call="theme_ecosire.s_ecosire_hero"/>
<t t-call="theme_ecosire.s_features_grid"/>
<t t-call="theme_ecosire.s_testimonials"/>
<t t-call="theme_ecosire.s_cta_banner"/>
</div>
</t>
</template>
8. Testando seu tema
# tests/test_theme.py
from odoo.tests.common import HttpCase, tagged
@tagged('post_install', '-at_install')
class TestThemeEcosire(HttpCase):
def test_01_theme_installation(self):
"""Theme installs without errors."""
module = self.env['ir.module.module'].search([
('name', '=', 'theme_ecosire')
])
self.assertEqual(module.state, 'installed')
def test_02_homepage_loads(self):
"""Homepage renders with theme applied."""
response = self.url_open('/')
self.assertEqual(response.status_code, 200)
self.assertIn('ecosire-theme', response.text)
def test_03_snippets_available(self):
"""Custom snippets appear in the builder."""
self.start_tour('/web#action=website.action_website',
'theme_ecosire_snippet_tour', login='admin')
def test_04_responsive_meta(self):
"""Viewport meta tag is present."""
response = self.url_open('/')
self.assertIn('viewport', response.text)
9. Publicação na Odoo App Store
| Requisito | Detalhes |
|---|---|
| Capturas de tela | 3-5 capturas de tela de alta qualidade (1920x1080) |
| Ícone | PNG 128x128 em static/description/ |
| Descrição | Descrição HTML em static/description/index.html |
| Licença | LGPL-3 para temas (prática padrão) |
| Versão | Partida da série Odoo (18.0.x.y.z) |
| Teste | Passe nas verificações automatizadas de linting do Odoo |
| Dados de demonstração | Incluir dados de demonstração para testar antes de comprar |
Perguntas frequentes
Posso usar Tailwind CSS em um tema Odoo?
Embora seja tecnicamente possível, usar Tailwind CSS junto com o Bootstrap 5 integrado do Odoo cria conflitos e aumenta significativamente o tamanho do pacote. A abordagem recomendada é usar utilitários Bootstrap 5 e propriedades personalizadas SCSS, que se integram nativamente ao pipeline de ativos e ao construtor de sites do Odoo. Se você precisar de classes de utilitários que o Bootstrap não fornece, crie utilitários SCSS personalizados.
Como posso tornar meu tema compatível com Odoo 17 e 18?
Crie ramificações separadas para cada versão do Odoo. Odoo 17 usa Bootstrap 5.1 enquanto Odoo 18 usa Bootstrap 5.3 com suporte a variáveis CSS. O registro de ativos também mudou – o Odoo 18 prefere a chave de ativos de manifesto, enquanto o Odoo 17 usa herança de modelo XML. Mantenha parciais SCSS compartilhados e arquivos de integração específicos de versão.
Por que minhas substituições de variáveis SCSS não estão funcionando?
A causa mais comum é a ordem de carregamento. Seu _variables.scss deve ser carregado antes da compilação do Bootstrap. Use a diretiva 'prepend' em seu manifesto: ('prepend', 'theme_name/static/src/scss/_variables.scss'). Certifique-se também de substituir os nomes de variáveis corretos - o Odoo agrupa algumas variáveis do Bootstrap com versões com prefixo o, como $o-brand-primary.
Como os snippets personalizados funcionam com o construtor de sites?
Snippets personalizados são modelos QWeb registrados no painel de snippets por meio de herança de modelo de website.snippets. Cada snippet precisa de um modelo para sua estrutura HTML, opções opcionais definidas em website.snippet_options para o painel de personalização e JavaScript opcional estendendo publicWidget.Widget para interatividade. O construtor de sites lida automaticamente com arrastar e soltar, edição inline e desfazer/refazer para snippets registrados.
Qual é a melhor maneira de adicionar fontes personalizadas a um tema Odoo?
Coloque os arquivos de fonte WOFF2 em static/src/fonts/ e defina regras @font-face em seu SCSS. Use font-display: swap para desempenho. Em seguida, substitua a variável $font-family-sans-serif Bootstrap em seu _variables.scss. Evite carregar fontes de CDNs externos, pois isso adiciona uma pesquisa de DNS e pode falhar em redes restritas. Subconjunto de fontes para incluir apenas os intervalos de caracteres necessários.
Como posso suportar o modo escuro no meu tema Odoo?
Odoo 18 introduziu suporte nativo ao modo escuro por meio de propriedades personalizadas CSS. Substitua as variáveis do modo escuro em seu tema usando o seletor [data-bs-theme="dark"]. Defina variantes claras e escuras de suas cores, planos de fundo e sombras personalizados. A alternância do construtor de sites cuida da mudança. Seu tema só precisa fornecer os valores de variáveis CSS corretos para ambos os modos.
Próximas etapas
O desenvolvimento do tema é onde o design encontra a engenharia. Um tema Odoo bem construído pode atender centenas de clientes e gerar receitas recorrentes significativas na Odoo App Store.
Para guias relacionados:
- Guia de desenvolvimento Odoo Python — Fundamentos de desenvolvimento de back-end
- Desenvolvimento de módulo personalizado Odoo — Visão geral da arquitetura do módulo
- Tutorial Odoo REST API — Padrões de integração de API
Precisa de um tema Odoo personalizado ou de uma reformulação do site? A equipe de personalização Odoo da ECOSIRE projeta e cria temas de nível empresarial com integração completa do construtor de sites, design responsivo em todos os pontos de interrupção e suporte RTL para implantações internacionais. Entregamos temas personalizados para clientes em mais de 40 países. Entre em contato conosco para uma consulta de design gratuita.
Escrito por
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
Artigos Relacionados
Segmentação de clientes baseada em IA: do RFM ao clustering preditivo
Saiba como a IA transforma a segmentação de clientes, desde a análise estática de RFM até o clustering preditivo dinâmico. Guia de implementação com dados Python, Odoo e ROI real.
IA para otimização da cadeia de suprimentos: visibilidade, previsão e automação
Transforme as operações da cadeia de suprimentos com IA: detecção de demanda, pontuação de risco de fornecedores, otimização de rotas, automação de armazéns e previsão de interrupções. Guia 2026.
Estratégia de comércio eletrônico B2B: construir um negócio online de atacado em 2026
Domine o comércio eletrônico B2B com estratégias de preços de atacado, gerenciamento de contas, condições de crédito, catálogos punchout e configuração do portal Odoo B2B.