Введение
Поле Many2One — ключевой элемент структуры данных Odoo. Каждый раз, когда вы связываете заказ с клиентом, относите товар к категории или закрепляете задачу за проектом, вы используете именно этот тип связи. Для разработчика и администратора Odoo понимание Many2One — базовый навык для построения связанной и удобной системы.
С точки зрения бизнеса Many2One превращает отдельные модули в единое целое. Благодаря таким связям информация свободно течёт между документами: пользователь может быстро перейти от счёта к контрагенту или от задачи к проекту без мысли о внутренней структуре базы данных.
В этом материале объясняем, что именно хранит поле Many2One, как оно работает в ORM и интерфейсе, как создать и настроить его через Odoo Studio, Python или API, и приводим практические примеры из CRM, продаж, склада и бухгалтерии.
Что такое поле Many2One в Odoo
В модели Odoo поле Many2One хранит ссылку от одной записи на одну запись другой модели. Название отражает взгляд текущей модели: многие записи здесь указывают на одну там. Так, множество заказов может ссылаться на одного клиента, а множество товаров — на одну категорию.
На уровне базы данных Many2One представлен внешним ключом в таблице текущей модели. Если заказ связан с партнёром с ID 42, в колонке partner_id таблицы sale_order будет число 42. ORM автоматически выполняет соединения и подставляет отображаемое имя связанной записи.
В интерфейсе поле Many2One выглядит как выпадающий список с подсказкой при вводе. Пользователь начинает печатать — система подбирает совпадения в реальном времени. При выборе в форме показывается читаемое имя записи, а не её внутренний идентификатор, что делает работу с такими полями интуитивной.
Пример объявления поля в Python-модели:
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
x_account_manager_id = fields.Many2One(
comodel_name='res.users',
string='Account Manager',
ondelete='set null',
)
Параметр comodel_name указывает техническое имя целевой модели. ondelete определяет поведение при удалении связанной записи: 'cascade' — удалить и текущую запись, 'set null' — обнулить ссылку, 'restrict' — запретить удаление, если на запись существуют ссылки.
В Odoo Studio поле Many2One доступно в панели полей как Many2One. При добавлении выбираете целевую модель из списка — Studio создаёт поле автоматически. Это быстрый способ наладить отношения между сущностями без кода.
Принцип работы поля
При чтении в ORM вы получаете recordset с ссылочной записью; если связи нет — пустой recordset. Через Python можно сразу обращаться к полям связанной записи точечной нотацией, как к обычному объекту:
order = self.env['sale.order'].browse(1)
customer_name = order.partner_id.name
customer_city = order.partner_id.city
Такая цепочная навигация освобождает от ручного написания SQL‑JOIN: ORM сам подтягивает нужные данные в фоне, что ускоряет разработку и делает код более читаемым.
Через XML‑RPC Many2One возвращается как массив из двух элементов: ID и отображаемое имя, например [42, "Acme Corp"]. Если поле пустое — возвращается False. Это важно учитывать при обработке ответов API в скриптах.
Ключевые атрибуты поля
Ниже — основные параметры, которые задают поведение Many2One в Odoo.
- comodel_name: Техническое имя модели, на которую ссылаются. Обязательное поле.
- ondelete: Поведение при удалении связанной записи —
'cascade','set null'или'restrict'. По умолчанию обычно'set null'. - domain: Фильтр, ограничивающий список доступных записей в выпадающем списке. Например,
domain=[('customer_rank', '>', 0)]оставит только клиентов. - context: Дополнительные значения контекста при открытии формы связанной записи или списка. Позволяет подставлять значения по умолчанию при создании связанной записи на лету.
- required: Делает поле обязательным — запись не сохранится без заполнения этого поля.
- readonly: Запрещает редактирование поля пользователем, полезно когда связь устанавливается программно.
- delegate: При
Trueвсе поля связанной модели становятся доступными на текущей модели — механизм, применяемый для наследования моделей, а не для типичных связей.
Отображение в представлениях
В формах Many2One рендерится как выпадающая строка с поиском; рядом обычно есть стрелка для быстрого перехода в форму связанной записи. Это заметно облегчает навигацию между документами без захода в меню модуля.
В списковых представлениях поле показывает имя связанной записи и поддерживает группировку: например, сгруппировать заказы по клиентам или задачи по проектам — одна из самых востребованных возможностей отчётности Odoo.
В поисковых фильтрах Many2One используются для отбора и группировки. Когда вы фильтруете в CRM по ответственному менеджеру, вы фактически работаете с Many2One.
Обратная связь — One2Many
У каждой Many2One есть логическое обратное поле One2Many. Если заказы ссылаются на клиента через Many2One, у карточки клиента можно показать список всех связанных заказов через One2Many. Эта обратная связь не добавляет столбец в базу — она формируется по внешнему ключу с другой стороны и удобна для двунаправленной навигации.
Бизнес-сценарии применения
Many2One повсеместно используется в стандартных бизнес‑процессах Odoo. Ниже — пять практических кейсов.
CRM: назначение ответственных за лиды
В CRM лиды и возможности имеют поле user_id — Many2One на модель пользователей (res.users). Это позволяет назначать менеджеров, фильтровать воронку, смотреть эффективность по сотрудникам и массово перераспределять лиды. При необходимости можно добавить ещё одно Many2One‑поле для аккаунт‑менеджера или второго исполнителя.
Продажи: связь заказов с клиентами и прайс‑листами
В заказе продаж важные Many2One — partner_id (клиент) и pricelist_id (прайс‑лист). При выборе клиента Odoo может автоматически подставить прайс‑лист, условия оплаты и адрес доставки через onchange‑логики, которые читают значение Many2One и заполняют связанные поля — это заметно упрощает работу продавца.
Склад: товары и их категории
Каждый товар привязан к категории через поле categ_id на product.template. Категория управляет учётом себестоимости, методами переоценки и стратегиями списания. Корректное распределение по категориям критично для точной отчётности — Many2One здесь экономит время: одна карточка категории применяется к множеству товаров.
Бухгалтерия: проводки и журналы
Любая бухгалтерская проводка связана с журналом через journal_id. Журнал задаёт нумерацию, тип записей и иногда стандартные счёта. Неправильно выбранный журнал может привести к неверному отражению операции в главной книге, поэтому Many2One в этом месте — не только удобство, но и точка контроля.
Проекты: задачи и проекты
В Project каждая задача привязана к проекту через project_id. Эта связь определяет набор стадий, доступ команды и учёт времени. Для компаний, выставляющих счета по проектам, связка задач‑таймшитов‑проекта критична для правильного начисления дохода и выставления счётов.
Создание и настройка поля Many2One
Способы добавления поля Many2One
Существует несколько подходов — выбор зависит от того, кто и где вносит правку: администратор через Studio, разработчик в модуле или автоматизированный скрипт через API.
Через Odoo Studio (без кода)
- Odoo Studio — инструмент для быстрых правок интерфейса. Чтобы добавить Many2One без кода:
- Откройте Studio в нужном меню.
- Перейдите на форму, куда хотите добавить поле.
- Перетащите элемент Many2One из панели полей на форму.
- В настройках укажите целевую модель и при необходимости domain, чтобы ограничить выбор.
- Сохраните изменения и закройте Studio.
Studio создаст поле с префиксом x_studio_ и сразу добавит его в представление. Поле будет работать мгновенно: пользователи смогут выбирать связанные записи без участия разработчика — быстрый и безопасный способ для бизнес‑пользователей расширять формы.
Через Python в кастомном модуле
Для продакшн‑решений, которые нужно хранить в системе контроля версий и деплоить, поля объявляют в Python внутри модуля. Это даёт полный контроль над параметрами и поведением:
from odoo import fields, models
class ProjectTask(models.Model):
_inherit = 'project.task'
x_client_contact_id = fields.Many2One(
comodel_name='res.partner',
string='Client Contact',
domain=[('type', '=', 'contact')],
ondelete='set null',
help='The main contact at the client for this task.',
)
После добавления поля нужно обновить XML‑представления и выполнить обновление модуля, чтобы применились изменения в базе. Этот подход обязателен для крупных проектов и сложных интеграций, где важны тестирование и откат изменений.
Рекомендуется параллельно добавить обратное One2Many‑поле, чтобы навигация работала в обе стороны:
class ResPartner(models.Model):
_inherit = 'res.partner'
x_task_ids = fields.One2Many(
comodel_name='project.task',
inverse_name='x_client_contact_id',
string='Related Tasks',
)
Через XML‑RPC API
Если вы автоматизируете конфигурацию извне (скрипт деплоя, CI/CD или удалённая настройка), Many2One можно создать через XML‑RPC, работая с метаданными модели:
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_client_segment_id',
'field_description': 'Client Segment',
'model_id': model_id,
'ttype': 'many2one',
'relation': 'res.partner.category',
'on_delete': 'set null',
'state': 'manual',
}]
)
Ключ relation задаёт целевую модель, on_delete — поведение при удалении. При создании через API не забывайте добавить обратное One2Many — иначе навигация будет односторонней; это обязательное правило при автоматизированных настройках.
Рекомендации по использованию
1. Всегда создавайте обратную One2Many
При добавлении Many2One стоит также определить One2Many на связанной модели. Это не создаёт лишних столбцов, зато делает интерфейс двунаправленным: с карточки клиента удобно видеть все связанные заказы или задачи.
2. Ограничивайте выбор с помощью domain
По умолчанию Many2One на res.partner показывает всех партнёров: клиентов, поставщиков, контакты. Если поле предназначено только для клиентов, добавьте domain вроде [('customer_rank', '>', 0)] — так пользователю не придётся искать среди лишних записей и снижается риск ошибочного выбора.
3. Взвешенно подбирайте ondelete
Опция ondelete может привести к неожиданным последствиям. 'cascade' удаляет зависимые записи вместе с родительской — это опасно для рабочих данных. Чаще всего безопаснее использовать 'set null'. 'restrict' уместен, если связанные записи нельзя удалять при наличии ссылок, например для критичных справочников.
4. Не дублируйте данные там, где нужна Many2One
Распространённая ошибка на старте проекта — хранить имя компании в char‑поле, хотя существует модель партнёров. Такое дублирование усложняет фильтрацию, группировку и приводит к рассинхронизированным данным при переименованиях. Если информация уже есть в другой модели — используйте Many2One.
5. Используйте context для подстановки значений
Контекст позволяет подставлять дефолтные значения при создании связанной записи прямо из выпадающего списка. Например, при выборе контакта внутри проекта можно автоматически заполнить поле проект — это сокращает ручной ввод и сохраняет согласованность данных.
Типичные ошибки и ловушки
Забыли обратную One2Many
Частая оплошность — добавить Many2One, но не создать обратное поле. Результат — односторонняя связь: с дочерней записи видно родителя, а с родительской нельзя увидеть все связанные дочерние записи. Пользователи начинают жаловаться на неудобство, и в итоге приходится писать дополнительный отчёт или поиск.
Незадумчивое применение cascade
Если установить ondelete='cascade' для связи между важными операционными данными и справочником, можно получить массовые удаления. Например, удалили категорию — исчезли все товары в ней. Для бизнес‑записей чаще подходят 'set null' или 'restrict'.
Не учитывают False при навигации в Python
При пустом Many2One чтение возвращает пустой recordset, который оценивается как False. Если писать order.partner_id.name без проверки, вы получите пустую строку — это обычно безопасно, но при последовательной навигации (order.partner_id.country_id.name) отсутствие промежуточной ссылки может привести к молчаливым ошибкам в отчётах или письмах. Всегда проверяйте наличие записи, если поле не обязательно.
Указание неправильной модели
Модель res.partner объединяет клиентов, поставщиков и контакты. Если Many2One ссылается на неё без domain, пользователь увидит лишние варианты — внутренние пользователи, адреса доставки, поставщиков. Определяйте domain, соответствующий бизнес‑назначению поля.
Чрезмерное использование Many2One вместо Selection
Если набор значений небольшой и неизменяемый, лучше выбрать Selection: он проще и быстрее в работе. Many2One оправдан, когда перечень большой, управляем пользователем или используется в нескольких моделях; в противном случае Selection уменьшит число JOIN‑ов и упростит структуру.
Заключение
Итоги
Главные выводы: всегда добавляйте обратную One2Many для двунаправленной навигации, используйте domain для релевантных выпадающих списков, обдуманно выбирайте ondelete, и не применяйте Many2One там, где достаточно Selection.
Независимо от того, делаете вы правку через Studio, пишете модуль на Python или управляете моделями через XML‑RPC, правильная настройка реляционных полей с самого начала делает систему более надёжной и удобной в сопровождении.
О нас — Dasolo Свяжитесь с нами и обсудим ваш проект по Odoo.