Odoo Python Geliştirme: Yeni Başlayanlar ve Profesyoneller için Tam Kılavuz
2025 Python Geliştirici Anketine göre, kurumsal Python geliştiricilerinin %47'sinden fazlası en az bir ERP çerçevesiyle çalışıyor ve Odoo, açık kaynaklı ERP'ler arasında %68 pazar payıyla bu segmente hakim durumda. İster ilk modülünüzü oluşturuyor olun ister çok şirketli bir kurulumun mimarisini yapıyor olun, Odoo'nun Python katmanını anlamak geliştirebileceğiniz en etkili beceridir.
Bu kılavuz yüzeysel genel bakışların çok ötesine geçer. ORM'nin metasınıf büyüsünden gelişmiş denetleyici modellerine, üretim sorunlarında hata ayıklamaktan regresyonları gerçekten yakalayan testler yazmaya kadar Odoo'nun Python arka ucunun her katmanını inceleyeceğiz.
Temel Çıkarımlar
- Odoo'nun ORM'si, SQL şemalarını otomatik olarak oluşturan, önbelleğe almayı yöneten ve çok şirketli güvenliği yöneten metasınıflar üzerine kurulu tam bir nesne ilişkisel eşleyicidir; bunu derinlemesine anlamak, Odoo ile çalışmak ve onunla savaşmak arasındaki farktır.
- Beş alan kategorisi tüm veri modelleme ihtiyaçlarını karşılar: temel (Char, Integer, Float, Boolean, Text, Html, Date, Datetime, Binary, Selection), ilişkisel (Many2one, One2many, Many2many), hesaplanan, ilgili ve parasal alanlar.
- Üç miras mekanizması Odoo'nun herhangi bir bölümünü çatallanmadan genişletmenize olanak tanır: sınıf mirası (_inherit), prototip mirası (_inherit + _name) ve yetki devri (_inherits).
- Denetleyiciler, JSON-RPC ve HTTP uç nokta desenleri, kimlik doğrulama modları ve CORS yapılandırmasıyla dekoratörleri (@http.route) kullanarak HTTP'yi yönetir.
- TransactionCase ve HttpCase ile yapılan otomatik testler, gerilemeleri üretime ulaşmadan önce yakalar; iş mantığında %80'in üzerinde kapsama hedefleyin.
1. Geliştirme Ortamınızı Kurma
Kod yazmadan önce uygun bir geliştirme ortamına ihtiyacınız var. İşte 2026 için önerilen kurulum:
# 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
Modülleriniz için bir custom_addons dizini oluşturun. Her modül, Odoo'nun beklediği belirli bir yapıya sahip kendi alt dizininde bulunur.
2. Modül Mimarisine Derin Bakış
Her Odoo modülü, konfigürasyon üzerinden konvansiyon yapısını takip eder. Her bileşenin rolünü anlamak önemlidir:
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 Dosyası
__manifest__.py modülünüzün kalbidir. Her alan önemlidir:
# __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
},
}
Sürüm numaralandırma kuralı: {odoo_version}.{major}.{minor}.{patch} — ilk sayı her zaman Odoo serisiyle eşleşir.
3. ORM API: Tam Referans
Odoo'nun ORM'si çoğu geliştiricinin düşündüğünden çok daha güçlüdür. Şema oluşturmayı, önbelleğe almayı, erişim kontrolünü ve çok şirketli filtrelemeyi otomatik olarak yönetir.
Model Türleri
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 %')
Alan Türlerine Ayrıntılı Bakış
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 İşlemleri
# 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
Etki Alanı İfadeleri
Alan adları Odoo'nun sorgulama dilidir. Onlara hakim olun:
# 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. Miras Kalıpları
Odoo, kaynak kodunu değiştirmeden herhangi bir modülü genişletmenize olanak tanıyan üç miras mekanizması sağlar. Sınıf mirası, mevcut modellere alanlar ve yöntemler ekler. Prototip devralma, ayrı veritabanı tablolarıyla mevcut modelleri temel alan yeni modeller oluşturur. Yetki devralma, modelleri kompozisyon yoluyla birbirine bağlayarak her iki tabloda da kayıtları otomatik olarak oluşturur.
Sınıf Mirası (En Yaygın)
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,
})
Prototip Mirası
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')
Yetki Devri
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. İş Mantığı Kalıpları
Kısıtlamalar
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.'),
]
Değişiklik Yöntemleri
@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.',
}
}
Durum Makinesi ile İş Akışı
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. Denetleyiciler ve HTTP Uç Noktaları
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'),
]
)
Kimlik Doğrulama Modları
| Modu | Açıklama | Kullanım Örneği |
|---|---|---|
| KOD0 | Oturum açma oturumu gerektirir | Dahili sayfalar, kullanıcı işlemleri |
| KOD0 | Oturum isteğe bağlıdır, anonim olabilir | Web sitesi sayfaları |
| KOD0 | Oturum yok, ortam yok | Durum kontrolleri, statik uç noktalar |
| KOD0 | Başlıkta API anahtarı | Harici REST entegrasyonları |
7. Güvenlik: ACL ve Kayıt Kuralları
Erişim Kontrol Listeleri (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
Kayıt Kuralları (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. Hata Ayıklama Teknikleri
Odoo Kabuğunu Kullanma
# 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
Günlük kaydı
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
Performans Profili Oluşturma
# 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()
Genel performans tuzakları:
- Döngülerdeki ilişkisel alanlara erişim (N+1 sorgu) — bunun yerine
read()veyamapped()kullanın - Döngüler içinde
search()çağrılması — aramalarınızı toplu olarak yapın - Sık sık filtrelenen hesaplanan alanlarda
store=Truekullanılmaması sudo()öğesinin unutulması, her işlemde ekstra erişim hakkı kontrollerine neden olur
9. Otomatik Test
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')
Testleri şununla çalıştırın:
# 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. Gelişmiş Desenler
Planlanmış Eylemler (Cron İşleri)
<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>
Çok Şirketli Modeller
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)])
Yeniden Kullanılabilir Mantık için Karıştırma Modeli
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']
Gerçek Dünyadan Örnek: Komple Komisyon Modülü
Eksiksiz, üretime hazır bir devreye alma modülü örneği için bu kılavuzda ele alınan tüm en iyi uygulamaları takip eden bağlayıcıları, yardımcı programları ve iş modüllerini yayınladığımız ECOSIRE'ın açık kaynaklı Odoo modüllerini ziyaret edin.
Odoo geliştirme ekibimiz, tam olarak bu kalıpları kullanarak 60'ın üzerinde Odoo modülü oluşturdu. Tek bir alan eklemeden tam bir ERP uygulamasına kadar özel Odoo Python geliştirmeye ihtiyacınız varsa, ücretsiz danışmanlık için ekibimizle iletişime geçin.
Sıkça Sorulan Sorular
Odoo 18 hangi Python sürümünü gerektirir?
Odoo 18, Python 3.10 veya üzerini gerektirir; Python 3.12 önerilen sürümdür. Odoo 17, Python 3.10+ sürümünü destekler. Odoo sürümünüz için gereken tam bağımlılıkları öğrenmek için her zaman resmi gereksinimleri.txt dosyasını kontrol edin.
Odoo ORM sorgularında nasıl hata ayıklayabilirim?
Odoo.conf dosyanızda log_level'i debug_sql olarak ayarlayarak SQL günlük kaydını etkinleştirin veya komut satırında --log-sql'i iletin. Koddaki sorguları izlemek için self.env.cr.sql_log komutunu da kullanabilirsiniz. Performans profili oluşturmak için odoo.tools.profiler'daki yerleşik @profile dekoratörünü kullanın.
Modelleri genişletmek için _inherit mi yoksa _inherits mi kullanmalıyım?
%95 oranında _inherit (sınıf mirası) kullanın — mevcut bir modelin tablosuna alanlar ve yöntemler ekler. _inherits'i (yetki verme) yalnızca, tüm hr.employee alanlarını devralan özel bir çalışan türü oluşturmak gibi, başka bir modelin alanlarına şeffaf bir şekilde erişen ayrı bir modele ihtiyaç duyduğunuzda kullanın.
Çok şirketli veri izolasyonunu nasıl halledebilirim?
Self.env.company değerini döndüren varsayılan lambda ile modelinize bir şirket_id Many2one alanı ekleyin. Daha sonra [('company_id', 'in', Company_ids)] etki alanıyla global bir ir.rule oluşturun. Bu, kullanıcıların yalnızca erişim sahibi oldukları şirketlerin kayıtlarını görmesini sağlar; bu, Odoo'nun standart çok şirketli modelidir.
@api.depends ve @api.onchange arasındaki fark nedir?
@api.depends, veritabanında belirtilen alanlar değiştiğinde tetiklenir ve depolanan hesaplanan alanlar için kullanılır; hem kullanıcı arayüzü hem de programatik değişiklikler için çalışır. @api.onchange, kullanıcı bir alanı değiştirdiğinde yalnızca kullanıcı arayüzünde tetiklenir ve varsayılanları ayarlamak veya uyarıları göstermek için kullanılır. Veri bütünlüğü için @api.depends'u ve kullanıcı deneyimi iyileştirmeleri için @api.onchange'i kullanın.
Odoo modülümü etkili bir şekilde nasıl test edebilirim?
İş mantığını test eden birim testleri için TransactionCase'i kullanın; her test, geri alınan bir işlemde çalışır. Denetleyicileri ve web turlarını test etmek için HttpCase'i kullanın. Modül kurulumundan sonra çalıştırmak için testleri @tagged('post_install', '-at_install') ile etiketleyin. İş mantığı yöntemlerinde en az %80 kod kapsamı hedefleyin.
Sonraki Adımlar
Üretim düzeyinde Odoo modülleri oluşturmak hem teknik beceri hem de alan uzmanlığı gerektirir. Basit sınıf mirasıyla başlayın, ardından tam özel modüllere geçin. Daha fazla okumak için:
- Odoo Özel Modül Geliştirme Kılavuzu — Modül geliştirmeye üst düzey genel bakış
- Odoo REST API Eğitimi — Odoo ile harici entegrasyonlar
- En İyi ERP Yazılımı 2026 — Odoo'nun pazardaki konumunu anlamak
Uzman Odoo geliştiricilerine mi ihtiyacınız var? ECOSIRE'ın Odoo geliştirme ekibi 40 ülkedeki işletmeler için 200'den fazla özel modül sundu. Ekibimiz, komisyon motorlarından tam pazar entegrasyonlarına kadar bu kılavuzdaki her modeli takip ederek üretime hazır kod yazar. Ücretsiz danışmanlık alın.
Yazan
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.
İlgili Makaleler
Yapay Zeka Destekli Müşteri Segmentasyonu: RFM'den Tahmine Dayalı Kümelemeye
Yapay zekanın müşteri segmentasyonunu statik RFM analizinden dinamik tahmine dayalı kümelemeye nasıl dönüştürdüğünü öğrenin. Python, Odoo ve gerçek yatırım getirisi verilerini içeren uygulama kılavuzu.
Tedarik Zinciri Optimizasyonu için Yapay Zeka: Görünürlük, Tahmin ve Otomasyon
Yapay zeka ile tedarik zinciri operasyonlarını dönüştürün: talep algılama, tedarikçi risk puanlaması, rota optimizasyonu, depo otomasyonu ve kesinti tahmini. 2026 kılavuzu.
B2B E-ticaret Stratejisi: 2026'da Toptan Satış Çevrimiçi İş Kurun
Toptan satış fiyatlandırması, hesap yönetimi, kredi koşulları, delme katalogları ve Odoo B2B portal yapılandırması stratejileriyle B2B e-ticarette uzmanlaşın.