Odoo Python 開発: 初心者とプロのための完全ガイド
2025 年の Python 開発者調査によると、エンタープライズ Python 開発者の 47% 以上が少なくとも 1 つの ERP フレームワークを使用しており、Odoo はオープンソース ERP の中で 68% の市場シェアを獲得し、このセグメントを独占しています。初めてのモジュールを構築する場合でも、複数の企業での展開を設計する場合でも、Odoo の Python レイヤーを理解することは、開発できる唯一の最も影響力のあるスキルです。
このガイドは、表面レベルの概要をはるかに超えています。 Odoo の Python バックエンドのすべての層を、ORM のメタクラス マジックから高度なコントローラー パターンに至るまで、本番環境の問題のデバッグから実際にリグレッションをキャッチするテストの作成に至るまで、詳しく説明します。
重要なポイント
- Odoo の ORM は、SQL スキーマの自動生成、キャッシュの管理、および複数企業のセキュリティの処理を行うメタクラスに基づいて構築された完全なオブジェクト リレーショナル マッパーです。Odoo を深く理解することが、Odoo と協力するか戦うかの違いです。
- 5 つのフィールド カテゴリ は、基本 (Char、Integer、Float、Boolean、Text、Html、Date、Datetime、Binary、Selection)、リレーショナル (Many2one、One2many、Many2many)、計算フィールド、関連フィールド、および通貨フィールドのすべてのデータ モデリング ニーズをカバーします。
- 3 つの継承メカニズム により、クラス継承 (_inherit)、プロトタイプ継承 (_inherit + _name)、委任継承 (_inherits) の 3 つの継承メカニズムを使用して、フォークせずに Odoo の任意の部分を拡張できます。
- コントローラーは、JSON-RPC および HTTP エンドポイント パターン、認証モード、CORS 構成を備えたデコレーター (@http.route) を使用して HTTP を処理します。
- TransactionCase と HttpCase による自動テスト は、運用環境に到達する前に回帰を検出します。ビジネス ロジックの 80% 以上のカバー率を目指します。
1. 開発環境のセットアップ
コードを記述する前に、適切な開発環境が必要です。 2026 年に推奨されるセットアップは次のとおりです。
# Install system dependencies (Ubuntu/Debian)
# sudo apt install python3.12 python3.12-venv python3.12-dev
# sudo apt install postgresql-17 libpq-dev libxml2-dev libxslt1-dev
# Clone Odoo source (enterprise requires license)
# git clone https://github.com/odoo/odoo.git --branch 18.0 --depth 1
# git clone https://github.com/odoo/enterprise.git --branch 18.0 --depth 1
# Create virtual environment
# python3.12 -m venv odoo-venv
# source odoo-venv/bin/activate
# pip install -r odoo/requirements.txt
# Create database and start Odoo
# createdb mydb
# python odoo/odoo-bin -d mydb --addons-path=odoo/addons,enterprise,custom_addons
モジュール用の custom_addons ディレクトリを作成します。各モジュールは、Odoo が期待する特定の構造を持つ独自のサブディレクトリに存在します。
2. モジュール アーキテクチャの詳細
すべての Odoo モジュールは、構成よりも規約の構造に従います。各コンポーネントの役割を理解することが不可欠です。
my_module/
├── __init__.py # Python package initializer
├── __manifest__.py # Module metadata and dependencies
├── models/
│ ├── __init__.py
│ ├── sale_order.py # Business logic models
│ └── res_partner.py # Partner extensions
├── views/
│ ├── sale_order_views.xml # Form, tree, search views
│ └── menu.xml # Menu items and actions
├── controllers/
│ ├── __init__.py
│ └── main.py # HTTP controllers
├── security/
│ ├── ir.model.access.csv # ACL rules
│ └── security.xml # Record rules and groups
├── data/
│ ├── data.xml # Default data
│ └── demo.xml # Demo data (dev only)
├── wizards/
│ ├── __init__.py
│ └── mass_update.py # TransientModel wizards
├── report/
│ ├── report_invoice.xml # QWeb report templates
│ └── report.py # Report parsers
├── static/
│ ├── description/
│ │ └── icon.png # Module icon (128x128)
│ └── src/
│ ├── js/ # OWL components
│ └── xml/ # OWL templates
└── tests/
├── __init__.py
└── test_sale_order.py # Automated tests
マニフェストファイル
__manifest__.py はモジュールの中心です。どの分野も重要です:
# __manifest__.py
{
'name': 'Sales Commission Engine',
'version': '18.0.1.0.0', # Odoo version.major.minor.patch
'category': 'Sales/Commission',
'summary': 'Automated commission calculations for sales teams',
'description': """
Long description with RST formatting.
Supports multi-tier commission plans.
""",
'author': 'ECOSIRE Private Limited',
'website': 'https://ecosire.com',
'license': 'LGPL-3',
'depends': ['sale', 'account', 'hr'], # Required modules
'data': [
'security/ir.model.access.csv',
'views/commission_plan_views.xml',
'views/menu.xml',
'data/commission_data.xml',
],
'demo': ['data/demo.xml'],
'installable': True,
'application': True, # Shows in Apps menu
'auto_install': False,
'external_dependencies': {
'python': ['pandas'], # pip packages
},
}
バージョン番号付け規則: {odoo_version}.{major}.{minor}.{patch} — 最初の番号は常に Odoo シリーズと一致します。
3. ORM API: 完全なリファレンス
Odoo の ORM は、ほとんどの開発者が認識しているよりもはるかに強力です。スキーマの生成、キャッシュ、アクセス制御、複数会社のフィルタリングを自動的に処理します。
モデルの種類
from odoo import models, fields, api
# Persistent model — stored in database
class CommissionPlan(models.Model):
_name = 'commission.plan'
_description = 'Commission Plan'
_order = 'sequence, name'
_rec_name = 'name'
name = fields.Char(string='Plan Name', required=True, index=True)
active = fields.Boolean(default=True)
sequence = fields.Integer(default=10)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
# Transient model — auto-cleaned after ~1 hour
class CommissionWizard(models.TransientModel):
_name = 'commission.calculate.wizard'
_description = 'Calculate Commissions'
date_from = fields.Date(required=True)
date_to = fields.Date(required=True)
# Abstract model — no database table, only for inheritance
class CommissionMixin(models.AbstractModel):
_name = 'commission.mixin'
_description = 'Commission Mixin'
commission_rate = fields.Float(string='Commission %')
フィールドタイプの詳細
class SaleCommission(models.Model):
_name = 'sale.commission'
_description = 'Sale Commission Record'
# Basic fields
name = fields.Char(string='Reference', required=True, copy=False,
default=lambda self: self.env['ir.sequence'].next_by_code('sale.commission'))
amount = fields.Float(string='Amount', digits=(16, 2))
percentage = fields.Float(string='Rate %', digits=(5, 2))
is_paid = fields.Boolean(string='Paid', default=False)
notes = fields.Text(string='Internal Notes')
description = fields.Html(string='Description', sanitize=True)
date = fields.Date(string='Commission Date', default=fields.Date.today)
create_datetime = fields.Datetime(string='Created', default=fields.Datetime.now)
document = fields.Binary(string='Attachment')
document_name = fields.Char(string='File Name')
# Selection field
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('paid', 'Paid'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True)
# Relational fields
salesperson_id = fields.Many2one('res.users', string='Salesperson',
required=True, ondelete='restrict')
order_id = fields.Many2one('sale.order', string='Sale Order')
line_ids = fields.One2many('sale.commission.line', 'commission_id', string='Lines')
tag_ids = fields.Many2many('sale.commission.tag', string='Tags')
# Computed field with store and search
total_amount = fields.Monetary(string='Total', compute='_compute_total',
store=True, currency_field='currency_id')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
@api.depends('line_ids.amount')
def _compute_total(self):
for record in self:
record.total_amount = sum(record.line_ids.mapped('amount'))
# Related field (shortcut for computed)
salesperson_email = fields.Char(related='salesperson_id.email', store=True)
CRUD 操作
# CREATE — returns new recordset
commission = self.env['sale.commission'].create({
'salesperson_id': self.env.user.id,
'order_id': order.id,
'state': 'draft',
'line_ids': [
(0, 0, {'product_id': product.id, 'amount': 150.00}), # Create
],
})
# Batch create (much faster than loop)
vals_list = [{'name': f'COM-{i}', 'amount': i * 10} for i in range(100)]
records = self.env['sale.commission'].create(vals_list)
# READ — browse by IDs
commission = self.env['sale.commission'].browse(42)
commissions = self.env['sale.commission'].browse([42, 43, 44])
# SEARCH — returns recordset matching domain
pending = self.env['sale.commission'].search([
('state', '=', 'confirmed'),
('date', '>=', '2026-01-01'),
('salesperson_id.department_id.name', '=', 'Sales'),
], order='date desc', limit=50)
# search_count — just the count
count = self.env['sale.commission'].search_count([('state', '=', 'draft')])
# search_read — search + read in one query (optimal for API calls)
data = self.env['sale.commission'].search_read(
[('state', '=', 'confirmed')],
fields=['name', 'amount', 'salesperson_id'],
limit=20, offset=0, order='amount desc'
)
# UPDATE
commission.write({'state': 'paid', 'notes': 'Paid on March 2026'})
# Batch update (all records get same values)
pending.write({'state': 'confirmed'})
# DELETE
old_drafts.unlink()
# SPECIAL: (0, 0, vals) create, (1, id, vals) update, (2, id, 0) delete
# (3, id, 0) unlink M2M, (4, id, 0) link M2M, (5, 0, 0) clear M2M
# (6, 0, [ids]) replace M2M
ドメイン式
ドメインは Odoo のクエリ言語です。それらをマスターしてください:
# Basic operators: =, !=, >, >=, <, <=, like, ilike, in, not in
# Special operators: =like (SQL LIKE), =ilike (case-insensitive LIKE)
# child_of, parent_of — for hierarchical models
domain = [
'|', # OR (next two leaves)
('state', '=', 'confirmed'),
'&', # AND (next two leaves)
('state', '=', 'draft'),
('amount', '>', 1000),
('salesperson_id', '!=', False), # Implicit AND with above
('date', '>=', '2026-01-01'),
('tag_ids.name', 'in', ['priority', 'vip']), # Dot notation for relations
]
# Negation
domain = [('state', 'not in', ['cancelled', 'paid'])]
# NULL checks
domain = [('notes', '!=', False)] # Has notes
domain = [('notes', '=', False)] # No notes
4. 継承パターン
Odoo には、ソース コードを変更せずにモジュールを拡張できる 3 つの継承メカニズムが用意されています。クラス継承により、既存のモデルにフィールドとメソッドが追加されます。プロトタイプの継承により、別個のデータベース テーブルを持つ既存のモデルに基づいて新しいモデルが作成されます。委任の継承により、合成を通じてモデルがリンクされ、両方のテーブルにレコードが自動的に作成されます。
クラスの継承 (最も一般的)
class SaleOrderInherit(models.Model):
_inherit = 'sale.order' # Extend existing model
# Add new fields
commission_plan_id = fields.Many2one('commission.plan', string='Commission Plan')
commission_amount = fields.Monetary(compute='_compute_commission')
@api.depends('amount_total', 'commission_plan_id.rate')
def _compute_commission(self):
for order in self:
rate = order.commission_plan_id.rate or 0
order.commission_amount = order.amount_total * rate / 100
# Override existing method
def action_confirm(self):
res = super().action_confirm() # Always call super()
for order in self:
if order.commission_plan_id:
order._create_commission_record()
return res
def _create_commission_record(self):
self.ensure_one()
self.env['sale.commission'].create({
'order_id': self.id,
'salesperson_id': self.user_id.id,
'amount': self.commission_amount,
})
プロトタイプの継承
class ProjectCommission(models.Model):
_name = 'project.commission'
_inherit = 'sale.commission' # Copy structure to new model
_description = 'Project-based Commission'
# Has all fields from sale.commission PLUS its own table
project_id = fields.Many2one('project.project', required=True)
milestone_id = fields.Many2one('project.milestone')
委任の継承
class CommissionEmployee(models.Model):
_name = 'commission.employee'
_inherits = {'hr.employee': 'employee_id'} # Delegation
_description = 'Commission-eligible Employee'
employee_id = fields.Many2one('hr.employee', required=True, ondelete='cascade')
commission_plan_id = fields.Many2one('commission.plan')
lifetime_earnings = fields.Monetary()
# Can access employee fields directly: record.name, record.department_id
5. ビジネスロジックパターン
制約
from odoo.exceptions import ValidationError
class CommissionPlan(models.Model):
_name = 'commission.plan'
rate = fields.Float(required=True)
name = fields.Char(required=True)
# Python constraint
@api.constrains('rate')
def _check_rate(self):
for plan in self:
if not 0 <= plan.rate <= 100:
raise ValidationError(
f"Commission rate must be between 0 and 100. Got: {plan.rate}"
)
# SQL constraint (faster, database-level)
_sql_constraints = [
('name_unique', 'UNIQUE(name, company_id)',
'Commission plan name must be unique per company.'),
('rate_positive', 'CHECK(rate >= 0)',
'Commission rate must be positive.'),
]
変更時メソッド
@api.onchange('commission_plan_id')
def _onchange_plan(self):
"""Triggered in UI when field changes. Sets defaults and warnings."""
if self.commission_plan_id:
self.percentage = self.commission_plan_id.rate
if self.commission_plan_id.rate > 30:
return {
'warning': {
'title': 'High Commission Rate',
'message': 'This plan has a rate above 30%. Please confirm with management.',
}
}
ステートマシンを使用したワークフロー
class SaleCommission(models.Model):
_name = 'sale.commission'
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('approved', 'Approved'),
('paid', 'Paid'),
('cancelled', 'Cancelled'),
], default='draft', tracking=True)
def action_confirm(self):
for rec in self.filtered(lambda r: r.state == 'draft'):
rec.state = 'confirmed'
rec.message_post(body="Commission confirmed.")
def action_approve(self):
self.filtered(lambda r: r.state == 'confirmed').write({
'state': 'approved',
'approved_by': self.env.user.id,
'approved_date': fields.Datetime.now(),
})
def action_pay(self):
for rec in self.filtered(lambda r: r.state == 'approved'):
rec._process_payment()
rec.state = 'paid'
def action_cancel(self):
allowed = self.filtered(lambda r: r.state in ('draft', 'confirmed'))
allowed.write({'state': 'cancelled'})
6. コントローラーと HTTP エンドポイント
from odoo import http
from odoo.http import request, Response
import json
class CommissionController(http.Controller):
# JSON-RPC endpoint (used by Odoo's internal JS framework)
@http.route('/commission/calculate', type='json', auth='user', methods=['POST'])
def calculate_commission(self, order_id, **kwargs):
order = request.env['sale.order'].browse(order_id)
if not order.exists():
return {'error': 'Order not found'}
commission = order.commission_amount
return {'order_id': order_id, 'commission': commission}
# HTTP endpoint (REST-style, for external integrations)
@http.route('/api/v1/commissions', type='http', auth='api_key',
methods=['GET'], csrf=False)
def list_commissions(self, **kwargs):
domain = [('state', '!=', 'cancelled')]
if kwargs.get('salesperson_id'):
domain.append(('salesperson_id', '=', int(kwargs['salesperson_id'])))
commissions = request.env['sale.commission'].search_read(
domain,
fields=['name', 'amount', 'state', 'date'],
limit=int(kwargs.get('limit', 50)),
offset=int(kwargs.get('offset', 0)),
)
return Response(
json.dumps({'data': commissions, 'count': len(commissions)}),
content_type='application/json',
status=200,
)
# File download endpoint
@http.route('/commission/report/<int:commission_id>', type='http', auth='user')
def download_report(self, commission_id, **kwargs):
commission = request.env['sale.commission'].browse(commission_id)
pdf = request.env['ir.actions.report']._render_qweb_pdf(
'my_module.commission_report', commission.ids
)
return request.make_response(
pdf[0],
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'attachment; filename=commission_{commission_id}.pdf'),
]
)
認証モード
| モード | 説明 | 使用例 |
|---|---|---|
| コード0 | ログインセッションが必要です | 内部ページ、ユーザーアクション |
| コード0 | セッションはオプションで、匿名にすることもできます。ウェブサイトのページ | |
| コード0 | セッションも環境もありません | ヘルスチェック、静的エンドポイント |
| コード0 | ヘッダーの API キー | 外部 REST 統合 |
7. セキュリティ: ACL とレコード ルール
アクセス制御リスト (ir.model.access.csv)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_commission_user,commission.user,model_sale_commission,sales_team.group_sale_salesman,1,0,0,0
access_commission_manager,commission.manager,model_sale_commission,sales_team.group_sale_manager,1,1,1,0
access_commission_admin,commission.admin,model_sale_commission,base.group_system,1,1,1,1
レコード ルール (security.xml)
<record id="commission_own_rule" model="ir.rule">
<field name="name">Salesperson sees own commissions</field>
<field name="model_id" ref="model_sale_commission"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
<field name="domain_force">[('salesperson_id', '=', user.id)]</field>
</record>
<record id="commission_company_rule" model="ir.rule">
<field name="name">Company-level commission isolation</field>
<field name="model_id" ref="model_sale_commission"/>
<field name="global" eval="True"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
</record>
8. デバッグ手法
Odoo シェルの使用
# Start interactive shell with full ORM access
python odoo-bin shell -d mydb --addons-path=addons,custom_addons
# In shell:
>>> orders = env['sale.order'].search([('state', '=', 'sale')], limit=5)
>>> for o in orders:
... print(f"{o.name}: {o.amount_total} - {o.partner_id.name}")
>>> env.cr.commit() # Commit changes made in shell
ロギング
import logging
_logger = logging.getLogger(__name__)
class SaleCommission(models.Model):
_name = 'sale.commission'
def action_confirm(self):
_logger.info("Confirming commission %s for user %s", self.name, self.env.user.name)
try:
self._validate_commission()
except Exception as e:
_logger.exception("Failed to confirm commission %s: %s", self.name, e)
raise
パフォーマンスプロファイリング
# Enable SQL logging in odoo.conf:
# log_level = debug_sql
# Or use the profiler decorator
from odoo.tools.profiler import profile
@profile('/tmp/commission_profile')
def action_bulk_calculate(self):
# This will generate a profile dump you can analyze
for order in self.env['sale.order'].search([]):
order._calculate_commission()
パフォーマンスに関する一般的な落とし穴:
- ループ内のリレーショナル フィールドへのアクセス (N+1 クエリ) - 代わりに
read()またはmapped()を使用します。 - ループ内で
search()を呼び出す - 検索をバッチ処理する - 頻繁にフィルターされる計算フィールドでは
store=Trueを使用しない sudo()を忘れると、すべての操作で追加のアクセス権チェックが発生します
9. 自動テスト
from odoo.tests.common import TransactionCase, HttpCase, tagged
from odoo.exceptions import ValidationError
@tagged('post_install', '-at_install')
class TestCommission(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env['res.partner'].create({'name': 'Test Customer'})
cls.user = cls.env['res.users'].create({
'name': 'Test Salesperson',
'login': 'test_sales',
'groups_id': [(4, cls.env.ref('sales_team.group_sale_salesman').id)],
})
cls.plan = cls.env['commission.plan'].create({
'name': 'Standard 10%',
'rate': 10.0,
})
def test_commission_calculation(self):
"""Commission amount should be 10% of order total."""
order = self.env['sale.order'].create({
'partner_id': self.partner.id,
'user_id': self.user.id,
'commission_plan_id': self.plan.id,
})
# Add order line...
self.assertAlmostEqual(order.commission_amount, 100.0, places=2)
def test_rate_constraint(self):
"""Rates above 100 should raise ValidationError."""
with self.assertRaises(ValidationError):
self.env['commission.plan'].create({
'name': 'Invalid Plan',
'rate': 150.0,
})
def test_state_machine(self):
"""Commission should follow draft -> confirmed -> paid flow."""
commission = self.env['sale.commission'].create({
'salesperson_id': self.user.id,
'amount': 500,
})
self.assertEqual(commission.state, 'draft')
commission.action_confirm()
self.assertEqual(commission.state, 'confirmed')
# HTTP/Tour tests
@tagged('post_install', '-at_install')
class TestCommissionTour(HttpCase):
def test_commission_dashboard(self):
"""Test that the commission dashboard loads correctly."""
self.start_tour('/web#action=commission.action_dashboard',
'commission_dashboard_tour', login='admin')
以下を使用してテストを実行します。
# Run all tests for a module
python odoo-bin -d testdb -i my_module --test-enable --stop-after-init
# Run specific test class
python odoo-bin -d testdb --test-tags /my_module:TestCommission --stop-after-init
# Run with coverage
coverage run odoo-bin -d testdb -i my_module --test-enable --stop-after-init
coverage report --include="custom_addons/my_module/*"
10. 高度なパターン
スケジュールされたアクション (Cron ジョブ)
<record id="ir_cron_monthly_commission" model="ir.cron">
<field name="name">Calculate Monthly Commissions</field>
<field name="model_id" ref="model_sale_commission"/>
<field name="state">code</field>
<field name="code">model._cron_calculate_monthly()</field>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field>
</record>
複数の会社のパターン
class CommissionPlan(models.Model):
_name = 'commission.plan'
company_id = fields.Many2one('res.company', required=True,
default=lambda self: self.env.company)
# Use sudo() carefully — only when crossing company boundaries is intended
def get_global_plans(self):
return self.sudo().search([('global_plan', '=', True)])
再利用可能なロジックのミックスイン パターン
class CommissionMixin(models.AbstractModel):
_name = 'commission.mixin'
_description = 'Adds commission tracking to any model'
commission_ids = fields.One2many('sale.commission', 'res_id')
commission_total = fields.Monetary(compute='_compute_commission_total', store=True)
@api.depends('commission_ids.amount')
def _compute_commission_total(self):
for record in self:
record.commission_total = sum(record.commission_ids.mapped('amount'))
# Apply to any model via class inheritance
class SaleOrder(models.Model):
_inherit = ['sale.order', 'commission.mixin']
実際の例: 完全なコミッションモジュール
本番環境に対応した完全なコミッション モジュールの例については、ECOSIRE のオープンソース Odoo モジュール にアクセスしてください。ここでは、このガイドで説明されているすべてのベスト プラクティスに従ってコネクタ、ユーティリティ、ビジネス モジュールが公開されています。
私たちの Odoo 開発チーム は、これらの正確なパターンを使用して 60 を超える Odoo モジュールを構築しました。単一フィールドの追加から完全な ERP 実装まで、カスタム Odoo Python 開発が必要な場合は、チームにお問い合わせ して無料相談を行ってください。
よくある質問
Odoo 18 にはどの Python バージョンが必要ですか?
Odoo 18 には Python 3.10 以降が必要で、Python 3.12 が推奨バージョンです。 Odoo 17 は Python 3.10 以降をサポートします。 Odoo バージョンに必要な正確な依存関係については、常に公式の required.txt を確認してください。
Odoo ORM クエリをデバッグするにはどうすればよいですか?
odoo.conf ファイルで log_level を debug_sql に設定するか、コマンド ラインで --log-sql を渡すことで SQL ログを有効にします。 self.env.cr.sql_log を使用して、コード内のクエリをトレースすることもできます。パフォーマンス プロファイリングには、odoo.tools.profiler の組み込み @profile デコレータを使用します。
モデルの拡張には _inherit または _inherits を使用する必要がありますか?
95% の確率で _inherit (クラス継承) を使用します。これにより、フィールドとメソッドが既存のモデルのテーブルに追加されます。 _inherits (委任) は、すべての hr.employee フィールドを継承する特殊な従業員タイプの作成など、別のモデルのフィールドに透過的にアクセスする別のモデルが必要な場合にのみ使用してください。
複数の会社のデータ分離はどのように処理すればよいですか?
self.env.company を返すデフォルトのラムダを使用して、company_id Many2one フィールドをモデルに追加します。次に、ドメイン [('company_id', 'in', company_ids)] を使用してグローバル ir.rule を作成します。これにより、ユーザーはアクセス権のある企業のレコードのみを参照できるようになります。これは、標準的な Odoo 複数企業パターンです。
@api.depends と @api.onchange の違いは何ですか?
@api.depends は、データベース内の指定されたフィールドが変更されたときにトリガーされ、保存された計算フィールドに使用されます。これは、UI とプログラムによる変更の両方で機能します。 @api.onchange は、ユーザーがフィールドを変更したときに UI でのみトリガーされ、デフォルトの設定または警告の表示に使用されます。データの整合性のために @api.depends を使用し、UX の向上のために @api.onchange を使用します。
Odoo モジュールを効果的にテストするにはどうすればよいですか?
ビジネス ロジックをテストする単体テストには TransactionCase を使用します。各テストはロールバックされるトランザクションで実行されます。コントローラーと Web ツアーのテストには HttpCase を使用します。 @tagged('post_install', '-at_install') でテストをタグ付けし、モジュールのインストール後に実行します。ビジネス ロジック メソッドのコード カバレッジが少なくとも 80% になることを目指します。
次のステップ
実稼働グレードの Odoo モジュールを構築するには、技術スキルとドメインの専門知識の両方が必要です。単純なクラスの継承から始めて、完全なカスタム モジュールに進みます。さらに読むには:
- Odoo カスタム モジュール開発ガイド — モジュール開発の高レベルの概要
- Odoo REST API チュートリアル — Odoo との外部統合
- ベスト ERP ソフトウェア 2026 — 市場における Odoo の立場を理解する
Odoo の専門開発者が必要ですか? ECOSIRE の Odoo 開発チーム は、40 か国の企業に 200 以上のカスタム モジュールを提供してきました。コミッションエンジンから完全なマーケットプレイスの統合まで、私たちのチームはこのガイドのすべてのパターンに従って本番環境に対応したコードを作成します。 無料相談を受ける。
執筆者
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.
関連記事
AI を活用した顧客セグメンテーション: RFM から予測クラスタリングまで
AI が顧客セグメンテーションを静的な RFM 分析から動的な予測クラスタリングにどのように変換するかを学びます。 Python、Odoo、および実際の ROI データを使用した実装ガイド。
サプライチェーン最適化のための AI: 可視性、予測、自動化
AI を使用してサプライ チェーンの運用を変革します。需要の検知、サプライヤーのリスク スコアリング、ルートの最適化、倉庫の自動化、混乱の予測などです。 2026年のガイド。
B2B E コマース戦略: 2026 年に卸売オンライン ビジネスを構築する
卸売価格設定、アカウント管理、クレジット条件、パンチアウト カタログ、Odoo B2B ポータル構成の戦略を使用して B2B e コマースをマスターします。