Створення модуля

Небезпека

Цей підручник застарів. Натомість радимо прочитати Початок.

Попередження

Для цього посібника потрібно встановити 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 створив індекс бази даних для стовпця.

Прості поля

Існує дві широкі категорії полів: «прості» поля, які є атомарними значеннями, що зберігаються безпосередньо в таблиці моделі, і «реляційні» поля, що зв’язують записи (однієї моделі або різних моделей).

Приклади простих полів: Boolean, Date, Char.

Зарезервовані поля

Odoo створює кілька полів у всіх моделях1. Ці поля керуються системою, і в них не слід записувати. Їх можна прочитати, якщо це буде корисно або необхідно:

id (Id)

Унікальний ідентифікатор для запису в його моделі.

create_date (Datetime)

Дата створення запису.

create_uid (Many2one)

Користувач, який створив запис.

write_date (Datetime)

Дата останньої зміни запису.

write_uid (Many2one)

користувач, який останній змінював запис.

Спеціальні поля

За замовчуванням 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, щоб зберегти зміни у базі даних.

Дії та меню

Дії та меню - це звичайні записи в базі даних, зазвичай оголошені через файли даних. Дії можна запустити трьома способами:

  1. натисканням пунктів меню (пов’язаних із певними діями)

  2. натисканням кнопок у поданнях (якщо вони пов’язані з діями)

  3. як контекстні дії на об’єкт

Оскільки меню дещо складно оголосити, існує ярлик <menuitem>, щоб оголосити ir.ui.menu і легше підключити його до відповідної дії.

<record model="ir.actions.act_window" id="action_list_ideas">
    <field name="name">Ideas</field>
    <field name="res_model">idea.idea</field>
    <field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
          action="action_list_ideas"/>

Небезпека

Дія має бути оголошена перед відповідним меню у файлі XML.

Файли даних виконуються послідовно, id дії має бути присутнім у базі даних, перш ніж можна буде створити меню.

Exercise

Визначте нові пункти меню

Визначте нові пункти меню для доступу до курсів у пункті меню 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)

Небезпека

Оскільки One2many є віртуальним зв’язком, має бути поле Many2one у other_model та його назва має бути related_field

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 надає два механізми успадкування для розширення існуючої моделі модульним способом.

Перший механізм успадкування дозволяє модулям змінювати поведінку моделі, визначеної в іншому модулі, шляхом:

  • додати поля до моделі,

  • перевизначати визначення полів у моделі,

  • додати обмеження до моделі,

  • додати методи до моделі,

  • перевизначати існуючі методи в моделі.

Другий механізм успадкування (делегування) дозволяє зв’язати кожен запис моделі із записом у батьківській моделі та забезпечує прозорий доступ до полів батьківського запису.

../../_images/inheritance_methods.png

Перегляньте також

  • _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 ,додайте такі обмеження:

  1. ПЕРЕКОНАЙТЕСЯ, що опис курсу та назва курсу відрізняються

  2. Зробіть назву курсу УНІКАЛЬНОЮ

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

Представлення пошуку

  1. Додайте кнопку для фільтрації курсів, за які відповідає поточний користувач, у вікні пошуку курсів. Вибрати його за замовчуванням.

  2. Додайте кнопку групових курсів відповідальним користувачем.

Гант

Попередження

Для представлення 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

Запустіть майстер

  1. Визначте представлення форми для майстра.

  2. Додайте дію, щоб запустити його в контексті моделі Session.

  3. Визначте значення за замовчуванням для поля сеансу в майстрі; використовуйте контекстний параметр 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, наприклад:

1

можна вимкнути автоматичне створення деяких полів

2

написання необроблених запитів SQL можливе, але вимагає обережності, оскільки воно обходить усі механізми автентифікації та безпеки Odoo.