تطوير Odoo Python: الدليل الكامل للمبتدئين والمحترفين
وفقًا لاستطلاع مطوري Python لعام 2025، يعمل أكثر من 47% من مطوري Python للمؤسسات باستخدام إطار عمل واحد على الأقل لتخطيط موارد المؤسسات (ERP)، ويهيمن Odoo على هذا القطاع بحصة سوقية تبلغ 68% بين أنظمة تخطيط موارد المؤسسات (ERP) مفتوحة المصدر. سواء كنت تقوم ببناء الوحدة الأولى الخاصة بك أو تصميم نشر متعدد الشركات، فإن فهم طبقة Python الخاصة بـ Odoo هو المهارة الوحيدة الأكثر تأثيرًا التي يمكنك تطويرها.
يذهب هذا الدليل إلى ما هو أبعد من النظرات العامة على مستوى السطح. سنتعرف على كل طبقة من طبقات واجهة Python الخلفية لـ Odoo — بدءًا من سحر metaclass الخاص بـ ORM وحتى أنماط التحكم المتقدمة، ومن تصحيح أخطاء الإنتاج إلى كتابة الاختبارات التي تكتشف الانحدارات بالفعل.
الوجبات السريعة الرئيسية
- Odoo's ORM عبارة عن مخطط كامل للكائنات العلائقية مبني على الفئات التعريفية التي تنشئ مخططات SQL تلقائيًا، وتدير التخزين المؤقت، وتتعامل مع الأمان متعدد الشركات - إن فهمه بعمق هو الفرق بين العمل مع Odoo ومكافحته.
- خمس فئات حقول تغطي كل احتياجات نمذجة البيانات: الأساسية (Char، Integer، Float، Boolean، Text، Html، Date، Datetime، Binary، Selection)، العلائقية (Many2one، One2many، Many2many)، الحقول المحسوبة والمرتبطة والنقدية.
- ثلاث آليات للوراثة تتيح لك توسيع أي جزء من Odoo دون التفرع: وراثة الفئة (_inherit)، وراثة النموذج الأولي (_inherit + _name)، ووراثة التفويض (_inherits).
- تتعامل وحدات التحكم مع HTTP باستخدام أدوات الديكور (@http.route) مع أنماط نقطة نهاية JSON-RPC وHTTP، وأوضاع المصادقة، وتكوين CORS.
- الاختبار الآلي باستخدام 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: مرجع كامل
يعد ORM الخاص بـ Odoo أقوى بكثير مما يدركه معظم المطورين. فهو يتعامل مع إنشاء المخطط والتخزين المؤقت والتحكم في الوصول وتصفية الشركات المتعددة تلقائيًا.
أنواع النماذج
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)
العمليات الخام
# 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 ثلاث آليات توريث تتيح لك توسيع أي وحدة دون تعديل كود المصدر الخاص بها. يضيف وراثة الفئة الحقول والأساليب إلى النماذج الموجودة. يقوم وراثة النموذج الأولي بإنشاء نماذج جديدة تعتمد على النماذج الموجودة مع جداول قاعدة بيانات منفصلة. ويربط وراثة التفويض النماذج من خلال التكوين، مما يؤدي إلى إنشاء سجلات في كلا الجدولين تلقائيًا.
وراثة الفئة (الأكثر شيوعًا)
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'),
]
)
طرق المصادقة
| الوضع | الوصف | حالة الاستخدام |
|---|---|---|
auth='user' | يتطلب جلسة تسجيل الدخول | الصفحات الداخلية، إجراءات المستخدم |
auth='public' | الجلسة اختيارية، يمكن أن تكون مجهولة المصدر | صفحات الموقع |
auth='none' | لا جلسة ولا بيئة | فحوصات السلامة، نقاط النهاية الثابتة |
auth='api_key' | مفتاح 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 Shell
# 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. الأنماط المتقدمة
الإجراءات المجدولة (وظائف كرون)
<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)])
نمط Mixin للمنطق القابل لإعادة الاستخدام
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']
مثال من العالم الحقيقي: وحدة العمولة الكاملة
للحصول على مثال كامل لوحدة العمولة الجاهزة للإنتاج، تفضل بزيارة وحدات Odoo مفتوحة المصدر من ECOSIRE حيث ننشر الموصلات والأدوات المساعدة ووحدات الأعمال باتباع أفضل الممارسات التي يغطيها هذا الدليل.
قام فريق تطوير Odoo ببناء أكثر من 60 وحدة Odoo باستخدام هذه الأنماط الدقيقة. إذا كنت بحاجة إلى تطوير Odoo Python مخصص — بدءًا من إضافة حقل واحد وحتى التنفيذ الكامل لتخطيط موارد المؤسسات — اتصل بفريقنا للحصول على استشارة مجانية.
الأسئلة المتداولة
ما هو إصدار Python الذي يتطلبه Odoo 18؟
يتطلب Odoo 18 إصدار Python 3.10 أو أعلى، مع كون Python 3.12 هو الإصدار الموصى به. يدعم Odoo 17 إصدار Python 3.10+. تحقق دائمًا من ملف require.txt الرسمي لمعرفة التبعيات الدقيقة المطلوبة لإصدار Odoo الخاص بك.
كيف يمكنني تصحيح أخطاء استعلامات Odoo ORM؟
قم بتمكين تسجيل SQL عن طريق تعيين log_level على debug_sql في ملف odoo.conf، أو تمرير --log-sql في سطر الأوامر. يمكنك أيضًا استخدام self.env.cr.sql_log لتتبع الاستعلامات في التعليمات البرمجية. للحصول على ملفات تعريف الأداء، استخدم مصمم @profile المضمن من odoo.tools.profiler.
هل يجب علي استخدام _inherit أو _inherits لتوسيع النماذج؟
استخدم _inherit (وراثة الفئة) 95% من الوقت — فهو يضيف الحقول والأساليب إلى جدول نموذج موجود. استخدم _inherits (التفويض) فقط عندما تحتاج إلى نموذج منفصل يصل بشفافية إلى حقول نموذج آخر، مثل إنشاء نوع موظف متخصص يرث جميع حقول hr.employee.
كيف أتعامل مع عزل بيانات الشركات المتعددة؟
أضف حقل Company_id Many2one إلى النموذج الخاص بك مع إرجاع lambda الافتراضي self.env.company. ثم أنشئ قاعدة ir.rule عامة بالمجال [('company_id', 'in',company_ids)]. ويضمن ذلك للمستخدمين رؤية السجلات من الشركات التي يمكنهم الوصول إليها فقط، وهو نمط Odoo القياسي للشركات المتعددة.
ما الفرق بين @api.depends و@api.onchange؟
يتم تشغيل @api.depends عندما تتغير الحقول المحددة في قاعدة البيانات ويتم استخدامه للحقول المحسوبة المخزنة - وهو يعمل مع كل من تغييرات واجهة المستخدم والتغييرات البرمجية. يتم تشغيل @api.onchange فقط في واجهة المستخدم عندما يقوم المستخدم بتعديل حقل ويتم استخدامه لتعيين الإعدادات الافتراضية أو عرض التحذيرات. استخدم @api.depends لتكامل البيانات و@api.onchange لتحسينات تجربة المستخدم.
كيف يمكنني اختبار وحدة Odoo الخاصة بي بفعالية؟
استخدم TransactionCase لاختبارات الوحدة التي تختبر منطق الأعمال - يتم تشغيل كل اختبار في معاملة تم إرجاعها. استخدم HttpCase لاختبار وحدات التحكم وجولات الويب. اختبارات العلامات باستخدام @tagged('post_install', '-at_install') للتشغيل بعد تثبيت الوحدة. اهدف إلى تغطية التعليمات البرمجية بنسبة 80% على الأقل في أساليب منطق الأعمال.
الخطوات التالية
يتطلب إنشاء وحدات Odoo على مستوى الإنتاج مهارة فنية وخبرة في المجال. ابدأ بوراثة فئة بسيطة، ثم انتقل إلى الوحدات المخصصة الكاملة. لمزيد من القراءة:
- دليل تطوير الوحدة المخصصة لـ Odoo — نظرة عامة على المستوى الأعلى لتطوير الوحدة
- البرنامج التعليمي لـ Odoo REST API — عمليات التكامل الخارجية مع Odoo
- أفضل برنامج تخطيط موارد المؤسسات لعام 2026 — فهم مكانة Odoo في السوق
هل تحتاج إلى مطورين خبراء في Odoo؟ قدم فريق تطوير Odoo التابع لـ ECOSIRE أكثر من 200 وحدة مخصصة للشركات في 40 دولة. بدءًا من المحركات العمولة وحتى عمليات التكامل الكاملة للسوق، يقوم فريقنا بكتابة تعليمات برمجية جاهزة للإنتاج باتباع كل نمط في هذا الدليل. احصل على استشارة مجانية.
بقلم
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.
مقالات ذات صلة
تجزئة العملاء المدعومة بالذكاء الاصطناعي: من RFM إلى التجميع التنبؤي
تعرف على كيفية قيام الذكاء الاصطناعي بتحويل تجزئة العملاء من تحليل RFM الثابت إلى التجميع التنبؤي الديناميكي. دليل التنفيذ باستخدام Python وOdoo وبيانات عائد الاستثمار الحقيقي.
الذكاء الاصطناعي لتحسين سلسلة التوريد: الرؤية والتنبؤ والأتمتة
تحويل عمليات سلسلة التوريد باستخدام الذكاء الاصطناعي: استشعار الطلب، وتسجيل مخاطر الموردين، وتحسين المسار، وأتمتة المستودعات، والتنبؤ بالاضطرابات. دليل 2026.
استراتيجية التجارة الإلكترونية B2B: بناء الأعمال التجارية عبر الإنترنت بالجملة في عام 2026
إتقان التجارة الإلكترونية بين الشركات (B2B) مع إستراتيجيات لتسعير الجملة، وإدارة الحسابات، وشروط الائتمان، والكتالوجات النهائية، وتكوين بوابة Odoo B2B.