[MOV] mass_mailing: reorganize module according to guidelines

PURPOSE

Mass mailing is currently a bit messy. As SMS will be added as a way to notify
people in mass through SMS let us take this opportunity to somehow clean this
application: organization, light code cleaning, model renaming.

SPECIFICATIONS

Guidelines: re-organize model files and split them according to main
models. Also reorganize views.

Done in this commit

  * split big python file by main models;
  * split views according to python files;
  * move statistics report to /report;
  * merge some exploded view files (assets, snippets, application menus);

LINKS

Task ID 2037906
Preparing task ID 1997464 (SMS addition in mass mailing)
PR #34938
This commit is contained in:
Thibault Delavallée
2019-07-17 15:39:06 +00:00
parent 69276607cc
commit b3e7fd040b
21 changed files with 837 additions and 820 deletions
+1
View File
@@ -3,4 +3,5 @@
from . import controllers
from . import models
from . import report
from . import wizard
+7 -8
View File
@@ -20,26 +20,25 @@
],
'data': [
'security/mass_mailing_security.xml',
'security/ir.model.access.csv',
'data/mail_data.xml',
'data/mass_mailing_data.xml',
'wizard/mail_compose_message_views.xml',
'wizard/mass_mailing_list_merge_views.xml',
'wizard/test_mailing_views.xml',
'wizard/mass_mailing_schedule_date_views.xml',
'views/mass_mailing_views_menus.xml',
'views/mass_mailing_report_views.xml',
'views/mass_mailing_stats_views.xml',
'views/link_tracker_views.xml',
'views/mass_mailing_template.xml',
'views/mass_mailing_contact_views.xml',
'views/mass_mailing_list_views.xml',
'views/mass_mailing_views.xml',
'views/res_config_settings_views.xml',
'security/ir.model.access.csv',
'views/mass_mailing_layout.xml',
'views/editor_field_html.xml',
'report/mass_mailing_stats_report_views.xml',
'views/assets.xml',
'views/mass_mailing_templates_portal.xml',
'views/themes_templates.xml',
'views/snippets_themes.xml',
'views/snippets_themes_options.xml',
'views/mass_mailing_blacklist_views.xml',
'views/unsubscribe_templates.xml',
],
'demo': [
'data/mass_mailing_demo.xml',
@@ -1,22 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="mass_mailing_mail_style">
<style>
@media screen and (max-width: 768px) {
.o_mail_col_mv {
display: block !important;
width: auto !important;
}
.o_mail_table_styles {
width: 100% !important;
}
.o_mail_col_container {
margin: 0px 0px 10px 0px !important;
}
}
</style>
</template>
<odoo><data noupdate="1">
<!-- Reference: https://litmus.com/community/learning/24-how-to-code-a-responsive-email-from-scratch -->
<template id="mass_mailing_mail_layout">
&lt;!DOCTYPE html&gt;
@@ -34,4 +17,4 @@
</body>
</html>
</template>
</odoo>
</data></odoo>
+3 -2
View File
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import link_tracker
from . import mass_mailing_contact
from . import mass_mailing_list
from . import mass_mailing
from . import mass_mailing_stats
from . import mail_mail
from . import mail_template
from . import mail_thread
from . import res_config_settings
from . import mass_mailing_report
from . import link_tracker
+1 -243
View File
@@ -43,251 +43,9 @@ class MassMailingTag(models.Model):
color = fields.Integer(string='Color Index')
_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists !"),
('name_uniq', 'unique (name)', "Tag name already exists !"),
]
class MassMailingContactListRel(models.Model):
""" Intermediate model between mass mailing list and mass mailing contact
Indicates if a contact is opted out for a particular list
"""
_name = 'mail.mass_mailing.list_contact_rel'
_description = 'Mass Mailing Subscription Information'
_table = 'mail_mass_mailing_contact_list_rel'
_rec_name = 'contact_id'
contact_id = fields.Many2one('mail.mass_mailing.contact', string='Contact', ondelete='cascade', required=True)
list_id = fields.Many2one('mail.mass_mailing.list', string='Mailing List', ondelete='cascade', required=True)
opt_out = fields.Boolean(string='Opt Out',
help='The contact has chosen not to receive mails anymore from this list', default=False)
unsubscription_date = fields.Datetime(string='Unsubscription Date')
contact_count = fields.Integer(related='list_id.contact_nbr', store=False, readonly=False)
message_bounce = fields.Integer(related='contact_id.message_bounce', store=False, readonly=False)
is_blacklisted = fields.Boolean(related='contact_id.is_blacklisted', store=False, readonly=False)
_sql_constraints = [
('unique_contact_list', 'unique (contact_id, list_id)',
'A contact cannot be subscribed multiple times to the same list!')
]
@api.model
def create(self, vals):
if 'opt_out' in vals:
vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
return super(MassMailingContactListRel, self).create(vals)
def write(self, vals):
if 'opt_out' in vals:
vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
return super(MassMailingContactListRel, self).write(vals)
def action_open_mailing_list_contact(self):
"""TODO DBE : To remove - Deprecated"""
contact_id = self.contact_id
action = {
'name': _(contact_id.name),
'type': 'ir.actions.act_window',
'res_model': 'mail.mass_mailing.contact',
'view_mode': 'form',
'target': 'current',
'res_id': contact_id.id
}
return action
class MassMailingList(models.Model):
"""Model of a contact list. """
_name = 'mail.mass_mailing.list'
_order = 'name'
_description = 'Mailing List'
name = fields.Char(string='Mailing List', required=True)
active = fields.Boolean(default=True)
contact_nbr = fields.Integer(compute="_compute_contact_nbr", string='Number of Contacts')
contact_ids = fields.Many2many(
'mail.mass_mailing.contact', 'mail_mass_mailing_contact_list_rel', 'list_id', 'contact_id',
string='Mailing Lists')
subscription_contact_ids = fields.One2many('mail.mass_mailing.list_contact_rel', 'list_id',
string='Subscription Information')
is_public = fields.Boolean(default=True, help="The mailing list can be accessible by recipient in the unsubscription"
" page to allows him to update his subscription preferences.")
# Compute number of contacts non opt-out, non blacklisted and valid email recipient for a mailing list
def _compute_contact_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
left join mail_blacklist bl on c.email_normalized = bl.email and bl.active
where
list_id in %s
AND COALESCE(r.opt_out,FALSE) = FALSE
AND c.email_normalized IS NOT NULL
AND bl.id IS NULL
group by
list_id
''', (tuple(self.ids), ))
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_nbr = data.get(mailing_list.id, 0)
def name_get(self):
return [(list.id, "%s (%s)" % (list.name, list.contact_nbr)) for list in self]
def action_merge(self, src_lists, archive):
"""
Insert all the contact from the mailing lists 'src_lists' to the
mailing list in 'self'. Possibility to archive the mailing lists
'src_lists' after the merge except the destination mailing list 'self'.
"""
# Explation of the SQL query with an example. There are the following lists
# A (id=4): yti@odoo.com; yti@example.com
# B (id=5): yti@odoo.com; yti@openerp.com
# C (id=6): nothing
# To merge the mailing lists A and B into C, we build the view st that looks
# like this with our example:
#
# contact_id | email | row_number | list_id |
# ------------+---------------------------+------------------------
# 4 | yti@odoo.com | 1 | 4 |
# 6 | yti@odoo.com | 2 | 5 |
# 5 | yti@example.com | 1 | 4 |
# 7 | yti@openerp.com | 1 | 5 |
#
# The row_column is kind of an occurence counter for the email address.
# Then we create the Many2many relation between the destination list and the contacts
# while avoiding to insert an existing email address (if the destination is in the source
# for example)
self.ensure_one()
# Put destination is sources lists if not already the case
src_lists |= self
self.env.cr.execute("""
INSERT INTO mail_mass_mailing_contact_list_rel (contact_id, list_id)
SELECT st.contact_id AS contact_id, %s AS list_id
FROM
(
SELECT
contact.id AS contact_id,
contact.email AS email,
mailing_list.id AS list_id,
row_number() OVER (PARTITION BY email ORDER BY email) AS rn
FROM
mail_mass_mailing_contact contact,
mail_mass_mailing_contact_list_rel contact_list_rel,
mail_mass_mailing_list mailing_list
WHERE contact.id=contact_list_rel.contact_id
AND COALESCE(contact_list_rel.opt_out,FALSE) = FALSE
AND contact.email_normalized NOT IN (select email from mail_blacklist where active = TRUE)
AND mailing_list.id=contact_list_rel.list_id
AND mailing_list.id IN %s
AND NOT EXISTS
(
SELECT 1
FROM
mail_mass_mailing_contact contact2,
mail_mass_mailing_contact_list_rel contact_list_rel2
WHERE contact2.email = contact.email
AND contact_list_rel2.contact_id = contact2.id
AND contact_list_rel2.list_id = %s
)
) st
WHERE st.rn = 1;""", (self.id, tuple(src_lists.ids), self.id))
self.invalidate_cache()
if archive:
(src_lists - self).write({'active': False})
def close_dialog(self):
return {'type': 'ir.actions.act_window_close'}
class MassMailingContact(models.Model):
"""Model of a contact. This model is different from the partner model
because it holds only some basic information: name, email. The purpose is to
be able to deal with large contact list to email without bloating the partner
base."""
_name = 'mail.mass_mailing.contact'
_inherit = ['mail.thread.blacklist']
_description = 'Mass Mailing Contact'
_order = 'email'
_rec_name = 'email'
name = fields.Char()
company_name = fields.Char(string='Company Name')
title_id = fields.Many2one('res.partner.title', string='Title')
email = fields.Char(required=True)
is_email_valid = fields.Boolean(compute='_compute_is_email_valid', store=True)
list_ids = fields.Many2many(
'mail.mass_mailing.list', 'mail_mass_mailing_contact_list_rel',
'contact_id', 'list_id', string='Mailing Lists')
subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel',
'contact_id', string='Subscription Information')
country_id = fields.Many2one('res.country', string='Country')
tag_ids = fields.Many2many('res.partner.category', string='Tags')
opt_out = fields.Boolean('Opt Out', compute='_compute_opt_out', search='_search_opt_out',
help='Opt out flag for a specific mailing list.'
'This field should not be used in a view without a unique and active mailing list context.')
@api.depends('email')
def _compute_is_email_valid(self):
for record in self:
normalized = tools.email_normalize(record.email)
record.is_email_valid = normalized if not normalized else True
@api.model
def _search_opt_out(self, operator, value):
# Assumes operator is '=' or '!=' and value is True or False
if operator != '=':
if operator == '!=' and isinstance(value, bool):
value = not value
else:
raise NotImplementedError()
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
contacts = self.env['mail.mass_mailing.list_contact_rel'].search([('list_id', '=', active_list_id)])
return [('id', 'in', [record.contact_id.id for record in contacts if record.opt_out == value])]
else:
raise UserError('Search opt out cannot be executed without a unique and valid active mailing list context.')
@api.depends('subscription_list_ids')
def _compute_opt_out(self):
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
for record in self:
active_subscription_list = record.subscription_list_ids.filtered(lambda l: l.list_id.id == active_list_id)
record.opt_out = active_subscription_list.opt_out
else:
for record in self:
record.opt_out = False
def get_name_email(self, name):
name, email = self.env['res.partner']._parse_partner_name(name)
if name and not email:
email = name
if email and not name:
name = email
return name, email
@api.model
def name_create(self, name):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email})
return contact.name_get()[0]
@api.model
def add_to_list(self, name, list_id):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email, 'list_ids': [(4, list_id)]})
return contact.name_get()[0]
def _message_get_default_recipients(self):
return {r.id: {
'partner_ids': [],
'email_to': r.email_normalized,
'email_cc': False}
for r in self}
class MassMailingStage(models.Model):
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError
class MassMailingContactListRel(models.Model):
""" Intermediate model between mass mailing list and mass mailing contact
Indicates if a contact is opted out for a particular list
"""
_name = 'mail.mass_mailing.list_contact_rel'
_description = 'Mass Mailing Subscription Information'
_table = 'mail_mass_mailing_contact_list_rel'
_rec_name = 'contact_id'
contact_id = fields.Many2one('mail.mass_mailing.contact', string='Contact', ondelete='cascade', required=True)
list_id = fields.Many2one('mail.mass_mailing.list', string='Mailing List', ondelete='cascade', required=True)
opt_out = fields.Boolean(string='Opt Out',
help='The contact has chosen not to receive mails anymore from this list', default=False)
unsubscription_date = fields.Datetime(string='Unsubscription Date')
contact_count = fields.Integer(related='list_id.contact_nbr', store=False, readonly=False)
message_bounce = fields.Integer(related='contact_id.message_bounce', store=False, readonly=False)
is_blacklisted = fields.Boolean(related='contact_id.is_blacklisted', store=False, readonly=False)
_sql_constraints = [
('unique_contact_list', 'unique (contact_id, list_id)',
'A contact cannot be subscribed multiple times to the same list!')
]
@api.model
def create(self, vals):
if 'opt_out' in vals:
vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
return super(MassMailingContactListRel, self).create(vals)
def write(self, vals):
if 'opt_out' in vals:
vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
return super(MassMailingContactListRel, self).write(vals)
def action_open_mailing_list_contact(self):
"""TODO DBE : To remove - Deprecated"""
contact_id = self.contact_id
action = {
'name': _(contact_id.name),
'type': 'ir.actions.act_window',
'res_model': 'mail.mass_mailing.contact',
'view_mode': 'form',
'target': 'current',
'res_id': contact_id.id
}
return action
class MassMailingContact(models.Model):
"""Model of a contact. This model is different from the partner model
because it holds only some basic information: name, email. The purpose is to
be able to deal with large contact list to email without bloating the partner
base."""
_name = 'mail.mass_mailing.contact'
_inherit = ['mail.thread.blacklist']
_description = 'Mass Mailing Contact'
_order = 'email'
_rec_name = 'email'
name = fields.Char()
company_name = fields.Char(string='Company Name')
title_id = fields.Many2one('res.partner.title', string='Title')
email = fields.Char(required=True)
is_email_valid = fields.Boolean(compute='_compute_is_email_valid', store=True)
list_ids = fields.Many2many(
'mail.mass_mailing.list', 'mail_mass_mailing_contact_list_rel',
'contact_id', 'list_id', string='Mailing Lists')
subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel',
'contact_id', string='Subscription Information')
country_id = fields.Many2one('res.country', string='Country')
tag_ids = fields.Many2many('res.partner.category', string='Tags')
opt_out = fields.Boolean('Opt Out', compute='_compute_opt_out', search='_search_opt_out',
help='Opt out flag for a specific mailing list.'
'This field should not be used in a view without a unique and active mailing list context.')
@api.depends('email')
def _compute_is_email_valid(self):
for record in self:
normalized = tools.email_normalize(record.email)
record.is_email_valid = normalized if not normalized else True
@api.model
def _search_opt_out(self, operator, value):
# Assumes operator is '=' or '!=' and value is True or False
if operator != '=':
if operator == '!=' and isinstance(value, bool):
value = not value
else:
raise NotImplementedError()
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
contacts = self.env['mail.mass_mailing.list_contact_rel'].search([('list_id', '=', active_list_id)])
return [('id', 'in', [record.contact_id.id for record in contacts if record.opt_out == value])]
else:
raise UserError('Search opt out cannot be executed without a unique and valid active mailing list context.')
@api.depends('subscription_list_ids')
def _compute_opt_out(self):
if 'default_list_ids' in self._context and isinstance(self._context['default_list_ids'], (list, tuple)) and len(self._context['default_list_ids']) == 1:
[active_list_id] = self._context['default_list_ids']
for record in self:
active_subscription_list = record.subscription_list_ids.filtered(lambda l: l.list_id.id == active_list_id)
record.opt_out = active_subscription_list.opt_out
else:
for record in self:
record.opt_out = False
def get_name_email(self, name):
name, email = self.env['res.partner']._parse_partner_name(name)
if name and not email:
email = name
if email and not name:
name = email
return name, email
@api.model
def name_create(self, name):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email})
return contact.name_get()[0]
@api.model
def add_to_list(self, name, list_id):
name, email = self.get_name_email(name)
contact = self.create({'name': name, 'email': email, 'list_ids': [(4, list_id)]})
return contact.name_get()[0]
def _message_get_default_recipients(self):
return {r.id: {
'partner_ids': [],
'email_to': r.email_normalized,
'email_cc': False}
for r in self
}
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class MassMailingList(models.Model):
"""Model of a contact list. """
_name = 'mail.mass_mailing.list'
_order = 'name'
_description = 'Mailing List'
name = fields.Char(string='Mailing List', required=True)
active = fields.Boolean(default=True)
contact_nbr = fields.Integer(compute="_compute_contact_nbr", string='Number of Contacts')
contact_ids = fields.Many2many(
'mail.mass_mailing.contact', 'mail_mass_mailing_contact_list_rel', 'list_id', 'contact_id',
string='Mailing Lists')
subscription_contact_ids = fields.One2many('mail.mass_mailing.list_contact_rel', 'list_id',
string='Subscription Information')
is_public = fields.Boolean(default=True, help="The mailing list can be accessible by recipient in the unsubscription"
" page to allows him to update his subscription preferences.")
# Compute number of contacts non opt-out, non blacklisted and valid email recipient for a mailing list
def _compute_contact_nbr(self):
self.env.cr.execute('''
select
list_id, count(*)
from
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
left join mail_blacklist bl on c.email_normalized = bl.email and bl.active
where
list_id in %s
AND COALESCE(r.opt_out,FALSE) = FALSE
AND c.email_normalized IS NOT NULL
AND bl.id IS NULL
group by
list_id
''', (tuple(self.ids), ))
data = dict(self.env.cr.fetchall())
for mailing_list in self:
mailing_list.contact_nbr = data.get(mailing_list.id, 0)
def name_get(self):
return [(list.id, "%s (%s)" % (list.name, list.contact_nbr)) for list in self]
def action_merge(self, src_lists, archive):
"""
Insert all the contact from the mailing lists 'src_lists' to the
mailing list in 'self'. Possibility to archive the mailing lists
'src_lists' after the merge except the destination mailing list 'self'.
"""
# Explation of the SQL query with an example. There are the following lists
# A (id=4): yti@odoo.com; yti@example.com
# B (id=5): yti@odoo.com; yti@openerp.com
# C (id=6): nothing
# To merge the mailing lists A and B into C, we build the view st that looks
# like this with our example:
#
# contact_id | email | row_number | list_id |
# ------------+---------------------------+------------------------
# 4 | yti@odoo.com | 1 | 4 |
# 6 | yti@odoo.com | 2 | 5 |
# 5 | yti@example.com | 1 | 4 |
# 7 | yti@openerp.com | 1 | 5 |
#
# The row_column is kind of an occurence counter for the email address.
# Then we create the Many2many relation between the destination list and the contacts
# while avoiding to insert an existing email address (if the destination is in the source
# for example)
self.ensure_one()
# Put destination is sources lists if not already the case
src_lists |= self
self.env.cr.execute("""
INSERT INTO mail_mass_mailing_contact_list_rel (contact_id, list_id)
SELECT st.contact_id AS contact_id, %s AS list_id
FROM
(
SELECT
contact.id AS contact_id,
contact.email AS email,
mailing_list.id AS list_id,
row_number() OVER (PARTITION BY email ORDER BY email) AS rn
FROM
mail_mass_mailing_contact contact,
mail_mass_mailing_contact_list_rel contact_list_rel,
mail_mass_mailing_list mailing_list
WHERE contact.id=contact_list_rel.contact_id
AND COALESCE(contact_list_rel.opt_out,FALSE) = FALSE
AND contact.email_normalized NOT IN (select email from mail_blacklist where active = TRUE)
AND mailing_list.id=contact_list_rel.list_id
AND mailing_list.id IN %s
AND NOT EXISTS
(
SELECT 1
FROM
mail_mass_mailing_contact contact2,
mail_mass_mailing_contact_list_rel contact_list_rel2
WHERE contact2.email = contact.email
AND contact_list_rel2.contact_id = contact2.id
AND contact_list_rel2.list_id = %s
)
) st
WHERE st.rn = 1;""", (self.id, tuple(src_lists.ids), self.id))
self.invalidate_cache()
if archive:
(src_lists - self).write({'active': False})
def close_dialog(self):
return {'type': 'ir.actions.act_window_close'}
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mass_mailing_stats_report
+78
View File
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="mass_mailing assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.scss"/>
<link rel="stylesheet" href="/mass_mailing/static/src/css/email_template.css"/>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_widget.js"></script>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_record.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_renderer.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_view.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/unsubscribe.js"></script>
</xpath>
</template>
<template id="assets_mail_themes">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/themes/theme_basic.scss"/>
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/themes/theme_default.scss"/>
<t t-call="mass_mailing.mass_mailing_mail_style"/>
</template>
<template id="assets_mail_themes_edition"> <!-- maybe to remove and convert into a field dumy with attr invisible if the template is not selected -->
<t t-call="web._assets_helpers">
<link rel="stylesheet" type="text/scss" href="/web_editor/static/src/scss/wysiwyg_variables.scss"/>
</t>
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.ui.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/webclient.scss"/>
</template>
<template id="iframe_css_assets_edit">
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<t t-call-assets="web_editor.assets_wysiwyg" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes_edition" t-js="false"/>
</template>
<template id="iframe_css_assets_readonly">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/css/basic_theme_readonly.css"/>
</template>
<template id="qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="." position="inside">
<script type="text/javascript">
odoo.define('mass_mailing.FieldHtml.test', function (require) {
'use strict';
var MassMailingFieldHtml = require('mass_mailing.FieldHtml');
MassMailingFieldHtml.include({jsLibs: []});
});
</script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_snippets.js"/>
<script type="text/javascript" src="/mass_mailing/static/tests/mass_mailing_html_tests.js"/>
</xpath>
</template>
<template id="mass_mailing_mail_style">
<style>
@media screen and (max-width: 768px) {
.o_mail_col_mv {
display: block !important;
width: auto !important;
}
.o_mail_table_styles {
width: 100% !important;
}
.o_mail_col_container {
margin: 0px 0px 10px 0px !important;
}
}
</style>
</template>
</odoo>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_mail_themes">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/themes/theme_basic.scss"/>
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/themes/theme_default.scss"/>
<t t-call="mass_mailing.mass_mailing_mail_style"/>
</template>
<template id="assets_mail_themes_edition"> <!-- maybe to remove and convert into a field dumy with attr invisible if the template is not selected -->
<t t-call="web._assets_helpers">
<link rel="stylesheet" type="text/scss" href="/web_editor/static/src/scss/wysiwyg_variables.scss"/>
</t>
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.ui.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/webclient.scss"/>
</template>
<template id="iframe_css_assets_edit">
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<t t-call-assets="web_editor.assets_wysiwyg" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes_edition" t-js="false"/>
</template>
<template id="iframe_css_assets_readonly">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/css/basic_theme_readonly.css"/>
</template>
<template id="qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="." position="inside">
<script type="text/javascript">
odoo.define('mass_mailing.FieldHtml.test', function (require) {
'use strict';
var MassMailingFieldHtml = require('mass_mailing.FieldHtml');
MassMailingFieldHtml.include({jsLibs: []});
});
</script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_snippets.js"/>
<script type="text/javascript" src="/mass_mailing/static/tests/mass_mailing_html_tests.js"/>
</xpath>
</template>
</odoo>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<menuitem id="mail_blacklist_mm_menu" name="Blacklist" action="mail.mail_blacklist_action" parent="mass_mailing_configuration"/>
</odoo>
@@ -0,0 +1,267 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MAILING CONTACT -->
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_view_form">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.view.form</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<form string="Mailing List contacts">
<sheet>
<group>
<field name="list_id"/>
<field name="is_blacklisted" invisible="1"/>
<label for="contact_id" class="oe_inline"/>
<div class="o_row o_row_readonly">
<i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}" groups="base.group_user"></i>
<field name="contact_id"/>
</div>
<field name="unsubscription_date" readonly="1"/>
<field name="opt_out"/>
<field name="message_bounce" readonly="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_list_contact_view_tree">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.list_contact.view.tree</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="arch" type="xml">
<tree string="Mailing Lists">
<field name="contact_id"/>
<field name="unsubscription_date"/>
<field name="opt_out"/>
<field name="message_bounce"/>
<field name="is_blacklisted"/>
<button name="action_open_mailing_list_contact" string="Open Contact" type="object" icon="fa-user"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_view_search">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.view.search</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="arch" type="xml">
<search string="Mailing List Contacts">
<field name="contact_id"/>
<field name="opt_out"/>
<field name="list_id"/>
<separator/>
<filter string="Valid Recipients" name="valid_recipients" domain="[('opt_out', '=', False), ('contact_id.is_blacklisted', '=', False), ('contact_id.is_email_valid', '=', True)]"/>
<separator/>
<filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
<separator/>
<filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('contact_id.is_blacklisted', '=', False)]"/>
<separator/>
<filter string="Exclude Invalid Emails" name="valid_email" domain="[('contact_id.is_email_valid', '=', True)]"/>
<separator/>
<filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
<group expand="0" string="Group By">
<filter string="Creation Date" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_search">
<field name="name">mail.mass_mailing.contact.search</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml">
<search string="Mailing List Contacts">
<field name="name" filter_domain="['|','|',('name','ilike',self),('company_name','ilike',self), ('email','ilike',self)]"
string="Name / Email"/>
<field name="tag_ids"/>
<field name="list_ids"/>
<separator/>
<filter string="Valid Recipients" name="valid_recipients" domain="[('opt_out', '=', False), ('is_blacklisted', '=', False), ('is_email_valid', '=', True)]"
invisible="'default_list_ids' not in context"/>
<separator/>
<filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('is_blacklisted', '=', False)]"/>
<separator/>
<filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"
invisible="'default_list_ids' not in context"/>
<separator/>
<separator/>
<filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
<group expand="0" string="Group By">
<filter string="Creation Date" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_tree">
<field name="name">mail.mass_mailing.contact.tree</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Mailing List Contacts">
<field name="create_date"/>
<field name="name"/>
<field name="company_name"/>
<field name="email"/>
<field name="message_bounce"/>
<field name="opt_out" invisible="'default_list_ids' not in context"/>
<field name="is_blacklisted"/>
</tree>
</field>
</record>
<record id="view_mail_mass_mailing_contact_kanban" model="ir.ui.view">
<field name="name">mail.mass_mailing.contact.kanban</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<field name="company_name"/>
<field name="email"/>
<field name="message_bounce"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<t t-esc="record.name.value"/>
</strong>
</div>
<span class="badge badge-pill" title="Number of bounced email.">
<i class="fa fa-exclamation-triangle" role="img" aria-label="Warning" title="Warning"/> <t t-esc="record.message_bounce.value" title=""/>
</span>
</div>
<div class="o_kanban_record_body">
<field name="tag_ids"/>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<strong>
<t t-esc="record.email.value"/>
</strong>
</div>
<div class="oe_kanban_bottom_right">
<t t-esc="record.company_name.value"/>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_form">
<field name="name">mail.mass_mailing.contact.form</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<form string="Mailing List Contacts">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. John Smith"/>
</h1>
<label for="tag_ids" class="oe_edit_only"/>
<div>
<field name="tag_ids" widget="many2many_tags" style="width: 100%%"/>
</div>
</div>
<group>
<group>
<field name="is_blacklisted" invisible="1"/>
<label for="email" class="oe_inline"/>
<div class="o_row o_row_readonly">
<i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}" groups="base.group_user"></i>
<field name="email" widget="email"/>
</div>
<field name="title_id"/>
<field name="company_name"/>
<field name="country_id"/>
</group>
<group>
<field name="create_date" readonly="1"/>
<label for="message_bounce"/>
<div class="o_row">
<field name="message_bounce"/>
<span> times</span>
</div>
</group>
</group>
<field name="subscription_list_ids">
<tree editable="bottom">
<field name="list_id"/>
<field name="unsubscription_date"/>
<field name="opt_out"/>
</tree>
</field>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_pivot">
<field name="name">mail.mass_mailing.contact.pivot</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<pivot string="Mailing List Contacts" stacked="True">
<field name="create_date" type="row"/>
</pivot>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_graph">
<field name="name">mail.mass_mailing.contact.graph</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<graph string="Mailing List Contacts" stacked="True">
<field name="create_date" type="row"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts">
<field name="name">Mailing List Contacts</field>
<field name="res_model">mail.mass_mailing.contact</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="context">{'search_default_valid_recipient': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a contact in your address book
</p><p>
Odoo helps you to easily track all activities related to a customer.
</p>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts_from_list">
<field name="name">Recipients</field>
<field name="res_model">mail.mass_mailing.contact</field>
<field name="view_mode">tree,form,graph</field>
<field name="domain">[('list_ids', 'in', [active_id])]</field>
<field name="context">{'search_default_valid_recipients': 1, 'default_list_ids': [active_id]}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new recipient
</p>
</field>
</record>
<menuitem name="Mailing List Contacts" id="menu_email_mass_mailing_contacts"
parent="mass_mailing_mailing_list_menu" sequence="4"
action="action_view_mass_mailing_contacts"/>
</odoo>
@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MAILING LIST -->
<record model="ir.ui.view" id="view_mail_mass_mailing_list_search">
<field name="name">mail.mass_mailing.list.search</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<search string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
<field name="is_public"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
<group expand="0" string="Group By">
<filter string="Creation Period" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_tree">
<field name="name">mail.mass_mailing.list.tree</field>
<field name="model">mail.mass_mailing.list</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
<field name="is_public"/>
<field name="contact_nbr"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_form">
<field name="name">mail.mass_mailing.list.form</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<form string="Contact List">
<sheet>
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_button_box" name="button_box">
<button name="%(mass_mailing.action_view_mass_mailing_contacts_from_list)d"
type="action" icon="fa-user" class="oe_stat_button">
<field name="contact_nbr" string="Recipients" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Consumer Newsletter"/>
</h1>
</div>
<group>
<field name="active" invisible="1"/>
<field name="is_public"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_mail_mass_mailing_list_form_simplified" model="ir.ui.view">
<field name="name">mail.mass_mailing.list.form.simplified</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<form string="Contact List">
<group>
<group>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Consumer Newsletter"/>
</h1>
</div>
</group>
</group>
<group>
<field name="is_public"/>
</group>
<footer>
<button string="Create" name="close_dialog" type="object" class="btn-primary"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="open_create_mass_mailing_list" model="ir.actions.act_window">
<field name="name">Create a Mass Mailing List</field>
<field name="res_model">mail.mass_mailing.list</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_mail_mass_mailing_list_form_simplified"/>
<field name="target">new</field>
</record>
<record id="view_mail_mass_mailing_list_kanban" model="ir.ui.view">
<field name="name">mail.mass_mailing.list.kanban</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<kanban js_class="mass_mailing_list_kanban" class="o_kanban_mobile" on_create="mass_mailing.open_create_mass_mailing_list">
<field name="name"/>
<field name="contact_nbr"/>
<field name="active"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_mass_mailing_kanban_main">
<div class="o_kanban_card_content">
<div class="o_kanban_primary_left">
<div class="o_primary">
<span><t t-esc="record.name.value"/></span>
</div>
</div>
</div>
<div class="o_kanban_card_manage_pane">
<div class="o_kanban_card_manage_section o_dropdown_kanban dropdown">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a role="menuitem" class="dropdown-item o_kanban_mailing_active" name="toggle_active" type="object">
<t t-if="record.active.raw_value">Archive</t>
<t t-if="!record.active.raw_value">Restore</t>
</a>
<ul class="oe_kanban_colorpicker" data-field="color"/>
</div>
</div>
</div>
</div>
<div class="o_mailing_list_kanban_boxes">
<a class="o_project_kanban_box" name="%(action_view_mass_mailing_contacts_from_list)d" type="action">
<div>
<span class="badge badge-pill">
<i class="fa fa-user" role="img" aria-label="Contacts" title="Contacts"/>
<t t-esc="record.contact_nbr.value"/>
</span>
</div>
</a>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_lists">
<field name="name">Mailing Lists</field>
<field name="res_model">mail.mass_mailing.list</field>
<field name="view_mode">kanban,tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new mailing list
</p><p>
You don't need to import your mailing lists, you can easily
send emails<br/> to any contact saved in other Odoo apps.
</p>
</field>
</record>
<menuitem name="Mailing Lists" id="menu_email_mass_mailing_lists"
parent="mass_mailing_mailing_list_menu" sequence="3"
action="action_view_mass_mailing_lists"/>
</odoo>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="mass_mailing assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.scss"/>
<link rel="stylesheet" href="/mass_mailing/static/src/css/email_template.css"/>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_widget.js"></script>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_record.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_renderer.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_view.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/unsubscribe.js"></script>
</xpath>
</template>
</odoo>
@@ -1,435 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MAILING CONTACT -->
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_view_form">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.view.form</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<form string="Mailing List contacts">
<sheet>
<group>
<field name="list_id"/>
<field name="is_blacklisted" invisible="1"/>
<label for="contact_id" class="oe_inline"/>
<div class="o_row o_row_readonly">
<i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}" groups="base.group_user"></i>
<field name="contact_id"/>
</div>
<field name="unsubscription_date" readonly="1"/>
<field name="opt_out"/>
<field name="message_bounce" readonly="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_list_contact_view_tree">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.list_contact.view.tree</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="arch" type="xml">
<tree string="Mailing Lists">
<field name="contact_id"/>
<field name="unsubscription_date"/>
<field name="opt_out"/>
<field name="message_bounce"/>
<field name="is_blacklisted"/>
<button name="action_open_mailing_list_contact" string="Open Contact" type="object" icon="fa-user"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="mail_mass_mailing_list_contact_view_search">
<!--TODO DBE : To remove - Deprecated -->
<field name="name">mail.mass_mailing.list_contact_rel.view.search</field>
<field name="model">mail.mass_mailing.list_contact_rel</field>
<field name="arch" type="xml">
<search string="Mailing List Contacts">
<field name="contact_id"/>
<field name="opt_out"/>
<field name="list_id"/>
<separator/>
<filter string="Valid Recipients" name="valid_recipients" domain="[('opt_out', '=', False), ('contact_id.is_blacklisted', '=', False), ('contact_id.is_email_valid', '=', True)]"/>
<separator/>
<filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
<separator/>
<filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('contact_id.is_blacklisted', '=', False)]"/>
<separator/>
<filter string="Exclude Invalid Emails" name="valid_email" domain="[('contact_id.is_email_valid', '=', True)]"/>
<separator/>
<filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
<group expand="0" string="Group By">
<filter string="Creation Date" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_search">
<field name="name">mail.mass_mailing.contact.search</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml">
<search string="Mailing List Contacts">
<field name="name" filter_domain="['|','|',('name','ilike',self),('company_name','ilike',self), ('email','ilike',self)]"
string="Name / Email"/>
<field name="tag_ids"/>
<field name="list_ids"/>
<separator/>
<filter string="Valid Recipients" name="valid_recipients" domain="[('opt_out', '=', False), ('is_blacklisted', '=', False), ('is_email_valid', '=', True)]"
invisible="'default_list_ids' not in context"/>
<separator/>
<filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('is_blacklisted', '=', False)]"/>
<separator/>
<filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"
invisible="'default_list_ids' not in context"/>
<separator/>
<separator/>
<filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
<group expand="0" string="Group By">
<filter string="Creation Date" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_tree">
<field name="name">mail.mass_mailing.contact.tree</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Mailing List Contacts">
<field name="create_date"/>
<field name="name"/>
<field name="company_name"/>
<field name="email"/>
<field name="message_bounce"/>
<field name="opt_out" invisible="'default_list_ids' not in context"/>
<field name="is_blacklisted"/>
</tree>
</field>
</record>
<record id="view_mail_mass_mailing_contact_kanban" model="ir.ui.view">
<field name="name">mail.mass_mailing.contact.kanban</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<field name="company_name"/>
<field name="email"/>
<field name="message_bounce"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<t t-esc="record.name.value"/>
</strong>
</div>
<span class="badge badge-pill" title="Number of bounced email.">
<i class="fa fa-exclamation-triangle" role="img" aria-label="Warning" title="Warning"/> <t t-esc="record.message_bounce.value" title=""/>
</span>
</div>
<div class="o_kanban_record_body">
<field name="tag_ids"/>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<strong>
<t t-esc="record.email.value"/>
</strong>
</div>
<div class="oe_kanban_bottom_right">
<t t-esc="record.company_name.value"/>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_form">
<field name="name">mail.mass_mailing.contact.form</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<form string="Mailing List Contacts">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. John Smith"/>
</h1>
<label for="tag_ids" class="oe_edit_only"/>
<div>
<field name="tag_ids" widget="many2many_tags" style="width: 100%%"/>
</div>
</div>
<group>
<group>
<field name="is_blacklisted" invisible="1"/>
<label for="email" class="oe_inline"/>
<div class="o_row o_row_readonly">
<i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}" groups="base.group_user"></i>
<field name="email" widget="email"/>
</div>
<field name="title_id"/>
<field name="company_name"/>
<field name="country_id"/>
</group>
<group>
<field name="create_date" readonly="1"/>
<label for="message_bounce"/>
<div class="o_row">
<field name="message_bounce"/>
<span> times</span>
</div>
</group>
</group>
<field name="subscription_list_ids">
<tree editable="bottom">
<field name="list_id"/>
<field name="unsubscription_date"/>
<field name="opt_out"/>
</tree>
</field>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_pivot">
<field name="name">mail.mass_mailing.contact.pivot</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<pivot string="Mailing List Contacts" stacked="True">
<field name="create_date" type="row"/>
</pivot>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_graph">
<field name="name">mail.mass_mailing.contact.graph</field>
<field name="model">mail.mass_mailing.contact</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<graph string="Mailing List Contacts" stacked="True">
<field name="create_date" type="row"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts">
<field name="name">Mailing List Contacts</field>
<field name="res_model">mail.mass_mailing.contact</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="context">{'search_default_valid_recipient': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a contact in your address book
</p><p>
Odoo helps you to easily track all activities related to a customer.
</p>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts_from_list">
<field name="name">Recipients</field>
<field name="res_model">mail.mass_mailing.contact</field>
<field name="view_mode">tree,form,graph</field>
<field name="domain">[('list_ids', 'in', [active_id])]</field>
<field name="context">{'search_default_valid_recipients': 1, 'default_list_ids': [active_id]}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new recipient
</p>
</field>
</record>
<menuitem name="Mailing List Contacts" id="menu_email_mass_mailing_contacts"
parent="mass_mailing_mailing_list_menu" sequence="4"
action="action_view_mass_mailing_contacts"/>
<!-- MAILING LIST -->
<record model="ir.ui.view" id="view_mail_mass_mailing_list_search">
<field name="name">mail.mass_mailing.list.search</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<search string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
<field name="is_public"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
<group expand="0" string="Group By">
<filter string="Creation Period" name="group_create_date"
context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_tree">
<field name="name">mail.mass_mailing.list.tree</field>
<field name="model">mail.mass_mailing.list</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
<field name="is_public"/>
<field name="contact_nbr"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_list_form">
<field name="name">mail.mass_mailing.list.form</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<form string="Contact List">
<sheet>
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_button_box" name="button_box">
<button name="%(mass_mailing.action_view_mass_mailing_contacts_from_list)d"
type="action" icon="fa-user" class="oe_stat_button">
<field name="contact_nbr" string="Recipients" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Consumer Newsletter"/>
</h1>
</div>
<group>
<field name="active" invisible="1"/>
<field name="is_public"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_mail_mass_mailing_list_form_simplified" model="ir.ui.view">
<field name="name">mail.mass_mailing.list.form.simplified</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<form string="Contact List">
<group>
<group>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Consumer Newsletter"/>
</h1>
</div>
</group>
</group>
<group>
<field name="is_public"/>
</group>
<footer>
<button string="Create" name="close_dialog" type="object" class="btn-primary"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="open_create_mass_mailing_list" model="ir.actions.act_window">
<field name="name">Create a Mass Mailing List</field>
<field name="res_model">mail.mass_mailing.list</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_mail_mass_mailing_list_form_simplified"/>
<field name="target">new</field>
</record>
<record id="view_mail_mass_mailing_list_kanban" model="ir.ui.view">
<field name="name">mail.mass_mailing.list.kanban</field>
<field name="model">mail.mass_mailing.list</field>
<field name="arch" type="xml">
<kanban js_class="mass_mailing_list_kanban" class="o_kanban_mobile" on_create="mass_mailing.open_create_mass_mailing_list">
<field name="name"/>
<field name="contact_nbr"/>
<field name="active"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_mass_mailing_kanban_main">
<div class="o_kanban_card_content">
<div class="o_kanban_primary_left">
<div class="o_primary">
<span><t t-esc="record.name.value"/></span>
</div>
</div>
</div>
<div class="o_kanban_card_manage_pane">
<div class="o_kanban_card_manage_section o_dropdown_kanban dropdown">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a role="menuitem" class="dropdown-item o_kanban_mailing_active" name="toggle_active" type="object">
<t t-if="record.active.raw_value">Archive</t>
<t t-if="!record.active.raw_value">Restore</t>
</a>
<ul class="oe_kanban_colorpicker" data-field="color"/>
</div>
</div>
</div>
</div>
<div class="o_mailing_list_kanban_boxes">
<a class="o_project_kanban_box" name="%(action_view_mass_mailing_contacts_from_list)d" type="action">
<div>
<span class="badge badge-pill">
<i class="fa fa-user" role="img" aria-label="Contacts" title="Contacts"/>
<t t-esc="record.contact_nbr.value"/>
</span>
</div>
</a>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_lists">
<field name="name">Mailing Lists</field>
<field name="res_model">mail.mass_mailing.list</field>
<field name="view_mode">kanban,tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new mailing list
</p><p>
You don't need to import your mailing lists, you can easily
send emails<br/> to any contact saved in other Odoo apps.
</p>
</field>
</record>
<menuitem name="Mailing Lists" id="menu_email_mass_mailing_lists"
parent="mass_mailing_mailing_list_menu" sequence="3"
action="action_view_mass_mailing_lists"/>
<!-- MAILING !-->
<record model="ir.ui.view" id="view_mail_mass_mailing_search">
<field name="name">mail.mass_mailing.search</field>
@@ -4,9 +4,15 @@
<menuitem name="Email Marketing" id="mass_mailing_menu_root" sequence="60" web_icon="mass_mailing,static/description/icon.png"/>
<menuitem name="Mailing Lists" id="mass_mailing_mailing_list_menu"
parent="mass_mailing_menu_root" sequence="2" groups="mass_mailing.group_mass_mailing_user"/>
<!-- Marketing / Configuration -->
<menuitem name="Configuration" id="mass_mailing_configuration"
parent="mass_mailing_menu_root"
sequence="100"
groups="mass_mailing.group_mass_mailing_user"/>
<!-- Configuration / Blacklist -->
<menuitem id="mail_blacklist_mm_menu" name="Blacklist"
action="mail.mail_blacklist_action"
parent="mass_mailing_configuration"/>
</odoo>
@@ -769,4 +769,52 @@
</div>
</div>
</template>
<!-- Snippet themes Options -->
<template id="snippet_options">
<t t-call="web_editor.snippet_options"/>
<t t-raw="0"/>
<div data-js="mass_mailing_sizing_x"
data-selector="img, .mv, .col_mv, td, th"
data-exclude=".o_mail_no_resize, .o_mail_no_options"/>
<div data-js="mass_mailing_table_item"
data-selector="td, th"
data-exclude=".o_mail_no_options"/>
<div data-js="table_row"
data-selector="tr:has(> .row), tr:has(> .col_mv)"
data-exclude=".o_mail_no_options"
data-drop-near="tr:has(> .row), tr:has(> .col_mv)"/>
<div data-js="table_column"
data-selector=".col>td, .col>th"
data-exclude=".o_mail_no_options"
data-drop-near=".col>td, .col>th"/>
<div data-js="table_column_mv"
data-selector=".col_mv, td, th"
data-exclude=".o_mail_no_options"
data-drop-near=".col_mv, td, th"/>
<div data-js="content"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, .oe_snippet_body"
data-exclude=".o_mail_no_options"
data-drop-near="[data-oe-field='body_html']:not(:has(.o_layout)) > *, .oe_structure > *"
data-drop-in="[data-oe-field='body_html']:not(:has(.o_layout)), .oe_structure"/>
<div data-js="sizing_y"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
data-exclude=".o_mail_no_resize, .o_mail_no_options"/>
<div data-js="colorpicker"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
data-exclude=".o_mail_no_colorpicker, .o_mail_no_options">
<div class="dropdown-submenu">
<a tabindex="-1" href="#" class="dropdown-item"><i class="fa fa-eyedropper"/>Background Color</a>
<div class="dropdown-menu" role="menu"/>
</div>
</div>
</template>
</odoo>
@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="snippet_options">
<t t-call="web_editor.snippet_options"/>
<t t-raw="0"/>
<div data-js="mass_mailing_sizing_x"
data-selector="img, .mv, .col_mv, td, th"
data-exclude=".o_mail_no_resize, .o_mail_no_options"/>
<div data-js="mass_mailing_table_item"
data-selector="td, th"
data-exclude=".o_mail_no_options"/>
<div data-js="table_row"
data-selector="tr:has(> .row), tr:has(> .col_mv)"
data-exclude=".o_mail_no_options"
data-drop-near="tr:has(> .row), tr:has(> .col_mv)"/>
<div data-js="table_column"
data-selector=".col>td, .col>th"
data-exclude=".o_mail_no_options"
data-drop-near=".col>td, .col>th"/>
<div data-js="table_column_mv"
data-selector=".col_mv, td, th"
data-exclude=".o_mail_no_options"
data-drop-near=".col_mv, td, th"/>
<div data-js="content"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, .oe_snippet_body"
data-exclude=".o_mail_no_options"
data-drop-near="[data-oe-field='body_html']:not(:has(.o_layout)) > *, .oe_structure > *"
data-drop-in="[data-oe-field='body_html']:not(:has(.o_layout)), .oe_structure"/>
<div data-js="sizing_y"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
data-exclude=".o_mail_no_resize, .o_mail_no_options"/>
<div data-js="colorpicker"
data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th"
data-exclude=".o_mail_no_colorpicker, .o_mail_no_options">
<div class="dropdown-submenu">
<a tabindex="-1" href="#" class="dropdown-item"><i class="fa fa-eyedropper"/>Background Color</a>
<div class="dropdown-menu" role="menu"/>
</div>
</div>
</template>
</odoo>