summaryrefslogtreecommitdiff
path: root/addons/mass_mailing/wizard
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mass_mailing/wizard
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mass_mailing/wizard')
-rw-r--r--addons/mass_mailing/wizard/__init__.py7
-rw-r--r--addons/mass_mailing/wizard/mail_compose_message.py92
-rw-r--r--addons/mass_mailing/wizard/mail_compose_message_views.xml19
-rw-r--r--addons/mass_mailing/wizard/mailing_list_merge.py45
-rw-r--r--addons/mass_mailing/wizard/mailing_list_merge_views.xml37
-rw-r--r--addons/mass_mailing/wizard/mailing_mailing_schedule_date.py22
-rw-r--r--addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml28
-rw-r--r--addons/mass_mailing/wizard/mailing_mailing_test.py57
-rw-r--r--addons/mass_mailing/wizard/mailing_mailing_test_views.xml30
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>