[REF] account, hr_{expense|holidays}, mail: move main attachment to mixin

Main attachment is quite costly and as it is implemented in mail.thread and is
therefore present in most model. In order to improve performance, we remove it
here from mail.thread and add it through a new mixin,only on model that
effectively use that features.

See sub commits for more details.

Task-2648976

closes odoo/odoo#110734

Related: odoo/enterprise#36164
Related: odoo/upgrade#4262
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2023-03-02 12:32:12 +01:00
committed by GitHub
15 changed files with 87 additions and 47 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ EMPTY = object()
class AccountMove(models.Model):
_name = "account.move"
_inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin', 'sequence.mixin']
_inherit = ['portal.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin', 'sequence.mixin']
_description = "Journal Entry"
_order = 'date desc, name desc, id desc'
_mail_post_access = 'read'
+1 -1
View File
@@ -7,7 +7,7 @@ from odoo.tools.misc import format_date, formatLang
class AccountPayment(models.Model):
_name = "account.payment"
_inherits = {'account.move': 'move_id'}
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin']
_description = "Payments"
_order = "date desc, name desc"
_check_company_auto = True
-4
View File
@@ -444,10 +444,6 @@ Andrew</p>]]></field>
<field name="subtype_id" ref="mail.mt_comment"/>
<field name="attachment_ids" eval="[(6, 0, [ref('msg_case15_attach1')])]"/>
</record>
<!--Main attachment is usually registered by message_post, so here we must set it manually-->
<record id="crm_case_15" model="crm.lead">
<field name="message_main_attachment_id" ref="msg_case15_attach1"/>
</record>
<record id="msg_case15_3" model="mail.message">
<field name="subject">Re: Plan to buy RedHat servers</field>
<field name="model">crm.lead</field>
+1 -1
View File
@@ -26,7 +26,7 @@ class HrEmployeePrivate(models.Model):
_name = "hr.employee"
_description = "Employee"
_order = 'name'
_inherit = ['hr.employee.base', 'mail.thread', 'mail.activity.mixin', 'resource.mixin', 'avatar.mixin']
_inherit = ['hr.employee.base', 'mail.thread.main.attachment', 'mail.activity.mixin', 'resource.mixin', 'avatar.mixin']
_mail_post_access = 'read'
# resource and user
+2 -2
View File
@@ -15,7 +15,7 @@ from odoo.tools import email_split, float_repr, float_compare, is_html_empty
class HrExpense(models.Model):
_name = "hr.expense"
_inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'analytic.mixin']
_description = "Expense"
_order = "date desc, id desc"
_check_company_auto = True
@@ -784,7 +784,7 @@ class HrExpenseSheet(models.Model):
=================================================================================
"""
_name = "hr.expense.sheet"
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin']
_description = "Expense Report"
_order = "accounting_date desc, id desc"
_check_company_auto = True
+1 -1
View File
@@ -72,7 +72,7 @@ class HolidaysRequest(models.Model):
_name = "hr.leave"
_description = "Time Off"
_order = "date_from desc"
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin']
_mail_post_access = 'read'
@api.model
+1 -1
View File
@@ -22,7 +22,7 @@ class Applicant(models.Model):
_name = "hr.applicant"
_description = "Applicant"
_order = "priority desc, id desc"
_inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin']
_inherit = ['mail.thread.cc', 'mail.thread.main.attachment', 'mail.activity.mixin', 'utm.mixin']
_mailing_enabled = True
_primary_email = 'email_from'
+1
View File
@@ -13,6 +13,7 @@ from . import mail_composer_mixin
from . import mail_thread
from . import mail_thread_blacklist
from . import mail_thread_cc
from . import mail_thread_main_attachment
from . import template_reset_mixin
# mail models
+14 -12
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
from odoo import models
from odoo.exceptions import AccessError
from odoo.http import request
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
@@ -18,23 +21,22 @@ class IrAttachment(models.Model):
def register_as_main_attachment(self, force=True):
""" Registers this attachment as the main one of the model it is
attached to.
:param bool force: if set, the method always updates the existing main attachment
otherwise it only sets the main attachment if there is none.
"""
self.ensure_one()
if not self.res_model:
if not self.res_model or not self.res_id:
return
related_record = self.env[self.res_model].browse(self.res_id)
if not related_record.check_access_rights('write', raise_exception=False):
if not related_record or \
not related_record.check_access_rights('write', raise_exception=False) or \
not hasattr(related_record, 'message_main_attachment_id'):
return
# message_main_attachment_id field can be empty, that's why we compare to False;
# we are just checking that it exists on the model before writing it
if related_record and hasattr(related_record, 'message_main_attachment_id'):
if force or not related_record.message_main_attachment_id:
#Ignore AccessError, if you don't have access to modify the document
#Just don't set the value
try:
related_record.message_main_attachment_id = self
except AccessError:
pass
if force or not related_record.message_main_attachment_id:
with contextlib.suppress(AccessError):
related_record.message_main_attachment_id = self
def _delete_and_notify(self):
for attachment in self:
-17
View File
@@ -107,7 +107,6 @@ class MailThread(models.AbstractModel):
'Number of errors', compute='_compute_message_has_error',
help="Number of messages with delivery error")
message_attachment_count = fields.Integer('Attachment Count', compute='_compute_message_attachment_count', groups="base.group_user")
message_main_attachment_id = fields.Many2one(string="Main Attachment", comodel_name='ir.attachment', copy=False)
@api.depends('message_follower_ids')
def _compute_message_partner_ids(self):
@@ -1971,11 +1970,6 @@ class MailThread(models.AbstractModel):
""" Hook to add custom behavior after having posted the message. Both
message and computed value are given, to try to lessen query count by
using already-computed values instead of having to rebrowse things. """
# Set main attachment field if necessary
self._message_set_main_attachment_id([
attachment_command[1]
for attachment_command in (msg_values['attachment_ids'] or [])
])
def _message_mail_after_hook(self, mails):
""" Hook to add custom behavior after having sent an mass mailing.
@@ -1983,16 +1977,6 @@ class MailThread(models.AbstractModel):
:param mail.mail mails: mail.mail records about to be sent"""
return
def _message_set_main_attachment_id(self, attachment_ids):
if self._name == 'mail.thread':
return
if attachment_ids and not self.message_main_attachment_id:
all_attachments = self.env['ir.attachment'].browse(attachment_ids)
prioritary_attachments = all_attachments.filtered(lambda x: x.mimetype.endswith('pdf')) \
or all_attachments.filtered(lambda x: x.mimetype.startswith('image')) \
or all_attachments
self.sudo().with_context(tracking_disable=True).message_main_attachment_id = prioritary_attachments[0].id
def _process_attachments_for_post(self, attachments, attachment_ids, message_values):
""" Preprocess attachments for MailTread.message_post() or MailMail.create().
Purpose is to
@@ -3797,7 +3781,6 @@ class MailThread(models.AbstractModel):
res['activities'] = self.activity_ids.activity_format()
if 'attachments' in request_list:
res['attachments'] = self._get_mail_thread_data_attachments()._attachment_format()
res['mainAttachment'] = {'id': self.message_main_attachment_id.id} if self.message_main_attachment_id else [('clear',)]
if 'followers' in request_list:
res['followers'] = [{
'id': follower.id,
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class MailMainAttachmentMixin(models.AbstractModel):
""" Mixin that adds main attachment support to the MailThread class. """
_name = 'mail.thread.main.attachment'
_inherit = 'mail.thread'
_description = 'Mail Main Attachment management'
message_main_attachment_id = fields.Many2one(string="Main Attachment", comodel_name='ir.attachment', copy=False)
def _message_post_after_hook(self, message, msg_values):
""" Set main attachment field if necessary """
super()._message_post_after_hook(message, msg_values)
self._message_set_main_attachment_id([
attachment_command[1]
for attachment_command in (msg_values['attachment_ids'] or [])
])
def _message_set_main_attachment_id(self, attachment_ids):
if attachment_ids and not self.message_main_attachment_id:
# Assign one of the attachments as the main according to the following priority: pdf, image, other types.
self.sudo().with_context(tracking_disable=True).message_main_attachment_id = max(
self.env['ir.attachment'].browse(attachment_ids),
key=lambda r: (r.mimetype.endswith('pdf'), r.mimetype.startswith('image'))
).id
def _get_mail_thread_data(self, request_list):
res = super()._get_mail_thread_data(request_list)
if 'attachments' in request_list:
res['mainAttachment'] = {'id': self.message_main_attachment_id.id} if self.message_main_attachment_id else [('clear',)]
return res
@@ -398,9 +398,12 @@ patch(MockServer.prototype, "mail/controllers/discuss", {
res["attachments"] = this._mockIrAttachment_attachmentFormat(
attachments.map((attachment) => attachment.id)
);
res["mainAttachment"] = thread.message_main_attachment_id
? { id: thread.message_main_attachment_id[0] }
: [["clear"]];
// Specific implementation of mail.thread.main.attachment
if (this.models[thread_model].fields["message_main_attachment_id"]) {
res["mainAttachment"] = thread.message_main_attachment_id
? { id: thread.message_main_attachment_id[0] }
: [["clear"]];
}
}
if (request_list.includes("followers")) {
const followers = this.pyEnv["mail.followers"].searchRead([
@@ -32,6 +32,12 @@ class MailTestSimple(models.Model):
return headers
class MailTestSimpleWithMainAttachment(models.Model):
_description = 'Simple Chatter Model With Main Attachment Management'
_name = 'mail.test.simple.main.attachment'
_inherit = ['mail.test.simple', 'mail.thread.main.attachment']
class MailTestGateway(models.Model):
""" A very simple model only inheriting from mail.thread to test pure mass
mailing features and base performances. """
@@ -3,6 +3,8 @@ access_mail_performance_thread,access_mail_performance_thread,model_mail_perform
access_mail_performance_tracking_user,mail.performance.tracking,model_mail_performance_tracking,base.group_user,1,1,1,1
access_mail_test_simple_portal,mail.test.simple.portal,model_mail_test_simple,base.group_portal,1,0,0,0
access_mail_test_simple_user,mail.test.simple.user,model_mail_test_simple,base.group_user,1,1,1,1
access_mail_test_simple_main_attachment_portal,mail.test.simple.main.attachment.portal,model_mail_test_simple_main_attachment,base.group_portal,1,0,0,0
access_mail_test_simple_main_attachment_user,mail.test.simple.main.attachment.user,model_mail_test_simple_main_attachment,base.group_user,1,1,1,1
access_mail_test_gateway_portal,mail.test.gateway.portal,model_mail_test_gateway,base.group_portal,1,0,0,0
access_mail_test_gateway_user,mail.test.gateway.user,model_mail_test_gateway,base.group_user,1,1,1,1
access_mail_test_gateway_groups_portal,mail.test.gateway.groups.portal,model_mail_test_gateway_groups,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_mail_performance_tracking_user mail.performance.tracking model_mail_performance_tracking base.group_user 1 1 1 1
4 access_mail_test_simple_portal mail.test.simple.portal model_mail_test_simple base.group_portal 1 0 0 0
5 access_mail_test_simple_user mail.test.simple.user model_mail_test_simple base.group_user 1 1 1 1
6 access_mail_test_simple_main_attachment_portal mail.test.simple.main.attachment.portal model_mail_test_simple_main_attachment base.group_portal 1 0 0 0
7 access_mail_test_simple_main_attachment_user mail.test.simple.main.attachment.user model_mail_test_simple_main_attachment base.group_user 1 1 1 1
8 access_mail_test_gateway_portal mail.test.gateway.portal model_mail_test_gateway base.group_portal 1 0 0 0
9 access_mail_test_gateway_user mail.test.gateway.user model_mail_test_gateway base.group_user 1 1 1 1
10 access_mail_test_gateway_groups_portal mail.test.gateway.groups.portal model_mail_test_gateway_groups base.group_portal 1 0 0 0
+15 -4
View File
@@ -919,7 +919,13 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_message_schedule')
def test_message_post_w_attachments(self):
def test_message_post_w_attachments_on_main_attachment_model(self):
""" Test posting a message with attachments on a model inheriting from
the mixin mail.thread.main.attachment.
As the mixin inherits from mail.thread, we test mainly features from
mail.thread but with the ones added of the main attachment mixin.
"""
_attachments = [
('List1', b'My first attachment'),
('List2', b'My second attachment'),
@@ -929,7 +935,12 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
)
_attachment_records[1].write({'mimetype': 'image/png'}) # to test message_main_attachment heuristic
test_record = self.env['mail.test.simple'].browse(self.test_record.ids)
test_record = self.env['mail.test.simple.main.attachment'].with_context(self._test_context).create({
'name': 'Test',
'email_from': 'ignasse@example.com',
})
self._reset_mail_context(test_record)
self.test_message.model = test_record._name
self.assertFalse(test_record.message_main_attachment_id)
with self.mock_mail_gateway():
@@ -949,8 +960,8 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase):
# message attachments
self.assertEqual(len(msg.attachment_ids), 5)
self.assertEqual(set(msg.attachment_ids.mapped('res_model')), set([self.test_record._name]))
self.assertEqual(set(msg.attachment_ids.mapped('res_id')), set([self.test_record.id]))
self.assertEqual(set(msg.attachment_ids.mapped('res_model')), {test_record._name})
self.assertEqual(set(msg.attachment_ids.mapped('res_id')), {test_record.id})
self.assertEqual(set(base64.b64decode(x) for x in msg.attachment_ids.mapped('datas')),
set([b'AttContent_00', b'AttContent_01', b'AttContent_02', _attachments[0][1], _attachments[1][1]]))
self.assertTrue(set(_attachment_records.ids).issubset(msg.attachment_ids.ids),