[MERGE] mail, various: improve post and notification API and performances
Purpose of this merge is to clean and improve performances of post and notification mechanism contained in mail application. In this merge we provide * centralization of notification code inside mail.thread instead of being dispatched in mail.thread, mail.message and res.partner; * simplification of call chain, removal of unnecessary methods; * better propagation of messages value through the various calls in order to use given value and avoid browse and cache miss adding queries; * optimization of attachments management in post; * cleaning of message_post now limited to records and simplified in its management of parameter; * cleaning and simplification of recipients grouping for email notification; * cleaning of message_notify; * various code cleaning and tweaking in order to reduce query count; Performance tests indicate number of queries did fall, especially in use cases involving management of followers and attachments. See sub commits for more details. This merge is linked to task ID 1943901. closes odoo/odoo#32404 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -1060,17 +1060,16 @@ class AccountInvoice(models.Model):
|
||||
return self.filtered(lambda inv: inv.state != 'cancel').action_cancel()
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Give access button to users and portal customer as portal is integrated
|
||||
in account. Customer and portal group have probably no right to see
|
||||
the document so they don't have the access button. """
|
||||
groups = super(AccountInvoice, self)._notify_get_groups(message, groups)
|
||||
groups = super(AccountInvoice, self)._notify_get_groups()
|
||||
|
||||
if self.state not in ('draft', 'cancel'):
|
||||
for group_name, group_method, group_data in groups:
|
||||
if group_name in ('customer', 'portal'):
|
||||
continue
|
||||
group_data['has_button_access'] = True
|
||||
if group_name not in ('customer', 'portal'):
|
||||
group_data['has_button_access'] = True
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
@@ -252,12 +252,13 @@ class AccountInvoice(models.Model):
|
||||
# Handle both _Attachment namedtuple in mail.thread or ir.attachment.
|
||||
return hasattr(attachment, 'content') and getattr(attachment, 'content') or base64.b64decode(attachment.datas)
|
||||
filename = _get_attachment_filename(attachment)
|
||||
content = _get_attachment_content(attachment)
|
||||
|
||||
# Check if the attachment is a pdf.
|
||||
if not filename.endswith('.pdf'):
|
||||
return
|
||||
|
||||
content = _get_attachment_content(attachment)
|
||||
|
||||
with io.BytesIO(content) as buffer:
|
||||
try:
|
||||
reader = PdfFileReader(buffer)
|
||||
|
||||
@@ -227,7 +227,8 @@ class Attendee(models.Model):
|
||||
""" Marks event invitation as Accepted. """
|
||||
result = self.write({'state': 'accepted'})
|
||||
for attendee in self:
|
||||
attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
|
||||
if attendee.event_id:
|
||||
attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
@@ -235,7 +236,8 @@ class Attendee(models.Model):
|
||||
""" Marks event invitation as Declined. """
|
||||
res = self.write({'state': 'declined'})
|
||||
for attendee in self:
|
||||
attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
|
||||
if attendee.event_id:
|
||||
attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@@ -1114,10 +1114,10 @@ class Lead(models.Model):
|
||||
return super(Lead, self)._track_subtype(init_values)
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Handle salesman recipients that can convert leads into opportunities
|
||||
and set opportunities as won / lost. """
|
||||
groups = super(Lead, self)._notify_get_groups(message, groups)
|
||||
groups = super(Lead, self)._notify_get_groups()
|
||||
|
||||
self.ensure_one()
|
||||
if self.type == 'lead':
|
||||
@@ -1211,7 +1211,7 @@ class Lead(models.Model):
|
||||
defaults['company_id'] = self.env['crm.team'].browse(defaults['team_id']).company_id.id
|
||||
return super(Lead, self).message_new(msg_dict, custom_values=defaults)
|
||||
|
||||
def _message_post_after_hook(self, message, *args, **kwargs):
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
@@ -1222,7 +1222,7 @@ class Lead(models.Model):
|
||||
('partner_id', '=', False),
|
||||
('email_from', '=', new_partner.email),
|
||||
('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
|
||||
return super(Lead, self)._message_post_after_hook(message, *args, **kwargs)
|
||||
return super(Lead, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
@api.multi
|
||||
def _message_partner_info_from_emails(self, emails, link_mail=False):
|
||||
|
||||
@@ -503,7 +503,7 @@ class EventRegistration(models.Model):
|
||||
'email_cc': False}
|
||||
for r in self}
|
||||
|
||||
def _message_post_after_hook(self, message, *args, **kwargs):
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
@@ -515,7 +515,7 @@ class EventRegistration(models.Model):
|
||||
('email', '=', new_partner.email),
|
||||
('state', 'not in', ['cancel']),
|
||||
]).write({'partner_id': new_partner.id})
|
||||
return super(EventRegistration, self)._message_post_after_hook(message, *args, **kwargs)
|
||||
return super(EventRegistration, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
@api.multi
|
||||
def action_send_badge_email(self):
|
||||
|
||||
@@ -567,7 +567,7 @@ class Challenge(models.Model):
|
||||
body=body_html,
|
||||
partner_ids=challenge.mapped('user_ids.partner_id.id'),
|
||||
subtype='mail.mt_comment',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
if challenge.report_message_group_id:
|
||||
challenge.report_message_group_id.message_post(
|
||||
@@ -586,18 +586,18 @@ class Challenge(models.Model):
|
||||
'gamification.challenge',
|
||||
challenge.id)
|
||||
|
||||
# send message only to users, not on the challenge
|
||||
self.env['gamification.challenge'].message_post(
|
||||
# notify message only to users, do not post on the challenge
|
||||
challenge.message_notify(
|
||||
body=body_html,
|
||||
partner_ids=[(4, user.partner_id.id)],
|
||||
partner_ids=[user.partner_id.id],
|
||||
subtype='mail.mt_comment',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
if challenge.report_message_group_id:
|
||||
challenge.report_message_group_id.message_post(
|
||||
body=body_html,
|
||||
subtype='mail.mt_comment',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
return challenge.write({'last_report_date': fields.Date.today()})
|
||||
|
||||
|
||||
@@ -222,12 +222,11 @@ class Goal(models.Model):
|
||||
.get_email_template(self.id)
|
||||
body_html = self.env['mail.template'].with_context(template._context)\
|
||||
._render_template(template.body_html, 'gamification.goal', self.id)
|
||||
# TDE NOTE: should be a notification (see with xdo)
|
||||
self.env['mail.thread'].message_post(
|
||||
self.message_notify(
|
||||
body=body_html,
|
||||
partner_ids=[self.user_id.partner_id.id],
|
||||
subtype='mail.mt_comment',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
|
||||
return {'to_update': True}
|
||||
|
||||
@@ -280,7 +280,7 @@ class Employee(models.Model):
|
||||
tools.image_resize_images(vals)
|
||||
employee = super(Employee, self).create(vals)
|
||||
url = '/web#%s' % url_encode({'action': 'hr.plan_wizard_action', 'active_id': employee.id, 'active_model': 'hr.employee'})
|
||||
employee._message_log(_('<b>Congratulations !</b> May I recommand you to setup an <a href="%s">onboarding plan ?</a>') % (url))
|
||||
employee._message_log(body=_('<b>Congratulations !</b> May I recommand you to setup an <a href="%s">onboarding plan ?</a>') % (url))
|
||||
if employee.department_id:
|
||||
self.env['mail.channel'].sudo().search([
|
||||
('subscription_department_ids', 'in', employee.department_id.id)
|
||||
@@ -363,8 +363,8 @@ class Employee(models.Model):
|
||||
self = self.sudo(real_user)
|
||||
return self
|
||||
|
||||
def _message_log(self, *args, **kwargs):
|
||||
return super(Employee, self._post_author())._message_log(*args, **kwargs)
|
||||
def _message_log(self, **kwargs):
|
||||
return super(Employee, self._post_author())._message_log(**kwargs)
|
||||
|
||||
@api.multi
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
|
||||
@@ -532,7 +532,7 @@ class HrExpense(models.Model):
|
||||
subject='Re: %s' % msg_dict.get('subject', ''),
|
||||
body=body,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
else:
|
||||
self.env['mail.mail'].create({
|
||||
|
||||
@@ -851,10 +851,10 @@ class HolidaysRequest(models.Model):
|
||||
return super(HolidaysRequest, self)._track_subtype(init_values)
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Handle HR users and officers recipients that can validate or refuse holidays
|
||||
directly from email. """
|
||||
groups = super(HolidaysRequest, self)._notify_get_groups(message, groups)
|
||||
groups = super(HolidaysRequest, self)._notify_get_groups()
|
||||
|
||||
self.ensure_one()
|
||||
hr_actions = []
|
||||
|
||||
@@ -611,10 +611,10 @@ class HolidaysAllocation(models.Model):
|
||||
return super(HolidaysAllocation, self)._track_subtype(init_values)
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Handle HR users and officers recipients that can validate or refuse holidays
|
||||
directly from email. """
|
||||
groups = super(HolidaysAllocation, self)._notify_get_groups(message, groups)
|
||||
groups = super(HolidaysAllocation, self)._notify_get_groups()
|
||||
|
||||
self.ensure_one()
|
||||
hr_actions = []
|
||||
|
||||
@@ -353,7 +353,7 @@ class Applicant(models.Model):
|
||||
res['stage_id'] = (applicant.stage_id.template_id, {
|
||||
'auto_delete_message': True,
|
||||
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
|
||||
'notif_layout': 'mail.mail_notification_light'
|
||||
'email_layout_xmlid': 'mail.mail_notification_light'
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -414,7 +414,7 @@ class Applicant(models.Model):
|
||||
defaults.update(custom_values)
|
||||
return super(Applicant, self).message_new(msg, custom_values=defaults)
|
||||
|
||||
def _message_post_after_hook(self, message, *args, **kwargs):
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
@@ -425,7 +425,7 @@ class Applicant(models.Model):
|
||||
('partner_id', '=', False),
|
||||
('email_from', '=', new_partner.email),
|
||||
('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
|
||||
return super(Applicant, self)._message_post_after_hook(message, *args, **kwargs)
|
||||
return super(Applicant, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
@api.multi
|
||||
def create_employee_from_applicant(self):
|
||||
|
||||
@@ -48,7 +48,7 @@ class MailChannel(models.Model):
|
||||
record.is_chat = True
|
||||
|
||||
@api.multi
|
||||
def _channel_message_notifications(self, message):
|
||||
def _channel_message_notifications(self, message, message_format=False):
|
||||
""" When a anonymous user create a mail.channel, the operator is not notify (to avoid massive polling when
|
||||
clicking on livechat button). So when the anonymous person is sending its FIRST message, the channel header
|
||||
should be added to the notification, since the user cannot be listining to the channel.
|
||||
@@ -56,7 +56,7 @@ class MailChannel(models.Model):
|
||||
livechat_channels = self.filtered(lambda x: x.channel_type == 'livechat')
|
||||
other_channels = self.filtered(lambda x: x.channel_type != 'livechat')
|
||||
notifications = super(MailChannel, livechat_channels)._channel_message_notifications(message.with_context(im_livechat_use_username=True)) + \
|
||||
super(MailChannel, other_channels)._channel_message_notifications(message)
|
||||
super(MailChannel, other_channels)._channel_message_notifications(message, message_format)
|
||||
for channel in self:
|
||||
# add uuid for private livechat channels to allow anonymous to listen
|
||||
if channel.channel_type == 'livechat' and channel.public == 'private':
|
||||
|
||||
@@ -386,14 +386,16 @@ class MailActivity(models.Model):
|
||||
engine='ir.qweb',
|
||||
minimal_qcontext=True
|
||||
)
|
||||
self.env['mail.thread'].message_notify(
|
||||
partner_ids=activity.user_id.partner_id.ids,
|
||||
body=body,
|
||||
subject=_('%s: %s assigned to you') % (activity.res_name, activity.summary or activity.activity_type_id.name),
|
||||
record_name=activity.res_name,
|
||||
model_description=model_description,
|
||||
notif_layout='mail.mail_notification_light'
|
||||
)
|
||||
record = self.env[activity.res_model].browse(activity.res_id)
|
||||
if activity.user_id:
|
||||
record.message_notify(
|
||||
partner_ids=activity.user_id.partner_id.ids,
|
||||
body=body,
|
||||
subject=_('%s: %s assigned to you') % (activity.res_name, activity.summary or activity.activity_type_id.name),
|
||||
record_name=activity.res_name,
|
||||
model_description=model_description,
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def action_done(self):
|
||||
|
||||
@@ -301,28 +301,24 @@ class Channel(models.Model):
|
||||
if not self.email_send:
|
||||
notification = _('<div class="o_mail_notification">left <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (self.id, self.name,)
|
||||
# post 'channel left' message as root since the partner just unsubscribed from the channel
|
||||
self.sudo().message_post(body=notification, message_type="notification", subtype="mail.mt_comment", author_id=partner.id)
|
||||
self.sudo().message_post(body=notification, subtype="mail.mt_comment", author_id=partner.id)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" 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_groups(message, groups)
|
||||
groups = super(Channel, self)._notify_get_groups()
|
||||
for (index, (group_name, group_func, group_data)) in enumerate(groups):
|
||||
if group_name != 'customer':
|
||||
groups[index] = (group_name, lambda partner: False, group_data)
|
||||
return groups
|
||||
|
||||
@api.multi
|
||||
def _notify_specific_email_values(self, message):
|
||||
res = super(Channel, self)._notify_specific_email_values(message)
|
||||
try:
|
||||
headers = safe_eval(res.get('headers', dict()))
|
||||
except Exception:
|
||||
headers = {}
|
||||
def _notify_email_header_dict(self):
|
||||
headers = super(Channel, self)._notify_email_header_dict()
|
||||
headers['Precedence'] = 'list'
|
||||
# avoid out-of-office replies from MS Exchange
|
||||
# http://blogs.technet.com/b/exchange/archive/2006/10/06/3395024.aspx
|
||||
@@ -334,8 +330,7 @@ class Channel(models.Model):
|
||||
# X-Forge-To: will replace To: after SMTP envelope is determined by ir.mail.server
|
||||
list_to = '"%s" <%s@%s>' % (self.name, self.alias_name, self.alias_domain)
|
||||
headers['X-Forge-To'] = list_to
|
||||
res['headers'] = repr(headers)
|
||||
return res
|
||||
return headers
|
||||
|
||||
@api.multi
|
||||
def _message_receive_bounce(self, email, partner, mail_id=None):
|
||||
@@ -346,16 +341,16 @@ class Channel(models.Model):
|
||||
return super(Channel, self)._message_receive_bounce(email, partner, mail_id=mail_id)
|
||||
|
||||
@api.multi
|
||||
def _notify_email_recipients(self, message, recipient_ids):
|
||||
def _notify_email_recipient_values(self, recipient_ids):
|
||||
# Excluded Blacklisted
|
||||
whitelist = self.env['res.partner'].sudo().search([('id', 'in', recipient_ids)]).filtered(lambda p: not p.is_blacklisted)
|
||||
whitelist = self.env['res.partner'].sudo().browse(recipient_ids).filtered(lambda p: not p.is_blacklisted)
|
||||
# real mailing list: multiple recipients (hidden by X-Forge-To)
|
||||
if self.alias_domain and self.alias_name:
|
||||
return {
|
||||
'email_to': ','.join(formataddr((partner.name, partner.email_normalized)) for partner in whitelist if partner.email_normalized),
|
||||
'recipient_ids': [],
|
||||
}
|
||||
return super(Channel, self)._notify_email_recipients(message, whitelist.ids)
|
||||
return super(Channel, self)._notify_email_recipient_values(whitelist.ids)
|
||||
|
||||
def _extract_moderation_values(self, message_type, **kwargs):
|
||||
""" This method is used to compute moderation status before the creation
|
||||
@@ -511,33 +506,31 @@ class Channel(models.Model):
|
||||
notifications.append([(self._cr.dbname, 'res.partner', partner.id), channel_info])
|
||||
return notifications
|
||||
|
||||
@api.multi
|
||||
def _notify(self, message):
|
||||
""" Broadcast the given message on the current channels.
|
||||
Send the message on the Bus Channel (uuid for public mail.channel, and partner private bus channel (the tuple)).
|
||||
A partner will receive only on message on its bus channel, even if this message belongs to multiple mail channel. Then 'channel_ids' field
|
||||
of the received message indicates on wich mail channel the message should be displayed.
|
||||
:param : mail.message to broadcast
|
||||
"""
|
||||
if not self:
|
||||
return
|
||||
message.ensure_one()
|
||||
notifications = self._channel_message_notifications(message)
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
def _notify_thread(self, message, msg_vals=False, model_description=False, mail_auto_delete=True):
|
||||
# When posting a message on a mail channel, manage moderation and postpone notify users
|
||||
if not msg_vals or msg_vals.get('moderation_status') != 'pending_moderation':
|
||||
super(Channel, self)._notify_thread(
|
||||
message,
|
||||
msg_vals=msg_vals,
|
||||
model_description=model_description,
|
||||
mail_auto_delete=mail_auto_delete,
|
||||
)
|
||||
else:
|
||||
message._notify_pending_by_chat()
|
||||
|
||||
@api.multi
|
||||
def _channel_message_notifications(self, message):
|
||||
def _channel_message_notifications(self, message, message_format=False):
|
||||
""" Generate the bus notifications for the given message
|
||||
:param message : the mail.message to sent
|
||||
:returns list of bus notifications (tuple (bus_channe, message_content))
|
||||
"""
|
||||
message_values = message.message_format()[0]
|
||||
message_format = message_format or message.message_format()[0]
|
||||
notifications = []
|
||||
for channel in self:
|
||||
notifications.append([(self._cr.dbname, 'mail.channel', channel.id), dict(message_values)])
|
||||
notifications.append([(self._cr.dbname, 'mail.channel', channel.id), dict(message_format)])
|
||||
# add uuid to allow anonymous to listen
|
||||
if channel.public == 'public':
|
||||
notifications.append([channel.uuid, dict(message_values)])
|
||||
notifications.append([channel.uuid, dict(message_format)])
|
||||
return notifications
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -168,7 +168,7 @@ class MailMail(models.Model):
|
||||
'failure_type': failure_type,
|
||||
'failure_reason': failure_reason,
|
||||
})
|
||||
messages = notifications.mapped('mail_message_id').filtered(lambda m: m.res_id and m.model)
|
||||
messages = notifications.mapped('mail_message_id').filtered(lambda m: m.is_thread_message())
|
||||
messages._notify_failure_update() # notify user that we have a failure
|
||||
(notifications - failed).sudo().write({
|
||||
'email_status': 'sent',
|
||||
@@ -312,7 +312,7 @@ class MailMail(models.Model):
|
||||
bounce_alias = ICP.get_param("mail.bounce.alias")
|
||||
catchall_domain = ICP.get_param("mail.catchall.domain")
|
||||
if bounce_alias and catchall_domain:
|
||||
if mail.model and mail.res_id:
|
||||
if mail.mail_message_id.is_thread_message():
|
||||
headers['Return-Path'] = '%s+%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
|
||||
else:
|
||||
headers['Return-Path'] = '%s+%d@%s' % (bounce_alias, mail.id, catchall_domain)
|
||||
|
||||
+156
-195
@@ -59,7 +59,8 @@ class Message(models.Model):
|
||||
message_type = fields.Selection([
|
||||
('email', 'Email'),
|
||||
('comment', 'Comment'),
|
||||
('notification', 'System notification')],
|
||||
('notification', 'System notification'),
|
||||
('user_notification', 'User Specific Notification')],
|
||||
'Type', required=True, default='email',
|
||||
help="Message type: email for email message, notification for system "
|
||||
"message, comment for other messages such as user replies",
|
||||
@@ -79,8 +80,7 @@ class Message(models.Model):
|
||||
author_avatar = fields.Binary("Author's avatar", related='author_id.image_small', readonly=False)
|
||||
# recipients: include inactive partners (they may have been archived after
|
||||
# the message was sent, but they should remain visible in the relation)
|
||||
partner_ids = fields.Many2many('res.partner', string='Recipients',
|
||||
context={'active_test': False})
|
||||
partner_ids = fields.Many2many('res.partner', string='Recipients', context={'active_test': False})
|
||||
needaction_partner_ids = fields.Many2many(
|
||||
'res.partner', 'mail_message_res_partner_needaction_rel', string='Partners with Need Action',
|
||||
context={'active_test': False})
|
||||
@@ -124,7 +124,7 @@ class Message(models.Model):
|
||||
moderator_id = fields.Many2one('res.users', string="Moderated By", index=True)
|
||||
need_moderation = fields.Boolean('Need moderation', compute='_compute_need_moderation', search='_search_need_moderation')
|
||||
#keep notification layout informations to be able to generate mail again
|
||||
layout = fields.Char('Layout', copy=False) # xml id of layout
|
||||
email_layout_xmlid = fields.Char('Layout', copy=False, oldname='layout') # xml id of layout
|
||||
add_sign = fields.Boolean(default=True)
|
||||
|
||||
@api.multi
|
||||
@@ -393,7 +393,8 @@ class Message(models.Model):
|
||||
customer_email_data.append((partner_tree[notification.res_partner_id.id][0], partner_tree[notification.res_partner_id.id][1], notification.email_status))
|
||||
|
||||
has_access_to_model = message.model and self.env[message.model].check_access_rights('read', raise_exception=False)
|
||||
main_attachment = has_access_to_model and message.res_id and self.env[message.model].search([('id', '=',message.res_id)]) and getattr(self.env[message.model].browse(message.res_id), 'message_main_attachment_id')
|
||||
if message.attachment_ids and message.res_id and issubclass(self.pool[message.model], self.pool['mail.thread']) and has_access_to_model:
|
||||
main_attachment = self.env[message.model].browse(message.res_id).message_main_attachment_id
|
||||
attachment_ids = []
|
||||
for attachment in message.attachment_ids:
|
||||
if attachment.id in attachments_tree:
|
||||
@@ -417,7 +418,13 @@ class Message(models.Model):
|
||||
|
||||
@api.multi
|
||||
def message_fetch_failed(self):
|
||||
messages = self.search([('has_error', '=', True), ('author_id.id', '=', self.env.user.partner_id.id), ('res_id', '!=', 0), ('model', '!=', False)])
|
||||
messages = self.search([
|
||||
('has_error', '=', True),
|
||||
('author_id.id', '=', self.env.user.partner_id.id),
|
||||
('res_id', '!=', 0),
|
||||
('model', '!=', False),
|
||||
('message_type', '!=', 'user_notification')
|
||||
])
|
||||
return messages._format_mail_failures()
|
||||
|
||||
@api.model
|
||||
@@ -515,7 +522,7 @@ class Message(models.Model):
|
||||
message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', [])
|
||||
message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id
|
||||
message['is_discussion'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id
|
||||
message['is_notification'] = message['is_note'] and not message['model'] and not message['res_id']
|
||||
message['is_notification'] = message['message_type'] == 'user_notification'
|
||||
message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
|
||||
if message['model'] and self.env[message['model']]._original_module:
|
||||
message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module)
|
||||
@@ -583,6 +590,19 @@ class Message(models.Model):
|
||||
if not self._cr.fetchone():
|
||||
self._cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
|
||||
|
||||
@api.multi
|
||||
def is_thread_message(self, vals=None):
|
||||
if vals:
|
||||
res_id = vals.get('res_id')
|
||||
model = vals.get('model')
|
||||
message_type = vals.get('message_type')
|
||||
else:
|
||||
self.ensure_one()
|
||||
res_id = self.res_id
|
||||
model = self.model
|
||||
message_type = self.message_type
|
||||
return res_id and model and message_type != 'user_notification'
|
||||
|
||||
@api.model
|
||||
def _find_allowed_model_wise(self, doc_model, doc_dict):
|
||||
doc_ids = list(doc_dict)
|
||||
@@ -640,7 +660,7 @@ class Message(models.Model):
|
||||
super(Message, self.sudo(access_rights_uid or self._uid)).check_access_rights('read')
|
||||
|
||||
self._cr.execute("""
|
||||
SELECT DISTINCT m.id, m.model, m.res_id, m.author_id,
|
||||
SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.message_type,
|
||||
COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id),
|
||||
channel_partner.channel_id as channel_id
|
||||
FROM "%s" m
|
||||
@@ -656,14 +676,14 @@ class Message(models.Model):
|
||||
ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s
|
||||
|
||||
WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=pid, ids=ids))
|
||||
for id, rmod, rid, author_id, partner_id, channel_id in self._cr.fetchall():
|
||||
for id, rmod, rid, author_id, message_type, partner_id, channel_id in self._cr.fetchall():
|
||||
if author_id == pid:
|
||||
author_ids.add(id)
|
||||
elif partner_id == pid:
|
||||
partner_ids.add(id)
|
||||
elif channel_id:
|
||||
channel_ids.add(id)
|
||||
elif rmod and rid:
|
||||
elif rmod and rid and message_type != 'user_notification':
|
||||
model_ids.setdefault(rmod, {}).setdefault(rid, set()).add(id)
|
||||
|
||||
allowed_ids = self._find_allowed_doc_ids(model_ids)
|
||||
@@ -733,13 +753,14 @@ class Message(models.Model):
|
||||
(self._description, operation))
|
||||
|
||||
# Read mail_message.ids to have their values
|
||||
message_values = dict((res_id, {}) for res_id in self.ids)
|
||||
message_values = dict((message_id, {}) for message_id in self.ids)
|
||||
|
||||
if operation == 'read':
|
||||
self._cr.execute("""
|
||||
SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id,
|
||||
COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id),
|
||||
channel_partner.channel_id as channel_id, m.moderation_status
|
||||
channel_partner.channel_id as channel_id, m.moderation_status,
|
||||
m.message_type as message_type
|
||||
FROM "%s" m
|
||||
LEFT JOIN "mail_message_res_partner_rel" partner_rel
|
||||
ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s
|
||||
@@ -752,7 +773,7 @@ class Message(models.Model):
|
||||
LEFT JOIN "mail_channel_partner" channel_partner
|
||||
ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s
|
||||
WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, ids=self.ids))
|
||||
for mid, rmod, rid, author_id, parent_id, partner_id, channel_id, moderation_status in self._cr.fetchall():
|
||||
for mid, rmod, rid, author_id, parent_id, partner_id, channel_id, moderation_status, message_type in self._cr.fetchall():
|
||||
message_values[mid] = {
|
||||
'model': rmod,
|
||||
'res_id': rid,
|
||||
@@ -760,13 +781,15 @@ class Message(models.Model):
|
||||
'parent_id': parent_id,
|
||||
'moderation_status': moderation_status,
|
||||
'moderator_id': False,
|
||||
'notified': any((message_values[mid].get('notified'), partner_id, channel_id))
|
||||
'notified': any((message_values[mid].get('notified'), partner_id, channel_id)),
|
||||
'message_type': message_type,
|
||||
}
|
||||
elif operation == 'write':
|
||||
self._cr.execute("""
|
||||
SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status,
|
||||
COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id),
|
||||
channel_partner.channel_id as channel_id, channel_moderator_rel.res_users_id as moderator_id
|
||||
channel_partner.channel_id as channel_id, channel_moderator_rel.res_users_id as moderator_id,
|
||||
m.message_type as message_type
|
||||
FROM "%s" m
|
||||
LEFT JOIN "mail_message_res_partner_rel" partner_rel
|
||||
ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s
|
||||
@@ -783,7 +806,7 @@ class Message(models.Model):
|
||||
LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel
|
||||
ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = %%(uid)s
|
||||
WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, uid=self.env.user.id, ids=self.ids))
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status, partner_id, channel_id, moderator_id in self._cr.fetchall():
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status, partner_id, channel_id, moderator_id, message_type in self._cr.fetchall():
|
||||
message_values[mid] = {
|
||||
'model': rmod,
|
||||
'res_id': rid,
|
||||
@@ -791,35 +814,38 @@ class Message(models.Model):
|
||||
'parent_id': parent_id,
|
||||
'moderation_status': moderation_status,
|
||||
'moderator_id': moderator_id,
|
||||
'notified': any((message_values[mid].get('notified'), partner_id, channel_id))
|
||||
'notified': any((message_values[mid].get('notified'), partner_id, channel_id)),
|
||||
'message_type': message_type,
|
||||
}
|
||||
elif operation == 'create':
|
||||
self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, moderation_status FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,))
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status in self._cr.fetchall():
|
||||
self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, moderation_status, message_type FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,))
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status, message_type in self._cr.fetchall():
|
||||
message_values[mid] = {
|
||||
'model': rmod,
|
||||
'res_id': rid,
|
||||
'author_id': author_id,
|
||||
'parent_id': parent_id,
|
||||
'moderation_status': moderation_status,
|
||||
'moderator_id': False
|
||||
'moderator_id': False,
|
||||
'message_type': message_type,
|
||||
}
|
||||
else: # unlink
|
||||
self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, channel_moderator_rel.res_users_id as moderator_id
|
||||
self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, channel_moderator_rel.res_users_id as moderator_id, m.message_type as message_type
|
||||
FROM "%s" m
|
||||
LEFT JOIN "mail_channel" moderated_channel
|
||||
ON m.moderation_status = 'pending_moderation' AND m.res_id = moderated_channel.id
|
||||
LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel
|
||||
ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = (%%s)
|
||||
WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.id, self.ids,))
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status, moderator_id in self._cr.fetchall():
|
||||
for mid, rmod, rid, author_id, parent_id, moderation_status, moderator_id, message_type in self._cr.fetchall():
|
||||
message_values[mid] = {
|
||||
'model': rmod,
|
||||
'res_id': rid,
|
||||
'author_id': author_id,
|
||||
'parent_id': parent_id,
|
||||
'moderation_status': moderation_status,
|
||||
'moderator_id': moderator_id
|
||||
'moderator_id': moderator_id,
|
||||
'message_type': message_type,
|
||||
}
|
||||
|
||||
# Author condition (READ, WRITE, CREATE (private))
|
||||
@@ -832,7 +858,56 @@ class Message(models.Model):
|
||||
if message.get('moderation_status') != 'pending_moderation' and message.get('author_id') == self.env.user.partner_id.id]
|
||||
elif operation == 'create':
|
||||
author_ids = [mid for mid, message in message_values.items()
|
||||
if not message.get('model') and not message.get('res_id')]
|
||||
if not self.is_thread_message(message)]
|
||||
|
||||
# Moderator condition: allow to WRITE, UNLINK if moderator of a pending message
|
||||
moderator_ids = []
|
||||
if operation in ['write', 'unlink']:
|
||||
moderator_ids = [mid for mid, message in message_values.items() if message.get('moderator_id')]
|
||||
messages_to_check = self.ids
|
||||
messages_to_check = set(messages_to_check).difference(set(author_ids), set(moderator_ids))
|
||||
if not messages_to_check:
|
||||
return
|
||||
|
||||
# Recipients condition, for read and write (partner_ids)
|
||||
# keep on top, usefull for systray notifications
|
||||
notified_ids = []
|
||||
model_record_ids = _generate_model_record_ids(message_values, messages_to_check)
|
||||
if operation in ['read', 'write']:
|
||||
notified_ids = [mid for mid, message in message_values.items() if message.get('notified')]
|
||||
|
||||
messages_to_check = set(messages_to_check).difference(set(notified_ids))
|
||||
if not messages_to_check:
|
||||
return
|
||||
|
||||
# CRUD: Access rights related to the document
|
||||
document_related_ids = []
|
||||
document_related_candidate_ids = [mid for mid, message in message_values.items()
|
||||
if (message.get('model') and message.get('res_id') and
|
||||
message.get('message_type') != 'user_notification' and
|
||||
(message.get('moderation_status') != 'pending_moderation' or operation not in ['write', 'unlink']))]
|
||||
model_record_ids = _generate_model_record_ids(message_values, document_related_candidate_ids)
|
||||
for model, doc_ids in model_record_ids.items():
|
||||
DocumentModel = self.env[model]
|
||||
if hasattr(DocumentModel, 'get_mail_message_access'):
|
||||
check_operation = DocumentModel.get_mail_message_access(doc_ids, operation) ## why not giving model here?
|
||||
else:
|
||||
check_operation = self.env['mail.thread'].get_mail_message_access(doc_ids, operation, model_name=model)
|
||||
records = DocumentModel.browse(doc_ids)
|
||||
records.check_access_rights(check_operation)
|
||||
mids = records.browse(doc_ids)._filter_access_rules(check_operation)
|
||||
document_related_ids += [
|
||||
mid for mid, message in message_values.items()
|
||||
if (message.get('model') == model and
|
||||
message.get('res_id') in mids.ids and
|
||||
message.get('message_type') != 'user_notification' and
|
||||
(message.get('moderation_status') != 'pending_moderation' or
|
||||
operation not in ['write', 'unlink']))]
|
||||
|
||||
messages_to_check = messages_to_check.difference(set(document_related_ids))
|
||||
|
||||
if not messages_to_check:
|
||||
return
|
||||
|
||||
# Parent condition, for create (check for received notifications for the created message parent)
|
||||
notified_ids = []
|
||||
@@ -854,17 +929,12 @@ class Message(models.Model):
|
||||
notified_ids += [mid for mid, message in message_values.items()
|
||||
if message.get('parent_id') in not_parent_ids]
|
||||
|
||||
# Moderator condition: allow to WRITE, UNLINK if moderator of a pending message
|
||||
moderator_ids = []
|
||||
if operation in ['write', 'unlink']:
|
||||
moderator_ids = [mid for mid, message in message_values.items() if message.get('moderator_id')]
|
||||
messages_to_check = messages_to_check.difference(set(notified_ids))
|
||||
if not messages_to_check:
|
||||
return
|
||||
|
||||
# Recipients condition, for read and write (partner_ids) and create (message_follower_ids)
|
||||
other_ids = set(self.ids).difference(set(author_ids), set(notified_ids), set(moderator_ids))
|
||||
model_record_ids = _generate_model_record_ids(message_values, other_ids)
|
||||
if operation in ['read', 'write']:
|
||||
notified_ids = [mid for mid, message in message_values.items() if message.get('notified')]
|
||||
elif operation == 'create':
|
||||
# Recipients condition for create (message_follower_ids)
|
||||
if operation == 'create':
|
||||
for doc_model, doc_ids in model_record_ids.items():
|
||||
followers = self.env['mail.followers'].sudo().search([
|
||||
('res_model', '=', doc_model),
|
||||
@@ -873,30 +943,16 @@ class Message(models.Model):
|
||||
])
|
||||
fol_mids = [follower.res_id for follower in followers]
|
||||
notified_ids += [mid for mid, message in message_values.items()
|
||||
if message.get('model') == doc_model and message.get('res_id') in fol_mids]
|
||||
if message.get('model') == doc_model and
|
||||
message.get('res_id') in fol_mids and
|
||||
message.get('message_type') != 'user_notification'
|
||||
]
|
||||
|
||||
# CRUD: Access rights related to the document
|
||||
other_ids = other_ids.difference(set(notified_ids))
|
||||
model_record_ids = _generate_model_record_ids(message_values, other_ids)
|
||||
document_related_ids = []
|
||||
for model, doc_ids in model_record_ids.items():
|
||||
DocumentModel = self.env[model]
|
||||
mids = DocumentModel.browse(doc_ids).exists()
|
||||
if hasattr(DocumentModel, 'check_mail_message_access'):
|
||||
DocumentModel.check_mail_message_access(mids.ids, operation) # ?? mids ?
|
||||
else:
|
||||
self.env['mail.thread'].check_mail_message_access(mids.ids, operation, model_name=model)
|
||||
if operation in ['write', 'unlink']:
|
||||
document_related_ids += [mid for mid, message in message_values.items()
|
||||
if message.get('model') == model and message.get('res_id') in mids.ids and
|
||||
message.get('moderation_status') != 'pending_moderation']
|
||||
else:
|
||||
document_related_ids += [mid for mid, message in message_values.items()
|
||||
if message.get('model') == model and message.get('res_id') in mids.ids]
|
||||
messages_to_check = messages_to_check.difference(set(notified_ids))
|
||||
if not messages_to_check:
|
||||
return
|
||||
|
||||
# Calculate remaining ids: if not void, raise an error
|
||||
other_ids = other_ids.difference(set(document_related_ids))
|
||||
if not (other_ids and self.browse(other_ids).exists()):
|
||||
if not self.browse(messages_to_check).exists():
|
||||
return
|
||||
raise AccessError(
|
||||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') %
|
||||
@@ -915,32 +971,41 @@ class Message(models.Model):
|
||||
@api.model
|
||||
def _get_reply_to(self, values):
|
||||
""" Return a specific reply_to for the document """
|
||||
model, res_id, email_from = values.get('model', self._context.get('default_model')), values.get('res_id', self._context.get('default_res_id')), values.get('email_from') # ctx values / defualt_get res ?
|
||||
records = self.env[model].browse([res_id]) if model and res_id else None
|
||||
return self.env['mail.thread']._notify_get_reply_to_on_records(default=email_from, records=records)[res_id or False]
|
||||
model = values.get('model', self._context.get('default_model'))
|
||||
res_id = values.get('res_id', self._context.get('default_res_id'))
|
||||
email_from = values.get('email_from')
|
||||
message_type = values.get('message_type')
|
||||
records = None
|
||||
if self.is_thread_message({'model': model, 'res_id': res_id, 'message_type': message_type}):
|
||||
records = self.env[model].browse([res_id])
|
||||
else:
|
||||
res_id = False
|
||||
return self.env['mail.thread']._notify_get_reply_to_on_records(default=email_from, records=records)[res_id]
|
||||
|
||||
@api.model
|
||||
def _get_message_id(self, values):
|
||||
if values.get('no_auto_thread', False) is True:
|
||||
message_id = tools.generate_tracking_message_id('reply_to')
|
||||
elif values.get('res_id') and values.get('model'):
|
||||
elif self.is_thread_message(values):
|
||||
message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
else:
|
||||
message_id = tools.generate_tracking_message_id('private')
|
||||
return message_id
|
||||
|
||||
@api.multi
|
||||
def _invalidate_documents(self):
|
||||
def _invalidate_documents(self, model=None, res_id=None):
|
||||
""" Invalidate the cache of the documents followed by ``self``. """
|
||||
for record in self:
|
||||
if record.model and record.res_id and 'message_ids' in self.env[record.model]:
|
||||
self.env[record.model].invalidate_cache(fnames=[
|
||||
model = model or record.model
|
||||
res_id = res_id or record.res_id
|
||||
if 'message_ids' in self.env[model]:
|
||||
self.env[model].invalidate_cache(fnames=[
|
||||
'message_ids',
|
||||
'message_unread',
|
||||
'message_unread_counter',
|
||||
'message_needaction',
|
||||
'message_needaction_counter',
|
||||
], ids=[record.res_id])
|
||||
], ids=[res_id])
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
@@ -958,8 +1023,7 @@ class Message(models.Model):
|
||||
values['record_name'] = self._get_record_name(values)
|
||||
|
||||
if 'attachment_ids' not in values:
|
||||
values.setdefault('attachment_ids', [])
|
||||
|
||||
values['attachment_ids'] = []
|
||||
# extract base64 images
|
||||
if 'body' in values:
|
||||
Attachments = self.env['ir.attachment']
|
||||
@@ -985,8 +1049,21 @@ class Message(models.Model):
|
||||
tracking_values_cmd = values.pop('tracking_value_ids', False)
|
||||
message = super(Message, self).create(values)
|
||||
|
||||
# check attachement access
|
||||
if values.get('attachment_ids'):
|
||||
message.attachment_ids.check(mode='read')
|
||||
attachment_ids = []
|
||||
if all(isinstance(command, int) or command[0] in (4, 6) for command in values.get('attachment_ids')):
|
||||
attachment_ids = []
|
||||
for command in values.get('attachment_ids'):
|
||||
if isinstance(command, int):
|
||||
attachment_ids += [command]
|
||||
elif command[0] == 6:
|
||||
attachment_ids += command[2]
|
||||
else: # command[0] == 4:
|
||||
attachment_ids += [command[1]]
|
||||
else:
|
||||
attachment_ids = message.mapped('attachment_ids').ids # fallback on read if any unknow command
|
||||
self.env['ir.attachment'].browse(attachment_ids).check(mode='read')
|
||||
|
||||
if tracking_values_cmd:
|
||||
vals_lst = [dict(cmd[2], mail_message_id=message.id) for cmd in tracking_values_cmd if len(cmd) == 3 and cmd[0] == 0]
|
||||
@@ -996,8 +1073,8 @@ class Message(models.Model):
|
||||
if other_cmd:
|
||||
message.sudo().write({'tracking_value_ids': tracking_values_cmd})
|
||||
|
||||
if values.get('model') and values.get('res_id'):
|
||||
message._invalidate_documents()
|
||||
if message.is_thread_message(values):
|
||||
message._invalidate_documents(values.get('model'), values.get('res_id'))
|
||||
|
||||
return message
|
||||
|
||||
@@ -1010,13 +1087,14 @@ class Message(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'model' in vals or 'res_id' in vals:
|
||||
record_changed = 'model' in vals or 'res_id' in vals
|
||||
if record_changed or 'message_type' in vals:
|
||||
self._invalidate_documents()
|
||||
res = super(Message, self).write(vals)
|
||||
if vals.get('attachment_ids'):
|
||||
for mail in self:
|
||||
mail.attachment_ids.check(mode='read')
|
||||
if 'notification_ids' in vals or 'model' in vals or 'res_id' in vals:
|
||||
if 'notification_ids' in vals or record_changed:
|
||||
self._invalidate_documents()
|
||||
return res
|
||||
|
||||
@@ -1030,129 +1108,11 @@ class Message(models.Model):
|
||||
self.mapped('attachment_ids').filtered(
|
||||
lambda attach: attach.res_model == self._name and (attach.res_id in self.ids or attach.res_id == 0)
|
||||
).unlink()
|
||||
self._invalidate_documents()
|
||||
for elem in self:
|
||||
if elem.is_thread_message():
|
||||
elem._invalidate_documents()
|
||||
return super(Message, self).unlink()
|
||||
|
||||
#------------------------------------------------------
|
||||
# Messaging API
|
||||
#------------------------------------------------------
|
||||
|
||||
@api.multi
|
||||
def _notify(self, record, msg_vals, force_send=False, send_after_commit=True, model_description=False, mail_auto_delete=True):
|
||||
""" Main notification method. This method basically does two things
|
||||
|
||||
* call ``_notify_compute_recipients`` that computes recipients to
|
||||
notify based on message record or message creation values if given
|
||||
(to optimize performance if we already have data computed);
|
||||
* call ``_notify_recipients`` that performs the notification process;
|
||||
|
||||
:param record: record on which the message is posted, if any;
|
||||
:param msg_vals: dictionary of values used to create the message. If given
|
||||
it is used instead of accessing ``self`` to lesen query count in some
|
||||
simple cases where no notification is actually required;
|
||||
:param force_send: tells whether to send notification emails within the
|
||||
current transaction or to use the email queue;
|
||||
:param send_after_commit: if force_send, tells whether to send emails after
|
||||
the transaction has been committed using a post-commit hook;
|
||||
:param model_description: optional data used in notification process (see
|
||||
notification templates);
|
||||
:param mail_auto_delete: delete notification emails once sent;
|
||||
"""
|
||||
msg_vals = msg_vals if msg_vals else {}
|
||||
rdata = self._notify_compute_recipients(record, msg_vals)
|
||||
return self._notify_recipients(
|
||||
rdata, record, msg_vals,
|
||||
force_send=force_send, send_after_commit=send_after_commit,
|
||||
model_description=model_description, mail_auto_delete=mail_auto_delete)
|
||||
|
||||
@api.multi
|
||||
def _notify_compute_recipients(self, record, msg_vals):
|
||||
""" Compute recipients to notify based on subtype and followers. This
|
||||
method returns data structured as expected for ``_notify_recipients``. """
|
||||
msg_sudo = self.sudo()
|
||||
|
||||
pids = [x[1] for x in msg_vals.get('partner_ids')] if 'partner_ids' in msg_vals else msg_sudo.partner_ids.ids
|
||||
cids = [x[1] for x in msg_vals.get('channel_ids')] if 'channel_ids' in msg_vals else msg_sudo.channel_ids.ids
|
||||
subtype_id = msg_vals.get('subtype_id') if 'subtype_id' in msg_vals else msg_sudo.subtype_id.id
|
||||
|
||||
recipient_data = {
|
||||
'partners': [],
|
||||
'channels': [],
|
||||
}
|
||||
res = self.env['mail.followers']._get_recipient_data(record, subtype_id, pids, cids)
|
||||
author_id = msg_vals.get('author_id') or self.author_id.id if res else False
|
||||
for pid, cid, active, pshare, ctype, notif, groups in res:
|
||||
if pid and pid == author_id and not self.env.context.get('mail_notify_author'): # do not notify the author of its own messages
|
||||
continue
|
||||
if pid:
|
||||
if active is False:
|
||||
# avoid to notify inactive partner by email (odoobot)
|
||||
continue
|
||||
pdata = {'id': pid, 'active': active, 'share': pshare, 'groups': groups}
|
||||
if notif == 'inbox':
|
||||
recipient_data['partners'].append(dict(pdata, notif=notif, type='user'))
|
||||
else:
|
||||
if not pshare and notif: # has an user and is not shared, is therefore user
|
||||
recipient_data['partners'].append(dict(pdata, notif='email', type='user'))
|
||||
elif pshare and notif: # has an user but is shared, is therefore portal
|
||||
recipient_data['partners'].append(dict(pdata, notif='email', type='portal'))
|
||||
else: # has no user, is therefore customer
|
||||
recipient_data['partners'].append(dict(pdata, notif='email', type='customer'))
|
||||
elif cid:
|
||||
recipient_data['channels'].append({'id': cid, 'notif': notif, 'type': ctype})
|
||||
return recipient_data
|
||||
|
||||
@api.multi
|
||||
def _notify_recipients(self, rdata, record, msg_vals,
|
||||
force_send=False, send_after_commit=True,
|
||||
model_description=False, mail_auto_delete=True):
|
||||
""" Main method implementing the notification process.
|
||||
|
||||
:param rdata: dict containing recipients data: {
|
||||
'partners': list of dict containing partner data: id, share status (boolean),
|
||||
notification type ('email' or 'inbox'), type (main classification group used
|
||||
in notification process), groups (user groups)
|
||||
'channels': list of dict containing channel data: id, notification type
|
||||
('email' for mailing list otherwise 'inbox'), type (channel_type)
|
||||
}
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
email_cids = [r['id'] for r in rdata['channels'] if r['notif'] == 'email']
|
||||
inbox_pids = [r['id'] for r in rdata['partners'] if r['notif'] == 'inbox']
|
||||
|
||||
message_values = {}
|
||||
if rdata['channels']:
|
||||
message_values['channel_ids'] = [(6, 0, [r['id'] for r in rdata['channels']])]
|
||||
if rdata['partners']:
|
||||
message_values['needaction_partner_ids'] = [(6, 0, [r['id'] for r in rdata['partners']])]
|
||||
if message_values and record and hasattr(record, '_notify_customize_recipients'):
|
||||
message_values.update(record._notify_customize_recipients(self, message_values, rdata))
|
||||
if message_values:
|
||||
self.write(message_values)
|
||||
|
||||
# notify partners and channels
|
||||
if email_cids:
|
||||
new_pids = self.env['res.partner'].sudo().search([
|
||||
('id', 'not in', [r['id'] for r in rdata['partners']]),
|
||||
('channel_ids', 'in', email_cids),
|
||||
('email', 'not in', [self.author_id.email, self.email_from]),
|
||||
])
|
||||
for partner in new_pids:
|
||||
rdata['partners'].append({'id': partner.id, 'share': True, 'notif': 'email', 'type': 'customer', 'groups': []})
|
||||
|
||||
partner_email_rdata = [r for r in rdata['partners'] if r['notif'] == 'email']
|
||||
if partner_email_rdata:
|
||||
self.env['res.partner']._notify(self, partner_email_rdata, record, force_send=force_send, send_after_commit=send_after_commit, model_description=model_description, mail_auto_delete=mail_auto_delete)
|
||||
|
||||
if inbox_pids:
|
||||
self.env['res.partner'].browse(inbox_pids)._notify_by_chat(self)
|
||||
|
||||
if rdata['channels']:
|
||||
self.env['mail.channel'].sudo().browse([r['id'] for r in rdata['channels']])._notify(self)
|
||||
|
||||
return True
|
||||
|
||||
# --------------------------------------------------
|
||||
# Moderation
|
||||
# --------------------------------------------------
|
||||
@@ -1214,8 +1174,9 @@ class Message(models.Model):
|
||||
})
|
||||
# proceed with notification process to send notification emails and Inbox messages
|
||||
for message in self:
|
||||
record = self.env[message.model].browse(message.res_id) if message.model and message.res_id else None
|
||||
message._notify(record, {})
|
||||
if message.is_thread_message(): # note, since we will only intercept _notify_thread for message posted on channel,
|
||||
# message will always be a thread_message. This check should always be true.
|
||||
self.env[message.model].browse(message.res_id)._notify_thread(message)
|
||||
|
||||
@api.multi
|
||||
def _moderate_send_reject_email(self, subject, comment):
|
||||
@@ -1312,7 +1273,7 @@ class Message(models.Model):
|
||||
MailThread = self.env['mail.thread'].with_context(mail_notify_author=True)
|
||||
for moderator in moderators_to_notify:
|
||||
MailThread.message_notify(
|
||||
moderator.partner_id.ids,
|
||||
partner_ids=moderator.partner_id.ids,
|
||||
subject=_('Message are pending moderation'), # tocheck: target language
|
||||
body=template.render({'record': moderator.partner_id}, engine='ir.qweb', minimal_qcontext=True),
|
||||
email_from=moderator.company_id.catchall or moderator.company_id.email,
|
||||
|
||||
+829
-514
File diff suppressed because it is too large
Load Diff
@@ -17,3 +17,18 @@ class BaseModel(models.AbstractModel):
|
||||
activity_box = E.div(field, {'t-name': "activity-box"})
|
||||
templates = E.templates(activity_box)
|
||||
return E.activity(templates, string=self._description)
|
||||
|
||||
@api.multi
|
||||
def _notify_email_headers(self):
|
||||
"""
|
||||
Generate the email headers based on record
|
||||
"""
|
||||
if not self:
|
||||
return {}
|
||||
self.ensure_one()
|
||||
return repr(self._notify_email_header_dict())
|
||||
|
||||
def _notify_email_header_dict(self):
|
||||
return {
|
||||
'X-Odoo-Objects': "%s-%s" % (self._name, self.id),
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from odoo.tools.misc import split_every
|
||||
|
||||
from odoo import _, api, fields, models, registry, SUPERUSER_ID
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.addons.bus.models.bus_presence import AWAY_TIMER
|
||||
from odoo.addons.bus.models.bus_presence import DISCONNECTION_TIMER
|
||||
from odoo.osv import expression
|
||||
@@ -40,176 +37,6 @@ class Partner(models.Model):
|
||||
'email_cc': False}
|
||||
for r in self}
|
||||
|
||||
@api.model
|
||||
def _notify_prepare_template_context(self, message, record, model_description=False, mail_auto_delete=True):
|
||||
# compute send user and its related signature
|
||||
signature = ''
|
||||
if message.author_id and message.author_id.user_ids:
|
||||
user = message.author_id.user_ids[0]
|
||||
if message.add_sign:
|
||||
signature = user.signature
|
||||
else:
|
||||
user = self.env.user
|
||||
if message.add_sign:
|
||||
signature = "<p>-- <br/>%s</p>" % message.author_id.name
|
||||
|
||||
company = record.company_id.sudo() if record and 'company_id' in record else user.company_id
|
||||
if company.website:
|
||||
website_url = 'http://%s' % company.website if not company.website.lower().startswith(('http:', 'https:')) else company.website
|
||||
else:
|
||||
website_url = False
|
||||
|
||||
# Retrieve the language in which the template was rendered, in order to render the custom
|
||||
# layout in the same language.
|
||||
lang = self.env.context.get('lang')
|
||||
if {'default_template_id', 'default_model', 'default_res_id'} <= self.env.context.keys():
|
||||
template = self.env['mail.template'].browse(self.env.context['default_template_id'])
|
||||
if template and template.lang:
|
||||
lang = template._render_template(template.lang, self.env.context['default_model'], self.env.context['default_res_id'])
|
||||
|
||||
if not model_description and message.model:
|
||||
model_description = self.env['ir.model'].with_context(lang=lang)._get(message.model).display_name
|
||||
|
||||
tracking = []
|
||||
for tracking_value in self.env['mail.tracking.value'].sudo().search([('mail_message_id', '=', message.id)]):
|
||||
groups = tracking_value.field_groups
|
||||
if not groups or self.user_has_groups(groups):
|
||||
tracking.append((tracking_value.field_desc,
|
||||
tracking_value.get_old_display_value()[0],
|
||||
tracking_value.get_new_display_value()[0]))
|
||||
|
||||
is_discussion = message.subtype_id.id == self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
|
||||
|
||||
return {
|
||||
'message': message,
|
||||
'signature': signature,
|
||||
'website_url': website_url,
|
||||
'company': company,
|
||||
'model_description': model_description,
|
||||
'record': record,
|
||||
'record_name': message.record_name,
|
||||
'tracking_values': tracking,
|
||||
'is_discussion': is_discussion,
|
||||
'subtype': message.subtype_id,
|
||||
'lang': lang,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _notify(self, message, rdata, record, force_send=False, send_after_commit=True, model_description=False, mail_auto_delete=True):
|
||||
""" Method to send email linked to notified messages. The recipients are
|
||||
the recordset on which this method is called.
|
||||
|
||||
:param message: mail.message record to notify;
|
||||
:param rdata: recipient data (see mail.message _notify);
|
||||
:param record: optional record on which the message was posted;
|
||||
:param force_send: tells whether to send notification emails within the
|
||||
current transaction or to use the email queue;
|
||||
:param send_after_commit: if force_send, tells whether to send emails after
|
||||
the transaction has been committed using a post-commit hook;
|
||||
:param model_description: optional data used in notification process (see
|
||||
notification templates);
|
||||
:param mail_auto_delete: delete notification emails once sent;
|
||||
"""
|
||||
if not rdata:
|
||||
return True
|
||||
|
||||
base_template_ctx = self._notify_prepare_template_context(message, record, model_description=model_description)
|
||||
template_xmlid = message.layout if message.layout else 'mail.message_notification_email'
|
||||
try:
|
||||
base_template = self.env.ref(template_xmlid, raise_if_not_found=True).with_context(lang=base_template_ctx['lang'])
|
||||
except ValueError:
|
||||
_logger.warning('QWeb template %s not found when sending notification emails. Sending without layouting.' % (template_xmlid))
|
||||
base_template = False
|
||||
|
||||
# prepare notification mail values
|
||||
base_mail_values = {
|
||||
'mail_message_id': message.id,
|
||||
'mail_server_id': message.mail_server_id.id,
|
||||
'auto_delete': mail_auto_delete,
|
||||
'references': message.parent_id.message_id if message.parent_id else False
|
||||
}
|
||||
if record:
|
||||
base_mail_values.update(self.env['mail.thread']._notify_specific_email_values_on_records(message, records=record))
|
||||
|
||||
# classify recipients: actions / no action
|
||||
recipients = self.env['mail.thread']._notify_classify_recipients_on_records(message, rdata, records=record)
|
||||
|
||||
Mail = self.env['mail.mail'].sudo()
|
||||
emails = self.env['mail.mail'].sudo()
|
||||
email_pids = set()
|
||||
recipients_nbr, recipients_max = 0, 50
|
||||
for group_tpl_values in [group for group in recipients.values() if group['recipients']]:
|
||||
# generate notification email content
|
||||
template_ctx = {**base_template_ctx, **group_tpl_values}
|
||||
mail_body = base_template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
|
||||
mail_body = self.env['mail.thread']._replace_local_links(mail_body)
|
||||
mail_subject = message.subject or (message.record_name and 'Re: %s' % message.record_name)
|
||||
|
||||
# send email
|
||||
for email_chunk in split_every(50, group_tpl_values['recipients']):
|
||||
recipient_values = self.env['mail.thread']._notify_email_recipients_on_records(message, email_chunk, records=record)
|
||||
create_values = {
|
||||
'body_html': mail_body,
|
||||
'subject': mail_subject,
|
||||
}
|
||||
create_values.update(base_mail_values)
|
||||
create_values.update(recipient_values)
|
||||
recipient_ids = [r[1] for r in create_values.get('recipient_ids', [])]
|
||||
email = Mail.create(create_values)
|
||||
|
||||
if email and recipient_ids:
|
||||
notifications = self.env['mail.notification'].sudo().search([
|
||||
('mail_message_id', '=', email.mail_message_id.id),
|
||||
('res_partner_id', 'in', list(recipient_ids))
|
||||
])
|
||||
notifications.write({
|
||||
'is_email': True,
|
||||
'mail_id': email.id,
|
||||
'is_read': True, # handle by email discards Inbox notification
|
||||
'email_status': 'ready',
|
||||
})
|
||||
|
||||
emails |= email
|
||||
email_pids.update(recipient_ids)
|
||||
|
||||
# NOTE:
|
||||
# 1. for more than 50 followers, use the queue system
|
||||
# 2. do not send emails immediately if the registry is not loaded,
|
||||
# to prevent sending email during a simple update of the database
|
||||
# using the command-line.
|
||||
test_mode = getattr(threading.currentThread(), 'testing', False)
|
||||
if force_send and len(emails) < recipients_max and \
|
||||
(not self.pool._init or test_mode):
|
||||
email_ids = emails.ids
|
||||
dbname = self.env.cr.dbname
|
||||
_context = self._context
|
||||
|
||||
def send_notifications():
|
||||
db_registry = registry(dbname)
|
||||
with api.Environment.manage(), db_registry.cursor() as cr:
|
||||
env = api.Environment(cr, SUPERUSER_ID, _context)
|
||||
env['mail.mail'].browse(email_ids).send()
|
||||
|
||||
# unless asked specifically, send emails after the transaction to
|
||||
# avoid side effects due to emails being sent while the transaction fails
|
||||
if not test_mode and send_after_commit:
|
||||
self._cr.after('commit', send_notifications)
|
||||
else:
|
||||
emails.send()
|
||||
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _notify_by_chat(self, message):
|
||||
""" Broadcast the message to all the partner since """
|
||||
if not self:
|
||||
return
|
||||
message_values = message.message_format()[0]
|
||||
notifications = []
|
||||
for partner in self:
|
||||
notifications.append([(self._cr.dbname, 'ir.needaction', partner.id), dict(message_values)])
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
|
||||
@api.model
|
||||
def get_needaction_count(self):
|
||||
""" compute the number of needaction of the current user """
|
||||
@@ -294,3 +121,4 @@ class Partner(models.Model):
|
||||
return self.env.cr.dictfetchall()
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
|
||||
* @return {boolean}
|
||||
*/
|
||||
isLinkedToDocumentThread: function () {
|
||||
return !!(this._documentModel !== 'mail.channel' && this._documentID);
|
||||
return !!(this._documentModel !== 'mail.channel' && this._documentID && this._type !== 'user_notification');
|
||||
},
|
||||
/**
|
||||
* State whether the current user is the author of this message
|
||||
|
||||
@@ -72,11 +72,13 @@ class Invite(models.TransientModel):
|
||||
'no_auto_thread': True,
|
||||
'add_sign': True,
|
||||
})
|
||||
self.env['res.partner'].with_context(auto_delete=True)._notify(
|
||||
message,
|
||||
[{'id': pid, 'share': True, 'notif': 'email', 'type': 'customer', 'groups': []} for pid in new_partners.ids],
|
||||
document,
|
||||
force_send=True,
|
||||
send_after_commit=False)
|
||||
partners_data = [{
|
||||
'id': pid,
|
||||
'share': True,
|
||||
'notif': 'email',
|
||||
'type': 'customer',
|
||||
'groups': []
|
||||
} for pid in new_partners.ids]
|
||||
document._notify_record_by_email(message, partners_data, send_after_commit=False)
|
||||
message.unlink()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -184,9 +184,9 @@ class MailComposer(models.TransientModel):
|
||||
result['model'] = parent.model
|
||||
if not values.get('res_id'):
|
||||
result['res_id'] = parent.res_id
|
||||
partner_ids = values.get('partner_ids', list()) + [(4, id) for id in parent.partner_ids.ids]
|
||||
partner_ids = values.get('partner_ids', list()) + parent.partner_ids.ids
|
||||
if self._context.get('is_private') and parent.author_id: # check message is private then add author also in partner list.
|
||||
partner_ids += [(4, parent.author_id.id)]
|
||||
partner_ids += [parent.author_id.id]
|
||||
result['partner_ids'] = partner_ids
|
||||
elif values.get('model') and values.get('res_id'):
|
||||
doc_name_get = self.env[values.get('model')].browse(values.get('res_id')).name_get()
|
||||
@@ -274,14 +274,18 @@ class MailComposer(models.TransientModel):
|
||||
post_params = dict(
|
||||
message_type=wizard.message_type,
|
||||
subtype_id=subtype_id,
|
||||
notif_layout=notif_layout,
|
||||
email_layout_xmlid=notif_layout,
|
||||
add_sign=not bool(wizard.template_id),
|
||||
mail_auto_delete=wizard.template_id.auto_delete if wizard.template_id else False,
|
||||
model_description=model_description,
|
||||
**mail_values)
|
||||
if ActiveModel._name == 'mail.thread' and wizard.model:
|
||||
post_params['model'] = wizard.model
|
||||
ActiveModel.browse(res_id).message_post(**post_params)
|
||||
model_description=model_description)
|
||||
post_params.update(mail_values)
|
||||
if ActiveModel._name == 'mail.thread':
|
||||
if wizard.model:
|
||||
post_params['model'] = wizard.model
|
||||
post_params['res_id'] = res_id
|
||||
ActiveModel.message_notify(**post_params)
|
||||
else:
|
||||
ActiveModel.browse(res_id).message_post(**post_params)
|
||||
|
||||
if wizard.composition_mode == 'mass_mail':
|
||||
batch_mails.send(auto_commit=auto_commit)
|
||||
@@ -332,7 +336,8 @@ class MailComposer(models.TransientModel):
|
||||
|
||||
# mass mailing: rendering override wizard static values
|
||||
if mass_mail_mode and self.model:
|
||||
mail_values.update(self.env['mail.thread']._notify_specific_email_values_on_records(False, records=self.env[self.model].browse(res_id)))
|
||||
record = self.env[self.model].browse(res_id)
|
||||
mail_values['headers'] = record._notify_email_headers()
|
||||
# keep a copy unless specifically requested, reset record name (avoid browsing records)
|
||||
mail_values.update(notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False)
|
||||
# auto deletion of mail_mail
|
||||
@@ -362,7 +367,7 @@ class MailComposer(models.TransientModel):
|
||||
mail_values.pop('attachments', []),
|
||||
attachment_ids,
|
||||
{'model': 'mail.message', 'res_id': 0}
|
||||
)
|
||||
)['attachment_ids']
|
||||
# Filter out the blacklisted records by setting the mail state to cancel -> Used for Mass Mailing stats
|
||||
if res_id in blacklisted_rec_ids:
|
||||
mail_values['state'] = 'cancel'
|
||||
|
||||
@@ -63,26 +63,20 @@ class MailResendMessage(models.TransientModel):
|
||||
notif_to_cancel.sudo().write({'email_status': 'canceled'})
|
||||
if to_send:
|
||||
message = wizard.mail_message_id
|
||||
record = self.env[message.model].browse(message.res_id) if message.model and message.res_id else None
|
||||
record = self.env[message.model].browse(message.res_id) if message.is_thread_message() else self.env['mail.thread']
|
||||
|
||||
rdata = []
|
||||
email_partners_data = []
|
||||
for pid, cid, active, pshare, ctype, notif, groups in self.env['mail.followers']._get_recipient_data(None, False, pids=to_send.ids):
|
||||
if pid and notif == 'email' or not notif:
|
||||
pdata = {'id': pid, 'share': pshare, 'active': active, 'notif': 'email', 'groups': groups or []}
|
||||
if not pshare and notif: # has an user and is not shared, is therefore user
|
||||
rdata.append(dict(pdata, type='user'))
|
||||
email_partners_data.append(dict(pdata, type='user'))
|
||||
elif pshare and notif: # has an user and is shared, is therefore portal
|
||||
rdata.append(dict(pdata, type='portal'))
|
||||
email_partners_data.append(dict(pdata, type='portal'))
|
||||
else: # has no user, is therefore customer
|
||||
rdata.append(dict(pdata, type='customer'))
|
||||
email_partners_data.append(dict(pdata, type='customer'))
|
||||
|
||||
self.env['res.partner']._notify(
|
||||
message,
|
||||
rdata,
|
||||
record,
|
||||
force_send=True,
|
||||
send_after_commit=False
|
||||
)
|
||||
record._notify_record_by_email(message, email_partners_data, send_after_commit=False)
|
||||
|
||||
self.mail_message_id._notify_failure_update()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -214,7 +214,7 @@ class MailBot(models.AbstractModel):
|
||||
|
||||
def _is_bot_pinged(self, values):
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("base.partner_root")
|
||||
return (4, odoobot_id) in values.get('partner_ids', [])
|
||||
return odoobot_id in values.get('partner_ids', [])
|
||||
|
||||
def _is_bot_in_private_channel(self, record):
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("base.partner_root")
|
||||
|
||||
@@ -7,6 +7,6 @@ from odoo import models
|
||||
class MailThread(models.AbstractModel):
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
def _message_post_after_hook(self, message, values, model_description=False, mail_auto_delete=True):
|
||||
self.env['mail.bot']._apply_logic(self, values)
|
||||
return super(MailThread, self)._message_post_after_hook(message, values, model_description=model_description, mail_auto_delete=mail_auto_delete)
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
self.env['mail.bot']._apply_logic(self, msg_vals)
|
||||
return super(MailThread, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
@@ -20,7 +20,7 @@ class MassMailController(http.Controller):
|
||||
def _log_blacklist_action(self, blacklist_entry, mailing_id, description):
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
model_display = mailing.mailing_model_id.display_name
|
||||
blacklist_entry._message_log(description + " ({})".format(model_display))
|
||||
blacklist_entry._message_log(body=description + " ({})".format(model_display))
|
||||
|
||||
@http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public')
|
||||
def unsubscribe_placeholder_link(self, **post):
|
||||
|
||||
@@ -73,4 +73,4 @@ class MailThread(models.AbstractModel):
|
||||
if max(stats) > min(stats) + datetime.timedelta(weeks=1):
|
||||
blacklist_rec = self.env['mail.blacklist'].sudo()._add(email)
|
||||
blacklist_rec._message_log(
|
||||
'This email has been automatically blacklisted because of too much bounced.')
|
||||
body='This email has been automatically blacklisted because of too much bounced.')
|
||||
|
||||
@@ -81,11 +81,13 @@ def _message_post_helper(res_model, res_id, message, token='', nosubscribe=True,
|
||||
kw.pop('attachment_ids', None)
|
||||
kw.pop('hash', None)
|
||||
kw.pop('pid', None)
|
||||
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
|
||||
message_type=kw.pop('message_type', "comment"),
|
||||
subtype=kw.pop('subtype', "mt_comment"),
|
||||
author_id=author_id,
|
||||
**kw)
|
||||
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(
|
||||
body=message,
|
||||
message_type=kw.pop('message_type', "comment"),
|
||||
subtype=kw.pop('subtype', "mt_comment"),
|
||||
author_id=author_id,
|
||||
**kw
|
||||
)
|
||||
|
||||
|
||||
class PortalChatter(http.Controller):
|
||||
|
||||
@@ -72,10 +72,10 @@ class PortalMixin(models.AbstractModel):
|
||||
return '%s?%s' % ('/mail/view' if redirect else self.access_url, url_encode(params))
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
access_token = self._portal_ensure_token()
|
||||
customer = self['partner_id']
|
||||
|
||||
groups = super(PortalMixin, self)._notify_get_groups()
|
||||
if access_token and customer:
|
||||
additional_params = {
|
||||
'access_token': self.access_token,
|
||||
@@ -88,13 +88,12 @@ class PortalMixin(models.AbstractModel):
|
||||
'has_button_access': False,
|
||||
'button_access': {
|
||||
'url': access_link,
|
||||
'title': _('View %s') % self.env['ir.model']._get(message.model).display_name,
|
||||
},
|
||||
})
|
||||
]
|
||||
else:
|
||||
new_group = []
|
||||
return super(PortalMixin, self)._notify_get_groups(message, new_group + groups)
|
||||
return new_group + groups
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self, access_uid=None):
|
||||
|
||||
@@ -58,7 +58,7 @@ class PortalShare(models.TransientModel):
|
||||
'share_link': share_link},
|
||||
subject=_("You are invited to access %s" % active_record.display_name),
|
||||
subtype_id=note.id,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
partner_ids=[(6, 0, partner.ids)])
|
||||
# when partner not user send individual mail with signup token
|
||||
for partner in self.partner_ids - partner_ids:
|
||||
@@ -70,6 +70,6 @@ class PortalShare(models.TransientModel):
|
||||
'share_link': share_link},
|
||||
subject=_("You are invited to access %s" % active_record.display_name),
|
||||
subtype_id=note.id,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
partner_ids=[(6, 0, partner.ids)])
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -792,7 +792,7 @@ class Task(models.Model):
|
||||
res['stage_id'] = (test_task.stage_id.mail_template_id, {
|
||||
'auto_delete_message': True,
|
||||
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
|
||||
'notif_layout': 'mail.mail_notification_light'
|
||||
'email_layout_xmlid': 'mail.mail_notification_light'
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -812,12 +812,12 @@ class Task(models.Model):
|
||||
return super(Task, self)._track_subtype(init_values)
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" 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_groups(message, groups)
|
||||
groups = super(Task, self)._notify_get_groups()
|
||||
|
||||
self.ensure_one()
|
||||
|
||||
@@ -836,9 +836,8 @@ class Task(models.Model):
|
||||
groups = [new_group] + groups
|
||||
|
||||
for group_name, group_method, group_data in groups:
|
||||
if group_name == 'customer':
|
||||
continue
|
||||
group_data['has_button_access'] = True
|
||||
if group_name != 'customer':
|
||||
group_data['has_button_access'] = True
|
||||
|
||||
return groups
|
||||
|
||||
@@ -907,22 +906,17 @@ class Task(models.Model):
|
||||
return recipients
|
||||
|
||||
@api.multi
|
||||
def _notify_specific_email_values(self, message):
|
||||
res = super(Task, self)._notify_specific_email_values(message)
|
||||
try:
|
||||
headers = safe_eval(res.get('headers', dict()))
|
||||
except Exception:
|
||||
headers = {}
|
||||
def _notify_email_header_dict(self):
|
||||
headers = super(Task, self)._notify_email_header_dict()
|
||||
if self.project_id:
|
||||
current_objects = [h for h in headers.get('X-Odoo-Objects', '').split(',') if h]
|
||||
current_objects.insert(0, 'project.project-%s, ' % self.project_id.id)
|
||||
headers['X-Odoo-Objects'] = ','.join(current_objects)
|
||||
if self.tag_ids:
|
||||
headers['X-Odoo-Tags'] = ','.join(self.tag_ids.mapped('name'))
|
||||
res['headers'] = repr(headers)
|
||||
return res
|
||||
return headers
|
||||
|
||||
def _message_post_after_hook(self, message, *args, **kwargs):
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
@@ -933,7 +927,7 @@ class Task(models.Model):
|
||||
('partner_id', '=', False),
|
||||
('email_from', '=', new_partner.email),
|
||||
('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
|
||||
return super(Task, self)._message_post_after_hook(message, *args, **kwargs)
|
||||
return super(Task, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
def action_assign_to_me(self):
|
||||
self.write({'user_id': self.env.user.id})
|
||||
|
||||
@@ -155,12 +155,12 @@ class RatingMixin(models.AbstractModel):
|
||||
if subtype_id is False:
|
||||
subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
|
||||
if force_send:
|
||||
self = self.with_context(mail_notify_force_send=True)
|
||||
self = self.with_context(mail_notify_force_send=True) # default value is True, should be set to false if not?
|
||||
for record in self:
|
||||
record.message_post_with_template(
|
||||
template.id,
|
||||
composition_mode=composition_mode,
|
||||
notif_layout=notif_layout if notif_layout is not None else 'mail.mail_notification_light',
|
||||
email_layout_xmlid=notif_layout if notif_layout is not None else 'mail.mail_notification_light',
|
||||
subtype_id=subtype_id
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class PaymentTransaction(models.Model):
|
||||
for trans in self.filtered(lambda t: t.sale_order_ids):
|
||||
trans = trans.with_context(ctx_company)
|
||||
for invoice in trans.invoice_ids:
|
||||
invoice.message_post_with_template(int(default_template), notif_layout="mail.mail_notification_paynow")
|
||||
invoice.message_post_with_template(int(default_template), email_layout_xmlid="mail.mail_notification_paynow")
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -667,7 +667,7 @@ class SaleOrder(models.Model):
|
||||
template_id = self._find_mail_template(force_confirmation_template=True)
|
||||
if template_id:
|
||||
for order in self:
|
||||
order.with_context(force_send=True).message_post_with_template(template_id, composition_mode='comment', notif_layout="mail.mail_notification_paynow")
|
||||
order.with_context(force_send=True).message_post_with_template(template_id, composition_mode='comment', email_layout_xmlid="mail.mail_notification_paynow")
|
||||
|
||||
@api.multi
|
||||
def action_done(self):
|
||||
@@ -782,18 +782,17 @@ class SaleOrder(models.Model):
|
||||
return (self.state == 'sent' or (self.state == 'draft' and include_draft)) and not self.is_expired and self.require_payment and transaction.state != 'done' and self.amount_total
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" 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(SaleOrder, self)._notify_get_groups(message, groups)
|
||||
groups = super(SaleOrder, self)._notify_get_groups()
|
||||
|
||||
self.ensure_one()
|
||||
if self.state not in ('draft', 'cancel'):
|
||||
for group_name, group_method, group_data in groups:
|
||||
if group_name in ('customer', 'portal'):
|
||||
continue
|
||||
group_data['has_button_access'] = True
|
||||
if group_name not in ('customer', 'portal'):
|
||||
group_data['has_button_access'] = True
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ class SaleOrder(models.Model):
|
||||
self.message_post_with_template(
|
||||
template.id, composition_mode='comment',
|
||||
model='sale.coupon', res_id=coupon.id,
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
|
||||
def _get_applicable_programs(self):
|
||||
|
||||
@@ -17,24 +17,17 @@ class TestChatterTweaks(BaseFunctionalTest, TestRecipients):
|
||||
def test_post_no_subscribe_recipients(self):
|
||||
original = self.test_record.message_follower_ids
|
||||
self.test_record.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True}).message_post(
|
||||
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
|
||||
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[self.partner_1.id, self.partner_2.id])
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('partner_id'), original.mapped('partner_id'))
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
|
||||
|
||||
def test_post_subscribe_recipients(self):
|
||||
original = self.test_record.message_follower_ids
|
||||
self.test_record.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True, 'mail_post_autofollow': True}).message_post(
|
||||
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
|
||||
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[self.partner_1.id, self.partner_2.id])
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('partner_id'), original.mapped('partner_id') | self.partner_1 | self.partner_2)
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
|
||||
|
||||
def test_post_subscribe_recipients_partial(self):
|
||||
original = self.test_record.message_follower_ids
|
||||
self.test_record.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True, 'mail_post_autofollow': True, 'mail_post_autofollow_partner_ids': [self.partner_2.id]}).message_post(
|
||||
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('partner_id'), original.mapped('partner_id') | self.partner_2)
|
||||
self.assertEqual(self.test_record.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
|
||||
|
||||
def test_chatter_mail_create_nolog(self):
|
||||
""" Test disable of automatic chatter message at create """
|
||||
rec = self.env['mail.test.simple'].sudo(self.user_employee).with_context({'mail_create_nolog': True}).create({'name': 'Test'})
|
||||
|
||||
@@ -595,45 +595,3 @@ class TestMailgateway(BaseFunctionalTest, MockEmails):
|
||||
self.assertEqual(len(record), 1)
|
||||
self.assertEqual(record.name, 'Spammy')
|
||||
self.assertEqual(record._name, 'mail.test.gateway')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_private_discussion(self):
|
||||
""" Testing private discussion between partners. """
|
||||
self.partner_2 = self.env['res.partner'].with_context(BaseFunctionalTest._test_context).create({
|
||||
'name': 'Admin',
|
||||
'email': 'admin@agrolait.com',
|
||||
})
|
||||
msg1_pids = [self.partner_1.id, self.partner_2.id]
|
||||
|
||||
# Do: Raoul writes to Bert and Administrator, with a model specific by parameter that should not be taken into account
|
||||
msg1 = self.env['mail.thread'].sudo(self.user_employee).message_post(partner_ids=msg1_pids, subtype='mail.mt_comment', model='mail.test')
|
||||
|
||||
# Test: message recipients
|
||||
msg = self.env['mail.message'].browse(msg1.id)
|
||||
self.assertEqual(msg.partner_ids, self.partner_2 | self.partner_1,
|
||||
'message_post: private discussion: incorrect recipients')
|
||||
self.assertEqual(msg.model, False,
|
||||
'message_post: private discussion: parameter model not correctly ignored when having no res_id')
|
||||
# Test: message-id
|
||||
self.assertIn('openerp-private', msg.message_id.split('@')[0], 'message_post: private discussion: message-id should contain the private keyword')
|
||||
|
||||
# Do: Bert replies through mailgateway (is a customer)
|
||||
self.format_and_process(MAIL_TEMPLATE, 'valid.lelitre@agrolait.com', 'not_important@mydomain.com', extra='In-Reply-To: %s' % msg.message_id)
|
||||
|
||||
# Test: last mail_message created
|
||||
msg2 = self.env['mail.message'].search([], limit=1)
|
||||
# Test: message recipients
|
||||
self.assertEqual(msg2.author_id, self.partner_1,
|
||||
'message_post: private discussion: wrong author through mailgateway based on email')
|
||||
self.assertEqual(msg2.partner_ids, self.user_employee.partner_id | self.partner_2,
|
||||
'message_post: private discussion: incorrect recipients when replying')
|
||||
|
||||
# Do: Bert replies through chatter (is a customer)
|
||||
msg3 = self.env['mail.thread'].message_post(author_id=self.partner_1.id, parent_id=msg1.id, subtype='mail.mt_comment')
|
||||
|
||||
# Test: message recipients
|
||||
msg = self.env['mail.message'].browse(msg3.id)
|
||||
self.assertEqual(msg.partner_ids, self.user_employee.partner_id | self.partner_2,
|
||||
'message_post: private discussion: incorrect recipients when replying')
|
||||
self.assertEqual(msg.needaction_partner_ids, self.user_employee.partner_id | self.partner_2,
|
||||
'message_post: private discussion: incorrect notified recipients when replying')
|
||||
|
||||
@@ -205,17 +205,20 @@ class TestMessagePost(BaseFunctionalTest, MockEmails, TestRecipients):
|
||||
|
||||
def test_post_notify(self):
|
||||
self.user_employee.write({'notification_type': 'inbox'})
|
||||
new_notification = self.env['mail.thread'].message_notify(
|
||||
new_notification = self.test_record.message_notify(
|
||||
subject='This should be a subject',
|
||||
body='<p>You have received a notification</p>',
|
||||
partner_ids=[(4, self.partner_1.id), (4, self.user_employee.partner_id.id)],
|
||||
partner_ids=[self.partner_1.id, self.user_employee.partner_id.id],
|
||||
)
|
||||
|
||||
self.assertEqual(new_notification.subtype_id, self.env.ref('mail.mt_note'))
|
||||
self.assertEqual(new_notification.message_type, 'user_notification')
|
||||
self.assertEqual(new_notification.body, '<p>You have received a notification</p>')
|
||||
self.assertEqual(new_notification.author_id, self.env.user.partner_id)
|
||||
self.assertEqual(new_notification.email_from, formataddr((self.env.user.name, self.env.user.email)))
|
||||
self.assertEqual(new_notification.needaction_partner_ids, self.partner_1 | self.user_employee.partner_id)
|
||||
self.assertNotIn(new_notification, self.test_record.message_ids)
|
||||
# todo xdo add test message_notify on thread with followers and stuff
|
||||
|
||||
|
||||
class TestComposer(BaseFunctionalTest, MockEmails, TestRecipients):
|
||||
|
||||
@@ -163,8 +163,8 @@ class TestTracking(common.BaseFunctionalTest, common.MockEmails):
|
||||
self.record._fields['email_from'].groups = 'base.group_erp_manager' # patch the group attribute
|
||||
self.record.sudo().write({'email_from': 'X'})
|
||||
|
||||
msg_emp = self.partner_employee.sudo(self.user_employee)._notify_prepare_template_context(self.record.message_ids, self.record)
|
||||
msg_admin = self.partner_admin.sudo(self.user_admin)._notify_prepare_template_context(self.record.message_ids, self.record)
|
||||
msg_emp = self.record.sudo(self.user_employee)._notify_prepare_template_context(self.record.message_ids, {})
|
||||
msg_admin = self.record.sudo(self.user_admin)._notify_prepare_template_context(self.record.message_ids, {})
|
||||
self.assertFalse(msg_emp.get('tracking_values'), "should not have protected tracking values")
|
||||
self.assertTrue(msg_admin.get('tracking_values'), "should have protected tracking values")
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import base64
|
||||
|
||||
from email.utils import formataddr
|
||||
|
||||
@@ -82,7 +83,7 @@ class TestMailPerformance(TransactionCase):
|
||||
'partner_id': self.env.ref('base.res_partner_12').id,
|
||||
})
|
||||
|
||||
with self.assertQueryCount(__system__=5, demo=5): # test_mail only: 5 - 5
|
||||
with self.assertQueryCount(__system__=4, demo=4): # test_mail only: 4 - 4
|
||||
record.track = 'X'
|
||||
|
||||
@users('__system__', 'demo')
|
||||
@@ -98,13 +99,13 @@ class TestMailPerformance(TransactionCase):
|
||||
@warmup
|
||||
def test_create_mail_with_tracking(self):
|
||||
""" Create records inheriting from 'mail.thread' (with field tracking). """
|
||||
with self.assertQueryCount(__system__=10, demo=10): # test_mail only: 10 - 10
|
||||
with self.assertQueryCount(__system__=9, demo=9): # test_mail only: 9 - 9
|
||||
self.env['test_performance.mail'].create({'name': 'X'})
|
||||
|
||||
@users('__system__', 'emp')
|
||||
@warmup
|
||||
def test_create_mail_simple(self):
|
||||
with self.assertQueryCount(__system__=8, emp=8): # test_mail only: 8 - 8
|
||||
with self.assertQueryCount(__system__=7, emp=7): # test_mail only: 7 - 7
|
||||
self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
@users('__system__', 'emp')
|
||||
@@ -160,7 +161,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_adv_activity(self):
|
||||
model = self.env['mail.test.activity']
|
||||
|
||||
with self.assertQueryCount(__system__=9, emp=8): # test_mail only: 9 - 8
|
||||
with self.assertQueryCount(__system__=8, emp=7): # test_mail only: 8 - 7
|
||||
model.create({'name': 'Test'})
|
||||
|
||||
@users('__system__', 'emp')
|
||||
@@ -172,7 +173,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
'default_res_model': 'mail.test.activity',
|
||||
})
|
||||
|
||||
with self.assertQueryCount(__system__=10, emp=17): # com runbot: 10 - 16 // test_mail only: 10 - 15
|
||||
with self.assertQueryCount(__system__=10, emp=13): # com runbot: 10 - 13 // test_mail only: 10 - 13
|
||||
activity = MailActivity.create({
|
||||
'summary': 'Test Activity',
|
||||
'res_id': record.id,
|
||||
@@ -182,7 +183,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
#voip module read activity_type during create leading to one less query in enterprise on action_feedback
|
||||
category = activity.activity_type_id.category
|
||||
|
||||
with self.assertQueryCount(__system__=24, emp=41): # com runbot: 24 - 41 // test_mail only: 24 - 39
|
||||
with self.assertQueryCount(__system__=24, emp=31): # com runbot: 24 - 31 // test_mail only: 24 - 31
|
||||
activity.action_feedback(feedback='Zizisse Done !')
|
||||
|
||||
@users('__system__', 'emp')
|
||||
@@ -191,7 +192,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_adv_activity_mixin(self):
|
||||
record = self.env['mail.test.activity'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=10, emp=17): # com runbot: 10 - 16 // test_mail only: 10 - 15
|
||||
with self.assertQueryCount(__system__=10, emp=14): # com runbot: 10 - 14 // test_mail only: 10 - 14
|
||||
activity = record.action_start('Test Start')
|
||||
#read activity_type to normalize cache between enterprise and community
|
||||
#voip module read activity_type during create leading to one less query in enterprise on action_close
|
||||
@@ -199,7 +200,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
|
||||
record.write({'name': 'Dupe write'})
|
||||
|
||||
with self.assertQueryCount(__system__=26, emp=42): # com runbot: 26 - 42 // test_mail only: 26 - 40
|
||||
with self.assertQueryCount(__system__=26, emp=31): # com runbot: 26 - 31 // test_mail only: 26 - 31
|
||||
record.action_close('Dupe feedback')
|
||||
|
||||
self.assertEqual(record.activity_ids, self.env['mail.activity'])
|
||||
@@ -210,7 +211,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_assignation_email(self):
|
||||
self.user_test.write({'notification_type': 'email'})
|
||||
record = self.env['mail.test.track'].create({'name': 'Test'})
|
||||
with self.assertQueryCount(__system__=61, emp=76): # com runbot: 61 - 76 // test_mail only: 61 - 71
|
||||
with self.assertQueryCount(__system__=56, emp=59): # com runbot: 56 - 59 // test_mail only: 56 - 59
|
||||
record.write({
|
||||
'user_id': self.user_test.id,
|
||||
})
|
||||
@@ -219,7 +220,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
@warmup
|
||||
def test_message_assignation_inbox(self):
|
||||
record = self.env['mail.test.track'].create({'name': 'Test'})
|
||||
with self.assertQueryCount(__system__=37, emp=44): # test_mail only: 37 - 44
|
||||
with self.assertQueryCount(__system__=34, emp=40): # test_mail only: 34 - 40
|
||||
record.write({
|
||||
'user_id': self.user_test.id,
|
||||
})
|
||||
@@ -239,7 +240,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_log_with_post(self):
|
||||
record = self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=7, emp=13): # test_mail only: 7 - 13
|
||||
with self.assertQueryCount(__system__=6, emp=7): # test_mail only: 6 - 7
|
||||
record.message_post(
|
||||
body='<p>Test message_post as log</p>',
|
||||
subtype='mail.mt_note',
|
||||
@@ -250,7 +251,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_post_no_notification(self):
|
||||
record = self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=7, emp=13): # test_mail only: 7 - 13
|
||||
with self.assertQueryCount(__system__=6, emp=7): # test_mail only: 6 - 7
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances basic</p>',
|
||||
partner_ids=[],
|
||||
@@ -263,7 +264,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_post_one_email_notification(self):
|
||||
record = self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=52, emp=72): # com runbot: 49 - 69 // test_mail only: 52 - 69
|
||||
with self.assertQueryCount(__system__=47, emp=51): # com runbot: 47 - 51 // test_mail only: 47 - 51
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances with an email ping</p>',
|
||||
partner_ids=self.customer.ids,
|
||||
@@ -275,7 +276,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_post_one_inbox_notification(self):
|
||||
record = self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=32, emp=44): # com runbot 31 - 43 // test_mail only: 32 - 44
|
||||
with self.assertQueryCount(__system__=30, emp=36): # com runbot 30 - 36 // test_mail only: 30 - 36
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances with an inbox ping</p>',
|
||||
partner_ids=self.user_test.partner_id.ids,
|
||||
@@ -385,8 +386,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
'recipient_ids': [(4, pid) for pid in self.partners.ids],
|
||||
})
|
||||
mail_ids = mail.ids
|
||||
|
||||
with self.assertQueryCount(__system__=14, emp=21): # test_mail only: 14 - 21
|
||||
with self.assertQueryCount(__system__=16, emp=22): # test_mail only: 16 - 22
|
||||
self.env['mail.mail'].browse(mail_ids).send()
|
||||
|
||||
self.assertEqual(mail.body_html, '<p>Test</p>')
|
||||
@@ -399,7 +399,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
self.umbrella.message_subscribe(self.user_portal.partner_id.ids)
|
||||
record = self.umbrella.sudo(self.env.user)
|
||||
|
||||
with self.assertQueryCount(__system__=85, emp=108): # com runbot: 82 - 105 // test_mail only: 85 - 105
|
||||
with self.assertQueryCount(__system__=78, emp=82): # com runbot: 78 - 82 // test_mail only: 78 - 82
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances</p>',
|
||||
message_type='comment',
|
||||
@@ -416,7 +416,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
record = self.umbrella.sudo(self.env.user)
|
||||
template_id = self.env.ref('test_mail.mail_test_tpl').id
|
||||
|
||||
with self.assertQueryCount(__system__=102, emp=126): # com runbot: 99 - 123 // test_mail only: 102 - 123
|
||||
with self.assertQueryCount(__system__=95, emp=101): # com runbot: 95 - 101 // test_mail only: 95 - 101
|
||||
record.message_post_with_template(template_id, message_type='comment', composition_mode='comment')
|
||||
|
||||
self.assertEqual(record.message_ids[0].body, '<p>Adding stuff on %s</p>' % record.name)
|
||||
@@ -485,7 +485,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
'user_id': self.env.uid,
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id)
|
||||
with self.assertQueryCount(__system__=60, emp=75): # com runbot: 60 - 75 // test_mail only: 60 - 72
|
||||
with self.assertQueryCount(__system__=55, emp=58): # com runbot: 55 - 58 // test_mail only: 55 - 58
|
||||
rec.write({'user_id': self.user_portal.id})
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id)
|
||||
# write tracking message
|
||||
@@ -505,7 +505,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
customer_id = self.customer.id
|
||||
user_id = self.user_portal.id
|
||||
|
||||
with self.assertQueryCount(__system__=155, emp=172): # com runbot: 155 - 172 // test_mail only: 154 - 168
|
||||
with self.assertQueryCount(__system__=147, emp=155): # com runbot: 147 - 155 // test_mail only: 147 - 155
|
||||
rec = self.env['mail.test.full'].create({
|
||||
'name': 'Test',
|
||||
'umbrella_id': umbrella_id,
|
||||
@@ -532,7 +532,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id)
|
||||
self.assertEqual(len(rec.message_ids), 1)
|
||||
with self.assertQueryCount(__system__=99, emp=122): # com runbot: 99 - 122 // test_mail only: 99 - 119
|
||||
with self.assertQueryCount(__system__=96, emp=101): # com runbot: 96 - 101 // test_mail only: 96 - 101
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'umbrella_id': self.umbrella.id,
|
||||
@@ -568,7 +568,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id)
|
||||
|
||||
with self.assertQueryCount(__system__=105, emp=127): # test_mail only: 104 - 123
|
||||
with self.assertQueryCount(__system__=101, emp=106): # test_mail only: 101 - 106
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'umbrella_id': umbrella_id,
|
||||
@@ -600,7 +600,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id)
|
||||
|
||||
with self.assertQueryCount(__system__=52, emp=66): # test_mail only: 51 - 66
|
||||
with self.assertQueryCount(__system__=50, emp=62): # test_mail only: 50 - 62
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'customer_id': customer_id,
|
||||
@@ -617,3 +617,159 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
self.assertEqual(rec.message_ids[2].subtype_id, self.env.ref('test_mail.st_mail_test_full_umbrella_upd'))
|
||||
self.assertEqual(rec.message_ids[2].needaction_partner_ids, self.partners | self.user_portal.partner_id)
|
||||
self.assertEqual(len(rec.message_ids), 3)
|
||||
|
||||
|
||||
@tagged('mail_performance')
|
||||
class TestMailPerformancePost(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMailPerformancePost, self).setUp()
|
||||
|
||||
self._quick_create_ctx = {
|
||||
'no_reset_password': True,
|
||||
'mail_create_nolog': True,
|
||||
'mail_create_nosubscribe': True,
|
||||
'mail_notrack': True,
|
||||
}
|
||||
self.user_employee = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'Ernest Employee',
|
||||
'login': 'emp',
|
||||
'email': 'e.e@example.com',
|
||||
'signature': '--\nErnest',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
# record
|
||||
self.customer = self.env['res.partner'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'customer',
|
||||
'email': 'customer@example.com',
|
||||
})
|
||||
self.record = self.env['mail.test'].with_context(mail_create_nosubscribe=True).create({
|
||||
'name': 'Test record',
|
||||
'customer_id': self.customer.id,
|
||||
'alias_name': 'test-alias',
|
||||
})
|
||||
# followers
|
||||
self.user_follower_email = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_follower_email',
|
||||
'login': 'user_follower_email',
|
||||
'email': 'user_follower_email@example.com',
|
||||
'notification_type': 'email',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.user_follower_inbox = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_follower_inbox',
|
||||
'login': 'user_follower_inbox',
|
||||
'email': 'user_follower_inbox@example.com',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.partner_follower = self.env['res.partner'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'partner_follower',
|
||||
'email': 'partner_follower@example.com',
|
||||
})
|
||||
self.record.message_subscribe([
|
||||
self.partner_follower.id,
|
||||
self.user_follower_inbox.partner_id.id,
|
||||
self.user_follower_email.partner_id.id
|
||||
])
|
||||
|
||||
# partner_ids
|
||||
self.user_inbox = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_inbox',
|
||||
'login': 'user_inbox',
|
||||
'email': 'user_inbox@example.com',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.user_email = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_email',
|
||||
'login': 'user_email',
|
||||
'email': 'user_email@example.com',
|
||||
'notification_type': 'email',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.partner = self.env['res.partner'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'partner',
|
||||
'email': 'partner@example.com',
|
||||
})
|
||||
# channels user/partner
|
||||
self.partner_channel_inbox = self.env['res.partner'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'partner_channel_inbox',
|
||||
'email': 'partner_channel_inbox@example.com',
|
||||
})
|
||||
self.partner_channel_email = self.env['res.partner'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'partner_channel_email',
|
||||
'email': 'partner_channel_email@example.com',
|
||||
})
|
||||
self.user_channel_inbox = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_channel_inbox',
|
||||
'login': 'user_channel_inbox',
|
||||
'email': 'user_channel_inbox@example.com',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
self.user_channel_email = self.env['res.users'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'user_channel_email',
|
||||
'login': 'user_channel_email',
|
||||
'email': 'user_channel_email@example.com',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])],
|
||||
})
|
||||
# channels
|
||||
self.channel_inbox = self.env['mail.channel'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'channel_inbox',
|
||||
'channel_partner_ids': [(4, self.partner_channel_inbox.id), (4, self.user_channel_inbox.partner_id.id)]
|
||||
})
|
||||
self.channel_email = self.env['mail.channel'].with_context(self._quick_create_ctx).create({
|
||||
'name': 'channel_email',
|
||||
'email_send': True,
|
||||
'channel_partner_ids': [(4, self.partner_channel_email.id), (4, self.user_channel_email.partner_id.id)]
|
||||
})
|
||||
self.vals = [{
|
||||
'datas': base64.b64encode(bytes("attachement content %s" % i, 'utf-8')),
|
||||
'name': 'attach %s' % i,
|
||||
'datas_fname': 'fileText_test%s.txt' % i,
|
||||
'mimetype': 'text/plain',
|
||||
'res_model': 'mail.compose.message',
|
||||
'res_id': 0,
|
||||
} for i in range(3)]
|
||||
|
||||
self.patch(self.env.registry, 'ready', True)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
@users('emp')
|
||||
@warmup
|
||||
def test_complete_message_post(self):
|
||||
# aims to cover as much features of message_post as possible
|
||||
partner_ids = [self.user_inbox.partner_id.id, self.user_email.partner_id.id, self.partner.id]
|
||||
channel_ids = [self.channel_inbox.id, self.channel_email.id]
|
||||
record = self.record.sudo(self.env.user)
|
||||
attachements = [ # not linear on number of attachements
|
||||
('attach tuple 1', "attachement tupple content 1"),
|
||||
('attach tuple 2', "attachement tupple content 2", {'cid': 'cid1'}),
|
||||
('attach tuple 3', "attachement tupple content 3", {'cid': 'cid2'}),
|
||||
]
|
||||
self.attachements = self.env['ir.attachment'].sudo(self.env.user).create(self.vals) #-> 163-> 165 query
|
||||
attachement_ids = self.attachements.ids
|
||||
with self.assertQueryCount(emp=163): # com runbot 156 // test_mail only: 133
|
||||
self.cr.sql_log = self.warm and self.cr.sql_log_count
|
||||
record.with_context({}).message_post(
|
||||
body='<p>Test body <img src="cid:cid1"> <img src="cid:cid2"></p>',
|
||||
subject='Test Subject',
|
||||
message_type='notification',
|
||||
subtype=None,
|
||||
partner_ids=partner_ids,
|
||||
channel_ids=channel_ids,
|
||||
parent_id=False,
|
||||
attachments=attachements,
|
||||
attachment_ids=attachement_ids,
|
||||
add_sign=True,
|
||||
model_description=False,
|
||||
mail_auto_delete=True
|
||||
)
|
||||
self.cr.sql_log = False
|
||||
self.assertTrue(record.message_ids[0].body.startswith('<p>Test body <img src="/web/image/'))
|
||||
self.assertEqual(self.attachements.mapped('res_model'), [record._name for i in range(3)])
|
||||
self.assertEqual(self.attachements.mapped('res_id'), [record.id for i in range(3)])
|
||||
# self.assertEqual(record.message_ids[0].needaction_partner_ids, [])
|
||||
|
||||
@@ -248,9 +248,9 @@ class BlogPost(models.Model):
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Add access button to everyone if the document is published. """
|
||||
groups = super(BlogPost, self)._notify_get_groups(message, groups)
|
||||
groups = super(BlogPost, self)._notify_get_groups()
|
||||
|
||||
if self.website_published:
|
||||
for group_name, group_method, group_data in groups:
|
||||
@@ -259,14 +259,14 @@ class BlogPost(models.Model):
|
||||
return groups
|
||||
|
||||
@api.multi
|
||||
def _notify_customize_recipients(self, message, msg_vals, recipients_vals):
|
||||
def _notify_customize_recipients(self, message, msg_vals):
|
||||
""" Override to avoid keeping all notified recipients of a comment.
|
||||
We avoid tracking needaction on post comments. Only emails should be
|
||||
sufficient. """
|
||||
msg_type = msg_vals.get('message_type') or message.message_type
|
||||
if msg_type == 'comment':
|
||||
return {'needaction_partner_ids': []}
|
||||
return {}
|
||||
return super(BlogPost, self)._notify_customize_recipients(message, msg_vals)
|
||||
|
||||
def _default_website_meta(self):
|
||||
res = super(BlogPost, self)._default_website_meta()
|
||||
|
||||
@@ -183,7 +183,7 @@ class CrmLead(models.Model):
|
||||
if comment:
|
||||
message += '<p>%s</p>' % comment
|
||||
for lead in self:
|
||||
lead.message_post(body=message, subtype="mail.mt_note")
|
||||
lead.message_post(body=message)
|
||||
lead.sudo().convert_opportunity(lead.partner_id.id) # sudo required to convert partner data
|
||||
|
||||
@api.multi
|
||||
@@ -197,7 +197,7 @@ class CrmLead(models.Model):
|
||||
self.message_unsubscribe(partner_ids=partner_ids.ids)
|
||||
if comment:
|
||||
message += '<p>%s</p>' % comment
|
||||
self.message_post(body=message, subtype="mail.mt_note")
|
||||
self.message_post(body=message)
|
||||
values = {
|
||||
'partner_assigned_id': False,
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ class Track(models.Model):
|
||||
'composition_mode': 'comment',
|
||||
'auto_delete_message': True,
|
||||
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
|
||||
'notif_layout': 'mail.mail_notification_light'
|
||||
'email_layout_xmlid': 'mail.mail_notification_light'
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -163,7 +163,7 @@ class Track(models.Model):
|
||||
track._message_add_suggested_recipient(recipients, email=track.partner_email, reason=_('Speaker Email'))
|
||||
return recipients
|
||||
|
||||
def _message_post_after_hook(self, message, *args, **kwargs):
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.partner_email and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
@@ -175,7 +175,7 @@ class Track(models.Model):
|
||||
('partner_email', '=', new_partner.email),
|
||||
('stage_id.is_cancel', '=', False),
|
||||
]).write({'partner_id': new_partner.id})
|
||||
return super(Track, self)._message_post_after_hook(message, *args, **kwargs)
|
||||
return super(Track, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
@api.multi
|
||||
def open_track_speakers_list(self):
|
||||
|
||||
@@ -422,12 +422,13 @@ class Post(models.Model):
|
||||
return post
|
||||
|
||||
@api.model
|
||||
def check_mail_message_access(self, res_ids, operation, model_name=None):
|
||||
def get_mail_message_access(self, res_ids, operation, model_name=None):
|
||||
# XDO FIXME: to be correctly fixed with new get_mail_message_access and filter access rule
|
||||
if operation in ('write', 'unlink') and (not model_name or model_name == 'forum.post'):
|
||||
# Make sure only author or moderator can edit/delete messages
|
||||
if any(not post.can_edit for post in self.browse(res_ids)):
|
||||
raise KarmaError('Not enough karma to edit a post.')
|
||||
return super(Post, self).check_mail_message_access(res_ids, operation, model_name=model_name)
|
||||
return super(Post, self).get_mail_message_access(res_ids, operation, model_name=model_name)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
@@ -771,9 +772,9 @@ class Post(models.Model):
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Add access button to everyone if the document is active. """
|
||||
groups = super(Post, self)._notify_get_groups(message, groups)
|
||||
groups = super(Post, self)._notify_get_groups()
|
||||
|
||||
if self.state == 'active':
|
||||
for group_name, group_method, group_data in groups:
|
||||
@@ -784,7 +785,6 @@ class Post(models.Model):
|
||||
@api.multi
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, message_type='notification', **kwargs):
|
||||
question_followers = self.env['res.partner']
|
||||
if self.ids and message_type == 'comment': # user comments have a restriction on karma
|
||||
# add followers of comments on the parent post
|
||||
if self.parent_id:
|
||||
@@ -795,7 +795,7 @@ class Post(models.Model):
|
||||
('res_id', '=', self.parent_id.id),
|
||||
('partner_id', '!=', False),
|
||||
]).filtered(lambda fol: comment_subtype in fol.subtype_ids).mapped('partner_id')
|
||||
partner_ids += [(4, partner.id) for partner in question_followers]
|
||||
partner_ids += question_followers.ids
|
||||
kwargs['partner_ids'] = partner_ids
|
||||
|
||||
self.ensure_one()
|
||||
@@ -806,14 +806,14 @@ class Post(models.Model):
|
||||
return super(Post, self).message_post(message_type=message_type, **kwargs)
|
||||
|
||||
@api.multi
|
||||
def _notify_customize_recipients(self, message, msg_vals, recipients_vals):
|
||||
def _notify_customize_recipients(self, message, msg_vals):
|
||||
""" Override to avoid keeping all notified recipients of a comment.
|
||||
We avoid tracking needaction on post comments. Only emails should be
|
||||
sufficient. """
|
||||
msg_type = msg_vals.get('message_type') or message.message_type
|
||||
if msg_type == 'comment':
|
||||
return {'needaction_partner_ids': [], 'partner_ids': []}
|
||||
return {}
|
||||
return super(Post, self)._notify_customize_recipients(message, msg_vals)
|
||||
|
||||
|
||||
class PostReason(models.Model):
|
||||
|
||||
@@ -14,18 +14,13 @@ class MailGroup(models.Model):
|
||||
_inherit = 'mail.channel'
|
||||
|
||||
@api.multi
|
||||
def _notify_specific_email_values(self, message):
|
||||
res = super(MailGroup, self)._notify_specific_email_values(message)
|
||||
try:
|
||||
headers = safe_eval(res.get('headers', dict()))
|
||||
except Exception:
|
||||
headers = {}
|
||||
def _notify_email_header_dict(self):
|
||||
headers = super(MailGroup, self)._notify_email_header_dict()
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
headers['List-Archive'] = '<%s/groups/%s>' % (base_url, slug(self)),
|
||||
headers['List-Subscribe'] = '<%s/groups>' % (base_url),
|
||||
headers['List-Unsubscribe'] = '<%s/groups?unsubscribe>' % (base_url,),
|
||||
res['headers'] = repr(headers)
|
||||
return res
|
||||
return headers
|
||||
|
||||
@api.multi
|
||||
def _send_confirmation_email(self, partner_ids, unsubscribe=False):
|
||||
|
||||
@@ -337,9 +337,9 @@ class Slide(models.Model):
|
||||
return super(Slide, self).get_access_action(access_uid)
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
def _notify_get_groups(self):
|
||||
""" Add access button to everyone if the document is active. """
|
||||
groups = super(Slide, self)._notify_get_groups(message, groups)
|
||||
groups = super(Slide, self)._notify_get_groups()
|
||||
|
||||
if self.website_published:
|
||||
for group_name, group_method, group_data in groups:
|
||||
@@ -361,7 +361,7 @@ class Slide(models.Model):
|
||||
subject=subject,
|
||||
body=html_body,
|
||||
subtype='website_slides.mt_channel_slide_published',
|
||||
notif_layout='mail.mail_notification_light',
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from collections import defaultdict
|
||||
import uuid
|
||||
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
from odoo.exceptions import AccessError, ValidationError, MissingError
|
||||
from odoo.tools import config, human_size, ustr, html_escape
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
|
||||
@@ -313,6 +313,8 @@ class IrAttachment(models.Model):
|
||||
def _check_serving_attachments(self):
|
||||
# restrict writing on attachments that could be served by the
|
||||
# ir.http's dispatch exception handling
|
||||
# XDO note: this should be done in check(write), constraints for access rights?
|
||||
# XDO note: if read on sudo, read twice, one for constraints, one for _inverse_datas as user
|
||||
if self.env.user._is_admin():
|
||||
return
|
||||
if self.type == 'binary' and self.url:
|
||||
@@ -326,13 +328,15 @@ class IrAttachment(models.Model):
|
||||
In the 'document' module, it is overriden to relax this hard rule, since
|
||||
more complex ones apply there.
|
||||
"""
|
||||
if self.env.user._is_superuser():
|
||||
return True
|
||||
# collect the records to check (by model)
|
||||
model_ids = defaultdict(set) # {model_name: set(ids)}
|
||||
require_employee = False
|
||||
if self:
|
||||
self._cr.execute('SELECT res_model, res_id, create_uid, public, res_field FROM ir_attachment WHERE id IN %s', [tuple(self.ids)])
|
||||
for res_model, res_id, create_uid, public, res_field in self._cr.fetchall():
|
||||
if self.env.user.id != SUPERUSER_ID and not self.env.user._is_system() and res_field:
|
||||
if not self.env.user._is_system() and res_field:
|
||||
raise AccessError(_("Sorry, you are not allowed to access this document."))
|
||||
if public and mode == 'read':
|
||||
continue
|
||||
@@ -488,12 +492,20 @@ class IrAttachment(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
record_tuple_set = set()
|
||||
for values in vals_list:
|
||||
# remove computed field depending of datas
|
||||
for field in ('file_size', 'checksum'):
|
||||
values.pop(field, False)
|
||||
values = self._check_contents(values)
|
||||
self.browse().check('write', values=values)
|
||||
# 'check()' only uses res_model and res_id from values, and make an exists.
|
||||
# We can group the values by model, res_id to make only one query when
|
||||
# creating multiple attachments on a single record.
|
||||
record_tuple = (values.get('res_model'), values.get('res_id'))
|
||||
record_tuple_set.add(record_tuple)
|
||||
for record_tuple in record_tuple_set:
|
||||
(res_model, res_id) = record_tuple
|
||||
self.check('write', values={'res_model':res_model, 'res_id':res_id})
|
||||
return super(IrAttachment, self).create(vals_list)
|
||||
|
||||
@api.multi
|
||||
@@ -504,10 +516,13 @@ class IrAttachment(models.Model):
|
||||
def generate_access_token(self):
|
||||
if self.access_token:
|
||||
return self.access_token
|
||||
access_token = str(uuid.uuid4())
|
||||
access_token = self._generate_access_token()
|
||||
self.write({'access_token': access_token})
|
||||
return access_token
|
||||
|
||||
def _generate_access_token(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@api.model
|
||||
def action_get(self):
|
||||
return self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment')
|
||||
|
||||
Reference in New Issue
Block a user