[MERGE][PRF] mail, various: improve performance of activities
PURPOSE
Improve performance of activities creation and management. Improve overall
code.
SPECIFICATIONS
Main code changes are
* classify activities by model, to browse related records in batch per model
instead of one by one;
* rewrite create and _action_done main methods to be done in batch;
* skip useless computation (use categories before browsing records, avoid
useless ref / direct xml ID check, ...);
* globally batch when possible;
See sub commits for more details.
LINKS
Task-2884301 (Voip synchronization simplification)
Task-2883589 (Activity creation performance)
closes odoo/odoo#93682
Related: odoo/enterprise#28421
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -24,17 +24,15 @@ class MailActivity(models.Model):
|
||||
return action
|
||||
|
||||
def _action_done(self, feedback=False, attachment_ids=False):
|
||||
events = self.mapped('calendar_event_id')
|
||||
messages, activities = super(MailActivity, self)._action_done(feedback=feedback, attachment_ids=attachment_ids)
|
||||
if feedback:
|
||||
for event in events:
|
||||
for event in self.calendar_event_id:
|
||||
description = event.description
|
||||
description = '%s<br />%s' % (
|
||||
description if not tools.is_html_empty(description) else '',
|
||||
_('Feedback: %(feedback)s', feedback=tools.plaintext2html(feedback)) if feedback else '',
|
||||
)
|
||||
event.write({'description': description})
|
||||
return messages, activities
|
||||
return super(MailActivity, self)._action_done(feedback=feedback, attachment_ids=attachment_ids)
|
||||
|
||||
def unlink_w_meeting(self):
|
||||
events = self.mapped('calendar_event_id')
|
||||
|
||||
@@ -31,21 +31,25 @@ class MailActivity(models.Model):
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _default_activity_type_id(self):
|
||||
ActivityType = self.env["mail.activity.type"]
|
||||
activity_type_todo = self.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False)
|
||||
def _default_activity_type(self):
|
||||
default_vals = self.default_get(['res_model_id', 'res_model'])
|
||||
if not default_vals.get('res_model_id'):
|
||||
return ActivityType
|
||||
current_model_id = default_vals['res_model_id']
|
||||
current_model = self.env["ir.model"].sudo().browse(current_model_id)
|
||||
return False
|
||||
|
||||
current_model = self.env["ir.model"].sudo().browse(default_vals['res_model_id']).model
|
||||
return self._default_activity_type_for_model(current_model)
|
||||
|
||||
@api.model
|
||||
def _default_activity_type_for_model(self, model):
|
||||
todo_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mail_activity_data_todo', raise_if_not_found=False)
|
||||
activity_type_todo = self.env['mail.activity.type'].browse(todo_id) if todo_id else self.env['mail.activity.type']
|
||||
if activity_type_todo and activity_type_todo.active and \
|
||||
(activity_type_todo.res_model == current_model.model or not activity_type_todo.res_model):
|
||||
(activity_type_todo.res_model == model or not activity_type_todo.res_model):
|
||||
return activity_type_todo
|
||||
activity_type_model = ActivityType.search([('res_model', '=', current_model.model)], limit=1)
|
||||
activity_type_model = self.env['mail.activity.type'].search([('res_model', '=', model)], limit=1)
|
||||
if activity_type_model:
|
||||
return activity_type_model
|
||||
activity_type_generic = ActivityType.search([('res_model', '=', False)], limit=1)
|
||||
activity_type_generic = self.env['mail.activity.type'].search([('res_model', '=', False)], limit=1)
|
||||
return activity_type_generic
|
||||
|
||||
# owner
|
||||
@@ -63,7 +67,7 @@ class MailActivity(models.Model):
|
||||
activity_type_id = fields.Many2one(
|
||||
'mail.activity.type', string='Activity Type',
|
||||
domain="['|', ('res_model', '=', False), ('res_model', '=', res_model)]", ondelete='restrict',
|
||||
default=_default_activity_type_id)
|
||||
default=_default_activity_type)
|
||||
activity_category = fields.Selection(related='activity_type_id.category', readonly=True)
|
||||
activity_decoration = fields.Selection(related='activity_type_id.decoration_type', readonly=True)
|
||||
icon = fields.Char('Icon', related='activity_type_id.icon', readonly=True)
|
||||
@@ -240,27 +244,32 @@ class MailActivity(models.Model):
|
||||
is to allow assigned user to handle their activities. For that purpose
|
||||
assigned user should be able to at least read the document. We therefore
|
||||
raise an UserError if the assigned user has no access to the document. """
|
||||
for activity in self:
|
||||
model = self.env[activity.res_model].with_user(activity.user_id).with_context(allowed_company_ids=activity.user_id.company_ids.ids)
|
||||
try:
|
||||
model.check_access_rights('read')
|
||||
except exceptions.AccessError:
|
||||
raise exceptions.UserError(
|
||||
_('Assigned user %s has no access to the document and is not able to handle this activity.',
|
||||
activity.user_id.display_name))
|
||||
else:
|
||||
for model, activity_data in self._classify_by_model().items():
|
||||
# group activities / user, in order to batch the check of ACLs
|
||||
per_user = dict()
|
||||
for activity in activity_data['activities'].filtered(lambda act: act.user_id):
|
||||
if activity.user_id not in per_user:
|
||||
per_user[activity.user_id] = activity
|
||||
else:
|
||||
per_user[activity.user_id] += activity
|
||||
for user, activities in per_user.items():
|
||||
RecordModel = self.env[model].with_user(user).with_context(
|
||||
allowed_company_ids=user.company_ids.ids
|
||||
)
|
||||
try:
|
||||
target_user = activity.user_id
|
||||
target_record = self.env[activity.res_model].browse(activity.res_id)
|
||||
if hasattr(target_record, 'company_id') and (
|
||||
target_record.company_id != target_user.company_id and (
|
||||
len(target_user.sudo().company_ids) > 1)):
|
||||
return # in that case we skip the check, assuming it would fail because of the company
|
||||
model.browse(activity.res_id).check_access_rule('read')
|
||||
RecordModel.check_access_rights('read')
|
||||
except exceptions.AccessError:
|
||||
raise exceptions.UserError(
|
||||
_('Assigned user %s has no access to the document and is not able to handle this activity.',
|
||||
activity.user_id.display_name))
|
||||
user.display_name))
|
||||
else:
|
||||
try:
|
||||
target_records = self.env[model].browse(activities.mapped('res_id'))
|
||||
target_records.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
raise exceptions.UserError(
|
||||
_('Assigned user %s has no access to the document and is not able to handle this activity.',
|
||||
user.display_name))
|
||||
|
||||
# ------------------------------------------------------
|
||||
# ORM overrides
|
||||
@@ -269,29 +278,48 @@ class MailActivity(models.Model):
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
activities = super(MailActivity, self).create(vals_list)
|
||||
for activity in activities:
|
||||
need_sudo = False
|
||||
try: # in multicompany, reading the partner might break
|
||||
partner_id = activity.user_id.partner_id.id
|
||||
except exceptions.AccessError:
|
||||
need_sudo = True
|
||||
partner_id = activity.user_id.sudo().partner_id.id
|
||||
|
||||
# send a notification to assigned user; in case of manually done activity also check
|
||||
# target has rights on document otherwise we prevent its creation. Automated activities
|
||||
# are checked since they are integrated into business flows that should not crash.
|
||||
if activity.user_id != self.env.user:
|
||||
if not activity.automated:
|
||||
activity._check_access_assignation()
|
||||
if not self.env.context.get('mail_activity_quick_update', False):
|
||||
if need_sudo:
|
||||
activity.sudo().action_notify()
|
||||
else:
|
||||
activity.action_notify()
|
||||
# find partners related to responsible users, separate readable from unreadable
|
||||
if any(user != self.env.user for user in activities.user_id):
|
||||
user_partners = activities.user_id.partner_id
|
||||
readable_user_partners = user_partners._filter_access_rules_python('read')
|
||||
else:
|
||||
readable_user_partners = self.env.user.partner_id
|
||||
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[partner_id])
|
||||
if activity.date_deadline <= fields.Date.today():
|
||||
self.env['bus.bus']._sendone(activity.user_id.partner_id, 'mail.activity/updated', {'activity_created': True})
|
||||
# when creating activities for other: send a notification to assigned user;
|
||||
# in case of manually done activity also check target has rights on document
|
||||
# otherwise we prevent its creation. Automated activities are checked since
|
||||
# they are integrated into business flows that should not crash.
|
||||
if self.env.context.get('mail_activity_quick_update'):
|
||||
activities_to_notify = self.env['mail.activity']
|
||||
else:
|
||||
activities_to_notify = activities.filtered(lambda act: act.user_id != self.env.user)
|
||||
activities_to_notify.filtered(lambda act: not act.automated)._check_access_assignation()
|
||||
if activities_to_notify:
|
||||
to_sudo = activities_to_notify.filtered(lambda act: act.user_id.partner_id not in readable_user_partners)
|
||||
other = activities_to_notify - to_sudo
|
||||
to_sudo.sudo().action_notify()
|
||||
other.action_notify()
|
||||
|
||||
# subscribe (batch by model and user to speedup)
|
||||
for model, activity_data in activities._classify_by_model().items():
|
||||
per_user = dict()
|
||||
for activity in activity_data['activities'].filtered(lambda act: act.user_id):
|
||||
if activity.user_id not in per_user:
|
||||
per_user[activity.user_id] = [activity.res_id]
|
||||
else:
|
||||
per_user[activity.user_id].append(activity.res_id)
|
||||
for user, res_ids in per_user.items():
|
||||
pids = user.partner_id.ids if user.partner_id in readable_user_partners else user.sudo().partner_id.ids
|
||||
self.env[model].browse(res_ids).message_subscribe(partner_ids=pids)
|
||||
|
||||
# send notifications about activity creation
|
||||
todo_activities = activities.filtered(lambda act: act.date_deadline <= fields.Date.today())
|
||||
if todo_activities:
|
||||
self.env['bus.bus']._sendmany([
|
||||
(activity.user_id.partner_id, 'mail.activity/updated', {'activity_created': True})
|
||||
for activity in todo_activities
|
||||
])
|
||||
return activities
|
||||
|
||||
def read(self, fields=None, load='_classic_read'):
|
||||
@@ -319,18 +347,27 @@ class MailActivity(models.Model):
|
||||
user_changes.action_notify()
|
||||
for activity in user_changes:
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
|
||||
if activity.date_deadline <= fields.Date.today():
|
||||
self.env['bus.bus']._sendone(activity.user_id.partner_id, 'mail.activity/updated', {'activity_created': True})
|
||||
for activity in user_changes:
|
||||
if activity.date_deadline <= fields.Date.today():
|
||||
for partner in pre_responsibles:
|
||||
self.env['bus.bus']._sendone(partner, 'mail.activity/updated', {'activity_deleted': True})
|
||||
|
||||
# send bus notifications
|
||||
todo_activities = user_changes.filtered(lambda act: act.date_deadline <= fields.Date.today())
|
||||
if todo_activities:
|
||||
self.env['bus.bus']._sendmany([
|
||||
[partner, 'mail.activity/updated', {'activity_created': True}]
|
||||
for partner in todo_activities.user_id.partner_id
|
||||
])
|
||||
self.env['bus.bus']._sendmany([
|
||||
[partner, 'mail.activity/updated', {'activity_deleted': True}]
|
||||
for partner in pre_responsibles
|
||||
])
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
for activity in self:
|
||||
if activity.date_deadline <= fields.Date.today():
|
||||
self.env['bus.bus']._sendone(activity.user_id.partner_id, 'mail.activity/updated', {'activity_deleted': True})
|
||||
todo_activities = self.filtered(lambda act: act.date_deadline <= fields.Date.today())
|
||||
if todo_activities:
|
||||
self.env['bus.bus']._sendmany([
|
||||
[partner, 'mail.activity/updated', {'activity_deleted': True}]
|
||||
for partner in todo_activities.user_id.partner_id
|
||||
])
|
||||
return super(MailActivity, self).unlink()
|
||||
|
||||
@api.model
|
||||
@@ -435,7 +472,6 @@ class MailActivity(models.Model):
|
||||
def action_notify(self):
|
||||
if not self:
|
||||
return
|
||||
body_template = self.env.ref('mail.message_activity_assigned')
|
||||
for activity in self:
|
||||
if activity.user_id.lang:
|
||||
# Send the notification in the assigned user's language
|
||||
@@ -526,40 +562,44 @@ class MailActivity(models.Model):
|
||||
activity_id = attachment['res_id']
|
||||
activity_attachments[activity_id].append(attachment['id'])
|
||||
|
||||
for activity in self:
|
||||
# extract value to generate next activities
|
||||
if activity.chaining_type == 'trigger':
|
||||
vals = activity.with_context(activity_previous_deadline=activity.date_deadline)._prepare_next_activity_values()
|
||||
next_activities_values.append(vals)
|
||||
for model, activity_data in self._classify_by_model().items():
|
||||
records = self.env[model].browse(activity_data['record_ids'])
|
||||
for record, activity in zip(records, activity_data['activities']):
|
||||
# extract value to generate next activities
|
||||
if activity.chaining_type == 'trigger':
|
||||
vals = activity.with_context(activity_previous_deadline=activity.date_deadline)._prepare_next_activity_values()
|
||||
next_activities_values.append(vals)
|
||||
|
||||
# post message on activity, before deleting it
|
||||
record = self.env[activity.res_model].browse(activity.res_id)
|
||||
record.message_post_with_view(
|
||||
'mail.message_activity_done',
|
||||
values={
|
||||
'activity': activity,
|
||||
'feedback': feedback,
|
||||
'display_assignee': activity.user_id != self.env.user
|
||||
},
|
||||
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_activities'),
|
||||
mail_activity_type_id=activity.activity_type_id.id,
|
||||
attachment_ids=[Command.link(attachment_id) for attachment_id in attachment_ids] if attachment_ids else [],
|
||||
)
|
||||
# post message on activity, before deleting it
|
||||
_, activity_message = record.message_post_with_view(
|
||||
'mail.message_activity_done',
|
||||
values={
|
||||
'activity': activity,
|
||||
'feedback': feedback,
|
||||
'display_assignee': activity.user_id != self.env.user
|
||||
},
|
||||
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_activities'),
|
||||
mail_activity_type_id=activity.activity_type_id.id,
|
||||
attachment_ids=[Command.link(attachment_id) for attachment_id in attachment_ids] if attachment_ids else [],
|
||||
)
|
||||
|
||||
# Moving the attachments in the message
|
||||
# TODO: Fix void res_id on attachment when you create an activity with an image
|
||||
# directly, see route /web_editor/attachment/add
|
||||
activity_message = record.message_ids[0]
|
||||
message_attachments = self.env['ir.attachment'].browse(activity_attachments[activity.id])
|
||||
if message_attachments:
|
||||
message_attachments.write({
|
||||
'res_id': activity_message.id,
|
||||
'res_model': activity_message._name,
|
||||
})
|
||||
activity_message.attachment_ids = message_attachments
|
||||
messages |= activity_message
|
||||
# Moving the attachments in the message
|
||||
# TODO: Fix void res_id on attachment when you create an activity with an image
|
||||
# directly, see route /web_editor/attachment/add
|
||||
if activity_attachments[activity.id]:
|
||||
message_attachments = self.env['ir.attachment'].browse(activity_attachments[activity.id])
|
||||
if message_attachments:
|
||||
message_attachments.write({
|
||||
'res_id': activity_message.id,
|
||||
'res_model': activity_message._name,
|
||||
})
|
||||
activity_message.attachment_ids = message_attachments
|
||||
messages += activity_message
|
||||
|
||||
next_activities = self.env['mail.activity']
|
||||
if next_activities_values:
|
||||
next_activities = self.env['mail.activity'].create(next_activities_values)
|
||||
|
||||
next_activities = self.env['mail.activity'].create(next_activities_values)
|
||||
self.unlink() # will unlink activity, dont access `self` after that
|
||||
|
||||
return messages, next_activities
|
||||
@@ -635,6 +675,28 @@ class MailActivity(models.Model):
|
||||
# TOOLS
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
def _classify_by_model(self):
|
||||
""" To ease batch computation of various activities related methods they
|
||||
are classified by model. Activities not linked to a valid record through
|
||||
res_model / res_id are ignored.
|
||||
|
||||
:return dict: for each model having at least one activity in self, have
|
||||
a sub-dict containing
|
||||
* activities: activities related to that model;
|
||||
* record IDs: record linked to the activities of that model, in same
|
||||
order;
|
||||
"""
|
||||
data_by_model = {}
|
||||
for activity in self.filtered(lambda act: act.res_model and act.res_id):
|
||||
if activity.res_model not in data_by_model:
|
||||
data_by_model[activity.res_model] = {
|
||||
'activities': self.env['mail.activity'],
|
||||
'record_ids': [],
|
||||
}
|
||||
data_by_model[activity.res_model]['activities'] += activity
|
||||
data_by_model[activity.res_model]['record_ids'].append(activity.res_id)
|
||||
return data_by_model
|
||||
|
||||
def _prepare_next_activity_values(self):
|
||||
""" Prepare the next activity values based on the current activity record and applies _onchange methods
|
||||
:returns a dict of values for the new activity
|
||||
|
||||
@@ -45,9 +45,7 @@ class MailActivityMixin(models.AbstractModel):
|
||||
Can be overriden to specify the default activity type of a model.
|
||||
It is only called in in activity_schedule() for now.
|
||||
"""
|
||||
return self.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False) \
|
||||
or self.env['mail.activity.type'].search([('res_model', '=', self._name)], limit=1) \
|
||||
or self.env['mail.activity.type'].search([('res_model', '=', False)], limit=1)
|
||||
return self.env['mail.activity']._default_activity_type_for_model(self._name)
|
||||
|
||||
activity_ids = fields.One2many(
|
||||
'mail.activity', 'res_id', 'Activities',
|
||||
@@ -401,16 +399,20 @@ class MailActivityMixin(models.AbstractModel):
|
||||
if isinstance(date_deadline, datetime):
|
||||
_logger.warning("Scheduled deadline should be a date (got %s)", date_deadline)
|
||||
if act_type_xmlid:
|
||||
activity_type = self.env.ref(act_type_xmlid, raise_if_not_found=False) or self._default_activity_type()
|
||||
activity_type_id = self.env['ir.model.data']._xmlid_to_res_id(act_type_xmlid, raise_if_not_found=False)
|
||||
if activity_type_id:
|
||||
activity_type = self.env['mail.activity.type'].browse(activity_type_id)
|
||||
else:
|
||||
activity_type = self._default_activity_type()
|
||||
else:
|
||||
activity_type_id = act_values.get('activity_type_id', False)
|
||||
activity_type = activity_type_id and self.env['mail.activity.type'].sudo().browse(activity_type_id)
|
||||
activity_type = self.env['mail.activity.type'].browse(activity_type_id) if activity_type_id else self.env['mail.activity.type']
|
||||
|
||||
model_id = self.env['ir.model']._get(self._name).id
|
||||
activities = self.env['mail.activity']
|
||||
create_vals_list = []
|
||||
for record in self:
|
||||
create_vals = {
|
||||
'activity_type_id': activity_type and activity_type.id,
|
||||
'activity_type_id': activity_type.id,
|
||||
'summary': summary or activity_type.summary,
|
||||
'automated': True,
|
||||
'note': note or activity_type.default_note,
|
||||
@@ -421,8 +423,8 @@ class MailActivityMixin(models.AbstractModel):
|
||||
create_vals.update(act_values)
|
||||
if not create_vals.get('user_id'):
|
||||
create_vals['user_id'] = activity_type.default_user_id.id or self.env.uid
|
||||
activities |= self.env['mail.activity'].create(create_vals)
|
||||
return activities
|
||||
create_vals_list.append(create_vals)
|
||||
return self.env['mail.activity'].create(create_vals_list)
|
||||
|
||||
def _activity_schedule_with_view(self, act_type_xmlid='', date_deadline=None, summary='', views_or_xmlid='', render_context=None, **act_values):
|
||||
""" Helper method: Schedule an activity on each record of the current record set.
|
||||
@@ -443,7 +445,7 @@ class MailActivityMixin(models.AbstractModel):
|
||||
for record in self:
|
||||
render_context['object'] = record
|
||||
note = self.env['ir.qweb']._render(view_ref, render_context, minimal_qcontext=True, raise_if_not_found=False)
|
||||
activities |= record.activity_schedule(act_type_xmlid=act_type_xmlid, date_deadline=date_deadline, summary=summary, note=note, **act_values)
|
||||
activities += record.activity_schedule(act_type_xmlid=act_type_xmlid, date_deadline=date_deadline, summary=summary, note=note, **act_values)
|
||||
return activities
|
||||
|
||||
def activity_reschedule(self, act_type_xmlids, user_id=None, date_deadline=None, new_user_id=None):
|
||||
|
||||
@@ -997,7 +997,7 @@ class Message(models.Model):
|
||||
except AccessError:
|
||||
continue
|
||||
else:
|
||||
messages |= message
|
||||
messages += message
|
||||
messages_per_partner = defaultdict(lambda: self.env['mail.message'])
|
||||
for message in messages:
|
||||
if not self.env.user._is_public():
|
||||
|
||||
@@ -450,6 +450,39 @@ class MailThread(models.AbstractModel):
|
||||
if any(message.message_type != 'comment' for message in messages):
|
||||
raise exceptions.UserError(_("Only messages type comment can have their content updated"))
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# FIELDS HELPERS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _mail_get_partner_fields(self):
|
||||
""" This method returns the fields to use to find the contact to link
|
||||
when sending emails or notifications. Having partner is not always
|
||||
necessary but gives more flexibility to notifications management. """
|
||||
return [fname for fname in ('partner_id', 'partner_ids')
|
||||
if fname in self]
|
||||
|
||||
def _mail_get_partners(self):
|
||||
""" Give the default partners associated to customers.
|
||||
|
||||
:return dict: for each record ID, a res.partner recordsets being default
|
||||
customers to contact;
|
||||
"""
|
||||
partner_fields = self._mail_get_partner_fields()
|
||||
return dict(
|
||||
(record.id, self.env['res.partner'].union(*[record[fname] for fname in partner_fields]))
|
||||
for record in self
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _mail_get_primary_email_field(self):
|
||||
""" Check if the "_primary_email" model attribute is correctly set and
|
||||
matches an existing field, and return it. Otherwise return None. """
|
||||
primary_email = getattr(self, '_primary_email', None)
|
||||
if primary_email and primary_email in self._fields:
|
||||
return primary_email
|
||||
return None
|
||||
|
||||
# ------------------------------------------------------
|
||||
# TRACKING / LOG
|
||||
# ------------------------------------------------------
|
||||
@@ -816,24 +849,13 @@ class MailThread(models.AbstractModel):
|
||||
for model in [bl_model for bl_model in bl_models if bl_model.model in self.env]: # transient test mode
|
||||
self.env[model.model].sudo().search([('message_bounce', '>', 0), ('email_normalized', '=', valid_email)])._message_reset_bounce(valid_email)
|
||||
|
||||
@api.model
|
||||
def _get_primary_email_field(self):
|
||||
"""Check if the "_primary_email" model attribute is correctly set.
|
||||
|
||||
If it is correctly set, return the _primary_email field name
|
||||
Otherwise return None
|
||||
"""
|
||||
primary_email = getattr(self, '_primary_email', None)
|
||||
if primary_email and primary_email in self._fields:
|
||||
return primary_email
|
||||
|
||||
@api.model
|
||||
def _detect_loop_sender_domain(self, email_from_normalized):
|
||||
"""Return the domain to be used to detect duplicated records created by alias.
|
||||
|
||||
:param email_from_normalized: FROM of the incoming email, normalized
|
||||
"""
|
||||
primary_email = self._get_primary_email_field()
|
||||
primary_email = self._mail_get_primary_email_field()
|
||||
if primary_email:
|
||||
return [(primary_email, 'ilike', email_from_normalized)]
|
||||
|
||||
@@ -1233,12 +1255,12 @@ class MailThread(models.AbstractModel):
|
||||
data = {}
|
||||
if isinstance(custom_values, dict):
|
||||
data = custom_values.copy()
|
||||
fields = self.fields_get()
|
||||
model_fields = self.fields_get()
|
||||
name_field = self._rec_name or 'name'
|
||||
if name_field in fields and not data.get('name'):
|
||||
if name_field in model_fields and not data.get('name'):
|
||||
data[name_field] = msg_dict.get('subject', '')
|
||||
|
||||
primary_email = self._get_primary_email_field()
|
||||
primary_email = self._mail_get_primary_email_field()
|
||||
if primary_email and msg_dict.get('email_from'):
|
||||
data[primary_email] = msg_dict['email_from']
|
||||
|
||||
@@ -2023,7 +2045,7 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
def message_post_with_view(self, views_or_xmlid, **kwargs):
|
||||
""" Helper method to send a mail / post a message using a view_id """
|
||||
self._message_compose_with_view(views_or_xmlid, **kwargs)
|
||||
return self._message_compose_with_view(views_or_xmlid, **kwargs)
|
||||
|
||||
def message_post_with_template(self, template_id, email_layout_xmlid=None, auto_commit=False, **kwargs):
|
||||
""" Helper method to send a mail with a template
|
||||
@@ -2496,7 +2518,7 @@ class MailThread(models.AbstractModel):
|
||||
'is_read': True, # discard Inbox notification
|
||||
'notification_status': 'ready',
|
||||
} for recipient_id in tocreate_recipient_ids]
|
||||
emails |= new_email
|
||||
emails += new_email
|
||||
|
||||
if notif_create_values:
|
||||
SafeNotification.create(notif_create_values)
|
||||
|
||||
@@ -55,6 +55,9 @@ class Partner(models.Model):
|
||||
# MESSAGING
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _mail_get_partners(self):
|
||||
return dict((partner.id, partner) for partner in self)
|
||||
|
||||
def _message_get_suggested_recipients(self):
|
||||
recipients = super(Partner, self)._message_get_suggested_recipients()
|
||||
for partner in self:
|
||||
|
||||
@@ -240,7 +240,13 @@ class MailComposer(models.TransientModel):
|
||||
|
||||
def _action_send_mail(self, auto_commit=False):
|
||||
""" Process the wizard content and proceed with sending the related
|
||||
email(s), rendering any template patterns on the fly if needed. """
|
||||
email(s), rendering any template patterns on the fly if needed.
|
||||
|
||||
:return tuple: (
|
||||
result_mails_su: in mass mode, sent emails (as sudo),
|
||||
result_messages: in comment mode, posted messages
|
||||
)
|
||||
"""
|
||||
# Several custom layouts make use of the model description at rendering, e.g. in the
|
||||
# 'View <document>' button. Some models are used for different business concepts, such as
|
||||
# 'purchase.order' which is used for a RFQ and and PO. To avoid confusion, we must use a
|
||||
@@ -248,6 +254,7 @@ class MailComposer(models.TransientModel):
|
||||
# Therefore, we can set the description in the context from the beginning to avoid falling
|
||||
# back on the regular display_name retrieved in ``_notify_by_email_prepare_rendering_context()``.
|
||||
model_description = self._context.get('model_description')
|
||||
result_mails_su, result_messages = self.env['mail.mail'].sudo(), self.env['mail.message']
|
||||
|
||||
for wizard in self:
|
||||
# Duplicate attachments linked to the email.template.
|
||||
@@ -299,7 +306,7 @@ class MailComposer(models.TransientModel):
|
||||
all_mail_values = wizard.get_mail_values(res_ids)
|
||||
for res_id, mail_values in all_mail_values.items():
|
||||
if wizard.composition_mode == 'mass_mail':
|
||||
batch_mails_sudo |= self.env['mail.mail'].sudo().create(mail_values)
|
||||
batch_mails_sudo += self.env['mail.mail'].sudo().create(mail_values)
|
||||
else:
|
||||
post_params = dict(
|
||||
message_type=wizard.message_type,
|
||||
@@ -317,11 +324,14 @@ class MailComposer(models.TransientModel):
|
||||
# if message_notify returns an empty record set, no recipients where found.
|
||||
raise UserError(_("No recipient found."))
|
||||
else:
|
||||
ActiveModel.browse(res_id).message_post(**post_params)
|
||||
result_messages += ActiveModel.browse(res_id).message_post(**post_params)
|
||||
|
||||
result_mails_su += batch_mails_sudo
|
||||
if wizard.composition_mode == 'mass_mail':
|
||||
batch_mails_sudo.send(auto_commit=auto_commit)
|
||||
|
||||
return result_mails_su, result_messages
|
||||
|
||||
def action_save_as_template(self):
|
||||
""" hit save as template button: current form value will be a new
|
||||
template attached to the current document. """
|
||||
|
||||
@@ -13,7 +13,7 @@ class NoteController(http.Controller):
|
||||
note = request.env['note.note'].create({'memo': note})
|
||||
if date_deadline:
|
||||
note.activity_schedule(
|
||||
activity_type_id=activity_type_id or request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id,
|
||||
activity_type_id=activity_type_id or request.env['mail.activity.type'].search([('category', '=', 'reminder')], limit=1).id,
|
||||
note=note.memo,
|
||||
date_deadline=date_deadline
|
||||
)
|
||||
|
||||
@@ -301,7 +301,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
||||
""" Test a single registration creation using Form """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=202): # tef 185 / com 190
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=202): # tef 185 / com 189
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['event.registration']) as reg_form:
|
||||
reg_form.event_id = event
|
||||
@@ -317,7 +317,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
||||
""" Test a single registration creation using Form """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=209): # tef 190 / com 195
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=209): # tef 190 / com 194
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['event.registration']) as reg_form:
|
||||
reg_form.event_id = event
|
||||
|
||||
@@ -243,7 +243,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
|
||||
# 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__=17, employee=19): # com+tm 16/18
|
||||
with self.assertQueryCount(__system__=14, employee=15): # com+tm 14/15
|
||||
activity.action_feedback(feedback='Zizisse Done !')
|
||||
|
||||
@users('__system__', 'employee')
|
||||
@@ -252,7 +252,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
|
||||
def test_adv_activity_mixin(self):
|
||||
record = self.env['mail.test.activity'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(__system__=8, employee=8):
|
||||
with self.assertQueryCount(__system__=7, employee=7):
|
||||
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
|
||||
@@ -260,7 +260,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
|
||||
|
||||
record.write({'name': 'Dupe write'})
|
||||
|
||||
with self.assertQueryCount(__system__=18, employee=21): # com+tm 17/20
|
||||
with self.assertQueryCount(__system__=15, employee=17): # com+tm 15/17
|
||||
record.action_close('Dupe feedback')
|
||||
|
||||
self.assertEqual(record.activity_ids, self.env['mail.activity'])
|
||||
@@ -278,7 +278,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
|
||||
for values in self.test_attachments_vals
|
||||
])
|
||||
|
||||
with self.assertQueryCount(__system__=8, employee=8):
|
||||
with self.assertQueryCount(__system__=7, employee=7):
|
||||
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
|
||||
@@ -286,7 +286,7 @@ class TestMailAPIPerformance(BaseMailPerformance):
|
||||
|
||||
record.write({'name': 'Dupe write'})
|
||||
|
||||
with self.assertQueryCount(__system__=21, employee=25): # com+tm 19/23
|
||||
with self.assertQueryCount(__system__=18, employee=21): # com+tm 17/20
|
||||
record.action_close('Dupe feedback', attachment_ids=attachments.ids)
|
||||
|
||||
# notifications
|
||||
|
||||
Reference in New Issue
Block a user