Напишіть імпортовані модулі¶
Важливо
Цей посібник передбачає знайомство з посібником Серверний фреймворк 101 та посібником Визначення даних модуля.
Хоча, як розробники, ми воліємо мати повну потужність Python для написання наших модулів, іноді це неможливо; зазвичай на керованих хостингових рішеннях, які не дозволяють розгортати власний код Python, як-от платформа Odoo.com.
Однак, гнучка природа Odoo дозволяє налаштування одразу після встановлення. Хоча багато можливостей можна знайти за допомогою Studio, також можна визначити моделі, поля та логіку у XML-файлах даних. Це спрощує розробку, підтримку та розгортання цих налаштувань.
У цьому посібнику ми дізнаємося, як визначати моделі, поля та логіку у файлах даних XML та об’єднувати їх у модуль. Їх іноді називають імпортованими модулями або модулями даних. Ми також побачимо обмеження цього підходу до розробки модулів.
Постановка проблеми¶
Як і в посібнику Серверний фреймворк 101, ми працюватимемо над концепціями нерухомості.
Наша мета - створити новий застосунок для керування нерухомістю подібним (хоча й простішим) способом до того, що описано в посібнику Серверний фреймворк 101. Ми визначимо моделі, поля та логіку у файлах даних XML, а не у файлах Python.
Після завершення цього посібника ми зможемо досягти наступного в нашому додатку:
Керування об’єктами нерухомості, що продаються
Опублікуйте ці властивості на вебсайті
Приймайте пропозиції онлайн на вебсайті
Виставити рахунок покупцю після продажу нерухомості
Структура модуля¶
Як і в будь-якому проєкті розробки, чітка структура полегшує керування та підтримку коду.
На відміну від стандартних модулів Odoo, які використовують файли як Python, так і XML, модулі даних використовують лише XML-файли. Тому очікується, що ваше дерево робіт виглядатиме приблизно так:
estate
├── actions
│ └── *.xml
├── models
│ └── *.xml
├── security
│ └── ir.model.access.csv
│ └── estate_security.xml
├── views
│ └── *.xml
├── __init__.py
└── __manifest__.py
Єдині файли Python, які у вас будуть, це файли __init__.py та __manifest__.py. Файл __manifest__.py буде таким самим, як і для будь-якого модуля Odoo, але також імпортуватиме його моделі у список data.
Не забудьте перерахувати файли в розділі data файлу __manifest__.py у порядку залежностей, зазвичай починаючи з файлів моделі.
Файл __init__.py порожній, але він потрібен для того, щоб Odoo розпізнав модуль, якщо ви коли-небудь захочете розгорнути свій модуль класичним способом (додавши його до шляху додатків). Він не є абсолютно необхідним для модулів, які будуть імпортовані, але його бажано зберегти.
Розгортання модуля¶
Щоб розгорнути модуль, вам потрібно створити zip-файл модуля та завантажити його до вашого екземпляра Odoo. Переконайтеся, що модуль base_import_module встановлено у вашому екземплярі, потім перейдіть до та завантажте zip-файл. Ви повинні бути в режим розробника, щоб побачити пункт меню Import Module.
Якщо ви зміните модуль, вам потрібно буде створити новий zip-файл і завантажити його знову, що призведе до повторного завантаження всіх даних у модулі. Однак зверніть увагу, що деякі операції неможливі, наприклад, зміна типу поля, яке ви створили раніше. Дані, створені попередніми версіями модуля (наприклад, видалені поля), не будуть автоматично видалені. Загалом, найпростіший спосіб вирішити цю проблему – почати з нової бази даних або видалити модуль перед завантаженням нової версії.
Під час завантаження модуля майстер прийматиме два варіанти:
Force init: якщо ваш модуль вже встановлено, і ви завантажуєте його знову; вибір цієї опції призведе до примусового оновлення всіх даних, позначених якnoupdate="1"у XML-файлах.Import demo data: пояснюється без потреби
Також можна розгорнути модуль за допомогою інструмента командного рядка odoo-bin за допомогою команди deploy:
$ odoo-bin deploy <path_to_your_module> https://<your_odoo_instance> --login <your_login> --password <your_password>
Ця команда також приймає опцію --force, яка еквівалентна опції Force init у майстрі.
Зверніть увагу, що користувач, якого ви використовуєте для розгортання модуля, повинен мати права доступу Administration/Settings.
Exercise
Створіть такі папки та файли:
/home/$USER/src/tutorials/estate/__init__.py/home/$USER/src/tutorials/estate/__manifest__.py
Файл
__manifest__.pyмає визначати лише назву та залежності наших модулів. Єдиним необхідним модулем фреймворку наразі єbase(таbase_import_module- хоча ваш модуль, строго кажучи, не залежить від нього, він вам потрібен для можливості імпорту вашого модуля).Створіть zip-файл вашого модуля та завантажте його до вашого екземпляра Odoo.
Моделі та основні поля¶
Як ви можете собі уявити, визначення моделей та полів у XML-файлах не таке просте, як у Python.
Оскільки файли даних зчитуються послідовно, елементи необхідно визначити в правильному порядку. Наприклад, перш ніж визначити поле для цієї моделі, необхідно визначити модель, а поля – перед тим, як додавати їх до представлення.
Крім того, XML набагато детальніший, ніж Python.
Почнемо з визначення простої моделі для представлення об’єкта нерухомості в каталозі models нашого модуля.
Моделі Odoo зберігаються в базі даних як записи ir.model. Як і будь-який інший запис, їх можна визначити у XML-файлах:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="model_real_estate_property" model="ir.model">
<field name="name">Real Estate Property</field>
<field name="model">x_estate.property</field>
</record>
</odoo>
Зверніть увагу, що всі моделі та поля, визначені у файлах даних, повинні мати префікс x_; це обов’язково та використовується для їх відрізнення від моделей та полів, визначених у файлах Python.
Як і для класичних моделей, визначених у Python, Odoo автоматично додасть кілька полів до моделі:
id(Id) Унікальний ідентифікатор запису моделі.create_date(Datetime) Дата створення запису.create_uid(Many2one) Користувач, який створив запис.write_date(Datetime) Дата останньої зміни запису.write_uid(Many2one) Користувач, який останнім чином змінював запис.
Ми також можемо додати кілька полів до нашої нової моделі. Додамо кілька простих полів, таких як назва (рядок), ціна продажу (число з плаваючою комою), опис (як html) та поштовий індекс (як char).
Як і у випадку з моделями, поля – це просто записи моделі ir.model.fields і можуть бути визначені як такі у файлах даних:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_name" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_name</field>
<field name="field_description">Name</field>
<field name="ttype">char</field>
<field name="required">True</field>
</record>
<record id="field_real_estate_property_selling_price" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_selling_price</field>
<field name="field_description">Selling Price</field>
<field name="ttype">float</field>
<field name="required">True</field>
</record>
<record id="field_real_estate_property_description" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_description</field>
<field name="field_description">Description</field>
<field name="ttype">html</field>
</record>
<record id="field_real_estate_property_postcode" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_postcode</field>
<field name="field_description">Postcode</field>
<field name="ttype">char</field>
</record>
</odoo>
Ви можете встановити різні атрибути для вашого нового поля. Для базових полів це включає:
name: технічна назва поля (має починатися зx_)field_description: мітка поляhelp: текст довідки для поля, що відображається в інтерфейсіttype: тип поля (наприклад,char,integer,float,htmlтощо)required: чи є поле обов’язковим чи ні (за замовчуванням:False)readonly: чи є поле доступним лише для читання чи ні (за замовчуванням:False)index: чи індексовано поле чи ні (за замовчуванням:False)copied: чи копіюється поле під час дублювання запису чи ні (за замовчуванням:Trueдля нереляційних необчислюваних полів,Falseдля реляційних та обчислюваних полів)translate: чи є поле перекладним чи ні (за замовчуванням:False)
Атрибути також доступні для керування очищенням HTML, а також для інших, більш розширених функцій; для повного списку зверніться до моделі ir.model.fields у базі даних, доступній у меню , або перегляньте визначення моделі ir.model.fields у модулі base.
Exercise
Додайте в таблицю такі основні поля:
Поле |
Тип |
Обов’язково |
|---|---|---|
x_date_availability |
Date |
|
x_expected_price |
Float |
True |
x_bedrooms |
Integer |
|
x_living_area |
Integer |
|
x_facades |
Integer |
|
x_garage |
Boolean |
|
x_garden |
Boolean |
|
x_garden_area |
Integer |
|
x_garden_orientation |
Вибір |
Поле x_garden_orientation повинно мати 4 можливі значення: „North“, „South“, „East“ та „West“. Список вибору потрібно створити, спочатку створивши запис ir.model.fields для самого поля, а потім створивши записи ir.model.fields.selection. Ці записи приймають три поля: field_id, name (назва в інтерфейсі користувача) та value (значення в базі даних). Також можна встановити поле sequence, яке контролює порядок, у якому вибрані елементи відображаються в інтерфейсі користувача (спочатку відображаються нижчі значення послідовності).
Значення по замовчуванню¶
У Python значення за замовчуванням можна встановити для полів за допомогою аргументу default в оголошенні поля. У модулях даних значення за замовчуванням встановлюються шляхом створення запису ir.default для кожного поля. Наприклад, можна встановити значення за замовчуванням для поля x_selling_price на 100000 для всіх властивостей, створивши наступний запис:
<odoo>
<!-- ...model definition from before... -->
<record id="default_real_estate_property_selling_price" model="ir.default">
<field name="field_id" ref="estate.field_real_estate_property_selling_price" />
<field name="json_value">100000</field>
</record>
</odoo>
Для отримання додаткової інформації зверніться до моделі ir.default у базі даних, доступній у меню , або перегляньте визначення моделі ir.default у модулі base.
Попередження
Ці значення за замовчуванням є статичними, але можуть бути встановлені компанією та/або користувачем за допомогою полів user_id та company_id запису ir.default. Це означає, що, наприклад, неможливо встановити динамічне значення за замовчуванням «сьогодні» для поля x_date_availability.
Безпека¶
Безпека в модулях даних така ж сама, як і для модулів Python, і її можна знайти в Розділ 4: Безпека – короткий вступ.
Зверніться до цього посібника для отримання детальної інформації.
Exercise
Створіть файл
ir.model.access.csvу відповідній папці та визначте його у файлі__manifest__.py.Надайте дозволи на читання, запис, створення та від’єднання групи
base.group_user.
Порада
Попереджувальне повідомлення в журналі містить більшу частину рішення ;-)
Представлення¶
Представлення – це компоненти інтерфейсу користувача, які дозволяють користувачам взаємодіяти з даними. Вони визначені у XML-файлах і знаходяться в каталозі views вашого модуля.
Оскільки представлення (views) та дії (actions) вже визначені у Розділ 5: Нарешті, трохи інтерфейсу користувача для експериментів та Розділ 6: Основні представлення, ми не будемо вдаватися тут у подробиці.
Exercise
Додайте базовий інтерфейс користувача до модуля estate.
Додайте базовий інтерфейс користувача до модуля estate, щоб дозволити користувачам переглядати, створювати, редагувати та видаляти об’єкти нерухомості.
Створіть дію для моделі
x_estate.property.Створіть деревоподібне представлення для моделі
x_estate.property.Створіть представлення форми для моделі
x_estate.property.Додайте представлення до дії.
Додайте пункт меню до головного меню, щоб дозволити користувачам отримати доступ до дії.
Зв’язки¶
Справжня сила реляційних систем, таких як Odoo, полягає у можливості пов’язувати записи між собою. У звичайному модулі Python можна було б визначити нові поля в моделі, щоб зв’язати її з іншими моделями в одному рядку коду. У модулі даних це все ще можливо, але вимагає трохи більше роботи, оскільки ми не можемо використовувати той самий синтаксис, що й у Python.
Як і в Розділ 7: Зв’язки між моделями, ми додамо деякі зв’язки до нашого модуля estate. Ми додамо посилання на:
клієнт, який придбав нерухомість
агент з нерухомості, який продав нерухомість
тип нерухомості: будинок, квартира, пентхаус, замок…
перелік тегів, що характеризують нерухомість: затишно, з ремонтом…
перелік отриманих пропозицій
Багато до одного¶
Багато-до-одного – це просте посилання на інший об’єкт. Наприклад, щоб визначити посилання на res.partner, ми можемо визначити нове поле в нашій моделі:
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_partner_id" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_partner_id</field>
<field name="field_description">Customer</field>
<field name="ttype">many2one</field>
<field name="relation">res.partner</field>
</record>
</odoo>
У випадку полів багато до одного можна встановити кілька атрибутів для деталізації зв’язку:
relation: назва моделі, з якою потрібно зв’язати (обов’язково)ondelete: дія, яку потрібно виконати під час видалення запису (за замовчуванням:set null)domain: фільтр домену, який застосовується до зв’язку
Exercise
Створіть нову модель
x_estate.property.typeз такими полями:Поле
Тип
Обов’язково
nameChar
True
Додайте дію, список та пункт меню для моделі
x_estate.property.type.Додайте права доступу до моделі
x_estate.property.type, щоб надати доступ користувачам.Створіть такі поля в моделі
x_estate.property:Поле
Тип
Обов’язково
x_property_type_idMany2one (
x_estate.property.type)True
x_partner_id(buyer)Many2one (
res.partner)x_user_id(salesperson)Many2one (
res.users)Включіть нові поля у представлення форми моделі
x_estate.property.
Many-to-many¶
Зв’язок many-to-many – це зв’язок до списку об’єктів. У нашому прикладі ми визначимо відношення many-to-many до нової моделі x_estate.property.tag. Цей тег представляє характеристику нерухомості, наприклад: відремонтований, затишний тощо.
Властивість може мати багато тегів, а тег може бути призначений багатьом властивостям – це типовий зв’язок many-to-many.
Поля many-to-many визначаються так само, як і поля many-to-one, але з параметром ttype, встановленим на many2many. Атрибут relation також встановлюється на назву моделі, з якою потрібно зв’язати. Для керування відношенням можна встановити інші атрибути:
relation_table: назва таблиці, яку слід використовувати для зв’язкуcolumn1таcolumn2: назви стовпців, які слід використовувати для зв’язку
Ці атрибути є необов’язковими та зазвичай їх слід вказувати лише тоді, коли між двома моделями є кілька полів типу many-to-many, щоб уникнути конфліктів; у більшості випадків Odoo ORM зможе визначити правильну таблицю зв’язків та стовпці для використання.
Exercise
Створіть нову модель
x_estate.property.tagз такими полями:Поле
Тип
Обов’язково
nameChar
True
Додайте дію, список та пункт меню для моделі
x_estate.property.tag.Додайте права доступу до моделі
x_estate.property.tag, щоб надати доступ користувачам.Створіть такі поля в моделі
x_estate.property:Поле
Тип
x_property_tag_idsMany2many (
x_estate.property.tag)Включіть нове поле у представленні форми моделі
x_estate.property.
One-to-many¶
Зв’язок one-to-many – це зв’язок до списку об’єктів. У нашому прикладі ми визначимо відношення one-to-many до нової моделі x_estate.property.offer. Ця пропозиція представляє собою пропозицію, зроблену клієнтом, щодо купівлі нерухомості.
Поля one-to-many визначаються так само, як і поля many-to-one, але з параметром ttype, встановленим на one2many. Атрибут relation також встановлюється на назву моделі, з якою потрібно зв’язати. Для керування відношенням необхідно встановити інший атрибут:
relation_field: назва поля у пов’язаній моделі, яке містить посилання на батьківську модель (поле many-to-one). Використовується для зв’язування двох моделей разом.
Exercise
Створіть нову модель
x_estate.property.offerз такими полями:Поле
Тип
Обов’язково
Значення
x_priceFloat
True
x_statusВибір
Accepted, Refused
x_partner_idMany2one (
res.partner)True
x_property_idMany2one (
x_estate.property)True
Додайте права доступу до моделі
x_estate.property.offer, щоб надати доступ користувачам.- Створіть деревоподібне представлення та представлення форми з полями price, partner_id та status.Не потрібно створювати дію чи меню.
Додайте поле
x_offer_idsдо вашої моделіx_estate.propertyта у її форматі.
Обчислювані поля¶
Обчислювані поля є основною концепцією в Odoo та використовуються для визначення полів, які обчислюються на основі інших полів. Це корисно для полів, що походять від інших полів, наприклад, сума підзаписів (підсумовування ціни всіх товарів у замовленні на продаж).
Посилання: документацію, пов’язану з цією темою, можна знайти в Обчислювані поля.
Модулі даних можуть визначати обчислювані поля будь-якого типу, але їхня кількість досить обмежена порівняно з модулями Python. Дійсно, оскільки модулі даних призначені для розгортання на системах, які не дозволяють виконання довільного коду, дозволений код Python дуже обмежений.
Примітка
Весь код Python, написаний для модулів даних, виконується в ізольованому середовищі, яке обмежує операції, які можна виконувати. Наприклад, ви не можете імпортувати бібліотеки, не можете отримати доступ до жодних файлів ОС і навіть не можете друкувати в консоль. Деякі утиліти надаються, але це залежить від типу ізольованого середовища, яке використовується.
У випадку методів обчислення, пісочниця дуже обмежена та надає лише мінімальний мінімум утиліт для виконання коду. Окрім вбудованих модулів Python, ви також маєте доступ до модулів datetime, dateutil та time (наприклад, для допомоги з обчисленням дат).
Також зауважте, що «призначення крапкою» вимкнено в пісочниці, тому ви не можете записати property.x_total_area = 1 у методі compute. Вам потрібно використовувати доступ до словника: property['x_total_area'] = 1. Крапкова нотація для поля access працює нормально: property.x_garden_area поверне значення поля x_garden_area.
Раніше ми визначили два поля «площа» в нашій моделі x_estate.property: living_area та garden_area. Щоб визначити обчислюване поле в моделі, яке повертає суму двох площ, ми можемо додати наступний код до нашого модуля даних:
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_total_area" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_total_area</field>
<field name="field_description">Total Area</field>
<field name="ttype">float</field>
<field name="depends">x_living_area,x_garden_area</field>
<field name="compute"><![CDATA[
for property in self:
property['x_total_area'] = property.x_living_area + property.x_garden_area
]]>
</field>
</record>
</odoo>
Примітка
У той час як у діях сервера ви виконуєте ітерацію по змінній records, у випадку обчислюваного поля ви виконуєте ітерацію по змінній self, яка містить набір записів, для якого обчислюється поле.
Атрибут depends використовується для визначення полів, від яких залежить обчислюване поле, а атрибут compute використовується для визначення коду, який виконується для обчислення поля (за допомогою коду Python).
На відміну від модулів Python, обчислювані поля зберігаються за замовчуванням. Якщо ви не хочете, щоб обчислюване поле зберігалося (наприклад, з міркувань продуктивності або щоб уникнути роздування бази даних), ви можете встановити атрибут store у значення False. Те саме стосується readonly: якщо ви хочете, щоб ваше обчислюване поле не можна було редагувати, вам потрібно встановити атрибут readonly у значення True.
Розділ CDATA використовується для вказівки XML-парсерам, що вміст є рядком, а не XML; це запобігає спробам парсера інтерпретувати код Python як XML або додавати зайві пробіли тощо, коли код вставляється в базу даних під час встановлення модуля.
Exercise
Додайте обчислюване поле до моделі
x_estate.property, яке повертає суму полівx_living_areaтаx_garden_area, як показано вище.Включити поле у представленні форми моделі
x_estate.property.
Примітка
На відміну від модулів Python, неможливо визначити інверсний метод або метод пошуку для обчислюваних полів.
Код та бізнес-логіка¶
Дії сервера¶
У модулі Python ви можете вільно визначати будь-який метод у вашій моделі. Одним із поширених шаблонів використання є додавання так званих методів «дії» до вашої моделі, а потім прив’язування цих методів до кнопок в інтерфейсі користувача (наприклад, для підтвердження цінової пропозиції, публікації рахунку-фактури тощо).
У модулі даних ви можете досягти того ж ефекту, визначивши Дії сервера, пов’язаний з вашою моделлю. Дії сервера представляють собою фрагменти логіки, які динамічно виконуються на сервері. Ці дії можна налаштувати вручну в базі даних безпосередньо через меню і вони можуть бути різних типів; у нашому випадку ми використовуватимемо тип code, який дозволяє нам запускати будь-який код Python в ізольованому середовищі.
Це середовище містить кілька утиліт, які допоможуть вам взаємодіяти з базою даних Odoo:
env: середовище записуmodel: модель записуuserтаuid: поточний користувач та його iddatetime,dateutil,timezoneтаtime: бібліотеки для допомоги в обчисленні дати/часуfloat_compare: корисна функція для порівняння двох значень типу «float» із заданою точністюb64encodeтаb64decode: допоміжні функції для кодування та декодування значень у base64Command: допоміжний клас для створення складних виразів та команд (див. класCommandу довіднику ORM)
Крім того, ви маєте доступ до набору записів, над яким виконується дія (зазвичай це один запис, коли дія виконується з представлення форми, та кілька записів, коли дія виконується з подапредставленняння списку), через змінні record та records.
Примітка
Якщо ваша дія має повернути клієнту результат дії (наприклад, перенаправити користувача до іншого представлення), ви можете призначити його змінній action в коді дії вашого сервера. Пісочниця коду перевірятиме змінні, визначені у вашому коді, після його виконання та автоматично повертатиме його, якщо виявить наявність змінної action.
Якщо модуль website встановлено, об’єкт request буде доступний у «пісочниці» коду, і ви зможете призначити об’єкт response змінній response, щоб повернути відповідь клієнту аналогічним чином. Більш детально це розглядається в розділі Контролери веб-сайту.
Наприклад, ми могли б визначити дію в моделі x_estate.property, яка встановлює поле x_status усіх її пропозицій на Refused:
<record id="action_x_estate_property_refuse_all_offers" model="ir.actions.server">
<field name="name">Refuse all offers</field>
<field name="model_id" ref="estate.model_real_estate_property"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for property in records:
property.x_offer_ids.write({'x_status': 'refused'})
]]></field>
</record>
Щоб включити цю дію як кнопку у вікні форми моделі x_estate.property, ми можемо додати наступний вузол button у заголовок нашого вікна форми:
<!-- form view definition from your code... -->
<header>
<button name="estate.action_x_estate_property_refuse_all_offers" type="action" string="Refuse all offers"/>
</header>
Також можна додати запис у значок шестерні (), щоб виконати цю дію (наприклад, щоб уникнути додавання кнопок до вже переповнених представлень). Для цього ви можете прив’язати дію сервера до моделі та певних типів представлень:
<record id="action_x_estate_property_refuse_all_offers" model="ir.actions.server">
<field name="name">Refuse all offers</field>
<field name="model_id" ref="estate.model_real_estate_property"/>
<field name="state">code</field>
<field name="binding_model_id" ref="estate.model_real_estate_property"/>
<field name="binding_view_types">tree,form</field>
<field name="code"><![CDATA[
for property in records:
property.x_offer_ids.write({'x_status': 'refused'})
]]></field>
</record>
Це зробить дію доступною у значку шестерні () моделі x_estate.property, у списку (коли один або декілька записів вибрано за допомогою прапорця) та у представленні форми.
Exercise
Додайте дію сервера до моделі
x_estate.property.offer, яка встановлює полеx_statusпропозиції наAcceptedта відповідно оновлює ціну продажу та покупця нерухомості, до якої додано пропозицію. Ця дія також повинна позначати всі інші пропозиції щодо тієї ж нерухомості якRefused.Додайте кнопку у вбудований список пропозицій, яка дозволяє виконати цю дію
Перевизначення моделей Python¶
Через елементи інтерфейсу користувача¶
На відміну від модулів Python, неможливо повністю перевизначити метод моделі Python.
Однак, у деяких випадках можливо замінити елементи інтерфейсу користувача, які викликають ці методи, та перехоплювати виклики цих методів у дії сервера.
Типовим прикладом може бути інтеграція з додатком Sales від Odoo. Уявімо, що ваш модуль нерухомості інтегрується з додатком Sales таким чином, що коли продається певний продукт (наприклад, цінова пропозиція на управління продажем нерухомості), ви хочете автоматично створювати новий запис про нерухомість у вашому модулі.
Щоб досягти цього, вам потрібно буде:
створити дію сервера, яка викликає оригінальний метод кнопки, та додати власну логіку до або після виклику цього методу
замінити кнопку у предствленні на власну кнопку, яка викликає дію сервера
<record id="view_sale_order_form" model="ir.ui.view">
<field name="name">sale.order.form.inherit.estate</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//button[@name='action_confirm'][@type='object']" position="attributes">
<attribute name="type">action</attribute>
<attribute name="name">estate.action_x_estate_property_create_from_sale_order</attribute>
</xpath>
<!-- since the button is present twice in the original view, we need to replace it twice -->
<xpath expr="//button[@name='action_confirm'][@type='object']" position="attributes">
<attribute name="type">action</attribute>
<attribute name="name">estate.action_x_estate_property_create_from_sale_order</attribute>
</xpath>
</field>
</record>
<record id="action_x_estate_property_create_from_sale_order" model="ir.actions.server">
<field name="name">Confirm and create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for order in records:
order.action_confirm()
property_type = env['x_estate.property.type'].sudo().search([('x_name', '=', 'Other')], limit=1)
property = env['x_estate.property'].sudo().create({
'x_name': order.name,
'x_expected_price': 0,
'x_selling_price': 0,
'x_sale_order_id': order.id,
'x_property_type_id': property_type.id,
})
]]></field>
</record>
За допомогою правил автоматизації¶
Правила автоматизації – це спосіб автоматичного виконання дій із записами в базі даних на основі певних тригерів, таких як зміни стану, додавання тегу тощо. Вони можуть бути корисними для прив’язки поведінки до подій життєвого циклу записів, наприклад, шляхом надсилання електронного листа, коли пропозицію прийнято.
Використання правил автоматизації для розширення стандартної поведінки може бути надійнішим, ніж підхід на основі інтерфейсу користувача, оскільки він також працюватиме, якщо подія життєвого циклу запускається іншим способом, ніж через кнопку (наприклад, через вебхук або прямий виклик методу; наприклад, коли цінова пропозиція підтверджена через портал або електронну комерцію). Однак їх трохи складніше налаштувати належним чином, оскільки потрібно переконатися, що автоматизація запускатиметься лише у потрібний момент, налаштувавши певні поля для спостереження тощо.
Документація: повнішу документацію з цієї теми можна знайти у Правила автоматизації.
Примітка
Правила автоматизації не є частиною модуля base; вони постачаються з модулем base_automation; тому, якщо ви визначаєте правила автоматизації у своєму модулі даних, вам потрібно переконатися, що base_automation є частиною залежностей вашого модуля.
Після встановлення, правила автоматизації керуються через меню .
Правила автоматизації особливо корисні для прив’язки модуля даних до існуючого стандартного модуля Odoo. Оскільки модулі даних не можуть перевизначати методи, прив’язка автоматизації до змін життєвого циклу стандартних моделей є поширеним способом розширення стандартних модулів.
Якби ми переписали наш приклад з попереднього розділу, використовуючи правила автоматизації, знадобилося б кілька змін:
дія сервера більше не повинна викликати оригінальний метод кнопки (натомість оригінальний метод ініціюватиме зміну, яка запустить правило автоматизації)
розширення представлення не потрібне
нам потрібно визначити правило автоматизації, щоб ініціювати дію сервера у разі відповідної події
<record id="action_x_estate_property_create_from_sale_order" model="ir.actions.server">
<field name="name">Create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for order in records:
property_type = env['x_estate.property.type'].sudo().search([('x_name', '=', 'Other')], limit=1)
property = env['x_estate.property'].sudo().create({
'x_name': order.name,
'x_expected_price': 0,
'x_selling_price': 0,
'x_sale_order_id': order.id,
'x_property_type_id': property_type.id,
})
]]></field>
</record>
<record id="automation_rule_x_estate_property_create_from_sale_order" model="base.automation">
<field name="name">Create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="trigger">on_state_set</field>
<field name="trg_selection_field_id" ref="sale.selection__sale_order__state__sale"/>
<field name="trigger_field_ids" eval="[(4, ref('sale.field_sale_order__state'))]"/>
<field name="action_server_ids" eval="[(4, ref('estate.action_x_estate_property_create_from_sale_order'))]"/>
</record>
Зверніть увагу, що XML ID для стандартних моделей, полів, значень вибору тощо Odoo можна знайти в самому екземплярі Odoo, перейшовши до відповідного запису в технічному меню та скориставшись пунктом меню View Metadata меню налагодження. XML ID для моделей – це просто назва моделі з крапками, заміненими підкресленнями, та префіксом model_ (наприклад, sale.model_sale_order – це sale.order, як визначено в модулі sale); XML ID для полів – це назва моделі з крапками, заміненими підкресленнями, та префіксом field_, назва моделі та назва поля (наприклад, sale.field_sale_order__name – це XML ID для поля name моделі sale.order, яка визначена в модулі sale).
Контролери веб-сайту¶
HTTP-контролери в Odoo зазвичай визначаються в каталозі controllers модуля. У модулях даних можна визначити дії сервера, які поводяться як контролери, якщо встановлено модуль веб-сайту.
Після встановлення модуля веб-сайту дії сервера можна позначити як Available on the website та вказати шлях (повний шлях завжди починається з префікса /actions, щоб уникнути колізій URL-адрес); глобальний об’єкт request стає доступним у локальній області дії сервера коду.
Об’єкт request надає кілька методів для доступу до тіла запиту:
request.get_http_params(): витягує пари ключ-значення з рядка запиту та форм, присутніх у тілі запиту (якapplication/x-www-form-urlencoded, так іmultipart/form-data).request.get_json_data(): витягує дані JSON з тіла запиту.
Оскільки неможливо повернути значення з дії сервера, щоб визначити відповідь, яку потрібно повернути, можна призначити змінній response об’єкт, подібний до відповіді, який буде автоматично повертатися на веб-сайт.
Ось приклад простого контролера веб-сайту, який повертатиме список властивостей при виклику URL-адреси /actions/estate:
<record id="server_action_estate_list" model="ir.actions.server">
<field name="name">Estate List Controller</field>
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="website_published">True</field>
<field name="website_path">estate</field>
<field name="state">code</field>
<field name="code"><![CDATA[
html = '<html><body><h1>Properties</h1><ul>'
for property in request.env['x_estate.property'].search([]):
html += f'<li>{property.x_name}</li>'
html += '</ul></body></html>'
response = request.make_response(html)
]]></field>
</record>
В об’єкті request доступно кілька корисних методів для полегшення генерації об’єкта відповіді:
request.render(template, qcontext=None, lazy=True, **kw)для рендерингу шаблону QWeb з використанням його xmlid; додаткові ключові аргументи пересилаються до об’єктаwerkzeug.Response(наприклад, для встановлення файлів cookie, заголовків тощо)request.redirect(location, code=303, local=True)для перенаправлення на іншу URL-адресу; аргументlocalвикористовується для визначення того, чи має перенаправлення бути відносно веб-сайту чи ні (за замовчуванням:True).request.not_found()для повернення виняткуwerkzeug.HTTPException, щоб сигналізувати веб-сайту про помилку 404.request.make_response(data, headers=None, cookies=None, status=200)для ручного створення об’єктаwerkzeug.Response; аргументstatus– це код стану HTTP, який потрібно повернути (за замовчуванням: 200).request.make_json_response(data, headers=None, cookies=None, status=200)для ручного створення JSON-відповіді; дані будуть серіалізовані за допомогою JSON за допомогою утилітиjson.dumps; це може бути корисним для налаштування зв’язку між серверами через виклики API.
Щоб дізнатися деталі реалізації або інші (менш поширені) методи, зверніться до реалізації об’єкта Request у модулі odoo.http.
Зверніть увагу, що питання безпеки залишаються на розсуд розробника (зазвичай через правила безпеки або за допомогою sudo для доступу до записів).
Примітка
Модель, що використовується в полі model_id дії сервера, має бути доступною для публічного користувача для виконання операції запису; інакше дія сервера поверне помилку 403. Спосіб уникнути надання доступу – це пов’язати дію сервера з моделлю, яка вже доступна публічному користувачеві. Типовим (хоч і дивним) прикладом є пов’язування дії сервера з моделлю ir.filters.
Exercise
Додайте JSON API до вашого модуля, щоб зовнішні сервіси могли отримувати список об’єктів нерухомості на продаж.
додати до моделі нове поле
x_api_published, щоб контролювати, чи публікуються властивості в API чи нідодати запис прав доступу, щоб дозволити загальнодоступним користувачам читати та записувати модель
заборонити будь-який запис від публічного користувача, додавши правило запису для операції запису з неможливим доменом (наприклад,
[('id', '=', False)])додати правило запису, щоб властивості, позначені як
x_api_published, могли бути прочитані публічним користувачемдодати дію сервера для повернення списку властивостей у форматі JSON, коли викликається URL-адреса
/actions/api/estate
Трохи JavaScript¶
Хоча імпортовані модулі не можуть містити файли Python, для файлів JavaScript такого обмеження не існує. Додавання файлів JavaScript до вашого імпортованого модуля відбувається точно так само, як і додавання їх до стандартного модуля Odoo.
Це означає, що імпортований модуль може містити нові компоненти полів або навіть абсолютно нові представлення.
Як приклад, додамо простий „tour“ до модуля Estate. Тури – це стандартний механізм в Odoo, який використовується для залучення користувачів, проводячи їх через ваш застосунок.
Дуже мінімалістичний тур з одним кроком можна додати, додавши цей файл до static/src/js/tour.js:
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add('estate_tour', {
url: "/web",
sequence: 170,
steps: () => [{
trigger: '.o_app[data-menu-xmlid="estate.menu_root"]',
content: 'Start selling your properties from this app!',
position: 'bottom',
}],
});
Потім вам потрібно включити файл до відповідного пакету у файлі маніфесту:
{
"name": "Real Estate",
# [...]
"assets": {
"web.assets_backend": [
"estate/static/src/js/tour.js",
],
},
}
Примітка
На відміну від звичайних модулів Python, розширення glob не підтримується в імпортованих модулях; тому вам потрібно перерахувати кожен файл, який ви хочете включити до модуля, окремо.