mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-21 06:02:01 +02:00
Initial commit: Mail packages
This commit is contained in:
commit
4e53507711
1948 changed files with 751201 additions and 0 deletions
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mail_compose_message
|
||||
from . import mailing_contact_import
|
||||
from . import mailing_contact_to_list
|
||||
from . import mailing_list_merge
|
||||
from . import mailing_mailing_test
|
||||
from . import mailing_mailing_schedule_date
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
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', ondelete='set null')
|
||||
mass_mailing_name = fields.Char(string='Mass Mailing Name', help='If set, a mass mailing will be created so that you can track its results in the Email Marketing app.')
|
||||
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. """
|
||||
now = fields.Datetime.now()
|
||||
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_count([('model', '=', self.model), ('is_mail_thread', '=', True)]):
|
||||
mass_mailing = self.mass_mailing_id
|
||||
if not mass_mailing:
|
||||
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': self.reply_to_mode,
|
||||
'reply_to': self.reply_to if self.reply_to_mode == 'new' else False,
|
||||
'sent_date': now,
|
||||
'body_html': self.body,
|
||||
'mailing_model_id': self.env['ir.model']._get(self.model).id,
|
||||
'mailing_domain': self.active_domain,
|
||||
'attachment_ids': [(6, 0, self.attachment_ids.ids)],
|
||||
})
|
||||
self.mass_mailing_id = mass_mailing.id
|
||||
|
||||
recipients_info = self._process_recipient_values(res)
|
||||
for res_id in res_ids:
|
||||
mail_values = res[res_id]
|
||||
if mail_values.get('body_html'):
|
||||
body = self.env['ir.qweb']._render('mass_mailing.mass_mailing_mail_layout',
|
||||
{'body': mail_values['body_html']},
|
||||
minimal_qcontext=True, raise_if_not_found=False)
|
||||
if body:
|
||||
mail_values['body_html'] = body
|
||||
|
||||
trace_vals = {
|
||||
'message_id': mail_values['message_id'],
|
||||
'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': recipients_info[res_id]['mail_to'][0] if recipients_info[res_id]['mail_to'] else '',
|
||||
}
|
||||
# propagate failed states to trace when still-born
|
||||
if mail_values.get('state') == 'cancel':
|
||||
trace_vals['trace_status'] = 'cancel'
|
||||
elif mail_values.get('state') == 'exception':
|
||||
trace_vals['trace_status'] = 'error'
|
||||
if mail_values.get('failure_type'):
|
||||
trace_vals['failure_type'] = mail_values['failure_type']
|
||||
|
||||
mail_values.update({
|
||||
'mailing_id': mass_mailing.id,
|
||||
'mailing_trace_ids': [(0, 0, trace_vals)],
|
||||
# email-mode: keep original message for routing
|
||||
'is_notification': mass_mailing.reply_to_mode == 'update',
|
||||
'auto_delete': not mass_mailing.keep_archives,
|
||||
})
|
||||
return res
|
||||
|
||||
def _get_done_emails(self, mail_values_dict):
|
||||
seen_list = super(MailComposeMessage, self)._get_done_emails(mail_values_dict)
|
||||
if self.mass_mailing_id:
|
||||
seen_list += self.mass_mailing_id._get_seen_list()
|
||||
return seen_list
|
||||
|
||||
def _get_optout_emails(self, mail_values_dict):
|
||||
opt_out_list = super(MailComposeMessage, self)._get_optout_emails(mail_values_dict)
|
||||
if self.mass_mailing_id:
|
||||
opt_out_list += self.mass_mailing_id._get_opt_out_list()
|
||||
return opt_out_list
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?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>
|
||||
<xpath expr="//button[@name='action_send_mail'][not(hasclass('o_mail_send'))]" position="attributes">
|
||||
<!-- 'Log' button -->
|
||||
<attribute name="attrs">
|
||||
{'invisible': [
|
||||
'|',
|
||||
('is_log', '=', False),
|
||||
'&',
|
||||
('mass_mailing_name', '!=', ''),
|
||||
('mass_mailing_name', '!=', False)
|
||||
]}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[hasclass('o_mail_send')]" position="attributes">
|
||||
<!-- 'Send' button -->
|
||||
<attribute name="attrs">
|
||||
{'invisible': [
|
||||
'|',
|
||||
('is_log', '=', True),
|
||||
'&',
|
||||
('mass_mailing_name', '!=', ''),
|
||||
('mass_mailing_name', '!=', False)
|
||||
]}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_send_mail']" position="after">
|
||||
<button string="Send Mass Mailing" name="action_send_mail" type="object" class="btn-primary o_mail_send"
|
||||
attrs="{'invisible': ['|', ('mass_mailing_name', '==', ''), ('mass_mailing_name', '==', False)]}" data-hotkey="q"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, tools, _
|
||||
from odoo.tools.misc import clean_context
|
||||
|
||||
|
||||
class MailingContactImport(models.TransientModel):
|
||||
_name = 'mailing.contact.import'
|
||||
_description = 'Mailing Contact Import'
|
||||
|
||||
mailing_list_ids = fields.Many2many('mailing.list', string='Lists')
|
||||
contact_list = fields.Text('Contact List', help='Contact list that will be imported, one contact per line')
|
||||
|
||||
def action_import(self):
|
||||
"""Import each lines of "contact_list" as a new contact."""
|
||||
self.ensure_one()
|
||||
contacts = tools.email_split_tuples(', '.join((self.contact_list or '').splitlines()))
|
||||
if not contacts:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': _('No valid email address found.'),
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
'sticky': False,
|
||||
'type': 'warning',
|
||||
}
|
||||
}
|
||||
|
||||
if len(contacts) > 5000:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': _('You have to much emails, please upload a file.'),
|
||||
'type': 'warning',
|
||||
'sticky': False,
|
||||
'next': self.action_open_base_import(),
|
||||
}
|
||||
}
|
||||
|
||||
all_emails = list({values[1].lower() for values in contacts})
|
||||
|
||||
existing_contacts = self.env['mailing.contact'].search([
|
||||
('email_normalized', 'in', all_emails),
|
||||
('list_ids', 'in', self.mailing_list_ids.ids),
|
||||
])
|
||||
existing_contacts = {
|
||||
contact.email_normalized: contact
|
||||
for contact in existing_contacts
|
||||
}
|
||||
|
||||
# Remove duplicated record, keep only the first non-empty name for each email address
|
||||
unique_contacts = {}
|
||||
for name, email in contacts:
|
||||
email = email.lower()
|
||||
if unique_contacts.get(email, {}).get('name'):
|
||||
continue
|
||||
|
||||
if email in existing_contacts and not self.mailing_list_ids < existing_contacts[email].list_ids:
|
||||
existing_contacts[email].list_ids |= self.mailing_list_ids
|
||||
if email not in existing_contacts:
|
||||
unique_contacts[email] = {
|
||||
'name': name,
|
||||
'list_ids': self.mailing_list_ids.ids,
|
||||
}
|
||||
|
||||
if not unique_contacts:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': _('No contacts were imported. All email addresses are already in the mailing list.'),
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
'sticky': False,
|
||||
'type': 'warning',
|
||||
}
|
||||
}
|
||||
|
||||
new_contacts = self.env['mailing.contact'].with_context(clean_context(self.env.context)).create([
|
||||
{
|
||||
'email': email,
|
||||
**values,
|
||||
}
|
||||
for email, values in unique_contacts.items()
|
||||
])
|
||||
|
||||
ignored = len(contacts) - len(unique_contacts)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': (
|
||||
_('%i Contacts have been imported.', len(unique_contacts))
|
||||
+ (_(' %i duplicates have been ignored.', ignored) if ignored else '')
|
||||
),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {
|
||||
'context': self.env.context,
|
||||
'domain': [('id', 'in', new_contacts.ids)],
|
||||
'name': _('New contacts imported'),
|
||||
'res_model': 'mailing.contact',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'list',
|
||||
'views': [[False, 'list'], [False, 'form']],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def action_open_base_import(self):
|
||||
"""Open the base import wizard to import mailing list contacts with a xlsx file."""
|
||||
self.ensure_one()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'import',
|
||||
'name': _('Import Mailing Contacts'),
|
||||
'params': {
|
||||
'context': self.env.context,
|
||||
'model': 'mailing.contact',
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="mailing_contact_import_view_form" model="ir.ui.view">
|
||||
<field name="name">mailing.contact.import.view.form</field>
|
||||
<field name="model">mailing.contact.import</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import Mailing Contacts">
|
||||
<group>
|
||||
<field name="mailing_list_ids" string="Import contacts in"
|
||||
widget="many2many_tags" placeholder="Select mailing lists"
|
||||
options="{'no_create': True}"/>
|
||||
</group>
|
||||
<p>
|
||||
Write or paste email addresses in the field below.
|
||||
Each line will be imported as a mailing list contact.
|
||||
</p>
|
||||
<label for="contact_list" class="mb-2">Contact List</label>
|
||||
<field name="contact_list"
|
||||
nolabel="1" default_focus="1"
|
||||
placeholder='"Damien Roberts" <d.roberts@example.com> "Rick Sanchez" <rick_sanchez@example.com> victor_hugo@example.com'/>
|
||||
<p class="text-muted mb-0">
|
||||
Want to import country, company name and more?
|
||||
<button type="object" name="action_open_base_import"
|
||||
class="fw-normal px-0 btn btn-link">
|
||||
Upload a file
|
||||
</button>
|
||||
</p>
|
||||
<footer>
|
||||
<button string="Import" type="object" name="action_import"
|
||||
class="btn-primary" data-hotkey="i"/>
|
||||
<button string="Discard" class="btn-secondary"
|
||||
special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mailing_contact_import_action" model="ir.actions.act_window">
|
||||
<field name="name">Import Mailing Contacts</field>
|
||||
<field name="res_model">mailing.contact.import</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
|
||||
class MailingContactToList(models.TransientModel):
|
||||
_name = "mailing.contact.to.list"
|
||||
_description = "Add Contacts to Mailing List"
|
||||
|
||||
contact_ids = fields.Many2many('mailing.contact', string='Contacts')
|
||||
mailing_list_id = fields.Many2one('mailing.list', string='Mailing List', required=True)
|
||||
|
||||
def action_add_contacts(self):
|
||||
""" Simply add contacts to the mailing list and close wizard. """
|
||||
return self._add_contacts_to_mailing_list({'type': 'ir.actions.act_window_close'})
|
||||
|
||||
def action_add_contacts_and_send_mailing(self):
|
||||
""" Add contacts to the mailing list and redirect to a new mailing on
|
||||
this list. """
|
||||
self.ensure_one()
|
||||
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing.mailing_mailing_action_mail")
|
||||
action['views'] = [[False, "form"]]
|
||||
action['target'] = 'current'
|
||||
action['context'] = {
|
||||
'default_contact_list_ids': [self.mailing_list_id.id]
|
||||
}
|
||||
return self._add_contacts_to_mailing_list(action)
|
||||
|
||||
def _add_contacts_to_mailing_list(self, action):
|
||||
self.ensure_one()
|
||||
|
||||
previous_count = len(self.mailing_list_id.contact_ids)
|
||||
self.mailing_list_id.write({
|
||||
'contact_ids': [
|
||||
(4, contact.id)
|
||||
for contact in self.contact_ids
|
||||
if contact not in self.mailing_list_id.contact_ids]
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'message': _("%s Mailing Contacts have been added. ",
|
||||
len(self.mailing_list_id.contact_ids) - previous_count
|
||||
),
|
||||
'sticky': False,
|
||||
'next': action,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="mailing_contact_to_list_view_form" model="ir.ui.view" >
|
||||
<field name="name">mailing.contact.to.list.view.form</field>
|
||||
<field name="model">mailing.contact.to.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Send a Sample Mail">
|
||||
<group>
|
||||
<field name="mailing_list_id" options="{'no_create_edit': True, 'no_open': True}"/>
|
||||
<field name="contact_ids" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Add" name="action_add_contacts" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Add and Send Mailing" name="action_add_contacts_and_send_mailing" type="object" class="btn-primary" data-hotkey="w"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mailing_contact_to_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Add Selected Contacts to a Mailing List</field>
|
||||
<field name="res_model">mailing.contact.to.list</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -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
|
||||
|
|
@ -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_count" string="Number of Recipients"/>
|
||||
</tree>
|
||||
</field>
|
||||
<footer>
|
||||
<button name="action_mailing_lists_merge" type="object" string="Merge" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MailingMailingScheduleDate(models.TransientModel):
|
||||
_name = "mailing.mailing.schedule.date"
|
||||
_description = "schedule a mailing"
|
||||
|
||||
schedule_date = fields.Datetime(string='Scheduled for')
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', required=True)
|
||||
|
||||
def action_schedule_date(self):
|
||||
self.mass_mailing_id.write({'schedule_type': 'scheduled', 'schedule_date': self.schedule_date})
|
||||
self.mass_mailing_id.action_put_in_queue()
|
||||
|
|
@ -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="action_schedule_date" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Discard " class="btn-secondary" special="cancel" data-hotkey="z" />
|
||||
</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>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import _, fields, models, tools
|
||||
|
||||
|
||||
class TestMassMailing(models.TransientModel):
|
||||
_name = 'mailing.mailing.test'
|
||||
_description = 'Sample Mail Wizard'
|
||||
|
||||
email_to = fields.Text(string='Recipients', required=True,
|
||||
help='Carriage-return-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()
|
||||
valid_emails = []
|
||||
invalid_candidates = []
|
||||
for candidate in self.email_to.splitlines():
|
||||
test_email = tools.email_split(candidate)
|
||||
if test_email:
|
||||
valid_emails.append(test_email[0])
|
||||
else:
|
||||
invalid_candidates.append(candidate)
|
||||
|
||||
mailing = self.mass_mailing_id
|
||||
record = self.env[mailing.mailing_model_real].search([], limit=1)
|
||||
|
||||
# If there is atleast 1 record for the model used in this mailing, then we use this one to render the template
|
||||
# Downside: Qweb 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 Qweb
|
||||
body = mailing.with_context(preserve_comments=True)._render_field('body_html', record.ids, post_process=True)[record.id]
|
||||
preview = mailing._render_field('preview', record.ids, post_process=True)[record.id]
|
||||
full_body = mailing._prepend_preview(Markup(body), preview)
|
||||
subject = mailing._render_field('subject', record.ids)[record.id]
|
||||
else:
|
||||
full_body = mailing._prepend_preview(mailing.body_html, mailing.preview)
|
||||
subject = mailing.subject
|
||||
|
||||
# Convert links in absolute URLs before the application of the shortener
|
||||
full_body = self.env['mail.render.mixin']._replace_local_links(full_body)
|
||||
|
||||
for valid_email in valid_emails:
|
||||
mail_values = {
|
||||
'email_from': mailing.email_from,
|
||||
'reply_to': mailing.reply_to,
|
||||
'email_to': valid_email,
|
||||
'subject': subject,
|
||||
'body_html': self.env['ir.qweb']._render('mass_mailing.mass_mailing_mail_layout', {'body': full_body}, minimal_qcontext=True),
|
||||
'is_notification': True,
|
||||
'mailing_id': mailing.id,
|
||||
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
|
||||
'auto_delete': False, # they are manually deleted after notifying the document
|
||||
'mail_server_id': mailing.mail_server_id.id,
|
||||
}
|
||||
mail = self.env['mail.mail'].sudo().create(mail_values)
|
||||
mails_sudo |= mail
|
||||
mails_sudo.send()
|
||||
|
||||
notification_messages = []
|
||||
if invalid_candidates:
|
||||
notification_messages.append(
|
||||
_('Mailing addresses incorrect: %s', ', '.join(invalid_candidates)))
|
||||
|
||||
for mail_sudo in mails_sudo:
|
||||
if mail_sudo.state == 'sent':
|
||||
notification_messages.append(
|
||||
_('Test mailing successfully sent to %s', mail_sudo.email_to))
|
||||
elif mail_sudo.state == 'exception':
|
||||
notification_messages.append(
|
||||
_('Test mailing could not be sent to %s:<br>%s',
|
||||
mail_sudo.email_to,
|
||||
mail_sudo.failure_reason)
|
||||
)
|
||||
|
||||
# manually delete the emails since we passed 'auto_delete: False'
|
||||
mails_sudo.unlink()
|
||||
|
||||
if notification_messages:
|
||||
self.mass_mailing_id._message_log(body='<ul>%s</ul>' % ''.join(
|
||||
['<li>%s</li>' % notification_message for notification_message in notification_messages]
|
||||
))
|
||||
|
||||
return True
|
||||
|
|
@ -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 mailing for testing purpose to the address below.
|
||||
</p>
|
||||
<group>
|
||||
<field name="email_to" placeholder="email1@example.com email2@example.com"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Send" name="send_mail_test" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue