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 متدهای کمکی برای تلاش به تبدیل به نوع سازگار دارند:
to_date()به یکdatetime.dateتبدیل میکندto_datetime()به یکdatetime.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
Many2oneusing 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 بازمیگرداند، در غیر این صورت مانند=رفتار میکند)=like(وnot =like)field_exprرا با الگویvalueتطبیق میدهد. یک خط تیره زیر_در الگو به معنای (تطبیق با) هر یک کاراکتر است؛ علامت درصد%با هر رشتهای از صفر یا چند کاراکتر تطبیق مییابد.like(وnot like)field_exprرا با الگوی%value%تطبیق میدهد. مشابه=likeاماvalueرا قبل از تطبیق با '%' احاطه میکندilike(وnot ilike)likeغیرحساس به حروف=ilike(وnot =ilike)=likeغیرحساس به حروفin(وnot in)با هر یک از موارد
valueبرابر است،valueباید مجموعهای از موارد باشدchild_ofفرزند (نواده) یک رکورد
valueاست (مقدار میتواند یک مورد یا فهرستی از موارد باشد).معناشناسی مدل را در نظر میگیرد (یعنی پیروی از فیلد رابطهای که توسط
_parent_nameنامگذاری شده است).parent_ofوالد (نیاکان) یک رکورد
valueاست (مقدار میتواند یک مورد یا فهرستی از موارد باشد).معناشناسی مدل را در نظر میگیرد (یعنی پیروی از فیلد رابطهای که توسط
_parent_nameنامگذاری شده است).any(وnot 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
Unlink¶
اطلاعات رکورد(ست)¶
- 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 سه مکانیزم متفاوت برای توسعه مدلها به روش ماژولار فراهم میکند:
ایجاد یک مدل جدید از یک مدل موجود، افزودن اطلاعات جدید به کپی اما رها کردن ماژول اصلی به همان شکل
توسعه مدلهای تعریفشده در ماژولهای دیگر در محل، جایگزینی نسخه قبلی
تفویض برخی از فیلدهای مدل به رکوردهایی که در بر میگیرد
وراثت کلاسیک¶
هنگام استفاده از ویژگیهای _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
هشدار
_inheritsis more or less implemented, avoid it if you can;chained
_inheritsis 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")