Створення модуля¶
Небезпека
Цей підручник застарів. Натомість радимо прочитати Початок.
Попередження
Для цього посібника потрібно встановити Odoo
Запуск/Зупинка сервера Odoo¶
Odoo використовує архітектуру клієнт/сервер, у якій клієнти є веб-браузерами, які отримують доступ до сервера Odoo через RPC.
Бізнес-логіка та розширення зазвичай виконуються на стороні сервера, хоча клієнтські функції підтримки (наприклад, нове представлення даних, наприклад інтерактивні карти) можна додати до клієнта.
Щоб запустити сервер, просто викличте команду odoo-bin в оболонці, додавши повний шлях до файлу, якщо необхідно:
odoo-bin
Сервер зупиняється дворазовим натисканням Ctrl-C
з терміналу або завершенням відповідного процесу ОС.
Створити модуль Odoo¶
І серверні, і клієнтські розширення упаковані як модулі, які за бажанням завантажуються в базу даних.
Модулі Odoo можуть або додати нову бізнес-логіку до системи Odoo, або змінити та розширити існуючу бізнес-логіку: можна створити модуль, щоб додати правила бухгалтерського обліку вашої країни до загальної підтримки бухгалтерського обліку Odoo, тоді як наступний модуль додає підтримку візуалізації в реальному часі автобусного парку.
Таким чином, все в Odoo починається і закінчується модулями.
Композиція модуля¶
Модуль Odoo може містити декілька елементів:
- Бізнес-об’єкти
Ці ресурси, оголошені як класи Python, автоматично зберігаються Odoo на основі їхньої конфігурації
- Об’єкт представлення
Визначення відображення інтерфейсу бізнес-об’єктів
- Файли даних
Файли XML або CSV, що декларують метадані моделі:
представлення або звіти,
конфігураційні дані (параметризація модулів, правила безпеки),
демонстраційні дані
та більше
- Веб-контролери
Обробляти запити від веб-браузерів
- Статичні веб-дані
Зображення, CSS або файли JavaScript, які використовуються веб-інтерфейсом або веб-сайтом
Структура модуля¶
Кожен модуль є каталогом у каталозі модулів. Каталоги модулів вказуються за допомогою параметра --addons-path
.
Порада
більшість параметрів командного рядка також можна встановити за допомогою файлу конфігурації
Модуль Odoo оголошується його маніфест.
Модуль також є пакетом Python з файлом __init__.py
, який містить інструкції з імпорту для різних файлів Python у модуль.
Наприклад, якщо модуль має один файл mymodule.py
, __init__.py
може містити:
from . import mymodule
Odoo надає механізм, який допомагає налаштувати новий модуль, odoo-bin має підкоманду scaffold для створення порожнього модуля:
$ odoo-bin scaffold <module name> <where to put it>
Ця команда створює підкаталог для вашого модуля та автоматично створює групу стандартних файлів для модуля. Більшість із них просто містять коментований код або XML. Використання більшості цих файлів буде пояснено в цьому посібнику.
Exercise
Створення модулю
Використовуйте командний рядок вище, щоб створити порожній модуль Open Academy та встановити його в Odoo.
Об’єктно-реляційне відображення¶
Ключовим компонентом Odoo є рівень ORM. Цей рівень дозволяє уникнути необхідності писати більшість SQL вручну та забезпечує розширюваність і послуги безпеки2.
Бізнес-об’єкти оголошуються як класи Python, що розширюють Model
, який інтегрує їх в автоматизовану систему збереження.
Моделі можна налаштувати, встановивши ряд атрибутів при їх визначенні. Найважливішим атрибутом є _name
, який є обов’язковим і визначає назву моделі в системі Odoo. Ось мінімально повне визначення моделі:
from odoo import models
class MinimalModel(models.Model):
_name = 'test.model'
Поле моделі¶
Поля використовуються для визначення того, що і де може зберігати модель. Поля визначаються як атрибути в класі моделі:
from odoo import models, fields
class LessMinimalModel(models.Model):
_name = 'test.model2'
name = fields.Char()
Загальні атрибути¶
Подібно до самої моделі, її поля можна налаштувати, передавши атрибути конфігурації як параметри:
name = fields.Char(required=True)
Деякі атрибути доступні для всіх полів, ось найпоширеніші:
string
(unicode
, за умовчанням: назва поля)Мітка поля в інтерфейсі користувача (видима користувачами).
required
(bool
, за умовчанням:False
)Якщо
True
, поле не може бути порожнім, воно повинно або мати значення за замовчуванням, або завжди мати значення під час створення запису.help
(unicode
, за замовчуванням:''
)Довга форма, надає довідкову підказку користувачам в інтерфейсі користувача.
index
(bool
, за умовчанням:False
)Запитує, щоб Odoo створив індекс бази даних для стовпця.
Прості поля¶
Існує дві широкі категорії полів: «прості» поля, які є атомарними значеннями, що зберігаються безпосередньо в таблиці моделі, і «реляційні» поля, що зв’язують записи (однієї моделі або різних моделей).
Зарезервовані поля¶
Odoo створює кілька полів у всіх моделях1. Ці поля керуються системою, і в них не слід записувати. Їх можна прочитати, якщо це буде корисно або необхідно:
Спеціальні поля¶
За замовчуванням Odoo також вимагає поле name
на всіх моделях для різних дій відображення та пошуку. Поле, яке використовується для цих цілей, можна замінити, встановивши _rec_name
.
Exercise
Визначення моделі
Визначте нову модель даних Курс у модулі openacademy. Курс має назву та опис. Курси повинні мати назву.
Файли даних¶
Odoo - це система, яка сильно керується даними. Хоча поведінка налаштовується за допомогою коду Python, частина значення модуля міститься в даних, які він встановлює під час завантаження.
Порада
деякі модулі існують виключно для додавання даних до Odoo
Дані модуля оголошуються через файли даних, файли XML з елементами <record>
. Кожен елемент <record>
створює або оновлює запис бази даних.
<odoo>
<record model="{model name}" id="{record identifier}">
<field name="{a field name}">{a value}</field>
</record>
</odoo>
model
- це назва моделі Odoo для запису.id
є зовнішнім ідентифікатором, він дозволяє посилатися на запис (без необхідності знати його ідентифікатор у базі даних).Елементи
<field>
маютьname
, яке є назвою поля в моделі (наприклад,description
). Їхнє тіло є значенням поля.
Файли даних мають бути оголошені у файлі маніфесту для завантаження, їх можна оголошувати у списку 'data`
(завантажується завжди) або в 'demo'
списку (завантажується лише в режимі демонстрації) .
Exercise
Визначити демонстраційні дані
Створіть демонстраційні дані, заповнивши модель Курси кількома демонстраційними курсами.
Порада
Вміст файлів даних завантажується лише під час встановлення або оновлення модуля.
Після внесення деяких змін не забудьте використати odoo-bin -u openacademy, щоб зберегти зміни у базі даних.
Основні представлення¶
Представлення визначають спосіб відображення записів моделі. Кожен тип перегляду представляє режим візуалізації (список записів, графік їх агрегації, …). Представлення можна запитувати як узагальнено через їхній тип (наприклад, список партнерів), так і безпосередньо через їхні ідентифікатори. Для загальних запитів буде використано подання з правильним типом і найнижчим пріоритетом (тому подання з найнижчим пріоритетом кожного типу є представленням за замовчуванням для цього типу).
Наслідування представлення дозволяє змінювати представлення, оголошені в іншому місці (додавання або видалення вмісту).
Оголошення загального представлення¶
Представлення оголошується як запис моделі ir.ui.view
. Тип представлення визначається кореневим елементом поля arch
:
<record model="ir.ui.view" id="view_id">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
Небезпека
Вмістом представлення є XML.
Тому для правильного аналізу поле arch
має бути оголошено як type="xml"
.
Представлення списку¶
Представлення списку, які також називаються представленням списків, відображають записи в табличній формі.
Їх кореневим елементом є <tree>
. Найпростіша форма представлення списку просто містить список усіх полів для відображення в таблиці (кожне поле як стовпець):
<tree string="Idea list">
<field name="name"/>
<field name="inventor_id"/>
</tree>
Представлення форми¶
Форми використовуються для створення та редагування окремих записів.
Їх кореневим елементом є <form>
. Вони складаються з елементів структури високого рівня (групи, блокноти) та інтерактивних елементів (кнопок і полів):
<form string="Idea form">
<group colspan="4">
<group colspan="2" col="2">
<separator string="General stuff" colspan="2"/>
<field name="name"/>
<field name="inventor_id"/>
</group>
<group colspan="2" col="2">
<separator string="Dates" colspan="2"/>
<field name="active"/>
<field name="invent_date" readonly="1"/>
</group>
<notebook colspan="4">
<page string="Description">
<field name="description" nolabel="1"/>
</page>
</notebook>
<field name="state"/>
</group>
</form>
Exercise
Налаштуйте представлення форми за допомогою XML
Створіть власне представлення форми для об’єкта Курс. Відображені дані мають бути: назва та опис курсу.
Exercise
Блокноти
У вікні форми курсу розташуйте поле опису під вкладкою, щоб пізніше було легше додавати інші вкладки, що містять додаткову інформацію.
Представлення форм також можуть використовувати звичайний HTML для більш гнучких макетів:
<form string="Idea Form">
<header>
<button string="Confirm" type="object" name="action_confirm"
states="draft" class="oe_highlight" />
<button string="Mark as done" type="object" name="action_done"
states="confirmed" class="oe_highlight"/>
<button string="Reset to draft" type="object" name="action_draft"
states="confirmed,done" />
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Idea Name" />
<h1><field name="name" /></h1>
</div>
<separator string="General" colspan="2" />
<group colspan="2" col="2">
<field name="description" placeholder="Idea description..." />
</group>
</sheet>
</form>
Представлення пошуку¶
Представлення пошуку налаштовують поле пошуку, пов’язане з представленням списку (та іншими зведеними поданнями). Їх кореневим елементом є <search>
, і вони складаються з полів, які визначають, у яких полях можна здійснювати пошук:
<search>
<field name="name"/>
<field name="inventor_id"/>
</search>
Якщо для моделі не існує представлення пошуку, Odoo генерує таке, яке дозволяє здійснювати пошук лише за полем name
.
Exercise
Пошук курсів
Дозволити пошук курсів за назвою або описом.
Відносини між моделями¶
Запис з моделі може бути пов’язаний із записом з іншої моделі. Наприклад, запис замовлення на продаж пов’язаний із записом клієнта, який містить дані клієнта; це також пов’язано із записами рядка замовлення на продаж.
Exercise
Створити модель сеансу
Для модуля Open Academy ми розглядаємо модель сесій: сесія - це повторення курсу, який викладається в певний час для даної аудиторії.
Створіть модель для сеансів. Сеанс має назву, дату початку, тривалість і кількість місць. Додайте дію та пункт меню для їх відображення. Зробіть нову модель видимою за допомогою пункту меню.
Реляційні поля¶
Реляційні поля пов’язують записи однієї моделі (ієрархії) або між різними моделями.
Типи реляційних полів:
Many2one(other_model, ondelete='set null')
Просте посилання на інший об’єкт:
print(foo.other_id.name)
Перегляньте також
One2many(other_model, related_field)
Віртуальне відношення, зворотне до
Many2one
.One2many
поводиться як контейнер із записами, доступ до якого призводить до (можливо, порожнього) набору записів:for other in foo.other_ids: print(other.name)
Many2many(other_model)
Двонаправлений множинний зв’язок, будь-який запис на одній стороні може бути пов’язаний з будь-якою кількістю записів на іншій стороні. Поводиться як контейнер із записами, доступ до нього також призводить до можливо порожнього набору записів:
for other in foo.other_ids: print(other.name)
Exercise
Відношення Many2one
Використовуючи many2one, змініть моделі Курси і Сессії, щоб відобразити їх зв’язок з іншими моделями:
У курсу є відповідальний користувач; значення цього поля є записом вбудованої моделі
res.users
.Заняття має інструктора; значення цього поля є записом вбудованої моделі
res.partner
.Сесія пов’язана з курсом; значення цього поля є записом моделі
openacademy.course
і є обов’язковим.Адаптація представлень.
Exercise
Зворотні відносини one2many
Використовуючи зворотне реляційне поле one2many, змініть моделі, щоб відобразити зв’язок між курсами та сесіями.
Exercise
Множинні відносини many2many
Використовуючи реляційне поле many2many, змініть модель Сіссії, щоб пов’язати кожен сеанс з набором учасники. Учасники будуть представлені партнерськими записами, тому ми будемо мати відношення до вбудованої моделі res.partner
. Відповідно адаптуйте представлення.
Спадкування¶
Спадкування моделі¶
Odoo надає два механізми успадкування для розширення існуючої моделі модульним способом.
Перший механізм успадкування дозволяє модулям змінювати поведінку моделі, визначеної в іншому модулі, шляхом:
додати поля до моделі,
перевизначати визначення полів у моделі,
додати обмеження до моделі,
додати методи до моделі,
перевизначати існуючі методи в моделі.
Другий механізм успадкування (делегування) дозволяє зв’язати кожен запис моделі із записом у батьківській моделі та забезпечує прозорий доступ до полів батьківського запису.

Перегляньте також
_inherit
_inherits
Переглянути успадкування¶
Замість того, щоб змінювати існуючі представлення на місці (шляхом їх перезапису), Odoo забезпечує успадкування представлень, де дочірні представлення «розширень» застосовуються поверх кореневих представлень і можуть додавати або видаляти вміст із батьківського.
Представлення розширення посилається на свого батька за допомогою поля inherit_id
, і замість одного подання його поле arch
складається з будь-якої кількості елементів xpath
, які вибирають і змінюють вміст свого батьківського подання:
<!-- improved idea categories list -->
<record id="idea_category_list2" model="ir.ui.view">
<field name="name">id.category.list2</field>
<field name="model">idea.category</field>
<field name="inherit_id" ref="id_category_list"/>
<field name="arch" type="xml">
<!-- find field description and add the field
idea_ids after it -->
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" string="Number of ideas"/>
</xpath>
</field>
</record>
expr
Вираз XPath вибирає один елемент у батьківському представленні. Викликає помилку, якщо не відповідає жодному елементу або більше одного
position
Операція для застосування до відповідного елемента:
inside
додає тіло
xpath
в кінці відповідного елементаreplace
замінює відповідний елемент тілом
xpath
, замінюючи будь-який вузол$0
у новому тілі оригінальним елементомbefore
вставляє тіло
xpath
як однорідний перед відповідним елементомafter
вставляє тіло
xpaths
як однорідний елемент після відповідного елементаattributes
змінює атрибути відповідного елемента за допомогою спеціальних елементів
attribute
у тіліxpath
Порада
При зіставленні одного елемента атрибут position
можна встановити безпосередньо для елемента, який потрібно знайти. Обидва успадкування нижче дадуть однаковий результат.
<xpath expr="//field[@name='description']" position="after"> <field name="idea_ids" /> </xpath> <field name="description" position="after"> <field name="idea_ids" /> </field>
Exercise
Змінити наявний вміст
Використовуючи успадкування моделі, змініть існуючу модель Клієнт, додавши логічне поле
instructor
і поле many2many, яке відповідає зв’язку сеанс-клієнтВикористовуючи успадкування представлення, відобразіть ці поля в представленні форми клієнта
Домени¶
В Odoo Пошук доменів - це значення, які кодують умови для записів. Домен - це список критеріїв, які використовуються для вибору підмножини записів моделі. Кожен критерій є трійкою з назвою поля, оператором і значенням.
Наприклад, при використанні в моделі Продукт наступний домен вибирає всі послуги з ціною за одиницю понад 1000:
[('product_type', '=', 'service'), ('unit_price', '>', 1000)]
За замовчуванням критерії поєднуються з неявним AND. Логічні оператори &
(AND), |
(OR) і !
(NOT) можна використовувати для явного об’єднання критеріїв. Вони використовуються в позиції префікса (оператор вставляється перед його аргументами, а не між ними). Наприклад, щоб вибрати продукти, «які є послугами OR мають ціну за одиницю NOT від 1000 до 2000»:
['|',
('product_type', '=', 'service'),
'!', '&',
('unit_price', '>=', 1000),
('unit_price', '<', 2000)]
Параметр domain
можна додати до полів відношення, щоб обмежити дійсні записи для відношення під час спроби вибрати записи в інтерфейсі клієнта.
Exercise
Домени на реляційних полях
Під час вибору інструктора для Сеанс мають бути видимі лише інструктори (клієнти, для яких instructor
встановлено на True
).
Exercise
Більш складні домени
Створіть нові клієнтські категорії Вчитель / Рівень 1 і Вчитель / Рівень 2. Викладач заняття може бути інструктором або викладачем (будь-якого рівня).
Обчислені поля та значення за замовчуванням¶
До цього часу поля зберігалися безпосередньо в базі даних і витягувалися безпосередньо з бази даних. Поля також можна обчислити. У цьому випадку значення поля не витягується з бази даних, а обчислюється на льоту шляхом виклику методу моделі.
Щоб створити обчислюване поле, створіть поле та встановіть для його атрибута compute
назву методу. Метод обчислення має просто встановити значення поля для обчислення для кожного запису в self
.
Небезпека
self
- це колекція
Об’єкт self
є набором записів, тобто впорядкованою колекцією записів. Він підтримує стандартні операції Python над колекціями, як-от len(self)
і iter(self)
, а також додаткові операції з наборами, як-от recs1 + recs2
.
Ітерація над self
дає записи один за одним, де кожен запис сам по собі є колекцією розміру 1. Ви можете отримати доступ до полів/призначити поля для окремих записів за допомогою крапкової нотації, наприклад record.name
.
import random
from odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))
Залежності¶
Значення обчисленого поля зазвичай залежить від значень інших полів у обчисленому записі. ORM очікує, що розробник вкаже ці залежності від методу обчислення за допомогою декоратора depends()
. Наведені залежності використовуються ORM для ініціювання повторного обчислення поля кожного разу, коли деякі з його залежностей були змінені:
from odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
value = fields.Integer()
@api.depends('value')
def _compute_name(self):
for record in self:
record.name = "Record with value %s" % record.value
Exercise
Обчислювані поля
Додайте відсоток зайнятих місць до моделі Сеанси
Відобразіть це поле в представленнях список та форма
Відобразити поле як індикатор виконання
Значення по замовчуванню¶
Будь-якому полю можна надати значення за умовчанням. У визначенні поля додайте опцію default=X
, де X
є або літеральним значенням Python (логічне значення, ціле число, число з плаваючою точкою, рядок), або функцією, яка приймає набір записів і повертає значення:
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
Примітка
Об’єкт self.env
надає доступ до параметрів запиту та інших корисних речей:
self.env.cr
абоself._cr
- об’єкт курсор бази даних; він використовується для запиту до бази даних
self.env.uid
абоself._uid
це ідентифікатор бази даних поточного користувача
self.env.user
- це поточний запис користувача
self.env.context
абоself._context
- це контекстний словник
self.env.ref(xml_id)
повертає запис, що відповідає ідентифікатору XML
self.env[model_name]
повертає екземпляр даної моделі
Exercise
Активні об’єкти – значення за замовчуванням
Визначте значення start_date за замовчуванням як сьогодні (див.
Date
).Додайте поле
active
до класу Сеанс і встановіть сеанси як активні за замовчуванням.
Onchange¶
Механізм «onchange» надає клієнтському інтерфейсу можливість оновлювати форму кожного разу, коли користувач заповнює значення в полі, не зберігаючи нічого в базі даних.
Наприклад, припустімо, що модель має три поля amount
, unit_price
і price
, і ви хочете оновити ціну у формі, коли будь-яке з інших полів буде змінено. Щоб досягти цього, визначте метод, де self
представляє запис у представленні форми, і прикрасьте його onchange()
, щоб вказати, у якому полі він має бути запущений. Будь-яка зміна, яку ви вносите в self
, буде відображена у формі.
<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
# set auto-changing field
self.price = self.amount * self.unit_price
# Can optionally return a warning and domains
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}
Для обчислюваних полів вбудована поведінка значення onchange
, як можна побачити, погравши з формою Сеанси: змініть кількість місць або учасників, і індикатор прогресу taken_seats
автоматично оновиться.
Exercise
Попередження
Додайте явне значення onchange, щоб попереджати про недійсні значення, як-от від’ємна кількість місць або більше учасників, ніж місць.
Обмеження моделі¶
Odoo надає два способи налаштування автоматично перевірених інваріантів: Обмеження Python
і Обмеження SQL
.
Обмеження Python визначається як метод, з декоратором constrains()
, який викликається в наборі записів. Декоратор визначає, які поля задіяні в обмеженні, щоб обмеження автоматично оцінювалося, коли одне з них змінено. Очікується, що метод викличе виняток, якщо його інваріант не задовольняється:
from odoo.exceptions import ValidationError
@api.constrains('age')
def _check_something(self):
for record in self:
if record.age > 20:
raise ValidationError("Your record is too old: %s" % record.age)
# all records passed the test, don't return anything
Exercise
Додайте обмеження Python
Додайте обмеження, яке перевіряє, чи інструктор не присутній серед відвідувачів його/її сеансу.
Обмеження SQL визначаються через атрибут моделі _sql_constraints
. Останній призначається списку потрійних рядків (name, sql_definition, message)
, де name
- дійсна назва обмеження SQL, sql_definition
- вираз table_constraint, а ``message `` - це повідомлення про помилку.
Exercise
Додайте обмеження SQL
За допомогою Документація PostgreSQL ,додайте такі обмеження:
ПЕРЕКОНАЙТЕСЯ, що опис курсу та назва курсу відрізняються
Зробіть назву курсу УНІКАЛЬНОЮ
Exercise
Вправа 6 - Додайте повторюваний варіант
Оскільки ми додали обмеження для унікальності назви курсу, більше не можна використовувати функцію «дублювати» (
).Повторно реалізуйте свій власний метод «копіювання», який дозволяє дублювати об’єкт Курс, змінюючи оригінальну назву на «Копія [оригінальна назва]».
Розширені представлення¶
Представлення списку¶
Представлення списку можуть приймати додаткові атрибути для подальшого налаштування своєї поведінки:
decoration-{$name}
дозволяють змінювати стиль тексту рядка на основі атрибутів відповідного запису.
Значення є виразами Python. Для кожного запису вираз обчислюється з атрибутами запису як контекстними значеннями, і якщо
true
, до рядка застосовується відповідний стиль. Ось деякі інші значення, доступні в контексті:uid
: id поточного користувача,today
: поточна місцева дата у вигляді рядкаYYYY-MM-DD
,now
: те саме, щоtoday
з додаванням поточного часу. Це значення має форматYYYY-MM-DD hh:mm:ss
.
{$name}
може бутиbf
(font-weight: bold
),it
(font-style: italic
) або будь-якийзавантажувальний контекстний колір`
<https://getbootstrap.com/docs/3.3/components/#available-variations>`_ (danger
,info
,muted
,primary
,success
абоwarning
).<tree string="Idea Categories" decoration-info="state=='draft'" decoration-danger="state=='trashed'"> <field name="name"/> <field name="state"/> </tree>
editable
Або
"top"
або"bottom"
. Робить представлення списку доступним для редагування на місці (замість того, щоб переходити через представлення форми), значенням є позиція, де з’являються нові рядки.
Exercise
Розфарбовування списку
Змініть представлення списку сеансів таким чином, щоб сеанси, які тривали менше 5 днів, позначалися синім кольором, а ті, що тривали більше 15 днів, - червоним.
Календарі¶
Відображає записи як події календаря. Їхнім кореневим елементом є <calendar>
, а найпоширенішими атрибутами є:
color
Назва поля, яке використовується для сегментації кольорів. Кольори автоматично розподіляються між подіями, але події в одному кольоровому сегменті (записи, які мають однакове значення для свого поля
@color
) матимуть той самий колір.date_start
поле запису, що містить дату/час початку події
date_stop
(необов’язковий)поле запису, що містить дату/час закінчення події
string
поле запису, щоб визначити мітку для кожної події календаря
<calendar string="Ideas" date_start="invent_date" color="inventor_id">
<field name="name"/>
</calendar>
Exercise
Представлення календаря
Додайте представлення календаря до моделі Сеанс, щоб користувач міг переглядати події, пов’язані з Open Academy.
Представлення пошуку¶
Елементи представлення пошуку <field>
можуть мати @filter_domain
, який замінює домен, створений для пошуку в заданому полі. У вказаному домені self
представляє значення, введене користувачем. У наведеному нижче прикладі він використовується для пошуку в полях name
і description
.
Представлення пошуку також можуть містити елементи <filter>
, які діють як перемикачі для попередньо визначених пошуків. Фільтри повинні мати один із таких атрибутів:
domain
додати даний домен до поточного пошуку
context
додати певний контекст до поточного пошуку; використовуйте ключ
group_by
, щоб згрупувати результати за вказаною назвою поля
<search string="Ideas">
<field name="name"/>
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
<field name="inventor_id"/>
<field name="country_id" widget="selection"/>
<filter name="my_ideas" string="My Ideas"
domain="[('inventor_id', '=', uid)]"/>
<group string="Group By">
<filter name="group_by_inventor" string="Inventor"
context="{'group_by': 'inventor_id'}"/>
</group>
</search>
Щоб використовувати нестандартне подання пошуку в дії, його слід пов’язати за допомогою поля search_view_id
запису дії.
Дія також може встановити значення за замовчуванням для полів пошуку через поле context
: ключі контексту у формі search_default_field_name
ініціалізують field_name наданим значенням. Фільтри пошуку повинні мати необов’язковий @name
, щоб мати значення за замовчуванням і поводитися як логічні (їх можна ввімкнути лише за замовчуванням).
Exercise
Представлення пошуку
Додайте кнопку для фільтрації курсів, за які відповідає поточний користувач, у вікні пошуку курсів. Вибрати його за замовчуванням.
Додайте кнопку групових курсів відповідальним користувачем.
Гант¶
Попередження
Для представлення gantt потрібен модуль web_gantt, який присутній у версії enterprise edition.
Горизонтальні стовпчасті діаграми зазвичай використовуються для відображення планування та просування проекту, їхнім кореневим елементом є <gantt>
.
<gantt string="Ideas"
date_start="invent_date"
date_stop="date_finished"
progress="progress"
default_group_by="inventor_id" />
Exercise
Діаграми Ганта
Додайте діаграму Ганта, щоб користувач міг переглядати розклад сеансів, пов’язаний із модулем Open Academy. Заняття мають бути згруповані за інструктором.
Представлення графіків¶
Представлення графіків дозволяють зведений огляд і аналіз моделей, їх кореневим елементом є <graph>
.
Примітка
Зведені таблиці (елемент <pivot>
) - багатовимірна таблиця, яка дозволяє вибрати файли та розміри, щоб отримати потрібний сукупний набір даних перед переходом до більш графічного огляду. Зведена таблиця має те саме визначення вмісту, що й представлення графіків.
Представлення графіків мають 4 режими відображення, режим за замовчуванням вибирається за допомогою атрибута @type
.
- Bar (за замовчуванням)
стовпчаста діаграма, перший вимір використовується для визначення груп на горизонтальній осі, інші виміри визначають агреговані стовпчики в кожній групі.
За замовчуванням смужки розташовані поруч, їх можна скласти за допомогою
@stacked="True"
на<graph>
- Line
2-вимірна лінійна діаграма
- Pie
2-dimensional pie
Подання графіків містять <field>
з обов’язковим атрибутом @type
, який приймає значення:
row
(за замовчуванням)поле має бути агреговане за замовчуванням
measure
поле слід агрегувати, а не групувати
<graph string="Total idea score by Inventor">
<field name="inventor_id"/>
<field name="score" type="measure"/>
</graph>
Попередження
Графічні представлення виконують агрегації значень бази даних, вони не працюють із незбереженими обчисленими полями.
Exercise
Вигляд графіка
Додайте представлення графіка в об’єкт Session, який відображає для кожного курсу кількість відвідувачів у формі гістограми.
Канбан¶
Використовується для організації завдань, виробничих процесів тощо… їх кореневим елементом є <kanban>
.
Подання канбан показує набір карток, можливо згрупованих у стовпці. Кожна картка представляє запис, а кожен стовпець – значення поля агрегації.
Наприклад, завдання проекту можуть бути організовані за етапами (кожен стовпець є етапом), або відповідальними (кожен стовпець є користувачем) і так далі.
Представлення канбан визначають структуру кожної картки як суміш елементів форми (включаючи базовий HTML) і Шаблони QWeb.
Exercise
Представлення канбан
Додайте представлення канбан, яке відображає сеанси, згруповані за курсами (тому стовпці є курсами).
Безпека¶
Механізми контролю доступу повинні бути налаштовані для досягнення узгодженої політики безпеки.
Групові механізми контролю доступу¶
Групи створюються як звичайні записи в моделі res.groups
, і їм надається доступ до меню через визначення меню. Однак навіть без меню об’єкти можуть бути доступні опосередковано, тому фактичні дозволи на рівні об’єкта (читання, запис, створення, роз’єднання) повинні бути визначені для груп. Зазвичай вони вставляються через файли CSV всередині модулів. Також можна обмежити доступ до певних полів у поданні або об’єкті за допомогою атрибута груп полів.
Права доступу¶
Права доступу визначаються як записи моделі ir.model.access
. Кожне право доступу пов’язане з моделлю, групою (або без групи для глобального доступу) та набором дозволів: читання, запис, створення, від’єднання. Такі права доступу зазвичай створюються файлом CSV, названим за його моделлю: ir.model.access.csv
.
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0
Exercise
Додайте контроль доступу через інтерфейс Odoo
Створіть нового користувача «John Smith». Потім створіть групу «OpenAcademy / Session Read» із доступом на читання до моделі Session.
Exercise
Додайте контроль доступу через файли даних у вашому модулі
Використовуючи файли даних,
Створіть групу OpenAcademy / Manager з повним доступом до всіх моделей OpenAcademy
Зробіть Сеанс і Курс доступними для читання всім користувачам
Правила запису¶
Правило запису обмежує права доступу до підмножини записів даної моделі. Правило є записом моделі ir.rule
і пов’язане з моделлю, кількома групами (поле many2many), дозволами, до яких застосовується обмеження, і доменом. Домен визначає, до яких записів обмежені права доступу.
Ось приклад правила, яке запобігає видаленню лідів, які не перебувають у стані cancel
. Зауважте, що значення поля groups
має відповідати тим самим умовам, що й метод write()
ORM.
<record id="delete_cancelled_only" model="ir.rule">
<field name="name">Only cancelled leads may be deleted</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="1" />
<field name="domain_force">[('state','=','cancel')]</field>
</record>
Exercise
Правила запису
Додайте правило запису для модельного курсу та групи «OpenAcademy / Manager», яке обмежує доступ «запис» і «від’єднання» відповідальним за курс. Якщо курс не має відповідальних, усі користувачі групи повинні мати можливість змінювати його.
Майстри¶
Майстри описують інтерактивні сеанси з користувачем (або діалоговими вікнами) через динамічні форми. Майстер - це просто модель, яка розширює клас TransientModel
замість Model
. Клас TransientModel
розширює Model
і повторно використовує всі існуючі механізми з такими особливостями:
Записи майстра не повинні бути постійними; вони автоматично видаляються з бази даних через певний час. Ось чому їх називають перехідними.
Записи майстра можуть посилатися на звичайні записи або записи майстра через реляційні поля (many2one або many2many), але звичайні записи не можуть посилатися на записи майстра через поле many2one.
Ми хочемо створити майстер, який дозволить користувачам створювати учасників для окремого сеансу або для списку сеансів одночасно.
Exercise
Визначте майстра
Створіть модель майстра зі зв’язком many2one з моделлю Session і зв’язком many2many з моделлю Partner.
Запуск майстрів¶
Майстри - це просто дії вікна з полем target
, встановленим у значення new
, яке відкриває представлення (зазвичай форма) в окремому діалоговому вікні. Дія може бути викликана за допомогою пункту меню, але в більш загальному випадку вона запускається кнопкою.
Іншим способом запуску майстрів є меню binding_model_id
дії. Установлення цього поля призведе до того, що дія буде відображатися на представленнях моделі, до якої «прив’язана» дія.
<record id="launch_the_wizard" model="ir.actions.act_window">
<field name="name">Launch the Wizard</field>
<field name="res_model">wizard.model.name</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="model_context_model_ref"/>
</record>
Порада
Хоча майстри використовують звичайні подання та кнопки, зазвичай натискання будь-якої кнопки у формі спочатку зберігає форму, а потім закриває діалогове вікно. Оскільки це часто небажано у майстрах, доступний спеціальний атрибут special="cancel"
, який негайно закриває майстер без збереження форми.
Exercise
Запустіть майстер
Визначте представлення форми для майстра.
Додайте дію, щоб запустити його в контексті моделі Session.
Визначте значення за замовчуванням для поля сеансу в майстрі; використовуйте контекстний параметр
self._context
для отримання поточного сеансу.
Exercise
Реєстрація учасників
Додайте кнопки до майстра та реалізуйте відповідний метод додавання учасників до даного сеансу.
Exercise
Реєстрація учасників для кількох сеансів
Змініть модель майстра, щоб учасники могли бути зареєстровані для кількох сеансів.
Інтернаціоналізація¶
Кожен модуль може забезпечувати власні переклади в каталозі i18n, маючи файли з назвою LANG.po, де LANG є кодом мови для мови або комбінацією мови та країни, якщо вони відрізняються (наприклад, pt.po або pt_BR.po). Odoo автоматично завантажуватиме переклади для всіх увімкнених мов. Розробники завжди використовують англійську мову під час створення модуля, а потім експортують терміни модуля за допомогою функції експорту gettext POT Odoo (
без вказівки мови), щоб створити POT-файл шаблону модуля, а потім отримати перекладені PO-файли. Багато IDE мають плагіни або режими для редагування та об’єднання файлів PO/POT.Порада
Файли Portable Object, створені Odoo, публікуються на Transifex, що полегшує переклад програмного забезпечення.
|- idea/ # The module directory
|- i18n/ # Translation files
| - idea.pot # Translation Template (exported from Odoo)
| - fr.po # French translation
| - pt_BR.po # Brazilian Portuguese translation
| (...)
Порада
За замовчуванням POT-експорт Odoo лише витягує мітки всередині XML-файлів або внутрішні визначення полів у коді Python, але будь-який рядок Python можна перекласти таким чином, оточивши його функцією odoo._()
(наприклад, _("Label" ")
)
Exercise
Перекласти модуль
Виберіть другу мову для встановлення Odoo. Перекладіть свій модуль за допомогою засобів, наданих Odoo.
Звітність¶
Друковані звіти¶
Odoo використовує систему звітів на основі Шаблони QWeb, Twitter Bootstrap і Wkhtmltopdf.
Звіт - це комбінація двох елементів:
ir.actions.report
, який налаштовує різні основні параметри для звіту (тип за замовчуванням, чи слід зберігати звіт у базі даних після створення,…)<record id="account_invoices" model="ir.actions.report"> <field name="name">Invoices</field> <field name="model">account.invoice</field> <field name="report_type">qweb-pdf</field> <field name="report_name">account.report_invoice</field> <field name="report_file">account.report_invoice</field> <field name="attachment_use" eval="True"/> <field name="attachment">(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')</field> <field name="binding_model_id" ref="model_account_invoice"/> <field name="binding_type">report</field> </record>
Порада
Оскільки це здебільшого стандартна дія, як і у випадку з Майстри, загалом корисно додати звіт як контекстний елемент у перегляд дерева та/або форми моделі, про яку звітують, за допомогою ` поле
binding_model_id`
.Тут ми також використовуємо
binding_type
для того, щоб звіт був у контекстному меню звіту, а не в меню дій. Немає технічної різниці, але розміщення елементів у правильному місці допомагає користувачам.Стандартний QWeb-представлення для фактичного звіту:
<t t-call="web.html_container"> <t t-foreach="docs" t-as="o"> <t t-call="web.external_layout"> <div class="page"> <h2>Report title</h2> </div> </t> </t> </t>
стандартний контекст візуалізації надає ряд елементів, найважливішими з яких є:
docs
записи, для яких друкується звіт
user
користувач друкує звіт
Оскільки звіти є стандартними веб-сторінками, вони доступні через URL-адресу, а вихідними параметрами можна керувати через цю URL-адресу, наприклад, HTML-версія звіту Рахунок доступна через http://localhost:8069/report/html/account .report_invoice/1 (якщо встановлено account
) і PDF-версію через http://localhost:8069/report/pdf/account.report_invoice/1.
Небезпека
Якщо виявляється, що у вашому PDF-звіті відсутні стилі (тобто текст відображається, але стиль/макет відрізняється від HTML-версії), ймовірно, ваш процес wkhtmltopdf не може отримати доступ до веб-сервера, щоб завантажити їх.
Якщо ви перевіряєте журнали свого сервера та бачите, що стилі CSS не завантажуються під час створення звіту PDF, швидше за все, це проблема.
Процес wkhtmltopdf використовуватиме системний параметр web.base.url
як кореневий шлях до всіх пов’язаних файлів, але цей параметр автоматично оновлюється щоразу, коли адміністратор входить до системи. Якщо ваш сервер знаходиться за певним типом проксі, який недоступний. Ви можете виправити це, додавши один із цих системних параметрів:
report.url
, що вказує на URL-адресу, доступну з вашого сервера (ймовірно,http://localhost:8069
або щось подібне). Він використовуватиметься лише для цієї конкретної мети.web.base.url.freeze
, якщо встановленоTrue
, припинить автоматичне оновленняweb.base.url
.
Exercise
Створіть звіт для моделі сеансу
Для кожного сеансу має відображатися назва сеансу, його початок і кінець, а також список учасників сеансу.
Інф. панелі¶
Exercise
Визначте інф. панель
Визначте інф. панель, яка містить створений вами графік, подання календаря сеансів і подання списку курсів (з можливістю перемикання на подання форми). Ця інф. панель має бути доступною через пункт меню в меню та автоматично відображатися у веб-клієнті, коли вибрано головне меню OpenAcademy.
WebServices¶
Модуль веб-сервісів пропонує загальний інтерфейс для всіх веб-сервісів:
XML-RPC
JSON-RPC
До бізнес-об’єктів також можна отримати доступ через механізм розподілених об’єктів. Усі вони можуть бути змінені через інтерфейс клієнта з контекстним представленням.
Odoo доступний через інтерфейси XML-RPC/JSON-RPC, для яких існують бібліотеки багатьма мовами.
Бібліотека XML-RPC¶
У наступному прикладі наведено програму на Python 3, яка взаємодіє з сервером Odoo за допомогою бібліотеки xmlrpc.client
:
import xmlrpc.client
root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
uid = xmlrpc.client.ServerProxy(root + 'common').login(DB, USER, PASS)
print("Logged in as %s (uid: %d)" % (USER, uid))
# Create a new note
sock = xmlrpc.client.ServerProxy(root + 'object')
args = {
'color' : 8,
'memo' : 'This is a note',
'create_uid': uid,
}
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)
Exercise
Додати нову послугу клієнту
Напишіть програму на Python, здатну надсилати запити XML-RPC на ПК, на якому запущено Odoo (ваш або ваш інструктор). Ця програма має відображати всі сесії та відповідну кількість місць. Він також має створити новий сеанс для одного з курсів.
Бібліотека JSON-RPC¶
У наступному прикладі наведено програму Python 3, яка взаємодіє з сервером Odoo за допомогою стандартних бібліотек Python urllib.request
і json
. У цьому прикладі передбачається, що встановлено додаток Productivity (note
):
import json
import random
import urllib.request
HOST = 'localhost'
PORT = 8069
DB = 'openacademy'
USER = 'admin'
PASS = 'admin'
def json_rpc(url, method, params):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": random.randint(0, 1000000000),
}
req = urllib.request.Request(url=url, data=json.dumps(data).encode(), headers={
"Content-Type":"application/json",
})
reply = json.loads(urllib.request.urlopen(req).read().decode('UTF-8'))
if reply.get("error"):
raise Exception(reply["error"])
return reply["result"]
def call(url, service, method, *args):
return json_rpc(url, "call", {"service": service, "method": method, "args": args})
# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)
# create a new note
args = {
'color': 8,
'memo': 'This is another note',
'create_uid': uid,
}
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)
Приклади можна легко адаптувати з XML-RPC на JSON-RPC.
Примітка
Існує низка API високого рівня на різних мовах для доступу до систем Odoo без явного проходження через XML-RPC або JSON-RPC, наприклад: