From b0eb876160935cede1c9b4ab84ea988f63bbdc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Feb 2013 14:26:25 +0100 Subject: [PATCH 01/16] [FIX] mail: first try to fix attachment duplication. bzr revid: tde@openerp.com-20130228132625-8yjggruhrvbvep21 --- addons/email_template/email_template.py | 34 +++++++++++-------- .../wizard/mail_compose_message.py | 5 ++- addons/mail/wizard/mail_compose_message.py | 5 ++- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 743384059b4..48fdc7edcd1 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -292,8 +292,7 @@ class email_template(osv.osv): 'copyvalue': self.build_expression(field_value.name, False, null_value or False), 'null_value': null_value or False }) - return {'value':result} - + return {'value': result} def generate_email(self, cr, uid, template_id, res_id, context=None): """Generates an email from the template for given (model, res_id) pair. @@ -346,11 +345,13 @@ class email_template(osv.osv): report_name += ext attachments.append((report_name, result)) + attachment_ids = [] # Add template attachments for attach in template.attachment_ids: - attachments.append((attach.datas_fname, attach.datas)) + attachment_ids.append(attach.id) values['attachments'] = attachments + values['attachment_ids'] = attachments return values def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None): @@ -365,28 +366,33 @@ class email_template(osv.osv): was executed for this message only. :returns: id of the mail.message that was created """ - if context is None: context = {} + if context is None: + context = {} mail_mail = self.pool.get('mail.mail') ir_attachment = self.pool.get('ir.attachment') + + # create a mail_mail based on values, without attachments values = self.generate_email(cr, uid, template_id, res_id, context=context) - assert 'email_from' in values, 'email_from is missing or empty after template rendering, send_mail() cannot proceed' - attachments = values.pop('attachments') or {} - del values['email_recipients'] # TODO Properly use them. - msg_id = mail_mail.create(cr, uid, values, context=context) - # link attachments - attachment_ids = [] - for fname, fcontent in attachments.iteritems(): + assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed' + del values['email_recipients'] # TODO Properly use them. + + # manage attachments + attachment_ids = values.pop('attachment_ids', []) + attachments = values.pop('attachments', []) + for fname, fcontent in attachments: attachment_data = { 'name': fname, 'datas_fname': fname, 'datas': fcontent, - 'res_model': mail_mail._name, - 'res_id': msg_id, + 'res_model': values.get('model', False), + 'res_id': values.get('res_id', False), } context.pop('default_type', None) attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context)) if attachment_ids: - mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context) + values['attachment_ids'] = [(6, 0, attachment_ids)] + + msg_id = mail_mail.create(cr, uid, values, context=context) if force_send: mail_mail.send(cr, uid, [msg_id], context=context) return msg_id diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index b39ed4e5639..397f913e912 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -71,7 +71,6 @@ class mail_compose_message(osv.TransientModel): # FIXME odo: change the mail generation to avoid attachment duplication values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context) # transform attachments into attachment_ids - values['attachment_ids'] = [] ir_attach_obj = self.pool.get('ir.attachment') for attach_fname, attach_datas in values.pop('attachments', []): data_attach = { @@ -80,7 +79,7 @@ class mail_compose_message(osv.TransientModel): 'datas_fname': attach_fname, 'res_model': model, 'res_id': res_id, - 'type': 'binary', # override default_type from context, possibly meant for another model! + 'type': 'binary', # override default_type from context, possibly meant for another model! } values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context)) else: @@ -122,7 +121,7 @@ class mail_compose_message(osv.TransientModel): mail.compose.message, transform email_cc and email_to into partner_ids """ template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context) # filter template values - fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments'] + fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachment_ids', 'attachments'] values = dict((field, template_values[field]) for field in fields if template_values.get(field)) values['body'] = values.pop('body_html', '') # transform email_to, email_cc into partner_ids diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index ec0c2a8de06..5741aacb449 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -205,13 +205,16 @@ class mail_compose_message(osv.TransientModel): 'body': wizard.body, 'parent_id': wizard.parent_id and wizard.parent_id.id, 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids], - 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], + 'attachments': [], + 'attachment_ids': [(4, attach.id) for attach in wizard.attachment_ids], } # mass mailing: render and override default values if mass_mail_mode and wizard.model: email_dict = self.render_message(cr, uid, wizard, res_id, context=context) new_partner_ids = email_dict.pop('partner_ids', []) post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids] + new_attachment_ids = email_dict.pop('attachment_ids', []) + post_values['attachments'] += new_attachment_ids new_attachments = email_dict.pop('attachments', []) post_values['attachments'] += new_attachments post_values.update(email_dict) From afbb5816e3e43bbb8f4f590c5686ccc708be2fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Feb 2013 14:36:33 +0100 Subject: [PATCH 02/16] [FIX] email_template: fixed previous patch, wrong var name. bzr revid: tde@openerp.com-20130228133633-1ur9jbqw021yoork --- addons/email_template/email_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 48fdc7edcd1..64c09d1eaff 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -351,7 +351,7 @@ class email_template(osv.osv): attachment_ids.append(attach.id) values['attachments'] = attachments - values['attachment_ids'] = attachments + values['attachment_ids'] = attachment_ids return values def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None): From cca08468043c17025cfbd6fd771227cc67ee93a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Feb 2013 14:49:01 +0100 Subject: [PATCH 03/16] [FIX] email_template: fixed attachments management in send_mail. bzr revid: tde@openerp.com-20130228134901-ibtnt2zl4yne7o4n --- addons/email_template/email_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 64c09d1eaff..1856479ebbe 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -379,11 +379,11 @@ class email_template(osv.osv): # manage attachments attachment_ids = values.pop('attachment_ids', []) attachments = values.pop('attachments', []) - for fname, fcontent in attachments: + for attachment in attachments: attachment_data = { - 'name': fname, - 'datas_fname': fname, - 'datas': fcontent, + 'name': attachment[0], + 'datas_fname': attachment[0], + 'datas': attachment[1], 'res_model': values.get('model', False), 'res_id': values.get('res_id', False), } From d6fcee5a4fb96c410f2d6580f7a239b2080e864d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Feb 2013 15:46:30 +0100 Subject: [PATCH 04/16] [FIX] email-template: attachments should have correct model and res_id from template. bzr revid: tde@openerp.com-20130228144630-7x6kh5jcn4w557do --- addons/email_template/email_template.py | 5 +++-- addons/email_template/tests/test_mail.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 1856479ebbe..8045cf62204 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -375,6 +375,7 @@ class email_template(osv.osv): values = self.generate_email(cr, uid, template_id, res_id, context=context) assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed' del values['email_recipients'] # TODO Properly use them. + template = self.get_email_template(cr, uid, template_id, res_id, context=context) # manage attachments attachment_ids = values.pop('attachment_ids', []) @@ -384,8 +385,8 @@ class email_template(osv.osv): 'name': attachment[0], 'datas_fname': attachment[0], 'datas': attachment[1], - 'res_model': values.get('model', False), - 'res_id': values.get('res_id', False), + 'res_model': template.model, + 'res_id': res_id, } context.pop('default_type', None) attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context)) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index b87b6d63000..0da73dc0f33 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -48,8 +48,8 @@ class test_message_compose(TestMailBase): _body_html1 = 'Fans of Pigs, unite !' _body_html2 = 'I am angry !' _attachments = [ - {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')}, - {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')} + {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'dummy.model'}, + {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'dummy.model'} ] _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] @@ -118,7 +118,7 @@ class test_message_compose(TestMailBase): # Test: mail.compose.message: attachments # Test: mail.message: attachments for attach in compose.attachment_ids: - self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect') + self.assertEqual(attach.res_model, 'dummy.model', 'mail.message attachment res_model through templat was overriden') self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test, 'mail.message attachment name / data incorrect') From aa865c8b61d53a1c9b402f616f419ad36c38bc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 28 Feb 2013 15:55:13 +0100 Subject: [PATCH 05/16] [FIX] Still fixing attachments through templates. bzr revid: tde@openerp.com-20130228145513-6g30ujr3cm0j8b3u --- addons/email_template/tests/test_mail.py | 8 ++++---- addons/mail/wizard/mail_compose_message.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index 0da73dc0f33..39b1023bc64 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -48,8 +48,8 @@ class test_message_compose(TestMailBase): _body_html1 = 'Fans of Pigs, unite !' _body_html2 = 'I am angry !' _attachments = [ - {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'dummy.model'}, - {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'dummy.model'} + {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'mail.group', 'res_id': self.group_pigs_id}, + {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'mail.group', 'res_id': self.group_pigs_id} ] _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] @@ -118,9 +118,9 @@ class test_message_compose(TestMailBase): # Test: mail.compose.message: attachments # Test: mail.message: attachments for attach in compose.attachment_ids: - self.assertEqual(attach.res_model, 'dummy.model', 'mail.message attachment res_model through templat was overriden') + self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model through templat was overriden') self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') - self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test, + self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test, 'mail.message attachment name / data incorrect') # ---------------------------------------- diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 5741aacb449..6389246bd7b 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -214,7 +214,7 @@ class mail_compose_message(osv.TransientModel): new_partner_ids = email_dict.pop('partner_ids', []) post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids] new_attachment_ids = email_dict.pop('attachment_ids', []) - post_values['attachments'] += new_attachment_ids + post_values['attachment_ids'] += [(4, attach_id) for attach_id in new_attachment_ids] new_attachments = email_dict.pop('attachments', []) post_values['attachments'] += new_attachments post_values.update(email_dict) From 46a5b63caddf857f38017ed4fb618f73a95a12a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 1 Mar 2013 15:48:27 +0100 Subject: [PATCH 06/16] [IMP] email_template: better management of attachments. In mass mail, we want attachments to be duplicated due to the ownership concept of attachments in OpenERP. bzr revid: tde@openerp.com-20130301144827-762362bgng5m4nye --- addons/email_template/tests/test_mail.py | 18 ++++++++--- .../wizard/mail_compose_message.py | 19 ++++++++++++ addons/mail/wizard/mail_compose_message.py | 31 +++++++++++-------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index 39b1023bc64..9e6ed15f904 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -48,8 +48,8 @@ class test_message_compose(TestMailBase): _body_html1 = 'Fans of Pigs, unite !' _body_html2 = 'I am angry !' _attachments = [ - {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'mail.group', 'res_id': self.group_pigs_id}, - {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'mail.group', 'res_id': self.group_pigs_id} + {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')}, + {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}, ] _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] @@ -116,10 +116,18 @@ class test_message_compose(TestMailBase): self.assertIn(_body_html1, compose.body, 'mail.compose.message body incorrect') self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect') # Test: mail.compose.message: attachments - # Test: mail.message: attachments for attach in compose.attachment_ids: - self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model through templat was overriden') - self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') + self.assertEqual(attach.res_model, False, 'mail.compose.message attachment res_model through templat was overriden') + self.assertEqual(attach.res_id, 0, 'mail.compose.message attachment res_id incorrect') + self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test, + 'mail.message attachment name / data incorrect') + # Test: mail.message: attachments + mail_compose.send_mail(cr, uid, [compose_id]) + group_pigs.refresh() + message_pigs = group_pigs.message_ids[0] + for attach in message_pigs.attachment_ids: + self.assertEqual(attach.res_model, 'mail.group', 'mail.compose.message attachment res_model through templat was overriden') + self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.compose.message attachment res_id incorrect') self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test, 'mail.message attachment name / data incorrect') diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 397f913e912..5631b927f46 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -61,6 +61,25 @@ class mail_compose_message(osv.TransientModel): 'template_id': fields.selection(_get_templates, 'Template', size=-1), } + def send_mail(self, cr, uid, ids, context=None): + """ Override of send_mail to duplicate attachments linked to the email.template. + Indeed, basic mail.compose.message wizard duplicates attachments in mass + mailing mode. But in 'single post' mode, attachments of an email template + also have to be duplicated to avoid changing their ownership. """ + for wizard in self.browse(cr, uid, ids, context=context): + if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id: + continue + active_model_pool_name = wizard.model if wizard.model else 'mail.thread' + template = self.pool.get('email.template').browse(cr, uid, wizard.template_id, context=context) + new_attachment_ids = [] + for attachment in wizard.attachment_ids: + if attachment in template.attachment_ids: + new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': active_model_pool_name, 'res_id': wizard.res_id}, context=context)) + else: + new_attachment_ids.append(attachment.id) + self.write(cr, uid, wizard.id, {'attachment_ids': [(6, 0, new_attachment_ids)]}, context=context) + return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) + def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None): """ - mass_mailing: we cannot render, so return the template values - normal mode: return rendered values """ diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index bb084405c29..0ec5242937e 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -190,41 +190,46 @@ class mail_compose_message(osv.TransientModel): email(s), rendering any template patterns on the fly if needed. """ if context is None: context = {} + ir_attachment_obj = self.pool.get('ir.attachment') active_ids = context.get('active_ids') - is_log = context.get('mail_compose_log', False) for wizard in self.browse(cr, uid, ids, context=context): mass_mail_mode = wizard.composition_mode == 'mass_mail' - active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread') + active_model_pool_name = wizard.model if wizard.model else 'mail.thread' + active_model_pool = self.pool.get(active_model_pool_name) # wizard works in batch mode: [res_id] or active_ids res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id] for res_id in res_ids: - # default values, according to the wizard options + # mail.message values, according to the wizard options post_values = { 'subject': wizard.subject, 'body': wizard.body, 'parent_id': wizard.parent_id and wizard.parent_id.id, 'partner_ids': [partner.id for partner in wizard.partner_ids], - 'attachments': [], 'attachment_ids': [attach.id for attach in wizard.attachment_ids], } # mass mailing: render and override default values if mass_mail_mode and wizard.model: email_dict = self.render_message(cr, uid, wizard, res_id, context=context) - new_partner_ids = email_dict.pop('partner_ids', []) - post_values['partner_ids'] += new_partner_ids - new_attachment_ids = email_dict.pop('attachment_ids', []) - post_values['attachment_ids'] += new_attachment_ids - new_attachments = email_dict.pop('attachments', []) - post_values['attachments'] += new_attachments + post_values['partner_ids'] += email_dict.pop('partner_ids', []) + post_values['attachments'] = email_dict.pop('attachments', []) + post_values['attachment_ids'] += email_dict.pop('attachment_ids', []) + # we must duplicate attachments, as each document will have its own copy of the attachment + attachment_ids = [] + for attach_id in post_values.pop('attachment_ids'): + new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': active_model_pool_name, 'res_id': res_id}, context=context) + attachment_ids.append(new_attach_id) + post_values['attachment_ids'] = attachment_ids post_values.update(email_dict) # post the message - subtype = 'mail.mt_comment' - if is_log: - subtype = False + subtype = 'mail.mt_comment' if not context.get('mail_compose_log', False) else False active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values) + # mass mailing: delete mail.compose.message attachments, added by the user and that have been duplicated + if mass_mail_mode and wizard.attachment_ids: + wizard.attachment_ids.unlink() + return {'type': 'ir.actions.act_window_close'} def render_message(self, cr, uid, wizard, res_id, context=None): From c7d8842b0285760216d9d44cf3110f7344c3c57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 4 Mar 2013 10:22:48 +0100 Subject: [PATCH 07/16] [IMP] mail, email_template: fixed attachment management (template does not override provided attachments anymore; fixed possible unexisting attachment ids key; fixed some duplication. bzr revid: tde@openerp.com-20130304092248-mqadzmrhgdu58j8y --- .../wizard/mail_compose_message.py | 18 +++++++++++------- addons/mail/wizard/mail_compose_message.py | 11 +++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 5631b927f46..1221cfde34e 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -84,20 +84,21 @@ class mail_compose_message(osv.TransientModel): """ - mass_mailing: we cannot render, so return the template values - normal mode: return rendered values """ if template_id and composition_mode == 'mass_mail': - values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context) + values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html', 'attachment_ids'], context) values.pop('id') elif template_id: # FIXME odo: change the mail generation to avoid attachment duplication values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context) - # transform attachments into attachment_ids + # transform attachments into attachment_ids; not attached to the document because this will + # be done further in the posting process, allowing to clean database if email not send ir_attach_obj = self.pool.get('ir.attachment') for attach_fname, attach_datas in values.pop('attachments', []): data_attach = { 'name': attach_fname, 'datas': attach_datas, 'datas_fname': attach_fname, - 'res_model': model, - 'res_id': res_id, + 'res_model': 'mail.compose.message', + 'res_id': 0, 'type': 'binary', # override default_type from context, possibly meant for another model! } values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context)) @@ -140,12 +141,15 @@ class mail_compose_message(osv.TransientModel): mail.compose.message, transform email_cc and email_to into partner_ids """ template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context) # filter template values + values = { + 'attachment_ids': [], + 'partner_ids': [], + } fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachment_ids', 'attachments'] - values = dict((field, template_values[field]) for field in fields if template_values.get(field)) + values.update(dict((field, template_values[field]) for field in fields if template_values.get(field))) values['body'] = values.pop('body_html', '') - # transform email_to, email_cc into partner_ids - values['partner_ids'] = [] + # transform email_to, email_cc into partner_ids mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', '')) for mail in mails: partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 0ec5242937e..f70e3624357 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -214,8 +214,10 @@ class mail_compose_message(osv.TransientModel): email_dict = self.render_message(cr, uid, wizard, res_id, context=context) post_values['partner_ids'] += email_dict.pop('partner_ids', []) post_values['attachments'] = email_dict.pop('attachments', []) - post_values['attachment_ids'] += email_dict.pop('attachment_ids', []) - # we must duplicate attachments, as each document will have its own copy of the attachment + # manage attachments : + # - do not re-add attachments from template already displayed + # - duplicate template attachments because of ownership concept in OpenERP + post_values['attachment_ids'] += filter(lambda item: item not in post_values['attachment_ids'], email_dict.pop('attachment_ids', [])) attachment_ids = [] for attach_id in post_values.pop('attachment_ids'): new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': active_model_pool_name, 'res_id': res_id}, context=context) @@ -227,8 +229,8 @@ class mail_compose_message(osv.TransientModel): active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values) # mass mailing: delete mail.compose.message attachments, added by the user and that have been duplicated - if mass_mail_mode and wizard.attachment_ids: - wizard.attachment_ids.unlink() + if wizard.attachment_ids: + ir_attachment_obj.unlink(cr, uid, [attach.id for attach in wizard.attachment_ids if attach.res_model == self._name], context=context) return {'type': 'ir.actions.act_window_close'} @@ -239,6 +241,7 @@ class mail_compose_message(osv.TransientModel): return { 'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context), 'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context), + 'attachment_ids': [attach.id for attach in wizard.attachment_ids], } def render_template(self, cr, uid, template, model, res_id, context=None): From f789005c9a6c26322565a1af7cc4e57a60eab9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 6 Mar 2013 14:44:10 +0100 Subject: [PATCH 08/16] [REV] email_template: send_mail: back to 2-steps creation, because attachments have to be garbage-collected, and not attached to the document. bzr revid: tde@openerp.com-20130306134410-mw9hau809qh1nbqh --- addons/email_template/email_template.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 8045cf62204..bcee1da9783 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -375,25 +375,25 @@ class email_template(osv.osv): values = self.generate_email(cr, uid, template_id, res_id, context=context) assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed' del values['email_recipients'] # TODO Properly use them. - template = self.get_email_template(cr, uid, template_id, res_id, context=context) - - # manage attachments attachment_ids = values.pop('attachment_ids', []) attachments = values.pop('attachments', []) + msg_id = mail_mail.create(cr, uid, values, context=context) + + # manage attachments for attachment in attachments: attachment_data = { 'name': attachment[0], 'datas_fname': attachment[0], 'datas': attachment[1], - 'res_model': template.model, - 'res_id': res_id, + 'res_model': 'mail.message', + 'res_id': msg_id, } context.pop('default_type', None) attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context)) if attachment_ids: values['attachment_ids'] = [(6, 0, attachment_ids)] + mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context) - msg_id = mail_mail.create(cr, uid, values, context=context) if force_send: mail_mail.send(cr, uid, [msg_id], context=context) return msg_id From d794920d431d75e912a7654d1daf5cb16a54e6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 6 Mar 2013 14:44:37 +0100 Subject: [PATCH 09/16] [IMP] mail.compose.message: remove attachment_ids from render_message as it should not be there; assume wizard.attachment_ids is always correct. bzr revid: tde@openerp.com-20130306134437-ywoi1p1nr2e1no97 --- addons/mail/wizard/mail_compose_message.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index f70e3624357..c946a71de9f 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -19,10 +19,8 @@ # ############################################################################## -import base64 import re from openerp import tools - from openerp.osv import osv from openerp.osv import fields from openerp.tools.safe_eval import safe_eval as eval @@ -214,10 +212,6 @@ class mail_compose_message(osv.TransientModel): email_dict = self.render_message(cr, uid, wizard, res_id, context=context) post_values['partner_ids'] += email_dict.pop('partner_ids', []) post_values['attachments'] = email_dict.pop('attachments', []) - # manage attachments : - # - do not re-add attachments from template already displayed - # - duplicate template attachments because of ownership concept in OpenERP - post_values['attachment_ids'] += filter(lambda item: item not in post_values['attachment_ids'], email_dict.pop('attachment_ids', [])) attachment_ids = [] for attach_id in post_values.pop('attachment_ids'): new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': active_model_pool_name, 'res_id': res_id}, context=context) @@ -241,7 +235,6 @@ class mail_compose_message(osv.TransientModel): return { 'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context), 'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context), - 'attachment_ids': [attach.id for attach in wizard.attachment_ids], } def render_template(self, cr, uid, template, model, res_id, context=None): From 4665d10f0550beabc3c1b6027436641749761a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 6 Mar 2013 14:46:12 +0100 Subject: [PATCH 10/16] [IMP] mail, email_template: better consistence of mail.compose.message mdoel ans 0 or res_id res_id for attachments on composer; generate_email_for_composer should avoid returning keys for void values. bzr revid: tde@openerp.com-20130306134612-yazwi6cgwvw8suyw --- .../email_template/wizard/mail_compose_message.py | 14 ++++++-------- addons/mail/mail_message.py | 2 +- addons/mail/mail_thread.py | 1 - 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 1221cfde34e..151cc3384ac 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -69,12 +69,11 @@ class mail_compose_message(osv.TransientModel): for wizard in self.browse(cr, uid, ids, context=context): if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id: continue - active_model_pool_name = wizard.model if wizard.model else 'mail.thread' template = self.pool.get('email.template').browse(cr, uid, wizard.template_id, context=context) new_attachment_ids = [] for attachment in wizard.attachment_ids: if attachment in template.attachment_ids: - new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': active_model_pool_name, 'res_id': wizard.res_id}, context=context)) + new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': 'mail.compose.message', 'res_id': wizard.id}, context=context)) else: new_attachment_ids.append(attachment.id) self.write(cr, uid, wizard.id, {'attachment_ids': [(6, 0, new_attachment_ids)]}, context=context) @@ -87,10 +86,10 @@ class mail_compose_message(osv.TransientModel): values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html', 'attachment_ids'], context) values.pop('id') elif template_id: - # FIXME odo: change the mail generation to avoid attachment duplication values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context) # transform attachments into attachment_ids; not attached to the document because this will # be done further in the posting process, allowing to clean database if email not send + values['attachment_ids'] = values.pop('attachment_ids', []) ir_attach_obj = self.pool.get('ir.attachment') for attach_fname, attach_datas in values.pop('attachments', []): data_attach = { @@ -141,15 +140,12 @@ class mail_compose_message(osv.TransientModel): mail.compose.message, transform email_cc and email_to into partner_ids """ template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context) # filter template values - values = { - 'attachment_ids': [], - 'partner_ids': [], - } fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachment_ids', 'attachments'] - values.update(dict((field, template_values[field]) for field in fields if template_values.get(field))) + values = dict((field, template_values[field]) for field in fields if template_values.get(field)) values['body'] = values.pop('body_html', '') # transform email_to, email_cc into partner_ids + values['partner_ids'] = [] mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', '')) for mail in mails: partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context) @@ -170,6 +166,8 @@ class mail_compose_message(osv.TransientModel): values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context) else: values = {} + # remove attachments as they should not be rendered + values.pop('attachment_ids', None) # get values to return email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context) values.update(email_dict) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 2f56e620543..f29d6c8c543 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -761,7 +761,7 @@ class mail_message(osv.Model): attachments_to_delete = [] for message in self.browse(cr, uid, ids, context=context): for attach in message.attachment_ids: - if attach.res_model == self._name and attach.res_id == message.id: + if attach.res_model == self._name and (attach.res_id == message.id or attach.res_id == 0): attachments_to_delete.append(attach.id) if attachments_to_delete: self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index a456e82d466..cf9b6ecf316 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -944,7 +944,6 @@ class mail_thread(osv.AbstractModel): if attachment_ids: filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [ ('res_model', '=', 'mail.compose.message'), - ('res_id', '=', 0), ('create_uid', '=', uid), ('id', 'in', attachment_ids)], context=context) if filtered_attachment_ids: From b1b3ed971ad1be9386618d9501fb9fdf97b9a55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 6 Mar 2013 17:05:17 +0100 Subject: [PATCH 11/16] [REM] Removed attachment removal because garbage collecting will do it. bzr revid: tde@openerp.com-20130306160517-b0rdbait2h6c9c77 --- addons/mail/wizard/mail_compose_message.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index c946a71de9f..f4b7eb8115d 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -222,10 +222,6 @@ class mail_compose_message(osv.TransientModel): subtype = 'mail.mt_comment' if not context.get('mail_compose_log', False) else False active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values) - # mass mailing: delete mail.compose.message attachments, added by the user and that have been duplicated - if wizard.attachment_ids: - ir_attachment_obj.unlink(cr, uid, [attach.id for attach in wizard.attachment_ids if attach.res_model == self._name], context=context) - return {'type': 'ir.actions.act_window_close'} def render_message(self, cr, uid, wizard, res_id, context=None): From 40244df4c18900f806055093ed64942667cf5d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 6 Mar 2013 17:11:51 +0100 Subject: [PATCH 12/16] [IMP] mail.compose.message: copied attachments in mass mail mode are linked to the composer, because message_post will update their model and res_id. bzr revid: tde@openerp.com-20130306161151-gczob78pmt683kw7 --- addons/mail/wizard/mail_compose_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index f4b7eb8115d..93b7ff07b80 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -214,7 +214,7 @@ class mail_compose_message(osv.TransientModel): post_values['attachments'] = email_dict.pop('attachments', []) attachment_ids = [] for attach_id in post_values.pop('attachment_ids'): - new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': active_model_pool_name, 'res_id': res_id}, context=context) + new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context) attachment_ids.append(new_attach_id) post_values['attachment_ids'] = attachment_ids post_values.update(email_dict) From 1aac220ee25b7dc45c073c76112e50c68a3c4944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 7 Mar 2013 12:06:57 +0100 Subject: [PATCH 13/16] [IMP] mail: tests: updated tests for attachments bzr revid: tde@openerp.com-20130307110657-1cdelhyd1girmp28 --- addons/email_template/tests/test_mail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index 9e6ed15f904..a4ecb9b6f91 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -48,8 +48,8 @@ class test_message_compose(TestMailBase): _body_html1 = 'Fans of Pigs, unite !' _body_html2 = 'I am angry !' _attachments = [ - {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')}, - {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}, + {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'res.partner', 'res_id': self.partner_admin_id}, + {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'res.partner', 'res_id': self.partner_admin_id}, ] _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] @@ -115,10 +115,10 @@ class test_message_compose(TestMailBase): self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect') self.assertIn(_body_html1, compose.body, 'mail.compose.message body incorrect') self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect') - # Test: mail.compose.message: attachments + # Test: mail.compose.message: attachments (owner has not been modified) for attach in compose.attachment_ids: - self.assertEqual(attach.res_model, False, 'mail.compose.message attachment res_model through templat was overriden') - self.assertEqual(attach.res_id, 0, 'mail.compose.message attachment res_id incorrect') + self.assertEqual(attach.res_model, 'res.partner', 'mail.compose.message attachment res_model through templat was overriden') + self.assertEqual(attach.res_id, self.partner_admin_id, 'mail.compose.message attachment res_id incorrect') self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test, 'mail.message attachment name / data incorrect') # Test: mail.message: attachments From fcea61b6ebce561743bfe3876dd5a705ce6a7bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 7 Mar 2013 14:25:17 +0100 Subject: [PATCH 14/16] [IMP] mail: added a cron job to delete mail attachments (lost user input or reports generated on-the-fly). They have: res_model = mail.compose.message; res_id 0; more than one day of inactivity. bzr revid: tde@openerp.com-20130307132517-4xivu90dzn3o744a --- addons/mail/data/mail_data.xml | 13 +++++++++++++ addons/mail/mail_thread.py | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml index 3f517ee7858..02cfd626768 100644 --- a/addons/mail/data/mail_data.xml +++ b/addons/mail/data/mail_data.xml @@ -27,6 +27,19 @@ 1000 + + Garbage Collect Mail Attachments + + + 1 + weeks + -1 + + mail.thread + _garbage_collect_attachments + () + + Discussions diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 173ec951b32..2d43b93dd9a 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -389,6 +389,26 @@ class mail_thread(osv.AbstractModel): return [('message_unread', '=', True)] return [] + def _garbage_collect_attachments(self, cr, uid, context=None): + """ Garbage collect lost mail attachments. Those are attachments + - linked to res_model 'mail.compose.message', the composer wizard + - with res_id 0, because they were created outside of an existing + wizard (typically user input through Chatter or reports + created on-the-fly by the templates) + - unused since at least one day (create_date and write_date) + """ + limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1) + limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + ir_attachment_obj = self.pool.get('ir.attachment') + attach_ids = ir_attachment_obj.search(cr, uid, [ + ('res_model', '=', 'mail.compose.message'), + ('res_id', '=', 0), + ('create_date', '<', limit_date_str), + ('write_date', '<', limit_date_str), + ], context=context) + ir_attachment_obj.unlink(cr, uid, attach_ids, context=context) + return True + #------------------------------------------------------ # Email specific #------------------------------------------------------ From 70ce1e5eb4586d6f08248430fe6bee9360dba285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 14 Mar 2013 09:53:15 +0100 Subject: [PATCH 15/16] [FIX] Fixed forgotten conflict when merging trunk. bzr revid: tde@openerp.com-20130314085315-m4didtupt20gmkyv --- addons/email_template/wizard/mail_compose_message.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index f70f29e4b5e..2e051338352 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -145,11 +145,7 @@ class mail_compose_message(osv.TransientModel): values['body'] = values.pop('body_html', '') # transform email_to, email_cc into partner_ids -<<<<<<< TREE - values['partner_ids'] = [] -======= partner_ids = set() ->>>>>>> MERGE-SOURCE mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', '')) for mail in mails: partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context) From 9e4486ae528e97e364e995b5bb3396f1e6b0dd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 11 Apr 2013 12:46:59 +0200 Subject: [PATCH 16/16] [FIX] mail: fixed missing conflict resolution bzr revid: tde@openerp.com-20130411104659-ik62af6wubzu5j6q --- addons/mail/wizard/mail_compose_message.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 205fa3a8ef0..301dff515b3 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -21,11 +21,7 @@ import re from openerp import tools -<<<<<<< TREE -======= - from openerp import SUPERUSER_ID ->>>>>>> MERGE-SOURCE from openerp.osv import osv from openerp.osv import fields from openerp.tools.safe_eval import safe_eval as eval