[IMP] mail: cleanup _notify_get_recipients code bits

This commit contains mainly code cleaning, docstrings and a small split
for notification tool methods. In this commit we

  * make some notification groups variable explicit;
  * move the filler of groups into its own submethod to ease being called
    from other code (to be used soon);
  * fix some strange overrides or code manipulation;
  * propagate some additional parameters to ease future commits that will
    improve rendering of groups-based notification emails;
  * cleanup, fixup and improve docstrings;

This does not change anything from functional point of view, just preparing
further work.

Task-3046371 (Mail: Better Language Support in Composer)

Part-of: odoo/odoo#106177
This commit is contained in:
Thibault Delavallée
2023-03-09 15:54:12 +01:00
parent 983ac6de70
commit c088d5423e
20 changed files with 378 additions and 224 deletions
+9 -3
View File
@@ -1924,10 +1924,12 @@ class Lead(models.Model):
_('Deadline: %s', self.date_deadline.strftime(get_lang(self.env).date_format)))
return render_context
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Handle salesman recipients that can convert leads into opportunities
and set opportunities as won / lost. """
groups = super(Lead, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
@@ -1955,7 +1957,11 @@ class Lead(models.Model):
new_group = (
'group_sale_salesman',
lambda pdata: pdata['type'] == 'user' and salesman_group_id in pdata['groups'],
{'actions': salesman_actions}
{
'actions': salesman_actions,
'active': True,
'has_button_access': True,
}
)
return [new_group] + groups
+9 -3
View File
@@ -1648,10 +1648,12 @@ class HolidaysRequest(models.Model):
return leave_notif_subtype or self.env.ref('hr_holidays.mt_leave')
return super(HolidaysRequest, self)._track_subtype(init_values)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Handle HR users and officers recipients that can validate or refuse holidays
directly from email. """
groups = super(HolidaysRequest, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
@@ -1670,7 +1672,11 @@ class HolidaysRequest(models.Model):
new_group = (
'group_hr_holidays_user',
lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'],
{'actions': hr_actions}
{
'actions': hr_actions,
'active': True,
'has_button_access': True,
}
)
return [new_group] + groups
@@ -793,10 +793,12 @@ class HolidaysAllocation(models.Model):
return allocation_notif_subtype_id or self.env.ref('hr_holidays.mt_leave_allocation')
return super(HolidaysAllocation, self)._track_subtype(init_values)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Handle HR users and officers recipients that can validate or refuse holidays
directly from email. """
groups = super(HolidaysAllocation, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
@@ -815,7 +817,11 @@ class HolidaysAllocation(models.Model):
new_group = (
'group_hr_holidays_user',
lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'],
{'actions': hr_actions}
{
'actions': hr_actions,
'active': True,
'has_button_access': True,
}
)
return [new_group] + groups
+1 -1
View File
@@ -26,7 +26,7 @@ class MailController(http.Controller):
base_link = request.httprequest.path
params = dict(request.params)
params.pop('token', '')
valid_token = request.env['mail.thread']._notify_encode_link(base_link, params)
valid_token = request.env['mail.thread']._encode_link(base_link, params)
return consteq(valid_token, str(token))
@classmethod
+4 -2
View File
@@ -545,12 +545,14 @@ class Channel(models.Model):
return recipients_data
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" All recipients of a message on a channel are considered as partners.
This means they will receive a minimal email, without a link to access
in the backend. Mailing lists should indeed send minimal emails to avoid
the noise. """
groups = super(Channel, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
for (index, (group_name, _group_func, group_data)) in enumerate(groups):
if group_name != 'customer':
groups[index] = (group_name, lambda partner: False, group_data)
+280 -180
View File
@@ -2879,11 +2879,10 @@ class MailThread(models.AbstractModel):
* performs the notification process by calling the various notification
methods implemented;
:param message: ``mail.message`` record to notify;
:param msg_vals: dictionary of values used to create the message. If given it
may be used to access values related to ``message`` without accessing it
directly. It lessens query count in some optimized use cases by avoiding
access message content in db;
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
Kwargs allow to pass various parameters that are given to sub notification
methods. See those methods for more details about supported parameters.
@@ -2927,26 +2926,28 @@ class MailThread(models.AbstractModel):
return recipients_data
def _notify_thread_by_inbox(self, message, recipients_data, msg_vals=False, **kwargs):
""" Notification method: inbox. Does two main things :
""" Notificaty recipients inbox of a message. It does two main things :
* create inbox notifications for users;
* send bus notifications;
:param message: ``mail.message`` record to notify;
:param recipients_data: list of recipients information (based on res.partner
records), formatted like
[{'active': partner.active;
'id': id of the res.partner being recipient to notify;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': partner.partner_share;
'type': 'customer', 'portal', 'user;'
}, {...}].
See ``MailThread._notify_get_recipients``;
:param msg_vals: dictionary of values used to create the message. If given it
may be used to access values related to ``message`` without accessing it
directly. It lessens query count in some optimized use cases by avoiding
access message content in db;
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param list recipients_data: list of recipients data based on <res.partner>
records formatted like [
{
'active': partner.active;
'id': id of the res.partner being recipient to notify;
'is_follower': follows the message related document;
'lang': its lang;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': is partner a customer (partner.partner_share);
'type': partner usage ('customer', 'portal', 'user');
'ushare': are users shared (if users, all users are shared);
}, {...}]. See ``MailThread._notify_get_recipients()``;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
"""
bus_notifications = []
inbox_pids = [r['id'] for r in recipients_data if r['notif'] == 'inbox']
@@ -2968,39 +2969,44 @@ class MailThread(models.AbstractModel):
def _notify_thread_by_email(self, message, recipients_data, msg_vals=False,
mail_auto_delete=True, # mail.mail
model_description=False, force_email_company=False, force_email_lang=False, # rendering
subtitles=None, # rendering
resend_existing=False, force_send=True, send_after_commit=True, # email send
subtitles=None, **kwargs):
""" Method to send email linked to notified messages.
**kwargs):
""" Method to send emails notifications linked to a message.
:param message: ``mail.message`` record to notify;
:param recipients_data: list of recipients information (based on res.partner
records), formatted like
[{'active': partner.active;
'id': id of the res.partner being recipient to notify;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': partner.partner_share;
'type': 'customer', 'portal', 'user;'
}, {...}].
See ``MailThread._notify_get_recipients``;
:param msg_vals: dictionary of values used to create the message. If given it
may be used to access values related to ``message`` without accessing it
directly. It lessens query count in some optimized use cases by avoiding
access message content in db;
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param list recipients_data: list of recipients data based on <res.partner>
records formatted like [
{
'active': partner.active;
'id': id of the res.partner being recipient to notify;
'is_follower': follows the message related document;
'lang': its lang;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': is partner a customer (partner.partner_share);
'type': partner usage ('customer', 'portal', 'user');
'ushare': are users shared (if users, all users are shared);
}, {...}]. See ``MailThread._notify_get_recipients()``;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
:param mail_auto_delete: delete notification emails once sent;
:param bool mail_auto_delete: delete notification emails once sent;
:param model_description: model description used in email notification process
(computed if not given);
:param force_email_company: see ``_notify_by_email_prepare_rendering_context``;
:param force_email_lang: see ``_notify_by_email_prepare_rendering_context``;
:param str model_description: description of current model, given to
avoid fetching it and easing translation support;
:param record force_email_company: <res.company> record used when rendering
notification layout. Otherwise computed based on current record;
:param str force_email_lang: lang used when rendering content, used
notably to compute model name or translate access buttons;
:param list subtitles: optional list set as template value "subtitles";
:param resend_existing: check for existing notifications to update based on
mailed recipient, otherwise create new notifications;
:param force_send: send emails directly instead of using queue;
:param send_after_commit: if force_send, tells whether to send emails after
:param bool resend_existing: check for existing notifications to update
based on mailed recipient, otherwise create new notifications;
:param bool force_send: send emails directly instead of using queue;
:param bool send_after_commit: if force_send, tells to send emails after
the transaction has been committed using a post-commit hook;
:param subtitles: optional list that will be set as template value "subtitles"
"""
partners_data = [r for r in recipients_data if r['notif'] == 'email']
if not partners_data:
@@ -3008,9 +3014,11 @@ class MailThread(models.AbstractModel):
model = msg_vals.get('model') if msg_vals else message.model
model_name = model_description or (self.env['ir.model']._get(model).display_name if model else False) # one query for display name
recipients_groups_data = self._notify_get_recipients_classify(partners_data, model_name, msg_vals=msg_vals)
recipients_groups_list = self._notify_get_recipients_classify(
message, partners_data, model_name, msg_vals=msg_vals
)
if not recipients_groups_data:
if not recipients_groups_list:
return True
force_send = self.env.context.get('mail_notify_force_send', force_send)
@@ -3039,10 +3047,10 @@ class MailThread(models.AbstractModel):
# loop on groups (customer, portal, user, ... + model specific like group_sale_salesman)
notif_create_values = []
recipients_max = 50
for recipients_group_data in recipients_groups_data:
for recipients_group in recipients_groups_list:
# generate notification email content
recipients_ids = recipients_group_data.pop('recipients')
render_values = {**template_values, **recipients_group_data}
recipients_ids = recipients_group.pop('recipients')
render_values = {**template_values, **recipients_group}
# {company, is_discussion, lang, message, model_description, record, record_name, signature, subtype, tracking_values, website_url}
# {actions, button_access, has_button_access, recipients}
@@ -3114,8 +3122,10 @@ class MailThread(models.AbstractModel):
return True
def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False, model_description=False,
force_email_company=False, force_email_lang=False):
def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False,
model_description=False,
force_email_company=False,
force_email_lang=False):
""" Prepare rendering context for notification email.
Signature: if asked a default signature is computed based on author. Either
@@ -3133,16 +3143,18 @@ class MailThread(models.AbstractModel):
notification layout in the same language as the email content. A new
parameter allows to force its value.
:param msg_vals: dictionary of values used to create the message. If given it
may be used to access values related to ``message`` without accessing it
directly. It lessens query count in some optimized use cases by avoiding
access message content in db;
:param model_description: model description used in email notification process
(computed if not given);
:param force_email_company: res.company record used when rendering notification
layout. Otherwise computed based on current record;
:param force_email_lang: lang used when rendering content, used notably to
compute model name;
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
:param str model_description: description of current model, given to
avoid fetching it and easing translation support;
:param record force_email_company: <res.company> record used when rendering
notification layout. Otherwise computed based on current record;
:param str force_email_lang: lang used when rendering content, used
notably to compute model name or translate access buttons;
:return: dictionary of values used when rendering notification layout;
"""
if msg_vals is False:
msg_vals = {}
@@ -3226,12 +3238,15 @@ class MailThread(models.AbstractModel):
}
def _notify_by_email_get_base_mail_values(self, message, additional_values=None):
""" Add model-specific values to the dictionary used to create the
notification email. Its base behavior is to compute model-specific
headers.
""" Return model-specific and message-related values to be used when
creating notification emails. It serves as a common basis for all
notification emails based on a given message.
:param dict base_mail_values: base mail.mail values, holding message
to notify (mail_message_id and its fields), server, references, subject.
:param record message: <mail.message> record being notified;
:param dict additional_values: optional additional values to add (ease
custom calls and inheritance);
:return: dictionary of values suitable for a <mail.mail> create;
"""
mail_subject = message.subject
if not mail_subject and self:
@@ -3265,14 +3280,20 @@ class MailThread(models.AbstractModel):
base_mail_values['headers'] = repr(headers)
return base_mail_values
def _notify_by_email_get_final_mail_values(self, recipient_ids, base_mail_values, additional_values=None):
""" Format email notification recipient values to store on the notification
mail.mail. Basic method just set the recipient partners as mail_mail
recipients. Override to generate other mail values like email_to or
email_cc.
:param recipient_ids: res.partner recordset to notify
def _notify_by_email_get_final_mail_values(self, recipient_ids, mail_values,
additional_values=None):
""" Perform final formatting of values to create notification emails.
Basic method just set the recipient partners as mail_mail recipients.
Override to generate other mail values like email_to or email_cc.
:param list recipient_ids: res.partner IDs to notify;
:param dict mail_values: notification mail values;
:param dict additional_values: optional additional values to add (ease
custom calls and inheritance);
:return: a new dictionary of values suitable for a <mail.mail> create;
"""
final_mail_values = dict(base_mail_values)
final_mail_values = dict(mail_values)
final_mail_values['recipient_ids'] = [Command.link(pid) for pid in recipient_ids]
if additional_values:
final_mail_values.update(additional_values)
@@ -3282,8 +3303,10 @@ class MailThread(models.AbstractModel):
""" Compute recipients to notify based on subtype and followers. This
method returns data structured as expected for ``_notify_recipients``.
TDE/XDO TODO: flag rdata directly, with for example r['notif'] = 'ocn_client' and r['needaction']=False
and correctly override _notify_get_recipients
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
Kwargs allow to pass various parameters that are used by sub notification
methods. See those methods for more details about supported parameters.
@@ -3297,15 +3320,23 @@ class MailThread(models.AbstractModel):
to avoid having several notifications / partner as it would make
constraints crash. This is disabled by default to optimize speed;
:return list recipients_data: this is a list of recipients information (see
``MailFollowers._get_recipient_data()`` for more details) formatted like
[{'active': partner.active;
'id': id of the res.partner;
TDE/XDO TODO: flag rdata directly, for example r['notif'] = 'ocn_client'
and r['needaction']=False and correctly override _notify_get_recipients
:return list recipients_data: list of recipients information (see
``MailFollowers._get_recipient_data()`` for more details) formatted
like [
{
'active': partner.active;
'id': id of the res.partner being recipient to notify;
'is_follower': follows the message related document;
'lang': its lang;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': partner.partner_share;
'type': 'customer', 'portal', 'user;'
}, {...}]
'share': is partner a customer (partner.partner_share);
'type': partner usage ('customer', 'portal', 'user');
'ushare': are users shared (if users, all users are shared);
}, {...}]
"""
msg_sudo = message.sudo()
# get values from msg_vals or from message if msg_vals doen't exists
@@ -3352,133 +3383,184 @@ class MailThread(models.AbstractModel):
return recipients_data
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Return groups used to classify recipients of a notification email.
Groups is a list of tuple containing of form (group_name, group_func,
group_data) where
* group_name is an identifier used only to be able to override and manipulate
groups. Default groups are user (recipients linked to an employee user),
portal (recipients linked to a portal user) and customer (recipients not
linked to any user). An example of override use would be to add a group
linked to a res.groups like Hr Officers to set specific action buttons to
them.
* group_func is a function pointer taking a partner record as parameter. This
method will be applied on recipients to know whether they belong to a given
group or not. Only first matching group is kept. Evaluation order is the
list order.
* group_data is a dict containing parameters for the notification email
* has_button_access: whether to display Access <Document> in email. True
by default for new groups, False for portal / customer.
* button_access: dict with url and title of the button
* actions: list of action buttons to display in the notification email.
Each action is a dict containing url and title of the button.
Groups has a default value that you can find in mail_thread
``_notify_get_recipients_classify`` method.
Groups is a list of tuple (group_name, group_func, group_data) where
* 'group_name' is an identifier used only to be able to override and
manipulate groups;
* 'group_func' is a function pointer taking a partner data dict as
parameter. It is called on recipients to know if they belong to
the group. Only first matching group is kept, iterating on the
group list in order.
* 'group_data' is a dict containing parameters used in notification
process like {
'active': if not, it is skipped in notification process (ease
inheritance to be already present);
'actions': list of actions to display as links or buttons in form
{'url': link of the action, 'title': link or button
string};
'button_access': main access document button information, {'url'
link of the access, 'title': link or button
string};
'has_button_access': display access document main button in email;
'notification_group_name': name of the group, to ease usage;
'recipients': list of partner IDs, will be fillup when evaluating
groups;
}
Default groups:
* 'user': recipients linked to an internal user;
* 'portal': recipients linked to a portal user;
* 'follower': recipients (not internal/portal users) follower of the
related record;
* 'customer': other recipients;
When having to find a group for recipients, the first matching one
when iterating on groups is used. Reordering those groups is doable
through override. Adding groups is a common override, to add specific
buttons or actions for users belonging to some user groups.
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param str model_description: description of current model, given to
avoid fetching it and easing translation support;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
:return: list of groups definition
"""
is_thread_notification = self._notify_get_recipients_thread_info(msg_vals=msg_vals)['is_thread_notification']
return [
[
'user',
lambda pdata: pdata['type'] == 'user',
{'has_button_access': is_thread_notification}
{
'active': True,
'has_button_access': self._is_thread_message(msg_vals=msg_vals),
}
], [
'portal',
lambda pdata: pdata['type'] == 'portal',
{'active': False, # activate only on demand if rights are enabled
'has_button_access': False,
{
'active': False, # activate only on demand if rights are enabled
'has_button_access': False,
}
], [
'follower',
lambda pdata: pdata['is_follower'],
{'active': False, # activate only on demand if rights are enabled
'has_button_access': False,
{
'active': False, # activate only on demand if rights are enabled
'has_button_access': False,
}
], [
'customer',
lambda pdata: True,
{'has_button_access': False}
{
'active': True,
'has_button_access': False,
}
]
]
def _notify_get_recipients_classify(self, recipient_data, model_name, msg_vals=None):
def _notify_get_recipients_groups_fillup(self, groups, model_description, msg_vals=None):
""" Iterate on recipients groups (see '_notify_get_recipients_groups')
and fill up the result with default values, allowing to compute links or
titles once.
:param list groups: recipients groups;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
:param str model_description: description of current model, given to
avoid fetching it and easing translation support;
:return: updated groups;
"""
access_link = self._notify_get_action_link('view', **msg_vals)
if model_description:
view_title = _('View %s', model_description)
else:
view_title = _('View')
is_thread_message = self._is_thread_message(msg_vals=msg_vals)
# fill group_data with default_values if they are not complete
for group_name, _group_func, group_data in groups:
group_data.setdefault('active', True)
group_data.setdefault('actions', [])
group_data.setdefault('has_button_access', is_thread_message)
group_data.setdefault('notification_group_name', group_name)
group_data.setdefault('recipients', [])
group_button_access = group_data.setdefault('button_access', {})
group_button_access.setdefault('url', access_link)
group_button_access.setdefault('title', view_title)
return groups
def _notify_get_recipients_classify(self, message, recipients_data,
model_description, msg_vals=None):
""" Classify recipients to be notified of a message in groups to have
specific rendering depending on their group. For example users could
have access to buttons customers should not have in their emails.
Module-specific grouping should be done by overriding ``_notify_get_recipients_groups``
method defined here-under.
:param recipient_data: list of recipients information (based on res.partner
records). See ``MailThread._notify_get_recipients()``;
:param record message: <mail.message> record being notified. May be
void as 'msg_vals' superseeds it;
:param list recipients_data: list of recipients data based on <res.partner>
records formatted like [
{
'active': partner.active;
'id': id of the res.partner being recipient to notify;
'is_follower': follows the message related document;
'lang': its lang;
'groups': res.group IDs if linked to a user;
'notif': 'inbox', 'email', 'sms' (SMS App);
'share': is partner a customer (partner.partner_share);
'type': partner usage ('customer', 'portal', 'user');
'ushare': are users shared (if users, all users are shared);
}, {...}]. See ``MailThread._notify_get_recipients()``;
:param str model_description: description of current model, given to
avoid fetching it and easing translation support;
:param dict msg_vals: values dict used to create the message, allows to
skip message usage and spare some queries;
:return list: list of groups formatted for notification processing like
[{'active': True,
'actions': [],
'button_access': {},
'has_button_access': False,
'recipients': [11],},
{'active': True,
'actions': [],
'button_access': {'title': 'View Simple Chatter Model',
'url': '/mail/view?model=mail.test.simple&res_id=1497'},
'has_button_access': True,
'recipients': [4, 5, 6],},
{'active': True,
'actions': [],
'button_access': {'title': 'View Simple Chatter Model',
'url': '/mail/view?model=mail.test.simple&res_id=1497'},
'has_button_access': True,
'recipients': [10, 11, 12],}
]
:return list: list of groups (see '_notify_get_recipients_groups')
with 'recipients' key filled with matching partners, like
[{
'active': True,
'actions': [],
'button_access': {},
'has_button_access': False,
'notification_group_name': 'user',
'recipients': [11],
}, {...}]
"""
# keep a local copy of msg_vals as it may be modified to include more information about groups or links
# keep a local copy of msg_vals as it may be modified to include more
# information about groups or links
local_msg_vals = dict(msg_vals) if msg_vals else {}
groups = self._notify_get_recipients_groups(msg_vals=local_msg_vals)
access_link = self._notify_get_action_link('view', **local_msg_vals)
if model_name:
view_title = _('View %s', model_name)
else:
view_title = _('View')
# fill group_data with default_values if they are not complete
for group_name, group_func, group_data in groups:
is_thread_notification = self._notify_get_recipients_thread_info(msg_vals=local_msg_vals)['is_thread_notification']
group_data.setdefault('active', True)
group_data.setdefault('actions', list())
group_data.setdefault('has_button_access', is_thread_notification)
group_data.setdefault('notification_is_customer', False)
group_data.setdefault('notification_group_name', group_name)
group_data.setdefault('recipients', list())
group_button_access = group_data.setdefault('button_access', {})
group_button_access.setdefault('url', access_link)
group_button_access.setdefault('title', view_title)
groups = self._notify_get_recipients_groups_fillup(
self._notify_get_recipients_groups(
message, model_description, msg_vals=local_msg_vals
),
model_description,
msg_vals=local_msg_vals
)
# classify recipients in each group
for recipient in recipient_data:
for group_name, group_func, group_data in groups:
if group_data['active'] and group_func(recipient):
group_data['recipients'].append(recipient['id'])
for recipient_data in recipients_data:
for _group_name, group_func, group_data in groups:
if group_data['active'] and group_func(recipient_data):
group_data['recipients'].append(recipient_data['id'])
break
# filter out groups without recipients
return [group_data for _group_name, _group_func, group_data in groups
if group_data['recipients']]
def _notify_get_recipients_thread_info(self, msg_vals=None):
""" Tool method to compute thread info used in ``_notify_classify_recipients``
and its sub-methods. """
res_model = msg_vals['model'] if (msg_vals and 'model' in msg_vals) else self._name
res_id = msg_vals['res_id'] if (msg_vals and 'res_id' in msg_vals) else (self.ids[0] if self.ids else False)
return {
'is_thread_notification': bool(res_id) if (res_model and res_model != 'mail.thread') else False
}
@api.model
def _notify_encode_link(self, base_link, params):
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params)))
hm = hmac.new(secret.encode('utf-8'), token.encode('utf-8'), hashlib.sha1).hexdigest()
return hm
return [
group_data
for _group_name, _group_func, group_data in groups
if group_data['recipients']
]
def _notify_get_action_link(self, link_type, **kwargs):
""" Prepare link to an action: view document, follow document, ... """
@@ -3507,7 +3589,7 @@ class MailThread(models.AbstractModel):
return ''
if link_type not in ['view']:
token = self._notify_encode_link(base_link, params)
token = self._encode_link(base_link, params)
params['token'] = token
link = '%s?%s' % (base_link, urls.url_encode(params, sort=True))
@@ -3516,6 +3598,24 @@ class MailThread(models.AbstractModel):
return link
@api.model
def _encode_link(self, base_link, params):
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params)))
hm = hmac.new(secret.encode('utf-8'), token.encode('utf-8'), hashlib.sha1).hexdigest()
return hm
def _is_thread_message(self, msg_vals=None):
""" Tool method to compute thread validity in notification methods.
msg_vals is used as a replacement for self, allowing to force model
and res_id independently of current recordset. Void values in dict
are kept e.g. model=False is valid. """
if msg_vals is None:
msg_vals = {}
res_model = msg_vals['model'] if 'model' in msg_vals else self._name
res_id = msg_vals['res_id'] if 'res_id' in msg_vals else (self.ids[0] if self.ids else False)
return bool(res_id) if (res_model and res_model != 'mail.thread') else False
# ------------------------------------------------------
# FOLLOWERS API
# ------------------------------------------------------
+6 -4
View File
@@ -16,8 +16,10 @@ class MailThread(models.AbstractModel):
domain=lambda self: [('model', '=', self._name), '|', ('message_type', '=', 'comment'), ('message_type', '=', 'email')], auto_join=True,
help="Website communication history")
def _notify_get_recipients_groups(self, msg_vals=None):
groups = super()._notify_get_recipients_groups(msg_vals=msg_vals)
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
@@ -37,11 +39,11 @@ class MailThread(models.AbstractModel):
new_group = [
('portal_customer', lambda pdata: pdata['id'] == customer.id, {
'has_button_access': True,
'active': True,
'button_access': {
'url': access_link,
},
'notification_is_customer': True,
'has_button_access': True,
})
]
else:
+8 -3
View File
@@ -2440,12 +2440,14 @@ class Task(models.Model):
res -= waiting_subtype
return res
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Handle project users and managers recipients that can assign
tasks and create new one directly from notification emails. Also give
access button to portal users and portal customers. If they are notified
they should probably have access to the document. """
groups = super(Task, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
@@ -2464,7 +2466,10 @@ class Task(models.Model):
groups.insert(0, (
'allowed_portal_users',
lambda pdata: pdata['type'] == 'portal',
{}
{
'active': True,
'has_button_access': True,
}
))
portal_privacy = self.project_id.privacy_visibility == 'portal'
for group_name, _group_method, group_data in groups:
+4 -2
View File
@@ -341,10 +341,12 @@ class PurchaseOrder(models.Model):
kwargs['notify_author'] = self.env.user.partner_id.id in (kwargs.get('partner_ids') or [])
return super(PurchaseOrder, self.with_context(**po_ctx)).message_post(**kwargs)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Tweak 'view document' button for portal customers, calling directly
routes for confirm specific to PO model. """
groups = super(PurchaseOrder, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
+4 -2
View File
@@ -1249,11 +1249,13 @@ class SaleOrder(models.Model):
kwargs['notify_author'] = self.env.user.partner_id.id in (kwargs.get('partner_ids') or [])
return super(SaleOrder, self.with_context(**so_ctx)).message_post(**kwargs)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Give access button to users and portal customer as portal is integrated
in sale. Customer and portal group have probably no right to see
the document so they don't have the access button. """
groups = super()._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
+2 -2
View File
@@ -8,8 +8,9 @@ class Followers(models.Model):
_inherit = ['mail.followers']
def _get_recipient_data(self, records, message_type, subtype_id, pids=None):
recipients_data = super()._get_recipient_data(records, message_type, subtype_id, pids=pids)
if message_type != 'sms' or not (pids or records):
return super(Followers, self)._get_recipient_data(records, message_type, subtype_id, pids=pids)
return recipients_data
if pids is None and records:
records_pids = dict(
@@ -20,7 +21,6 @@ class Followers(models.Model):
records_pids = dict((record.id, pids) for record in records)
else:
records_pids = {0: pids if pids else []}
recipients_data = super(Followers, self)._get_recipient_data(records, message_type, subtype_id, pids=pids)
for rid, rdata in recipients_data.items():
sms_pids = records_pids.get(rid) or []
for pid, pdata in rdata.items():
@@ -72,8 +72,10 @@ class MailTestLang(models.Model):
def _mail_get_partner_fields(self):
return ['customer_id']
def _notify_get_recipients_groups(self, msg_vals=None):
groups = super(MailTestLang, self)._notify_get_recipients_groups(msg_vals=msg_vals)
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
local_msg_vals = dict(msg_vals or {})
+8 -4
View File
@@ -160,11 +160,13 @@ class MailTestTicket(models.Model):
for record in self
)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Activate more groups to test query counters notably (and be backward
compatible for tests). """
local_msg_vals = dict(msg_vals or {})
groups = super()._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
for group_name, _group_method, group_data in groups:
if group_name == 'portal':
group_data['active'] = True
@@ -311,10 +313,12 @@ class MailTestContainer(models.Model):
for record in self
)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Activate more groups to test query counters notably (and be backward
compatible for tests). """
groups = super(MailTestContainer, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
for group_name, _group_method, group_data in groups:
if group_name == 'portal':
group_data['active'] = True
+8 -3
View File
@@ -222,7 +222,8 @@ class TestMailNotifyAPI(TestMessagePostCommon):
# test notifying the class (void recordset)
classify_res = self.env[base_record._name]._notify_get_recipients_classify(
pdata, 'My Custom Model Name', msg_vals=notify_msg_vals
self.env['mail.message'], pdata, 'My Custom Model Name',
msg_vals=notify_msg_vals,
)
# find back information for each recipients
partner_info = next(item for item in classify_res if item['recipients'] == self.partner_1.ids)
@@ -249,7 +250,9 @@ class TestMailNotifyAPI(TestMessagePostCommon):
'res_id': res_id,
})
classify_res = self.env[model].browse(res_id)._notify_get_recipients_classify(
pdata, 'Test', msg_vals=notify_msg_vals)
self.env['mail.message'], pdata, 'Test',
msg_vals=notify_msg_vals,
)
# find back information for partner
partner_info = next(item for item in classify_res if item['recipients'] == self.partner_1.ids)
emp_info = next(item for item in classify_res if item['recipients'] == self.partner_employee.ids)
@@ -271,7 +274,9 @@ class TestMailNotifyAPI(TestMessagePostCommon):
'res_id': res_id,
})
classify_res = base_record._notify_get_recipients_classify(
pdata, 'Test', msg_vals=notify_msg_vals)
self.env['mail.message'], pdata, 'Test',
msg_vals=notify_msg_vals,
)
# find back information for partner
partner_info = next(item for item in classify_res if item['recipients'] == self.partner_1.ids)
emp_info = next(item for item in classify_res if item['recipients'] == self.partner_employee.ids)
@@ -56,7 +56,9 @@ class TestMailThreadInternals(TestMailThreadInternalsCommon):
'By default access tokens are False with portal'
)
groups = test_record._notify_get_recipients_groups()
groups = test_record._notify_get_recipients_groups(
self.env['mail.message'], False,
)
portal_customer_group = next(
(group for group in groups if group[0] == 'portal_customer'),
False
+3 -1
View File
@@ -182,7 +182,9 @@ class TestPortalFlow(MailCommon, HttpCase):
'subject': 'Your quotation "{{ object.name }}"',
})
cls._create_portal_user()
for group_name, group_func, group_data in cls.record_portal.sudo()._notify_get_recipients_groups():
for group_name, group_func, group_data in cls.record_portal.sudo()._notify_get_recipients_groups(
cls.env['mail.message'], False
):
if group_name == 'portal_customer' and group_func(cls.customer):
cls.record_access_url = group_data['button_access']['url']
break
+4 -2
View File
@@ -274,9 +274,11 @@ class BlogPost(models.Model):
'res_id': self.id,
}
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Add access button to everyone if the document is published. """
groups = super(BlogPost, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
+4 -2
View File
@@ -934,9 +934,11 @@ class Post(models.Model):
'res_id': self.id,
}
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Add access button to everyone if the document is active. """
groups = super(Post, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
+4 -2
View File
@@ -402,10 +402,12 @@ class SaleOrder(models.Model):
self.cart_recovery_email_sent = True
return super(SaleOrder, self)._message_post_after_hook(message, msg_vals)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" In case of cart recovery email, update link to redirect directly
to the cart (like ``mail_template_sale_cart_recovery`` template). """
groups = super(SaleOrder, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups
+4 -2
View File
@@ -735,9 +735,11 @@ class Slide(models.Model):
}
return super(Slide, self)._get_access_action(access_uid=access_uid, force_website=force_website)
def _notify_get_recipients_groups(self, msg_vals=None):
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
""" Add access button to everyone if the document is active. """
groups = super(Slide, self)._notify_get_recipients_groups(msg_vals=msg_vals)
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if not self:
return groups