В Odoo реализованы полезные классы и миксины, которые облегчают добавление часто используемых функций к вашим объектам. В данном руководстве будет детально описано большинство из них, с примерами и вариантами использования.
Функции обмена сообщениями
Интеграция с системой обмена сообщениями
Базовая система обмена сообщениями
Интеграция функций обмена сообщениями в вашу модель чрезвычайно проста. Просто наследуйте модель mail.thread
и добавляйте поля сообщений (и соответствующие им виджеты) в ваше представление Form.
Пример
Давайте создадим простую модель, представляющую командировки. Деловые поездки, как правило, касаются нескольких людей и требуют обсуждений. Добавим поддержку для обмена сообщениями к этой модели.
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread']
_description = 'Business Trip'
name = fields.Char()
partner_id = fields.Many2one('res.partner', 'Responsible')
guest_ids = fields.Many2many('res.partner', 'Participants')
В представлении Form:
<record id="businness_trip_form" model="ir.ui.view">
<field name="name">business.trip.form</field>
<field name="model">business.trip</field>
<field name="arch" type="xml">
<form string="Business Trip">
<!-- Your usual form view goes here
...
Then comes chatter integration -->
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
После того, как вы добавили поддержку чата в вашу модель, пользователи могут легко добавлять в любой записи вашей модели сообщения или внутренние заметки, о каждой их них отправится уведомление (всем подписавшимся на уведомление, и сотрудникам (base.group_user) в качестве внутренней заметки). Если ваш почтовый шлюз catchall адрес правильно настроены, эти уведомления будут отправляться непосредственно от вашего почтового клиента; система автоматической маршрутизации почты распределит ответ на письмо к нужному треду.
На стороне сервера несколько вспомогательных функций помогают вам легко отправлять сообщения и управлять подписчиками к вашей записи:
Отправка сообщений
message_post(self, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, **kwargs)
Публикует новое сообщение в существующей чате, возвращает ID новой записи mail.message
.
- body (
str
) – тело сообщения, как правило это голый HTML, который будет потом проверен - message_type (
str
) – см. поле `` mail_message.message_type`` - parent_id (
int
) – обрабатывает ответ на предыдущее сообщение, добавляя родительских партнеров к сообщению в случае приватного обсуждения - attachments (
list
(
tuple
(
str
,
str
)
)
) – список кортежей , описывающих почтовые вложения в виде(name, content)
, где content НЕ кодируется base64 - **kwargs – дополнительные ключевые аргументы будут использоваться в качестве значений по умолчанию для полей в записи
mail.message
mail.message
message_post_with_view(views_or_xmlid, **kwargs):
Вспомогательный метод для отправки почты или публикации сообщения с использованием view_id
для рендеринга с использованием генератора шаблонов ir.qweb
. Этот метод является автономным, поскольку в шаблоне и компоновщике нет ничего, что позволяло бы обрабатывать представления в пакетном режиме. Этот метод, вероятно, исчезнет, когда шаблоны будут обрабатывать пользовательские интерфейсы ir.ui.view
.
str
) – external id или запись представления, которое должно быть отправленоmessage_post_with_template(template_id, **kwargs)
Вспомогательный метод для отправки почты с шаблоном
- template_id – ID шаблона, для рендеринга при создании тела сообщения
- **kwargs – параметр для создания
mail.compose.message
визарда (который унаследован отmail.message
)
Получение сообщений
Эти методы вызываются, когда новое письмо обрабатывается почтовым шлюзом. Эти письма либо создают новый тред (если они поступают на alias), либо добавляют ответ в уже существующий. Их переопределение позволяет вам устанавливать значения в запись треда в зависимости от значений самого письма (например, обновление даты или адреса электронной почты, добавить адреса из поля «Копии» в качестве подписчиков и т. д.).
message_new(msg_dict, custom_values=None)
Метод message_process
вызывается при получении нового сообщения для предоставленной модели с тредом, если сообщение не принадлежало существующему треду.
Поведением по умолчанию является создание новой записи соответствующей модели (на основе базовой информации, извлеченной из сообщения). Дополнительное поведение может быть реализовано переопределением этого метода.
- msg_dict (
dict
) – словарь содержащий детали письма и его вложения. Подробнее смотритеmessage_process
иmail.message.parse
- custom_values (
dict
) – необязательный словарь дополнительных значений полей для передачи в методcreate()
при создании новой записи в треде; Будьте внимательны, эти значения могут переопределять любые другие значения, поступающие из сообщения
message_update(msg_dict, update_vals=None)
Метод message_process
вызывается при получении нового сообщения для существующего треда. По умолчанию используется обновление записей update_vals
, взятых из входящего письма.
Дополнительное поведение может быть реализовано переопределением этого метода.
Управление подписчиками
message_subscribe(partner_ids=None, channel_ids=None, subtype_ids=None, force=True)
Добавляет партнеров к списку подписчиков записи.
- partner_ids (
list
(
int
)
) – ID партнеров, которые будут подписаны на запись - channel_ids (
list
(
int
)
) – ID каналов, которые будут подписаны на запись - subtype_ids (
list
(
int
)
) – ID подтипов, на которые будут подписаны каналы/партнеры (по умолчанию используются подтипы по умолчанию, еслиNone
) - force – если True, удаляет существующих подписчиков, перед созданием новых, используя подтипы, указанные в параметрах
message_unsubscribe(partner_ids=None, channel_ids=None)
Удаляет партнеров из списка подписчиков записи.
message_unsubscribe_users(user_ids=None)
Враппер для message_subscribe
используя пользователей.
Журналирование изменений
Модуль mail
добавляет мощную систему отслеживания изменения значения полей, позволяя вам вести записи изменения определенных полей в треде записи. Чтобы добавить отслеживание для поля, просто установите для атрибута отслеживания значение True.
Пример
Отслеживаем изменения имени и ответственных за нашими командировками:
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread']
_description = 'Business Trip'
name = fields.Char(tracking=True)
partner_id = fields.Many2one('res.partner', 'Responsible',
tracking=True)
guest_ids = fields.Many2many('res.partner', 'Participants')
Отныне каждое изменение имени или ответственного за командировку, будет создавать запись в тред. В уведомлении также будет отображаться поле name
, чтобы добавить контекст в уведомление (даже если имя не изменилось).
Подтипы
Подтипы дают вам более точечный контроль над сообщениями. Подтипы действуют как система классификации уведомлений, что позволяет подписчикам документа настраивать подтип уведомлений, которые они хотят получать.
Подтипы создаются как данные в вашем модуле; модель имеет следующие поля:
name
(обязательный параметр) -Char
- имя подтипа, отображается во всплывающем сообщении настройки уведомлений
description
-Char
- описание, которое будет добавлено в сообщение, отправленное для этого подтипа. Если не существует, то вместо него будет добавлено имя
internal
-Boolean
- сообщения с внутренними подтипами будут видны только сотрудникам, которые также являются членами группы
base.group_user
parent_id
-Many2one
- ссылки на подтипы для автоматической подписки; Например, подтипы проекта связаны с подтипами задач через эту ссылку. Когда кто-то подписывается на проект, он будет подписан на все задачи данного проекта
relation_field
-Char
- в качестве примера при связывании подтипов проекта и задач, для связи используется поле project_id в задаче
res_model
-Char
- модель, к которой относится подтип; Если False, этот подтип применяется ко всем моделям
default
-Boolean
- определяет будет ли этот подтип активирован по умолчанию при подписке
sequence
-Integer
- используется для упорядочивания подтипов во всплывающем меню настройки уведомлений
hidden
-Boolean
- определяет будет ли подтип скрыт во всплывающем окне настройки уведомлений
Взаимодействие подтипов с отслеживанием поля позволяет подписаться на различные виды уведомлений в зависимости от того, что может заинтересовать пользователей. Чтобы сделать это, вы можете переопределить функцию _track_subtype()
:
_track_subtype(init_values)
Укажите подтип, вызванный изменениями в записи, в соответствии со значениями, которые были обновлены.
dict
) – исходные значения записи; в словаре присутствуют только измененные поляПример
Давайте добавим поле state
в нашем примере и инициируем уведомление с определенным подтипом, когда это значение изменится.
Сначала давайте определим наш подтип:
<record id="mt_state_change" model="mail.message.subtype">
<field name="name">Trip confirmed</field>
<field name="res_model">business.trip</field>
<field name="default" eval="True"/>
<field name="description">Business Trip confirmed!</field>
</record>
Затем нам нужно переопределить функцию track_subtype()
. Эта функция вызывается системой отслеживания, чтобы знать, какой подтип должен использоваться в зависимости от применяемого в настоящее время изменения. В нашем случае мы хотим использовать наш новый новый подтип, когда поле state
изменяется c draft на confirm:
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread']
_description = 'Business Trip'
name = fields.Char(tracking=True)
partner_id = fields.Many2one('res.partner', 'Responsible',
tracking=True)
guest_ids = fields.Many2many('res.partner', 'Participants')
state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')],
tracking=True)
def _track_subtype(self, init_values):
# init_values contains the modified fields' values before the changes
#
# the applied values can be accessed on the record as they are already
# in cache
self.ensure_one()
if 'state' in init_values and self.state == 'confirmed':
return self.env.ref('my_module.mt_state_change')
return super(BusinessTrip, self)._track_subtype(init_values)
Настройка уведомлений
При отправке уведомлений подписчикам очень удобно добавлять кнопки в шаблон, чтобы разрешить быстрые действия непосредственно из электронной почты. Даже простая кнопка с прямой ссылкой на представление Form нужной записи может оказаться полезной ; Хотя в большинстве случаев это не нужно.
Система уведомлений позволяет настраивать шаблоны уведомлений следующими способами:
- Access Buttons: эти кнопки видны в верхней части письма и позволяют получателю получить доступ прямо в представление Form нужной записи
- Follow Buttons: эти кнопки позволяют получателю быстро подписаться на запись
- Кнопки отмены подписки: эти кнопки позволяют получателю отписаться от записи
- Кнопки кастомных действий: эти кнопки позволяют перейти на необходимую страницу и совершать полезные действия, доступные непосредственно из электронной почты (например, преобразовать инициативу в возможность, проверить список расходов для менеджера и т.д.),
Настройки этих кнопок можно применять к различным группам, которые вы можете определить самостоятельно, переопределив функцию _notification_recipients
.
_notification_recipients(message, groups)
Укажите подтип, вызванный изменениями в записи, в соответствии со значениями, которые были обновлены.
- message (
record
) –mail.message
запись в данный момент отправляется - groups (
list
(
tuple
)
) – Список кортежей формы (group_name
,group_func
,group_data
) где:group_name
- это идентификатор, используемый только для возможности переопределения и манипулирования группами. Группы по умолчанию - этоuser
(получатели связанные с пользователем из группы сотрудники),portal
(получатели связанные с пользователями из группы портал) иcustomer
(получатели не связанные с кем либо из пользователей). Примером использования переопределения может быть добавление группы, связанной с res.groups, такой как Hr Officers, для установки для них определенных кнопок действий.group_func
- это указатель функции, принимающий запись партнера в качестве параметра. Этот метод будет применяться к получателям, чтобы узнать, принадлежат ли они к данной группе или нет. Сохраняется только первая подходящая группа. Порядок выполнения происходит в порядке самого списка.group_data
- это параметр, содержащий параметры для письма со следующими возможными ключ - значене:has_button_access
, следует ли отображать ссылку на документ в письме. True по умолчанию для новых групп, False для портала/клиента.button_access
словарь с URL-адресом и названием кнопкиhas_button_follow
, следует ли отображать Follow в письме (если получатель в данный момент не подписан на запись). True по умолчанию для новых групп, False для портала/клиента. -button_follow
словарь с url и заголовком кнопкиhas_button_unfollow
, следует ли отображать Unfollow в электронной почте (если получатель в данный момент подписан на запись). True по умолчанию для новых групп, False для портала/клиента.button_unfollow
словарь с URL-адресом и названием кнопки - список действий кнопок для отображения в письме. Каждое действие - это словарь, содержащий url и название кнопки.
URL-адреса в списке действий могут быть сгенерированы автоматически, с помощью вызова функции _notification_link_helper ()
:
_notification_link_helper(self, link_type, **kwargs)
Генерирует ссылку указанного типа для текущей записи (или на конкретную запись, если установлены аргументы model
и res_id
).
str
) – тип ссылки; может быть любым из этих значений:
view
- ссылка на представление Form записи
assign
- назначить авторизованного пользователя на поле
user_id
записи (если оно существует) follow
- название говорит само за себя
unfollow
- название говорит само за себя
method
- вызвать метод записи; имя метода должно быть представлено как ключевой аргумент
method
new
- открыть пустое представление Form для новой записи; вы можете указать конкретное действие, указав его id (id в таблице базы данных или external id) в качестве аргументе
action_id
Пример
Давайте добавим пользовательскую кнопку в уведомление об изменении состояния командировки; Эта кнопка сбросит состояние в состояние Draft и будет видна только члену группы (воображаемой) Travel Manager (business.group_trip_manager
)
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread', 'mail.alias.mixin']
_description = 'Business Trip'
# Pevious code goes here
def action_cancel(self):
self.write({'state': 'draft'})
def _notification_recipients(self, message, groups):
""" Handle Trip Manager recipients that can cancel the trip at the last
minute and kill all the fun. """
groups = super(BusinessTrip, self)._notification_recipients(message, groups)
self.ensure_one()
if self.state == 'confirmed':
app_action = self._notification_link_helper('method',
method='action_cancel')
trip_actions = [{'url': app_action, 'title': _('Cancel')}]
new_group = (
'group_trip_manager',
lambda partner: bool(partner.user_ids) and
any(user.has_group('business.group_trip_manager')
for user in partner.user_ids),
{
'actions': trip_actions,
})
return [new_group] + groups
Обратите внимание, что могу определить исполняемую функцию вне этого метода и определить глобальную функцию вместо продемонстрированной лямбда-функции, но ради краткости в документации, я выбираю вариант с лямбда-функцией.
Переопределение значений по умолчанию
Существует несколько способов настроить поведение моделей mail.thread
, включая (но не ограничиваясь ими):
_mail_post_access
- атрибутModel
- требуемые права доступа - иметь возможность публиковать сообщение в модели; По умолчанию необходим доступ
write
, можно также установитьread
- Ключи контекста:
Эти ключи контекста можно использовать для частичного контроля функций
mail.thread
, таких как автоматическая подписка или отслеживание полей во время вызовов методовcreate()
илиwrite()
(или любого другого метода, где он может быть полезным).mail_create_nosubscribe
: при вызове методаcreate `` или ``message_post
не подписывать текущего пользователя на тред записиmail_create_nolog
: при вызове методаcreate
не создает автоматическое сообщение „<Document> создан“mail_notrack
: при вызове методаcreate
иwrite
не выполнять отслеживание значений при создании сообщенийtracking_disable
: при вызове методаcreate
иwrite
не выполняет никаких функций MailThread (автоматическая подписка, отслеживание, публикация, …)mail_auto_delete
: автоматическое удаление почтовых уведомлений; по умолчанию - Truemail_notify_force_send
: если менее 50 уведомлений электронной почты для отправки, отправьте их напрямую вместо использования очереди; по умолчанию - Truemail_notify_user_signature
: добавить текущую подпись пользователя в уведомлениях по email; по умолчанию - True
Почтовые алиасы
Алиасы - это настраиваемые адреса электронной почты, связанные с определенной записью (которая наследует модель mail.alias.mixin
). При поступлении письма на этот ящик будет создаваться новая запись. Это простой способ сделать вашу систему доступной извне, позволяя пользователям или клиентам быстро создавать записи в вашей базе данных без необходимости прямого подключения к Odoo.
Алиасы VS Шлюз входящей почты
Некоторые люди используютШлюз входящей почты для этой же цели. Для использования алиасов вам также нужен правильно сконфигурированный почтовый шлюз, однако достаточно одного почтового ящика, так как вся маршрутизация будет проходить внутри Odoo. Алиасы имеют несколько преимуществ по сравнению с отдельным шлюзом:
- Легче в настройке
- Один Шлюз входящей почты может использоваться множеством алиасов; Это избавляет от необходимости настраивать несколько ящиков на вашем домене (вся конфигурация выполняется внутри Odoo)
- Нет необходимости настраивать права доступа к системе для настройки алиасов
- Более логичное использование
- Настраивается в связанной записи, а не в меню Параметры
- Легче переопределить серверную часть
- Модель миксина построена так, чтобы сделать проще извлечение данных из входящих писем, чем с помощью почтового шлюза.
Поддержка интеграции алиасов
Алиасы обычно настраиваются на родительской модели, которая затем создает определенную запись при обращении по электронной почте. Например, Project имеет алиасы для создания задач или ишуек, Sales Team имеет алиасы для генерации лидов.
Примечание
Модель, которая будет создана алиасов должна наследовать модель mail_thread
.
Поддержка алиаса добавляется путем наследования mail.alias.mixin
; этот mixin будет создавать новую запись mail.alias
для каждой записи родительского класса при создании (например, каждая запись project.project
имеет свою запись mail.alias
, инициализированную при создании).
Примечание
Алиасы также могут быть созданы вручную и поддерживаться простым Many2one
. В этом руководстве предполагается, что вам нужна более полная интеграция с автоматическим созданием алиаса, значением по умолчанию для записи и т.д.
В отличие от наследования mail.thread
, mail.alias.mixin
требует некоторых определенных переопределений для правильной работы. Эти переопределения будут указывать значения созданного алиаса, такие как тип записи, которую он должен создать, и, возможно, некоторые значения по умолчанию, которые могут иметь эти записи в зависимости от родительского объекта:
get_alias_model_name(vals)
Возвращает имя модели для алиаса. Входящие письма, которые не являются ответом на существующие записи, приведут к созданию новой записи этой модели алиасов. Значение может зависеть от vals
, словаря значений, переданных create
, когда создается запись этой модели.
vals
) – значения вновь созданной записи, которые будет содержать алиасget_alias_values()
Возвращает значения для создания алиаса или для изменений записи алиаса после его создания. Хотя это и не является полностью обязательным, обычно требуется убедиться, что вновь созданные записи будут связаны с родительским алиасом (т.е. задачами, создаваемыми в правильном проекте), путем установки словаря значений по умолчанию в поле алиаса alias_defaults
.
Переопределение get_alias_values()
особенно интересно, так как оно позволяет вам легко изменять поведение ваших алиасов. Среди полей, которые могут быть установлены в алиасе, особый интерес представляют следующие:
alias_name
-Char
- имя алиаса электронной почты, например, «jobs», если вы хотите перехватывать электронные сообщения для <jobs@example.odoo.com>
alias_user_id
-Many2one
(res.users
)- владелец записей, созданных после получения электронных писем по этому алиасу; если это поле не установлено, система попытается найти нужного владельца на основе адреса отправителя (From) или будет использовать учетную запись администратора, если для этого адреса не найден системный пользователь
alias_defaults
-Text
- Словарь Python, который будет выполняться для предоставления значений по умолчанию при создании новых записей для этого алиаса
alias_force_thread_id
-Integer
- необязательный ID треда (записи), к которому будут прикреплены все входящие сообщения, даже если они не ответили на него; Если установлено, это полностью отключит создание новых записей
alias_contact
-Selection
Политика публикации сообщения в документе с помощью почтового шлюза
- everyone: может опубликовать любой
- partners: только партнеры, прошедшие проверку подлинности
- followers: только подписчики соответствующего документа или участники подписанных каналов
Обратите внимание, что алиасы используют наследование делегирования, это означает, что, хотя алиас хранится в другой таблице, у вас есть доступ ко всем этим полям непосредственно из вашего родительского объекта. Это позволяет вам легко настроить ваш алиас из представления Form записи.
Пример
Давайте добавим алиас в наш класс командировки, чтобы создавать документы расходов «на лету» по электронной почте.
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread', 'mail.alias.mixin']
_description = 'Business Trip'
name = fields.Char(tracking=True)
partner_id = fields.Many2one('res.partner', 'Responsible',
tracking=True)
guest_ids = fields.Many2many('res.partner', 'Participants')
state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')],
tracking=True)
expense_ids = fields.One2many('business.expense', 'trip_id', 'Expenses')
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict",
required=True)
def get_alias_model_name(self, vals):
""" Specify the model that will get created when the alias receives a message """
return 'business.expense'
def get_alias_values(self):
""" Specify some default values that will be set in the alias at its creation """
values = super(BusinessTrip, self).get_alias_values()
# alias_defaults holds a dictionnary that will be written
# to all records created by this alias
#
# in this case, we want all expense records sent to a trip alias
# to be linked to the corresponding business trip
values['alias_defaults'] = {'trip_id': self.id}
# we only want followers of the trip to be able to post expenses
# by default
values['alias_contact'] = 'followers'
return values
class BusinessExpense(models.Model):
_name = 'business.expense'
_inherit = ['mail.thread']
_description = 'Business Expense'
name = fields.Char()
amount = fields.Float('Amount')
trip_id = fields.Many2one('business.trip', 'Business Trip')
partner_id = fields.Many2one('res.partner', 'Created by')
Мы хотели бы, чтобы наш алиас легко настраивался из представления Form в наших командировках, поэтому добавим следующее в наше представление Form:
<page string="Emails">
<group name="group_alias">
<label for="alias_name" string="Email Alias"/>
<div name="alias_def">
<!-- display a link while in view mode and a configurable field
while in edit mode -->
<field name="alias_id" class="oe_read_only oe_inline"
string="Email Alias" required="0"/>
<div class="oe_edit_only oe_inline" name="edit_alias"
style="display: inline;" >
<field name="alias_name" class="oe_inline"/>
@
<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</div>
<field name="alias_contact" class="oe_inline"
string="Accept Emails From"/>
</group>
</page>
Теперь мы можем изменить адрес алиаса непосредственно представления Form и указать, кто может отправлять электронные письма на алиас.
Затем мы можем переопределить message_new ()
в нашей модели расхода, чтобы получать значения из письма, когда будут создаваться документы:
class BusinessExpense(models.Model):
# Previous code goes here
# ...
def message_new(self, msg, custom_values=None):
""" Override to set values according to the email.
In this simple example, we simply use the email title as the name
of the expense, try to find a partner with this email address and
do a regex match to find the amount of the expense."""
name = msg_dict.get('subject', 'New Expense')
# Match the last occurence of a float in the string
# Example: '50.3 bar 34.5' becomes '34.5'. This is potentially the price
# to encode on the expense. If not, take 1.0 instead
amount_pattern = '(\d+(\.\d*)?|\.\d+)'
expense_price = re.findall(amount_pattern, name)
price = expense_price and float(expense_price[-1][0]) or 1.0
# find the partner by looking for it's email
partner = self.env['res.partner'].search([('email', 'ilike', email_address)],
limit=1)
defaults = {
'name': name,
'amount': price,
'partner_id': partner.id
}
defaults.update(custom_values or {})
res = super(BusinessExpense, self).message_new(msg, custom_values=defaults)
return res
Отслеживание активностей
Активность - это действия, которые пользователи должны сделать, например, совершить телефонный звонок или организовать встречу. Активности поставляются вместе с модулем mail, поскольку они интегрированы в Chatter, но не связаны с mail.thread. Активность - это записи класса mail.activity
, которые имеют тип (mail.activity.type
), имя, описание, запланированное время. Ожидающие активности видны над историей сообщений в блоке обсуждения.
Вы можете интегрировать активности, используя класс mail.activity.mixin
, в ваш объект и конкретные виджеты, чтобы отображать их (через поле activity_ids
) в представлении Form или Kanban ваших записей (mail_activity
и kanban_activity
соответственно).
Пример
Организация деловой поездки - это утомительный процесс, и отслеживание необходимых действий, таких как заказ авиабилетов или такси для аэропорта, может быть полезным.. Для этого мы добавим действия mixin действий в нашу модель и отобразим следующие запланированные действия в истории сообщений нашей поездки.
class BusinessTrip(models.Model):
_name = 'business.trip'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Business Trip'
name = fields.Char()
# [...]
Мы изменяем представление Form наших поездок, чтобы показать их следующие активности:
<record id="businness_trip_form" model="ir.ui.view">
<field name="name">business.trip.form</field>
<field name="model">business.trip</field>
<field name="arch" type="xml">
<form string="Business Trip">
<!-- Your usual form view goes here -->
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
Вы можете найти конкретные примеры интеграции в следующих моделях:
crm.lead
в приложении CRMsale.order
в приложении Продажи (sale)project.task
в приложении Управление проектами (poject)
Дополнения для сайта
Отслеживание заходов
Класс utm.mixin
можно использовать для отслеживания онлайн маркетинговых кампаний с помощью аргументов в ссылках на указанные ресурсы. Миксин добавляет 3 поля к вашей модели:
campaign_id
:Many2one
ссылка наutm.campaign
(т.е. Christmas_Special, Fall_Collection и т.д.)source_id
:Many2one
ссылка наutm.source
(т.е. Search Engine, mailing list, и т.д.)medium_id
:Many2one
ссылка наutm.medium
(т.е. Snail Mail, e-Mail, social network update, и т.д.)
У этих моделей есть одно поле name
(т.е. Они просто различают кампании, но не имеют какого-либо конкретного поведения).
Когда клиент посещает ваш сайт с указанными в URL-адресе параметрами (например, http://www.odoo.com/?campaign_id=mixin_talk&source_id=www.odoo.com&medium_id=website), на веб-сайте посетителю для этих параметров устанавливаются три куки. Как только объект, который наследует utm.mixin, создается с веб-сайта (например, создание лида из заполненной формы, отклик на вакансию и т.д.), то код utm.mixin запускается и извлекает значения из cookie, чтобы установить их в созданной записи. После этого вы можете использовать поля campaign/source/medium как любое другое поле при определении отчетов и представлений (группировка и т.д.).
Чтобы расширить это поведение, просто добавьте реляционное поле в простую модель (модель должна поддерживать быстрое создание (то есть вызов create()
с единственным значением name
) и расширить функцию tracking_fields()
:
class UtmMyTrack(models.Model):
_name = 'my_module.my_track'
_description = 'My Tracking Object'
name = fields.Char(string='Name', required=True)
class MyModel(models.Models):
_name = 'my_module.my_model'
_inherit = ['utm.mixin']
_description = 'My Tracked Object'
my_field = fields.Many2one('my_module.my_track', 'My Field')
@api.model
def tracking_fields(self):
result = super(MyModel, self).tracking_fields()
result.append([
# ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES")
('my_field', 'my_field', 'odoo_utm_my_field')
])
return result
Это скажет системе создать cookie с именем odoo_utm_my_field со значением, найденным в параметре url my_field
; как только новая запись этой модели создается вызовом из формы веб-сайта, общее переопределение метода create()
модели utm.mixin
будет получать значения по умолчанию для этого поля из файла cookie (и запись my_module.my_track
будет создаваться на лету, если она еще не существует).
Вы можете найти конкретные примеры интеграции в следующих моделях:
crm.lead
в приложении CRMhr.applicant
в отклике на вакансию (hr_recruitment)helpdesk.ticket
в приложении Helpdesk (helpdesk - доступно только в Odoo Enterprise)
Видимость на веб-сайте
Вы можете легко добавить триггер видимости на веб-сайте к любой вашей записи. Хотя этот mixin довольно просто реализовать вручную, он чаще всего используется после наследования mail.thread
; что говорит о его полезности. Типичным примером использования этого mixin является любой объект с фронт-енд страницей; возможность контролировать видимость страницы позволяет вам не спешить во время редактирования страницы и публиковать ее только после того, как она будет готова.
Чтобы включить данный функционал, вам нужно только наследовать website.published.mixin
:
class BlogPost(models.Model):
_name = "blog.post"
_description = "Blog Post"
_inherit = ['website.published.mixin']
Этот mixin добавляет 2 поля к вашей модели:
website_published
:Boolean
поле, которое содержит статус публикацииwebsite_url
:Char
поле, которое хранит URL-адрес, по которому осуществляется доступ к объекту
Обратите внимание, что последнее поле является вычисляемым и должно быть описано для вашего класса:
def _compute_website_url(self):
for blog_post in self:
blog_post.website_url = "/blog/%s" % (log_post.blog_id)
Как только это сделано, вам нужно сделать триггер доступным, адаптировав интерфейс и представления бекэнда. В бэкэнд обычно добавляется кнопка:
<button class="oe_stat_button" name="website_publish_button"
type="object" icon="fa-globe">
<field name="website_published" widget="website_button"/>
</button>
Во фронтэнде необходимы проверки безопасности, чтобы избежать появления кнопок «Редактирование» для посетителей сайта:
<div id="website_published_button" class="float-right"
groups="base.group_website_publisher"> <!-- or any other meaningful group -->
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="action" t-value="'blog.blog_post_action'"/>
</t>
</div>
Обратите внимание, что вы должны передать свой объект в качестве переменной object
в шаблон; В этом примере запись blog.post
была передана в качестве переменной blog_post
в механизм рендеринга qweb
, необходимо указать это для шаблона управления публикацией. Переменная publish_edit
позволяет фронтэнд кнопке связываться с бэкэнд (позволяя легко переключаться между фронтэндом и бэкэндом и наоборот); Если установлено, вы должны указать полный external id действия, которое вы хотите вызвать в бэкэнд в переменной action
(обратите внимание, что для модели должно существовать представление Form).
Действие website_publish_button
определено в mixin и адаптирует его поведение к вашему объекту: если у класса есть вычисляемое значение URL website_url
, пользователь перенаправляется на фронтэнд при нажатии на кнопку; Пользователь может затем опубликовать страницу непосредственно с сайта. Это гарантирует, что никакая онлайн-публикация не произойдет случайно. Если нет вычисляемой функции, булевое web_published
просто устанавливается.
Метаданные веб-сайта
Этот простой mixin просто позволяет вам легко добавить метаданные к вашим страницам сайта.
class BlogPost(models.Model):
_name = "blog.post"
_description = "Blog Post"
_inherit = ['website.seo.metadata', 'website.published.mixin']
Этот mixin добавляет 3 поля к вашей модели:
website_meta_title
:Char
поле, которое позволяет вам установить дополнительный заголовок title страницыwebsite_meta_description
:Char
поле, которое содержит краткое описание страницы (для мета тега description). Часто используется в результатах поисковых системwebsite_meta_keywords
:Char
поле, которое содержит несколько ключевых слов, чтобы помочь вашей странице более точно классифицироваться поисковыми системами; Инструмент Promote (продвижение) поможет вам легко выбрать лексически связанные ключевые слова
Эти поля можно редактировать во внешнем интерфейсе с помощью инструмента Promote (Продвижение) на панели инструментов Редактора. Настройка этих полей поможет поисковым системам лучше индексировать ваши страницы. Обратите внимание, что поисковые системы не основывают свои результаты только на этих метаданных.
Другие
Рейтинг Клиентов
Миксин рейтинга позволяет автоматически отправлять письма email для запроса рейтинга у клиента, автоматически транслирует их в канабан процесс и агрегирует статистику по рейтингам.
Добавление рейтинга к вашей модели
Чтобы добавить поддержку рейтинга, просто наследуйте модель rating.mixin
:
class MyModel(models.Models):
_name = 'my_module.my_model'
_inherit = ['rating.mixin', 'mail.thread']
user_id = fields.Many2one('res.users', 'Responsible')
partner_id = fields.Many2one('res.partner', 'Customer')
Поведение миксина адаптируется к вашей модели:
Запись
rating.rating
будет связана с полемpartner_id
вашей модели (если это поле присутствует).- это поведение может быть переопределено функцией
rating_get_partner_id()
, если вы используете другое поле, отличное отpartner_id
- это поведение может быть переопределено функцией
Запись
rating.rating
будет связана с партнером через полеuser_id
вашей модели (если поле присутствует) (то есть, партнер, который будет оценивать)- это поведение можно переопределить с помощью функции
rating_get_rated_partner_id()
, если вы используете другое поле, отличное отuser_id
(обратите внимание, что функция должна возвращатьres.partner
, и поuser_id
автоматически выбирать партнера)
- это поведение можно переопределить с помощью функции
- История чата будет отображать рейтинговое событие (если ваша модель наследует
mail.thread
)
Отправить запрос рейтинга по электронной почте
Если вы хотите отправить письмо для запроса рейтинга, просто сгенерируйте сообщение со ссылками на объект рейтинга. Самый простой шаблон может выглядеть так:
<record id="rating_my_model_email_template" model="mail.template">
<field name="name">My Model: Rating Request</field>
<field name="email_from">${object.rating_get_rated_partner_id().email or '' | safe}</field>
<field name="subject">Service Rating Request</field>
<field name="model_id" ref="my_module.model_my_model"/>
<field name="partner_to" >${object.rating_get_partner_id().id}</field>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
% set access_token = object.rating_get_access_token()
<p>Hi,</p>
<p>How satsified are you?</p>
<ul>
<li><a href="/rating/${access_token}/10">Satisfied</a></li>
<li><a href="/rating/${access_token}/5">Not satisfied</a></li>
<li><a href="/rating/${access_token}/1">Very unsatisfied</a></li>
</ul>
]]></field>
</record>
Затем ваш клиент получит электронное письмо со ссылками на веб-сайт, через которую можно дать обратную связь о взаимодействии с вашими пользователями (включая текстовый отзыв).
Затем вы можете легко интегрировать свои рейтинги в представление в виде формы, определив действие для рейтингов:
<record id="rating_rating_action_my_model" model="ir.actions.act_window">
<field name="name">Customer Ratings</field>
<field name="res_model">rating.rating</field>
<field name="view_mode">kanban,pivot,graph</field>
<field name="domain">[('res_model', '=', 'my_module.my_model'), ('res_id', '=', active_id), ('consumed', '=', True)]</field>
</record>
<record id="my_module_my_model_view_form_inherit_rating" model="ir.ui.view">
<field name="name">my_module.my_model.view.form.inherit.rating</field>
<field name="model">my_module.my_model</field>
<field name="inherit_id" ref="my_module.my_model_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="%(rating_rating_action_my_model)d" type="action"
class="oe_stat_button" icon="fa-smile-o">
<field name="rating_count" string="Rating" widget="statinfo"/>
</button>
</xpath>
</field>
</record>
Обратите внимание, что для рейтингов доступны представления по умолчанию (Kanban, Pivot, Graph), которые позволяют быстро взглянуть на оценки клиентов.
Вы можете найти конкретные примеры интеграции в следующих моделях:
project.task
в приложении Управление проектов (rating_project)helpdesk.ticket
в приложении Helpdesk (helpdesk - доступно только в Odoo Enterprise)