API ORM

مدل‌ها

فیلدهای مدل به‌عنوان ویژگی‌هایی روی خود مدل تعریف می‌شوند:

from odoo import models, fields
class AModel(models.Model):
    _name = 'a.model.name'

    field1 = fields.Char()

هشدار

این بدان معناست که نمی‌توانید یک فیلد و یک متد با نام یکسان تعریف کنید؛ آخرین مورد، موارد قبلی را به‌صورت خاموش بازنویسی می‌کند.

به‌صورت پیش‌فرض، برچسب فیلد (نام قابل‌مشاهده برای کاربر) نسخه‌ای از نام فیلد با حرف بزرگ است؛ این را می‌توان با پارامتر string بازنویسی کرد.

field2 = fields.Integer(string="Field Label")

برای فهرست انواع و پارامترهای فیلدها، مرجع فیلدها را ببینید.

مقادیر پیش‌فرض به‌صورت پارامترهایی روی فیلدها، یا به‌صورت یک مقدار تعریف می‌شوند:

name = fields.Char(default="a value")

یا به‌عنوان تابعی که برای محاسبه مقدار پیش‌فرض فراخوانی می‌شود و باید آن مقدار را بازگرداند:

def _default_name(self):
    return self.get_value()

name = fields.Char(default=lambda self: self._default_name())

API

AbstractModel

مدل

TransientModel

فیلدها

فیلدهای پایه

فیلدهای پیشرفته

فیلدهای Date(time)

Dates و Datetimes فیلدهایی بسیار مهم در هر نوع اپلیکیشن کسب‌وکاری هستند. کاربرد نادرست آن‌ها می‌تواند باگ‌هایی نامرئی اما دردناک ایجاد کند؛ این بخش هدف دارد دانش لازم برای پرهیز از کاربرد نادرست این فیلدها را در اختیار توسعه‌دهندگان Odoo قرار دهد.

هنگام انتساب یک مقدار به فیلد Date/Datetime، گزینه‌های زیر معتبر هستند:

  • یک شیء date یا datetime.

  • یک رشته در قالب صحیح سرور:

    • YYYY-MM-DD برای فیلدهای Date,

    • YYYY-MM-DD HH:MM:SS برای فیلدهای Datetime.

  • False یا None.

کلاس فیلدهای Date و Datetime متدهای کمکی برای تلاش به تبدیل به نوع سازگار دارند:

Example

برای تجزیه date/datetimeهای آمده از منابع خارجی:

fields.Date.to_date(self._context.get('date_from'))

بهترین شیوه‌های مقایسه Date / Datetime:

  • فیلدهای Date فقط می‌توانند با اشیای date مقایسه شوند.

  • فیلدهای Datetime فقط می‌توانند با اشیای datetime مقایسه شوند.

هشدار

رشته‌های نمایانگر date و datetime می‌توانند با یکدیگر مقایسه شوند، اما نتیجه ممکن است آنچه انتظار می‌رود نباشد، زیرا یک رشته datetime همواره از یک رشته date بزرگ‌تر خواهد بود، بنابراین این روش به‌شدت توصیه نمی‌شود.

عملیات متداول با تاریخ‌ها و datetimeها مانند جمع، تفریق یا واکشی ابتدا/انتهای یک دوره از طریق هر دوی Date و Datetime در دسترس هستند. این کمک‌کننده‌ها همچنین با import کردن odoo.tools.date_utils در دسترس‌اند.

توجه

مناطق زمانی

فیلدهای Datetime به‌صورت ستون‌های timestamp without timezone در پایگاه داده و در منطقه زمانی UTC ذخیره می‌شوند. این عمدی است، زیرا پایگاه داده Odoo را از منطقه زمانی سیستم سرور میزبان مستقل می‌کند. تبدیل منطقه زمانی به‌طور کامل توسط سمت کلاینت مدیریت می‌شود.

فیلدهای رابطه‌ای

فیلدهای شبه‌رابطه‌ای

فیلدهای محاسبه‌شده

فیلدها می‌توانند با استفاده از پارامتر compute محاسبه شوند (به‌جای آنکه مستقیماً از پایگاه داده خوانده شوند). باید مقدار محاسبه‌شده را به فیلد اختصاص دهد. اگر از مقادیر فیلدهای دیگر استفاده می‌کند، باید آن فیلدها را با استفاده از depends() مشخص کند.

from odoo import api
total = fields.Float(compute='_compute_total')

@api.depends('value', 'tax')
def _compute_total(self):
    for record in self:
        record.total = record.value + record.value * record.tax
  • وابستگی‌ها هنگام استفاده از زیرفیلدها می‌توانند مسیرهای نقطه‌ای باشند:

    @api.depends('line_ids.value')
    def _compute_total(self):
        for record in self:
            record.total = sum(line.value for line in record.line_ids)
    
  • فیلدهای محاسبه‌شده به‌صورت پیش‌فرض ذخیره نمی‌شوند؛ آن‌ها هنگام درخواست محاسبه و بازگردانده می‌شوند. تنظیم store=True آن‌ها را در پایگاه داده ذخیره می‌کند و به‌صورت خودکار جستجو و گروه‌بندی را فعال می‌کند. توجه داشته باشید که به‌صورت پیش‌فرض، compute_sudo=True روی فیلد تنظیم شده است.

  • جستجو روی یک فیلد محاسبه‌شده را نیز می‌توان با تنظیم پارامتر search فعال کرد. مقدار، نام متدی است که یک دامنه‌های جستجو بازمی‌گرداند.

    upper_name = field.Char(compute='_compute_upper', search='_search_upper')
    
    def _search_upper(self, operator, value):
        if operator == 'like':
            operator = 'ilike'
        return Domain('name', operator, value)
    
  • فیلدهای محاسبه‌شده به‌صورت پیش‌فرض فقط‌خواندنی هستند. برای اجازه تنظیم مقادیر روی یک فیلد محاسبه‌شده، از پارامتر inverse استفاده کنید. این نام تابعی است که محاسبه را معکوس کرده و فیلدهای مرتبط را تنظیم می‌کند:

    document = fields.Char(compute='_get_document', inverse='_set_document')
    
    def _get_document(self):
        for record in self:
            with open(record.get_document_path) as f:
                record.document = f.read()
    def _set_document(self):
        for record in self:
            if not record.document: continue
            with open(record.get_document_path()) as f:
                f.write(record.document)
    
  • چند فیلد را می‌توان به‌طور همزمان توسط یک متد یکسان محاسبه کرد؛ کافی است متد یکسانی روی همه فیلدها استفاده کرده و همه آن‌ها را تنظیم کنید:

    discount_value = fields.Float(compute='_apply_discount')
    total = fields.Float(compute='_apply_discount')
    
    @api.depends('value', 'discount')
    def _apply_discount(self):
        for record in self:
            # compute actual discount from discount percentage
            discount = record.value * record.discount
            record.discount_value = discount
            record.total = record.value - discount
    

هشدار

اگرچه استفاده از یک متد compute یکسان برای چندین فیلد ممکن است، انجام این کار برای متد inverse توصیه نمی‌شود.

در طول محاسبه inverse، همه فیلدهایی که از آن inverse استفاده می‌کنند محافظت می‌شوند، به این معنا که نمی‌توان آن‌ها را محاسبه کرد، حتی اگر مقدارشان در کش نباشد.

اگر به هر یک از آن فیلدها دسترسی پیدا شود و مقدارش در کش نباشد، ORM به‌سادگی یک مقدار پیش‌فرض False برای این فیلدها بازمی‌گرداند. این بدان معناست که مقدار فیلدهای inverse (به‌جز فیلدی که متد inverse را تریگر می‌کند) ممکن است مقدار درست خود را ندهند و این احتمالاً رفتار مورد انتظار متد inverse را خراب خواهد کرد.

فیلدهای خودکار

Model.id

شناسه field

اگر طول recordset فعلی 1 باشد، id رکورد یکتای داخل آن را بازمی‌گرداند.

در غیر این صورت خطا ایجاد می‌کند.

Model.display_name

نام field که به‌صورت پیش‌فرض در کلاینت وب نمایش داده می‌شود

به‌صورت پیش‌فرض، با فیلد مقدار _rec_name برابر است، اما رفتار را می‌توان با بازتعریف _compute_display_name سفارشی کرد

فیلدهای لاگ دسترسی

اگر _log_access فعال باشد، این فیلدها به‌صورت خودکار تنظیم و به‌روزرسانی می‌شوند. می‌توان آن را غیرفعال کرد تا از ایجاد یا به‌روزرسانی آن فیلدها روی جداولی که برایشان مفید نیستند، اجتناب شود.

به‌صورت پیش‌فرض، _log_access روی مقدار یکسان با _auto تنظیم می‌شود

Model.create_date

ذخیره می‌کند چه زمانی رکورد ایجاد شد، Datetime

Model.create_uid

ذخیره می‌کند چه کسی رکورد را ایجاد کرد، Many2one به res.users.

Model.write_date

ذخیره می‌کند چه زمانی رکورد آخرین بار به‌روزرسانی شد، Datetime

Model.write_uid

ذخیره می‌کند چه کسی آخرین بار رکورد را به‌روزرسانی کرد، Many2one به res.users.

هشدار

_log_access باید روی TransientModel فعال باشد.

نام‌های فیلد رزرو شده

تعداد کمی از نام‌های فیلد برای رفتارهای از پیش‌تعریف‌شده فراتر از فیلدهای خودکار رزرو شده‌اند. آن‌ها باید روی یک مدل تعریف شوند زمانی که رفتار مرتبط مطلوب است:

Model.name

مقدار پیش‌فرض برای _rec_name، که برای نمایش رکوردها در زمینه‌ای که "نام‌گذاری" نمایانگر ضروری است استفاده می‌شود.

Char

Model.active

نمایان‌بودن سراسری رکورد را تغییر می‌دهد؛ اگر active روی False تنظیم شود، رکورد در بیشتر جستجوها و فهرست‌ها نامرئی است.

Boolean

متدهای ویژه:

Model.state

مراحل چرخه عمر شیء، که توسط ویژگی states روی fields استفاده می‌شود.

Selection

Model.parent_id

default_value از _parent_name، که برای سازماندهی رکوردها در ساختار درختی استفاده می‌شود و عملگرهای child_of و parent_of را در دامنه‌ها فعال می‌کند.

Many2one

Model.parent_path

وقتی _parent_store روی True تنظیم شده باشد، برای ذخیره مقداری که ساختار درختی _parent_name را منعکس می‌کند، و برای بهینه‌سازی عملگرهای child_of و parent_of در دامنه‌های جستجو استفاده می‌شود. باید با index=True برای عملکرد صحیح اعلام شود.

Char

Model.company_id

نام فیلد اصلی که برای رفتار چندشرکتی Odoo استفاده می‌شود.

توسط :meth:~odoo.models._check_company برای بررسی سازگاری چندشرکتی استفاده می‌شود. تعیین می‌کند که آیا یک رکورد بین شرکت‌ها به اشتراک گذاشته می‌شود (بدون مقدار) یا فقط توسط کاربران یک شرکت معین قابل دسترسی است.

Many2one :type: res_company

محدودیت‌ها و ایندکس‌ها

مشابه فیلدها، می‌توانید Constraint, Index و UniqueIndex را اعلام کنید. نام ویژگی باید با _ شروع شود تا از تداخل نام با نام‌های فیلد جلوگیری شود.

می‌توانید پیام‌های خطا را سفارشی کنید. آن‌ها می‌توانند رشته باشند که ترجمه‌شان در جدول constraint بازتاب‌داده‌شده داخلی فراهم خواهد شد. در غیر این صورت، می‌توانند توابعی باشند که (env, diag) را به‌عنوان پارامتر می‌گیرند که به‌ترتیب نمایانگر environment و تشخیص‌های psycopg هستند.

Example

class AModel(models.Model):
    _name = 'a.model'
    _my_check = models.Constraint("CHECK (x > y)", "x > y is not true")
    _name_idx = models.Index("(last_name, first_name)")

مجموعه‌های رکورد

تعاملات با مدل‌ها و رکوردها از طریق recordsetها، یک مجموعه مرتب از رکوردهای یک مدل، انجام می‌شود.

هشدار

برخلاف آنچه نام آن اشاره می‌کند، در حال حاضر امکان دارد recordsetها حاوی موارد تکراری باشند. این ممکن است در آینده تغییر کند.

متدهای تعریف‌شده روی یک مدل، روی یک recordset اجرا می‌شوند و self آن‌ها یک recordset است:

class AModel(models.Model):
    _name = 'a.model'
    def a_method(self):
        # self can be anything between 0 records and all records in the
        # database
        self.do_operation()

پیمایش روی یک recordset مجموعه‌های جدیدی از یک رکورد منفرد ("singletons") را بازمی‌گرداند، شبیه به پیمایش روی یک رشته Python که رشته‌هایی از یک کاراکتر منفرد بازمی‌گرداند:

def do_operation(self):
    print(self) # => a.model(1, 2, 3, 4, 5)
    for record in self:
        print(record) # => a.model(1), then a.model(2), then a.model(3), ...

دسترسی به فیلد

recordsetها یک رابط "Active Record" فراهم می‌کنند: فیلدهای مدل را می‌توان به‌طور مستقیم از رکورد به‌عنوان ویژگی خواند و نوشت.

توجه

هنگام دسترسی به فیلدهای غیررابطه‌ای روی یک recordset که احتمالاً چند رکورد دارد، از mapped() استفاده کنید:

total_qty = sum(self.mapped('qty'))

مقادیر فیلد را می‌توان مانند آیتم‌های dict نیز دسترسی پیدا کرد، که برای نام‌های پویای فیلد ظریف‌تر و امن‌تر از getattr() است. تنظیم مقدار یک فیلد، یک به‌روزرسانی روی پایگاه داده را تریگر می‌کند:

>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob

هشدار

تلاش برای خواندن یک فیلد روی چند رکورد، برای فیلدهای غیررابطه‌ای یک خطا ایجاد می‌کند.

دسترسی به یک فیلد رابطه‌ای (Many2one, One2many, Many2many) همواره یک recordset بازمی‌گرداند، در صورتی که فیلد تنظیم نشده باشد، خالی خواهد بود.

کش رکورد و پیش‌واکشی

Odoo یک کش برای فیلدهای رکوردها نگهداری می‌کند تا هر دسترسی به فیلد یک درخواست پایگاه داده صادر نکند، که برای کارایی بسیار بد خواهد بود. مثال زیر فقط برای دستور اول از پایگاه داده پرس‌وجو می‌کند:

record.name             # first access reads value from database
record.name             # second access gets value from cache

برای جلوگیری از خواندن یک فیلد روی یک رکورد در یک زمان، Odoo رکوردها و فیلدها را با پیروی از برخی روش‌های اکتشافی برای کسب کارایی خوب، پیش‌واکشی می‌کند. وقتی یک فیلد باید روی یک رکورد معین خوانده شود، ORM در واقع آن فیلد را روی یک recordset بزرگ‌تر می‌خواند و مقادیر بازگشتی را در کش برای استفاده بعدی ذخیره می‌کند. recordset پیش‌واکشی‌شده معمولاً recordsetی است که رکورد از طریق پیمایش از آن می‌آید. به‌علاوه، همه فیلدهای ذخیره‌شده ساده (boolean, integer, float, char, text, date, datetime, selection, many2one) یکجا واکشی می‌شوند؛ آن‌ها با ستون‌های جدول مدل متناظر هستند و در همان پرس‌وجو به‌طور کارآمد واکشی می‌شوند.

مثال زیر را در نظر بگیرید، که در آن partners یک recordset از 1000 رکورد است. بدون پیش‌واکشی، حلقه 2000 پرس‌وجو به پایگاه داده ایجاد می‌کند. با پیش‌واکشی، فقط یک پرس‌وجو ایجاد می‌شود:

for partner in partners:
    print partner.name          # first pass prefetches 'name' and 'lang'
                                # (and other fields) on all 'partners'
    print partner.lang

پیش‌واکشی همچنین روی رکوردهای ثانویه کار می‌کند: وقتی فیلدهای رابطه‌ای خوانده می‌شوند، مقادیر آن‌ها (که رکورد هستند) برای پیش‌واکشی آینده مشترک می‌شوند. دسترسی به یکی از آن رکوردهای ثانویه، همه رکوردهای ثانویه از همان مدل را پیش‌واکشی می‌کند. این باعث می‌شود مثال زیر فقط دو پرس‌وجو تولید کند، یکی برای partnerها و یکی برای کشورها:

countries = set()
for partner in partners:
    country = partner.country_id        # first pass prefetches all partners
    countries.add(country.name)         # first pass prefetches all countries

همچنین ببینید

متدهای search_fetch() و fetch() می‌توانند برای پر کردن کش رکوردها استفاده شوند، معمولاً در مواردی که مکانیزم پیش‌واکشی به‌خوبی کار نمی‌کند.

دکوراتورهای متد

محیط

>>> records.env
<Environment object ...>
>>> records.env.uid
3
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...>

هنگام ایجاد یک recordset از یک recordset دیگر، environment به ارث برده می‌شود. می‌توان از environment برای دریافت یک recordset خالی در یک مدل دیگر و پرس‌وجو از آن مدل استفاده کرد:

>>> self.env['res.partner']
res.partner()
>>> self.env['res.partner'].search([('is_company', '=', True), ('customer', '=', True)])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)

برخی ویژگی‌های lazy برای دسترسی به داده‌های environment (زمینه‌ای) در دسترس هستند:

متدهای کاربردی محیط

تغییر محیط

اجرای SQL

ویژگی cr روی environmentها مکان‌نما برای تراکنش فعلی پایگاه داده است و امکان اجرای مستقیم SQL را فراهم می‌کند، چه برای پرس‌وجوهایی که بیان آن‌ها با استفاده از ORM دشوار است (مثلاً joinهای پیچیده) یا به دلایل کارایی:

self.env.cr.execute("some_sql", params)

هشدار

اجرای SQL خام، ORM را دور می‌زند و در نتیجه، قوانین امنیتی Odoo را نیز. لطفاً مطمئن شوید پرس‌وجوهای شما هنگام استفاده از ورودی کاربر sanitize شده‌اند و در صورتی که واقعاً نیاز به استفاده از پرس‌وجوهای SQL ندارید، استفاده از ابزارهای ORM را ترجیح دهید.

روش توصیه‌شده برای ساخت پرس‌وجوهای SQL استفاده از شیء wrapper است

یک نکته مهم برای دانستن درباره مدل‌ها این است که آن‌ها لزوماً به‌روزرسانی‌های پایگاه داده را فوراً انجام نمی‌دهند. در واقع، به دلایل کارایی، فریم‌ورک بازمحاسبه فیلدها را پس از تغییر رکوردها به تأخیر می‌اندازد. و برخی به‌روزرسانی‌های پایگاه داده نیز به تأخیر می‌افتند. بنابراین، قبل از پرس‌وجو از پایگاه داده، باید مطمئن شد که داده‌های مرتبط برای پرس‌وجو را در بر می‌گیرد. این عملیات flushing نامیده می‌شود و به‌روزرسانی‌های پایگاه داده مورد انتظار را انجام می‌دهد.

Example

# make sure that 'partner_id' is up-to-date in database
self.env['model'].flush_model(['partner_id'])

self.env.cr.execute(SQL("SELECT id FROM model WHERE partner_id IN %s", ids))
ids = [row[0] for row in self.env.cr.fetchall()]

قبل از هر پرس‌وجوی SQL، باید داده‌های مورد نیاز آن پرس‌وجو را flush کرد. سه سطح برای flushing وجود دارد، هرکدام با API خود. می‌توان همه چیز را، همه رکوردهای یک مدل را، یا برخی رکوردهای خاص را flush کرد. چون به تأخیر انداختن به‌روزرسانی‌ها به‌طور کلی کارایی را بهبود می‌بخشد، توصیه می‌کنیم هنگام flush کردن مشخص عمل کنید.

چون مدل‌ها از یک مکان‌نما استفاده می‌کنند و Environment کش‌های مختلفی را در بر دارد، این کش‌ها باید هنگام تغییر پایگاه داده در SQL خام بی‌اعتبار شوند، در غیر این صورت استفاده‌های بعدی از مدل‌ها ممکن است ناهماهنگ شوند. هنگام استفاده از CREATE, UPDATE یا DELETE در SQL، پاک کردن کش‌ها ضروری است، اما برای SELECT (که صرفاً پایگاه داده را می‌خواند) نه.

Example

# make sure 'state' is up-to-date in database
self.env['model'].flush_model(['state'])

self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s", ['new', 'old'])

# invalidate 'state' from the cache
self.env['model'].invalidate_model(['state'])

درست مانند flushing، می‌توان کل کش، کش همه رکوردهای یک مدل، یا کش رکوردهای خاص را بی‌اعتبار کرد. حتی می‌توان فیلدهای خاص را روی برخی رکوردها یا همه رکوردهای یک مدل بی‌اعتبار کرد. چون کش به‌طور کلی کارایی را بهبود می‌بخشد، توصیه می‌کنیم هنگام بی‌اعتبارسازی مشخص عمل کنید.

متدهای فوق کش‌ها و پایگاه داده را با یکدیگر سازگار نگه می‌دارند. با این حال، اگر وابستگی‌های فیلد محاسبه‌شده در پایگاه داده تغییر یافته باشد، باید مدل‌ها را مطلع کرد تا فیلدهای محاسبه‌شده بازمحاسبه شوند. تنها چیزی که فریم‌ورک نیاز به دانستن دارد این است که چه فیلدهایی روی چه رکوردهایی تغییر کرده‌اند.

Example

# make sure 'state' is up-to-date in database
self.env['model'].flush_model(['state'])

# use the RETURNING clause to retrieve which rows have changed
self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s RETURNING id", ['new', 'old'])
ids = [row[0] for row in self.env.cr.fetchall()]

# invalidate the cache, and notify the update to the framework
records = self.env['model'].browse(ids)
records.invalidate_recordset(['state'])
records.modified(['state'])

باید پیدا کنید کدام رکوردها تغییر کرده‌اند. راه‌های زیادی برای انجام این کار وجود دارد که احتمالاً شامل پرس‌وجوهای SQL اضافی است. در مثال فوق، ما از بند RETURNING از PostgreSQL بهره می‌گیریم تا اطلاعات را بدون پرس‌وجوی اضافی بازیابی کنیم. پس از سازگار کردن کش با بی‌اعتبارسازی، متد modified را روی رکوردهای تغییریافته با فیلدهایی که به‌روزرسانی شده‌اند فراخوانی کنید.

متدهای متداول ORM

ایجاد/به‌روزرسانی

جستجو/خواندن

فیلدها

دامنه‌های جستجو

یک دامنه جستجو یک گزاره منطقی مرتبه اول است که برای فیلتر کردن و جستجوی recordsetها استفاده می‌شود. شما شرایط ساده روی یک عبارت فیلد را با عملگرهای منطقی ترکیب می‌کنید.

Domain می‌تواند به‌عنوان یک builder برای دامنه‌ها استفاده شود.

# simple condition domains
d1 = Domain('name', '=', 'abc')
d2 = Domain('phone', 'like', '7620')

# combine domains
d3 = d1 & d2  # and
d4 = d1 | d2  # or
d5 = ~d1      # not

# combine and parse multiple domains (any iterable of domains)
Domain.AND([d1, d2, d3, ...])
Domain.OR([d4, d5, ...])

# constants
Domain.TRUE   # true domain
Domain.FALSE  # false domain

یک دامنه می‌تواند یک شرط ساده (field_expr, operator, value) باشد که در آن:

  • field_expr (str)

    a field name of the current model, or a relationship traversal through a Many2one using dot-notation e.g. 'street' or 'partner_id.country'. If the field is a date(time) field, you can also specify a part of the date using 'field_name.granularity'. The supported granularities are 'year_number', 'quarter_number', 'month_number', 'iso_week_number', 'day_of_week', 'day_of_month', 'day_of_year', 'hour_number', 'minute_number', 'second_number'. They all use an integer as value.

  • operator (str)

    عملگری که برای مقایسه field_expr با value استفاده می‌شود. عملگرهای معتبر عبارت‌اند از:

    =

    برابر با

    !=

    نابرابر با

    >

    بزرگ‌تر از

    >=

    بزرگ‌تر یا مساوی با

    <

    کوچک‌تر از

    <=

    کوچک‌تر یا مساوی با

    =?

    تنظیم‌نشده یا برابر با (اگر value یا None یا False باشد true بازمی‌گرداند، در غیر این صورت مانند = رفتار می‌کند)

    =likenot =like)

    field_expr را با الگوی value تطبیق می‌دهد. یک خط تیره زیر _ در الگو به معنای (تطبیق با) هر یک کاراکتر است؛ علامت درصد % با هر رشته‌ای از صفر یا چند کاراکتر تطبیق می‌یابد.

    likenot like)

    field_expr را با الگوی %value% تطبیق می‌دهد. مشابه =like اما value را قبل از تطبیق با '%' احاطه می‌کند

    ilikenot ilike)

    like غیرحساس به حروف

    =ilikenot =ilike)

    =like غیرحساس به حروف

    innot in)

    با هر یک از موارد value برابر است، value باید مجموعه‌ای از موارد باشد

    child_of

    فرزند (نواده) یک رکورد value است (مقدار می‌تواند یک مورد یا فهرستی از موارد باشد).

    معناشناسی مدل را در نظر می‌گیرد (یعنی پیروی از فیلد رابطه‌ای که توسط _parent_name نامگذاری شده است).

    parent_of

    والد (نیاکان) یک رکورد value است (مقدار می‌تواند یک مورد یا فهرستی از موارد باشد).

    معناشناسی مدل را در نظر می‌گیرد (یعنی پیروی از فیلد رابطه‌ای که توسط _parent_name نامگذاری شده است).

    anynot any)

    اگر هر رکوردی در پیمایش رابطه‌ای از طریق field_expr (Many2one, One2many, یا Many2many) دامنه value ارائه‌شده را برآورده کند، تطبیق می‌یابد. field_expr باید نام یک فیلد باشد.

    any!not any!)

    مانند any، اما بررسی‌های دسترسی را دور می‌زند.

  • value

    نوع متغیر، باید (از طریق operator) با فیلد نام‌گذاری‌شده قابل مقایسه باشد.

Example

برای جستجوی شرکای با نام ABC، با شماره تلفن یا موبایل حاوی 7620:

Domain('name', '=', 'ABC') & (
  Domain('phone', 'ilike', '7620') | Domain('mobile', 'ilike', '7620')
)

برای جستجوی سفارش‌های فروش قابل صدور فاکتور که حداقل یک خط با محصولی که موجود نیست داشته باشند:

Domain('invoice_status', '=', 'to invoice') \
  & Domain('order_line', 'any', Domain('product_id.qty_available', '<=', 0))

برای جستجوی همه شرکای متولد ماه فوریه:

Domain('birthday.month_number', '=', 2)

Domain can be used to serialize the domain as a list of simple conditions represented by 3-item tuple (or a list). Such a serialized form may be sometimes faster to read or write. Domain conditions can be combined using logical operators in a prefix notation. You can combine 2 domains using '&' (AND), '|' (OR) and you can negate 1 using '!' (NOT).

# parse a domain (from list to Domain)
domain = Domain([('name', '=', 'abc'), ('phone', 'like', '7620')])

# serialize domain as a list (from Domain to list)
domain_list = list(domain)
# will output:
# ['&', ('name', '=', 'abc'), ('phone', 'like', '7620')]

مقادیر زمان پویا

In the context of search domains, for date and datetime fields, the value can be a moment relative to now in the timezone of the user. A simple language is provided to specify these dates. It is a space-separated string of terms. The first term is optional and is "today" (at midnight) or "now". Then, each term starts with "+" (add), "-" (subtract) or "=" (set), followed by an integer and date unit or a lower-case weekday.

The date units are: "d" (days), "w" (weeks), "m" (months), "y" (years), "H" (hours), "M" (minutes), "S" (seconds). For weekdays, "+" and "-" mean next and previous weekday (unless we are already in that weekday) and "=" means in current week starting on Monday. When setting a date, the lower-units (hours, minutes and seconds) are set to 0.

Example

Domain('some_date', '<', 'now')  # now
Domain('some_date', '<', 'today')  # today at midnight
Domain('some_date', '<', '-3d +1H')  # now - 3 days + 1 hour
Domain('some_date', '<', '=3H')  # today at 3:00:00
Domain('some_date', '<', '=5d')  # 5th day of current month at midnight
Domain('some_date', '<', '=1m')  # January, same day of month at midnight
Domain('some_date', '>=', '=monday -1w')  # Monday of the previous week

اطلاعات رکورد(ست)

odoo.models.env

محیط recordset داده‌شده را بازمی‌گرداند.

Type

Environment

عملیات

recordsetها تغییرناپذیر هستند، اما مجموعه‌های یک مدل را می‌توان با استفاده از عملیات مجموعه‌ای مختلف ترکیب کرد و recordsetهای جدید بازگرداند.

  • record in set بازمی‌گرداند که آیا record (که باید یک recordset 1-عنصری باشد) در set حضور دارد یا خیر. record not in set عملیات معکوس است

  • set1 <= set2 و set1 < set2 بازمی‌گردانند که آیا set1 زیرمجموعه‌ای از set2 است (به‌ترتیب اکید)

  • set1 >= set2 و set1 > set2 بازمی‌گردانند که آیا set1 ابرمجموعه‌ای از set2 است (به‌ترتیب اکید)

  • set1 | set2 اجتماع دو recordset را بازمی‌گرداند، یک recordset جدید حاوی همه رکوردهای حاضر در هر منبع

  • set1 & set2 اشتراک دو recordset را بازمی‌گرداند، یک recordset جدید فقط حاوی رکوردهای حاضر در هر دو منبع

  • set1 - set2 یک recordset جدید بازمی‌گرداند که فقط حاوی رکوردهای set1 است که در set2 نیستند

Recordsets are iterable so the usual Python tools are available for transformation (map(), sorted(), ifilter(), ...) however these return either a list or an iterator, removing the ability to call methods on their result, or to use set operations.

بنابراین recordsetها عملیات زیر را فراهم می‌کنند که خود recordset را (در صورت امکان) بازمی‌گردانند:

فیلتر

نگاشت

توجه

از V13 به بعد، دسترسی به فیلد چندرابطه‌ای پشتیبانی می‌شود و مانند یک فراخوانی mapped کار می‌کند:

records.partner_id  # == records.mapped('partner_id')
records.partner_id.bank_ids  # == records.mapped('partner_id.bank_ids')
records.partner_id.mapped('name')  # == records.mapped('partner_id.name')

مرتب‌سازی

گروه‌بندی

وراثت و توسعه

Odoo سه مکانیزم متفاوت برای توسعه مدل‌ها به روش ماژولار فراهم می‌کند:

  • ایجاد یک مدل جدید از یک مدل موجود، افزودن اطلاعات جدید به کپی اما رها کردن ماژول اصلی به همان شکل

  • توسعه مدل‌های تعریف‌شده در ماژول‌های دیگر در محل، جایگزینی نسخه قبلی

  • تفویض برخی از فیلدهای مدل به رکوردهایی که در بر می‌گیرد

../../../_images/inheritance_methods1.png

وراثت کلاسیک

هنگام استفاده از ویژگی‌های _inherit و _name با هم، Odoo یک مدل جدید را با استفاده از مدل موجود (ارائه‌شده از طریق _inherit) به‌عنوان پایه ایجاد می‌کند. مدل جدید همه فیلدها، متدها و فرامتن‌اطلاعات (پیش‌فرض‌ها و غیره) را از پایه خود می‌گیرد.

class Inheritance0(models.Model):
    _name = 'inheritance.0'
    _description = 'Inheritance Zero'

    name = fields.Char()

    def call(self):
        return self.check("model 0")

    def check(self, s):
        return "This is {} record {}".format(s, self.name)

class Inheritance1(models.Model):
    _name = 'inheritance.1'
    _inherit = ['inheritance.0']
    _description = 'Inheritance One'

    def call(self):
        return self.check("model 1")

و استفاده از آن‌ها:

a = env['inheritance.0'].create({'name': 'A'})
b = env['inheritance.1'].create({'name': 'B'})

a.call()
b.call()

نتیجه می‌دهد:

"This is model 0 record A" "This is model 1 record B"

مدل دوم از متد check و فیلد name مدل اول ارث برده است، اما متد call را بازنویسی کرده، همان‌طور که هنگام استفاده از وراثت Python استاندارد رخ می‌دهد.

توسعه

هنگام استفاده از _inherit اما رها کردن _name، مدل جدید مدل موجود را جایگزین می‌کند، اساساً آن را در محل توسعه می‌دهد. این برای افزودن فیلدها یا متدهای جدید به مدل‌های موجود (که در ماژول‌های دیگر ایجاد شده‌اند)، یا برای سفارشی‌سازی یا پیکربندی مجدد آن‌ها (مثلاً تغییر ترتیب مرتب‌سازی پیش‌فرض آن‌ها) مفید است

class Extension0(models.Model):
    _name = 'extension.0'
    _description = 'Extension zero'

    name = fields.Char(default="A")

class Extension0(models.Model):
    _inherit = 'extension.0'

    description = fields.Char(default="Extended")
record = env['extension.0'].create({})
record.read()[0]

نتیجه می‌دهد:

{'name': "A", 'description': "Extended"}

هشدار

When _inherit is set to a string, then _name is set to the same value, unless _name is explicitly set.

توجه

It will also yield the various automatic fields unless they've been disabled

تفویض

The third inheritance mechanism provides more flexibility (it can be altered at runtime) but less power: using the _inherits a model delegates the lookup of any field not found on the current model to "children" models. The delegation is performed via Reference fields automatically set up on the parent model.

The main difference is in the meaning. When using Delegation, the model has one instead of is one, turning the relationship in a composition instead of inheritance

class Screen(models.Model):
    _name = 'delegation.screen'
    _description = 'Screen'

    size = fields.Float(string='Screen Size in inches')

class Keyboard(models.Model):
    _name = 'delegation.keyboard'
    _description = 'Keyboard'

    layout = fields.Char(string='Layout')

class Laptop(models.Model):
    _name = 'delegation.laptop'
    _description = 'Laptop'

    _inherits = {
        'delegation.screen': 'screen_id',
        'delegation.keyboard': 'keyboard_id',
    }

    name = fields.Char(string='Name')
    maker = fields.Char(string='Maker')

    # a Laptop has a screen
    screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade")
    # a Laptop has a keyboard
    keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")
record = env['delegation.laptop'].create({
    'screen_id': env['delegation.screen'].create({'size': 13.0}).id,
    'keyboard_id': env['delegation.keyboard'].create({'layout': 'QWERTY'}).id,
})
record.size
record.layout

منجر می‌شود به:

13.0
'QWERTY'

و امکان نوشتن مستقیم روی فیلد تفویض‌شده وجود دارد:

record.write({'size': 14.0})

هشدار

when using delegation inheritance, methods are not inherited, only fields

هشدار

  • _inherits is more or less implemented, avoid it if you can;

  • chained _inherits is essentially not implemented, we cannot guarantee anything on the final behavior.

تعریف افزایشی فیلدها

A field is defined as class attribute on a model class. If the model is extended, one can also extend the field definition by redefining a field with the same name and same type on the subclass. In that case, the attributes of the field are taken from the parent class and overridden by the ones given in subclasses.

For instance, the second class below only adds a tooltip on the field state

class FirstFoo(models.Model):
    state = fields.Selection([...], required=True)

class FirstFoo(models.Model):
    _inherit = ['first.foo']
    state = fields.Selection(help="Blah blah blah")

class WrongFirstFooClassName(models.Model):
    _name = 'first.foo'  # force the model name
    _inherit = ['first.foo']
    state = fields.Selection(help="Blah blah blah")

مدیریت خطا