From 39dd2fc2abbe1401b690e8544edc443c137bbea1 Mon Sep 17 00:00:00 2001 From: miad-odoo Date: Mon, 10 Jul 2023 10:18:35 +0200 Subject: [PATCH] [IMP] mail: allow users to manage own templates Before this commit, all mail templates were shared, which was cluttering the UI for everyone. Now, each user can have their own templates that they can edit and save. Access is done through the mail composer wizard, where users can only access their own templates and templates that don't belong to anyone. Some groups are considered as admins and can access all templates in Settings/Technical/Email/Email Templates: - Sales Admin - Project Admins - Helpdesk Admins - Accountants - Event Admins - Recruitment Admins Task-2504439 Part-of: odoo/odoo#126049 --- addons/event/security/event_security.xml | 2 +- .../security/hr_recruitment_security.xml | 2 +- addons/mail/models/mail_template.py | 30 +++++- addons/mail/static/src/scss/composer.scss | 1 + addons/mail/views/mail_template_views.xml | 93 +++++++++++-------- addons/mail/wizard/mail_compose_message.py | 76 ++++++++++----- .../wizard/mail_compose_message_views.xml | 30 +++++- addons/project/security/project_security.xml | 2 +- .../security/sales_team_security.xml | 3 +- addons/test_mail/tests/test_mail_composer.py | 4 +- 10 files changed, 169 insertions(+), 74 deletions(-) diff --git a/addons/event/security/event_security.xml b/addons/event/security/event_security.xml index 645f798641e..573cbe09155 100644 --- a/addons/event/security/event_security.xml +++ b/addons/event/security/event_security.xml @@ -21,7 +21,7 @@ Administrator - + diff --git a/addons/hr_recruitment/security/hr_recruitment_security.xml b/addons/hr_recruitment/security/hr_recruitment_security.xml index 29a16ef8f3e..14f79b789d0 100644 --- a/addons/hr_recruitment/security/hr_recruitment_security.xml +++ b/addons/hr_recruitment/security/hr_recruitment_security.xml @@ -28,7 +28,7 @@ Administrator - + diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 840d4efb4ba..3eff2c4bee2 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -18,7 +18,7 @@ class MailTemplate(models.Model): _name = "mail.template" _inherit = ['mail.render.mixin', 'template.reset.mixin'] _description = 'Email Templates' - _order = 'name' + _order = 'user_id,name,id' _unrestricted_rendering = True @@ -46,6 +46,7 @@ class MailTemplate(models.Model): email_from = fields.Char('From', help="Sender address (placeholders may be used here). If not set, the default " "value will be the author's email alias if configured, or email address.") + user_id = fields.Many2one('res.users', string='User', domain="[('share', '=', False)]", help='The template belongs to this user') # recipients use_default_to = fields.Boolean( 'Default recipients', @@ -88,6 +89,7 @@ class MailTemplate(models.Model): # access can_write = fields.Boolean(compute='_compute_can_write', help='The current user can edit the template.') + is_template_editor = fields.Boolean(compute="_compute_is_template_editor") # Overrides of mail.render.mixin @api.depends('model') @@ -101,6 +103,10 @@ class MailTemplate(models.Model): for template in self: template.can_write = template in writable_templates + @api.depends_context('uid') + def _compute_is_template_editor(self): + self.is_template_editor = self.user_has_groups('mail.group_mail_template_editor') + @api.depends('active', 'description') def _compute_template_category(self): """ Base templates (or master templates) are active templates having @@ -166,10 +172,32 @@ class MailTemplate(models.Model): self._fix_attachment_ownership() return True + def open_delete_confirmation_modal(self): + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_id': self.id, + 'res_model': self._name, + 'target': 'new', + 'view_id': self.env.ref('mail.mail_template_view_form_confirm_delete').id, + 'context': {'dialog_size': 'medium'}, + 'name': _('Confirmation'), + } + def unlink(self): self.unlink_action() return super(MailTemplate, self).unlink() + def cancel_unlink(self): + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_id': self.id, + 'res_model': self._name, + 'target': 'new', + 'context': {'dialog_size': 'large'}, + } + @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}, diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss index 261b7a5ac7e..6fe5b13e7d4 100644 --- a/addons/mail/static/src/scss/composer.scss +++ b/addons/mail/static/src/scss/composer.scss @@ -48,6 +48,7 @@ } .o_mail_composer_form .o_form_renderer { + padding-bottom: 0px; .oe-bordered-editor[name=body] .o_readonly { border: 1px solid $o-gray-300; padding: 4px; diff --git a/addons/mail/views/mail_template_views.xml b/addons/mail/views/mail_template_views.xml index ecae072cc6c..2ebe4ba1219 100644 --- a/addons/mail/views/mail_template_views.xml +++ b/addons/mail/views/mail_template_views.xml @@ -7,42 +7,35 @@
+ + +
-
- - - - -
@@ -80,13 +72,24 @@ - - - - - + + + + + + + + + + + @@ -95,6 +98,20 @@ + + mail.template.view.form.confirm.delete + mail.template + + +

Are you sure you want to delete this Mail Template?

+
+
+ +
+
+ email.template.tree mail.template @@ -103,6 +120,7 @@ + @@ -120,6 +138,7 @@ + diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 4657479aa09..0bf09d68f59 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -83,7 +83,10 @@ class MailComposer(models.TransientModel): compute='_compute_body', readonly=False, store=True) parent_id = fields.Many2one( 'mail.message', 'Parent Message', ondelete='set null') - template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', model)]") + template_id = fields.Many2one( + 'mail.template', 'Use template', + domain="[('model', '=', model), '|', ('user_id','=', False), ('user_id', '=', uid)]" + ) attachment_ids = fields.Many2many( 'ir.attachment', 'mail_compose_message_ir_attachments_rel', 'wizard_id', 'attachment_id', string='Attachments', @@ -700,32 +703,55 @@ class MailComposer(models.TransientModel): return mails_sudo - def action_save_as_template(self): - """ hit save as template button: current form value will be a new - template attached to the current document. """ - for record in self: - model = self.env['ir.model']._get(record.model or 'mail.message') - model_name = model.name or '' - template_name = "%s: %s" % (model_name, tools.ustr(record.subject)) - values = { - 'name': template_name, - 'subject': record.subject or False, - 'body_html': record.body or False, - 'model_id': model.id or False, - 'use_default_to': True, - } - template = self.env['mail.template'].create(values) + def open_template_creation_wizard(self): + """ hit save as template button: opens a wizard that prompts for the template's subject. + `create_mail_template` is called when saving the new wizard. """ - if record.attachment_ids: - attachments = self.env['ir.attachment'].sudo().browse(record.attachment_ids.ids).filtered( - lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid) - if attachments: - attachments.write({'res_model': template._name, 'res_id': template.id}) - template.attachment_ids |= record.attachment_ids + self.ensure_one() + saved_subject = self.subject + self.subject = False + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'view_id': self.env.ref('mail.mail_compose_message_view_form_template_save').id, + 'name': _('Create a new Mail Template'), + 'res_model': 'mail.compose.message', + 'context': {'dialog_size': 'medium', 'mail_composer_saved_subject': saved_subject}, + 'target': 'new', + 'res_id': self.id, + } - # generate the saved template - record.write({'template_id': template.id}) - return _reopen(self, record.id, record.model, context=self.env.context) + def create_mail_template(self): + """ creates a mail template with the information form the current mail composer """ + self.ensure_one() + model_id = self.env['ir.model']._get_id(self.model) + values = { + 'name': self.subject, + 'subject': self.subject, + 'body_html': self.body, + 'model_id': model_id, + 'use_default_to': True, + 'user_id': self.env.uid, + } + template = self.env['mail.template'].create(values) + + if self.attachment_ids: + attachments = self.env['ir.attachment'].sudo().browse(self.attachment_ids.ids).filtered( + lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid) + if attachments: + attachments.write({'res_model': template._name, 'res_id': template.id}) + template.attachment_ids = self.attachment_ids + + # generate the saved template + self.write({'template_id': template.id}) + return _reopen(self, self.id, self.model, context={**self.env.context, 'dialog_size': 'large'}) + + def cancel_save_template(self): + """ Restore old subject when canceling the 'save as template' action + as it was erased to let user give a more custom input. """ + self.ensure_one() + self.subject = self.env.context.get('mail_composer_saved_subject') + return _reopen(self, self.id, self.model, context={**self.env.context, 'dialog_size': 'large'}) # ------------------------------------------------------------ # RENDERING / VALUES GENERATION diff --git a/addons/mail/wizard/mail_compose_message_views.xml b/addons/mail/wizard/mail_compose_message_views.xml index 7d6cd5b1cb6..7d59093065f 100644 --- a/addons/mail/wizard/mail_compose_message_views.xml +++ b/addons/mail/wizard/mail_compose_message_views.xml @@ -46,7 +46,9 @@ - + + @@ -56,7 +58,9 @@ - + + @@ -80,10 +84,26 @@ type="object" class="btn-primary" data-hotkey="q" invisible="not subtype_is_log"/>