Chapter 8: Computed Fields And Onchanges

The relations between models are a key component of any Odoo module. They are necessary for the modelization of any business case. However, we may want links between the fields within a given model. Sometimes the value of one field is determined from the values of other fields and other times we want to help the user with data entry.

Ці випадки підтверджуються концепціями обчислюваних полів і onchanges. Хоча цей розділ не є технічно складним, семантика обох понять дуже важлива. Це також перший раз, коли ми будемо писати логіку Python. Дотепер ми не писали нічого, крім визначень класів і оголошень полів.

Обчислювані поля

Посилання: документацію, пов’язану з цією темою, можна знайти в Обчислювані поля.

Примітка

Ціль: у кінці цього розділу:

  • У моделі власності слід обчислити загальну площу та найкращу пропозицію:

Обчислювальні поля
  • У моделі пропозиції власності дата дійсності повинна бути обчислена та може бути оновлена:

Обчислювальне поле з інверсією

У нашому модулі нерухомості ми визначили житлову площу, а також територію саду. Тоді природно визначити загальну площу як суму обох полів. Для цього ми будемо використовувати концепцію обчислюваного поля, тобто значення даного поля буде обчислюватись із значення інших полів.

До цього часу поля зберігалися безпосередньо в базі даних і витягувалися безпосередньо з бази даних. Поля також можна обчислити. У цьому випадку значення поля не витягується з бази даних, а обчислюється на льоту шляхом виклику методу моделі.

Щоб створити обчислюване поле, створіть поле та встановіть для його атрибута compute назву методу. Метод обчислення має встановити значення обчисленого поля для кожного запису в self.

By convention, compute methods are private, meaning that they cannot be called from the presentation tier, only from the business tier (see Розділ 1: Огляд архітектури). Private methods have a name starting with an underscore _.

Залежності

Значення обчисленого поля зазвичай залежить від значень інших полів у обчисленому записі. ORM очікує, що розробник вкаже ці залежності від методу обчислення за допомогою декоратора depends(). Наведені залежності використовуються ORM для ініціювання повторного обчислення поля кожного разу, коли деякі з його залежностей були змінені:

from odoo import api, fields, models

class TestComputed(models.Model):
    _name = "test.computed"

    total = fields.Float(compute="_compute_total")
    amount = fields.Float()

    @api.depends("amount")
    def _compute_total(self):
        for record in self:
            record.total = 2.0 * record.amount

Примітка

self - це колекція.

Об’єкт self є набором записів, тобто впорядкованою колекцією записів. Він підтримує стандартні операції Python над колекціями, напр. len(self) і iter(self), а також додаткові операції з наборами, такі як recs1 | recs2.

Ітерація над self дає записи один за одним, де кожен запис сам по собі є колекцією розміру 1. Ви можете отримати доступ до полів/призначити поля для окремих записів за допомогою нотації з крапками, наприклад. record.name.

Багато прикладів обчислюваних полів можна знайти в Odoo. Тут простий.

Exercise

Обчисліть загальну площу.

  • Додайте поле total_area до estate.property. Він визначається як сума living_area і garden_area.

  • Додайте поле у представленні форми, як показано на першому зображенні Цілі цього розділу.

Для реляційних полів можна використовувати шляхи через поле як залежність:

description = fields.Char(compute="_compute_description")
partner_id = fields.Many2one("res.partner")

@api.depends("partner_id.name")
def _compute_description(self):
    for record in self:
        record.description = "Test for partner %s" % record.partner_id.name

Приклад наведено з Many2one, але він дійсний для Many2many або One2many. Приклад можна знайти тут.

Давайте спробуємо це зробити в нашому модулі за допомогою наступної вправи!

Exercise

Обчисліть найкращу пропозицію.

  • Додайте поле best_price до estate.property. Вона визначається як найвища (тобто максимальна) price пропозиції.

  • Додайте поле до представлення форми, як показано на першому зображенні Цілі цього розділу.

Порада: ви можете спробувати використати метод mapped(). Перегляньте тут для простого прикладу.

Обернена функція

Можливо, ви помітили, що обчислювані поля за замовчуванням доступні лише для читання. Це очікувано, оскільки користувач не повинен встановлювати значення.

У деяких випадках може бути корисним мати можливість установити значення безпосередньо. У нашому прикладі з нерухомістю ми можемо визначити термін дії пропозиції та встановити дату дії. Ми хотіли б мати можливість установлювати тривалість або дату, щоб одне впливало на інше.

Для підтримки Odoo надає можливість використовувати функцію inverse:

from odoo import api, fields, models

class TestComputed(models.Model):
    _name = "test.computed"

    total = fields.Float(compute="_compute_total", inverse="_inverse_total")
    amount = fields.Float()

    @api.depends("amount")
    def _compute_total(self):
        for record in self:
            record.total = 2.0 * record.amount

    def _inverse_total(self):
        for record in self:
            record.amount = record.total / 2.0

Приклад можна знайти тут.

Метод обчислення встановлює поле, тоді як інверсний метод встановлює залежності поля.

Зауважте, що метод inverse викликається під час збереження запису, тоді як метод compute викликається при кожній зміні його залежностей.

Exercise

Обчисліть термін дії пропозицій.

  • Додайте наступні поля до моделі estate.property.offer:

Поле

Тип

За замовчуванням

термін дії

Integer

7

date_deadline

Дата

Де date_deadline - це обчислене поле, яке визначається як сума двох полів із пропозиції: create_date і validity. Визначте відповідну зворотну функцію, щоб користувач міг встановити або дату, або дійсність.

Порада: create_date заповнюється лише під час створення запису, тому вам знадобиться запасний варіант, щоб запобігти збою під час створення.

  • Додайте поля у представленні форми та списку, як показано на другому зображенні Цілі цього розділу.

Додаткова інформація

Обчислені поля за замовчуванням не зберігаються в базі даних. Тому неможливо шукати в обчисленому полі, якщо не визначено метод пошуку. Ця тема виходить за рамки цього тренінгу, тому ми не будемо її розглядати. Приклад можна знайти тут.

Іншим рішенням є збереження поля з атрибутом store=True. Хоча це зазвичай зручно, зверніть увагу на потенційне обчислювальне навантаження, додане до вашої моделі. Давайте повторно використаємо наш приклад:

description = fields.Char(compute="_compute_description", store=True)
partner_id = fields.Many2one("res.partner")

@api.depends("partner_id.name")
def _compute_description(self):
    for record in self:
        record.description = "Test for partner %s" % record.partner_id.name

Щоразу, коли name партнера змінюється, description автоматично перераховується для всіх записів, які посилаються на нього! Це може швидко стати непомірним для повторного обчислення, коли мільйони записів потребують повторного обчислення.

Також варто зазначити, що обчислюване поле може залежати від іншого обчислюваного поля. ORM достатньо розумна, щоб правильно перерахувати всі залежності в правильному порядку… але іноді ціною зниження продуктивності.

Загалом під час визначення обчислюваних полів завжди слід пам’ятати про продуктивність. Чим складніше поле для обчислення (наприклад, із великою кількістю залежностей або коли обчислюване поле залежить від інших обчислюваних полів), тим більше часу знадобиться для обчислення. Завжди виділяйте трохи часу, щоб заздалегідь оцінити вартість обчислюваного поля. У більшості випадків лише тоді, коли ваш код досягає робочого сервера, ви розумієте, що це уповільнює весь процес. Не круто :-(

Onchanges

Посилання: документацію щодо цієї теми можна знайти в onchange():

Примітка

Ціль: наприкінці цього розділу ввімкнення саду встановить область за замовчуванням 10 і орієнтацію на північ.

Onchange

У нашому модулі нерухомості ми також хочемо допомогти користувачеві з введенням даних. Коли встановлено поле „сад“, ми хочемо вказати значення за замовчуванням для площі саду, а також орієнтації. Крім того, якщо поле „сад“ не налаштовано, ми хочемо, щоб площа саду була скинута до нуля, а орієнтація була видалена. У цьому випадку значення певного поля змінює значення інших полів.

Механізм „onchange“ надає клієнтському інтерфейсу можливість оновлювати форму, не зберігаючи нічого в базі даних, коли користувач заповнює значення поля. Щоб досягти цього, ми визначаємо метод, у якому self представляє запис у поданні форми та прикрашаємо його onchange(), щоб визначити, яке поле його запускає. Будь-які зміни, які ви вносите в self, будуть відображені у формі:

from odoo import api, fields, models

class TestOnchange(models.Model):
    _name = "test.onchange"

    name = fields.Char(string="Name")
    description = fields.Char(string="Description")
    partner_id = fields.Many2one("res.partner", string="Partner")

    @api.onchange("partner_id")
    def _onchange_partner_id(self):
        self.name = "Document for %s" % (self.partner_id.name)
        self.description = "Default description for %s" % (self.partner_id.name)

У цьому прикладі зміна партнера також призведе до зміни назви та значень опису. Користувач вирішує, чи змінювати значення імені та опису згодом. Також зауважте, що ми не зациклюємося на self, це відбувається тому, що метод запускається лише у представленні форми, де self завжди є одним записом.

Exercise

Встановіть значення для площі саду та орієнтації.

Створіть onchange у моделі estate.property, щоб установити значення для площі саду (10) і орієнтації (північ), коли для саду встановлено значення True. Якщо не встановлено, очистити поля.

Додаткова інформація

Методи Onchanges також можуть повертати неблокуюче попередження (приклад).

Як їх використовувати?

Немає суворих правил використання обчислюваних полів і змін.

У багатьох випадках для досягнення того самого результату можна використовувати як обчислені поля, так і onchanges. Завжди віддавайте перевагу обчислюваним полям, оскільки вони також запускаються поза контекстом представлення форми. Ніколи не використовуйте onchange для додавання бізнес-логіки до вашої моделі. Це дуже погана ідея, оскільки onchanges не запускаються автоматично під час програмного створення запису; вони запускаються лише у представленні форми.

Зазвичай підводний камінь обчислених полів і onchanges полягає в спробі бути „надто розумним“, додаючи занадто багато логіки. Це може призвести до результату, протилежного очікуваному: кінцевий користувач буде збентежений усієї автоматизацією.

Обчислені поля, як правило, легше налагодити: таке поле встановлюється заданим методом, тому його легко відстежити, коли значення встановлено. З іншого боку, зміни можуть збити з пантелику: дуже важко визначити ступінь зміни. Оскільки кілька методів onchange можуть встановлювати однакові поля, легко стає важко відстежити, звідки походить значення.

Використовуючи збережені обчислювані поля, зверніть пильну увагу на залежності. Коли обчислювані поля залежать від інших обчислюваних полів, зміна значення може викликати велику кількість повторних обчислень. Це призводить до низької продуктивності.

In the next chapter, we’ll see how we can trigger some business logic when buttons are clicked.