Введение
Большинство разработчиков при необходимости связать одну запись с другой выбирают Many2one — и в подавляющем числе случаев это правильный выбор. Но иногда требуется гибкость: ссылка может указывать на документ одного из нескольких типов — счёт на оплату, закупку, производственный ордер или вовсе на задачу проекта. Для таких ситуаций в Odoo есть Reference-поле — специально созданное для «полиморфных» связей.
Reference — это один из наиболее универсальных типов полей в ORM Odoo. В отличие от Many2one, оно не фиксируется на одной модели: пользователь сначала выбирает тип документа из разрешённого списка, затем конкретную запись. В результате получается ссылка, которая может указывать на разные модели в зависимости от контекста.
В этой заметке мы разберём, как Reference-поле хранит данные, как оно ведёт себя в модели данных Odoo, как добавить и настроить его через Odoo Studio или в коде на Python, а также когда такое поле действительно решает бизнес-задачу.
Что такое Reference-поле в Odoo
Внутри ORM Odoo Reference — это особый тип поля, который хранит ссылку на запись любой модели, перечисленной в его параметре selection. Это отличает его от Many2one, который жёстко ссылается на одну, заранее заданную модель.
В базе значение Reference хранится как текст в формате model_name,record_id — например, ссылка на заказ продажи №42 будет записана как sale.order,42. Это важно учитывать при прямых запросах к базе и при построении фильтров.
С точки зрения интерфейса поле Reference реализовано как двухэтапный ввод: сначала пользователь выбирает тип документа (например, Заказ продажи, Счёт или Задача проекта), затем в поле поиска указывает конкретную запись. Для пользователей это интуитивно, как только понятен сам механизм двухшагового выбора.
Ниже — пример базового описания Reference-поля в Python:
from odoo import fields, models
class HelpdeskTicket(models.Model):
_inherit = 'helpdesk.ticket'
related_document = fields.Reference(
selection=[
('sale.order', 'Sale Order'),
('purchase.order', 'Purchase Order'),
('account.move', 'Invoice'),
('project.task', 'Project Task'),
],
string='Related Document',
)
Параметр selection — это список кортежей: первый элемент — техническое имя модели (например, sale.order), второй — понятная пользователю метка, которая отображается в выпадающем списке. Вы сами определяете, какие модели будут доступны.
Есть и динамический вариант: список selection можно формировать на лету из таблицы ir.model, делая доступными все установленные модели. Это удобно для очень гибких инструментов, но без фильтрации такой подход перегружает интерфейс пользователя.
В Odoo Studio поле Reference доступно в панели типов полей под названием Reference. При добавлении через Studio вы выбираете модели прямо в интерфейсе — код не нужен. Это делает Reference доступным вариантом для быстрого прототипирования и мелких правок.
Принцип работы поля Reference
Понимание того, как Reference хранит и извлекает данные, помогает корректно использовать его в разработке Odoo.
Хранение в базе
В отличие от Many2one, который хранит только целочисленный идентификатор, Reference сохраняет строку с именем модели и id записи, например sale.order,15 в колонке типа VARCHAR PostgreSQL. Из-за этого на уровне СУБД нет внешнего ключа — такое решение осознанно поддерживает полиморфизм поля.
Поскольку СУБД не поддерживает ограничение внешнего ключа для этого столбца, Odoo не очищает Reference автоматически при удалении связанной записи. Если связанный заказ удалили, поле останется с текстом sale.order,42. Это поведение стоит учитывать при проектировании логики и задач по обслуживанию данных.
Доступ к связанной записи в Python
Когда вы читаете Reference-поле в коде, Odoo возвращает объект записи соответствующей модели — так же, как и для Many2one. Если поле пустое — возвращается False.
ticket = self.env['helpdesk.ticket'].browse(1)
doc = ticket.related_document
if doc:
print(doc._name) # e.g. 'sale.order'
print(doc.name) # e.g. 'S00042'
print(doc.id) # e.g. 15
Это одно из удобств ORM: несмотря на текстовое хранение, при обращении в коде фреймворк разрешает строку в полноценный объект записи.
Ключевые атрибуты поля
Ниже — важные параметры Reference-поля, которые стоит знать при настройке:
- selection: список кортежей, определяющих доступные модели. Может быть и строкой — именем метода, возвращающего такой список динамически.
- string: метка поля, отображаемая пользователю в форме.
- required: делает поле обязательным. Пользователь должен выбрать и тип, и конкретную запись, прежде чем сохранить.
- readonly: запрещает изменение значения из интерфейса. Полезно, когда ссылка устанавливается программно.
- help: подсказка, которая появляется при наведении на заголовок поля — помогает пользователям понять, что выбирать.
- compute: Reference-поле можно вычислять методами Python, как и любое другое поле — это удобно для автоматической установки ссылок по бизнес-правилам.
Фильтрация и поиск
Поскольку значение хранится как текст, фильтры для Reference формируются в виде строковых доменов. Чтобы найти записи, связанные с конкретным документом, нужно указывать строку целиком:
tickets = self.env['helpdesk.ticket'].search([
('related_document', '=', 'sale.order,15')
])
Также можно фильтровать по типу модели с помощью оператора like:
tickets = self.env['helpdesk.ticket'].search([
('related_document', 'like', 'sale.order,')
])
Запомните: поведение фильтрации отличается от Many2one — при проектировании отчётов и вычисляемых полей учитывайте строковый формат хранения.
Практические бизнес-сценарии
Reference особенно полезен там, где одна и та же «контекстная» ссылка должна уметь указывать на разные типы документов — ниже несколько практических сценариев.
1. Тикеты службы поддержки, связанные с любым документом
Служба поддержки работает с разными случаями: спор со счётом, проблема с доставкой, вопросы по контракту или бракованный товар. Вместо множества отдельных полей для каждого типа документов удобнее одно Reference-поле на тикете: агент выбирает тип (Счёт, Заказ продажи, Доставка и т.д.), затем конкретную запись — вся информация собрана в одном месте.
2. CRM-активности, связанные с разными источниками
В продажах задачи и активности могут исходить из лидов, коммерческих предложений, действующих контрактов или кейсов поддержки. Reference-поле в модели активности позволяет привязать задачу к тому документу, откуда она пришла, без жёсткой привязки к одной модели.
3. Заметки и аннотации по разным модулям
Компании часто ведут единый реестр заметок, который должен прикрепляться к клиенту, задаче проекта, производственному ордеру или закупке. Одно Reference-поле на записи заметки решает проблему дублирования моделей заметок для каждого типа документа.
4. Универсальный процесс согласования
При реализации общего workflow согласования запрос должен указывать на документ, подлежащий одобрению: это может быть заказ закупки, отчёт по расходам, заявка на отпуск или договор. Reference на записи запроса позволяет единой логике обработки работать с любыми типами документов.
5. Отчёты по затратам, привязанные к проектам или заказам
В бухгалтерии расходы иногда нужно привязать либо к проекту клиента, либо к заказу продажи. Reference-поле с project.project и sale.order в списке selection даёт бухгалтеру гибкость прикреплять расход к тому документу, который релевантен — часто это важно для консалтинга и сервисных компаний.
Как создать или настроить Reference-поле
Добавление Reference-поля в модель можно выполнить двумя путями: через Odoo Studio без кода или напрямую в Python для полной кастомизации.
Через Odoo Studio
Studio позволяет быстро добавить Reference-поле в форму без программирования: откройте Studio для нужной модели, в панели полей выберите Reference и укажите набор моделей в выпадающем списке. Studio сохраняет такое поле с префиксом x_, как и другие поля, созданные через интерфейс.
Такой способ удобен для прототипов и правок без разработчиков. Но учтите: поля Studio ограничены в продвинутых настройках — например, сложная динамическая логика selection или вычисляемые значения потребуют кода.
Реализация в Python
Для полноценной разработки поле определяют в Python. Ниже пример с динамическим методом selection:
from odoo import api, fields, models
class ApprovalRequest(models.Model):
_name = 'approval.request'
_description = 'Approval Request'
name = fields.Char(string='Request Name', required=True)
@api.model
def _get_document_types(self):
return [
('purchase.order', 'Purchase Order'),
('hr.expense.sheet', 'Expense Report'),
('hr.leave', 'Time Off Request'),
('sale.order', 'Sale Order'),
]
document_ref = fields.Reference(
selection='_get_document_types',
string='Document',
help='Select the document this approval relates to.',
)
Использование метода для selection (передавая его имя строкой) даёт гибкость: можно добавлять условия, проверять установленные модули или формировать список из записей конфигурации.
Создание через XML-RPC API
Reference-поле можно программно создавать и через XML-RPC API Odoo — удобно при развёртывании конфигураций удалённо. Тип поля задаётся как reference, а selection передаётся в виде строкового представления списка:
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_related_document',
'field_description': 'Related Document',
'model_id': model_id,
'ttype': 'reference',
'selection': "[('sale.order', 'Sale Order'), ('purchase.order', 'Purchase Order')]",
'state': 'manual',
}]
)
Обратите внимание: при создании поля через API параметр selection передаётся как строка, которую Odoo интерпретирует как список при чтении из ir.model.fields.
Рекомендации по использованию
Практические рекомендации при работе с Reference-полями.
- Держите selection коротким и релевантным. Не добавляйте все модели подряд. Оставьте только те типы документов, которые действительно имеют смысл — длинный список путает пользователей.
- Если связь всегда с одной моделью — используйте Many2one. Reference нужен именно для полиморфных случаев. Для фиксированных связей Many2one проще, быстрее в запросах и лучше поддерживается отчётами Odoo.
- Всегда проверяйте пустые значения в вычисляемых полях. Если Reference пуст, в Python вернётся False — перед обращением к полю связанной записи обязательно делайте проверку.
- Обрабатывайте «сиротские» ссылки автоматическими задачами. Поскольку база не очищает Reference при удалении записи, имеет смысл настроить плановую задачу или автоматическое действие, которое периодически ищет и очищает устаревшие ссылки.
- Используйте понятные метки в selection. Второй элемент кортежа видит пользователь — пишите «Счёт клиента», а не account.move.
- Документируйте поле в техническом описании. Reference ведёт себя иначе, чем Many2one, — оставьте ясное пояснение, почему выбран Reference и какие модели он связывает.
Распространённые ошибки
Типичные ошибки при работе с Reference-полями, на которые стоит обратить внимание.
Использование доменов как для Many2one
Иногда пытаются писать домен, как для Many2one, например ('document_ref', '=', 15). Это не сработает: в базе хранится строка sale.order,15, а не чистый id. При формировании доменов нужно составлять полную строку.
Забывание о том, что удалённые записи оставляют «сиротские» значения
Из-за отсутствия FK удаление записи не очищает Reference. Если заказ удалён, поле всё ещё содержит sale.order,42; при чтении оно вернёт False. Логика, ожидающая валидную ссылку, должна обрабатывать такой сценарий.
Чрезмерное использование динамического выбора всех моделей
Можно вернуть из ir.model список всех установленных моделей, но это почти всегда слишком много для пользовательского поля. Выпадающий список с сотнями значений путает и ведёт к ошибкам ввода. Лучше ограничьте набор до осмысленных типов.
Ожидание нативной группировки в отчётах
Reference хранится как текст и не является FK — стандартные group-by и pivot в Odoo не работают с ним так же, как с Many2one. Если нужно группировать по типу связанного документа, добавьте вычисляемое поле, извлекающее имя модели, или напишите кастомную логику.
Путаница между Reference и Many2one в Odoo Studio
В Studio некоторые пользователи путают Reference и Many2one, поскольку оба позволяют связать запись с другой. Главное различие: Many2one фиксирует одну модель при создании поля, Reference даёт пользователю выбирать модель при заполнении. Если вы ошибочно создали Many2one вместо Reference, придётся заново добавить поле.
Заключение
Reference-поле закрывает ту нишу, которую Many2one не покрывает: когда ссылка должна быть гибкой и указывать на разные типы документов, Reference — правильный инструмент. Его легко задать в Studio для no-code сценариев и полноценно интегрировать через Python для сложных задач.
Главные вещи, которые стоит держать в уме: формат хранения — модель,id в виде строки, отсутствие автоматической очистки при удалении и необходимость формировать фильтры строковыми значениями. Поняв эти особенности, вы получите предсказуемое и надёжное поведение поля в модели данных.
Независимо от того, строите вы общий процесс согласований, связываете тикеты поддержки с разными документами или создаёте гибкую систему заметок для нескольких модулей, Reference помогает избежать дублирования логики и упрощает архитектуру.
Нужна помощь с внедрением Odoo?
В компании Dasolo мы помогаем организациям внедрять, кастомизировать и оптимизировать Odoo под реальные бизнес-процессы. Нужна настройка полей, проектирование модели данных с нуля или расширение существующей системы — у нашей команды есть технический опыт для качественного выполнения задач.
Если вы работаете над проектом на Odoo и нужен совет по типам полей, архитектуре данных или лучшим практикам разработки — напишите нам. Мы разберём вашу ситуацию и предложим оптимальное решение.