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 @@
+
+ mail.template.view.form.confirm.delete
+ 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"/>
-
+ class="float-end btn-secondary" data-hotkey="w" help="Save as a new template"/>
+
+
+
+
+
+
+ mail.compose.message.view.form.template.save
+ mail.compose.message
+
+
diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml
index 07c5d954955..260c9e8c599 100644
--- a/addons/project/security/project_security.xml
+++ b/addons/project/security/project_security.xml
@@ -15,7 +15,7 @@
Administrator
-
+
diff --git a/addons/sales_team/security/sales_team_security.xml b/addons/sales_team/security/sales_team_security.xml
index a77e6b98f56..45cf18f66bf 100644
--- a/addons/sales_team/security/sales_team_security.xml
+++ b/addons/sales_team/security/sales_team_security.xml
@@ -23,7 +23,8 @@
Administrator
the user will have an access to the sales configuration as well as statistic reports.
-
+
diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py
index 68be256d7c2..d81162fdab4 100644
--- a/addons/test_mail/tests/test_mail_composer.py
+++ b/addons/test_mail/tests/test_mail_composer.py
@@ -1176,14 +1176,14 @@ class TestComposerInternals(TestMailComposer):
).create({
'subject': 'Template Subject',
'body': '
Template Body
',
- }).action_save_as_template()
+ }).create_mail_template()
# Test: email_template subject, body_html, model
template = self.env['mail.template'].search([
('model', '=', self.test_record._name),
('subject', '=', 'Template Subject')
], limit=1)
- self.assertEqual(template.name, "%s: %s" % (self.env['ir.model']._get(self.test_record._name).name, 'Template Subject'))
+ self.assertEqual(template.name, 'Template Subject')
self.assertEqual(template.body_html, 'Template Body
', 'email_template incorrect body_html')
@users('erp_manager')