diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mass_mailing/wizard | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mass_mailing/wizard')
9 files changed, 337 insertions, 0 deletions
diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py new file mode 100644 index 00000000..d4f1e912 --- /dev/null +++ b/addons/mass_mailing/wizard/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mail_compose_message +from . import mailing_list_merge +from . import mailing_mailing_schedule_date +from . import mailing_mailing_test diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py new file mode 100644 index 00000000..8ba5c783 --- /dev/null +++ b/addons/mass_mailing/wizard/mail_compose_message.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, tools +from odoo.tools import email_re + + +class MailComposeMessage(models.TransientModel): + _inherit = 'mail.compose.message' + + mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', ondelete='cascade') + campaign_id = fields.Many2one('utm.campaign', string='Mass Mailing Campaign') + mass_mailing_name = fields.Char(string='Mass Mailing Name') + mailing_list_ids = fields.Many2many('mailing.list', string='Mailing List') + + def get_mail_values(self, res_ids): + """ Override method that generated the mail content by creating the + mailing.trace values in the o2m of mail_mail, when doing pure + email mass mailing. """ + self.ensure_one() + res = super(MailComposeMessage, self).get_mail_values(res_ids) + # use only for allowed models in mass mailing + if self.composition_mode == 'mass_mail' and \ + (self.mass_mailing_name or self.mass_mailing_id) and \ + self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1): + mass_mailing = self.mass_mailing_id + if not mass_mailing: + reply_to_mode = 'email' if self.no_auto_thread else 'thread' + reply_to = self.reply_to if self.no_auto_thread else False + mass_mailing = self.env['mailing.mailing'].create({ + 'campaign_id': self.campaign_id.id, + 'name': self.mass_mailing_name, + 'subject': self.subject, + 'state': 'done', + 'reply_to_mode': reply_to_mode, + 'reply_to': reply_to, + 'sent_date': fields.Datetime.now(), + 'body_html': self.body, + 'mailing_model_id': self.env['ir.model']._get(self.model).id, + 'mailing_domain': self.active_domain, + }) + + # Preprocess res.partners to batch-fetch from db + # if recipient_ids is present, it means they are partners + # (the only object to fill get_default_recipient this way) + recipient_partners_ids = [] + read_partners = {} + for res_id in res_ids: + mail_values = res[res_id] + if mail_values.get('recipient_ids'): + # recipient_ids is a list of x2m command tuples at this point + recipient_partners_ids.append(mail_values.get('recipient_ids')[0][1]) + read_partners = self.env['res.partner'].browse(recipient_partners_ids) + + partners_email = {p.id: p.email for p in read_partners} + + opt_out_list = self._context.get('mass_mailing_opt_out_list') + seen_list = self._context.get('mass_mailing_seen_list') + mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout', raise_if_not_found=False) + for res_id in res_ids: + mail_values = res[res_id] + if mail_values.get('email_to'): + mail_to = tools.email_normalize(mail_values['email_to']) + else: + partner_id = (mail_values.get('recipient_ids') or [(False, '')])[0][1] + mail_to = tools.email_normalize(partners_email.get(partner_id)) + if (opt_out_list and mail_to in opt_out_list) or (seen_list and mail_to in seen_list) \ + or (not mail_to or not email_re.findall(mail_to)): + # prevent sending to blocked addresses that were included by mistake + mail_values['state'] = 'cancel' + elif seen_list is not None: + seen_list.add(mail_to) + trace_vals = { + 'model': self.model, + 'res_id': res_id, + 'mass_mailing_id': mass_mailing.id, + # if mail_to is void, keep falsy values to allow searching / debugging traces + 'email': mail_to or mail_values.get('email_to'), + } + if mail_values.get('body_html') and mass_mail_layout: + mail_values['body_html'] = mass_mail_layout._render({'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True) + # propagate ignored state to trace when still-born + if mail_values.get('state') == 'cancel': + trace_vals['ignored'] = fields.Datetime.now() + mail_values.update({ + 'mailing_id': mass_mailing.id, + 'mailing_trace_ids': [(0, 0, trace_vals)], + # email-mode: keep original message for routing + 'notification': mass_mailing.reply_to_mode == 'thread', + 'auto_delete': not mass_mailing.keep_archives, + }) + return res diff --git a/addons/mass_mailing/wizard/mail_compose_message_views.xml b/addons/mass_mailing/wizard/mail_compose_message_views.xml new file mode 100644 index 00000000..07aec46f --- /dev/null +++ b/addons/mass_mailing/wizard/mail_compose_message_views.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <!-- Add mass mail campaign to the mail.compose.message form view --> + <record model="ir.ui.view" id="email_compose_form_mass_mailing"> + <field name="name">mail.compose.message.form.mass_mailing</field> + <field name="model">mail.compose.message</field> + <field name="inherit_id" ref="mail.email_compose_message_wizard_form"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='notify']" position="after"> + <field name="campaign_id" groups="mass_mailing.group_mass_mailing_campaign" + attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/> + <field name="mass_mailing_name" + attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/> + </xpath> + </field> + </record> + +</odoo> diff --git a/addons/mass_mailing/wizard/mailing_list_merge.py b/addons/mass_mailing/wizard/mailing_list_merge.py new file mode 100644 index 00000000..21310778 --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_list_merge.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class MassMailingListMerge(models.TransientModel): + _name = 'mailing.list.merge' + _description = 'Merge Mass Mailing List' + + @api.model + def default_get(self, fields): + res = super(MassMailingListMerge, self).default_get(fields) + + if not res.get('src_list_ids') and 'src_list_ids' in fields: + if self.env.context.get('active_model') != 'mailing.list': + raise UserError(_('You can only apply this action from Mailing Lists.')) + src_list_ids = self.env.context.get('active_ids') + res.update({ + 'src_list_ids': [(6, 0, src_list_ids)], + }) + if not res.get('dest_list_id') and 'dest_list_id' in fields: + src_list_ids = res.get('src_list_ids') or self.env.context.get('active_ids') + res.update({ + 'dest_list_id': src_list_ids and src_list_ids[0] or False, + }) + return res + + src_list_ids = fields.Many2many('mailing.list', string='Mailing Lists') + dest_list_id = fields.Many2one('mailing.list', string='Destination Mailing List') + merge_options = fields.Selection([ + ('new', 'Merge into a new mailing list'), + ('existing', 'Merge into an existing mailing list'), + ], 'Merge Option', required=True, default='new') + new_list_name = fields.Char('New Mailing List Name') + archive_src_lists = fields.Boolean('Archive source mailing lists', default=True) + + def action_mailing_lists_merge(self): + if self.merge_options == 'new': + self.dest_list_id = self.env['mailing.list'].create({ + 'name': self.new_list_name, + }).id + self.dest_list_id.action_merge(self.src_list_ids, self.archive_src_lists) + return self.dest_list_id diff --git a/addons/mass_mailing/wizard/mailing_list_merge_views.xml b/addons/mass_mailing/wizard/mailing_list_merge_views.xml new file mode 100644 index 00000000..e446e3ca --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_list_merge_views.xml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<odoo> + <!-- Merge Mailing List --> + <record id="mailing_list_merge_view_form" model="ir.ui.view"> + <field name="name">mailing.list.merge.form</field> + <field name="model">mailing.list.merge</field> + <field name="arch" type="xml"> + <form string="Merge Mass Mailing List"> + <group> + <field name="merge_options" widget="selection"/> + <field name="new_list_name" attrs="{'invisible': [('merge_options', '=', 'existing')], 'required': [('merge_options', '=', 'new')]}"/> + <field name="dest_list_id" attrs="{'invisible': [('merge_options', '=', 'new')], 'required': [('merge_options', '=', 'existing')]}"/> + <field name="archive_src_lists"/> + </group> + <field name="src_list_ids"> + <tree> + <field name="name"/> + <field name="contact_nbr" string="Number of Recipients"/> + </tree> + </field> + <footer> + <button name="action_mailing_lists_merge" type="object" string="Merge" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="mailing_list_merge_action" model="ir.actions.act_window"> + <field name="name">Merge</field> + <field name="res_model">mailing.list.merge</field> + <field name="view_mode">form</field> + <field name="target">new</field> + <field name="binding_model_id" ref="model_mailing_list"/> + <field name="binding_view_types">list</field> + </record> +</odoo> diff --git a/addons/mass_mailing/wizard/mailing_mailing_schedule_date.py b/addons/mass_mailing/wizard/mailing_mailing_schedule_date.py new file mode 100644 index 00000000..1b24087b --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_mailing_schedule_date.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class MailingMailingScheduleDate(models.TransientModel): + _name = 'mailing.mailing.schedule.date' + _description = 'Mass Mailing Scheduling' + + schedule_date = fields.Datetime(string='Scheduled for') + mass_mailing_id = fields.Many2one('mailing.mailing', required=True, ondelete='cascade') + + @api.constrains('schedule_date') + def _check_schedule_date(self): + for scheduler in self: + if scheduler.schedule_date < fields.Datetime.now(): + raise ValidationError(_('Please select a date equal/or greater than the current date.')) + + def set_schedule_date(self): + self.mass_mailing_id.write({'schedule_date': self.schedule_date, 'state': 'in_queue'}) diff --git a/addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml b/addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml new file mode 100644 index 00000000..4e39c434 --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<odoo> + <record id="mailing_mailing_schedule_date_view_form" model="ir.ui.view"> + <field name="name">mailing.mailing.schedule.date.view.form</field> + <field name="model">mailing.mailing.schedule.date</field> + <field name="arch" type="xml"> + <form string="Take Future Schedule Date"> + <group> + <group> + <field name="schedule_date" string="Send on" required="1"/> + </group> + </group> + <footer> + <button string="Schedule" name="set_schedule_date" type="object" class="btn-primary"/> + <button string="Discard " class="btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="mailing_mailing_schedule_date_action" model="ir.actions.act_window"> + <field name="name">When do you want to send your mailing?</field> + <field name="res_model">mailing.mailing.schedule.date</field> + <field name="type">ir.actions.act_window</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> +</odoo> diff --git a/addons/mass_mailing/wizard/mailing_mailing_test.py b/addons/mass_mailing/wizard/mailing_mailing_test.py new file mode 100644 index 00000000..e3c1970a --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_mailing_test.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, tools + + +class TestMassMailing(models.TransientModel): + _name = 'mailing.mailing.test' + _description = 'Sample Mail Wizard' + + email_to = fields.Char(string='Recipients', required=True, + help='Comma-separated list of email addresses.', default=lambda self: self.env.user.email_formatted) + mass_mailing_id = fields.Many2one('mailing.mailing', string='Mailing', required=True, ondelete='cascade') + + def send_mail_test(self): + self.ensure_one() + ctx = dict(self.env.context) + ctx.pop('default_state', None) + self = self.with_context(ctx) + + mails_sudo = self.env['mail.mail'].sudo() + mailing = self.mass_mailing_id + test_emails = tools.email_split(self.email_to) + mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout') + + record = self.env[mailing.mailing_model_real].search([], limit=1) + body = mailing._prepend_preview(mailing.body_html, mailing.preview) + subject = mailing.subject + + # If there is atleast 1 record for the model used in this mailing, then we use this one to render the template + # Downside: Jinja syntax is only tested when there is atleast one record of the mailing's model + if record: + # Returns a proper error if there is a syntax error with jinja + body = self.env['mail.render.mixin']._render_template(body, mailing.mailing_model_real, record.ids, post_process=True)[record.id] + subject = self.env['mail.render.mixin']._render_template(subject, mailing.mailing_model_real, record.ids)[record.id] + + # Convert links in absolute URLs before the application of the shortener + body = self.env['mail.render.mixin']._replace_local_links(body) + body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True) + + for test_mail in test_emails: + mail_values = { + 'email_from': mailing.email_from, + 'reply_to': mailing.reply_to, + 'email_to': test_mail, + 'subject': subject, + 'body_html': mass_mail_layout._render({'body': body}, engine='ir.qweb', minimal_qcontext=True), + 'notification': True, + 'mailing_id': mailing.id, + 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], + 'auto_delete': True, + 'mail_server_id': mailing.mail_server_id.id, + } + mail = self.env['mail.mail'].sudo().create(mail_values) + mails_sudo |= mail + mails_sudo.send() + return True diff --git a/addons/mass_mailing/wizard/mailing_mailing_test_views.xml b/addons/mass_mailing/wizard/mailing_mailing_test_views.xml new file mode 100644 index 00000000..901f5c91 --- /dev/null +++ b/addons/mass_mailing/wizard/mailing_mailing_test_views.xml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<odoo> + + <record model="ir.ui.view" id="view_mail_mass_mailing_test_form"> + <field name="name">mailing.mailing.test.form</field> + <field name="model">mailing.mailing.test</field> + <field name="arch" type="xml"> + <form string="Send a Sample Mail"> + <p class="text-muted"> + Send a sample email for testing purpose to the address below. + </p> + <group> + <field name="email_to"/> + </group> + <footer> + <button string="Send Sample Mail" name="send_mail_test" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="action_mail_mass_mailing_test" model="ir.actions.act_window"> + <field name="name">Mailing Test</field> + <field name="res_model">mailing.mailing.test</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + +</odoo> |
