Введение
Если вы когда-нибудь открывали заказ продаж в Odoo и видели список строк заказа под данными клиента — вы на практике сталкивались с полем One2many. Это одна из ключевых конструкций в модели данных Odoo: она отвечает за отображение и управление связями «родитель — множество дочерних записей», с которыми ежедневно работают пользователи и разработчики.
В этой статье мы разберём, что такое One2many, как он реализован в ORM Odoo, в каких случаях стоит использовать это поле и как его правильно создать — через Odoo Studio или напрямую в Python-коде в модуле.
Материал будет полезен и бизнес-пользователям, которые хотят понять структуру данных, и разработчикам, которым нужен практический и понятный гайд по работе с этим типом поля в Odoo.
Что такое поле One2many в Odoo
One2many — это тип реляционного поля в Odoo, позволяющий одному родительскому запису ссылаться на множество дочерних записей из другой модели.
Проще говоря: одна компания может иметь множество счетов, один заказ — множество строк заказа, один проект — множество задач. One2many служит для представления таких «списков» дочерних элементов внутри родительского объекта.
В пользовательском интерфейсе Odoo One2many обычно отображается как встроенный список в форме — таблица или перечень, где можно прямо из формы родителя добавлять, редактировать и удалять дочерние записи, не переходя на отдельные страницы.
Чем он отличается от других реляционных полей
В ORM Odoo есть три основных типа реляционных полей:
- Many2one: одиночная ссылка на одну запись в другой модели (например, заказ указывает на одного клиента).
- One2many: ссылка одного родителя на много дочерних записей (например, заказ связан со множеством строк заказа).
- Many2many: взаимные связи «многие ко многим» (например, товар может иметь несколько тегов, и тег может быть у многих товаров).
One2many применяют, когда каждая дочерняя запись принадлежит ровно одному родителю. Если запись должна одновременно относиться к нескольким родителям, нужно использовать Many2many, иначе придётся дублировать данные.
Особенность One2many в Odoo в том, что само поле не хранит данные в таблице родителя — это виртуальное поле, которое формируется по данным на стороне дочерней модели через соответствующий Many2one.
Как работает это поле
На уровне базы данных колонок для One2many в таблице родителя не создаётся: связь реализована через внешний ключ в таблице дочерних записей, указывающий на ID родителя.
Это базовый принцип ORM Odoo: каждая One2many имеет обратное поле Many2one в связанной модели. Фактически One2many — это обратный поиск: Odoo находит все дочерние строки, у которых поле Many2one совпадает с ID текущего родителя.
Взаимосвязь One2many и Many2one
При определении One2many в Python задают два основных параметра:
- comodel_name: модель, где находятся дочерние записи.
- inverse_name: имя поля Many2one на дочерней модели, указывающего на родителя.
Ниже — типичный пример из кастомного модуля, где договор сервиса связан со списком артефактов (deliverables):
deliverable_ids = fields.One2many(
comodel_name='service.deliverable',
inverse_name='contract_id',
string='Deliverables'
)
В этом примере contract_id — это поле Many2one на модели service.deliverable, которое указывает на контракт. Без такого обратного поля One2many просто не сможет работать.
Что реально происходит в базе данных
В базе данных Odoo информация по One2many хранится не в таблице родителя, а в таблице дочерних объектов: каждая дочерняя строка содержит столбец-внешний ключ, указывающий на ID родителя.
Когда Odoo загружает родительскую запись и нужно показать её One2many, ORM выполняет SQL-запрос: найти все строки в таблице child, где внешний ключ равен ID текущего родителя. Это стандартное поведение реляционных СУБД, которое Odoo скрывает за удобным API.
Именно поэтому One2many считается полем, которое не добавляет колонок в таблицу — это вычисляемое представление связанных записей.
Бизнес-сценарии применения
One2many встречается повсеместно в Odoo, поскольку естественно моделирует отношения «родитель — дети» в бизнес-процессах. Ниже — пять типичных примеров из реальной практики.
1. Заказы продаж и строки заказа
В модуле продаж модель sale.order использует One2many order_line_ids, ссылающийся на sale.order.line. Каждая строка хранит сведения о товаре, количестве, цене и скидках. Пользователь может прямо в форме заказа добавлять или удалять строки без лишних переходов.
2. Счета и строки счёта
В бухгалтерии модель account.move содержит One2many на строки счёта (account.move.line). Каждая строка — это начисление по продукту или услуге; родительский счёт аккумулирует суммы из всех своих строк.
3. Проекты и задачи
В проектном модуле у project.project есть One2many для задач; в форме проект может показывать задачи списком, канбаном или диаграммой Ганта — всё это разные представления одной и той же One2many-связи.
4. Контрагенты и контактные лица
Модель res.partner содержит поле child_ids, которое перечисляет контактных лиц компании. В карточке компании вкладка "Контакты" показывает всех сотрудников и контактных ответственных; у каждого из них есть Many2one parent_id, указывающий на компанию.
5. Пользовательские модели в сервисе или производстве
При создании своего модуля One2many — естественный выбор, когда одна запись владеет списком подзаписей: заявка на обслуживание с перечнем запасных частей, курс со списком дат сессий, договор с перечнем результатов работ и т.п.
Такая гибкость — одна из причин, почему One2many центральен при кастомизации Odoo и создании отраслевых моделей данных.
Создание и настройка поля
Добавить One2many можно двумя способами: через Odoo Studio без кода или написав поле в Python в кастомном модуле.
Работа через Odoo Studio
В Studio нельзя сразу создать One2many на родительской модели из палитры полей, потому что One2many всегда опирается на уже существующий Many2one на дочерней модели.
Рекомендуемый порядок действий в Studio:
- Сначала откройте дочернюю модель и добавьте в ней поле Many2one, указывающее на родителя.
- После сохранения вернитесь к родительской модели в Studio.
- Добавьте новое поле типа "One2many"; Studio попросит указать связанную модель и имя inverse (Many2one).
- После этого One2many появится во встроенном списке на форме родителя.
Для пользователей, не пишущих код, этот путь — самый простой: Studio создаёт поля, которые по поведению полностью эквивалентны полям, описанным в Python.
Определение в Python в кастомном модуле
Разработчикам удобнее и правильнее задавать One2many в коде, поскольку так доступен полный контроль над поведением, именованием и доп. ограничениями. Пример полного определения выглядит так:
from odoo import fields, models
class ServiceContract(models.Model):
_name = 'service.contract'
_description = 'Service Contract'
name = fields.Char(string='Contract Name', required=True)
deliverable_ids = fields.One2many(
comodel_name='service.deliverable',
inverse_name='contract_id',
string='Deliverables'
)
class ServiceDeliverable(models.Model):
_name = 'service.deliverable'
_description = 'Service Deliverable'
contract_id = fields.Many2one(
comodel_name='service.contract',
string='Contract',
ondelete='cascade'
)
name = fields.Char(string='Description', required=True)
Обратите внимание: поле Many2one (contract_id) должно быть определено в дочерней модели. Имя, указанное в inverse_name, должно точно совпадать с именем Many2one, иначе связь не заработает.
Создание поля через XML-RPC API
Если вы управляете полями Odoo программно, можно создавать One2many через XML-RPC, создавая записи в ir.model.fields. Принцип тот же: сначала создаёте Many2one, затем — One2many, указывая relation_field с именем обратного поля.
Такой подход полезен при автоматизации изменений структуры базы в разных окружениях или при массовом создании полей в нескольких БД.
Рекомендации по использованию
Из практики внедрений — набор правил, которые реально экономят время и сокращают ошибки при работе с One2many.
- Всегда создавайте Many2one в первую очередь. One2many не может существовать без обратного поля. Это относится ко всем сценариям — Studio, Python или API.
- Используйте суффикс
_idsдля имён One2many. Принято называть такие поля с суффиксом_ids(например,line_ids,task_ids), чтобы сразу было понятно, что поле возвращает набор записей. - Применяйте
ondelete='cascade', когда нужно. Если дочерние записи должны удаляться вместе с родителем, укажите это в Many2one — так вы избежите «осиротевших» строк в БД. - Фокусируйте список One2many на главном. Показывайте только ключевые колонки в встроенном списке: слишком много полей в One2many замедляет форму и снижает юзабилити. Для второстепенных колонок используйте опцию
optional, чтобы пользователь мог включать их по необходимости. - Ограничивайте видимые дочерние записи через domain. Добавление
domainк One2many помогает фильтровать дочерние записи по условию, что особенно важно, если та же модель используется несколькими родителями. - Аккуратно работайте с большими объёмами данных. Если у родителя потенциально тысячи дочерних записей, не загружайте их все в форме. Рассмотрите отдельный список или постраничность для вложенной таблицы.
Распространённые ошибки
Типичные ошибки при работе с One2many — они повторяются у многих команд и проектов.
Забывают создать обратный Many2one
Самая частая ошибка — попытка добавить One2many без существующего Many2one на дочерней модели. Odoo вернёт ошибку. Всегда проверяйте, что inverse_name действительно существует на дочерней модели.
Неверный синтаксис при записи данных
При обновлении One2many через код или API нужно использовать специальные команды (command tuples). Нельзя просто присвоить обычный Python-список. Правильные операции выглядят так:
(0, 0, values_dict)— создать новую дочернюю запись.(1, child_id, values_dict)— обновить существующую дочернюю запись.(2, child_id, 0)— удалить существующую дочернюю запись (и саму запись).(4, child_id, 0)— добавить существующую запись в отношение без создания копии.(5, 0, 0)— разорвать все связи (unlink связей) между родителем и дочерними записями, но не удалять сами дочерние записи.
Попытка писать простой список вроде record.line_ids = [1, 2, 3] не сработает как ожидается в ORM Odoo.
Путаница между One2many и Many2many
Если дочерняя запись должна принадлежать нескольким родителям одновременно, One2many не подойдёт — используйте Many2many. Попытки моделировать множественные связи через One2many ведут к дублированию данных и проблемам целостности.
Проблемы с производительностью при больших вложенных списках
Если в One2many показываются сотни или тысячи строк, загрузка формы существенно замедляется — это часто встречается в учёте и инвентаризации. Применяйте ограничение (limit) в представлении или направляйте пользователя на отдельный список для обработки больших наборов данных.
«Осиротевшие» записи после удаления родителя
Если удалить родителя, не настроив ondelete='cascade' в Many2one, дочерние записи останутся с пустым или некорректным ссылочным полем. Со временем это засоряет базу и может приводить к ошибкам в отчётах и видах. Планируйте политику удаления заранее.
Заключение
One2many — базовый элемент модели данных Odoo. Он отвечает за многие ключевые фичи платформы: от строк заказа и счетов до задач в проектах. Поняв принцип работы One2many и роль обратного Many2one, вы заметно упростите чтение и расширение модели данных Odoo.
Независимо от того, настраиваете ли вы систему через Studio, пишете модуль с нуля или администрируете существующую базу, One2many будет инструментом, к которому вы будете возвращаться постоянно. Знание, когда и как его правильно применять, а также умение избегать типичных ошибок сэкономит время и уберегёт данные.
Если нужны другие руководства по полям Odoo и работе с API, посмотрите коллекцию статей по данным и API Odoo — там много практических примеров и шаблонов.
Нужна помощь с внедрением Odoo?
Dasolo помогает компаниям внедрять, настраивать и оптимизировать Odoo под их бизнес‑процессы. Мы проектируем модели данных, пишем кастомные модули, создаём поля и помогаем извлечь максимум пользы из вашей системы.
Если у вас есть вопросы по проекту Odoo или вы хотите обсудить структуру данных, свяжитесь с нами— мы будем рады помочь.